@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.
- package/CHANGELOG.md +75 -0
- package/dist/CommandPalette.css +135 -0
- package/dist/command-Duq1xsiS.js +168 -0
- package/dist/command-Duq1xsiS.js.map +1 -0
- package/dist/command-qsEykgml.cjs +197 -0
- package/dist/command-qsEykgml.cjs.map +1 -0
- package/dist/components/ChatShell.cjs.js +96 -0
- package/dist/components/ChatShell.cjs.js.map +1 -0
- package/dist/components/ChatShell.d.ts +2 -0
- package/dist/components/ChatShell.es.js +92 -0
- package/dist/components/ChatShell.es.js.map +1 -0
- package/dist/components/Command.cjs.js +10 -191
- package/dist/components/Command.cjs.js.map +1 -1
- package/dist/components/Command.es.js +1 -167
- package/dist/components/Command.es.js.map +1 -1
- package/dist/components/CommandPalette.cjs.js +180 -0
- package/dist/components/CommandPalette.cjs.js.map +1 -0
- package/dist/components/CommandPalette.d.ts +2 -0
- package/dist/components/CommandPalette.es.js +169 -0
- package/dist/components/CommandPalette.es.js.map +1 -0
- package/dist/components/Kbd.cjs.js +3 -24
- package/dist/components/Kbd.cjs.js.map +1 -1
- package/dist/components/Kbd.es.js +1 -26
- package/dist/components/Kbd.es.js.map +1 -1
- package/dist/components/MessageScroller.cjs.js +8 -524
- package/dist/components/MessageScroller.cjs.js.map +1 -1
- package/dist/components/MessageScroller.es.js +2 -504
- package/dist/components/MessageScroller.es.js.map +1 -1
- package/dist/components/ThreadRail.cjs.js +1 -1
- package/dist/components/ThreadRail.es.js +1 -1
- package/dist/kbd-Br7kdwaB.cjs +34 -0
- package/dist/kbd-Br7kdwaB.cjs.map +1 -0
- package/dist/kbd-DE0nj4DL.js +32 -0
- package/dist/kbd-DE0nj4DL.js.map +1 -0
- package/dist/message-scroller-BQpwFsqj.js +563 -0
- package/dist/message-scroller-BQpwFsqj.js.map +1 -0
- package/dist/message-scroller-DIgCd5n3.cjs +589 -0
- package/dist/message-scroller-DIgCd5n3.cjs.map +1 -0
- package/dist/{message-scroller-hooks-DdXqrmC2.cjs → message-scroller-context-_VfCYFBU.cjs} +6 -48
- package/dist/message-scroller-context-_VfCYFBU.cjs.map +1 -0
- package/dist/message-scroller-context-tTA6jxho.js +23 -0
- package/dist/message-scroller-context-tTA6jxho.js.map +1 -0
- package/dist/message-scroller-hooks-BxAf8k0Y.cjs +72 -0
- package/dist/message-scroller-hooks-BxAf8k0Y.cjs.map +1 -0
- package/dist/{message-scroller-hooks-CdsHnth6.js → message-scroller-hooks-D5yxCon2.js} +3 -19
- package/dist/message-scroller-hooks-D5yxCon2.js.map +1 -0
- package/dist/src/ds/components/ChatShell/chat-shell.d.ts +46 -0
- package/dist/src/ds/components/ChatShell/chat-shell.stories.d.ts +8 -0
- package/dist/src/ds/components/ChatShell/index.d.ts +11 -0
- package/dist/src/ds/components/CommandPalette/command-palette.d.ts +37 -0
- package/dist/src/ds/components/CommandPalette/command-palette.stories.d.ts +12 -0
- package/dist/src/ds/components/CommandPalette/index.d.ts +1 -0
- package/dist/src/ds/components/Kbd/kbd.d.ts +2 -1
- package/dist/src/ds/components/MessageScroller/message-scroller-context.d.ts +4 -0
- package/dist/src/ds/components/MessageScroller/message-scroller.d.ts +8 -7
- package/dist/style.css +343 -2
- package/package.json +6 -6
- package/dist/message-scroller-hooks-CdsHnth6.js.map +0 -1
- 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
|