@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,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineComponent,
|
|
3
|
+
h,
|
|
4
|
+
mergeProps,
|
|
5
|
+
type SlotsType,
|
|
6
|
+
type VNodeChild,
|
|
7
|
+
} from "vue";
|
|
8
|
+
import { composerCancelDisabled } from "@assistant-ui/core/store/internal";
|
|
9
|
+
import { isAttrDisabled } from "./attrDisabled";
|
|
10
|
+
import { useAui } from "../useAui";
|
|
11
|
+
import { useAuiState } from "../useAuiState";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A button that cancels the current run. Disabled while nothing can be
|
|
15
|
+
* cancelled. A fallthrough click listener runs first and can veto the cancel
|
|
16
|
+
* with `preventDefault`.
|
|
17
|
+
*/
|
|
18
|
+
export const ComposerPrimitiveCancel = defineComponent({
|
|
19
|
+
name: "ComposerPrimitiveCancel",
|
|
20
|
+
inheritAttrs: false,
|
|
21
|
+
slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
|
|
22
|
+
setup(_, { attrs, slots }) {
|
|
23
|
+
const aui = useAui();
|
|
24
|
+
const disabled = useAuiState(composerCancelDisabled);
|
|
25
|
+
const onClick = (event: MouseEvent) => {
|
|
26
|
+
if (event.defaultPrevented || disabled.value || isAttrDisabled(attrs))
|
|
27
|
+
return;
|
|
28
|
+
aui.composer.cancel();
|
|
29
|
+
};
|
|
30
|
+
return () =>
|
|
31
|
+
h(
|
|
32
|
+
"button",
|
|
33
|
+
mergeProps(attrs, {
|
|
34
|
+
type: "button",
|
|
35
|
+
disabled: disabled.value || isAttrDisabled(attrs),
|
|
36
|
+
onClick,
|
|
37
|
+
}),
|
|
38
|
+
slots.default?.(),
|
|
39
|
+
);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { defineComponent, h, mergeProps, type SlotsType } from "vue";
|
|
2
|
+
import { flushTapSync } from "@assistant-ui/tap";
|
|
3
|
+
import { composerInputDisabled } from "@assistant-ui/core/store/internal";
|
|
4
|
+
import { isAttrDisabled } from "./attrDisabled";
|
|
5
|
+
import { useAui } from "../useAui";
|
|
6
|
+
import { useAuiState } from "../useAuiState";
|
|
7
|
+
import { useComposerSendState } from "./useComposerSendState";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A textarea bound to the composer text, disabled while the thread is
|
|
11
|
+
* disabled. Enter submits (Shift+Enter inserts a newline, IME composition is
|
|
12
|
+
* ignored) unless `submitOnEnter` is false; an Enter that cannot send falls
|
|
13
|
+
* through to a newline, and a fallthrough keydown listener can veto the
|
|
14
|
+
* submit with `preventDefault`. While the composer is not editing (an edit
|
|
15
|
+
* composer before `beginEdit`), the value stays controlled to the empty
|
|
16
|
+
* composer text.
|
|
17
|
+
*/
|
|
18
|
+
export const ComposerPrimitiveInput = defineComponent({
|
|
19
|
+
name: "ComposerPrimitiveInput",
|
|
20
|
+
inheritAttrs: false,
|
|
21
|
+
props: {
|
|
22
|
+
submitOnEnter: {
|
|
23
|
+
type: Boolean,
|
|
24
|
+
default: true,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
slots: Object as SlotsType<Record<string, never>>,
|
|
28
|
+
setup(props, { attrs }) {
|
|
29
|
+
const aui = useAui();
|
|
30
|
+
const text = useAuiState((s) =>
|
|
31
|
+
s.composer.isEditing ? s.composer.text : "",
|
|
32
|
+
);
|
|
33
|
+
const threadDisabled = useAuiState(composerInputDisabled);
|
|
34
|
+
const { disabled, send } = useComposerSendState();
|
|
35
|
+
|
|
36
|
+
const onInput = (event: Event) => {
|
|
37
|
+
const target = event.target as HTMLTextAreaElement;
|
|
38
|
+
if (!aui.composer.getState().isEditing) {
|
|
39
|
+
target.value = text.value;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
flushTapSync(() => aui.composer.setText(target.value));
|
|
43
|
+
};
|
|
44
|
+
const onKeydown = (event: KeyboardEvent) => {
|
|
45
|
+
if (event.defaultPrevented || !props.submitOnEnter) return;
|
|
46
|
+
if (event.key !== "Enter" || event.shiftKey || event.isComposing) return;
|
|
47
|
+
if (disabled.value || threadDisabled.value || isAttrDisabled(attrs))
|
|
48
|
+
return;
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
send();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return () =>
|
|
54
|
+
h(
|
|
55
|
+
"textarea",
|
|
56
|
+
mergeProps({ name: "input" }, attrs, {
|
|
57
|
+
value: text.value,
|
|
58
|
+
disabled: threadDisabled.value || isAttrDisabled(attrs),
|
|
59
|
+
onInput,
|
|
60
|
+
onKeydown,
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineComponent,
|
|
3
|
+
h,
|
|
4
|
+
mergeProps,
|
|
5
|
+
type SlotsType,
|
|
6
|
+
type VNodeChild,
|
|
7
|
+
} from "vue";
|
|
8
|
+
import { isAttrDisabled } from "./attrDisabled";
|
|
9
|
+
import { useComposerSendState } from "./useComposerSendState";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A button that sends the composer. Disabled while the composer cannot send,
|
|
13
|
+
* or while a run is in flight and the runtime does not queue sends. A
|
|
14
|
+
* fallthrough click listener runs first and can veto the send with
|
|
15
|
+
* `preventDefault`.
|
|
16
|
+
*/
|
|
17
|
+
export const ComposerPrimitiveSend = defineComponent({
|
|
18
|
+
name: "ComposerPrimitiveSend",
|
|
19
|
+
inheritAttrs: false,
|
|
20
|
+
slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
|
|
21
|
+
setup(_, { attrs, slots }) {
|
|
22
|
+
const { disabled, send } = useComposerSendState();
|
|
23
|
+
const onClick = (event: MouseEvent) => {
|
|
24
|
+
if (event.defaultPrevented || disabled.value || isAttrDisabled(attrs))
|
|
25
|
+
return;
|
|
26
|
+
send();
|
|
27
|
+
};
|
|
28
|
+
return () =>
|
|
29
|
+
h(
|
|
30
|
+
"button",
|
|
31
|
+
mergeProps(attrs, {
|
|
32
|
+
type: "button",
|
|
33
|
+
disabled: disabled.value || isAttrDisabled(attrs),
|
|
34
|
+
onClick,
|
|
35
|
+
}),
|
|
36
|
+
slots.default?.(),
|
|
37
|
+
);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
defineComponent,
|
|
4
|
+
h,
|
|
5
|
+
onScopeDispose,
|
|
6
|
+
type SlotsType,
|
|
7
|
+
type VNodeChild,
|
|
8
|
+
} from "vue";
|
|
9
|
+
import {
|
|
10
|
+
AuiConfig,
|
|
11
|
+
Derived,
|
|
12
|
+
type AssistantClient,
|
|
13
|
+
} from "@assistant-ui/store/client";
|
|
14
|
+
import type { ComposerMethods, MessageMethods } from "@assistant-ui/core/store";
|
|
15
|
+
import { AuiProvider } from "../AuiProvider";
|
|
16
|
+
import { useAui } from "../useAui";
|
|
17
|
+
import { createLastValidCache, createStaleReporter } from "./lastValidCache";
|
|
18
|
+
|
|
19
|
+
const idSets = new WeakMap<readonly { id: string }[], Set<string>>();
|
|
20
|
+
|
|
21
|
+
const hasMessage = (aui: AssistantClient, id: string) => {
|
|
22
|
+
const messages = aui.thread.getState().messages;
|
|
23
|
+
let set = idSets.get(messages);
|
|
24
|
+
if (!set) {
|
|
25
|
+
set = new Set(messages.map((message) => message.id));
|
|
26
|
+
idSets.set(messages, set);
|
|
27
|
+
}
|
|
28
|
+
return set.has(id);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Scopes the subtree to the thread message with `id`: descendants read the
|
|
33
|
+
* message through `s.message` and its edit composer through `s.composer`.
|
|
34
|
+
* The scope follows the message's identity rather than a positional slot, so
|
|
35
|
+
* it pairs with id-keyed iteration.
|
|
36
|
+
*/
|
|
37
|
+
export const MessageByIdProvider = defineComponent({
|
|
38
|
+
name: "MessageByIdProvider",
|
|
39
|
+
props: {
|
|
40
|
+
id: {
|
|
41
|
+
type: String,
|
|
42
|
+
required: true,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
|
|
46
|
+
setup(props, { slots }) {
|
|
47
|
+
const aui = useAui();
|
|
48
|
+
let disposed = false;
|
|
49
|
+
onScopeDispose(() => {
|
|
50
|
+
disposed = true;
|
|
51
|
+
});
|
|
52
|
+
const config = computed(() => {
|
|
53
|
+
const id = props.id;
|
|
54
|
+
const messageCache = createLastValidCache<MessageMethods>(
|
|
55
|
+
createStaleReporter({
|
|
56
|
+
name: "MessageByIdProvider",
|
|
57
|
+
index: id,
|
|
58
|
+
isCurrent: () => !disposed && id === props.id,
|
|
59
|
+
isValid: () => hasMessage(aui, id),
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
const composerCache = createLastValidCache<ComposerMethods>(null);
|
|
63
|
+
return AuiConfig({
|
|
64
|
+
message: Derived({
|
|
65
|
+
source: "thread",
|
|
66
|
+
query: { type: "id", id },
|
|
67
|
+
get: (aui) =>
|
|
68
|
+
messageCache.resolve(hasMessage(aui, id), () =>
|
|
69
|
+
aui.thread.message({ id }),
|
|
70
|
+
),
|
|
71
|
+
}),
|
|
72
|
+
composer: Derived({
|
|
73
|
+
source: "message",
|
|
74
|
+
query: {},
|
|
75
|
+
get: (aui) =>
|
|
76
|
+
composerCache.resolve(hasMessage(aui, id), () =>
|
|
77
|
+
aui.thread.message({ id }).composer(),
|
|
78
|
+
),
|
|
79
|
+
}),
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
return () =>
|
|
83
|
+
h(
|
|
84
|
+
AuiProvider,
|
|
85
|
+
{ config: config.value, extends: aui },
|
|
86
|
+
{ default: () => slots.default?.() },
|
|
87
|
+
);
|
|
88
|
+
},
|
|
89
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { createApp, defineComponent, h, type PropType } from "vue";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import type { ToolUIProps } from "./MessagePrimitiveParts";
|
|
4
|
+
import { MessagePrimitiveParts } from "./MessagePrimitiveParts";
|
|
5
|
+
|
|
6
|
+
const fixture = vi.hoisted(() => ({
|
|
7
|
+
recordInteraction: vi.fn(),
|
|
8
|
+
part: {
|
|
9
|
+
type: "tool-call",
|
|
10
|
+
toolCallId: "call-1",
|
|
11
|
+
toolName: "weather",
|
|
12
|
+
args: {},
|
|
13
|
+
argsText: "{}",
|
|
14
|
+
status: { type: "complete" },
|
|
15
|
+
},
|
|
16
|
+
state: {
|
|
17
|
+
message: { parts: [] as unknown[] },
|
|
18
|
+
optional: { tools: { toolUIs: {} as Record<string, unknown> } },
|
|
19
|
+
},
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
vi.mock("../useAui", async (importOriginal) => ({
|
|
23
|
+
...(await importOriginal<typeof import("../useAui")>()),
|
|
24
|
+
useAui: () => ({
|
|
25
|
+
part: {
|
|
26
|
+
addToolResult: vi.fn(),
|
|
27
|
+
resumeToolCall: vi.fn(),
|
|
28
|
+
respondToToolApproval: vi.fn(),
|
|
29
|
+
unstable_recordInteraction: fixture.recordInteraction,
|
|
30
|
+
},
|
|
31
|
+
}),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
vi.mock("../useAuiState", async (importOriginal) => ({
|
|
35
|
+
...(await importOriginal<typeof import("../useAuiState")>()),
|
|
36
|
+
useAuiState: <T>(selector: (state: unknown) => T) => ({
|
|
37
|
+
get value() {
|
|
38
|
+
return selector({ ...fixture.state, part: fixture.part });
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
vi.mock("./PartByIndexProvider", async (importOriginal) => {
|
|
44
|
+
const { defineComponent } = await import("vue");
|
|
45
|
+
return {
|
|
46
|
+
...(await importOriginal<typeof import("./PartByIndexProvider")>()),
|
|
47
|
+
PartByIndexProvider: defineComponent({
|
|
48
|
+
setup(_, { slots }) {
|
|
49
|
+
return () => slots.default?.();
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const interaction = { type: "action" as const, payload: { choice: "retry" } };
|
|
56
|
+
|
|
57
|
+
let app: ReturnType<typeof createApp> | undefined;
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
fixture.recordInteraction.mockReset();
|
|
61
|
+
fixture.state.message.parts = [fixture.part];
|
|
62
|
+
fixture.state.optional.tools.toolUIs = {};
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterEach(() => app?.unmount());
|
|
66
|
+
|
|
67
|
+
describe("MessagePrimitiveParts", () => {
|
|
68
|
+
it("forwards unstable_recordInteraction to a registered tool UI", () => {
|
|
69
|
+
const Tool = defineComponent({
|
|
70
|
+
props: {
|
|
71
|
+
tool: { type: Object as PropType<ToolUIProps>, required: true },
|
|
72
|
+
},
|
|
73
|
+
setup(props) {
|
|
74
|
+
return () => {
|
|
75
|
+
void props.tool.unstable_recordInteraction?.(interaction);
|
|
76
|
+
return h("span", "weather");
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
fixture.state.optional.tools.toolUIs = { weather: [{ render: Tool }] };
|
|
81
|
+
const target = document.createElement("div");
|
|
82
|
+
app = createApp(MessagePrimitiveParts);
|
|
83
|
+
|
|
84
|
+
app.mount(target);
|
|
85
|
+
|
|
86
|
+
expect(fixture.recordInteraction).toHaveBeenCalledWith(interaction);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineComponent,
|
|
3
|
+
h,
|
|
4
|
+
type Component,
|
|
5
|
+
type SlotsType,
|
|
6
|
+
type VNodeChild,
|
|
7
|
+
} from "vue";
|
|
8
|
+
import { isMcpAppUri } from "@assistant-ui/core";
|
|
9
|
+
import { getMessagePartKeys } from "@assistant-ui/core/internal";
|
|
10
|
+
import {
|
|
11
|
+
resolveToolCallText,
|
|
12
|
+
type PartMethods,
|
|
13
|
+
} from "@assistant-ui/core/store";
|
|
14
|
+
import { isDevelopment } from "@assistant-ui/core/store/internal";
|
|
15
|
+
import type { AssistantState } from "@assistant-ui/store/client";
|
|
16
|
+
import { useAui } from "../useAui";
|
|
17
|
+
import { useAuiState } from "../useAuiState";
|
|
18
|
+
import { PartByIndexProvider } from "./PartByIndexProvider";
|
|
19
|
+
import { useStableKeys } from "./stableKeys";
|
|
20
|
+
|
|
21
|
+
const warnedTypes = new Set<string>();
|
|
22
|
+
|
|
23
|
+
export const clearPartWarningsForTesting = () => warnedTypes.clear();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The value of the single `tool` prop passed to a tool UI registered in the
|
|
27
|
+
* `tools` scope. React spreads the part state as individual props; in Vue any
|
|
28
|
+
* prop the component does not declare falls through to `$attrs` and gets
|
|
29
|
+
* stringified onto its root element, so the Vue face passes one `tool` object
|
|
30
|
+
* instead, which a component always declares in full. The tools scope
|
|
31
|
+
* declares renderers as React's `ToolCallMessagePartComponent`, so the Vue
|
|
32
|
+
* face bridges the seam with casts at registration and render; a genuine
|
|
33
|
+
* React renderer arriving through a shared toolkit type-checks at `setToolUI`
|
|
34
|
+
* and then fails when invoked as a Vue component. This type is the Vue-facing
|
|
35
|
+
* contract for custom components. Toolkit `renderText` descriptors resolve
|
|
36
|
+
* through the neutral resolver and render string or number results directly;
|
|
37
|
+
* a react-element-valued descriptor is react-only and renders nothing here.
|
|
38
|
+
*/
|
|
39
|
+
export type ToolUIProps = {
|
|
40
|
+
part: Extract<AssistantState["part"], { type: "tool-call" }>;
|
|
41
|
+
addResult: PartMethods["addToolResult"];
|
|
42
|
+
resume: PartMethods["resumeToolCall"];
|
|
43
|
+
respondToApproval: PartMethods["respondToToolApproval"];
|
|
44
|
+
unstable_recordInteraction?: PartMethods["unstable_recordInteraction"];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** The value of the single `data` prop passed to a Vue data renderer. */
|
|
48
|
+
export type DataUIProps<T = unknown> = {
|
|
49
|
+
part: Omit<Extract<AssistantState["part"], { type: "data" }>, "data"> & {
|
|
50
|
+
data: T;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Renders the current message's content parts in order, each scoped through
|
|
56
|
+
* {@link PartByIndexProvider}. A `tool-call` part first resolves a renderer
|
|
57
|
+
* registered in the `tools` scope by tool name (`aui.tools.setToolUI`, or a
|
|
58
|
+
* `Tools({ toolkit })` config entry) and renders it with a single `tool` prop
|
|
59
|
+
* of type {@link ToolUIProps}.
|
|
60
|
+
* Otherwise a slot named after the part type (`text`, `reasoning`,
|
|
61
|
+
* `tool-call`, ...) renders that part; the `default` slot is the fallback for
|
|
62
|
+
* types without a named slot, except text, which always renders its text
|
|
63
|
+
* unless a `text` slot overrides it.
|
|
64
|
+
*/
|
|
65
|
+
export const MessagePrimitiveParts = defineComponent({
|
|
66
|
+
name: "MessagePrimitiveParts",
|
|
67
|
+
slots: Object as SlotsType<Record<string, (() => VNodeChild[]) | undefined>>,
|
|
68
|
+
setup(_, { slots }) {
|
|
69
|
+
const parts = useAuiState((s) => s.message.parts);
|
|
70
|
+
const partKeys = useStableKeys(() => getMessagePartKeys(parts.value));
|
|
71
|
+
const PartView = defineComponent({
|
|
72
|
+
name: "MessagePartView",
|
|
73
|
+
setup() {
|
|
74
|
+
const aui = useAui();
|
|
75
|
+
const type = useAuiState((s) => s.part.type);
|
|
76
|
+
const text = useAuiState((s) =>
|
|
77
|
+
s.part.type === "text" ? s.part.text : "",
|
|
78
|
+
);
|
|
79
|
+
const toolUI = useAuiState((s) => {
|
|
80
|
+
if (s.part.type !== "tool-call") return null;
|
|
81
|
+
return s.optional.tools?.toolUIs[s.part.toolName]?.[0] ?? null;
|
|
82
|
+
});
|
|
83
|
+
const mcpAppRender = useAuiState((s) =>
|
|
84
|
+
s.part.type === "tool-call" &&
|
|
85
|
+
isMcpAppUri(s.part.mcp?.app?.resourceUri)
|
|
86
|
+
? (s.optional.tools?.mcpApp?.render ?? null)
|
|
87
|
+
: null,
|
|
88
|
+
);
|
|
89
|
+
const toolPart = useAuiState((s) =>
|
|
90
|
+
s.part.type === "tool-call" ? s.part : null,
|
|
91
|
+
);
|
|
92
|
+
const dataRenderer = useAuiState((s) => {
|
|
93
|
+
if (s.part.type !== "data") return null;
|
|
94
|
+
const named =
|
|
95
|
+
s.optional.dataRenderers?.renderers[s.part.name]?.[0] ?? null;
|
|
96
|
+
return named ?? s.optional.dataRenderers?.fallbacks[0] ?? null;
|
|
97
|
+
});
|
|
98
|
+
const dataPart = useAuiState((s) =>
|
|
99
|
+
s.part.type === "data" ? s.part : null,
|
|
100
|
+
);
|
|
101
|
+
return () => {
|
|
102
|
+
if (type.value === "tool-call") {
|
|
103
|
+
const registration = toolUI.value;
|
|
104
|
+
const part = toolPart.value;
|
|
105
|
+
if (registration?.renderText && part) {
|
|
106
|
+
const resolved = resolveToolCallText(
|
|
107
|
+
registration.renderText,
|
|
108
|
+
part,
|
|
109
|
+
);
|
|
110
|
+
if (
|
|
111
|
+
typeof resolved === "string" ||
|
|
112
|
+
typeof resolved === "number"
|
|
113
|
+
) {
|
|
114
|
+
return resolved;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const Render = registration?.render ?? mcpAppRender.value;
|
|
119
|
+
if (Render && part) {
|
|
120
|
+
return h(Render as unknown as Component, {
|
|
121
|
+
tool: {
|
|
122
|
+
part,
|
|
123
|
+
addResult: aui.part.addToolResult,
|
|
124
|
+
resume: aui.part.resumeToolCall,
|
|
125
|
+
respondToApproval: aui.part.respondToToolApproval,
|
|
126
|
+
...(aui.part.unstable_recordInteraction && {
|
|
127
|
+
unstable_recordInteraction:
|
|
128
|
+
aui.part.unstable_recordInteraction,
|
|
129
|
+
}),
|
|
130
|
+
} satisfies ToolUIProps,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (type.value === "data") {
|
|
135
|
+
const Render = dataRenderer.value;
|
|
136
|
+
const part = dataPart.value;
|
|
137
|
+
if (Render && part) {
|
|
138
|
+
return h(Render as unknown as Component, {
|
|
139
|
+
data: { part } satisfies DataUIProps,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (type.value === "text") {
|
|
144
|
+
const slot = slots.text;
|
|
145
|
+
return slot
|
|
146
|
+
? slot()
|
|
147
|
+
: h("p", { style: { whiteSpace: "pre-line" } }, text.value);
|
|
148
|
+
}
|
|
149
|
+
const slot = slots[type.value] ?? slots.default;
|
|
150
|
+
if (slot) return slot();
|
|
151
|
+
if (isDevelopment && !warnedTypes.has(type.value)) {
|
|
152
|
+
warnedTypes.add(type.value);
|
|
153
|
+
console.warn(
|
|
154
|
+
`MessagePrimitiveParts: no slot for part type "${type.value}"; the part renders nothing. Add a #${type.value} or #default slot.`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
return () =>
|
|
162
|
+
partKeys.value.map((key, index) =>
|
|
163
|
+
h(PartByIndexProvider, { index, key }, { default: () => h(PartView) }),
|
|
164
|
+
);
|
|
165
|
+
},
|
|
166
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
defineComponent,
|
|
4
|
+
h,
|
|
5
|
+
onScopeDispose,
|
|
6
|
+
type SlotsType,
|
|
7
|
+
type VNodeChild,
|
|
8
|
+
} from "vue";
|
|
9
|
+
import { AuiConfig, Derived } from "@assistant-ui/store/client";
|
|
10
|
+
import type { PartMethods } from "@assistant-ui/core/store";
|
|
11
|
+
import { AuiProvider } from "../AuiProvider";
|
|
12
|
+
import { useAui } from "../useAui";
|
|
13
|
+
import { createLastValidCache, createStaleReporter } from "./lastValidCache";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Scopes the subtree to the message part at `index`: descendants read the
|
|
17
|
+
* part through `s.part`.
|
|
18
|
+
*/
|
|
19
|
+
export const PartByIndexProvider = defineComponent({
|
|
20
|
+
name: "PartByIndexProvider",
|
|
21
|
+
props: {
|
|
22
|
+
index: {
|
|
23
|
+
type: Number,
|
|
24
|
+
required: true,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
|
|
28
|
+
setup(props, { slots }) {
|
|
29
|
+
const aui = useAui();
|
|
30
|
+
let disposed = false;
|
|
31
|
+
onScopeDispose(() => {
|
|
32
|
+
disposed = true;
|
|
33
|
+
});
|
|
34
|
+
const config = computed(() => {
|
|
35
|
+
const index = props.index;
|
|
36
|
+
const cache = createLastValidCache<PartMethods>(
|
|
37
|
+
createStaleReporter({
|
|
38
|
+
name: "PartByIndexProvider",
|
|
39
|
+
index,
|
|
40
|
+
isCurrent: () => !disposed && index === props.index,
|
|
41
|
+
isValid: () => index < aui.message.getState().parts.length,
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
return AuiConfig({
|
|
45
|
+
part: Derived({
|
|
46
|
+
source: "message",
|
|
47
|
+
query: { type: "index", index },
|
|
48
|
+
get: (aui) =>
|
|
49
|
+
cache.resolve(index < aui.message.getState().parts.length, () =>
|
|
50
|
+
aui.message.part({ index }),
|
|
51
|
+
),
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
return () =>
|
|
56
|
+
h(
|
|
57
|
+
AuiProvider,
|
|
58
|
+
{ config: config.value, extends: aui },
|
|
59
|
+
{ default: () => slots.default?.() },
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineComponent,
|
|
3
|
+
h,
|
|
4
|
+
mergeProps,
|
|
5
|
+
type SlotsType,
|
|
6
|
+
type VNodeChild,
|
|
7
|
+
} from "vue";
|
|
8
|
+
import { useAuiState } from "../useAuiState";
|
|
9
|
+
import {
|
|
10
|
+
provideThreadListItemFocus,
|
|
11
|
+
useThreadListCollection,
|
|
12
|
+
} from "./threadListFocusGroup";
|
|
13
|
+
|
|
14
|
+
export const ThreadListItemPrimitiveRoot = defineComponent({
|
|
15
|
+
name: "ThreadListItemPrimitiveRoot",
|
|
16
|
+
inheritAttrs: false,
|
|
17
|
+
slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
|
|
18
|
+
setup(_, { attrs, slots }) {
|
|
19
|
+
const active = useAuiState(
|
|
20
|
+
(s) => s.threads.mainThreadId === s.threadListItem.id,
|
|
21
|
+
);
|
|
22
|
+
const focus = provideThreadListItemFocus();
|
|
23
|
+
const collection = useThreadListCollection();
|
|
24
|
+
const onKeydown = (event: KeyboardEvent) => {
|
|
25
|
+
if (event.defaultPrevented) return;
|
|
26
|
+
const trigger = focus.trigger.value;
|
|
27
|
+
if (
|
|
28
|
+
!trigger ||
|
|
29
|
+
event.target !== trigger ||
|
|
30
|
+
(event.key !== "ArrowDown" && event.key !== "ArrowUp")
|
|
31
|
+
) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const triggers = collection?.getTriggers();
|
|
35
|
+
if (!triggers) return;
|
|
36
|
+
const next =
|
|
37
|
+
triggers[
|
|
38
|
+
triggers.indexOf(trigger) + (event.key === "ArrowDown" ? 1 : -1)
|
|
39
|
+
];
|
|
40
|
+
if (!next) return;
|
|
41
|
+
next.focus();
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
};
|
|
44
|
+
return () =>
|
|
45
|
+
h(
|
|
46
|
+
"div",
|
|
47
|
+
mergeProps(attrs, {
|
|
48
|
+
...(active.value && {
|
|
49
|
+
"data-active": "true",
|
|
50
|
+
"aria-current": "true",
|
|
51
|
+
}),
|
|
52
|
+
onKeydown,
|
|
53
|
+
}),
|
|
54
|
+
slots.default?.(),
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineComponent,
|
|
3
|
+
h,
|
|
4
|
+
mergeProps,
|
|
5
|
+
onScopeDispose,
|
|
6
|
+
type ComponentPublicInstance,
|
|
7
|
+
type SlotsType,
|
|
8
|
+
type VNodeChild,
|
|
9
|
+
} from "vue";
|
|
10
|
+
import { isAttrDisabled } from "./attrDisabled";
|
|
11
|
+
import { useAui } from "../useAui";
|
|
12
|
+
import { useAuiState } from "../useAuiState";
|
|
13
|
+
import {
|
|
14
|
+
useThreadListCollection,
|
|
15
|
+
useThreadListItemFocus,
|
|
16
|
+
} from "./threadListFocusGroup";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A button that switches to the current thread-list item's thread. Carries
|
|
20
|
+
* `data-active` and `aria-current` while that thread is the main one, for
|
|
21
|
+
* standalone use; when nested under `ThreadListItemPrimitiveRoot` the root
|
|
22
|
+
* stamps them too, diverging from React's root-only pattern to keep existing
|
|
23
|
+
* standalone consumers styled.
|
|
24
|
+
*/
|
|
25
|
+
export const ThreadListItemPrimitiveTrigger = defineComponent({
|
|
26
|
+
name: "ThreadListItemPrimitiveTrigger",
|
|
27
|
+
inheritAttrs: false,
|
|
28
|
+
slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
|
|
29
|
+
setup(_, { attrs, slots }) {
|
|
30
|
+
const aui = useAui();
|
|
31
|
+
const active = useAuiState(
|
|
32
|
+
(s) => s.threads.mainThreadId === s.threadListItem.id,
|
|
33
|
+
);
|
|
34
|
+
const collection = useThreadListCollection();
|
|
35
|
+
const focus = useThreadListItemFocus();
|
|
36
|
+
const key = Symbol();
|
|
37
|
+
let trigger: HTMLButtonElement | null = null;
|
|
38
|
+
let unregister: (() => void) | undefined;
|
|
39
|
+
const setTrigger = (element: Element | ComponentPublicInstance | null) => {
|
|
40
|
+
const next = element instanceof HTMLButtonElement ? element : null;
|
|
41
|
+
const previous = trigger;
|
|
42
|
+
if (previous === next) return;
|
|
43
|
+
unregister?.();
|
|
44
|
+
if (focus && (next || focus.trigger.value === previous)) {
|
|
45
|
+
focus.trigger.value = next;
|
|
46
|
+
}
|
|
47
|
+
trigger = next;
|
|
48
|
+
unregister = next ? collection?.registerTrigger(key, next) : undefined;
|
|
49
|
+
};
|
|
50
|
+
onScopeDispose(() => {
|
|
51
|
+
unregister?.();
|
|
52
|
+
if (focus?.trigger.value === trigger) focus.trigger.value = null;
|
|
53
|
+
});
|
|
54
|
+
const onClick = (event: MouseEvent) => {
|
|
55
|
+
if (event.defaultPrevented || isAttrDisabled(attrs)) return;
|
|
56
|
+
aui.threadListItem.switchTo();
|
|
57
|
+
};
|
|
58
|
+
return () =>
|
|
59
|
+
h(
|
|
60
|
+
"button",
|
|
61
|
+
mergeProps(attrs, {
|
|
62
|
+
ref: setTrigger,
|
|
63
|
+
type: "button",
|
|
64
|
+
disabled: isAttrDisabled(attrs),
|
|
65
|
+
...(active.value && {
|
|
66
|
+
"data-active": "true",
|
|
67
|
+
"aria-current": "true",
|
|
68
|
+
}),
|
|
69
|
+
onClick,
|
|
70
|
+
}),
|
|
71
|
+
slots.default?.(),
|
|
72
|
+
);
|
|
73
|
+
},
|
|
74
|
+
});
|