@assistant-ui/react 0.12.28 → 0.14.2
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/README.md +58 -42
- package/dist/client/ExternalThread.d.ts +7 -0
- package/dist/client/ExternalThread.d.ts.map +1 -1
- package/dist/client/ExternalThread.js +24 -18
- package/dist/client/ExternalThread.js.map +1 -1
- package/dist/client/InMemoryThreadList.d.ts.map +1 -1
- package/dist/client/InMemoryThreadList.js +3 -0
- package/dist/client/InMemoryThreadList.js.map +1 -1
- package/dist/client/SingleThreadList.d.ts.map +1 -1
- package/dist/client/SingleThreadList.js +3 -0
- package/dist/client/SingleThreadList.js.map +1 -1
- package/dist/context/providers/ThreadViewportProvider.d.ts.map +1 -1
- package/dist/context/providers/ThreadViewportProvider.js +2 -10
- package/dist/context/providers/ThreadViewportProvider.js.map +1 -1
- package/dist/context/stores/ThreadViewport.d.ts +46 -4
- package/dist/context/stores/ThreadViewport.d.ts.map +1 -1
- package/dist/context/stores/ThreadViewport.js +51 -7
- package/dist/context/stores/ThreadViewport.js.map +1 -1
- package/dist/index.d.ts +5 -30
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -28
- package/dist/index.js.map +1 -1
- package/dist/legacy-runtime/cloud/auiV0.d.ts +10 -1
- package/dist/legacy-runtime/cloud/auiV0.d.ts.map +1 -1
- package/dist/legacy-runtime/cloud/auiV0.js +21 -3
- package/dist/legacy-runtime/cloud/auiV0.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.d.ts +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.js +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.js.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.d.ts +28 -0
- package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -0
- package/dist/mcp-apps/McpAppRenderer.js +115 -0
- package/dist/mcp-apps/McpAppRenderer.js.map +1 -0
- package/dist/mcp-apps/McpAppsRemoteHost.d.ts +3 -0
- package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -0
- package/dist/mcp-apps/McpAppsRemoteHost.js +27 -0
- package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -0
- package/dist/mcp-apps/app-frame.d.ts +3 -0
- package/dist/mcp-apps/app-frame.d.ts.map +1 -0
- package/dist/mcp-apps/app-frame.js +203 -0
- package/dist/mcp-apps/app-frame.js.map +1 -0
- package/dist/mcp-apps/bridge.d.ts +18 -0
- package/dist/mcp-apps/bridge.d.ts.map +1 -0
- package/dist/mcp-apps/bridge.js +290 -0
- package/dist/mcp-apps/bridge.js.map +1 -0
- package/dist/mcp-apps/index.d.ts +4 -0
- package/dist/mcp-apps/index.d.ts.map +1 -0
- package/dist/mcp-apps/index.js +3 -0
- package/dist/mcp-apps/index.js.map +1 -0
- package/dist/mcp-apps/types.d.ts +144 -0
- package/dist/mcp-apps/types.d.ts.map +1 -0
- package/dist/mcp-apps/types.js +3 -0
- package/dist/mcp-apps/types.js.map +1 -0
- package/dist/mcp-apps/utils.d.ts +5 -0
- package/dist/mcp-apps/utils.d.ts.map +1 -0
- package/dist/mcp-apps/utils.js +10 -0
- package/dist/mcp-apps/utils.js.map +1 -0
- package/dist/primitives/composer/ComposerInput.d.ts +6 -0
- package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +28 -6
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.js +17 -1
- package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts +33 -0
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js +80 -11
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerKeyboardResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerKeyboardResource.js +2 -1
- package/dist/primitives/composer/trigger/triggerKeyboardResource.js.map +1 -1
- package/dist/primitives/message/MessageRoot.d.ts +6 -30
- package/dist/primitives/message/MessageRoot.d.ts.map +1 -1
- package/dist/primitives/message/MessageRoot.js +68 -25
- package/dist/primitives/message/MessageRoot.js.map +1 -1
- package/dist/primitives/messagePart/useMessagePartSource.d.ts +22 -3
- package/dist/primitives/messagePart/useMessagePartSource.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewport.d.ts +38 -0
- package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewport.js +53 -5
- package/dist/primitives/thread/ThreadViewport.js.map +1 -1
- package/dist/primitives/thread/ThreadViewportFooter.d.ts +2 -1
- package/dist/primitives/thread/ThreadViewportFooter.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewportFooter.js +2 -1
- package/dist/primitives/thread/ThreadViewportFooter.js.map +1 -1
- package/dist/primitives/thread/topAnchor/computeTopAnchorSlack.d.ts +22 -0
- package/dist/primitives/thread/topAnchor/computeTopAnchorSlack.d.ts.map +1 -0
- package/dist/primitives/thread/topAnchor/computeTopAnchorSlack.js +53 -0
- package/dist/primitives/thread/topAnchor/computeTopAnchorSlack.js.map +1 -0
- package/dist/primitives/thread/topAnchor/createReserveObservers.d.ts +5 -0
- package/dist/primitives/thread/topAnchor/createReserveObservers.d.ts.map +1 -0
- package/dist/primitives/thread/topAnchor/createReserveObservers.js +38 -0
- package/dist/primitives/thread/topAnchor/createReserveObservers.js.map +1 -0
- package/dist/primitives/thread/topAnchor/mountTopAnchorReserve.d.ts +22 -0
- package/dist/primitives/thread/topAnchor/mountTopAnchorReserve.d.ts.map +1 -0
- package/dist/primitives/thread/topAnchor/mountTopAnchorReserve.js +75 -0
- package/dist/primitives/thread/topAnchor/mountTopAnchorReserve.js.map +1 -0
- package/dist/primitives/thread/topAnchor/topAnchorTurn.d.ts +15 -0
- package/dist/primitives/thread/topAnchor/topAnchorTurn.d.ts.map +1 -0
- package/dist/primitives/thread/topAnchor/topAnchorTurn.js +13 -0
- package/dist/primitives/thread/topAnchor/topAnchorTurn.js.map +1 -0
- package/dist/primitives/thread/topAnchor/topAnchorUtils.d.ts +15 -0
- package/dist/primitives/thread/topAnchor/topAnchorUtils.d.ts.map +1 -0
- package/dist/primitives/thread/topAnchor/topAnchorUtils.js +51 -0
- package/dist/primitives/thread/topAnchor/topAnchorUtils.js.map +1 -0
- package/dist/primitives/thread/topAnchor/useTopAnchorReserve.d.ts +7 -0
- package/dist/primitives/thread/topAnchor/useTopAnchorReserve.d.ts.map +1 -0
- package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js +18 -0
- package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js.map +1 -0
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +13 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/primitives/thread.d.ts +0 -1
- package/dist/primitives/thread.d.ts.map +1 -1
- package/dist/primitives/thread.js +0 -1
- package/dist/primitives/thread.js.map +1 -1
- package/dist/primitives/threadList/ThreadListLoadMore.d.ts +13 -0
- package/dist/primitives/threadList/ThreadListLoadMore.d.ts.map +1 -0
- package/dist/primitives/threadList/ThreadListLoadMore.js +11 -0
- package/dist/primitives/threadList/ThreadListLoadMore.js.map +1 -0
- package/dist/primitives/threadList.d.ts +1 -0
- package/dist/primitives/threadList.d.ts.map +1 -1
- package/dist/primitives/threadList.js +1 -0
- package/dist/primitives/threadList.js.map +1 -1
- package/dist/utils/hooks/useManagedRef.d.ts.map +1 -1
- package/dist/utils/hooks/useManagedRef.js +1 -0
- package/dist/utils/hooks/useManagedRef.js.map +1 -1
- package/dist/utils/hooks/useOnResizeContent.d.ts.map +1 -1
- package/dist/utils/hooks/useOnResizeContent.js +1 -2
- package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
- package/package.json +13 -13
- package/src/client/ExternalThread.ts +32 -19
- package/src/client/InMemoryThreadList.ts +3 -0
- package/src/client/SingleThreadList.ts +3 -0
- package/src/context/providers/ThreadViewportProvider.tsx +2 -12
- package/src/context/stores/ThreadViewport.ts +111 -11
- package/src/index.ts +20 -34
- package/src/legacy-runtime/cloud/auiV0.ts +37 -4
- package/src/legacy-runtime/runtime-cores/assistant-transport/utils.ts +1 -5
- package/src/mcp-apps/McpAppRenderer.tsx +215 -0
- package/src/mcp-apps/McpAppsRemoteHost.ts +52 -0
- package/src/mcp-apps/app-frame.tsx +280 -0
- package/src/mcp-apps/bridge.test.ts +391 -0
- package/src/mcp-apps/bridge.ts +435 -0
- package/src/mcp-apps/index.ts +16 -0
- package/src/mcp-apps/types.ts +158 -0
- package/src/mcp-apps/utils.ts +16 -0
- package/src/primitives/composer/ComposerInput.test.tsx +280 -0
- package/src/primitives/composer/ComposerInput.tsx +29 -6
- package/src/primitives/composer/trigger/TriggerPopover.tsx +21 -1
- package/src/primitives/composer/trigger/TriggerPopoverRootContext.test.tsx +152 -0
- package/src/primitives/composer/trigger/TriggerPopoverRootContext.tsx +134 -17
- package/src/primitives/composer/trigger/triggerKeyboardResource.test.ts +236 -0
- package/src/primitives/composer/trigger/triggerKeyboardResource.ts +2 -1
- package/src/primitives/message/MessageRoot.tsx +135 -57
- package/src/primitives/thread/ThreadViewport.tsx +95 -4
- package/src/primitives/thread/ThreadViewportFooter.tsx +2 -1
- package/src/primitives/thread/topAnchor/computeTopAnchorSlack.test.ts +131 -0
- package/src/primitives/thread/topAnchor/computeTopAnchorSlack.ts +94 -0
- package/src/primitives/thread/topAnchor/createReserveObservers.ts +50 -0
- package/src/primitives/thread/topAnchor/mountTopAnchorReserve.test.ts +131 -0
- package/src/primitives/thread/topAnchor/mountTopAnchorReserve.ts +127 -0
- package/src/primitives/thread/topAnchor/topAnchorTurn.test.ts +46 -0
- package/src/primitives/thread/topAnchor/topAnchorTurn.ts +30 -0
- package/src/primitives/thread/topAnchor/topAnchorUtils.ts +58 -0
- package/src/primitives/thread/topAnchor/useTopAnchorReserve.ts +19 -0
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +15 -1
- package/src/primitives/thread.ts +0 -1
- package/src/primitives/threadList/ThreadListLoadMore.tsx +24 -0
- package/src/primitives/threadList.ts +1 -0
- package/src/tests/BaseComposerRuntimeCore.test.ts +4 -0
- package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +138 -0
- package/src/tests/RemoteThreadListRuntime.deferredProvider.test.tsx +28 -17
- package/src/tests/auiV0Encode.test.ts +55 -0
- package/src/utils/hooks/useManagedRef.ts +1 -0
- package/src/utils/hooks/useOnResizeContent.ts +1 -2
- package/dist/legacy-runtime/runtime-cores/external-store/getExternalStoreMessage.d.ts +0 -3
- package/dist/legacy-runtime/runtime-cores/external-store/getExternalStoreMessage.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/external-store/getExternalStoreMessage.js +0 -3
- package/dist/legacy-runtime/runtime-cores/external-store/getExternalStoreMessage.js.map +0 -1
- package/dist/primitives/thread/ThreadViewportSlack.d.ts +0 -20
- package/dist/primitives/thread/ThreadViewportSlack.d.ts.map +0 -1
- package/dist/primitives/thread/ThreadViewportSlack.js +0 -80
- package/dist/primitives/thread/ThreadViewportSlack.js.map +0 -1
- package/src/legacy-runtime/runtime-cores/external-store/getExternalStoreMessage.ts +0 -6
- package/src/primitives/thread/ThreadViewportSlack.tsx +0 -116
|
@@ -29,17 +29,43 @@ export type TriggerPopoverLifecycleListener = {
|
|
|
29
29
|
removed(char: string): void;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* ARIA descriptor of the popover that is currently open. Consumed by the
|
|
34
|
+
* focused element (typically the composer textarea) so it can advertise the
|
|
35
|
+
* combobox relationship per the WAI-ARIA editable combobox pattern.
|
|
36
|
+
*/
|
|
37
|
+
export type TriggerPopoverActiveAria = {
|
|
38
|
+
popoverId: string;
|
|
39
|
+
highlightedItemId: string | undefined;
|
|
40
|
+
};
|
|
41
|
+
|
|
32
42
|
export type TriggerPopoverRootContextValue = {
|
|
33
43
|
register(trigger: RegisteredTrigger): () => void;
|
|
34
44
|
getTriggers(): ReadonlyMap<string, RegisteredTrigger>;
|
|
35
45
|
subscribe(listener: () => void): () => void;
|
|
36
46
|
/** Subscribe to per-trigger add/remove events. */
|
|
37
47
|
subscribeLifecycle(listener: TriggerPopoverLifecycleListener): () => void;
|
|
48
|
+
/** ARIA descriptor of the open popover, or null if none is open. */
|
|
49
|
+
getActiveAria(): TriggerPopoverActiveAria | null;
|
|
50
|
+
/** Subscribe to changes in the active ARIA descriptor. */
|
|
51
|
+
subscribeAria(listener: () => void): () => void;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Write-side of the ARIA descriptor, scoped to `TriggerPopover` children of a
|
|
56
|
+
* `TriggerPopoverRoot`. Intentionally not exposed on the public root context
|
|
57
|
+
* value: external consumers can read ARIA state but cannot publish or clear it.
|
|
58
|
+
*/
|
|
59
|
+
type TriggerPopoverAriaPublish = {
|
|
60
|
+
setActiveAria(char: string, aria: TriggerPopoverActiveAria | null): void;
|
|
38
61
|
};
|
|
39
62
|
|
|
40
63
|
const TriggerPopoverRootContext =
|
|
41
64
|
createContext<TriggerPopoverRootContextValue | null>(null);
|
|
42
65
|
|
|
66
|
+
const TriggerPopoverAriaPublishContext =
|
|
67
|
+
createContext<TriggerPopoverAriaPublish | null>(null);
|
|
68
|
+
|
|
43
69
|
export const useTriggerPopoverRootContext = () => {
|
|
44
70
|
const ctx = useContext(TriggerPopoverRootContext);
|
|
45
71
|
if (!ctx)
|
|
@@ -52,6 +78,19 @@ export const useTriggerPopoverRootContext = () => {
|
|
|
52
78
|
export const useTriggerPopoverRootContextOptional = () =>
|
|
53
79
|
useContext(TriggerPopoverRootContext);
|
|
54
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Internal hook used by `TriggerPopover` children to publish their open and
|
|
83
|
+
* highlight state. Not exported from the trigger module.
|
|
84
|
+
*/
|
|
85
|
+
export const useTriggerPopoverAriaPublish = (): TriggerPopoverAriaPublish => {
|
|
86
|
+
const ctx = useContext(TriggerPopoverAriaPublishContext);
|
|
87
|
+
if (!ctx)
|
|
88
|
+
throw new Error(
|
|
89
|
+
"useTriggerPopoverAriaPublish must be used within ComposerPrimitive.TriggerPopoverRoot",
|
|
90
|
+
);
|
|
91
|
+
return ctx;
|
|
92
|
+
};
|
|
93
|
+
|
|
55
94
|
/**
|
|
56
95
|
* Live map of registered triggers, re-rendering on change. Prefer
|
|
57
96
|
* `subscribeLifecycle` for incremental add/remove handling.
|
|
@@ -75,24 +114,57 @@ export const useTriggerPopoverTriggersOptional = () => {
|
|
|
75
114
|
);
|
|
76
115
|
};
|
|
77
116
|
|
|
117
|
+
const getNullAria = () => null;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Returns the ARIA descriptor of the currently open trigger popover, or
|
|
121
|
+
* `null` if none is open or the consumer is rendered outside a
|
|
122
|
+
* `TriggerPopoverRoot`.
|
|
123
|
+
*/
|
|
124
|
+
export const useTriggerPopoverActiveAriaOptional =
|
|
125
|
+
(): TriggerPopoverActiveAria | null => {
|
|
126
|
+
const ctx = useTriggerPopoverRootContextOptional();
|
|
127
|
+
return useSyncExternalStore(
|
|
128
|
+
ctx ? ctx.subscribeAria : noopSubscribe,
|
|
129
|
+
ctx ? ctx.getActiveAria : getNullAria,
|
|
130
|
+
ctx ? ctx.getActiveAria : getNullAria,
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
78
134
|
export namespace ComposerPrimitiveTriggerPopoverRoot {
|
|
79
135
|
export type Props = {
|
|
80
136
|
children: ReactNode;
|
|
81
137
|
};
|
|
82
138
|
}
|
|
83
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Local helper for the simple "notify-all listeners" subscribable pattern.
|
|
142
|
+
* Used twice in this file (trigger registry, active ARIA); kept inline to
|
|
143
|
+
* avoid pulling a single-use abstraction into the wider tree.
|
|
144
|
+
*/
|
|
145
|
+
function useSimpleSubscribable() {
|
|
146
|
+
const listenersRef = useRef<Set<() => void>>(new Set());
|
|
147
|
+
const notify = useCallback(() => {
|
|
148
|
+
for (const listener of listenersRef.current) listener();
|
|
149
|
+
}, []);
|
|
150
|
+
const subscribe = useCallback((listener: () => void) => {
|
|
151
|
+
listenersRef.current.add(listener);
|
|
152
|
+
return () => {
|
|
153
|
+
listenersRef.current.delete(listener);
|
|
154
|
+
};
|
|
155
|
+
}, []);
|
|
156
|
+
return { notify, subscribe };
|
|
157
|
+
}
|
|
158
|
+
|
|
84
159
|
const TriggerPopoverRootInner: FC<
|
|
85
160
|
ComposerPrimitiveTriggerPopoverRoot.Props
|
|
86
161
|
> = ({ children }) => {
|
|
87
162
|
const triggersRef = useRef<ReadonlyMap<string, RegisteredTrigger>>(new Map());
|
|
88
|
-
const listenersRef = useRef<Set<() => void>>(new Set());
|
|
89
163
|
const lifecycleListenersRef = useRef<Set<TriggerPopoverLifecycleListener>>(
|
|
90
164
|
new Set(),
|
|
91
165
|
);
|
|
92
166
|
|
|
93
|
-
const notify =
|
|
94
|
-
for (const listener of listenersRef.current) listener();
|
|
95
|
-
}, []);
|
|
167
|
+
const { notify, subscribe } = useSimpleSubscribable();
|
|
96
168
|
|
|
97
169
|
const register = useCallback<TriggerPopoverRootContextValue["register"]>(
|
|
98
170
|
(trigger) => {
|
|
@@ -137,16 +209,6 @@ const TriggerPopoverRootInner: FC<
|
|
|
137
209
|
TriggerPopoverRootContextValue["getTriggers"]
|
|
138
210
|
>(() => triggersRef.current, []);
|
|
139
211
|
|
|
140
|
-
const subscribe = useCallback<TriggerPopoverRootContextValue["subscribe"]>(
|
|
141
|
-
(listener) => {
|
|
142
|
-
listenersRef.current.add(listener);
|
|
143
|
-
return () => {
|
|
144
|
-
listenersRef.current.delete(listener);
|
|
145
|
-
};
|
|
146
|
-
},
|
|
147
|
-
[],
|
|
148
|
-
);
|
|
149
|
-
|
|
150
212
|
const subscribeLifecycle = useCallback<
|
|
151
213
|
TriggerPopoverRootContextValue["subscribeLifecycle"]
|
|
152
214
|
>((listener) => {
|
|
@@ -156,14 +218,69 @@ const TriggerPopoverRootInner: FC<
|
|
|
156
218
|
};
|
|
157
219
|
}, []);
|
|
158
220
|
|
|
221
|
+
const activeAriaRef = useRef<TriggerPopoverActiveAria | null>(null);
|
|
222
|
+
const activeAriaCharRef = useRef<string | null>(null);
|
|
223
|
+
const { notify: notifyAria, subscribe: subscribeAria } =
|
|
224
|
+
useSimpleSubscribable();
|
|
225
|
+
|
|
226
|
+
const setActiveAria = useCallback<TriggerPopoverAriaPublish["setActiveAria"]>(
|
|
227
|
+
(char, aria) => {
|
|
228
|
+
if (aria === null) {
|
|
229
|
+
if (activeAriaCharRef.current !== char) return;
|
|
230
|
+
activeAriaRef.current = null;
|
|
231
|
+
activeAriaCharRef.current = null;
|
|
232
|
+
notifyAria();
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const prev = activeAriaRef.current;
|
|
236
|
+
if (
|
|
237
|
+
activeAriaCharRef.current === char &&
|
|
238
|
+
prev !== null &&
|
|
239
|
+
prev.popoverId === aria.popoverId &&
|
|
240
|
+
prev.highlightedItemId === aria.highlightedItemId
|
|
241
|
+
) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
activeAriaRef.current = aria;
|
|
245
|
+
activeAriaCharRef.current = char;
|
|
246
|
+
notifyAria();
|
|
247
|
+
},
|
|
248
|
+
[notifyAria],
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const getActiveAria = useCallback<
|
|
252
|
+
TriggerPopoverRootContextValue["getActiveAria"]
|
|
253
|
+
>(() => activeAriaRef.current, []);
|
|
254
|
+
|
|
159
255
|
const value = useMemo<TriggerPopoverRootContextValue>(
|
|
160
|
-
() => ({
|
|
161
|
-
|
|
256
|
+
() => ({
|
|
257
|
+
register,
|
|
258
|
+
getTriggers,
|
|
259
|
+
subscribe,
|
|
260
|
+
subscribeLifecycle,
|
|
261
|
+
getActiveAria,
|
|
262
|
+
subscribeAria,
|
|
263
|
+
}),
|
|
264
|
+
[
|
|
265
|
+
register,
|
|
266
|
+
getTriggers,
|
|
267
|
+
subscribe,
|
|
268
|
+
subscribeLifecycle,
|
|
269
|
+
getActiveAria,
|
|
270
|
+
subscribeAria,
|
|
271
|
+
],
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
const ariaPublishValue = useMemo<TriggerPopoverAriaPublish>(
|
|
275
|
+
() => ({ setActiveAria }),
|
|
276
|
+
[setActiveAria],
|
|
162
277
|
);
|
|
163
278
|
|
|
164
279
|
return (
|
|
165
280
|
<TriggerPopoverRootContext.Provider value={value}>
|
|
166
|
-
{
|
|
281
|
+
<TriggerPopoverAriaPublishContext.Provider value={ariaPublishValue}>
|
|
282
|
+
{children}
|
|
283
|
+
</TriggerPopoverAriaPublishContext.Provider>
|
|
167
284
|
</TriggerPopoverRootContext.Provider>
|
|
168
285
|
);
|
|
169
286
|
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { createResourceRoot } from "@assistant-ui/tap";
|
|
3
|
+
import type {
|
|
4
|
+
Unstable_TriggerCategory,
|
|
5
|
+
Unstable_TriggerItem,
|
|
6
|
+
} from "@assistant-ui/core";
|
|
7
|
+
import { TriggerKeyboardResource } from "./triggerKeyboardResource";
|
|
8
|
+
|
|
9
|
+
const item = (id: string): Unstable_TriggerItem => ({
|
|
10
|
+
id,
|
|
11
|
+
type: "command",
|
|
12
|
+
label: id,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const category = (id: string): Unstable_TriggerCategory => ({
|
|
16
|
+
id,
|
|
17
|
+
label: id,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const makeKeyEvent = (key: string, shiftKey = false) => ({
|
|
21
|
+
key,
|
|
22
|
+
shiftKey,
|
|
23
|
+
preventDefault: vi.fn(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const tick = () => new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
27
|
+
|
|
28
|
+
const render = (
|
|
29
|
+
overrides: Partial<Parameters<typeof TriggerKeyboardResource>[0]> = {},
|
|
30
|
+
) => {
|
|
31
|
+
const props = {
|
|
32
|
+
navigableList: [item("a"), item("b"), item("c")],
|
|
33
|
+
isSearchMode: false,
|
|
34
|
+
activeCategoryId: null as string | null,
|
|
35
|
+
query: "",
|
|
36
|
+
popoverId: "popover",
|
|
37
|
+
open: true,
|
|
38
|
+
selectItem: vi.fn(),
|
|
39
|
+
selectCategory: vi.fn(),
|
|
40
|
+
goBack: vi.fn(),
|
|
41
|
+
close: vi.fn(),
|
|
42
|
+
...overrides,
|
|
43
|
+
};
|
|
44
|
+
const root = createResourceRoot();
|
|
45
|
+
const sub = root.render(TriggerKeyboardResource(props));
|
|
46
|
+
return { sub, props };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
describe("TriggerKeyboardResource", () => {
|
|
50
|
+
it("selects highlighted item on Tab", () => {
|
|
51
|
+
const { sub, props } = render();
|
|
52
|
+
const e = makeKeyEvent("Tab");
|
|
53
|
+
|
|
54
|
+
const consumed = sub.getValue().handleKeyDown(e);
|
|
55
|
+
|
|
56
|
+
expect(consumed).toBe(true);
|
|
57
|
+
expect(e.preventDefault).toHaveBeenCalled();
|
|
58
|
+
expect(props.selectItem).toHaveBeenCalledWith(props.navigableList[0]);
|
|
59
|
+
expect(props.selectCategory).not.toHaveBeenCalled();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("selects category on Tab when entry is a category", () => {
|
|
63
|
+
const { sub, props } = render({
|
|
64
|
+
navigableList: [category("cat-1"), item("b")],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const consumed = sub.getValue().handleKeyDown(makeKeyEvent("Tab"));
|
|
68
|
+
|
|
69
|
+
expect(consumed).toBe(true);
|
|
70
|
+
expect(props.selectCategory).toHaveBeenCalledWith("cat-1");
|
|
71
|
+
expect(props.selectItem).not.toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("lets Shift+Tab pass through for native focus traversal", () => {
|
|
75
|
+
const { sub, props } = render();
|
|
76
|
+
const e = makeKeyEvent("Tab", true);
|
|
77
|
+
|
|
78
|
+
const consumed = sub.getValue().handleKeyDown(e);
|
|
79
|
+
|
|
80
|
+
expect(consumed).toBe(false);
|
|
81
|
+
expect(e.preventDefault).not.toHaveBeenCalled();
|
|
82
|
+
expect(props.selectItem).not.toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("swallows Tab when the navigable list is empty", () => {
|
|
86
|
+
const { sub, props } = render({ navigableList: [] });
|
|
87
|
+
const e = makeKeyEvent("Tab");
|
|
88
|
+
|
|
89
|
+
const consumed = sub.getValue().handleKeyDown(e);
|
|
90
|
+
|
|
91
|
+
expect(consumed).toBe(true);
|
|
92
|
+
expect(e.preventDefault).toHaveBeenCalled();
|
|
93
|
+
expect(props.selectItem).not.toHaveBeenCalled();
|
|
94
|
+
expect(props.selectCategory).not.toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("does nothing when the popover is closed", () => {
|
|
98
|
+
const { sub, props } = render({ open: false });
|
|
99
|
+
const e = makeKeyEvent("Tab");
|
|
100
|
+
|
|
101
|
+
const consumed = sub.getValue().handleKeyDown(e);
|
|
102
|
+
|
|
103
|
+
expect(consumed).toBe(false);
|
|
104
|
+
expect(e.preventDefault).not.toHaveBeenCalled();
|
|
105
|
+
expect(props.selectItem).not.toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("moves the highlight forward on ArrowDown", async () => {
|
|
109
|
+
const { sub } = render();
|
|
110
|
+
|
|
111
|
+
const consumed = sub.getValue().handleKeyDown(makeKeyEvent("ArrowDown"));
|
|
112
|
+
await tick();
|
|
113
|
+
|
|
114
|
+
expect(consumed).toBe(true);
|
|
115
|
+
expect(sub.getValue().highlightedIndex).toBe(1);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("wraps the highlight to the top on ArrowDown past the last entry", async () => {
|
|
119
|
+
const { sub } = render();
|
|
120
|
+
const handle = sub.getValue().handleKeyDown;
|
|
121
|
+
|
|
122
|
+
handle(makeKeyEvent("ArrowDown"));
|
|
123
|
+
handle(makeKeyEvent("ArrowDown"));
|
|
124
|
+
handle(makeKeyEvent("ArrowDown"));
|
|
125
|
+
await tick();
|
|
126
|
+
|
|
127
|
+
expect(sub.getValue().highlightedIndex).toBe(0);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("moves the highlight backward on ArrowUp", async () => {
|
|
131
|
+
const { sub } = render();
|
|
132
|
+
const handle = sub.getValue().handleKeyDown;
|
|
133
|
+
|
|
134
|
+
handle(makeKeyEvent("ArrowDown"));
|
|
135
|
+
await tick();
|
|
136
|
+
handle(makeKeyEvent("ArrowUp"));
|
|
137
|
+
await tick();
|
|
138
|
+
|
|
139
|
+
expect(sub.getValue().highlightedIndex).toBe(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("wraps the highlight to the bottom on ArrowUp from the first entry", async () => {
|
|
143
|
+
const { sub } = render();
|
|
144
|
+
|
|
145
|
+
sub.getValue().handleKeyDown(makeKeyEvent("ArrowUp"));
|
|
146
|
+
await tick();
|
|
147
|
+
|
|
148
|
+
expect(sub.getValue().highlightedIndex).toBe(2);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("keeps the highlight at 0 on ArrowDown when navigableList is empty", async () => {
|
|
152
|
+
const { sub } = render({ navigableList: [] });
|
|
153
|
+
const e = makeKeyEvent("ArrowDown");
|
|
154
|
+
|
|
155
|
+
const consumed = sub.getValue().handleKeyDown(e);
|
|
156
|
+
await tick();
|
|
157
|
+
|
|
158
|
+
expect(consumed).toBe(true);
|
|
159
|
+
expect(e.preventDefault).toHaveBeenCalled();
|
|
160
|
+
expect(sub.getValue().highlightedIndex).toBe(0);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("selects the highlighted item on Enter", () => {
|
|
164
|
+
const { sub, props } = render();
|
|
165
|
+
const e = makeKeyEvent("Enter");
|
|
166
|
+
|
|
167
|
+
const consumed = sub.getValue().handleKeyDown(e);
|
|
168
|
+
|
|
169
|
+
expect(consumed).toBe(true);
|
|
170
|
+
expect(e.preventDefault).toHaveBeenCalled();
|
|
171
|
+
expect(props.selectItem).toHaveBeenCalledWith(props.navigableList[0]);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("lets Shift+Enter pass through for newline insertion", () => {
|
|
175
|
+
const { sub, props } = render();
|
|
176
|
+
const e = makeKeyEvent("Enter", true);
|
|
177
|
+
|
|
178
|
+
const consumed = sub.getValue().handleKeyDown(e);
|
|
179
|
+
|
|
180
|
+
expect(consumed).toBe(false);
|
|
181
|
+
expect(e.preventDefault).not.toHaveBeenCalled();
|
|
182
|
+
expect(props.selectItem).not.toHaveBeenCalled();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("closes the popover on Escape", () => {
|
|
186
|
+
const { sub, props } = render();
|
|
187
|
+
const e = makeKeyEvent("Escape");
|
|
188
|
+
|
|
189
|
+
const consumed = sub.getValue().handleKeyDown(e);
|
|
190
|
+
|
|
191
|
+
expect(consumed).toBe(true);
|
|
192
|
+
expect(e.preventDefault).toHaveBeenCalled();
|
|
193
|
+
expect(props.close).toHaveBeenCalled();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("drills back on Backspace when a category is active and the query is empty", () => {
|
|
197
|
+
const { sub, props } = render({
|
|
198
|
+
activeCategoryId: "cat-1",
|
|
199
|
+
query: "",
|
|
200
|
+
});
|
|
201
|
+
const e = makeKeyEvent("Backspace");
|
|
202
|
+
|
|
203
|
+
const consumed = sub.getValue().handleKeyDown(e);
|
|
204
|
+
|
|
205
|
+
expect(consumed).toBe(true);
|
|
206
|
+
expect(e.preventDefault).toHaveBeenCalled();
|
|
207
|
+
expect(props.goBack).toHaveBeenCalled();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("lets Backspace pass through when the query is non-empty", () => {
|
|
211
|
+
const { sub, props } = render({
|
|
212
|
+
activeCategoryId: "cat-1",
|
|
213
|
+
query: "foo",
|
|
214
|
+
});
|
|
215
|
+
const e = makeKeyEvent("Backspace");
|
|
216
|
+
|
|
217
|
+
const consumed = sub.getValue().handleKeyDown(e);
|
|
218
|
+
|
|
219
|
+
expect(consumed).toBe(false);
|
|
220
|
+
expect(e.preventDefault).not.toHaveBeenCalled();
|
|
221
|
+
expect(props.goBack).not.toHaveBeenCalled();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("lets Backspace pass through when no category is active", () => {
|
|
225
|
+
const { sub, props } = render({
|
|
226
|
+
activeCategoryId: null,
|
|
227
|
+
query: "",
|
|
228
|
+
});
|
|
229
|
+
const e = makeKeyEvent("Backspace");
|
|
230
|
+
|
|
231
|
+
const consumed = sub.getValue().handleKeyDown(e);
|
|
232
|
+
|
|
233
|
+
expect(consumed).toBe(false);
|
|
234
|
+
expect(props.goBack).not.toHaveBeenCalled();
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -5,14 +5,21 @@ import {
|
|
|
5
5
|
type ComponentRef,
|
|
6
6
|
forwardRef,
|
|
7
7
|
type ComponentPropsWithoutRef,
|
|
8
|
+
type ForwardedRef,
|
|
8
9
|
useCallback,
|
|
9
10
|
} from "react";
|
|
10
11
|
import { useAui, useAuiState } from "@assistant-ui/store";
|
|
11
12
|
import { useManagedRef } from "../../utils/hooks/useManagedRef";
|
|
12
|
-
import { useSizeHandle } from "../../utils/hooks/useSizeHandle";
|
|
13
13
|
import { useComposedRefs } from "@radix-ui/react-compose-refs";
|
|
14
|
-
import {
|
|
15
|
-
|
|
14
|
+
import {
|
|
15
|
+
useThreadViewport,
|
|
16
|
+
useThreadViewportStore,
|
|
17
|
+
} from "../../context/react/ThreadViewportContext";
|
|
18
|
+
import { parseCssLength } from "../thread/topAnchor/topAnchorUtils";
|
|
19
|
+
|
|
20
|
+
type ThreadViewportStore = NonNullable<
|
|
21
|
+
ReturnType<typeof useThreadViewportStore>
|
|
22
|
+
>;
|
|
16
23
|
|
|
17
24
|
const useIsHoveringRef = () => {
|
|
18
25
|
const aui = useAui();
|
|
@@ -47,54 +54,126 @@ const useIsHoveringRef = () => {
|
|
|
47
54
|
return useManagedRef(callbackRef);
|
|
48
55
|
};
|
|
49
56
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
*/
|
|
54
|
-
const useMessageViewportRef = () => {
|
|
55
|
-
const turnAnchor = useThreadViewport((s) => s.turnAnchor);
|
|
56
|
-
const registerUserHeight = useThreadViewport(
|
|
57
|
-
(s) => s.registerUserMessageHeight,
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
// inset rules:
|
|
61
|
-
// - the previous user message before the last assistant message registers its full height
|
|
62
|
-
const shouldRegisterAsInset = useAuiState(
|
|
57
|
+
const useIsTopAnchorUser = () => {
|
|
58
|
+
const activeAnchorId = useThreadViewport((s) => s.topAnchorTurn?.anchorId);
|
|
59
|
+
return useAuiState(
|
|
63
60
|
(s) =>
|
|
64
|
-
turnAnchor === "top" &&
|
|
65
61
|
s.message.role === "user" &&
|
|
62
|
+
s.message.index > 0 &&
|
|
66
63
|
s.message.index === s.thread.messages.length - 2 &&
|
|
67
|
-
s.thread.messages.at(-1)?.role === "assistant"
|
|
64
|
+
s.thread.messages.at(-1)?.role === "assistant" &&
|
|
65
|
+
(s.message.id === activeAnchorId || s.thread.isRunning),
|
|
68
66
|
);
|
|
67
|
+
};
|
|
69
68
|
|
|
70
|
-
|
|
69
|
+
const useIsTopAnchorTarget = () => {
|
|
70
|
+
const activeTargetId = useThreadViewport((s) => s.topAnchorTurn?.targetId);
|
|
71
|
+
return useAuiState(
|
|
72
|
+
(s) =>
|
|
73
|
+
s.message.isLast &&
|
|
74
|
+
s.message.role === "assistant" &&
|
|
75
|
+
s.message.index >= 1 &&
|
|
76
|
+
s.thread.messages.at(s.message.index - 1)?.role === "user" &&
|
|
77
|
+
(s.message.id === activeTargetId || s.thread.isRunning),
|
|
78
|
+
);
|
|
79
|
+
};
|
|
71
80
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
81
|
+
const useTopAnchorUserRef = (
|
|
82
|
+
active: boolean,
|
|
83
|
+
threadViewportStore: ThreadViewportStore,
|
|
84
|
+
) => {
|
|
85
|
+
const callback = useCallback(
|
|
86
|
+
(el: HTMLElement) => {
|
|
87
|
+
if (!active) return;
|
|
88
|
+
return threadViewportStore.getState().registerAnchorElement(el);
|
|
89
|
+
},
|
|
90
|
+
[active, threadViewportStore],
|
|
75
91
|
);
|
|
92
|
+
|
|
93
|
+
return useManagedRef<HTMLElement>(callback);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const useTopAnchorTargetRef = ({
|
|
97
|
+
active,
|
|
98
|
+
threadViewportStore,
|
|
99
|
+
}: {
|
|
100
|
+
active: boolean;
|
|
101
|
+
threadViewportStore: ThreadViewportStore;
|
|
102
|
+
}) => {
|
|
103
|
+
const targetRefCallback = useCallback(
|
|
104
|
+
(el: HTMLElement) => {
|
|
105
|
+
if (!active) return;
|
|
106
|
+
const state = threadViewportStore.getState();
|
|
107
|
+
const clamp = state.topAnchorMessageClamp;
|
|
108
|
+
|
|
109
|
+
return state.registerAnchorTargetElement(el, {
|
|
110
|
+
tallerThan: parseCssLength(clamp.tallerThan, el),
|
|
111
|
+
visibleHeight: parseCssLength(clamp.visibleHeight, el),
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
[active, threadViewportStore],
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return useManagedRef<HTMLElement>(targetRefCallback);
|
|
76
118
|
};
|
|
77
119
|
|
|
78
120
|
export namespace MessagePrimitiveRoot {
|
|
79
121
|
export type Element = ComponentRef<typeof Primitive.div>;
|
|
80
|
-
|
|
81
|
-
* Props for the MessagePrimitive.Root component.
|
|
82
|
-
* Accepts all standard div element props plus optional viewport slack tuning.
|
|
83
|
-
*/
|
|
84
|
-
export type Props = ComponentPropsWithoutRef<typeof Primitive.div> & {
|
|
85
|
-
/**
|
|
86
|
-
* Threshold at which the user message height clamps to the offset.
|
|
87
|
-
* @default "10em"
|
|
88
|
-
*/
|
|
89
|
-
fillClampThreshold?: string | undefined;
|
|
90
|
-
/**
|
|
91
|
-
* Offset used when clamping large user messages.
|
|
92
|
-
* @default "6em"
|
|
93
|
-
*/
|
|
94
|
-
fillClampOffset?: string | undefined;
|
|
95
|
-
};
|
|
122
|
+
export type Props = ComponentPropsWithoutRef<typeof Primitive.div>;
|
|
96
123
|
}
|
|
97
124
|
|
|
125
|
+
type MessagePrimitiveRootInternalProps = MessagePrimitiveRoot.Props & {
|
|
126
|
+
forwardedRef: ForwardedRef<MessagePrimitiveRoot.Element>;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const MessagePrimitiveRootDefault = ({
|
|
130
|
+
forwardedRef,
|
|
131
|
+
...props
|
|
132
|
+
}: MessagePrimitiveRootInternalProps) => {
|
|
133
|
+
const isHoveringRef = useIsHoveringRef();
|
|
134
|
+
const ref = useComposedRefs<HTMLDivElement>(forwardedRef, isHoveringRef);
|
|
135
|
+
const messageId = useAuiState((s) => s.message.id);
|
|
136
|
+
|
|
137
|
+
return <Primitive.div {...props} ref={ref} data-message-id={messageId} />;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const MessagePrimitiveRootTopAnchor = ({
|
|
141
|
+
forwardedRef,
|
|
142
|
+
threadViewportStore,
|
|
143
|
+
...props
|
|
144
|
+
}: MessagePrimitiveRootInternalProps & {
|
|
145
|
+
threadViewportStore: ThreadViewportStore;
|
|
146
|
+
}) => {
|
|
147
|
+
const isHoveringRef = useIsHoveringRef();
|
|
148
|
+
const isTopAnchorUser = useIsTopAnchorUser();
|
|
149
|
+
const isTopAnchorTarget = useIsTopAnchorTarget();
|
|
150
|
+
const topAnchorUserRef = useTopAnchorUserRef(
|
|
151
|
+
isTopAnchorUser,
|
|
152
|
+
threadViewportStore,
|
|
153
|
+
);
|
|
154
|
+
const topAnchorTargetRef = useTopAnchorTargetRef({
|
|
155
|
+
active: isTopAnchorTarget,
|
|
156
|
+
threadViewportStore,
|
|
157
|
+
});
|
|
158
|
+
const ref = useComposedRefs<HTMLDivElement>(
|
|
159
|
+
forwardedRef,
|
|
160
|
+
isHoveringRef,
|
|
161
|
+
topAnchorUserRef,
|
|
162
|
+
topAnchorTargetRef,
|
|
163
|
+
);
|
|
164
|
+
const messageId = useAuiState((s) => s.message.id);
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<Primitive.div
|
|
168
|
+
{...props}
|
|
169
|
+
ref={ref}
|
|
170
|
+
data-message-id={messageId}
|
|
171
|
+
data-aui-top-anchor-user={isTopAnchorUser ? "" : undefined}
|
|
172
|
+
data-aui-top-anchor-target={isTopAnchorTarget ? "" : undefined}
|
|
173
|
+
/>
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
98
177
|
/**
|
|
99
178
|
* The root container component for a message.
|
|
100
179
|
*
|
|
@@ -102,8 +181,10 @@ export namespace MessagePrimitiveRoot {
|
|
|
102
181
|
* hover state management for the message. It automatically tracks when the user
|
|
103
182
|
* is hovering over the message, which can be used by child components like action bars.
|
|
104
183
|
*
|
|
105
|
-
* When `turnAnchor="top"` is set on the viewport, this component
|
|
106
|
-
* registers itself as the
|
|
184
|
+
* When `turnAnchor="top"` is set on the viewport, this component automatically
|
|
185
|
+
* registers itself as the top-anchor user message (when it's the previous user
|
|
186
|
+
* message) or as the top-anchor target (when it's the streaming assistant
|
|
187
|
+
* response). No additional component is required.
|
|
107
188
|
*
|
|
108
189
|
* @example
|
|
109
190
|
* ```tsx
|
|
@@ -119,24 +200,21 @@ export namespace MessagePrimitiveRoot {
|
|
|
119
200
|
export const MessagePrimitiveRoot = forwardRef<
|
|
120
201
|
MessagePrimitiveRoot.Element,
|
|
121
202
|
MessagePrimitiveRoot.Props
|
|
122
|
-
>((
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
forwardRef,
|
|
127
|
-
isHoveringRef,
|
|
128
|
-
anchorUserMessageRef,
|
|
129
|
-
);
|
|
130
|
-
const messageId = useAuiState((s) => s.message.id);
|
|
203
|
+
>((props, forwardedRef) => {
|
|
204
|
+
const threadViewportStore = useThreadViewportStore();
|
|
205
|
+
// turnAnchor is initial-only viewport config (see ThreadViewportProvider).
|
|
206
|
+
const turnAnchor = threadViewportStore.getState().turnAnchor;
|
|
131
207
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
208
|
+
if (turnAnchor === "top") {
|
|
209
|
+
return (
|
|
210
|
+
<MessagePrimitiveRootTopAnchor
|
|
211
|
+
{...props}
|
|
212
|
+
forwardedRef={forwardedRef}
|
|
213
|
+
threadViewportStore={threadViewportStore}
|
|
214
|
+
/>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
return <MessagePrimitiveRootDefault {...props} forwardedRef={forwardedRef} />;
|
|
140
218
|
});
|
|
141
219
|
|
|
142
220
|
MessagePrimitiveRoot.displayName = "MessagePrimitive.Root";
|