@assistant-ui/vue 0.0.0 → 0.0.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/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 +51 -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 +21 -0
- package/dist/primitives/ThreadPrimitiveMessages.d.ts.map +1 -0
- package/dist/primitives/ThreadPrimitiveMessages.js +35 -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 +99 -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 +57 -6
- 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 +829 -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 +408 -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 +70 -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 +167 -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 +38 -0
- package/src/primitives/ThreadPrimitiveScrollToBottom.ts +65 -0
- package/src/primitives/ThreadPrimitiveViewport.ts +120 -0
- package/src/primitives/ThreadPrimitiveViewportFooter.test.ts +400 -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,400 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
createApp,
|
|
4
|
+
defineComponent,
|
|
5
|
+
h,
|
|
6
|
+
nextTick,
|
|
7
|
+
ref,
|
|
8
|
+
type Component,
|
|
9
|
+
} from "vue";
|
|
10
|
+
import { AuiConfig } from "@assistant-ui/store/client";
|
|
11
|
+
import { RuntimeAdapter } from "@assistant-ui/core/store";
|
|
12
|
+
import {
|
|
13
|
+
AssistantRuntimeImpl,
|
|
14
|
+
ExternalStoreRuntimeCore,
|
|
15
|
+
} from "@assistant-ui/core/internal";
|
|
16
|
+
import { AuiProvider } from "../AuiProvider";
|
|
17
|
+
import { ThreadPrimitiveScrollToBottom } from "./ThreadPrimitiveScrollToBottom";
|
|
18
|
+
import { ThreadPrimitiveViewport } from "./ThreadPrimitiveViewport";
|
|
19
|
+
import { ThreadPrimitiveViewportFooter } from "./ThreadPrimitiveViewportFooter";
|
|
20
|
+
|
|
21
|
+
const geometryElements = new Set<HTMLElement>();
|
|
22
|
+
|
|
23
|
+
const createTestRuntime = (withHistory = false) =>
|
|
24
|
+
new AssistantRuntimeImpl(
|
|
25
|
+
new ExternalStoreRuntimeCore({
|
|
26
|
+
messages: withHistory
|
|
27
|
+
? [
|
|
28
|
+
{
|
|
29
|
+
role: "user" as const,
|
|
30
|
+
content: [{ type: "text" as const, text: "history" }],
|
|
31
|
+
},
|
|
32
|
+
]
|
|
33
|
+
: [],
|
|
34
|
+
convertMessage: () => ({ role: "user", content: [] }),
|
|
35
|
+
onNew: async () => {},
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const mountChat = (runtime: AssistantRuntimeImpl, view: Component) => {
|
|
40
|
+
const app = createApp(
|
|
41
|
+
defineComponent({
|
|
42
|
+
setup: () => () =>
|
|
43
|
+
h(
|
|
44
|
+
AuiProvider,
|
|
45
|
+
{ config: AuiConfig({ threads: RuntimeAdapter(runtime) }) },
|
|
46
|
+
{ default: () => h(view) },
|
|
47
|
+
),
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
const el = document.createElement("div");
|
|
51
|
+
app.mount(el);
|
|
52
|
+
return { el, unmount: () => app.unmount() };
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const installViewportGeometry = (div: HTMLElement) => {
|
|
56
|
+
let scrollHeight = 500;
|
|
57
|
+
let scrollTop = 0;
|
|
58
|
+
const clientHeight = 100;
|
|
59
|
+
geometryElements.add(div);
|
|
60
|
+
Object.defineProperty(div, "scrollHeight", {
|
|
61
|
+
get: () => scrollHeight,
|
|
62
|
+
configurable: true,
|
|
63
|
+
});
|
|
64
|
+
Object.defineProperty(div, "clientHeight", {
|
|
65
|
+
get: () => clientHeight,
|
|
66
|
+
configurable: true,
|
|
67
|
+
});
|
|
68
|
+
Object.defineProperty(div, "scrollTop", {
|
|
69
|
+
get: () => scrollTop,
|
|
70
|
+
set: (value: number) => {
|
|
71
|
+
scrollTop = value;
|
|
72
|
+
},
|
|
73
|
+
configurable: true,
|
|
74
|
+
});
|
|
75
|
+
const scrollTo = vi.fn(({ top }: ScrollToOptions) => {
|
|
76
|
+
scrollTop = Math.max(0, Math.min(top ?? 0, scrollHeight - clientHeight));
|
|
77
|
+
div.dispatchEvent(new Event("scroll"));
|
|
78
|
+
});
|
|
79
|
+
Object.defineProperty(div, "scrollTo", {
|
|
80
|
+
value: scrollTo,
|
|
81
|
+
configurable: true,
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
scrollTo,
|
|
85
|
+
setScrollHeight: (value: number) => {
|
|
86
|
+
scrollHeight = value;
|
|
87
|
+
},
|
|
88
|
+
setScrollTop: (value: number) => {
|
|
89
|
+
scrollTop = value;
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const mountChatWithViewportGeometry = (
|
|
95
|
+
runtime: AssistantRuntimeImpl,
|
|
96
|
+
view: Component,
|
|
97
|
+
) => {
|
|
98
|
+
const geometries = new WeakMap<
|
|
99
|
+
HTMLElement,
|
|
100
|
+
ReturnType<typeof installViewportGeometry>
|
|
101
|
+
>();
|
|
102
|
+
const createElement = document.createElement.bind(document);
|
|
103
|
+
vi.spyOn(document, "createElement").mockImplementation((tagName: string) => {
|
|
104
|
+
const element = createElement(tagName);
|
|
105
|
+
if (tagName === "div") {
|
|
106
|
+
geometries.set(element, installViewportGeometry(element));
|
|
107
|
+
}
|
|
108
|
+
return element;
|
|
109
|
+
});
|
|
110
|
+
const mounted = mountChat(runtime, view);
|
|
111
|
+
const viewport = mounted.el.querySelector<HTMLElement>("div.viewport")!;
|
|
112
|
+
return { ...mounted, viewport, geometry: geometries.get(viewport)! };
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const installResizeObserver = () => {
|
|
116
|
+
const observers = new Set<ResizeObserverMock>();
|
|
117
|
+
class ResizeObserverMock {
|
|
118
|
+
readonly elements = new Set<Element>();
|
|
119
|
+
readonly callback: ResizeObserverCallback;
|
|
120
|
+
|
|
121
|
+
constructor(callback: ResizeObserverCallback) {
|
|
122
|
+
this.callback = callback;
|
|
123
|
+
observers.add(this);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
observe(element: Element) {
|
|
127
|
+
this.elements.add(element);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
unobserve(element: Element) {
|
|
131
|
+
this.elements.delete(element);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
disconnect() {
|
|
135
|
+
this.elements.clear();
|
|
136
|
+
observers.delete(this);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
takeRecords() {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
trigger() {
|
|
144
|
+
this.callback([], this);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
vi.stubGlobal("ResizeObserver", ResizeObserverMock);
|
|
148
|
+
return {
|
|
149
|
+
trigger: (element: Element) => {
|
|
150
|
+
for (const observer of observers) {
|
|
151
|
+
if (observer.elements.has(element)) observer.trigger();
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
afterEach(() => {
|
|
158
|
+
vi.restoreAllMocks();
|
|
159
|
+
vi.unstubAllGlobals();
|
|
160
|
+
for (const element of geometryElements) {
|
|
161
|
+
Reflect.deleteProperty(element, "scrollHeight");
|
|
162
|
+
Reflect.deleteProperty(element, "clientHeight");
|
|
163
|
+
Reflect.deleteProperty(element, "scrollTop");
|
|
164
|
+
Reflect.deleteProperty(element, "scrollTo");
|
|
165
|
+
}
|
|
166
|
+
geometryElements.clear();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("ThreadPrimitiveViewport initial pinning", () => {
|
|
170
|
+
it.each(["viewport", "content"])(
|
|
171
|
+
"follows the first %s resize over overflowing history when initialization scroll is disabled",
|
|
172
|
+
(target) => {
|
|
173
|
+
const observers = installResizeObserver();
|
|
174
|
+
const View = defineComponent({
|
|
175
|
+
setup: () => () =>
|
|
176
|
+
h(
|
|
177
|
+
ThreadPrimitiveViewport,
|
|
178
|
+
{ class: "viewport", scrollToBottomOnInitialize: false },
|
|
179
|
+
{ default: () => h("p", "history") },
|
|
180
|
+
),
|
|
181
|
+
});
|
|
182
|
+
const { viewport, geometry, unmount } = mountChatWithViewportGeometry(
|
|
183
|
+
createTestRuntime(true),
|
|
184
|
+
View,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
observers.trigger(
|
|
188
|
+
target === "viewport" ? viewport : viewport.firstElementChild!,
|
|
189
|
+
);
|
|
190
|
+
expect(geometry.scrollTo).toHaveBeenCalledWith({
|
|
191
|
+
top: 500,
|
|
192
|
+
behavior: "instant",
|
|
193
|
+
});
|
|
194
|
+
expect(viewport.scrollTop).toBe(400);
|
|
195
|
+
unmount();
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
it("follows a footer height reported before the viewport attaches", () => {
|
|
200
|
+
installResizeObserver();
|
|
201
|
+
vi.spyOn(HTMLElement.prototype, "offsetHeight", "get").mockImplementation(
|
|
202
|
+
function (this: HTMLElement) {
|
|
203
|
+
return this.classList.contains("footer") ? 50 : 0;
|
|
204
|
+
},
|
|
205
|
+
);
|
|
206
|
+
const View = defineComponent({
|
|
207
|
+
setup: () => () =>
|
|
208
|
+
h(
|
|
209
|
+
ThreadPrimitiveViewport,
|
|
210
|
+
{ class: "viewport", scrollToBottomOnInitialize: false },
|
|
211
|
+
{
|
|
212
|
+
default: () => [
|
|
213
|
+
h("p", "history"),
|
|
214
|
+
h(ThreadPrimitiveViewportFooter, { class: "footer" }),
|
|
215
|
+
],
|
|
216
|
+
},
|
|
217
|
+
),
|
|
218
|
+
});
|
|
219
|
+
const { viewport, geometry, unmount } = mountChatWithViewportGeometry(
|
|
220
|
+
createTestRuntime(true),
|
|
221
|
+
View,
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
expect(geometry.scrollTo).toHaveBeenCalledWith({
|
|
225
|
+
top: 500,
|
|
226
|
+
behavior: "instant",
|
|
227
|
+
});
|
|
228
|
+
expect(viewport.scrollTop).toBe(400);
|
|
229
|
+
unmount();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe("ThreadPrimitiveViewportFooter", () => {
|
|
234
|
+
it("registers its height and unregisters it when removed", async () => {
|
|
235
|
+
const observers = installResizeObserver();
|
|
236
|
+
const showFooter = ref(true);
|
|
237
|
+
const View = defineComponent({
|
|
238
|
+
setup: () => () =>
|
|
239
|
+
h(
|
|
240
|
+
ThreadPrimitiveViewport,
|
|
241
|
+
{ class: "viewport" },
|
|
242
|
+
{
|
|
243
|
+
default: () => [
|
|
244
|
+
showFooter.value
|
|
245
|
+
? h(ThreadPrimitiveViewportFooter, { class: "footer" })
|
|
246
|
+
: null,
|
|
247
|
+
h(
|
|
248
|
+
ThreadPrimitiveScrollToBottom,
|
|
249
|
+
{ class: "jump" },
|
|
250
|
+
{ default: () => "Jump" },
|
|
251
|
+
),
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
),
|
|
255
|
+
});
|
|
256
|
+
const { el, unmount } = mountChat(createTestRuntime(), View);
|
|
257
|
+
const div = el.querySelector<HTMLElement>("div.viewport")!;
|
|
258
|
+
const geometry = installViewportGeometry(div);
|
|
259
|
+
const footer = el.querySelector<HTMLElement>("div.footer")!;
|
|
260
|
+
Object.defineProperty(footer, "offsetHeight", {
|
|
261
|
+
get: () => 50,
|
|
262
|
+
configurable: true,
|
|
263
|
+
});
|
|
264
|
+
div.dispatchEvent(new Event("scroll"));
|
|
265
|
+
observers.trigger(footer);
|
|
266
|
+
await nextTick();
|
|
267
|
+
|
|
268
|
+
geometry.setScrollTop(355);
|
|
269
|
+
div.dispatchEvent(new Event("scroll"));
|
|
270
|
+
await nextTick();
|
|
271
|
+
expect(el.querySelector<HTMLButtonElement>("button.jump")!.disabled).toBe(
|
|
272
|
+
true,
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
showFooter.value = false;
|
|
276
|
+
await nextTick();
|
|
277
|
+
div.dispatchEvent(new Event("scroll"));
|
|
278
|
+
await nextTick();
|
|
279
|
+
expect(el.querySelector<HTMLButtonElement>("button.jump")!.disabled).toBe(
|
|
280
|
+
false,
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
Reflect.deleteProperty(footer, "offsetHeight");
|
|
284
|
+
unmount();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("sums the heights of multiple footers", async () => {
|
|
288
|
+
const observers = installResizeObserver();
|
|
289
|
+
const showSecond = ref(true);
|
|
290
|
+
const View = defineComponent({
|
|
291
|
+
setup: () => () =>
|
|
292
|
+
h(
|
|
293
|
+
ThreadPrimitiveViewport,
|
|
294
|
+
{ class: "viewport" },
|
|
295
|
+
{
|
|
296
|
+
default: () => [
|
|
297
|
+
h(ThreadPrimitiveViewportFooter, { class: "first-footer" }),
|
|
298
|
+
showSecond.value
|
|
299
|
+
? h(ThreadPrimitiveViewportFooter, { class: "second-footer" })
|
|
300
|
+
: null,
|
|
301
|
+
h(
|
|
302
|
+
ThreadPrimitiveScrollToBottom,
|
|
303
|
+
{ class: "jump" },
|
|
304
|
+
{ default: () => "Jump" },
|
|
305
|
+
),
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
),
|
|
309
|
+
});
|
|
310
|
+
const { el, unmount } = mountChat(createTestRuntime(), View);
|
|
311
|
+
const div = el.querySelector<HTMLElement>("div.viewport")!;
|
|
312
|
+
const geometry = installViewportGeometry(div);
|
|
313
|
+
const firstFooter = el.querySelector<HTMLElement>("div.first-footer")!;
|
|
314
|
+
const secondFooter = el.querySelector<HTMLElement>("div.second-footer")!;
|
|
315
|
+
Object.defineProperty(firstFooter, "offsetHeight", {
|
|
316
|
+
get: () => 20,
|
|
317
|
+
configurable: true,
|
|
318
|
+
});
|
|
319
|
+
Object.defineProperty(secondFooter, "offsetHeight", {
|
|
320
|
+
get: () => 30,
|
|
321
|
+
configurable: true,
|
|
322
|
+
});
|
|
323
|
+
div.dispatchEvent(new Event("scroll"));
|
|
324
|
+
observers.trigger(firstFooter);
|
|
325
|
+
observers.trigger(secondFooter);
|
|
326
|
+
await nextTick();
|
|
327
|
+
|
|
328
|
+
geometry.setScrollTop(355);
|
|
329
|
+
div.dispatchEvent(new Event("scroll"));
|
|
330
|
+
await nextTick();
|
|
331
|
+
expect(el.querySelector<HTMLButtonElement>("button.jump")!.disabled).toBe(
|
|
332
|
+
true,
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
showSecond.value = false;
|
|
336
|
+
await nextTick();
|
|
337
|
+
div.dispatchEvent(new Event("scroll"));
|
|
338
|
+
await nextTick();
|
|
339
|
+
expect(el.querySelector<HTMLButtonElement>("button.jump")!.disabled).toBe(
|
|
340
|
+
false,
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
Reflect.deleteProperty(firstFooter, "offsetHeight");
|
|
344
|
+
Reflect.deleteProperty(secondFooter, "offsetHeight");
|
|
345
|
+
unmount();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("follows a footer growth while pinned at the bottom", async () => {
|
|
349
|
+
const observers = installResizeObserver();
|
|
350
|
+
let footerHeight = 50;
|
|
351
|
+
const View = defineComponent({
|
|
352
|
+
setup: () => () =>
|
|
353
|
+
h(
|
|
354
|
+
ThreadPrimitiveViewport,
|
|
355
|
+
{ class: "viewport" },
|
|
356
|
+
{
|
|
357
|
+
default: () => [
|
|
358
|
+
h(ThreadPrimitiveViewportFooter, { class: "footer" }),
|
|
359
|
+
h(
|
|
360
|
+
ThreadPrimitiveScrollToBottom,
|
|
361
|
+
{ class: "jump" },
|
|
362
|
+
{ default: () => "Jump" },
|
|
363
|
+
),
|
|
364
|
+
],
|
|
365
|
+
},
|
|
366
|
+
),
|
|
367
|
+
});
|
|
368
|
+
const { el, unmount } = mountChat(createTestRuntime(), View);
|
|
369
|
+
const div = el.querySelector<HTMLElement>("div.viewport")!;
|
|
370
|
+
const geometry = installViewportGeometry(div);
|
|
371
|
+
const footer = el.querySelector<HTMLElement>("div.footer")!;
|
|
372
|
+
Object.defineProperty(footer, "offsetHeight", {
|
|
373
|
+
get: () => footerHeight,
|
|
374
|
+
configurable: true,
|
|
375
|
+
});
|
|
376
|
+
geometry.setScrollTop(400);
|
|
377
|
+
div.dispatchEvent(new Event("scroll"));
|
|
378
|
+
observers.trigger(footer);
|
|
379
|
+
await nextTick();
|
|
380
|
+
expect(geometry.scrollTo).toHaveBeenCalledWith({
|
|
381
|
+
top: 500,
|
|
382
|
+
behavior: "instant",
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
geometry.scrollTo.mockClear();
|
|
386
|
+
footerHeight = 80;
|
|
387
|
+
observers.trigger(footer);
|
|
388
|
+
await nextTick();
|
|
389
|
+
expect(geometry.scrollTo).toHaveBeenCalledWith({
|
|
390
|
+
top: 500,
|
|
391
|
+
behavior: "instant",
|
|
392
|
+
});
|
|
393
|
+
expect(el.querySelector<HTMLButtonElement>("button.jump")!.disabled).toBe(
|
|
394
|
+
true,
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
Reflect.deleteProperty(footer, "offsetHeight");
|
|
398
|
+
unmount();
|
|
399
|
+
});
|
|
400
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineComponent,
|
|
3
|
+
h,
|
|
4
|
+
inject,
|
|
5
|
+
mergeProps,
|
|
6
|
+
onMounted,
|
|
7
|
+
onScopeDispose,
|
|
8
|
+
shallowRef,
|
|
9
|
+
type SlotsType,
|
|
10
|
+
type VNodeChild,
|
|
11
|
+
} from "vue";
|
|
12
|
+
import { viewportInjectionKey } from "./viewportContext";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A footer container that measures its height into the viewport's content
|
|
16
|
+
* inset. Positions within the summed inset of the native bottom count as at
|
|
17
|
+
* bottom, and a growing inset re-follows a pinned viewport. Multiple footers
|
|
18
|
+
* sum. Typically used with `class="sticky bottom-0"`.
|
|
19
|
+
*/
|
|
20
|
+
export const ThreadPrimitiveViewportFooter = defineComponent({
|
|
21
|
+
name: "ThreadPrimitiveViewportFooter",
|
|
22
|
+
inheritAttrs: false,
|
|
23
|
+
slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
|
|
24
|
+
setup(_, { attrs, slots }) {
|
|
25
|
+
const viewport = inject(viewportInjectionKey, null);
|
|
26
|
+
const divRef = shallowRef<HTMLElement | null>(null);
|
|
27
|
+
let dispose: (() => void) | undefined;
|
|
28
|
+
|
|
29
|
+
onMounted(() => {
|
|
30
|
+
const div = divRef.value;
|
|
31
|
+
if (!div || !viewport) return;
|
|
32
|
+
|
|
33
|
+
const contentInset = viewport.registerContentInset();
|
|
34
|
+
const updateHeight = () => {
|
|
35
|
+
const marginTop =
|
|
36
|
+
Number.parseFloat(getComputedStyle(div).marginTop) || 0;
|
|
37
|
+
contentInset.setHeight(div.offsetHeight + marginTop);
|
|
38
|
+
};
|
|
39
|
+
updateHeight();
|
|
40
|
+
|
|
41
|
+
let resizeObserver: ResizeObserver | undefined;
|
|
42
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
43
|
+
resizeObserver = new ResizeObserver(updateHeight);
|
|
44
|
+
resizeObserver.observe(div);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
dispose = () => {
|
|
48
|
+
resizeObserver?.disconnect();
|
|
49
|
+
contentInset.unregister();
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
onScopeDispose(() => {
|
|
54
|
+
dispose?.();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return () =>
|
|
58
|
+
h("div", mergeProps(attrs, { ref: divRef }), slots.default?.());
|
|
59
|
+
},
|
|
60
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineComponent,
|
|
3
|
+
h,
|
|
4
|
+
mergeProps,
|
|
5
|
+
onScopeDispose,
|
|
6
|
+
type SlotsType,
|
|
7
|
+
type VNodeChild,
|
|
8
|
+
} from "vue";
|
|
9
|
+
import { flushTapSync } from "@assistant-ui/tap";
|
|
10
|
+
import {
|
|
11
|
+
actionBarCopyDisabled,
|
|
12
|
+
actionBarEditDisabled,
|
|
13
|
+
actionBarReloadDisabled,
|
|
14
|
+
} from "@assistant-ui/core/store/internal";
|
|
15
|
+
import { isAttrDisabled } from "./attrDisabled";
|
|
16
|
+
import { useAui } from "../useAui";
|
|
17
|
+
import { useAuiState } from "../useAuiState";
|
|
18
|
+
|
|
19
|
+
/** A button that begins editing the current message. Disabled while editing. */
|
|
20
|
+
export const ActionBarPrimitiveEdit = defineComponent({
|
|
21
|
+
name: "ActionBarPrimitiveEdit",
|
|
22
|
+
inheritAttrs: false,
|
|
23
|
+
slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
|
|
24
|
+
setup(_, { attrs, slots }) {
|
|
25
|
+
const aui = useAui();
|
|
26
|
+
const disabled = useAuiState(actionBarEditDisabled);
|
|
27
|
+
const onClick = (event: MouseEvent) => {
|
|
28
|
+
if (event.defaultPrevented || disabled.value || isAttrDisabled(attrs))
|
|
29
|
+
return;
|
|
30
|
+
aui.composer.beginEdit();
|
|
31
|
+
};
|
|
32
|
+
return () =>
|
|
33
|
+
h(
|
|
34
|
+
"button",
|
|
35
|
+
mergeProps(attrs, {
|
|
36
|
+
type: "button",
|
|
37
|
+
disabled: disabled.value || isAttrDisabled(attrs),
|
|
38
|
+
onClick,
|
|
39
|
+
}),
|
|
40
|
+
slots.default?.(),
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A button that regenerates the current assistant message. Disabled while a
|
|
47
|
+
* run is in flight, the thread is disabled, or the message is not an
|
|
48
|
+
* assistant message.
|
|
49
|
+
*/
|
|
50
|
+
export const ActionBarPrimitiveReload = defineComponent({
|
|
51
|
+
name: "ActionBarPrimitiveReload",
|
|
52
|
+
inheritAttrs: false,
|
|
53
|
+
slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
|
|
54
|
+
setup(_, { attrs, slots }) {
|
|
55
|
+
const aui = useAui();
|
|
56
|
+
const disabled = useAuiState(actionBarReloadDisabled);
|
|
57
|
+
const onClick = (event: MouseEvent) => {
|
|
58
|
+
if (event.defaultPrevented || disabled.value || isAttrDisabled(attrs))
|
|
59
|
+
return;
|
|
60
|
+
aui.message.reload();
|
|
61
|
+
};
|
|
62
|
+
return () =>
|
|
63
|
+
h(
|
|
64
|
+
"button",
|
|
65
|
+
mergeProps(attrs, {
|
|
66
|
+
type: "button",
|
|
67
|
+
disabled: disabled.value || isAttrDisabled(attrs),
|
|
68
|
+
onClick,
|
|
69
|
+
}),
|
|
70
|
+
slots.default?.(),
|
|
71
|
+
);
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const defaultCopyToClipboard = (text: string) => {
|
|
76
|
+
if (typeof navigator === "undefined" || !navigator.clipboard) {
|
|
77
|
+
return Promise.reject(new Error("Clipboard API is not available."));
|
|
78
|
+
}
|
|
79
|
+
return navigator.clipboard.writeText(text);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A button that copies the current message (or, while editing, the edit
|
|
84
|
+
* composer text). `data-copied` is present for `copiedDuration` milliseconds
|
|
85
|
+
* after a successful copy. Disabled while an assistant message is still
|
|
86
|
+
* running or the message has no text.
|
|
87
|
+
*/
|
|
88
|
+
export const ActionBarPrimitiveCopy = defineComponent({
|
|
89
|
+
name: "ActionBarPrimitiveCopy",
|
|
90
|
+
inheritAttrs: false,
|
|
91
|
+
props: {
|
|
92
|
+
copiedDuration: {
|
|
93
|
+
type: Number,
|
|
94
|
+
default: 3000,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
|
|
98
|
+
setup(props, { attrs, slots }) {
|
|
99
|
+
const aui = useAui();
|
|
100
|
+
const disabled = useAuiState(actionBarCopyDisabled);
|
|
101
|
+
const isCopied = useAuiState((s) => s.message.isCopied);
|
|
102
|
+
const isEditing = useAuiState((s) => s.composer.isEditing);
|
|
103
|
+
const composerText = useAuiState((s) => s.composer.text);
|
|
104
|
+
|
|
105
|
+
let copiedTimer: ReturnType<typeof setTimeout> | undefined;
|
|
106
|
+
let disposed = false;
|
|
107
|
+
onScopeDispose(() => {
|
|
108
|
+
disposed = true;
|
|
109
|
+
if (copiedTimer === undefined) return;
|
|
110
|
+
clearTimeout(copiedTimer);
|
|
111
|
+
copiedTimer = undefined;
|
|
112
|
+
aui.message.setIsCopied(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const onClick = (event: MouseEvent) => {
|
|
116
|
+
if (event.defaultPrevented || disabled.value || isAttrDisabled(attrs))
|
|
117
|
+
return;
|
|
118
|
+
const value = isEditing.value
|
|
119
|
+
? composerText.value
|
|
120
|
+
: aui.message.getCopyText();
|
|
121
|
+
if (!value) return;
|
|
122
|
+
// A by-index scope follows whatever message occupies the slot, so late
|
|
123
|
+
// completions and timers re-check the copied message's id before
|
|
124
|
+
// touching state.
|
|
125
|
+
const copiedMessageId = aui.message.getState().id;
|
|
126
|
+
const stillCopiedMessage = () =>
|
|
127
|
+
!disposed && aui.message.getState().id === copiedMessageId;
|
|
128
|
+
// The rejection handler swallows clipboard write failures (permission
|
|
129
|
+
// denied, API unavailable) so they don't surface as unhandled promise
|
|
130
|
+
// rejections.
|
|
131
|
+
Promise.resolve(defaultCopyToClipboard(value)).then(
|
|
132
|
+
() => {
|
|
133
|
+
if (!stillCopiedMessage()) return;
|
|
134
|
+
if (copiedTimer !== undefined) clearTimeout(copiedTimer);
|
|
135
|
+
flushTapSync(() => aui.message.setIsCopied(true));
|
|
136
|
+
copiedTimer = setTimeout(() => {
|
|
137
|
+
copiedTimer = undefined;
|
|
138
|
+
if (stillCopiedMessage()) {
|
|
139
|
+
flushTapSync(() => aui.message.setIsCopied(false));
|
|
140
|
+
}
|
|
141
|
+
}, props.copiedDuration);
|
|
142
|
+
},
|
|
143
|
+
() => {},
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return () =>
|
|
148
|
+
h(
|
|
149
|
+
"button",
|
|
150
|
+
mergeProps(attrs, {
|
|
151
|
+
type: "button",
|
|
152
|
+
disabled: disabled.value || isAttrDisabled(attrs),
|
|
153
|
+
...(isCopied.value && { "data-copied": "" }),
|
|
154
|
+
onClick,
|
|
155
|
+
}),
|
|
156
|
+
slots.default?.(),
|
|
157
|
+
);
|
|
158
|
+
},
|
|
159
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineComponent,
|
|
3
|
+
h,
|
|
4
|
+
mergeProps,
|
|
5
|
+
type SlotsType,
|
|
6
|
+
type VNodeChild,
|
|
7
|
+
} from "vue";
|
|
8
|
+
import { isAttrDisabled } from "./attrDisabled";
|
|
9
|
+
import { useAui } from "../useAui";
|
|
10
|
+
import { useAuiState } from "../useAuiState";
|
|
11
|
+
|
|
12
|
+
/** A wrapper element scoped to the current attachment. */
|
|
13
|
+
export const AttachmentPrimitiveRoot = defineComponent({
|
|
14
|
+
name: "AttachmentPrimitiveRoot",
|
|
15
|
+
inheritAttrs: false,
|
|
16
|
+
slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
|
|
17
|
+
setup(_, { attrs, slots }) {
|
|
18
|
+
return () => h("div", mergeProps(attrs, {}), slots.default?.());
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/** Renders the current attachment's file name as raw text. */
|
|
23
|
+
export const AttachmentPrimitiveName = defineComponent({
|
|
24
|
+
name: "AttachmentPrimitiveName",
|
|
25
|
+
slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
|
|
26
|
+
setup(_, { slots }) {
|
|
27
|
+
const name = useAuiState((s) => s.attachment.name);
|
|
28
|
+
return () => slots.default?.() ?? name.value;
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Renders a short label for the current attachment: the file extension when
|
|
34
|
+
* the name has one, otherwise the attachment type.
|
|
35
|
+
*/
|
|
36
|
+
export const AttachmentPrimitiveThumb = defineComponent({
|
|
37
|
+
name: "AttachmentPrimitiveThumb",
|
|
38
|
+
inheritAttrs: false,
|
|
39
|
+
slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
|
|
40
|
+
setup(_, { attrs, slots }) {
|
|
41
|
+
const label = useAuiState((s) => {
|
|
42
|
+
const name = s.attachment.name;
|
|
43
|
+
const dot = name.lastIndexOf(".");
|
|
44
|
+
if (dot > 0 && dot < name.length - 1) {
|
|
45
|
+
return `.${name.slice(dot + 1)}`;
|
|
46
|
+
}
|
|
47
|
+
return s.attachment.type;
|
|
48
|
+
});
|
|
49
|
+
return () =>
|
|
50
|
+
h("div", mergeProps(attrs, {}), slots.default?.() ?? label.value);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/** A button that removes the current attachment. */
|
|
55
|
+
export const AttachmentPrimitiveRemove = defineComponent({
|
|
56
|
+
name: "AttachmentPrimitiveRemove",
|
|
57
|
+
inheritAttrs: false,
|
|
58
|
+
slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
|
|
59
|
+
setup(_, { attrs, slots }) {
|
|
60
|
+
const aui = useAui();
|
|
61
|
+
const onClick = (event: MouseEvent) => {
|
|
62
|
+
if (event.defaultPrevented || isAttrDisabled(attrs)) return;
|
|
63
|
+
void aui.attachment.remove();
|
|
64
|
+
};
|
|
65
|
+
return () =>
|
|
66
|
+
h(
|
|
67
|
+
"button",
|
|
68
|
+
mergeProps(attrs, {
|
|
69
|
+
type: "button",
|
|
70
|
+
disabled: isAttrDisabled(attrs),
|
|
71
|
+
onClick,
|
|
72
|
+
}),
|
|
73
|
+
slots.default?.(),
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// A bare `disabled` template attribute arrives as "", which Boolean() would
|
|
2
|
+
// drop; mirror Vue's boolean-attribute semantics instead.
|
|
3
|
+
export const isAttrDisabled = (attrs: Record<string, unknown>): boolean =>
|
|
4
|
+
attrs.disabled != null &&
|
|
5
|
+
attrs.disabled !== false &&
|
|
6
|
+
attrs.disabled !== "false";
|