From 9a0c8e8f972e4f08be1ce4a4f6be42c22df71a64 Mon Sep 17 00:00:00 2001 From: borys3kk Date: Wed, 28 Jan 2026 18:09:55 +0100 Subject: [PATCH 1/4] make useChartInteractions react compiler compliant --- .../Charts/hooks/useChartInteractions.ts | 105 ++++++++++-------- 1 file changed, 57 insertions(+), 48 deletions(-) diff --git a/src/components/Charts/hooks/useChartInteractions.ts b/src/components/Charts/hooks/useChartInteractions.ts index e7a5422f5880..cca1b43a3bb1 100644 --- a/src/components/Charts/hooks/useChartInteractions.ts +++ b/src/components/Charts/hooks/useChartInteractions.ts @@ -1,4 +1,4 @@ -import {useMemo, useRef, useState} from 'react'; +import {useRef, useState} from 'react'; import {Gesture} from 'react-native-gesture-handler'; import type {SharedValue} from 'react-native-reanimated'; import {useAnimatedReaction, useAnimatedStyle, useDerivedValue} from 'react-native-reanimated'; @@ -6,6 +6,8 @@ import {scheduleOnRN} from 'react-native-worklets'; import {TOOLTIP_BAR_GAP} from '@components/Charts/constants'; import {useChartInteractionState} from './useChartInteractionState'; +const INITIAL_INTERACTION_STATE = {x: 0, y: {y: 0}}; + /** * Arguments passed to the checkIsOver callback for hit-testing */ @@ -76,11 +78,15 @@ type CartesianActionsHandle = { */ function useChartInteractions({handlePress, checkIsOver, barGeometry}: UseChartInteractionsProps) { /** Interaction state compatible with Victory Native's internal logic */ - const {state: chartInteractionState, isActive: isTooltipActiveState} = useChartInteractionState({x: 0, y: {y: 0}}); + const {state: chartInteractionState, isActive: isTooltipActiveState} = useChartInteractionState(INITIAL_INTERACTION_STATE); /** Ref passed to CartesianChart to allow manual touch injection */ const actionsRef = useRef(null); + /** To allow for react compiler compliance we mustn't access actionsRef directly */ + const handleTouchWorklet = useDerivedValue(() => { + return actionsRef.current?.handleTouch; + }); /** React state for the index of the point currently being interacted with */ const [activeDataIndex, setActiveDataIndex] = useState(-1); @@ -128,59 +134,62 @@ function useChartInteractions({handlePress, checkIsOver, barGeometry}: UseChartI * Hover gesture configuration. * Primarily used for web/desktop to track mouse movement without clicking. */ - const hoverGesture = useMemo( - () => - Gesture.Hover() - .onBegin((e) => { - 'worklet'; - - chartInteractionState.isActive.set(true); - chartInteractionState.cursor.x.set(e.x); - chartInteractionState.cursor.y.set(e.y); - actionsRef.current?.handleTouch(chartInteractionState, e.x, e.y); - }) - .onUpdate((e) => { - 'worklet'; - - chartInteractionState.cursor.x.set(e.x); - chartInteractionState.cursor.y.set(e.y); - actionsRef.current?.handleTouch(chartInteractionState, e.x, e.y); - }) - .onEnd(() => { - 'worklet'; - - chartInteractionState.isActive.set(false); - }), - [chartInteractionState], - ); + const hoverGesture = Gesture.Hover() + .onBegin((e) => { + 'worklet'; + + chartInteractionState.isActive.set(true); + chartInteractionState.cursor.x.set(e.x); + chartInteractionState.cursor.y.set(e.y); + + const touchFn = handleTouchWorklet.get(); + if (touchFn) { + touchFn(chartInteractionState, e.x, e.y); + } + }) + .onUpdate((e) => { + 'worklet'; + + chartInteractionState.cursor.x.set(e.x); + chartInteractionState.cursor.y.set(e.y); + + const touchFn = handleTouchWorklet.get(); + if (touchFn) { + touchFn(chartInteractionState, e.x, e.y); + } + }) + .onEnd(() => { + 'worklet'; + + chartInteractionState.isActive.set(false); + }); /** * Tap gesture configuration. * Handles clicks/touches and triggers handlePress if Victory matched a data point. */ - const tapGesture = useMemo( - () => - Gesture.Tap().onEnd((e) => { - 'worklet'; - - // Update cursor position - chartInteractionState.cursor.x.set(e.x); - chartInteractionState.cursor.y.set(e.y); - - // Let Victory calculate which data point was tapped - actionsRef.current?.handleTouch(chartInteractionState, e.x, e.y); - const matchedIndex = chartInteractionState.matchedIndex.get(); - - // If Victory matched a valid data point, trigger the press handler - if (matchedIndex >= 0) { - scheduleOnRN(handlePress, matchedIndex); - } - }), - [chartInteractionState, handlePress], - ); + + const tapGesture = Gesture.Tap().onEnd((e) => { + 'worklet'; + + // Update cursor position + chartInteractionState.cursor.x.set(e.x); + chartInteractionState.cursor.y.set(e.y); + + // Let Victory calculate which data point was tapped + const touchFn = handleTouchWorklet.get(); + if (touchFn) { + touchFn(chartInteractionState, e.x, e.y); + } + + const matchedIndex = chartInteractionState.matchedIndex.get(); + if (matchedIndex >= 0 && isCursorOverTarget.get()) { + scheduleOnRN(handlePress, matchedIndex); + } + }); /** Combined gesture object to be passed to CartesianChart's customGestures prop */ - const customGestures = useMemo(() => Gesture.Race(hoverGesture, tapGesture), [hoverGesture, tapGesture]); + const customGestures = Gesture.Race(hoverGesture, tapGesture); /** * Animated style for positioning a tooltip relative to the matched data point. From 2ace62a9892b3191996dc6254caa8087ce9ed9cb Mon Sep 17 00:00:00 2001 From: borys3kk Date: Thu, 29 Jan 2026 14:07:00 +0100 Subject: [PATCH 2/4] add auto completion for view key --- src/CONST/index.ts | 1 + src/components/Search/SearchAutocompleteList.tsx | 11 +++++++++++ src/components/Search/types.ts | 1 + src/libs/SearchParser/autocompleteParser.peggy | 1 + src/libs/SearchParser/searchParser.peggy | 1 + 5 files changed, 15 insertions(+) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 7ae8c55a3ea1..159f42b476cd 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -7276,6 +7276,7 @@ const CONST = { REPORT_FIELD: 'report-field', COLUMNS: 'columns', LIMIT: 'limit', + VIEW: 'view', }, get SEARCH_USER_FRIENDLY_VALUES_MAP() { return { diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index d6c4f88b1871..04775baac97c 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -322,6 +322,10 @@ function SearchAutocompleteList({ }, [allPoliciesTags]); const recentTagsAutocompleteList = useMemo(() => getAutocompleteRecentTags(allRecentTags), [allRecentTags]); + const viewAutocompleteList = useMemo(() => { + return Object.values(CONST.SEARCH.VIEW).map((value) => getUserFriendlyValue(value)); + }, []); + const [autocompleteParsedQuery, autocompleteQueryWithoutFilters] = useMemo(() => { const queryWithoutFilters = getQueryWithoutFilters(autocompleteQueryValue); return [parsedQuery, queryWithoutFilters]; @@ -495,6 +499,12 @@ function SearchAutocompleteList({ ); return filteredGroupBy.map((groupByValue) => ({filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.GROUP_BY, text: groupByValue})); } + case CONST.SEARCH.SYNTAX_ROOT_KEYS.VIEW: { + const filteredView = viewAutocompleteList.filter( + (viewValue) => viewValue.toLowerCase().includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedKeys.has(viewValue.toLowerCase()), + ); + return filteredView.map((viewValue) => ({filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.VIEW, text: viewValue})); + } case CONST.SEARCH.SYNTAX_ROOT_KEYS.STATUS: { const filteredStatuses = statusAutocompleteList .filter((status) => status.includes(autocompleteValue.toLowerCase()) && !alreadyAutocompletedKeys.has(status)) @@ -642,6 +652,7 @@ function SearchAutocompleteList({ statusAutocompleteList, feedAutoCompleteList, cardAutocompleteList, + viewAutocompleteList, translate, workspaceList, hasAutocompleteList, diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 5b7cca7cffed..5fe1c45c636f 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -236,6 +236,7 @@ type SearchFilterKey = | typeof CONST.SEARCH.SYNTAX_ROOT_KEYS.TYPE | typeof CONST.SEARCH.SYNTAX_ROOT_KEYS.STATUS | typeof CONST.SEARCH.SYNTAX_ROOT_KEYS.GROUP_BY + | typeof CONST.SEARCH.SYNTAX_ROOT_KEYS.VIEW | typeof CONST.SEARCH.SYNTAX_ROOT_KEYS.COLUMNS | typeof CONST.SEARCH.SYNTAX_ROOT_KEYS.LIMIT; diff --git a/src/libs/SearchParser/autocompleteParser.peggy b/src/libs/SearchParser/autocompleteParser.peggy index a50efdbcdbdd..b86365560c0a 100644 --- a/src/libs/SearchParser/autocompleteParser.peggy +++ b/src/libs/SearchParser/autocompleteParser.peggy @@ -120,6 +120,7 @@ autocompleteKey "key" / reportFieldDynamic / columns / limit + / view ) filterKey diff --git a/src/libs/SearchParser/searchParser.peggy b/src/libs/SearchParser/searchParser.peggy index c09a6d97c0ad..4cbb8729a360 100644 --- a/src/libs/SearchParser/searchParser.peggy +++ b/src/libs/SearchParser/searchParser.peggy @@ -263,6 +263,7 @@ key "key" / purchaseCurrency / purchaseAmount / reportFieldDynamic + / view ) filterKey From fdc19a02c5b025e9ef63c0150fc858c3cac7a46f Mon Sep 17 00:00:00 2001 From: borys3kk Date: Thu, 29 Jan 2026 14:14:03 +0100 Subject: [PATCH 3/4] restore previous useChartInteractios --- .../Charts/hooks/useChartInteractions.ts | 105 ++++++++---------- 1 file changed, 48 insertions(+), 57 deletions(-) diff --git a/src/components/Charts/hooks/useChartInteractions.ts b/src/components/Charts/hooks/useChartInteractions.ts index cca1b43a3bb1..e7a5422f5880 100644 --- a/src/components/Charts/hooks/useChartInteractions.ts +++ b/src/components/Charts/hooks/useChartInteractions.ts @@ -1,4 +1,4 @@ -import {useRef, useState} from 'react'; +import {useMemo, useRef, useState} from 'react'; import {Gesture} from 'react-native-gesture-handler'; import type {SharedValue} from 'react-native-reanimated'; import {useAnimatedReaction, useAnimatedStyle, useDerivedValue} from 'react-native-reanimated'; @@ -6,8 +6,6 @@ import {scheduleOnRN} from 'react-native-worklets'; import {TOOLTIP_BAR_GAP} from '@components/Charts/constants'; import {useChartInteractionState} from './useChartInteractionState'; -const INITIAL_INTERACTION_STATE = {x: 0, y: {y: 0}}; - /** * Arguments passed to the checkIsOver callback for hit-testing */ @@ -78,15 +76,11 @@ type CartesianActionsHandle = { */ function useChartInteractions({handlePress, checkIsOver, barGeometry}: UseChartInteractionsProps) { /** Interaction state compatible with Victory Native's internal logic */ - const {state: chartInteractionState, isActive: isTooltipActiveState} = useChartInteractionState(INITIAL_INTERACTION_STATE); + const {state: chartInteractionState, isActive: isTooltipActiveState} = useChartInteractionState({x: 0, y: {y: 0}}); /** Ref passed to CartesianChart to allow manual touch injection */ const actionsRef = useRef(null); - /** To allow for react compiler compliance we mustn't access actionsRef directly */ - const handleTouchWorklet = useDerivedValue(() => { - return actionsRef.current?.handleTouch; - }); /** React state for the index of the point currently being interacted with */ const [activeDataIndex, setActiveDataIndex] = useState(-1); @@ -134,62 +128,59 @@ function useChartInteractions({handlePress, checkIsOver, barGeometry}: UseChartI * Hover gesture configuration. * Primarily used for web/desktop to track mouse movement without clicking. */ - const hoverGesture = Gesture.Hover() - .onBegin((e) => { - 'worklet'; - - chartInteractionState.isActive.set(true); - chartInteractionState.cursor.x.set(e.x); - chartInteractionState.cursor.y.set(e.y); - - const touchFn = handleTouchWorklet.get(); - if (touchFn) { - touchFn(chartInteractionState, e.x, e.y); - } - }) - .onUpdate((e) => { - 'worklet'; - - chartInteractionState.cursor.x.set(e.x); - chartInteractionState.cursor.y.set(e.y); - - const touchFn = handleTouchWorklet.get(); - if (touchFn) { - touchFn(chartInteractionState, e.x, e.y); - } - }) - .onEnd(() => { - 'worklet'; - - chartInteractionState.isActive.set(false); - }); + const hoverGesture = useMemo( + () => + Gesture.Hover() + .onBegin((e) => { + 'worklet'; + + chartInteractionState.isActive.set(true); + chartInteractionState.cursor.x.set(e.x); + chartInteractionState.cursor.y.set(e.y); + actionsRef.current?.handleTouch(chartInteractionState, e.x, e.y); + }) + .onUpdate((e) => { + 'worklet'; + + chartInteractionState.cursor.x.set(e.x); + chartInteractionState.cursor.y.set(e.y); + actionsRef.current?.handleTouch(chartInteractionState, e.x, e.y); + }) + .onEnd(() => { + 'worklet'; + + chartInteractionState.isActive.set(false); + }), + [chartInteractionState], + ); /** * Tap gesture configuration. * Handles clicks/touches and triggers handlePress if Victory matched a data point. */ - - const tapGesture = Gesture.Tap().onEnd((e) => { - 'worklet'; - - // Update cursor position - chartInteractionState.cursor.x.set(e.x); - chartInteractionState.cursor.y.set(e.y); - - // Let Victory calculate which data point was tapped - const touchFn = handleTouchWorklet.get(); - if (touchFn) { - touchFn(chartInteractionState, e.x, e.y); - } - - const matchedIndex = chartInteractionState.matchedIndex.get(); - if (matchedIndex >= 0 && isCursorOverTarget.get()) { - scheduleOnRN(handlePress, matchedIndex); - } - }); + const tapGesture = useMemo( + () => + Gesture.Tap().onEnd((e) => { + 'worklet'; + + // Update cursor position + chartInteractionState.cursor.x.set(e.x); + chartInteractionState.cursor.y.set(e.y); + + // Let Victory calculate which data point was tapped + actionsRef.current?.handleTouch(chartInteractionState, e.x, e.y); + const matchedIndex = chartInteractionState.matchedIndex.get(); + + // If Victory matched a valid data point, trigger the press handler + if (matchedIndex >= 0) { + scheduleOnRN(handlePress, matchedIndex); + } + }), + [chartInteractionState, handlePress], + ); /** Combined gesture object to be passed to CartesianChart's customGestures prop */ - const customGestures = Gesture.Race(hoverGesture, tapGesture); + const customGestures = useMemo(() => Gesture.Race(hoverGesture, tapGesture), [hoverGesture, tapGesture]); /** * Animated style for positioning a tooltip relative to the matched data point. From 4100ff36c571d49ac6ed8335836b73723f1147c8 Mon Sep 17 00:00:00 2001 From: borys3kk Date: Thu, 29 Jan 2026 15:21:23 +0100 Subject: [PATCH 4/4] make useChartInteractionState react compiler compliant --- .../Charts/hooks/useChartInteractionState.ts | 49 +++++++++---------- .../Charts/hooks/useChartInteractions.ts | 5 +- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/components/Charts/hooks/useChartInteractionState.ts b/src/components/Charts/hooks/useChartInteractionState.ts index 3a4f957a25c2..4721c87d36b3 100644 --- a/src/components/Charts/hooks/useChartInteractionState.ts +++ b/src/components/Charts/hooks/useChartInteractionState.ts @@ -1,4 +1,4 @@ -import {useMemo, useState} from 'react'; +import {useState} from 'react'; import type {SharedValue} from 'react-native-reanimated'; import {makeMutable, useAnimatedReaction} from 'react-native-reanimated'; import {scheduleOnRN} from 'react-native-worklets'; @@ -96,34 +96,29 @@ function useChartInteractionState( state: ChartInteractionState; isActive: boolean; } { - const keys = Object.keys(initialValues.y).join(','); + const yState = {} as Record; position: SharedValue}>; - const state = useMemo(() => { - const yState = {} as Record; position: SharedValue}>; - - for (const [key, initVal] of Object.entries(initialValues.y)) { - yState[key as keyof Init['y']] = { - value: makeMutable(initVal), - position: makeMutable(0), - }; - } - - return { - isActive: makeMutable(false), - matchedIndex: makeMutable(-1), - x: { - value: makeMutable(initialValues.x), - position: makeMutable(0), - }, - y: yState, - yIndex: makeMutable(-1), - cursor: { - x: makeMutable(0), - y: makeMutable(0), - }, + for (const [key, initVal] of Object.entries(initialValues.y)) { + yState[key as keyof Init['y']] = { + value: makeMutable(initVal), + position: makeMutable(0), }; - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- keys is a stable string representation of y keys - }, [keys]); + } + + const state: ChartInteractionState = { + isActive: makeMutable(false), + matchedIndex: makeMutable(-1), + x: { + value: makeMutable(initialValues.x), + position: makeMutable(0), + }, + y: yState, + yIndex: makeMutable(-1), + cursor: { + x: makeMutable(0), + y: makeMutable(0), + }, + }; const isActive = useIsInteractionActive(state); diff --git a/src/components/Charts/hooks/useChartInteractions.ts b/src/components/Charts/hooks/useChartInteractions.ts index e7a5422f5880..02a4d34e1d9b 100644 --- a/src/components/Charts/hooks/useChartInteractions.ts +++ b/src/components/Charts/hooks/useChartInteractions.ts @@ -6,6 +6,8 @@ import {scheduleOnRN} from 'react-native-worklets'; import {TOOLTIP_BAR_GAP} from '@components/Charts/constants'; import {useChartInteractionState} from './useChartInteractionState'; +const INITIAL_INTERACTION_STATE = {x: 0, y: {y: 0}}; + /** * Arguments passed to the checkIsOver callback for hit-testing */ @@ -76,8 +78,7 @@ type CartesianActionsHandle = { */ function useChartInteractions({handlePress, checkIsOver, barGeometry}: UseChartInteractionsProps) { /** Interaction state compatible with Victory Native's internal logic */ - const {state: chartInteractionState, isActive: isTooltipActiveState} = useChartInteractionState({x: 0, y: {y: 0}}); - + const {state: chartInteractionState, isActive: isTooltipActiveState} = useChartInteractionState(INITIAL_INTERACTION_STATE); /** Ref passed to CartesianChart to allow manual touch injection */ const actionsRef = useRef(null);