@assistant-ui/vue 0.0.0-stage → 0.0.1
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/LICENSE +21 -0
- package/README.md +90 -2
- package/dist/AuiIf.d.ts +27 -0
- package/dist/AuiIf.d.ts.map +1 -0
- package/dist/AuiIf.js +30 -0
- package/dist/AuiIf.js.map +1 -0
- package/dist/AuiProvider.d.ts +41 -0
- package/dist/AuiProvider.d.ts.map +1 -0
- package/dist/AuiProvider.js +61 -0
- package/dist/AuiProvider.js.map +1 -0
- package/dist/context.d.ts +10 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +17 -0
- package/dist/context.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/primitives/AttachmentByIndexProvider.d.ts +30 -0
- package/dist/primitives/AttachmentByIndexProvider.d.ts.map +1 -0
- package/dist/primitives/AttachmentByIndexProvider.js +63 -0
- package/dist/primitives/AttachmentByIndexProvider.js.map +1 -0
- package/dist/primitives/ChainOfThoughtPrimitiveAccordionTrigger.d.ts +11 -0
- package/dist/primitives/ChainOfThoughtPrimitiveAccordionTrigger.d.ts.map +1 -0
- package/dist/primitives/ChainOfThoughtPrimitiveAccordionTrigger.js +31 -0
- package/dist/primitives/ChainOfThoughtPrimitiveAccordionTrigger.js.map +1 -0
- package/dist/primitives/ChainOfThoughtPrimitiveParts.d.ts +19 -0
- package/dist/primitives/ChainOfThoughtPrimitiveParts.d.ts.map +1 -0
- package/dist/primitives/ChainOfThoughtPrimitiveParts.js +85 -0
- package/dist/primitives/ChainOfThoughtPrimitiveParts.js.map +1 -0
- package/dist/primitives/ComposerPrimitiveCancel.d.ts +12 -0
- package/dist/primitives/ComposerPrimitiveCancel.d.ts.map +1 -0
- package/dist/primitives/ComposerPrimitiveCancel.js +33 -0
- package/dist/primitives/ComposerPrimitiveCancel.js.map +1 -0
- package/dist/primitives/ComposerPrimitiveInput.d.ts +26 -0
- package/dist/primitives/ComposerPrimitiveInput.d.ts.map +1 -0
- package/dist/primitives/ComposerPrimitiveInput.js +57 -0
- package/dist/primitives/ComposerPrimitiveInput.js.map +1 -0
- package/dist/primitives/ComposerPrimitiveSend.d.ts +13 -0
- package/dist/primitives/ComposerPrimitiveSend.d.ts.map +1 -0
- package/dist/primitives/ComposerPrimitiveSend.js +31 -0
- package/dist/primitives/ComposerPrimitiveSend.js.map +1 -0
- package/dist/primitives/MessageByIdProvider.d.ts +23 -0
- package/dist/primitives/MessageByIdProvider.d.ts.map +1 -0
- package/dist/primitives/MessageByIdProvider.js +70 -0
- package/dist/primitives/MessageByIdProvider.js.map +1 -0
- package/dist/primitives/MessagePrimitiveParts.d.ts +50 -0
- package/dist/primitives/MessagePrimitiveParts.d.ts.map +1 -0
- package/dist/primitives/MessagePrimitiveParts.js +93 -0
- package/dist/primitives/MessagePrimitiveParts.js.map +1 -0
- package/dist/primitives/PartByIndexProvider.d.ts +21 -0
- package/dist/primitives/PartByIndexProvider.d.ts.map +1 -0
- package/dist/primitives/PartByIndexProvider.js +50 -0
- package/dist/primitives/PartByIndexProvider.js.map +1 -0
- package/dist/primitives/ThreadListItemPrimitiveRoot.d.ts +7 -0
- package/dist/primitives/ThreadListItemPrimitiveRoot.d.ts.map +1 -0
- package/dist/primitives/ThreadListItemPrimitiveRoot.js +36 -0
- package/dist/primitives/ThreadListItemPrimitiveRoot.js.map +1 -0
- package/dist/primitives/ThreadListItemPrimitiveTrigger.d.ts +14 -0
- package/dist/primitives/ThreadListItemPrimitiveTrigger.d.ts.map +1 -0
- package/dist/primitives/ThreadListItemPrimitiveTrigger.js +58 -0
- package/dist/primitives/ThreadListItemPrimitiveTrigger.js.map +1 -0
- package/dist/primitives/ThreadListPrimitiveRoot.d.ts +7 -0
- package/dist/primitives/ThreadListPrimitiveRoot.d.ts.map +1 -0
- package/dist/primitives/ThreadListPrimitiveRoot.js +16 -0
- package/dist/primitives/ThreadListPrimitiveRoot.js.map +1 -0
- package/dist/primitives/ThreadPrimitiveMessages.d.ts +23 -0
- package/dist/primitives/ThreadPrimitiveMessages.d.ts.map +1 -0
- package/dist/primitives/ThreadPrimitiveMessages.js +37 -0
- package/dist/primitives/ThreadPrimitiveMessages.js.map +1 -0
- package/dist/primitives/ThreadPrimitiveScrollToBottom.d.ts +25 -0
- package/dist/primitives/ThreadPrimitiveScrollToBottom.d.ts.map +1 -0
- package/dist/primitives/ThreadPrimitiveScrollToBottom.js +43 -0
- package/dist/primitives/ThreadPrimitiveScrollToBottom.js.map +1 -0
- package/dist/primitives/ThreadPrimitiveViewport.d.ts +57 -0
- package/dist/primitives/ThreadPrimitiveViewport.d.ts.map +1 -0
- package/dist/primitives/ThreadPrimitiveViewport.js +174 -0
- package/dist/primitives/ThreadPrimitiveViewport.js.map +1 -0
- package/dist/primitives/ThreadPrimitiveViewportFooter.d.ts +13 -0
- package/dist/primitives/ThreadPrimitiveViewportFooter.d.ts.map +1 -0
- package/dist/primitives/ThreadPrimitiveViewportFooter.js +46 -0
- package/dist/primitives/ThreadPrimitiveViewportFooter.js.map +1 -0
- package/dist/primitives/actionBar.d.ts +41 -0
- package/dist/primitives/actionBar.d.ts.map +1 -0
- package/dist/primitives/actionBar.js +110 -0
- package/dist/primitives/actionBar.js.map +1 -0
- package/dist/primitives/attachment.d.ts +27 -0
- package/dist/primitives/attachment.d.ts.map +1 -0
- package/dist/primitives/attachment.js +63 -0
- package/dist/primitives/attachment.js.map +1 -0
- package/dist/primitives/attrDisabled.d.ts +2 -0
- package/dist/primitives/attrDisabled.d.ts.map +1 -0
- package/dist/primitives/attrDisabled.js +6 -0
- package/dist/primitives/attrDisabled.js.map +1 -0
- package/dist/primitives/branchPicker.d.ts +18 -0
- package/dist/primitives/branchPicker.d.ts.map +1 -0
- package/dist/primitives/branchPicker.js +48 -0
- package/dist/primitives/branchPicker.js.map +1 -0
- package/dist/primitives/composerAttachments.d.ts +57 -0
- package/dist/primitives/composerAttachments.d.ts.map +1 -0
- package/dist/primitives/composerAttachments.js +132 -0
- package/dist/primitives/composerAttachments.js.map +1 -0
- package/dist/primitives/error.d.ts +15 -0
- package/dist/primitives/error.d.ts.map +1 -0
- package/dist/primitives/error.js +29 -0
- package/dist/primitives/error.js.map +1 -0
- package/dist/primitives/lastValidCache.d.ts +6 -0
- package/dist/primitives/lastValidCache.d.ts.map +1 -0
- package/dist/primitives/lastValidCache.js +9 -0
- package/dist/primitives/lastValidCache.js.map +1 -0
- package/dist/primitives/message.d.ts +12 -0
- package/dist/primitives/message.d.ts.map +1 -0
- package/dist/primitives/message.js +44 -0
- package/dist/primitives/message.js.map +1 -0
- package/dist/primitives/messageAttachments.d.ts +12 -0
- package/dist/primitives/messageAttachments.d.ts.map +1 -0
- package/dist/primitives/messageAttachments.js +27 -0
- package/dist/primitives/messageAttachments.js.map +1 -0
- package/dist/primitives/stableKeys.d.ts +5 -0
- package/dist/primitives/stableKeys.d.ts.map +1 -0
- package/dist/primitives/stableKeys.js +14 -0
- package/dist/primitives/stableKeys.js.map +1 -0
- package/dist/primitives/suggestions.d.ts +66 -0
- package/dist/primitives/suggestions.d.ts.map +1 -0
- package/dist/primitives/suggestions.js +137 -0
- package/dist/primitives/suggestions.js.map +1 -0
- package/dist/primitives/thread.d.ts +11 -0
- package/dist/primitives/thread.d.ts.map +1 -0
- package/dist/primitives/thread.js +37 -0
- package/dist/primitives/thread.js.map +1 -0
- package/dist/primitives/threadList.d.ts +82 -0
- package/dist/primitives/threadList.d.ts.map +1 -0
- package/dist/primitives/threadList.js +128 -0
- package/dist/primitives/threadList.js.map +1 -0
- package/dist/primitives/threadListFocusGroup.d.ts +14 -0
- package/dist/primitives/threadListFocusGroup.d.ts.map +1 -0
- package/dist/primitives/threadListFocusGroup.js +32 -0
- package/dist/primitives/threadListFocusGroup.js.map +1 -0
- package/dist/primitives/threadListStructural.d.ts +29 -0
- package/dist/primitives/threadListStructural.d.ts.map +1 -0
- package/dist/primitives/threadListStructural.js +55 -0
- package/dist/primitives/threadListStructural.js.map +1 -0
- package/dist/primitives/useComposerSendState.d.ts +6 -0
- package/dist/primitives/useComposerSendState.d.ts.map +1 -0
- package/dist/primitives/useComposerSendState.js +15 -0
- package/dist/primitives/useComposerSendState.js.map +1 -0
- package/dist/primitives/viewportContext.d.ts +12 -0
- package/dist/primitives/viewportContext.d.ts.map +1 -0
- package/dist/primitives/viewportContext.js +6 -0
- package/dist/primitives/viewportContext.js.map +1 -0
- package/dist/useAui.d.ts +15 -0
- package/dist/useAui.d.ts.map +1 -0
- package/dist/useAui.js +21 -0
- package/dist/useAui.js.map +1 -0
- package/dist/useAuiEvent.d.ts +17 -0
- package/dist/useAuiEvent.d.ts.map +1 -0
- package/dist/useAuiEvent.js +52 -0
- package/dist/useAuiEvent.js.map +1 -0
- package/dist/useAuiState.d.ts +23 -0
- package/dist/useAuiState.d.ts.map +1 -0
- package/dist/useAuiState.js +42 -0
- package/dist/useAuiState.js.map +1 -0
- package/dist/useScrollLock.d.ts +3 -0
- package/dist/useScrollLock.d.ts.map +1 -0
- package/dist/useScrollLock.js +53 -0
- package/dist/useScrollLock.js.map +1 -0
- package/package.json +64 -3
- package/src/AuiIf.ts +34 -0
- package/src/AuiProvider.ts +90 -0
- package/src/__tests__/AuiIf.test.ts +54 -0
- package/src/__tests__/AuiProvider.test.ts +266 -0
- package/src/__tests__/byindex-shrink-race.test.ts +411 -0
- package/src/__tests__/dist-graph.test.ts +49 -0
- package/src/__tests__/fixtures.ts +51 -0
- package/src/__tests__/primitives-actionbar.test.ts +469 -0
- package/src/__tests__/primitives-attachment.test.ts +493 -0
- package/src/__tests__/primitives-content.test.ts +749 -0
- package/src/__tests__/primitives-keyed-items.test.ts +270 -0
- package/src/__tests__/primitives-keyed-messages.test.ts +264 -0
- package/src/__tests__/primitives-structural.test.ts +573 -0
- package/src/__tests__/primitives-suggestions.test.ts +374 -0
- package/src/__tests__/primitives-threadlist.test.ts +443 -0
- package/src/__tests__/primitives-tool-ui.test.ts +579 -0
- package/src/__tests__/primitives.test.ts +361 -0
- package/src/__tests__/useAuiEvent.test.ts +159 -0
- package/src/__tests__/useAuiState.test.ts +124 -0
- package/src/context.ts +31 -0
- package/src/index.ts +87 -0
- package/src/primitives/AttachmentByIndexProvider.ts +88 -0
- package/src/primitives/ChainOfThoughtPrimitiveAccordionTrigger.test.ts +105 -0
- package/src/primitives/ChainOfThoughtPrimitiveAccordionTrigger.ts +38 -0
- package/src/primitives/ChainOfThoughtPrimitiveParts.test.ts +254 -0
- package/src/primitives/ChainOfThoughtPrimitiveParts.ts +112 -0
- package/src/primitives/ComposerPrimitiveCancel.ts +41 -0
- package/src/primitives/ComposerPrimitiveInput.ts +64 -0
- package/src/primitives/ComposerPrimitiveSend.ts +39 -0
- package/src/primitives/MessageByIdProvider.ts +89 -0
- package/src/primitives/MessagePrimitiveParts.tool-interaction.test.ts +88 -0
- package/src/primitives/MessagePrimitiveParts.ts +166 -0
- package/src/primitives/PartByIndexProvider.ts +62 -0
- package/src/primitives/ThreadListItemPrimitiveRoot.ts +57 -0
- package/src/primitives/ThreadListItemPrimitiveTrigger.ts +74 -0
- package/src/primitives/ThreadListKeyboardNav.test.ts +298 -0
- package/src/primitives/ThreadListPrimitiveRoot.ts +18 -0
- package/src/primitives/ThreadPrimitiveMessages.ts +40 -0
- package/src/primitives/ThreadPrimitiveScrollToBottom.ts +65 -0
- package/src/primitives/ThreadPrimitiveViewport.ts +233 -0
- package/src/primitives/ThreadPrimitiveViewportFooter.test.ts +305 -0
- package/src/primitives/ThreadPrimitiveViewportFooter.ts +60 -0
- package/src/primitives/actionBar.ts +159 -0
- package/src/primitives/attachment.ts +76 -0
- package/src/primitives/attrDisabled.ts +6 -0
- package/src/primitives/branchPicker.ts +77 -0
- package/src/primitives/composerAttachments.ts +181 -0
- package/src/primitives/error.ts +36 -0
- package/src/primitives/lastValidCache.ts +12 -0
- package/src/primitives/message.ts +54 -0
- package/src/primitives/messageAttachments.ts +32 -0
- package/src/primitives/stableKeys.test.ts +19 -0
- package/src/primitives/stableKeys.ts +21 -0
- package/src/primitives/suggestions.ts +179 -0
- package/src/primitives/thread.ts +46 -0
- package/src/primitives/threadList.ts +163 -0
- package/src/primitives/threadListFocusGroup.ts +56 -0
- package/src/primitives/threadListStructural.ts +86 -0
- package/src/primitives/useComposerSendState.ts +13 -0
- package/src/primitives/viewportContext.ts +16 -0
- package/src/useAui.ts +18 -0
- package/src/useAuiEvent.ts +64 -0
- package/src/useAuiState.ts +55 -0
- package/src/useScrollLock.test.ts +207 -0
- package/src/useScrollLock.ts +79 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ComputedRef } from "vue";
|
|
2
|
+
import { type AssistantState } from "@assistant-ui/store/client";
|
|
3
|
+
/**
|
|
4
|
+
* Subscribes to a slice of `AssistantState` and returns it as a computed ref.
|
|
5
|
+
*
|
|
6
|
+
* The `selector` runs on every store update; dependents re-run only when the
|
|
7
|
+
* selected slice changes by `Object.is`. Returning the entire state object is
|
|
8
|
+
* not supported and throws — select a specific field instead, or compose
|
|
9
|
+
* multiple `useAuiState` calls. Returning a fresh object or array literal
|
|
10
|
+
* re-triggers dependents on every update; select primitives or a memoized
|
|
11
|
+
* reference.
|
|
12
|
+
*
|
|
13
|
+
* Scopes that may be unavailable can be read via `s.optional.<scope>`, which
|
|
14
|
+
* resolves to `undefined` instead of throwing.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const isRunning = useAuiState((s) => s.thread.isRunning);
|
|
19
|
+
* // template: <button :disabled="isRunning">…</button>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare const useAuiState: <T>(selector: (state: AssistantState) => T) => ComputedRef<T>;
|
|
23
|
+
//# sourceMappingURL=useAuiState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAuiState.d.ts","sourceRoot":"","sources":["../src/useAuiState.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,KAAK,CAAC;AAC7E,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,4BAA4B,CAAC;AAGpC;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,EAC3B,UAAU,CAAC,KAAK,EAAE,cAAc,KAAK,CAAC,KACrC,WAAW,CAAC,CAAC,CA0Bf,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useAuiContext } from "./context.js";
|
|
2
|
+
import { computed, onScopeDispose, shallowRef } from "vue";
|
|
3
|
+
import { getProxiedAssistantState } from "@assistant-ui/store/client";
|
|
4
|
+
//#region src/useAuiState.ts
|
|
5
|
+
/**
|
|
6
|
+
* Subscribes to a slice of `AssistantState` and returns it as a computed ref.
|
|
7
|
+
*
|
|
8
|
+
* The `selector` runs on every store update; dependents re-run only when the
|
|
9
|
+
* selected slice changes by `Object.is`. Returning the entire state object is
|
|
10
|
+
* not supported and throws — select a specific field instead, or compose
|
|
11
|
+
* multiple `useAuiState` calls. Returning a fresh object or array literal
|
|
12
|
+
* re-triggers dependents on every update; select primitives or a memoized
|
|
13
|
+
* reference.
|
|
14
|
+
*
|
|
15
|
+
* Scopes that may be unavailable can be read via `s.optional.<scope>`, which
|
|
16
|
+
* resolves to `undefined` instead of throwing.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const isRunning = useAuiState((s) => s.thread.isRunning);
|
|
21
|
+
* // template: <button :disabled="isRunning">…</button>
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
const useAuiState = (selector) => {
|
|
25
|
+
const { source } = useAuiContext();
|
|
26
|
+
const version = shallowRef(0);
|
|
27
|
+
const unsubscribe = source.subscribe(() => {
|
|
28
|
+
version.value++;
|
|
29
|
+
});
|
|
30
|
+
onScopeDispose(unsubscribe);
|
|
31
|
+
return computed(() => {
|
|
32
|
+
version.value;
|
|
33
|
+
const state = getProxiedAssistantState(source.getClient());
|
|
34
|
+
const slice = selector(state);
|
|
35
|
+
if (typeof slice === "object" && slice !== null && (slice === state || slice === state.optional)) throw new Error("You tried to return the entire AssistantState. This is not supported due to technical limitations.");
|
|
36
|
+
return slice;
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
export { useAuiState };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=useAuiState.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAuiState.js","names":[],"sources":["../src/useAuiState.ts"],"sourcesContent":["import { computed, onScopeDispose, shallowRef, type ComputedRef } from \"vue\";\nimport {\n getProxiedAssistantState,\n type AssistantState,\n} from \"@assistant-ui/store/client\";\nimport { useAuiContext } from \"./context\";\n\n/**\n * Subscribes to a slice of `AssistantState` and returns it as a computed ref.\n *\n * The `selector` runs on every store update; dependents re-run only when the\n * selected slice changes by `Object.is`. Returning the entire state object is\n * not supported and throws — select a specific field instead, or compose\n * multiple `useAuiState` calls. Returning a fresh object or array literal\n * re-triggers dependents on every update; select primitives or a memoized\n * reference.\n *\n * Scopes that may be unavailable can be read via `s.optional.<scope>`, which\n * resolves to `undefined` instead of throwing.\n *\n * @example\n * ```ts\n * const isRunning = useAuiState((s) => s.thread.isRunning);\n * // template: <button :disabled=\"isRunning\">…</button>\n * ```\n */\nexport const useAuiState = <T>(\n selector: (state: AssistantState) => T,\n): ComputedRef<T> => {\n const { source } = useAuiContext();\n\n const version = shallowRef(0);\n const unsubscribe = source.subscribe(() => {\n version.value++;\n });\n onScopeDispose(unsubscribe);\n\n return computed(() => {\n void version.value;\n const state = getProxiedAssistantState(source.getClient());\n const slice = selector(state);\n\n if (\n typeof slice === \"object\" &&\n slice !== null &&\n ((slice as unknown) === state || (slice as unknown) === state.optional)\n ) {\n throw new Error(\n \"You tried to return the entire AssistantState. This is not supported due to technical limitations.\",\n );\n }\n\n return slice;\n });\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,eACX,aACmB;CACnB,MAAM,EAAE,WAAW,cAAc;CAEjC,MAAM,UAAU,WAAW,CAAC;CAC5B,MAAM,cAAc,OAAO,gBAAgB;EACzC,QAAQ;CACV,CAAC;CACD,eAAe,WAAW;CAE1B,OAAO,eAAe;EACpB,QAAa;EACb,MAAM,QAAQ,yBAAyB,OAAO,UAAU,CAAC;EACzD,MAAM,QAAQ,SAAS,KAAK;EAE5B,IACE,OAAO,UAAU,YACjB,UAAU,SACR,UAAsB,SAAU,UAAsB,MAAM,WAE9D,MAAM,IAAI,MACR,oGACF;EAGF,OAAO;CACT,CAAC;AACH"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useScrollLock.d.ts","sourceRoot":"","sources":["../src/useScrollLock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAY/C,eAAO,MAAM,aAAa,GACxB,QAAQ,GAAG,CAAC,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC,EAC3C,mBAAmB,MAAM,eAgE1B,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { onScopeDispose } from "vue";
|
|
2
|
+
//#region src/useScrollLock.ts
|
|
3
|
+
const findScrollableAncestor = (element) => {
|
|
4
|
+
let current = element;
|
|
5
|
+
while (current) {
|
|
6
|
+
const { overflowY } = getComputedStyle(current);
|
|
7
|
+
if (overflowY === "scroll" || overflowY === "auto") return current;
|
|
8
|
+
current = current.parentElement;
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
};
|
|
12
|
+
const useScrollLock = (target, animationDuration) => {
|
|
13
|
+
let cleanup = null;
|
|
14
|
+
onScopeDispose(() => cleanup?.());
|
|
15
|
+
return () => {
|
|
16
|
+
cleanup?.();
|
|
17
|
+
const scrollContainer = findScrollableAncestor(target.value);
|
|
18
|
+
if (!scrollContainer) {
|
|
19
|
+
cleanup = null;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const scrollPosition = scrollContainer.scrollTop;
|
|
23
|
+
const scrollbarWidth = scrollContainer.style.scrollbarWidth;
|
|
24
|
+
const computed = getComputedStyle(scrollContainer);
|
|
25
|
+
const paddingSide = computed.direction === "rtl" ? "paddingLeft" : "paddingRight";
|
|
26
|
+
const previousPadding = scrollContainer.style[paddingSide];
|
|
27
|
+
const elementScrollbarSize = scrollContainer.offsetWidth - scrollContainer.clientWidth - parseFloat(computed.borderLeftWidth) - parseFloat(computed.borderRightWidth);
|
|
28
|
+
const ownerDocument = scrollContainer.ownerDocument;
|
|
29
|
+
const scrollbarSize = (scrollContainer === ownerDocument.documentElement || scrollContainer === ownerDocument.body) && elementScrollbarSize <= 0 ? (ownerDocument.defaultView?.innerWidth ?? 0) - ownerDocument.documentElement.clientWidth : elementScrollbarSize;
|
|
30
|
+
scrollContainer.style.scrollbarWidth = "none";
|
|
31
|
+
if (scrollbarSize > 0) scrollContainer.style[paddingSide] = `${parseFloat(computed[paddingSide]) + scrollbarSize}px`;
|
|
32
|
+
const restoreStyles = () => {
|
|
33
|
+
scrollContainer.style.scrollbarWidth = scrollbarWidth;
|
|
34
|
+
scrollContainer.style[paddingSide] = previousPadding;
|
|
35
|
+
};
|
|
36
|
+
const resetPosition = () => scrollContainer.scrollTop = scrollPosition;
|
|
37
|
+
scrollContainer.addEventListener("scroll", resetPosition);
|
|
38
|
+
const timeoutId = setTimeout(() => {
|
|
39
|
+
scrollContainer.removeEventListener("scroll", resetPosition);
|
|
40
|
+
restoreStyles();
|
|
41
|
+
cleanup = null;
|
|
42
|
+
}, animationDuration);
|
|
43
|
+
cleanup = () => {
|
|
44
|
+
clearTimeout(timeoutId);
|
|
45
|
+
scrollContainer.removeEventListener("scroll", resetPosition);
|
|
46
|
+
restoreStyles();
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
export { useScrollLock };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=useScrollLock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useScrollLock.js","names":[],"sources":["../src/useScrollLock.ts"],"sourcesContent":["import { onScopeDispose, type Ref } from \"vue\";\n\nconst findScrollableAncestor = (element: HTMLElement | null | undefined) => {\n let current = element;\n while (current) {\n const { overflowY } = getComputedStyle(current);\n if (overflowY === \"scroll\" || overflowY === \"auto\") return current;\n current = current.parentElement;\n }\n return null;\n};\n\nexport const useScrollLock = (\n target: Ref<HTMLElement | null | undefined>,\n animationDuration: number,\n) => {\n let cleanup: (() => void) | null = null;\n\n onScopeDispose(() => cleanup?.());\n\n return () => {\n cleanup?.();\n\n const scrollContainer = findScrollableAncestor(target.value);\n if (!scrollContainer) {\n cleanup = null;\n return;\n }\n\n const scrollPosition = scrollContainer.scrollTop;\n const scrollbarWidth = scrollContainer.style.scrollbarWidth;\n const computed = getComputedStyle(scrollContainer);\n const paddingSide =\n computed.direction === \"rtl\" ? \"paddingLeft\" : \"paddingRight\";\n const previousPadding = scrollContainer.style[paddingSide];\n const elementScrollbarSize =\n scrollContainer.offsetWidth -\n scrollContainer.clientWidth -\n parseFloat(computed.borderLeftWidth) -\n parseFloat(computed.borderRightWidth);\n // Root offsetWidth excludes the viewport scrollbar, so use the viewport\n // width only when the element's own gutter is absent.\n const ownerDocument = scrollContainer.ownerDocument;\n const isRootScroller =\n scrollContainer === ownerDocument.documentElement ||\n scrollContainer === ownerDocument.body;\n const scrollbarSize =\n isRootScroller && elementScrollbarSize <= 0\n ? (ownerDocument.defaultView?.innerWidth ?? 0) -\n ownerDocument.documentElement.clientWidth\n : elementScrollbarSize;\n\n scrollContainer.style.scrollbarWidth = \"none\";\n if (scrollbarSize > 0) {\n scrollContainer.style[paddingSide] = `${\n parseFloat(computed[paddingSide]) + scrollbarSize\n }px`;\n }\n\n const restoreStyles = () => {\n scrollContainer.style.scrollbarWidth = scrollbarWidth;\n scrollContainer.style[paddingSide] = previousPadding;\n };\n const resetPosition = () => (scrollContainer.scrollTop = scrollPosition);\n scrollContainer.addEventListener(\"scroll\", resetPosition);\n\n const timeoutId = setTimeout(() => {\n scrollContainer.removeEventListener(\"scroll\", resetPosition);\n restoreStyles();\n cleanup = null;\n }, animationDuration);\n\n cleanup = () => {\n clearTimeout(timeoutId);\n scrollContainer.removeEventListener(\"scroll\", resetPosition);\n restoreStyles();\n };\n };\n};\n"],"mappings":";;AAEA,MAAM,0BAA0B,YAA4C;CAC1E,IAAI,UAAU;CACd,OAAO,SAAS;EACd,MAAM,EAAE,cAAc,iBAAiB,OAAO;EAC9C,IAAI,cAAc,YAAY,cAAc,QAAQ,OAAO;EAC3D,UAAU,QAAQ;CACpB;CACA,OAAO;AACT;AAEA,MAAa,iBACX,QACA,sBACG;CACH,IAAI,UAA+B;CAEnC,qBAAqB,UAAU,CAAC;CAEhC,aAAa;EACX,UAAU;EAEV,MAAM,kBAAkB,uBAAuB,OAAO,KAAK;EAC3D,IAAI,CAAC,iBAAiB;GACpB,UAAU;GACV;EACF;EAEA,MAAM,iBAAiB,gBAAgB;EACvC,MAAM,iBAAiB,gBAAgB,MAAM;EAC7C,MAAM,WAAW,iBAAiB,eAAe;EACjD,MAAM,cACJ,SAAS,cAAc,QAAQ,gBAAgB;EACjD,MAAM,kBAAkB,gBAAgB,MAAM;EAC9C,MAAM,uBACJ,gBAAgB,cAChB,gBAAgB,cAChB,WAAW,SAAS,eAAe,IACnC,WAAW,SAAS,gBAAgB;EAGtC,MAAM,gBAAgB,gBAAgB;EAItC,MAAM,iBAFJ,oBAAoB,cAAc,mBAClC,oBAAoB,cAAc,SAEhB,wBAAwB,KACrC,cAAc,aAAa,cAAc,KAC1C,cAAc,gBAAgB,cAC9B;EAEN,gBAAgB,MAAM,iBAAiB;EACvC,IAAI,gBAAgB,GAClB,gBAAgB,MAAM,eAAe,GACnC,WAAW,SAAS,YAAY,IAAI,cACrC;EAGH,MAAM,sBAAsB;GAC1B,gBAAgB,MAAM,iBAAiB;GACvC,gBAAgB,MAAM,eAAe;EACvC;EACA,MAAM,sBAAuB,gBAAgB,YAAY;EACzD,gBAAgB,iBAAiB,UAAU,aAAa;EAExD,MAAM,YAAY,iBAAiB;GACjC,gBAAgB,oBAAoB,UAAU,aAAa;GAC3D,cAAc;GACd,UAAU;EACZ,GAAG,iBAAiB;EAEpB,gBAAgB;GACd,aAAa,SAAS;GACtB,gBAAgB,oBAAoB,UAAU,aAAa;GAC3D,cAAc;EAChB;CACF;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/vue",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Vue bindings for assistant-ui",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"assistant",
|
|
7
|
+
"ai",
|
|
8
|
+
"chat",
|
|
9
|
+
"vue",
|
|
10
|
+
"nuxt"
|
|
11
|
+
],
|
|
12
|
+
"author": "AgentbaseAI Inc.",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"src",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@assistant-ui/core": "^0.3.24",
|
|
31
|
+
"@assistant-ui/store": "^0.3.17",
|
|
32
|
+
"@assistant-ui/tap": "^0.9.21"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@types/react": "*",
|
|
36
|
+
"vue": "^3.5.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@assistant-ui/x-buildutils": "0.0.30",
|
|
40
|
+
"@types/node": "^26.6.3",
|
|
41
|
+
"@types/react": "^19.3.0",
|
|
42
|
+
"assistant-cloud": "0.2.5",
|
|
43
|
+
"jsdom": "^30.0.1",
|
|
44
|
+
"vitest": "^5.0.3",
|
|
45
|
+
"vue": "^3.5.43"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public",
|
|
49
|
+
"provenance": true
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://www.assistant-ui.com/",
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "git+https://github.com/assistant-ui/assistant-ui.git",
|
|
55
|
+
"directory": "packages/vue"
|
|
56
|
+
},
|
|
57
|
+
"bugs": {
|
|
58
|
+
"url": "https://github.com/assistant-ui/assistant-ui/issues"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "aui-build",
|
|
62
|
+
"test": "vitest run",
|
|
63
|
+
"test:coverage": "vitest run --coverage --coverage.reporter=text --coverage.reporter=html --coverage.reporter=json-summary",
|
|
64
|
+
"typecheck": "tsc --noEmit",
|
|
65
|
+
"test:watch": "vitest"
|
|
66
|
+
}
|
|
6
67
|
}
|
package/src/AuiIf.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineComponent,
|
|
3
|
+
type PropType,
|
|
4
|
+
type SlotsType,
|
|
5
|
+
type VNodeChild,
|
|
6
|
+
} from "vue";
|
|
7
|
+
import type { AssistantState } from "@assistant-ui/store/client";
|
|
8
|
+
import { useAuiState } from "./useAuiState";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Renders the default slot while `condition` selects `true` from the
|
|
12
|
+
* assistant state.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```html
|
|
16
|
+
* <AuiIf :condition="(s) => s.thread.isRunning">
|
|
17
|
+
* <StopButton />
|
|
18
|
+
* </AuiIf>
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export const AuiIf = defineComponent({
|
|
22
|
+
name: "AuiIf",
|
|
23
|
+
props: {
|
|
24
|
+
condition: {
|
|
25
|
+
type: Function as PropType<(state: AssistantState) => boolean>,
|
|
26
|
+
required: true,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
|
|
30
|
+
setup(props, { slots }) {
|
|
31
|
+
const active = useAuiState((state) => props.condition(state));
|
|
32
|
+
return () => (active.value ? slots.default?.() : null);
|
|
33
|
+
},
|
|
34
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineComponent,
|
|
3
|
+
inject,
|
|
4
|
+
onScopeDispose,
|
|
5
|
+
provide,
|
|
6
|
+
watch,
|
|
7
|
+
type PropType,
|
|
8
|
+
type SlotsType,
|
|
9
|
+
type VNodeChild,
|
|
10
|
+
} from "vue";
|
|
11
|
+
import {
|
|
12
|
+
createAssistantClient,
|
|
13
|
+
type AssistantClient,
|
|
14
|
+
type AuiConfig,
|
|
15
|
+
} from "@assistant-ui/store/client";
|
|
16
|
+
import { auiInjectionKey, createClientFacade } from "./context";
|
|
17
|
+
import { isDevelopment } from "@assistant-ui/core/store/internal";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates an `AssistantClient` from the given config and provides it to the
|
|
21
|
+
* subtree.
|
|
22
|
+
*
|
|
23
|
+
* At the top level, `config` alone creates this subtree's own client. Under a
|
|
24
|
+
* parent provider, `extends` is mandatory: pass `:extends="aui"` to extend the
|
|
25
|
+
* parent client or `:extends="null"` to isolate from it (enforced with a dev
|
|
26
|
+
* error).
|
|
27
|
+
*
|
|
28
|
+
* The `config` prop is live: passing a new config object updates element args
|
|
29
|
+
* in place (scopes keep their state) and mounts or unmounts added and removed
|
|
30
|
+
* scopes. Pass a computed config to derive it from reactive inputs. `extends`
|
|
31
|
+
* is captured when the provider mounts; remount (for example with a `key`) to
|
|
32
|
+
* change the parent.
|
|
33
|
+
*/
|
|
34
|
+
export const AuiProvider = defineComponent({
|
|
35
|
+
name: "AuiProvider",
|
|
36
|
+
props: {
|
|
37
|
+
config: {
|
|
38
|
+
type: Object as PropType<AuiConfig>,
|
|
39
|
+
required: true,
|
|
40
|
+
},
|
|
41
|
+
extends: {
|
|
42
|
+
type: Object as PropType<AssistantClient | null>,
|
|
43
|
+
default: undefined,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
|
|
47
|
+
setup(props, { slots }) {
|
|
48
|
+
const injected = inject(auiInjectionKey, null);
|
|
49
|
+
const hasExtends = props.extends !== undefined;
|
|
50
|
+
|
|
51
|
+
if (isDevelopment) {
|
|
52
|
+
if (injected && !hasExtends) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
'A parent AuiProvider exists. Pass :extends="aui" to inherit it or :extends="null" to isolate.',
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const parent = !hasExtends
|
|
60
|
+
? injected?.source
|
|
61
|
+
: props.extends === null
|
|
62
|
+
? undefined
|
|
63
|
+
: injected && props.extends === injected.aui
|
|
64
|
+
? injected.source
|
|
65
|
+
: props.extends;
|
|
66
|
+
|
|
67
|
+
const handle = createAssistantClient(
|
|
68
|
+
{
|
|
69
|
+
getConfig: () => props.config,
|
|
70
|
+
subscribe: (listener) => watch(() => props.config, listener),
|
|
71
|
+
},
|
|
72
|
+
parent ? { parent } : undefined,
|
|
73
|
+
);
|
|
74
|
+
// The provider holds the lifetime subscription that keeps the client
|
|
75
|
+
// mounted while descendants come and go
|
|
76
|
+
const release = handle.subscribe(() => {});
|
|
77
|
+
onScopeDispose(release);
|
|
78
|
+
|
|
79
|
+
const source = {
|
|
80
|
+
getClient: handle.getClient,
|
|
81
|
+
subscribe: handle.subscribe,
|
|
82
|
+
};
|
|
83
|
+
provide(auiInjectionKey, {
|
|
84
|
+
source,
|
|
85
|
+
aui: createClientFacade(source),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return () => slots.default?.();
|
|
89
|
+
},
|
|
90
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createApp, defineComponent, h, nextTick, type Component } from "vue";
|
|
3
|
+
import { flushTapSync } from "@assistant-ui/tap";
|
|
4
|
+
import { AuiConfig } from "@assistant-ui/store/client";
|
|
5
|
+
import { AuiProvider } from "../AuiProvider";
|
|
6
|
+
import { AuiIf } from "../AuiIf";
|
|
7
|
+
import { useAui } from "../useAui";
|
|
8
|
+
import { ThreadClient, type AnyClient } from "./fixtures";
|
|
9
|
+
|
|
10
|
+
const mountApp = (root: Component) => {
|
|
11
|
+
const app = createApp(root);
|
|
12
|
+
const el = document.createElement("div");
|
|
13
|
+
app.mount(el);
|
|
14
|
+
return { el, unmount: () => app.unmount() };
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe("AuiIf", () => {
|
|
18
|
+
it("renders the slot only while the condition selects true", async () => {
|
|
19
|
+
let aui!: AnyClient;
|
|
20
|
+
const Probe = defineComponent({
|
|
21
|
+
setup() {
|
|
22
|
+
aui = useAui() as AnyClient;
|
|
23
|
+
return () =>
|
|
24
|
+
h(
|
|
25
|
+
AuiIf,
|
|
26
|
+
{ condition: (s) => (s as AnyClient).thread.selected === 1 },
|
|
27
|
+
{ default: () => h("span", { id: "flag" }, "visible") },
|
|
28
|
+
);
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
const { el, unmount } = mountApp(
|
|
32
|
+
defineComponent({
|
|
33
|
+
setup: () => () =>
|
|
34
|
+
h(
|
|
35
|
+
AuiProvider,
|
|
36
|
+
{ config: AuiConfig({ thread: ThreadClient() } as never) },
|
|
37
|
+
{ default: () => h(Probe) },
|
|
38
|
+
),
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(el.querySelector("#flag")).toBeNull();
|
|
43
|
+
|
|
44
|
+
flushTapSync(() => aui.thread.setSelected(1));
|
|
45
|
+
await nextTick();
|
|
46
|
+
expect(el.querySelector("#flag")?.textContent).toBe("visible");
|
|
47
|
+
|
|
48
|
+
flushTapSync(() => aui.thread.setSelected(0));
|
|
49
|
+
await nextTick();
|
|
50
|
+
expect(el.querySelector("#flag")).toBeNull();
|
|
51
|
+
|
|
52
|
+
unmount();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
createApp,
|
|
4
|
+
defineComponent,
|
|
5
|
+
h,
|
|
6
|
+
nextTick,
|
|
7
|
+
shallowRef,
|
|
8
|
+
type Component,
|
|
9
|
+
} from "vue";
|
|
10
|
+
import { flushTapSync } from "@assistant-ui/tap";
|
|
11
|
+
import { AuiConfig, type AssistantClient } from "@assistant-ui/store/client";
|
|
12
|
+
import { AuiProvider } from "../AuiProvider";
|
|
13
|
+
import { useAui } from "../useAui";
|
|
14
|
+
import {
|
|
15
|
+
MessageClient,
|
|
16
|
+
ThreadClient,
|
|
17
|
+
messageDerived,
|
|
18
|
+
trackers,
|
|
19
|
+
type AnyClient,
|
|
20
|
+
} from "./fixtures";
|
|
21
|
+
|
|
22
|
+
const mountApp = (root: Component) => {
|
|
23
|
+
const app = createApp(root);
|
|
24
|
+
const el = document.createElement("div");
|
|
25
|
+
app.mount(el);
|
|
26
|
+
return { app, unmount: () => app.unmount() };
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const captureAui = () => {
|
|
30
|
+
let aui!: AnyClient;
|
|
31
|
+
const Probe = defineComponent({
|
|
32
|
+
setup() {
|
|
33
|
+
aui = useAui() as AnyClient;
|
|
34
|
+
return () => null;
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
return { Probe, getAui: () => aui };
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
describe("AuiProvider", () => {
|
|
41
|
+
it("provides a client built from the config", () => {
|
|
42
|
+
const { Probe, getAui } = captureAui();
|
|
43
|
+
const { unmount } = mountApp(
|
|
44
|
+
defineComponent({
|
|
45
|
+
setup: () => () =>
|
|
46
|
+
h(
|
|
47
|
+
AuiProvider,
|
|
48
|
+
{ config: AuiConfig({ thread: ThreadClient() } as never) },
|
|
49
|
+
{ default: () => h(Probe) },
|
|
50
|
+
),
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(getAui().thread.getState()).toEqual({ selected: 0 });
|
|
55
|
+
|
|
56
|
+
flushTapSync(() => getAui().thread.setSelected(1));
|
|
57
|
+
expect(getAui().thread.getState()).toEqual({ selected: 1 });
|
|
58
|
+
|
|
59
|
+
unmount();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("keeps the facade stable across structural changes", () => {
|
|
63
|
+
const { Probe, getAui } = captureAui();
|
|
64
|
+
const { unmount } = mountApp(
|
|
65
|
+
defineComponent({
|
|
66
|
+
setup: () => () =>
|
|
67
|
+
h(
|
|
68
|
+
AuiProvider,
|
|
69
|
+
{
|
|
70
|
+
config: AuiConfig({
|
|
71
|
+
thread: ThreadClient(),
|
|
72
|
+
message: messageDerived(),
|
|
73
|
+
} as never),
|
|
74
|
+
},
|
|
75
|
+
{ default: () => h(Probe) },
|
|
76
|
+
),
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const aui = getAui();
|
|
81
|
+
expect(aui.message.getState().id).toBe("m0");
|
|
82
|
+
|
|
83
|
+
flushTapSync(() => aui.thread.setSelected(1));
|
|
84
|
+
|
|
85
|
+
expect(getAui()).toBe(aui);
|
|
86
|
+
expect(aui.message.getState().id).toBe("m1");
|
|
87
|
+
|
|
88
|
+
unmount();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("extends the parent provider's client with an explicit extends", () => {
|
|
92
|
+
const { Probe, getAui } = captureAui();
|
|
93
|
+
const Nested = defineComponent({
|
|
94
|
+
setup(_, { slots }) {
|
|
95
|
+
const aui = useAui();
|
|
96
|
+
return () =>
|
|
97
|
+
h(
|
|
98
|
+
AuiProvider,
|
|
99
|
+
{
|
|
100
|
+
config: AuiConfig({ message: messageDerived() } as never),
|
|
101
|
+
extends: aui,
|
|
102
|
+
},
|
|
103
|
+
{ default: () => slots.default?.() },
|
|
104
|
+
);
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
const { unmount } = mountApp(
|
|
108
|
+
defineComponent({
|
|
109
|
+
setup: () => () =>
|
|
110
|
+
h(
|
|
111
|
+
AuiProvider,
|
|
112
|
+
{ config: AuiConfig({ thread: ThreadClient() } as never) },
|
|
113
|
+
{ default: () => h(Nested, null, { default: () => h(Probe) }) },
|
|
114
|
+
),
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const aui = getAui();
|
|
119
|
+
expect(aui.message.getState().id).toBe("m0");
|
|
120
|
+
expect(aui.thread.getState()).toEqual({ selected: 0 });
|
|
121
|
+
|
|
122
|
+
flushTapSync(() => aui.thread.setSelected(1));
|
|
123
|
+
expect(aui.message.getState().id).toBe("m1");
|
|
124
|
+
|
|
125
|
+
unmount();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("throws in dev when nested without an explicit extends", () => {
|
|
129
|
+
const Nested = defineComponent({
|
|
130
|
+
setup: () => () =>
|
|
131
|
+
h(
|
|
132
|
+
AuiProvider,
|
|
133
|
+
{ config: AuiConfig({ message: messageDerived() } as never) },
|
|
134
|
+
{ default: () => null },
|
|
135
|
+
),
|
|
136
|
+
});
|
|
137
|
+
expect(() =>
|
|
138
|
+
mountApp(
|
|
139
|
+
defineComponent({
|
|
140
|
+
setup: () => () =>
|
|
141
|
+
h(
|
|
142
|
+
AuiProvider,
|
|
143
|
+
{ config: AuiConfig({ thread: ThreadClient() } as never) },
|
|
144
|
+
{ default: () => h(Nested) },
|
|
145
|
+
),
|
|
146
|
+
}),
|
|
147
|
+
),
|
|
148
|
+
).toThrow("A parent AuiProvider exists.");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("isolates from the parent with extends null", () => {
|
|
152
|
+
const { Probe, getAui } = captureAui();
|
|
153
|
+
const Isolated = defineComponent({
|
|
154
|
+
setup: () => () =>
|
|
155
|
+
h(
|
|
156
|
+
AuiProvider,
|
|
157
|
+
{
|
|
158
|
+
config: AuiConfig({
|
|
159
|
+
message: MessageClient({ id: "iso" }),
|
|
160
|
+
} as never),
|
|
161
|
+
extends: null,
|
|
162
|
+
},
|
|
163
|
+
{ default: () => h(Probe) },
|
|
164
|
+
),
|
|
165
|
+
});
|
|
166
|
+
const { unmount } = mountApp(
|
|
167
|
+
defineComponent({
|
|
168
|
+
setup: () => () =>
|
|
169
|
+
h(
|
|
170
|
+
AuiProvider,
|
|
171
|
+
{ config: AuiConfig({ thread: ThreadClient() } as never) },
|
|
172
|
+
{ default: () => h(Isolated) },
|
|
173
|
+
),
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
expect(getAui().message.getState()).toEqual({ id: "iso", text: "" });
|
|
178
|
+
expect(() => getAui().thread.getState()).toThrow(
|
|
179
|
+
'The current scope does not have a "thread" property.',
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
unmount();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("applies config changes in place, keeping scope state", async () => {
|
|
186
|
+
const { Probe, getAui } = captureAui();
|
|
187
|
+
const config = shallowRef(
|
|
188
|
+
AuiConfig({ message: MessageClient({ id: "m0" }) } as never),
|
|
189
|
+
);
|
|
190
|
+
const { unmount } = mountApp(
|
|
191
|
+
defineComponent({
|
|
192
|
+
setup: () => () =>
|
|
193
|
+
h(AuiProvider, { config: config.value }, { default: () => h(Probe) }),
|
|
194
|
+
}),
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
flushTapSync(() => getAui().message.setText("draft"));
|
|
198
|
+
expect(getAui().message.getState()).toEqual({ id: "m0", text: "draft" });
|
|
199
|
+
|
|
200
|
+
config.value = AuiConfig({ message: MessageClient({ id: "m1" }) } as never);
|
|
201
|
+
await nextTick();
|
|
202
|
+
await vi.waitFor(() => {
|
|
203
|
+
expect(getAui().message.getState()).toEqual({ id: "m1", text: "draft" });
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
config.value = AuiConfig({
|
|
207
|
+
message: MessageClient({ id: "m1" }),
|
|
208
|
+
thread: ThreadClient(),
|
|
209
|
+
} as never);
|
|
210
|
+
await nextTick();
|
|
211
|
+
await vi.waitFor(() => {
|
|
212
|
+
expect(getAui().thread.getState()).toEqual({ selected: 0 });
|
|
213
|
+
});
|
|
214
|
+
expect(getAui().message.getState()).toEqual({ id: "m1", text: "draft" });
|
|
215
|
+
|
|
216
|
+
config.value = AuiConfig({ message: MessageClient({ id: "m1" }) } as never);
|
|
217
|
+
await nextTick();
|
|
218
|
+
await vi.waitFor(() => {
|
|
219
|
+
expect(() => getAui().thread.getState()).toThrow(
|
|
220
|
+
'The current scope does not have a "thread" property.',
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
unmount();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("releases the client on unmount, soft unmounting its scopes", async () => {
|
|
228
|
+
const before = trackers.threadCleanups;
|
|
229
|
+
const { Probe } = captureAui();
|
|
230
|
+
const { unmount } = mountApp(
|
|
231
|
+
defineComponent({
|
|
232
|
+
setup: () => () =>
|
|
233
|
+
h(
|
|
234
|
+
AuiProvider,
|
|
235
|
+
{ config: AuiConfig({ thread: ThreadClient() } as never) },
|
|
236
|
+
{ default: () => h(Probe) },
|
|
237
|
+
),
|
|
238
|
+
}),
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const afterMount = trackers.threadCleanups;
|
|
242
|
+
unmount();
|
|
243
|
+
await vi.waitFor(() =>
|
|
244
|
+
expect(trackers.threadCleanups).toBe(afterMount + 1),
|
|
245
|
+
);
|
|
246
|
+
expect(afterMount).toBeGreaterThanOrEqual(before);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("returns the throwing default client outside a provider", () => {
|
|
250
|
+
let aui!: AssistantClient;
|
|
251
|
+
const { unmount } = mountApp(
|
|
252
|
+
defineComponent({
|
|
253
|
+
setup() {
|
|
254
|
+
aui = useAui();
|
|
255
|
+
return () => null;
|
|
256
|
+
},
|
|
257
|
+
}),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
expect(() => (aui as AnyClient).thread.getState()).toThrow(
|
|
261
|
+
"Wrap your component in an <AuiProvider> component.",
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
unmount();
|
|
265
|
+
});
|
|
266
|
+
});
|