@assistant-ui/react 0.14.16 → 0.14.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/ExternalThread.d.ts +4 -3
- package/dist/client/ExternalThread.d.ts.map +1 -1
- package/dist/client/ExternalThread.js +46 -21
- package/dist/client/ExternalThread.js.map +1 -1
- package/dist/client/InMemoryThreadList.d.ts +1 -1
- package/dist/client/InMemoryThreadList.d.ts.map +1 -1
- package/dist/client/InMemoryThreadList.js +7 -5
- package/dist/client/InMemoryThreadList.js.map +1 -1
- package/dist/client/SingleThreadList.d.ts +1 -6
- package/dist/client/SingleThreadList.d.ts.map +1 -1
- package/dist/client/SingleThreadList.js +6 -4
- package/dist/client/SingleThreadList.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/mcp-apps/McpAppRenderer.d.ts +2 -10
- package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.js +3 -2
- package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.d.ts +1 -8
- package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.js +3 -2
- package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +3 -3
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts +2 -10
- package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverResource.js +3 -2
- package/dist/primitives/composer/trigger/TriggerPopoverResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts +2 -6
- package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerDetectionResource.js +3 -2
- package/dist/primitives/composer/trigger/triggerDetectionResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerKeyboardResource.d.ts +2 -17
- package/dist/primitives/composer/trigger/triggerKeyboardResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerKeyboardResource.js +3 -2
- package/dist/primitives/composer/trigger/triggerKeyboardResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts +2 -10
- package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.js +3 -2
- package/dist/primitives/composer/trigger/triggerNavigationResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts +2 -10
- package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.js +3 -2
- package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.d.ts +5 -2
- package/dist/primitives/messagePart/MessagePartText.d.ts.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
- package/dist/primitives/reasoning/useScrollLock.js +11 -2
- package/dist/primitives/reasoning/useScrollLock.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +5 -0
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/unstable/useComposerInputHistory.d.ts +30 -0
- package/dist/unstable/useComposerInputHistory.d.ts.map +1 -0
- package/dist/unstable/useComposerInputHistory.js +117 -0
- package/dist/unstable/useComposerInputHistory.js.map +1 -0
- package/dist/utils/smooth/useSmooth.d.ts +40 -2
- package/dist/utils/smooth/useSmooth.d.ts.map +1 -1
- package/dist/utils/smooth/useSmooth.js +48 -9
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/package.json +4 -4
- package/src/client/ExternalThread.ts +70 -27
- package/src/client/InMemoryThreadList.ts +11 -7
- package/src/client/SingleThreadList.ts +29 -27
- package/src/index.ts +8 -0
- package/src/mcp-apps/McpAppRenderer.tsx +5 -3
- package/src/mcp-apps/McpAppsRemoteHost.ts +5 -3
- package/src/primitives/composer/ComposerInput.test.tsx +1 -1
- package/src/primitives/composer/ComposerInput.tsx +3 -3
- package/src/primitives/composer/trigger/TriggerPopoverResource.ts +5 -3
- package/src/primitives/composer/trigger/triggerDetectionResource.ts +21 -21
- package/src/primitives/composer/trigger/triggerKeyboardResource.test.ts +5 -4
- package/src/primitives/composer/trigger/triggerKeyboardResource.ts +99 -101
- package/src/primitives/composer/trigger/triggerNavigationResource.ts +92 -98
- package/src/primitives/composer/trigger/triggerSelectionResource.ts +76 -76
- package/src/primitives/messagePart/MessagePartText.tsx +3 -2
- package/src/primitives/reasoning/useScrollLock.ts +25 -2
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +8 -0
- package/src/tests/external-thread-branches.test.tsx +160 -0
- package/src/tests/shouldContinue.test.ts +33 -0
- package/src/unstable/useComposerInputHistory.test.tsx +201 -0
- package/src/unstable/useComposerInputHistory.ts +160 -0
- package/src/utils/smooth/useSmooth.test.tsx +95 -0
- package/src/utils/smooth/useSmooth.ts +82 -10
|
@@ -22,7 +22,7 @@ import { useEscapeKeydown } from "@radix-ui/react-use-escape-keydown";
|
|
|
22
22
|
import { useOnScrollToBottom } from "../../utils/hooks/useOnScrollToBottom";
|
|
23
23
|
import { useMediaQuery } from "../../utils/hooks/useMediaQuery";
|
|
24
24
|
import { useAuiState, useAui } from "@assistant-ui/store";
|
|
25
|
-
import {
|
|
25
|
+
import { flushTapSync } from "@assistant-ui/tap";
|
|
26
26
|
import { useComposerInputPluginRegistryOptional } from "./ComposerInputPluginContext";
|
|
27
27
|
import { useTriggerPopoverActiveAriaOptional } from "./trigger/TriggerPopoverRootContext";
|
|
28
28
|
|
|
@@ -350,7 +350,7 @@ export const ComposerPrimitiveInput = forwardRef<
|
|
|
350
350
|
}
|
|
351
351
|
const isComposing = nativeIsComposing || compositionRef.current;
|
|
352
352
|
// keep controlled value in sync mid-IME so react does not reset the textarea to a stale value
|
|
353
|
-
|
|
353
|
+
flushTapSync(() => {
|
|
354
354
|
aui.composer().setText(e.target.value);
|
|
355
355
|
});
|
|
356
356
|
if (isComposing) return;
|
|
@@ -377,7 +377,7 @@ export const ComposerPrimitiveInput = forwardRef<
|
|
|
377
377
|
compositionRef.current = false;
|
|
378
378
|
if (!aui.composer().getState().isEditing) return;
|
|
379
379
|
const target = e.target as HTMLTextAreaElement;
|
|
380
|
-
|
|
380
|
+
flushTapSync(() => {
|
|
381
381
|
aui.composer().setText(target.value);
|
|
382
382
|
});
|
|
383
383
|
const pos = target.selectionStart ?? target.value.length;
|
|
@@ -51,7 +51,7 @@ export type TriggerPopoverResourceOutput = {
|
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
/** Composes detection, navigation, keyboard, and selection sub-resources. */
|
|
54
|
-
|
|
54
|
+
const useTriggerPopoverResource = ({
|
|
55
55
|
adapter,
|
|
56
56
|
text,
|
|
57
57
|
triggerChar,
|
|
@@ -66,7 +66,7 @@ export const TriggerPopoverResource = resource(function TriggerPopoverResource({
|
|
|
66
66
|
aui: AssistantClient;
|
|
67
67
|
/** Stable ID for accessible element IDs (pass React's useId() from component layer). */
|
|
68
68
|
popoverId: string;
|
|
69
|
-
}): TriggerPopoverResourceOutput {
|
|
69
|
+
}): TriggerPopoverResourceOutput => {
|
|
70
70
|
const detection = useResource(
|
|
71
71
|
TriggerDetectionResource({ text, triggerChar }),
|
|
72
72
|
);
|
|
@@ -133,4 +133,6 @@ export const TriggerPopoverResource = resource(function TriggerPopoverResource({
|
|
|
133
133
|
setCursorPosition: detection.setCursorPosition,
|
|
134
134
|
registerSelectItemOverride: selection.registerSelectItemOverride,
|
|
135
135
|
};
|
|
136
|
-
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const TriggerPopoverResource = resource(useTriggerPopoverResource);
|
|
@@ -18,27 +18,27 @@ export type TriggerDetectionResourceOutput = {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
/** Tracks cursor position and derives the active trigger + query from composer text. */
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const [cursorPosition, setCursorPosition] = useState(text.length);
|
|
21
|
+
const useTriggerDetectionResource = ({
|
|
22
|
+
text,
|
|
23
|
+
triggerChar,
|
|
24
|
+
}: {
|
|
25
|
+
text: string;
|
|
26
|
+
triggerChar: string;
|
|
27
|
+
}): TriggerDetectionResourceOutput => {
|
|
28
|
+
const [cursorPosition, setCursorPosition] = useState(text.length);
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
const trigger = useMemo(() => {
|
|
31
|
+
const pos = Math.min(cursorPosition, text.length);
|
|
32
|
+
return detectTrigger(text, triggerChar, pos);
|
|
33
|
+
}, [cursorPosition, text, triggerChar]);
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
const query = trigger?.query ?? "";
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
return {
|
|
38
|
+
trigger,
|
|
39
|
+
query,
|
|
40
|
+
setCursorPosition,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const TriggerDetectionResource = resource(useTriggerDetectionResource);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import { createTapRoot, useResource } from "@assistant-ui/tap";
|
|
3
3
|
import type {
|
|
4
4
|
Unstable_TriggerCategory,
|
|
5
5
|
Unstable_TriggerItem,
|
|
@@ -41,9 +41,10 @@ const render = (
|
|
|
41
41
|
close: vi.fn(),
|
|
42
42
|
...overrides,
|
|
43
43
|
};
|
|
44
|
-
const root =
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
const root = createTapRoot(function Root() {
|
|
45
|
+
return useResource(TriggerKeyboardResource(props));
|
|
46
|
+
});
|
|
47
|
+
return { sub: root, props };
|
|
47
48
|
};
|
|
48
49
|
|
|
49
50
|
describe("TriggerKeyboardResource", () => {
|
|
@@ -34,113 +34,111 @@ export type TriggerKeyboardResourceOutput = {
|
|
|
34
34
|
* Owns keyboard-driven highlight state for the popover. Delegates selection,
|
|
35
35
|
* category drill-in, back, and close to the callbacks supplied by the parent.
|
|
36
36
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const [highlightedIndex, setHighlightedIndex] = useState(0);
|
|
37
|
+
const useTriggerKeyboardResource = ({
|
|
38
|
+
navigableList,
|
|
39
|
+
isSearchMode,
|
|
40
|
+
activeCategoryId,
|
|
41
|
+
query,
|
|
42
|
+
popoverId,
|
|
43
|
+
open,
|
|
44
|
+
selectItem,
|
|
45
|
+
selectCategory,
|
|
46
|
+
goBack,
|
|
47
|
+
close,
|
|
48
|
+
}: {
|
|
49
|
+
navigableList: readonly (Unstable_TriggerCategory | Unstable_TriggerItem)[];
|
|
50
|
+
isSearchMode: boolean;
|
|
51
|
+
activeCategoryId: string | null;
|
|
52
|
+
query: string;
|
|
53
|
+
popoverId: string;
|
|
54
|
+
open: boolean;
|
|
55
|
+
selectItem: (item: Unstable_TriggerItem) => void;
|
|
56
|
+
selectCategory: (categoryId: string) => void;
|
|
57
|
+
goBack: () => void;
|
|
58
|
+
close: () => void;
|
|
59
|
+
}): TriggerKeyboardResourceOutput => {
|
|
60
|
+
const [highlightedIndex, setHighlightedIndex] = useState(0);
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
setHighlightedIndex(0);
|
|
64
|
+
}, [navigableList]);
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
setHighlightedIndex(0);
|
|
68
|
+
}, [isSearchMode, activeCategoryId]);
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
const highlightIndex = useEffectEvent((index: number) => {
|
|
71
|
+
if (index < 0 || index >= navigableList.length) return;
|
|
72
|
+
if (index === highlightedIndex) return;
|
|
73
|
+
setHighlightedIndex(index);
|
|
74
|
+
});
|
|
76
75
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!open) return false;
|
|
76
|
+
const handleKeyDown = useEffectEvent((e: TriggerPopoverKeyEvent): boolean => {
|
|
77
|
+
if (!open) return false;
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
79
|
+
switch (e.key) {
|
|
80
|
+
case "ArrowDown": {
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
setHighlightedIndex((prev) => {
|
|
83
|
+
const len = navigableList.length;
|
|
84
|
+
if (len === 0) return 0;
|
|
85
|
+
return prev < len - 1 ? prev + 1 : 0;
|
|
86
|
+
});
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
case "ArrowUp": {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
setHighlightedIndex((prev) => {
|
|
92
|
+
const len = navigableList.length;
|
|
93
|
+
if (len === 0) return 0;
|
|
94
|
+
return prev > 0 ? prev - 1 : len - 1;
|
|
95
|
+
});
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
case "Enter":
|
|
99
|
+
case "Tab": {
|
|
100
|
+
if (e.shiftKey) return false;
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
const item = navigableList[highlightedIndex];
|
|
103
|
+
if (!item) return true;
|
|
106
104
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
case "Escape": {
|
|
115
|
-
e.preventDefault();
|
|
116
|
-
close();
|
|
117
|
-
return true;
|
|
118
|
-
}
|
|
119
|
-
case "Backspace": {
|
|
120
|
-
if (activeCategoryId && query === "") {
|
|
121
|
-
e.preventDefault();
|
|
122
|
-
goBack();
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
default:
|
|
128
|
-
return false;
|
|
105
|
+
if (isTriggerItem(item)) {
|
|
106
|
+
selectItem(item);
|
|
107
|
+
} else {
|
|
108
|
+
selectCategory(item.id);
|
|
129
109
|
}
|
|
130
|
-
|
|
131
|
-
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
case "Escape": {
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
close();
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
case "Backspace": {
|
|
118
|
+
if (activeCategoryId && query === "") {
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
goBack();
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
default:
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const highlightedEntry = navigableList[highlightedIndex];
|
|
131
|
+
const highlightedItemId =
|
|
132
|
+
open && highlightedEntry
|
|
133
|
+
? `${popoverId}-option-${highlightedEntry.id}`
|
|
134
|
+
: undefined;
|
|
132
135
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
return {
|
|
137
|
+
highlightedIndex,
|
|
138
|
+
highlightedItemId,
|
|
139
|
+
highlightIndex,
|
|
140
|
+
handleKeyDown,
|
|
141
|
+
};
|
|
142
|
+
};
|
|
138
143
|
|
|
139
|
-
|
|
140
|
-
highlightedIndex,
|
|
141
|
-
highlightedItemId,
|
|
142
|
-
highlightIndex,
|
|
143
|
-
handleKeyDown,
|
|
144
|
-
};
|
|
145
|
-
},
|
|
146
|
-
);
|
|
144
|
+
export const TriggerKeyboardResource = resource(useTriggerKeyboardResource);
|
|
@@ -38,103 +38,97 @@ export type TriggerNavigationResourceOutput = {
|
|
|
38
38
|
* Computes categories, items, search results, and navigation state from the
|
|
39
39
|
* adapter + current query. Pure derivation — no side effects on the composer.
|
|
40
40
|
*/
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const lower = query.toLowerCase();
|
|
82
|
-
for (const cat of categories) {
|
|
83
|
-
for (const item of adapter.categoryItems(cat.id)) {
|
|
84
|
-
if (matchesQuery(item, lower)) {
|
|
85
|
-
all.push(item);
|
|
86
|
-
}
|
|
41
|
+
const useTriggerNavigationResource = ({
|
|
42
|
+
adapter,
|
|
43
|
+
query,
|
|
44
|
+
open,
|
|
45
|
+
}: {
|
|
46
|
+
adapter: Unstable_TriggerAdapter | undefined;
|
|
47
|
+
query: string;
|
|
48
|
+
open: boolean;
|
|
49
|
+
}): TriggerNavigationResourceOutput => {
|
|
50
|
+
const [activeCategoryId, setActiveCategoryId] = useState<string | null>(null);
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!open) setActiveCategoryId(null);
|
|
54
|
+
}, [open]);
|
|
55
|
+
|
|
56
|
+
const categories = useMemo<readonly Unstable_TriggerCategory[]>(() => {
|
|
57
|
+
if (!open || !adapter) return [];
|
|
58
|
+
return adapter.categories();
|
|
59
|
+
}, [open, adapter]);
|
|
60
|
+
|
|
61
|
+
const effectiveActiveCategoryId = open ? activeCategoryId : null;
|
|
62
|
+
|
|
63
|
+
const allItems = useMemo<readonly Unstable_TriggerItem[]>(() => {
|
|
64
|
+
if (!effectiveActiveCategoryId || !adapter) return [];
|
|
65
|
+
return adapter.categoryItems(effectiveActiveCategoryId);
|
|
66
|
+
}, [effectiveActiveCategoryId, adapter]);
|
|
67
|
+
|
|
68
|
+
const searchResults = useMemo<readonly Unstable_TriggerItem[] | null>(() => {
|
|
69
|
+
if (!open || !adapter || effectiveActiveCategoryId) return null;
|
|
70
|
+
// If categories exist and query is empty, show categories first (not search)
|
|
71
|
+
if (!query && categories.length > 0) return null;
|
|
72
|
+
if (adapter.search) return adapter.search(query);
|
|
73
|
+
|
|
74
|
+
// fallback: no adapter.search
|
|
75
|
+
const all: Unstable_TriggerItem[] = [];
|
|
76
|
+
const lower = query.toLowerCase();
|
|
77
|
+
for (const cat of categories) {
|
|
78
|
+
for (const item of adapter.categoryItems(cat.id)) {
|
|
79
|
+
if (matchesQuery(item, lower)) {
|
|
80
|
+
all.push(item);
|
|
87
81
|
}
|
|
88
82
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
);
|
|
83
|
+
}
|
|
84
|
+
return all;
|
|
85
|
+
}, [open, adapter, query, effectiveActiveCategoryId, categories]);
|
|
86
|
+
|
|
87
|
+
const isSearchMode = searchResults !== null;
|
|
88
|
+
|
|
89
|
+
const filteredCategories = useMemo(() => {
|
|
90
|
+
if (isSearchMode) return [];
|
|
91
|
+
if (!query) return categories;
|
|
92
|
+
const lower = query.toLowerCase();
|
|
93
|
+
return categories.filter((cat) => cat.label.toLowerCase().includes(lower));
|
|
94
|
+
}, [categories, query, isSearchMode]);
|
|
95
|
+
|
|
96
|
+
const filteredItems = useMemo(() => {
|
|
97
|
+
if (isSearchMode) return searchResults ?? [];
|
|
98
|
+
if (!query) return allItems;
|
|
99
|
+
const lower = query.toLowerCase();
|
|
100
|
+
return allItems.filter((item) => matchesQuery(item, lower));
|
|
101
|
+
}, [allItems, query, isSearchMode, searchResults]);
|
|
102
|
+
|
|
103
|
+
const navigableList = useMemo(() => {
|
|
104
|
+
if (isSearchMode) return searchResults ?? [];
|
|
105
|
+
if (effectiveActiveCategoryId) return filteredItems;
|
|
106
|
+
return filteredCategories;
|
|
107
|
+
}, [
|
|
108
|
+
isSearchMode,
|
|
109
|
+
searchResults,
|
|
110
|
+
effectiveActiveCategoryId,
|
|
111
|
+
filteredItems,
|
|
112
|
+
filteredCategories,
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
const selectCategory = useEffectEvent((categoryId: string) => {
|
|
116
|
+
setActiveCategoryId(categoryId);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const goBack = useEffectEvent(() => {
|
|
120
|
+
setActiveCategoryId(null);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
categories: filteredCategories,
|
|
125
|
+
items: filteredItems,
|
|
126
|
+
isSearchMode,
|
|
127
|
+
activeCategoryId: effectiveActiveCategoryId,
|
|
128
|
+
navigableList,
|
|
129
|
+
selectCategory,
|
|
130
|
+
goBack,
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const TriggerNavigationResource = resource(useTriggerNavigationResource);
|