@mastra/playground-ui 45.0.0 → 45.1.0-alpha.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/dist/CommandPalette.css +135 -0
  3. package/dist/command-Duq1xsiS.js +168 -0
  4. package/dist/command-Duq1xsiS.js.map +1 -0
  5. package/dist/command-qsEykgml.cjs +197 -0
  6. package/dist/command-qsEykgml.cjs.map +1 -0
  7. package/dist/components/ChatShell.cjs.js +96 -0
  8. package/dist/components/ChatShell.cjs.js.map +1 -0
  9. package/dist/components/ChatShell.d.ts +2 -0
  10. package/dist/components/ChatShell.es.js +92 -0
  11. package/dist/components/ChatShell.es.js.map +1 -0
  12. package/dist/components/Command.cjs.js +10 -191
  13. package/dist/components/Command.cjs.js.map +1 -1
  14. package/dist/components/Command.es.js +1 -167
  15. package/dist/components/Command.es.js.map +1 -1
  16. package/dist/components/CommandPalette.cjs.js +180 -0
  17. package/dist/components/CommandPalette.cjs.js.map +1 -0
  18. package/dist/components/CommandPalette.d.ts +2 -0
  19. package/dist/components/CommandPalette.es.js +169 -0
  20. package/dist/components/CommandPalette.es.js.map +1 -0
  21. package/dist/components/Kbd.cjs.js +3 -24
  22. package/dist/components/Kbd.cjs.js.map +1 -1
  23. package/dist/components/Kbd.es.js +1 -26
  24. package/dist/components/Kbd.es.js.map +1 -1
  25. package/dist/components/MessageScroller.cjs.js +8 -524
  26. package/dist/components/MessageScroller.cjs.js.map +1 -1
  27. package/dist/components/MessageScroller.es.js +2 -504
  28. package/dist/components/MessageScroller.es.js.map +1 -1
  29. package/dist/components/ThreadRail.cjs.js +1 -1
  30. package/dist/components/ThreadRail.es.js +1 -1
  31. package/dist/kbd-Br7kdwaB.cjs +34 -0
  32. package/dist/kbd-Br7kdwaB.cjs.map +1 -0
  33. package/dist/kbd-DE0nj4DL.js +32 -0
  34. package/dist/kbd-DE0nj4DL.js.map +1 -0
  35. package/dist/message-scroller-BQpwFsqj.js +563 -0
  36. package/dist/message-scroller-BQpwFsqj.js.map +1 -0
  37. package/dist/message-scroller-DIgCd5n3.cjs +589 -0
  38. package/dist/message-scroller-DIgCd5n3.cjs.map +1 -0
  39. package/dist/{message-scroller-hooks-DdXqrmC2.cjs → message-scroller-context-_VfCYFBU.cjs} +6 -48
  40. package/dist/message-scroller-context-_VfCYFBU.cjs.map +1 -0
  41. package/dist/message-scroller-context-tTA6jxho.js +23 -0
  42. package/dist/message-scroller-context-tTA6jxho.js.map +1 -0
  43. package/dist/message-scroller-hooks-BxAf8k0Y.cjs +72 -0
  44. package/dist/message-scroller-hooks-BxAf8k0Y.cjs.map +1 -0
  45. package/dist/{message-scroller-hooks-CdsHnth6.js → message-scroller-hooks-D5yxCon2.js} +3 -19
  46. package/dist/message-scroller-hooks-D5yxCon2.js.map +1 -0
  47. package/dist/src/ds/components/ChatShell/chat-shell.d.ts +46 -0
  48. package/dist/src/ds/components/ChatShell/chat-shell.stories.d.ts +8 -0
  49. package/dist/src/ds/components/ChatShell/index.d.ts +11 -0
  50. package/dist/src/ds/components/CommandPalette/command-palette.d.ts +37 -0
  51. package/dist/src/ds/components/CommandPalette/command-palette.stories.d.ts +12 -0
  52. package/dist/src/ds/components/CommandPalette/index.d.ts +1 -0
  53. package/dist/src/ds/components/Kbd/kbd.d.ts +2 -1
  54. package/dist/src/ds/components/MessageScroller/message-scroller-context.d.ts +4 -0
  55. package/dist/src/ds/components/MessageScroller/message-scroller.d.ts +8 -7
  56. package/dist/style.css +343 -2
  57. package/package.json +6 -6
  58. package/dist/message-scroller-hooks-CdsHnth6.js.map +0 -1
  59. package/dist/message-scroller-hooks-DdXqrmC2.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kbd-Br7kdwaB.cjs","sources":["../src/ds/components/Kbd/kbd.tsx"],"sourcesContent":["import { transitions } from '@/ds/primitives/transitions';\nimport { cn } from '@/lib/utils';\n\nexport type KbdProps = {\n children: React.ReactNode;\n theme?: 'light' | 'dark';\n size?: 'default' | 'sm' | 'xs';\n className?: string;\n};\n\nconst themeClasses: Record<NonNullable<KbdProps['theme']>, string> = {\n light: 'bg-gray-100 border-gray-300 text-gray-700 shadow-[0_1px_0_rgba(0,0,0,0.1)]',\n dark: 'bg-surface4 border-border1 text-neutral5 shadow-[0_1px_0_rgba(0,0,0,0.3)]',\n};\n\n// Fixed heights, not padding — `font-mono` normal leading varies per glyph set and desyncs the scale\nconst sizeClasses: Record<NonNullable<KbdProps['size']>, string> = {\n default: 'h-6 min-w-6 rounded-md px-1.5 text-ui-sm',\n sm: 'h-5 min-w-5 rounded-md px-1 text-ui-xs',\n xs: 'h-4 min-w-4 rounded px-1 text-ui-xs leading-none',\n};\n\nexport const Kbd = ({ children, theme = 'dark', size = 'default', className }: KbdProps) => {\n return (\n <kbd\n className={cn(\n 'inline-flex items-center justify-center border font-mono',\n sizeClasses[size],\n transitions.transform,\n 'active:scale-95 active:shadow-none',\n themeClasses[theme],\n className,\n )}\n >\n {children}\n </kbd>\n );\n};\n"],"names":["jsx","cn","transitions"],"mappings":";;;;;;AAUA,MAAM,YAAA,GAA+D;AAAA,EACnE,KAAA,EAAO,4EAAA;AAAA,EACP,IAAA,EAAM;AACR,CAAA;AAGA,MAAM,WAAA,GAA6D;AAAA,EACjE,OAAA,EAAS,0CAAA;AAAA,EACT,EAAA,EAAI,wCAAA;AAAA,EACJ,EAAA,EAAI;AACN,CAAA;AAEO,MAAM,GAAA,GAAM,CAAC,EAAE,QAAA,EAAU,QAAQ,MAAA,EAAQ,IAAA,GAAO,SAAA,EAAW,SAAA,EAAU,KAAgB;AAC1F,EAAA,uBACEA,cAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAWC,WAAA;AAAA,QACT,0DAAA;AAAA,QACA,YAAY,IAAI,CAAA;AAAA,QAChBC,kCAAA,CAAY,SAAA;AAAA,QACZ,oCAAA;AAAA,QACA,aAAa,KAAK,CAAA;AAAA,QAClB;AAAA,OACF;AAAA,MAEC;AAAA;AAAA,GACH;AAEJ;;;;"}
@@ -0,0 +1,32 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { transitions } from './primitives/transitions.es.js';
3
+ import { c as cn } from './cn-BakJzhuv.js';
4
+
5
+ const themeClasses = {
6
+ light: "bg-gray-100 border-gray-300 text-gray-700 shadow-[0_1px_0_rgba(0,0,0,0.1)]",
7
+ dark: "bg-surface4 border-border1 text-neutral5 shadow-[0_1px_0_rgba(0,0,0,0.3)]"
8
+ };
9
+ const sizeClasses = {
10
+ default: "h-6 min-w-6 rounded-md px-1.5 text-ui-sm",
11
+ sm: "h-5 min-w-5 rounded-md px-1 text-ui-xs",
12
+ xs: "h-4 min-w-4 rounded px-1 text-ui-xs leading-none"
13
+ };
14
+ const Kbd = ({ children, theme = "dark", size = "default", className }) => {
15
+ return /* @__PURE__ */ jsx(
16
+ "kbd",
17
+ {
18
+ className: cn(
19
+ "inline-flex items-center justify-center border font-mono",
20
+ sizeClasses[size],
21
+ transitions.transform,
22
+ "active:scale-95 active:shadow-none",
23
+ themeClasses[theme],
24
+ className
25
+ ),
26
+ children
27
+ }
28
+ );
29
+ };
30
+
31
+ export { Kbd as K };
32
+ //# sourceMappingURL=kbd-DE0nj4DL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kbd-DE0nj4DL.js","sources":["../src/ds/components/Kbd/kbd.tsx"],"sourcesContent":["import { transitions } from '@/ds/primitives/transitions';\nimport { cn } from '@/lib/utils';\n\nexport type KbdProps = {\n children: React.ReactNode;\n theme?: 'light' | 'dark';\n size?: 'default' | 'sm' | 'xs';\n className?: string;\n};\n\nconst themeClasses: Record<NonNullable<KbdProps['theme']>, string> = {\n light: 'bg-gray-100 border-gray-300 text-gray-700 shadow-[0_1px_0_rgba(0,0,0,0.1)]',\n dark: 'bg-surface4 border-border1 text-neutral5 shadow-[0_1px_0_rgba(0,0,0,0.3)]',\n};\n\n// Fixed heights, not padding — `font-mono` normal leading varies per glyph set and desyncs the scale\nconst sizeClasses: Record<NonNullable<KbdProps['size']>, string> = {\n default: 'h-6 min-w-6 rounded-md px-1.5 text-ui-sm',\n sm: 'h-5 min-w-5 rounded-md px-1 text-ui-xs',\n xs: 'h-4 min-w-4 rounded px-1 text-ui-xs leading-none',\n};\n\nexport const Kbd = ({ children, theme = 'dark', size = 'default', className }: KbdProps) => {\n return (\n <kbd\n className={cn(\n 'inline-flex items-center justify-center border font-mono',\n sizeClasses[size],\n transitions.transform,\n 'active:scale-95 active:shadow-none',\n themeClasses[theme],\n className,\n )}\n >\n {children}\n </kbd>\n );\n};\n"],"names":[],"mappings":";;;;AAUA,MAAM,YAAA,GAA+D;AAAA,EACnE,KAAA,EAAO,4EAAA;AAAA,EACP,IAAA,EAAM;AACR,CAAA;AAGA,MAAM,WAAA,GAA6D;AAAA,EACjE,OAAA,EAAS,0CAAA;AAAA,EACT,EAAA,EAAI,wCAAA;AAAA,EACJ,EAAA,EAAI;AACN,CAAA;AAEO,MAAM,GAAA,GAAM,CAAC,EAAE,QAAA,EAAU,QAAQ,MAAA,EAAQ,IAAA,GAAO,SAAA,EAAW,SAAA,EAAU,KAAgB;AAC1F,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,EAAA;AAAA,QACT,0DAAA;AAAA,QACA,YAAY,IAAI,CAAA;AAAA,QAChB,WAAA,CAAY,SAAA;AAAA,QACZ,oCAAA;AAAA,QACA,aAAa,KAAK,CAAA;AAAA,QAClB;AAAA,OACF;AAAA,MAEC;AAAA;AAAA,GACH;AAEJ;;;;"}
@@ -0,0 +1,563 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { ArrowDownIcon } from 'lucide-react';
3
+ import * as React from 'react';
4
+ import { D as DEFAULT_SCROLLABLE, a as DEFAULT_VISIBILITY, A as AUTO_SCROLL_ATTACH_THRESHOLD, b as DEFAULT_SCROLL_EDGE_THRESHOLD, c as DEFAULT_SCROLL_MARGIN, d as DEFAULT_SCROLL_PREVIOUS_ITEM_PEEK, e as DEFAULT_REACH_START_THRESHOLD, M as MessageScrollerActionsContext, f as MessageScrollerScrollableContext, g as MessageScrollerVisibilityContext, u as useRequiredMessageScrollerActionsContext, h as useRequiredMessageScrollerScrollableContext } from './message-scroller-context-tTA6jxho.js';
5
+ import { c as cn } from './cn-BakJzhuv.js';
6
+
7
+ const VISIBILITY_EPSILON = 0.5;
8
+ const mergeRefs = (...refs) => (element) => {
9
+ refs.forEach((ref) => {
10
+ if (!ref) return;
11
+ if (typeof ref === "function") {
12
+ ref(element);
13
+ return;
14
+ }
15
+ ref.current = element;
16
+ });
17
+ };
18
+ const scrollableMatches = (left, right) => left.start === right.start && left.end === right.end;
19
+ const visibilityMatches = (left, right) => left.currentAnchorId === right.currentAnchorId && left.visibleMessageIds.length === right.visibleMessageIds.length && left.visibleMessageIds.every((messageId, index) => messageId === right.visibleMessageIds[index]);
20
+ const getContentPadding = (contentElement) => {
21
+ if (!contentElement) return { start: 0, end: 0 };
22
+ const styles = window.getComputedStyle(contentElement);
23
+ return {
24
+ start: Number.parseFloat(styles.paddingBlockStart || styles.paddingTop || "0") || 0,
25
+ end: Number.parseFloat(styles.paddingBlockEnd || styles.paddingBottom || "0") || 0
26
+ };
27
+ };
28
+ const getRelativeTop = (element, viewportElement) => {
29
+ const elementRect = element.getBoundingClientRect();
30
+ const viewportRect = viewportElement.getBoundingClientRect();
31
+ return elementRect.top - viewportRect.top + viewportElement.scrollTop;
32
+ };
33
+ const scrollViewportTo = (viewportElement, top, behavior) => {
34
+ if (typeof viewportElement.scrollTo === "function") {
35
+ viewportElement.scrollTo({ top, behavior });
36
+ return;
37
+ }
38
+ viewportElement.scrollTop = top;
39
+ };
40
+ const scheduleScrollSync = (callback) => {
41
+ if (typeof window.requestAnimationFrame === "function") {
42
+ window.requestAnimationFrame(callback);
43
+ return;
44
+ }
45
+ window.setTimeout(callback, 0);
46
+ };
47
+ const getScrollTarget = ({
48
+ align,
49
+ element,
50
+ scrollMargin,
51
+ viewportElement
52
+ }) => {
53
+ const contentPadding = getContentPadding(element.parentElement);
54
+ const elementTop = getRelativeTop(element, viewportElement);
55
+ const elementHeight = element.getBoundingClientRect().height;
56
+ const visibleHeight = Math.max(0, viewportElement.clientHeight - contentPadding.start - contentPadding.end);
57
+ if (align === "center") return elementTop - contentPadding.start - (visibleHeight - elementHeight) / 2 - scrollMargin;
58
+ if (align === "end")
59
+ return elementTop - viewportElement.clientHeight + elementHeight + contentPadding.end + scrollMargin;
60
+ if (align === "nearest") {
61
+ const elementBottom = elementTop + elementHeight;
62
+ const viewportTop = viewportElement.scrollTop + contentPadding.start;
63
+ const viewportBottom = viewportElement.scrollTop + viewportElement.clientHeight - contentPadding.end;
64
+ if (elementTop >= viewportTop && elementBottom <= viewportBottom) return viewportElement.scrollTop;
65
+ return elementTop < viewportTop ? elementTop - contentPadding.start - scrollMargin : elementBottom - viewportElement.clientHeight + contentPadding.end + scrollMargin;
66
+ }
67
+ return elementTop - contentPadding.start - scrollMargin;
68
+ };
69
+ const getCurrentAnchorId = ({
70
+ fallbackAnchorId,
71
+ items,
72
+ scrollMargin,
73
+ scrollPreviousItemPeek,
74
+ visibleMessageIds,
75
+ viewportElement
76
+ }) => {
77
+ const anchorLine = viewportElement.getBoundingClientRect().top + scrollMargin + scrollPreviousItemPeek;
78
+ const anchors = items.filter(([, item]) => item.scrollAnchor);
79
+ let anchoredAboveViewport;
80
+ for (const [messageId, item] of anchors) {
81
+ if (item.element.getBoundingClientRect().top <= anchorLine + VISIBILITY_EPSILON) {
82
+ anchoredAboveViewport = messageId;
83
+ }
84
+ }
85
+ if (anchoredAboveViewport) return anchoredAboveViewport;
86
+ return anchors.find(([messageId]) => visibleMessageIds.has(messageId))?.[0] ?? fallbackAnchorId;
87
+ };
88
+ function MessageScrollerProvider({
89
+ autoScroll = false,
90
+ children,
91
+ defaultScrollPosition = "end",
92
+ onReachStart,
93
+ preserveScrollOnPrepend = false,
94
+ reachStartThreshold = DEFAULT_REACH_START_THRESHOLD,
95
+ scrollEdgeThreshold = DEFAULT_SCROLL_EDGE_THRESHOLD,
96
+ scrollMargin = DEFAULT_SCROLL_MARGIN,
97
+ scrollPreviousItemPeek = DEFAULT_SCROLL_PREVIOUS_ITEM_PEEK
98
+ }) {
99
+ const itemsRef = React.useRef(null);
100
+ itemsRef.current ??= /* @__PURE__ */ new Map();
101
+ const itemsRegistry = itemsRef.current;
102
+ const visibleMessageIdsRef = React.useRef(null);
103
+ visibleMessageIdsRef.current ??= /* @__PURE__ */ new Set();
104
+ const intersectingMessageIds = visibleMessageIdsRef.current;
105
+ const [itemsVersion, setItemsVersion] = React.useState(0);
106
+ const [rootElement, setRootElement] = React.useState(null);
107
+ const [viewportElement, setViewportElement] = React.useState(null);
108
+ const [contentElement, setContentElement] = React.useState(null);
109
+ const defaultScrollAppliedRef = React.useRef(false);
110
+ const [scrollable, setScrollable] = React.useState(DEFAULT_SCROLLABLE);
111
+ const [visibility, setVisibility] = React.useState(DEFAULT_VISIBILITY);
112
+ const atEndRef = React.useRef(true);
113
+ const reachStartArmedRef = React.useRef(false);
114
+ const reachStartFiredRef = React.useRef(false);
115
+ const prependAnchorRef = React.useRef(null);
116
+ const firstItemIdRef = React.useRef(void 0);
117
+ const onReachStartRef = React.useRef(onReachStart);
118
+ React.useEffect(() => {
119
+ onReachStartRef.current = onReachStart;
120
+ }, [onReachStart]);
121
+ const publishScrollable = React.useCallback(
122
+ (nextScrollable) => {
123
+ const scrollableValue = [nextScrollable.start && "start", nextScrollable.end && "end"].filter(Boolean).join(" ");
124
+ for (const element of [rootElement, viewportElement]) {
125
+ if (!element) continue;
126
+ if (scrollableValue) {
127
+ element.setAttribute("data-scrollable", scrollableValue);
128
+ } else {
129
+ element.removeAttribute("data-scrollable");
130
+ }
131
+ }
132
+ setScrollable((current) => scrollableMatches(current, nextScrollable) ? current : nextScrollable);
133
+ },
134
+ [rootElement, viewportElement]
135
+ );
136
+ const publishVisibility = React.useCallback((nextVisibility) => {
137
+ setVisibility((current) => visibilityMatches(current, nextVisibility) ? current : nextVisibility);
138
+ }, []);
139
+ const updateScrollable = React.useCallback(() => {
140
+ if (!viewportElement) {
141
+ atEndRef.current = true;
142
+ publishScrollable(DEFAULT_SCROLLABLE);
143
+ return;
144
+ }
145
+ const remainingScroll = viewportElement.scrollHeight - viewportElement.scrollTop - viewportElement.clientHeight;
146
+ atEndRef.current = remainingScroll < AUTO_SCROLL_ATTACH_THRESHOLD;
147
+ publishScrollable({
148
+ start: viewportElement.scrollTop > scrollEdgeThreshold,
149
+ end: remainingScroll > scrollEdgeThreshold
150
+ });
151
+ }, [publishScrollable, scrollEdgeThreshold, viewportElement]);
152
+ const updateVisibility = React.useCallback(() => {
153
+ const items = Array.from(itemsRegistry.entries());
154
+ const fallbackAnchorId = items.filter(([, item]) => item.scrollAnchor).at(-1)?.[0] ?? items.at(-1)?.[0];
155
+ if (items.length === 0) {
156
+ publishVisibility(DEFAULT_VISIBILITY);
157
+ return;
158
+ }
159
+ if (!viewportElement) {
160
+ publishVisibility({
161
+ currentAnchorId: fallbackAnchorId,
162
+ visibleMessageIds: fallbackAnchorId ? [fallbackAnchorId] : []
163
+ });
164
+ return;
165
+ }
166
+ const viewportRect = viewportElement.getBoundingClientRect();
167
+ const visibleMessageIds = /* @__PURE__ */ new Set();
168
+ if (typeof IntersectionObserver === "undefined") {
169
+ const visibilityTop = viewportRect.top + scrollMargin + scrollPreviousItemPeek;
170
+ items.forEach(([messageId, item]) => {
171
+ const rect = item.element.getBoundingClientRect();
172
+ if (rect.bottom > visibilityTop && rect.top < viewportRect.bottom) visibleMessageIds.add(messageId);
173
+ });
174
+ } else {
175
+ intersectingMessageIds.forEach((messageId) => visibleMessageIds.add(messageId));
176
+ }
177
+ const orderedVisibleMessageIds = items.flatMap(
178
+ ([messageId]) => visibleMessageIds.has(messageId) ? [messageId] : []
179
+ );
180
+ publishVisibility({
181
+ currentAnchorId: getCurrentAnchorId({
182
+ fallbackAnchorId,
183
+ items,
184
+ scrollMargin,
185
+ scrollPreviousItemPeek,
186
+ visibleMessageIds,
187
+ viewportElement
188
+ }),
189
+ visibleMessageIds: orderedVisibleMessageIds.length > 0 ? orderedVisibleMessageIds : fallbackAnchorId ? [fallbackAnchorId] : []
190
+ });
191
+ }, [intersectingMessageIds, itemsRegistry, publishVisibility, scrollMargin, scrollPreviousItemPeek, viewportElement]);
192
+ const syncAfterScroll = React.useCallback(() => {
193
+ updateScrollable();
194
+ updateVisibility();
195
+ }, [updateScrollable, updateVisibility]);
196
+ const notifyScroll = React.useCallback(() => {
197
+ const wasScrollable = Boolean(viewportElement && viewportElement.scrollHeight > viewportElement.clientHeight);
198
+ syncAfterScroll();
199
+ if (!viewportElement) return;
200
+ if (atEndRef.current && wasScrollable) reachStartArmedRef.current = true;
201
+ if (!reachStartArmedRef.current) return;
202
+ if (!wasScrollable) return;
203
+ if (viewportElement.scrollTop > reachStartThreshold) {
204
+ reachStartFiredRef.current = false;
205
+ return;
206
+ }
207
+ if (reachStartFiredRef.current) return;
208
+ if (!onReachStartRef.current) return;
209
+ reachStartFiredRef.current = true;
210
+ if (preserveScrollOnPrepend) {
211
+ prependAnchorRef.current = {
212
+ scrollHeight: viewportElement.scrollHeight,
213
+ scrollTop: viewportElement.scrollTop
214
+ };
215
+ }
216
+ onReachStartRef.current();
217
+ }, [preserveScrollOnPrepend, reachStartThreshold, syncAfterScroll, viewportElement]);
218
+ const notifyContentResize = React.useCallback(() => {
219
+ const followEnd = autoScroll && defaultScrollAppliedRef.current && atEndRef.current && viewportElement;
220
+ if (followEnd) {
221
+ scrollViewportTo(
222
+ viewportElement,
223
+ Math.max(0, viewportElement.scrollHeight - viewportElement.clientHeight),
224
+ "auto"
225
+ );
226
+ }
227
+ syncAfterScroll();
228
+ }, [autoScroll, syncAfterScroll, viewportElement]);
229
+ const scrollToElement = React.useCallback(
230
+ (element, {
231
+ align = "start",
232
+ behavior = "auto",
233
+ scrollMargin: optionScrollMargin = scrollMargin
234
+ } = {}) => {
235
+ if (!viewportElement || !contentElement?.contains(element)) return false;
236
+ const nextScrollTop = Math.max(
237
+ 0,
238
+ getScrollTarget({ align, element, scrollMargin: optionScrollMargin, viewportElement })
239
+ );
240
+ if (Math.abs(viewportElement.scrollTop - nextScrollTop) <= VISIBILITY_EPSILON) {
241
+ scrollViewportTo(viewportElement, nextScrollTop, "auto");
242
+ syncAfterScroll();
243
+ return true;
244
+ }
245
+ scrollViewportTo(viewportElement, nextScrollTop, behavior);
246
+ scheduleScrollSync(syncAfterScroll);
247
+ return true;
248
+ },
249
+ [contentElement, scrollMargin, syncAfterScroll, viewportElement]
250
+ );
251
+ const scrollToStart = React.useCallback(
252
+ ({ behavior = "auto" } = {}) => {
253
+ if (!viewportElement) return false;
254
+ scrollViewportTo(viewportElement, 0, behavior);
255
+ scheduleScrollSync(syncAfterScroll);
256
+ return true;
257
+ },
258
+ [syncAfterScroll, viewportElement]
259
+ );
260
+ const scrollToEnd = React.useCallback(
261
+ ({ behavior = "auto" } = {}) => {
262
+ if (!viewportElement) return false;
263
+ scrollViewportTo(
264
+ viewportElement,
265
+ Math.max(0, viewportElement.scrollHeight - viewportElement.clientHeight),
266
+ behavior
267
+ );
268
+ scheduleScrollSync(syncAfterScroll);
269
+ return true;
270
+ },
271
+ [syncAfterScroll, viewportElement]
272
+ );
273
+ const scrollToMessage = React.useCallback(
274
+ (messageId, options) => {
275
+ const item = itemsRegistry.get(messageId);
276
+ if (!item) return false;
277
+ return scrollToElement(item.element, options);
278
+ },
279
+ [itemsRegistry, scrollToElement]
280
+ );
281
+ const registerItem = React.useCallback(
282
+ (messageId, element, scrollAnchor) => {
283
+ itemsRegistry.set(messageId, { element, scrollAnchor });
284
+ setItemsVersion((version) => version + 1);
285
+ return () => {
286
+ const current = itemsRegistry.get(messageId);
287
+ if (current?.element !== element) return;
288
+ itemsRegistry.delete(messageId);
289
+ intersectingMessageIds.delete(messageId);
290
+ setItemsVersion((version) => version + 1);
291
+ };
292
+ },
293
+ [intersectingMessageIds, itemsRegistry]
294
+ );
295
+ React.useEffect(() => {
296
+ if (!contentElement || !viewportElement || typeof IntersectionObserver === "undefined") {
297
+ updateVisibility();
298
+ return void 0;
299
+ }
300
+ const messageIdByElement = /* @__PURE__ */ new Map();
301
+ for (const [messageId, item] of itemsRegistry) {
302
+ messageIdByElement.set(item.element, messageId);
303
+ }
304
+ const observer = new IntersectionObserver(
305
+ (entries) => {
306
+ for (const entry of entries) {
307
+ const messageId = messageIdByElement.get(entry.target);
308
+ if (!messageId) continue;
309
+ if (entry.isIntersecting) {
310
+ intersectingMessageIds.add(messageId);
311
+ } else {
312
+ intersectingMessageIds.delete(messageId);
313
+ }
314
+ }
315
+ updateVisibility();
316
+ },
317
+ {
318
+ root: viewportElement,
319
+ rootMargin: `${-(scrollMargin + scrollPreviousItemPeek)}px 0px 0px 0px`,
320
+ threshold: [0, 0.01, 0.5, 1]
321
+ }
322
+ );
323
+ for (const [, item] of itemsRegistry) observer.observe(item.element);
324
+ updateVisibility();
325
+ return () => observer.disconnect();
326
+ }, [
327
+ contentElement,
328
+ intersectingMessageIds,
329
+ itemsRegistry,
330
+ itemsVersion,
331
+ scrollMargin,
332
+ scrollPreviousItemPeek,
333
+ updateVisibility,
334
+ viewportElement
335
+ ]);
336
+ React.useEffect(() => {
337
+ if (!viewportElement || typeof ResizeObserver === "undefined") return void 0;
338
+ const observer = new ResizeObserver(syncAfterScroll);
339
+ observer.observe(viewportElement);
340
+ return () => observer.disconnect();
341
+ }, [syncAfterScroll, viewportElement]);
342
+ React.useLayoutEffect(() => {
343
+ updateScrollable();
344
+ updateVisibility();
345
+ }, [itemsVersion, updateScrollable, updateVisibility]);
346
+ React.useLayoutEffect(() => {
347
+ if (defaultScrollAppliedRef.current || !viewportElement || itemsRegistry.size === 0) return;
348
+ let didScroll = false;
349
+ if (defaultScrollPosition === "start") {
350
+ didScroll = scrollToStart({ behavior: "auto" });
351
+ } else if (defaultScrollPosition === "last-anchor") {
352
+ const lastAnchorId = Array.from(itemsRegistry.entries()).filter(([, item]) => item.scrollAnchor).at(-1)?.[0];
353
+ didScroll = lastAnchorId ? scrollToMessage(lastAnchorId, { align: "start", behavior: "auto" }) : scrollToEnd({ behavior: "auto" });
354
+ } else {
355
+ didScroll = scrollToEnd({ behavior: "auto" });
356
+ }
357
+ if (didScroll) defaultScrollAppliedRef.current = true;
358
+ }, [
359
+ defaultScrollPosition,
360
+ itemsRegistry,
361
+ itemsVersion,
362
+ scrollToEnd,
363
+ scrollToMessage,
364
+ scrollToStart,
365
+ viewportElement
366
+ ]);
367
+ React.useLayoutEffect(() => {
368
+ const previousFirstItemId = firstItemIdRef.current;
369
+ const firstItemId = contentElement?.querySelector('[data-slot="message-scroller-item"]')?.dataset.messageId;
370
+ firstItemIdRef.current = firstItemId;
371
+ const anchor = prependAnchorRef.current;
372
+ prependAnchorRef.current = null;
373
+ if (!anchor || !viewportElement || firstItemId === previousFirstItemId) return;
374
+ reachStartFiredRef.current = false;
375
+ const grownBy = viewportElement.scrollHeight - anchor.scrollHeight;
376
+ if (grownBy > 0) viewportElement.scrollTop = anchor.scrollTop + grownBy;
377
+ }, [contentElement, itemsVersion, viewportElement]);
378
+ React.useLayoutEffect(() => {
379
+ if (!autoScroll || !defaultScrollAppliedRef.current || !atEndRef.current) return;
380
+ scrollToEnd({ behavior: "auto" });
381
+ }, [autoScroll, itemsVersion, scrollToEnd]);
382
+ const actionsContextValue = React.useMemo(
383
+ () => ({
384
+ notifyContentResize,
385
+ notifyScroll,
386
+ registerItem,
387
+ scrollToEnd,
388
+ scrollToMessage,
389
+ scrollToStart,
390
+ setContentElement,
391
+ setRootElement,
392
+ setViewportElement,
393
+ syncAfterScroll
394
+ }),
395
+ [notifyContentResize, notifyScroll, registerItem, scrollToEnd, scrollToMessage, scrollToStart, syncAfterScroll]
396
+ );
397
+ const scrollableContextValue = React.useMemo(
398
+ () => ({
399
+ end: scrollable.end,
400
+ start: scrollable.start
401
+ }),
402
+ [scrollable.end, scrollable.start]
403
+ );
404
+ const visibilityContextValue = React.useMemo(
405
+ () => ({
406
+ currentAnchorId: visibility.currentAnchorId,
407
+ visibleMessageIds: visibility.visibleMessageIds
408
+ }),
409
+ [visibility.currentAnchorId, visibility.visibleMessageIds]
410
+ );
411
+ return /* @__PURE__ */ jsx(MessageScrollerActionsContext.Provider, { value: actionsContextValue, children: /* @__PURE__ */ jsx(MessageScrollerScrollableContext.Provider, { value: scrollableContextValue, children: /* @__PURE__ */ jsx(MessageScrollerVisibilityContext.Provider, { value: visibilityContextValue, children }) }) });
412
+ }
413
+ const MessageScroller = React.forwardRef(
414
+ ({ className, ...props }, ref) => {
415
+ const { setRootElement } = useRequiredMessageScrollerActionsContext("MessageScroller");
416
+ return /* @__PURE__ */ jsx(
417
+ "div",
418
+ {
419
+ ref: mergeRefs(setRootElement, ref),
420
+ "data-slot": "message-scroller",
421
+ className: cn("group/message-scroller relative flex size-full min-h-0 flex-col overflow-hidden", className),
422
+ ...props
423
+ }
424
+ );
425
+ }
426
+ );
427
+ MessageScroller.displayName = "MessageScroller";
428
+ const MessageScrollerViewport = React.forwardRef(
429
+ ({ className, onScroll, role, tabIndex, ...props }, ref) => {
430
+ const { setViewportElement, notifyScroll } = useRequiredMessageScrollerActionsContext("MessageScrollerViewport");
431
+ const viewportRef = React.useMemo(() => mergeRefs(setViewportElement, ref), [ref, setViewportElement]);
432
+ return /* @__PURE__ */ jsx(
433
+ "div",
434
+ {
435
+ ref: viewportRef,
436
+ role: role ?? "region",
437
+ tabIndex: tabIndex ?? 0,
438
+ "data-slot": "message-scroller-viewport",
439
+ className: cn(
440
+ "size-full min-h-0 min-w-0 overflow-y-auto overscroll-contain data-autoscrolling:scrollbar-thumb-transparent data-autoscrolling:scrollbar-track-transparent",
441
+ className
442
+ ),
443
+ onScroll: (event) => {
444
+ notifyScroll();
445
+ onScroll?.(event);
446
+ },
447
+ ...props
448
+ }
449
+ );
450
+ }
451
+ );
452
+ MessageScrollerViewport.displayName = "MessageScrollerViewport";
453
+ const MessageScrollerContent = React.forwardRef(
454
+ ({ children, className, spacerClassName, role, "aria-relevant": ariaRelevant = "additions", ...props }, ref) => {
455
+ const { setContentElement, notifyContentResize, syncAfterScroll } = useRequiredMessageScrollerActionsContext("MessageScrollerContent");
456
+ const [contentElement, setLocalContentElement] = React.useState(null);
457
+ const contentRef = React.useMemo(
458
+ () => mergeRefs(setContentElement, setLocalContentElement, ref),
459
+ [ref, setContentElement]
460
+ );
461
+ React.useLayoutEffect(() => {
462
+ syncAfterScroll();
463
+ }, [syncAfterScroll]);
464
+ React.useEffect(() => {
465
+ if (!contentElement || typeof MutationObserver === "undefined") return void 0;
466
+ const observer = new MutationObserver(notifyContentResize);
467
+ observer.observe(contentElement, { childList: true, subtree: false });
468
+ return () => observer.disconnect();
469
+ }, [contentElement, notifyContentResize]);
470
+ React.useEffect(() => {
471
+ if (!contentElement || typeof ResizeObserver === "undefined") return void 0;
472
+ const observer = new ResizeObserver(notifyContentResize);
473
+ observer.observe(contentElement);
474
+ return () => observer.disconnect();
475
+ }, [contentElement, notifyContentResize]);
476
+ return /* @__PURE__ */ jsxs(
477
+ "div",
478
+ {
479
+ ref: contentRef,
480
+ role: role ?? "log",
481
+ "aria-relevant": ariaRelevant,
482
+ "data-slot": "message-scroller-content",
483
+ className: cn("flex h-max min-h-full flex-col gap-6", className),
484
+ ...props,
485
+ children: [
486
+ children,
487
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", "data-message-scroller-spacer": "", hidden: true, className: spacerClassName })
488
+ ]
489
+ }
490
+ );
491
+ }
492
+ );
493
+ MessageScrollerContent.displayName = "MessageScrollerContent";
494
+ const MessageScrollerItem = React.forwardRef(
495
+ ({ className, messageId, scrollAnchor = false, ...props }, ref) => {
496
+ const registerItem = React.useContext(MessageScrollerActionsContext)?.registerItem;
497
+ const unregisterRef = React.useRef(void 0);
498
+ const itemRef = React.useCallback(
499
+ (element) => {
500
+ unregisterRef.current?.();
501
+ unregisterRef.current = void 0;
502
+ mergeRefs(ref)(element);
503
+ if (!element || !messageId || !registerItem) return;
504
+ unregisterRef.current = registerItem(messageId, element, scrollAnchor);
505
+ },
506
+ [messageId, ref, registerItem, scrollAnchor]
507
+ );
508
+ return /* @__PURE__ */ jsx(
509
+ "div",
510
+ {
511
+ ref: itemRef,
512
+ "data-slot": "message-scroller-item",
513
+ "data-message-id": messageId,
514
+ "data-scroll-anchor": scrollAnchor ? "true" : "false",
515
+ className: cn("min-w-0 shrink-0 [contain-intrinsic-size:auto_10rem] [content-visibility:auto]", className),
516
+ ...props
517
+ }
518
+ );
519
+ }
520
+ );
521
+ MessageScrollerItem.displayName = "MessageScrollerItem";
522
+ const MessageScrollerButton = React.forwardRef(
523
+ ({ behavior = "smooth", direction = "end", className, children, onClick, tabIndex, type = "button", ...props }, ref) => {
524
+ const { scrollToEnd, scrollToStart } = useRequiredMessageScrollerActionsContext("MessageScrollerButton");
525
+ const { start, end } = useRequiredMessageScrollerScrollableContext("MessageScrollerButton");
526
+ const active = direction === "start" ? start : end;
527
+ return /* @__PURE__ */ jsx(
528
+ "button",
529
+ {
530
+ ref,
531
+ type,
532
+ ...props,
533
+ "data-slot": "message-scroller-button",
534
+ "data-active": active ? "true" : "false",
535
+ "data-direction": direction,
536
+ tabIndex: active ? tabIndex : -1,
537
+ className: cn(
538
+ "absolute inset-s-1/2 inline-flex min-h-5 min-w-7 -translate-x-1/2 items-center justify-center rounded-full border border-border1 bg-surface3 text-neutral6 shadow-[0_1px_2px_-1px_oklch(0%_0_0deg/10%),0_8px_20px_-12px_oklch(0%_0_0deg/25%)] transition-[translate,scale,opacity] duration-200 hover:bg-surface4 data-[active=false]:pointer-events-none data-[active=false]:scale-95 data-[active=false]:opacity-0 data-[active=false]:duration-400 data-[active=false]:ease-[cubic-bezier(0.7,0,0.84,0)] data-[active=true]:translate-y-0 data-[active=true]:scale-100 data-[active=true]:opacity-100 data-[active=true]:ease-[cubic-bezier(0.23,1,0.32,1)] data-[direction=end]:bottom-4 data-[direction=end]:data-[active=false]:translate-y-full data-[direction=start]:top-4 data-[direction=start]:data-[active=false]:-translate-y-full rtl:translate-x-1/2 data-[direction=start]:[&_svg]:rotate-180",
539
+ className
540
+ ),
541
+ onClick: (event) => {
542
+ if (!active) return;
543
+ onClick?.(event);
544
+ if (event.defaultPrevented) return;
545
+ event.currentTarget.blur();
546
+ if (direction === "start") {
547
+ scrollToStart({ behavior });
548
+ } else {
549
+ scrollToEnd({ behavior });
550
+ }
551
+ },
552
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
553
+ /* @__PURE__ */ jsx(ArrowDownIcon, { className: "size-4", "aria-hidden": true }),
554
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: direction === "end" ? "Scroll to end" : "Scroll to start" })
555
+ ] })
556
+ }
557
+ );
558
+ }
559
+ );
560
+ MessageScrollerButton.displayName = "MessageScrollerButton";
561
+
562
+ export { MessageScroller as M, MessageScrollerButton as a, MessageScrollerContent as b, MessageScrollerItem as c, MessageScrollerProvider as d, MessageScrollerViewport as e };
563
+ //# sourceMappingURL=message-scroller-BQpwFsqj.js.map