@nadicodeai/ui 0.21.0 → 0.22.0
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/AGENTS.md +2 -3
- package/README.md +14 -21
- package/dist/agents.d.ts +10 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +9 -0
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/use-case-bento.d.ts +2 -4
- package/dist/components/use-case-bento.d.ts.map +1 -1
- package/dist/components/use-case-bento.js +2 -3
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +0 -1
- package/dist/index.d.ts +0 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -16
- package/dist/internal/assistant-ui/attachment.d.ts +5 -0
- package/dist/internal/assistant-ui/attachment.d.ts.map +1 -0
- package/dist/internal/assistant-ui/attachment.js +93 -0
- package/dist/internal/assistant-ui/follow-up-suggestions.d.ts +3 -0
- package/dist/internal/assistant-ui/follow-up-suggestions.d.ts.map +1 -0
- package/dist/internal/assistant-ui/follow-up-suggestions.js +8 -0
- package/dist/internal/assistant-ui/markdown-text.d.ts +2 -0
- package/dist/internal/assistant-ui/markdown-text.d.ts.map +1 -0
- package/dist/internal/assistant-ui/markdown-text.js +54 -0
- package/dist/internal/assistant-ui/reasoning.d.ts +31 -0
- package/dist/internal/assistant-ui/reasoning.d.ts.map +1 -0
- package/dist/internal/assistant-ui/reasoning.js +118 -0
- package/dist/internal/assistant-ui/thread.d.ts +26 -0
- package/dist/internal/assistant-ui/thread.d.ts.map +1 -0
- package/dist/internal/assistant-ui/thread.js +118 -0
- package/dist/internal/assistant-ui/tool-fallback.d.ts +10 -0
- package/dist/internal/assistant-ui/tool-fallback.d.ts.map +1 -0
- package/dist/internal/assistant-ui/tool-fallback.js +163 -0
- package/dist/internal/assistant-ui/tool-group.d.ts +18 -0
- package/dist/internal/assistant-ui/tool-group.d.ts.map +1 -0
- package/dist/internal/assistant-ui/tool-group.js +44 -0
- package/dist/internal/assistant-ui/tooltip-icon-button.d.ts +8 -0
- package/dist/internal/assistant-ui/tooltip-icon-button.d.ts.map +1 -0
- package/dist/internal/assistant-ui/tooltip-icon-button.js +10 -0
- package/dist/styles/globals.css +3 -209
- package/docs/agents/nadicodeai-ui.md +4 -6
- package/docs/component-classification.md +1 -1
- package/docs/consuming-cross-repo.md +9 -3
- package/docs/contract.md +6 -6
- package/docs/migration/base-ui-migration.md +0 -11
- package/docs/product-compositions.md +1 -1
- package/llms.txt +5 -6
- package/package.json +10 -28
- package/skills/nadicodeai-ui/SKILL.md +5 -7
- package/dist/ai-elements.d.ts +0 -49
- package/dist/ai-elements.d.ts.map +0 -1
- package/dist/ai-elements.js +0 -48
- package/dist/components/agent-avatar.d.ts +0 -7
- package/dist/components/agent-avatar.d.ts.map +0 -1
- package/dist/components/agent-avatar.js +0 -6
- package/dist/components/agent-chat.d.ts +0 -20
- package/dist/components/agent-chat.d.ts.map +0 -1
- package/dist/components/agent-chat.js +0 -10
- package/dist/components/agent-composer.d.ts +0 -56
- package/dist/components/agent-composer.d.ts.map +0 -1
- package/dist/components/agent-composer.js +0 -56
- package/dist/components/agent-message.d.ts +0 -117
- package/dist/components/agent-message.d.ts.map +0 -1
- package/dist/components/agent-message.js +0 -99
- package/dist/components/agent-thread.d.ts +0 -26
- package/dist/components/agent-thread.d.ts.map +0 -1
- package/dist/components/agent-thread.js +0 -63
- package/dist/components/agent-tool-approval.d.ts +0 -27
- package/dist/components/agent-tool-approval.d.ts.map +0 -1
- package/dist/components/agent-tool-approval.js +0 -43
- package/dist/components/ai-elements/agent.d.ts +0 -27
- package/dist/components/ai-elements/agent.d.ts.map +0 -1
- package/dist/components/ai-elements/agent.js +0 -25
- package/dist/components/ai-elements/artifact.d.ts +0 -24
- package/dist/components/ai-elements/artifact.d.ts.map +0 -1
- package/dist/components/ai-elements/artifact.js +0 -20
- package/dist/components/ai-elements/attachments.d.ts +0 -55
- package/dist/components/ai-elements/attachments.d.ts.map +0 -1
- package/dist/components/ai-elements/attachments.js +0 -125
- package/dist/components/ai-elements/audio-player.d.ts +0 -30
- package/dist/components/ai-elements/audio-player.d.ts.map +0 -1
- package/dist/components/ai-elements/audio-player.js +0 -41
- package/dist/components/ai-elements/canvas.d.ts +0 -8
- package/dist/components/ai-elements/canvas.d.ts.map +0 -1
- package/dist/components/ai-elements/canvas.js +0 -5
- package/dist/components/ai-elements/chain-of-thought.d.ts +0 -30
- package/dist/components/ai-elements/chain-of-thought.d.ts.map +0 -1
- package/dist/components/ai-elements/chain-of-thought.js +0 -49
- package/dist/components/ai-elements/checkpoint.d.ts +0 -12
- package/dist/components/ai-elements/checkpoint.d.ts.map +0 -1
- package/dist/components/ai-elements/checkpoint.js +0 -10
- package/dist/components/ai-elements/code-block.d.ts +0 -53
- package/dist/components/ai-elements/code-block.d.ts.map +0 -1
- package/dist/components/ai-elements/code-block.js +0 -239
- package/dist/components/ai-elements/commit.d.ts +0 -65
- package/dist/components/ai-elements/commit.d.ts.map +0 -1
- package/dist/components/ai-elements/commit.js +0 -83
- package/dist/components/ai-elements/confirmation.d.ts +0 -50
- package/dist/components/ai-elements/confirmation.d.ts.map +0 -1
- package/dist/components/ai-elements/confirmation.js +0 -57
- package/dist/components/ai-elements/connection.d.ts +0 -6
- package/dist/components/ai-elements/connection.d.ts.map +0 -1
- package/dist/components/ai-elements/connection.js +0 -10
- package/dist/components/ai-elements/context.d.ts +0 -33
- package/dist/components/ai-elements/context.d.ts.map +0 -1
- package/dist/components/ai-elements/context.js +0 -147
- package/dist/components/ai-elements/controls.d.ts +0 -5
- package/dist/components/ai-elements/controls.d.ts.map +0 -1
- package/dist/components/ai-elements/controls.js +0 -5
- package/dist/components/ai-elements/conversation.d.ts +0 -24
- package/dist/components/ai-elements/conversation.d.ts.map +0 -1
- package/dist/components/ai-elements/conversation.js +0 -41
- package/dist/components/ai-elements/edge.d.ts +0 -6
- package/dist/components/ai-elements/edge.d.ts.map +0 -1
- package/dist/components/ai-elements/edge.js +0 -88
- package/dist/components/ai-elements/environment-variables.d.ts +0 -39
- package/dist/components/ai-elements/environment-variables.d.ts.map +0 -1
- package/dist/components/ai-elements/environment-variables.js +0 -60
- package/dist/components/ai-elements/file-tree.d.ts +0 -27
- package/dist/components/ai-elements/file-tree.d.ts.map +0 -1
- package/dist/components/ai-elements/file-tree.js +0 -73
- package/dist/components/ai-elements/image.d.ts +0 -7
- package/dist/components/ai-elements/image.d.ts.map +0 -1
- package/dist/components/ai-elements/image.js +0 -3
- package/dist/components/ai-elements/inline-citation.d.ts +0 -39
- package/dist/components/ai-elements/inline-citation.d.ts.map +0 -1
- package/dist/components/ai-elements/inline-citation.js +0 -86
- package/dist/components/ai-elements/jsx-preview.d.ts +0 -30
- package/dist/components/ai-elements/jsx-preview.d.ts.map +0 -1
- package/dist/components/ai-elements/jsx-preview.js +0 -159
- package/dist/components/ai-elements/message.d.ts +0 -38
- package/dist/components/ai-elements/message.d.ts.map +0 -1
- package/dist/components/ai-elements/message.js +0 -91
- package/dist/components/ai-elements/mic-selector.d.ts +0 -46
- package/dist/components/ai-elements/mic-selector.d.ts.map +0 -1
- package/dist/components/ai-elements/mic-selector.js +0 -180
- package/dist/components/ai-elements/model-selector.d.ts +0 -36
- package/dist/components/ai-elements/model-selector.d.ts.map +0 -1
- package/dist/components/ai-elements/model-selector.js +0 -18
- package/dist/components/ai-elements/node.d.ts +0 -22
- package/dist/components/ai-elements/node.d.ts.map +0 -1
- package/dist/components/ai-elements/node.js +0 -12
- package/dist/components/ai-elements/open-in-chat.d.ts +0 -27
- package/dist/components/ai-elements/open-in-chat.d.ts.map +0 -1
- package/dist/components/ai-elements/open-in-chat.js +0 -81
- package/dist/components/ai-elements/package-info.d.ts +0 -30
- package/dist/components/ai-elements/package-info.d.ts.map +0 -1
- package/dist/components/ai-elements/package-info.js +0 -50
- package/dist/components/ai-elements/panel.d.ts +0 -6
- package/dist/components/ai-elements/panel.d.ts.map +0 -1
- package/dist/components/ai-elements/panel.js +0 -5
- package/dist/components/ai-elements/persona.d.ts +0 -49
- package/dist/components/ai-elements/persona.d.ts.map +0 -1
- package/dist/components/ai-elements/persona.js +0 -175
- package/dist/components/ai-elements/plan.d.ts +0 -27
- package/dist/components/ai-elements/plan.d.ts.map +0 -1
- package/dist/components/ai-elements/plan.js +0 -34
- package/dist/components/ai-elements/prompt-input.d.ts +0 -149
- package/dist/components/ai-elements/prompt-input.d.ts.map +0 -1
- package/dist/components/ai-elements/prompt-input.js +0 -704
- package/dist/components/ai-elements/queue.d.ts +0 -62
- package/dist/components/ai-elements/queue.d.ts.map +0 -1
- package/dist/components/ai-elements/queue.js +0 -24
- package/dist/components/ai-elements/reasoning.d.ts +0 -27
- package/dist/components/ai-elements/reasoning.d.ts.map +0 -1
- package/dist/components/ai-elements/reasoning.js +0 -92
- package/dist/components/ai-elements/sandbox.d.ts +0 -25
- package/dist/components/ai-elements/sandbox.d.ts.map +0 -1
- package/dist/components/ai-elements/sandbox.js +0 -15
- package/dist/components/ai-elements/schema-display.d.ts +0 -56
- package/dist/components/ai-elements/schema-display.d.ts.map +0 -1
- package/dist/components/ai-elements/schema-display.js +0 -90
- package/dist/components/ai-elements/shimmer.d.ts +0 -10
- package/dist/components/ai-elements/shimmer.d.ts.map +0 -1
- package/dist/components/ai-elements/shimmer.js +0 -11
- package/dist/components/ai-elements/snippet.d.ts +0 -19
- package/dist/components/ai-elements/snippet.d.ts.map +0 -1
- package/dist/components/ai-elements/snippet.js +0 -32
- package/dist/components/ai-elements/sources.d.ts +0 -13
- package/dist/components/ai-elements/sources.d.ts.map +0 -1
- package/dist/components/ai-elements/sources.js +0 -9
- package/dist/components/ai-elements/speech-input.d.ts +0 -55
- package/dist/components/ai-elements/speech-input.d.ts.map +0 -1
- package/dist/components/ai-elements/speech-input.js +0 -187
- package/dist/components/ai-elements/stack-trace.d.ts +0 -38
- package/dist/components/ai-elements/stack-trace.d.ts.map +0 -1
- package/dist/components/ai-elements/stack-trace.js +0 -181
- package/dist/components/ai-elements/suggestion.d.ts +0 -11
- package/dist/components/ai-elements/suggestion.d.ts.map +0 -1
- package/dist/components/ai-elements/suggestion.js +0 -13
- package/dist/components/ai-elements/task.d.ts +0 -15
- package/dist/components/ai-elements/task.d.ts.map +0 -1
- package/dist/components/ai-elements/task.js +0 -10
- package/dist/components/ai-elements/terminal.d.ts +0 -28
- package/dist/components/ai-elements/terminal.d.ts.map +0 -1
- package/dist/components/ai-elements/terminal.js +0 -55
- package/dist/components/ai-elements/test-results.d.ts +0 -59
- package/dist/components/ai-elements/test-results.d.ts.map +0 -1
- package/dist/components/ai-elements/test-results.js +0 -98
- package/dist/components/ai-elements/tool.d.ts +0 -32
- package/dist/components/ai-elements/tool.d.ts.map +0 -1
- package/dist/components/ai-elements/tool.js +0 -49
- package/dist/components/ai-elements/toolbar.d.ts +0 -6
- package/dist/components/ai-elements/toolbar.d.ts.map +0 -1
- package/dist/components/ai-elements/toolbar.js +0 -5
- package/dist/components/ai-elements/transcription.d.ts +0 -17
- package/dist/components/ai-elements/transcription.d.ts.map +0 -1
- package/dist/components/ai-elements/transcription.js +0 -36
- package/dist/components/ai-elements/voice-selector.d.ts +0 -65
- package/dist/components/ai-elements/voice-selector.d.ts.map +0 -1
- package/dist/components/ai-elements/voice-selector.js +0 -224
- package/dist/components/ai-elements/web-preview.d.ts +0 -35
- package/dist/components/ai-elements/web-preview.d.ts.map +0 -1
- package/dist/components/ai-elements/web-preview.js +0 -66
- package/dist/components/artifact-panel.d.ts +0 -130
- package/dist/components/artifact-panel.d.ts.map +0 -1
- package/dist/components/artifact-panel.js +0 -180
- package/dist/components/attachment.d.ts +0 -24
- package/dist/components/attachment.d.ts.map +0 -1
- package/dist/components/attachment.js +0 -68
- package/dist/components/bubble.d.ts +0 -17
- package/dist/components/bubble.d.ts.map +0 -1
- package/dist/components/bubble.js +0 -59
- package/dist/components/demo-surface.d.ts +0 -34
- package/dist/components/demo-surface.d.ts.map +0 -1
- package/dist/components/demo-surface.js +0 -21
- package/dist/components/files-preview.d.ts +0 -36
- package/dist/components/files-preview.d.ts.map +0 -1
- package/dist/components/files-preview.js +0 -85
- package/dist/components/marker.d.ts +0 -11
- package/dist/components/marker.d.ts.map +0 -1
- package/dist/components/marker.js +0 -34
- package/dist/components/message-scroller.d.ts +0 -17
- package/dist/components/message-scroller.d.ts.map +0 -1
- package/dist/components/message-scroller.js +0 -29
- package/dist/components/message.d.ts +0 -11
- package/dist/components/message.d.ts.map +0 -1
- package/dist/components/message.js +0 -21
- package/dist/components/state-notice.d.ts +0 -13
- package/dist/components/state-notice.d.ts.map +0 -1
- package/dist/components/state-notice.js +0 -8
- package/dist/components/watch-it-work-band.d.ts +0 -12
- package/dist/components/watch-it-work-band.d.ts.map +0 -1
- package/dist/components/watch-it-work-band.js +0 -8
- package/dist/hooks/use-controllable-state.d.ts +0 -9
- package/dist/hooks/use-controllable-state.d.ts.map +0 -1
- package/dist/hooks/use-controllable-state.js +0 -23
- package/dist/lib/agent-attachment.d.ts +0 -11
- package/dist/lib/agent-attachment.d.ts.map +0 -1
- package/dist/lib/agent-attachment.js +0 -19
- package/dist/lib/agent-contracts.d.ts +0 -132
- package/dist/lib/agent-contracts.d.ts.map +0 -1
- package/dist/lib/agent-contracts.js +0 -6
- package/dist/lib/agent-state.d.ts +0 -22
- package/dist/lib/agent-state.d.ts.map +0 -1
- package/dist/lib/agent-state.js +0 -96
- package/dist/lib/agent-work-state.d.ts +0 -36
- package/dist/lib/agent-work-state.d.ts.map +0 -1
- package/dist/lib/agent-work-state.js +0 -36
- package/dist/lib/message-scroller-primitive/components.d.ts +0 -17
- package/dist/lib/message-scroller-primitive/components.d.ts.map +0 -1
- package/dist/lib/message-scroller-primitive/components.js +0 -204
- package/dist/lib/message-scroller-primitive/geometry.d.ts +0 -51
- package/dist/lib/message-scroller-primitive/geometry.d.ts.map +0 -1
- package/dist/lib/message-scroller-primitive/geometry.js +0 -199
- package/dist/lib/message-scroller-primitive/index.d.ts +0 -12
- package/dist/lib/message-scroller-primitive/index.d.ts.map +0 -1
- package/dist/lib/message-scroller-primitive/index.js +0 -12
- package/dist/lib/message-scroller-primitive/stores.d.ts +0 -6
- package/dist/lib/message-scroller-primitive/stores.d.ts.map +0 -1
- package/dist/lib/message-scroller-primitive/stores.js +0 -53
- package/dist/lib/message-scroller-primitive/types.d.ts +0 -88
- package/dist/lib/message-scroller-primitive/types.d.ts.map +0 -1
- package/dist/lib/message-scroller-primitive/types.js +0 -40
- package/dist/lib/message-scroller-primitive/use-message-scroller-commands.d.ts +0 -19
- package/dist/lib/message-scroller-primitive/use-message-scroller-commands.d.ts.map +0 -1
- package/dist/lib/message-scroller-primitive/use-message-scroller-commands.js +0 -188
- package/dist/lib/message-scroller-primitive/use-message-scroller-controller.d.ts +0 -7
- package/dist/lib/message-scroller-primitive/use-message-scroller-controller.d.ts.map +0 -1
- package/dist/lib/message-scroller-primitive/use-message-scroller-controller.js +0 -506
- package/dist/lib/message-scroller-primitive/use-message-scroller-refs.d.ts +0 -49
- package/dist/lib/message-scroller-primitive/use-message-scroller-refs.d.ts.map +0 -1
- package/dist/lib/message-scroller-primitive/use-message-scroller-refs.js +0 -90
- package/dist/lib/message-scroller-primitive/use-render.d.ts +0 -22
- package/dist/lib/message-scroller-primitive/use-render.d.ts.map +0 -1
- package/dist/lib/message-scroller-primitive/use-render.js +0 -113
- package/dist/lib/message-scroller-primitive/utils.d.ts +0 -4
- package/dist/lib/message-scroller-primitive/utils.d.ts.map +0 -1
- package/dist/lib/message-scroller-primitive/utils.js +0 -9
- package/docs/agent-work-surfaces.md +0 -132
- package/docs/migration/ai-elements-vendoring.md +0 -29
- package/docs/terminal-tui-semantics.md +0 -172
|
@@ -1,506 +0,0 @@
|
|
|
1
|
-
// Vendored from @shadcn/react 0.2.1 (upstream github.com/shadcn-ui).
|
|
2
|
-
// Adapted only for repo import paths and formatting; not added as an npm dependency.
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { getContentBottom, getElementTop, getElementViewportTop, getFirstVisibleMessageItem, getFlexGap, getLastScrollAnchor, getMessageScrollerItems, getMessageScrollerScrollable, getMessageScrollerVisibilityState, getNewScrollAnchor, getUnanchoredScrollAnchor, hasMultipleNewScrollAnchors, } from "./geometry.js";
|
|
5
|
-
import { DEFAULT_SCROLL_EDGE_THRESHOLD, DEFAULT_SCROLL_MARGIN, DEFAULT_SCROLL_PREVIOUS_ITEM_PEEK, EMPTY_MESSAGE_SCROLLER_VISIBILITY_STATE, SCROLL_POSITION_EPSILON, } from "./types.js";
|
|
6
|
-
import { useMessageScrollerCommands } from "./use-message-scroller-commands.js";
|
|
7
|
-
import { useMessageScrollerRefs } from "./use-message-scroller-refs.js";
|
|
8
|
-
// Builds a ref callback that stores the node and runs onMount once it attaches.
|
|
9
|
-
function useElementRef(elementRef, onMount) {
|
|
10
|
-
return React.useCallback((element) => {
|
|
11
|
-
elementRef.current = element;
|
|
12
|
-
if (element) {
|
|
13
|
-
onMount();
|
|
14
|
-
}
|
|
15
|
-
}, [elementRef, onMount]);
|
|
16
|
-
}
|
|
17
|
-
// Orchestrator hook. Decides when to scroll and delegates the moves to
|
|
18
|
-
// useMessageScrollerCommands; state and visibility commits are coalesced on a
|
|
19
|
-
// requestAnimationFrame and torn down on cleanup for StrictMode safety.
|
|
20
|
-
function useMessageScrollerController({ autoScroll = false, defaultScrollPosition = "end", scrollEdgeThreshold = DEFAULT_SCROLL_EDGE_THRESHOLD, scrollPreviousItemPeek = DEFAULT_SCROLL_PREVIOUS_ITEM_PEEK, scrollMargin = DEFAULT_SCROLL_MARGIN, }) {
|
|
21
|
-
const refs = useMessageScrollerRefs({
|
|
22
|
-
autoScroll,
|
|
23
|
-
scrollEdgeThreshold,
|
|
24
|
-
scrollMargin,
|
|
25
|
-
scrollPreviousItemPeek,
|
|
26
|
-
});
|
|
27
|
-
const { streamingTurnRef, autoScrollRef, autoscrollingRef, autoscrollingTimeoutRef, contentRef, defaultScrollPositionAppliedRef, firstItemRef, itemCountRef, lastScrollTopRef, messageElementsRef, modeRef, pendingScrollFrameRef, pendingScrollToMessageRef, prependRestoreRef, preserveScrollOnPrependRef, rootRef, scrollEdgeThresholdRef, scrollMarginRef, scrollPreviousItemPeekRef, spacerGapRef, spacerHeightRef, spacerRef, stateFrameRef, stateStore, viewportRef, visibilityFrameRef, visibilityObserverRef, visibilityStore, visibleMessageIdsRef, handledScrollAnchorsRef, } = refs;
|
|
28
|
-
const previousDefaultScrollPositionRef = React.useRef(defaultScrollPosition);
|
|
29
|
-
if (previousDefaultScrollPositionRef.current !== defaultScrollPosition) {
|
|
30
|
-
previousDefaultScrollPositionRef.current = defaultScrollPosition;
|
|
31
|
-
defaultScrollPositionAppliedRef.current = false;
|
|
32
|
-
}
|
|
33
|
-
const writeStateAttributes = React.useCallback((state) => {
|
|
34
|
-
const root = rootRef.current;
|
|
35
|
-
const viewport = viewportRef.current;
|
|
36
|
-
const scrollable = [state.start && "start", state.end && "end"].filter(Boolean).join(" ");
|
|
37
|
-
const autoScrolling = autoscrollingRef.current;
|
|
38
|
-
for (const element of [root, viewport]) {
|
|
39
|
-
if (!element) {
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
if (scrollable) {
|
|
43
|
-
element.setAttribute("data-scrollable", scrollable);
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
element.removeAttribute("data-scrollable");
|
|
47
|
-
}
|
|
48
|
-
element.toggleAttribute("data-autoscrolling", autoScrolling);
|
|
49
|
-
}
|
|
50
|
-
}, []);
|
|
51
|
-
// Owns the one follow-bottom transition: arm at the bottom, release on any
|
|
52
|
-
// scroll away (including a scrollbar drag), suppressed during a programmatic
|
|
53
|
-
// scroll so the auto-scroll animation cannot release itself. Arming also
|
|
54
|
-
// skips the anchored-to-message hold: the tail spacer makes a freshly
|
|
55
|
-
// anchored turn read as "at the end", and re-arming there would let the
|
|
56
|
-
// first streamed chunk yank the reader off the anchor. The hold hands back
|
|
57
|
-
// to following in handleResize, once the reply consumes the tail spacer.
|
|
58
|
-
const reconcileFollowMode = React.useCallback((scrollable) => {
|
|
59
|
-
const scrollTop = viewportRef.current?.scrollTop ?? 0;
|
|
60
|
-
// Content growing past the live edge also reads as "not at the end", but
|
|
61
|
-
// only a scrollbar drag moves scrollTop up. Growth must not release
|
|
62
|
-
// follow-output: the resize handler is coalesced onto a frame, so a state
|
|
63
|
-
// commit can observe the grown content before follow catches up.
|
|
64
|
-
const scrolledUp = scrollTop < lastScrollTopRef.current - SCROLL_POSITION_EPSILON;
|
|
65
|
-
lastScrollTopRef.current = scrollTop;
|
|
66
|
-
if (autoScrollRef.current &&
|
|
67
|
-
!scrollable.end &&
|
|
68
|
-
modeRef.current !== "settling-jump" &&
|
|
69
|
-
modeRef.current !== "anchored-to-message") {
|
|
70
|
-
modeRef.current = "following-bottom";
|
|
71
|
-
}
|
|
72
|
-
else if (modeRef.current === "following-bottom" &&
|
|
73
|
-
scrollable.end &&
|
|
74
|
-
scrolledUp &&
|
|
75
|
-
!autoscrollingRef.current) {
|
|
76
|
-
modeRef.current = "free-scrolling";
|
|
77
|
-
}
|
|
78
|
-
}, []);
|
|
79
|
-
const commitScrollState = React.useCallback(() => {
|
|
80
|
-
const nextState = getMessageScrollerScrollable({
|
|
81
|
-
content: contentRef.current,
|
|
82
|
-
scrollEdgeThreshold: scrollEdgeThresholdRef.current,
|
|
83
|
-
spacer: spacerRef.current,
|
|
84
|
-
viewport: viewportRef.current,
|
|
85
|
-
});
|
|
86
|
-
reconcileFollowMode(nextState);
|
|
87
|
-
// While follow-output is engaged the scroller is already closing any gap a
|
|
88
|
-
// streamed chunk just opened, so publishing it as scrollable toward the
|
|
89
|
-
// end would strobe the scroll button once per chunk. Reconcile runs on the
|
|
90
|
-
// raw geometry first, so a commit that releases follow still publishes the
|
|
91
|
-
// gap it released over.
|
|
92
|
-
const publishedState = modeRef.current === "following-bottom" ? { ...nextState, end: false } : nextState;
|
|
93
|
-
writeStateAttributes(publishedState);
|
|
94
|
-
stateStore.setSnapshot(publishedState);
|
|
95
|
-
}, [reconcileFollowMode, stateStore, writeStateAttributes]);
|
|
96
|
-
const scheduleStateCommit = React.useCallback(() => {
|
|
97
|
-
if (stateFrameRef.current !== null) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
stateFrameRef.current = window.requestAnimationFrame(() => {
|
|
101
|
-
stateFrameRef.current = null;
|
|
102
|
-
commitScrollState();
|
|
103
|
-
});
|
|
104
|
-
}, [commitScrollState]);
|
|
105
|
-
const scheduleVisibilitySync = React.useCallback(() => {
|
|
106
|
-
if (!visibilityStore.hasListeners()) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (visibilityFrameRef.current !== null) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
visibilityFrameRef.current = window.requestAnimationFrame(() => {
|
|
113
|
-
visibilityFrameRef.current = null;
|
|
114
|
-
// A frame can outlive the last unsubscribe. Recomputing here would
|
|
115
|
-
// overwrite the EMPTY snapshot that teardown just wrote, leaving a stale
|
|
116
|
-
// value for the next subscriber to read.
|
|
117
|
-
if (!visibilityStore.hasListeners()) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
visibilityStore.setSnapshot(getMessageScrollerVisibilityState({
|
|
121
|
-
content: contentRef.current,
|
|
122
|
-
scrollMargin: scrollMarginRef.current,
|
|
123
|
-
scrollPreviousItemPeek: scrollPreviousItemPeekRef.current,
|
|
124
|
-
spacer: spacerRef.current,
|
|
125
|
-
viewport: viewportRef.current,
|
|
126
|
-
visibleMessageIds: visibleMessageIdsRef.current,
|
|
127
|
-
}));
|
|
128
|
-
});
|
|
129
|
-
}, [visibilityStore]);
|
|
130
|
-
const { flushPendingScrollToMessage, reanchorToAnchoredMessage, scrollToElement, scrollToEnd, scrollToMessage, scrollToStart, } = useMessageScrollerCommands({
|
|
131
|
-
refs,
|
|
132
|
-
commitScrollState,
|
|
133
|
-
scheduleStateCommit,
|
|
134
|
-
scheduleVisibilitySync,
|
|
135
|
-
});
|
|
136
|
-
const restorePrependedAnchor = React.useCallback(() => {
|
|
137
|
-
const anchor = prependRestoreRef.current;
|
|
138
|
-
const viewport = viewportRef.current;
|
|
139
|
-
if (!anchor || !viewport || !anchor.element.isConnected) {
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
// Compare the anchor relative to the viewport, not to the content. Native
|
|
143
|
-
// scroll anchoring leaves the viewport-relative position unchanged, so this
|
|
144
|
-
// is a no-op where the browser already handled the prepend and only corrects
|
|
145
|
-
// the scroll where it did not (e.g. Safari) — without trusting a capability
|
|
146
|
-
// flag, which some engines report incorrectly.
|
|
147
|
-
const nextViewportTop = getElementViewportTop(anchor.element, viewport);
|
|
148
|
-
const delta = nextViewportTop - anchor.viewportTop;
|
|
149
|
-
if (Math.abs(delta) <= SCROLL_POSITION_EPSILON) {
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
viewport.scrollTop += delta;
|
|
153
|
-
anchor.viewportTop = getElementViewportTop(anchor.element, viewport);
|
|
154
|
-
scheduleStateCommit();
|
|
155
|
-
scheduleVisibilitySync();
|
|
156
|
-
return true;
|
|
157
|
-
}, [scheduleStateCommit, scheduleVisibilitySync]);
|
|
158
|
-
const capturePrependAnchor = React.useCallback(() => {
|
|
159
|
-
const content = contentRef.current;
|
|
160
|
-
const viewport = viewportRef.current;
|
|
161
|
-
if (!content || !viewport) {
|
|
162
|
-
prependRestoreRef.current = null;
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
const anchor = getFirstVisibleMessageItem({
|
|
166
|
-
content,
|
|
167
|
-
spacer: spacerRef.current,
|
|
168
|
-
viewport,
|
|
169
|
-
});
|
|
170
|
-
prependRestoreRef.current = anchor
|
|
171
|
-
? {
|
|
172
|
-
element: anchor,
|
|
173
|
-
viewportTop: getElementViewportTop(anchor, viewport),
|
|
174
|
-
}
|
|
175
|
-
: null;
|
|
176
|
-
}, []);
|
|
177
|
-
const schedulePendingScrollToMessageFlush = React.useCallback(() => {
|
|
178
|
-
if (pendingScrollFrameRef.current !== null) {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
pendingScrollFrameRef.current = window.requestAnimationFrame(() => {
|
|
182
|
-
pendingScrollFrameRef.current = null;
|
|
183
|
-
if (flushPendingScrollToMessage()) {
|
|
184
|
-
capturePrependAnchor();
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
}, [capturePrependAnchor, flushPendingScrollToMessage]);
|
|
188
|
-
const applyDefaultScrollPosition = React.useCallback(() => {
|
|
189
|
-
if (!defaultScrollPosition ||
|
|
190
|
-
defaultScrollPositionAppliedRef.current ||
|
|
191
|
-
itemCountRef.current === 0) {
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
let handled = false;
|
|
195
|
-
if (defaultScrollPosition === "last-anchor") {
|
|
196
|
-
const content = contentRef.current;
|
|
197
|
-
const viewport = viewportRef.current;
|
|
198
|
-
const anchor = content && viewport
|
|
199
|
-
? getLastScrollAnchor(getMessageScrollerItems(content, spacerRef.current))
|
|
200
|
-
: null;
|
|
201
|
-
if (!content || !viewport || !anchor) {
|
|
202
|
-
handled = scrollToEnd({ behavior: "auto" });
|
|
203
|
-
}
|
|
204
|
-
else {
|
|
205
|
-
const anchorTop = getElementTop(anchor, viewport);
|
|
206
|
-
const contentBottom = getContentBottom({
|
|
207
|
-
content,
|
|
208
|
-
spacer: spacerRef.current,
|
|
209
|
-
viewport,
|
|
210
|
-
});
|
|
211
|
-
// A short last turn already fits below the anchor, so opening at the end
|
|
212
|
-
// shows the whole turn without leaving a blank gap beneath it.
|
|
213
|
-
const lastTurnFits = contentBottom - anchorTop <= viewport.clientHeight;
|
|
214
|
-
handled = lastTurnFits
|
|
215
|
-
? scrollToEnd({ behavior: "auto" })
|
|
216
|
-
: scrollToElement(anchor, { align: "start" }, { keepPreviousPeek: true });
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
handled =
|
|
221
|
-
defaultScrollPosition === "end"
|
|
222
|
-
? scrollToEnd({ behavior: "auto" })
|
|
223
|
-
: scrollToStart({ behavior: "auto" });
|
|
224
|
-
}
|
|
225
|
-
if (!handled) {
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
228
|
-
defaultScrollPositionAppliedRef.current = true;
|
|
229
|
-
return true;
|
|
230
|
-
}, [defaultScrollPosition, scrollToElement, scrollToEnd, scrollToStart]);
|
|
231
|
-
const handleContentChange = React.useCallback(() => {
|
|
232
|
-
const content = contentRef.current;
|
|
233
|
-
if (!content) {
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
const items = getMessageScrollerItems(content, spacerRef.current);
|
|
237
|
-
const previousItemCount = itemCountRef.current;
|
|
238
|
-
const previousFirstItem = firstItemRef.current;
|
|
239
|
-
itemCountRef.current = items.length;
|
|
240
|
-
firstItemRef.current = items[0] ?? null;
|
|
241
|
-
// Reconcile the scroll position with the new content. Every path re-captures
|
|
242
|
-
// the prepend anchor afterward, so each branch just returns.
|
|
243
|
-
//
|
|
244
|
-
// Branch order is load-bearing: first-content, prepended, appended, updated.
|
|
245
|
-
const reconcileScrollPosition = () => {
|
|
246
|
-
if (flushPendingScrollToMessage()) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
if (previousItemCount === 0) {
|
|
250
|
-
if (applyDefaultScrollPosition()) {
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
if (items.length > 0 && autoScrollRef.current && scrollToEnd({ behavior: "auto" })) {
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
commitScrollState();
|
|
257
|
-
scheduleVisibilitySync();
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
const previousFirstItemIndex = previousFirstItem ? items.indexOf(previousFirstItem) : -1;
|
|
261
|
-
const didPrepend = preserveScrollOnPrependRef.current && previousFirstItemIndex > 0;
|
|
262
|
-
if (didPrepend) {
|
|
263
|
-
// Prepended rows are not new appends. Restore the prior scroll position.
|
|
264
|
-
// The restore is a no-op where native scroll anchoring already did it.
|
|
265
|
-
restorePrependedAnchor();
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
if (items.length > previousItemCount) {
|
|
269
|
-
const anchor = getNewScrollAnchor(items, previousItemCount);
|
|
270
|
-
if (anchor) {
|
|
271
|
-
// While the reader is following the live end, a batch of several
|
|
272
|
-
// anchored turns arriving at once should keep following the end — not
|
|
273
|
-
// yank back to anchor the first turn of the batch. A single new anchor
|
|
274
|
-
// still moves to the top as usual.
|
|
275
|
-
if (autoScrollRef.current &&
|
|
276
|
-
modeRef.current === "following-bottom" &&
|
|
277
|
-
hasMultipleNewScrollAnchors(items, previousItemCount)) {
|
|
278
|
-
scrollToEnd({ behavior: "auto" });
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
scrollToElement(anchor, { align: "start" }, { keepPreviousPeek: true });
|
|
282
|
-
handledScrollAnchorsRef.current.add(anchor);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
if (items.length === previousItemCount) {
|
|
287
|
-
const anchor = getUnanchoredScrollAnchor(items, handledScrollAnchorsRef.current);
|
|
288
|
-
if (anchor) {
|
|
289
|
-
scrollToElement(anchor, { align: "start" }, { keepPreviousPeek: true });
|
|
290
|
-
handledScrollAnchorsRef.current.add(anchor);
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
// Appends with no new anchor (and content-only updates) fall through here:
|
|
295
|
-
// keep following the end if we still are, otherwise just recommit state.
|
|
296
|
-
if (modeRef.current === "following-bottom" && autoScrollRef.current) {
|
|
297
|
-
scrollToEnd({ behavior: "auto" });
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
commitScrollState();
|
|
301
|
-
scheduleVisibilitySync();
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
reconcileScrollPosition();
|
|
305
|
-
capturePrependAnchor();
|
|
306
|
-
}, [
|
|
307
|
-
applyDefaultScrollPosition,
|
|
308
|
-
capturePrependAnchor,
|
|
309
|
-
commitScrollState,
|
|
310
|
-
flushPendingScrollToMessage,
|
|
311
|
-
restorePrependedAnchor,
|
|
312
|
-
scheduleVisibilitySync,
|
|
313
|
-
scrollToElement,
|
|
314
|
-
scrollToEnd,
|
|
315
|
-
]);
|
|
316
|
-
const handleResize = React.useCallback(() => {
|
|
317
|
-
if (modeRef.current === "following-bottom" && autoScrollRef.current) {
|
|
318
|
-
scrollToEnd({ behavior: "auto" });
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
// Hold the anchored turn in place as content below it resizes (a reply
|
|
322
|
-
// streaming in, or a transient marker collapsing) — otherwise the shrinking
|
|
323
|
-
// content lets the browser clamp scrollTop and the turn drops.
|
|
324
|
-
const previousSpacerHeight = spacerHeightRef.current;
|
|
325
|
-
if (reanchorToAnchoredMessage()) {
|
|
326
|
-
// The reply streaming below the anchor consumes the tail spacer as it
|
|
327
|
-
// grows. Once the last of it is gone the reply has filled the viewport
|
|
328
|
-
// and the reader is genuinely at the live edge, so autoScroll hands off
|
|
329
|
-
// from the anchor hold to following the bottom. Requiring the >0 → 0
|
|
330
|
-
// transition keeps a turn taller than the viewport (placed with no
|
|
331
|
-
// spacer) held instead of yanked to the end.
|
|
332
|
-
if (autoScrollRef.current && previousSpacerHeight > 0 && spacerHeightRef.current === 0) {
|
|
333
|
-
scrollToEnd({ behavior: "auto" });
|
|
334
|
-
}
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
scheduleStateCommit();
|
|
338
|
-
scheduleVisibilitySync();
|
|
339
|
-
}, [reanchorToAnchoredMessage, scheduleStateCommit, scheduleVisibilitySync, scrollToEnd]);
|
|
340
|
-
const observeVisibility = React.useCallback(() => {
|
|
341
|
-
const viewport = viewportRef.current;
|
|
342
|
-
if (!viewport || !visibilityStore.hasListeners()) {
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
if (typeof IntersectionObserver === "undefined") {
|
|
346
|
-
scheduleVisibilitySync();
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
if (!visibilityObserverRef.current) {
|
|
350
|
-
visibilityObserverRef.current = new IntersectionObserver((entries) => {
|
|
351
|
-
for (const entry of entries) {
|
|
352
|
-
const messageId = entry.target.dataset.messageId;
|
|
353
|
-
if (!messageId) {
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
|
-
if (entry.isIntersecting) {
|
|
357
|
-
visibleMessageIdsRef.current.add(messageId);
|
|
358
|
-
}
|
|
359
|
-
else {
|
|
360
|
-
visibleMessageIdsRef.current.delete(messageId);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
scheduleVisibilitySync();
|
|
364
|
-
}, {
|
|
365
|
-
root: viewport,
|
|
366
|
-
// Shrink the root's top edge to the anchoring line so a previous turn
|
|
367
|
-
// peeking in the scrollMargin + peek band is not reported as visible,
|
|
368
|
-
// keeping visibleMessageIds consistent with currentAnchorId. Captured
|
|
369
|
-
// at observe time; a prop change rebuilds the observer on resubscribe.
|
|
370
|
-
rootMargin: `${-(scrollMarginRef.current + scrollPreviousItemPeekRef.current)}px 0px 0px 0px`,
|
|
371
|
-
threshold: [0, 0.01, 0.5, 1],
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
messageElementsRef.current.forEach((element) => {
|
|
375
|
-
visibilityObserverRef.current?.observe(element);
|
|
376
|
-
});
|
|
377
|
-
scheduleVisibilitySync();
|
|
378
|
-
}, [scheduleVisibilitySync, visibilityStore]);
|
|
379
|
-
const unobserveVisibility = React.useCallback(() => {
|
|
380
|
-
if (visibilityFrameRef.current !== null) {
|
|
381
|
-
window.cancelAnimationFrame(visibilityFrameRef.current);
|
|
382
|
-
visibilityFrameRef.current = null;
|
|
383
|
-
}
|
|
384
|
-
visibilityObserverRef.current?.disconnect();
|
|
385
|
-
visibilityObserverRef.current = null;
|
|
386
|
-
visibleMessageIdsRef.current.clear();
|
|
387
|
-
visibilityStore.setSnapshot(EMPTY_MESSAGE_SCROLLER_VISIBILITY_STATE);
|
|
388
|
-
}, [visibilityStore]);
|
|
389
|
-
const registerMessage = React.useCallback((messageId, element, removedElement) => {
|
|
390
|
-
if (element) {
|
|
391
|
-
messageElementsRef.current.set(messageId, element);
|
|
392
|
-
visibilityObserverRef.current?.observe(element);
|
|
393
|
-
scheduleVisibilitySync();
|
|
394
|
-
if (pendingScrollToMessageRef.current?.messageId === messageId) {
|
|
395
|
-
schedulePendingScrollToMessageFlush();
|
|
396
|
-
}
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
if (removedElement && messageElementsRef.current.get(messageId) === removedElement) {
|
|
400
|
-
messageElementsRef.current.delete(messageId);
|
|
401
|
-
visibleMessageIdsRef.current.delete(messageId);
|
|
402
|
-
visibilityObserverRef.current?.unobserve(removedElement);
|
|
403
|
-
scheduleVisibilitySync();
|
|
404
|
-
}
|
|
405
|
-
}, [schedulePendingScrollToMessageFlush, scheduleVisibilitySync]);
|
|
406
|
-
const userScrollIntent = React.useCallback(() => {
|
|
407
|
-
if (modeRef.current === "following-bottom" ||
|
|
408
|
-
modeRef.current === "anchored-to-message" ||
|
|
409
|
-
modeRef.current === "settling-jump") {
|
|
410
|
-
// A deliberate gesture releases auto-follow, turn-anchoring, and an in-flight
|
|
411
|
-
// programmatic jump so re-pinning (and re-arming) never fights the reader.
|
|
412
|
-
streamingTurnRef.current = null;
|
|
413
|
-
modeRef.current = "free-scrolling";
|
|
414
|
-
}
|
|
415
|
-
}, []);
|
|
416
|
-
const mirrorStateAttributes = React.useCallback(() => writeStateAttributes(stateStore.getSnapshot()), [stateStore, writeStateAttributes]);
|
|
417
|
-
const setRootElement = useElementRef(rootRef, mirrorStateAttributes);
|
|
418
|
-
const setViewportElement = useElementRef(viewportRef, mirrorStateAttributes);
|
|
419
|
-
const setContentElement = React.useCallback((element) => {
|
|
420
|
-
contentRef.current = element;
|
|
421
|
-
}, []);
|
|
422
|
-
const setSpacerElement = React.useCallback((element) => {
|
|
423
|
-
spacerRef.current = element;
|
|
424
|
-
spacerGapRef.current = getFlexGap(element?.parentElement ?? null);
|
|
425
|
-
}, []);
|
|
426
|
-
const syncAfterScroll = React.useCallback(() => {
|
|
427
|
-
commitScrollState();
|
|
428
|
-
scheduleVisibilitySync();
|
|
429
|
-
capturePrependAnchor();
|
|
430
|
-
}, [capturePrependAnchor, commitScrollState, scheduleVisibilitySync]);
|
|
431
|
-
const context = React.useMemo(() => ({
|
|
432
|
-
handleContentChange,
|
|
433
|
-
handleResize,
|
|
434
|
-
observeVisibility,
|
|
435
|
-
preserveScrollOnPrependRef,
|
|
436
|
-
scrollToEnd,
|
|
437
|
-
scrollToMessage,
|
|
438
|
-
scrollToStart,
|
|
439
|
-
setContentElement,
|
|
440
|
-
setRootElement,
|
|
441
|
-
setSpacerElement,
|
|
442
|
-
setViewportElement,
|
|
443
|
-
stateStore,
|
|
444
|
-
syncAfterScroll,
|
|
445
|
-
unobserveVisibility,
|
|
446
|
-
userScrollIntent,
|
|
447
|
-
viewportRef,
|
|
448
|
-
visibilityStore,
|
|
449
|
-
}), [
|
|
450
|
-
handleContentChange,
|
|
451
|
-
handleResize,
|
|
452
|
-
observeVisibility,
|
|
453
|
-
scrollToEnd,
|
|
454
|
-
scrollToMessage,
|
|
455
|
-
scrollToStart,
|
|
456
|
-
setContentElement,
|
|
457
|
-
setRootElement,
|
|
458
|
-
setSpacerElement,
|
|
459
|
-
setViewportElement,
|
|
460
|
-
stateStore,
|
|
461
|
-
syncAfterScroll,
|
|
462
|
-
unobserveVisibility,
|
|
463
|
-
userScrollIntent,
|
|
464
|
-
visibilityStore,
|
|
465
|
-
]);
|
|
466
|
-
React.useLayoutEffect(() => {
|
|
467
|
-
applyDefaultScrollPosition();
|
|
468
|
-
}, [applyDefaultScrollPosition]);
|
|
469
|
-
React.useEffect(() => {
|
|
470
|
-
return () => {
|
|
471
|
-
// Reset every ref after cancelling. StrictMode replays effects on the same
|
|
472
|
-
// refs (unmount then remount), so a frame id left non-null here makes the
|
|
473
|
-
// scheduler on remount think a frame is still pending and never reschedule.
|
|
474
|
-
if (stateFrameRef.current !== null) {
|
|
475
|
-
window.cancelAnimationFrame(stateFrameRef.current);
|
|
476
|
-
stateFrameRef.current = null;
|
|
477
|
-
}
|
|
478
|
-
if (visibilityFrameRef.current !== null) {
|
|
479
|
-
window.cancelAnimationFrame(visibilityFrameRef.current);
|
|
480
|
-
visibilityFrameRef.current = null;
|
|
481
|
-
}
|
|
482
|
-
if (autoscrollingTimeoutRef.current !== null) {
|
|
483
|
-
window.clearTimeout(autoscrollingTimeoutRef.current);
|
|
484
|
-
autoscrollingTimeoutRef.current = null;
|
|
485
|
-
}
|
|
486
|
-
if (pendingScrollFrameRef.current !== null) {
|
|
487
|
-
window.cancelAnimationFrame(pendingScrollFrameRef.current);
|
|
488
|
-
pendingScrollFrameRef.current = null;
|
|
489
|
-
}
|
|
490
|
-
visibilityObserverRef.current?.disconnect();
|
|
491
|
-
visibilityObserverRef.current = null;
|
|
492
|
-
};
|
|
493
|
-
}, []);
|
|
494
|
-
React.useLayoutEffect(() => {
|
|
495
|
-
if (autoScroll && modeRef.current === "following-bottom" && itemCountRef.current > 0) {
|
|
496
|
-
scrollToEnd({ behavior: "auto" });
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
commitScrollState();
|
|
500
|
-
}, [autoScroll, commitScrollState, scrollToEnd]);
|
|
501
|
-
return {
|
|
502
|
-
context,
|
|
503
|
-
registerMessage,
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
export { useMessageScrollerController };
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import type { MessageScrollerMode, MessageScrollerScrollable, MessageScrollerScrollOptions, MessageScrollerStore, MessageScrollerVisibilityStore } from "./types";
|
|
3
|
-
type MessageScrollerRefs = {
|
|
4
|
-
autoScrollRef: React.RefObject<boolean>;
|
|
5
|
-
autoscrollingRef: React.RefObject<boolean>;
|
|
6
|
-
autoscrollingTimeoutRef: React.RefObject<number | null>;
|
|
7
|
-
streamingTurnRef: React.RefObject<HTMLElement | null>;
|
|
8
|
-
contentRef: React.RefObject<HTMLDivElement | null>;
|
|
9
|
-
defaultScrollPositionAppliedRef: React.RefObject<boolean>;
|
|
10
|
-
firstItemRef: React.RefObject<HTMLElement | null>;
|
|
11
|
-
itemCountRef: React.RefObject<number>;
|
|
12
|
-
lastScrollTopRef: React.RefObject<number>;
|
|
13
|
-
messageElementsRef: React.RefObject<Map<string, HTMLElement>>;
|
|
14
|
-
modeRef: React.RefObject<MessageScrollerMode>;
|
|
15
|
-
pendingScrollFrameRef: React.RefObject<number | null>;
|
|
16
|
-
pendingScrollToMessageRef: React.RefObject<{
|
|
17
|
-
messageId: string;
|
|
18
|
-
options?: MessageScrollerScrollOptions;
|
|
19
|
-
} | null>;
|
|
20
|
-
prependRestoreRef: React.RefObject<{
|
|
21
|
-
element: HTMLElement;
|
|
22
|
-
viewportTop: number;
|
|
23
|
-
} | null>;
|
|
24
|
-
preserveScrollOnPrependRef: React.RefObject<boolean>;
|
|
25
|
-
rootRef: React.RefObject<HTMLDivElement | null>;
|
|
26
|
-
scrollEdgeThresholdRef: React.RefObject<number>;
|
|
27
|
-
scrollMarginRef: React.RefObject<number>;
|
|
28
|
-
scrollPreviousItemPeekRef: React.RefObject<number>;
|
|
29
|
-
spacerGapRef: React.RefObject<number>;
|
|
30
|
-
spacerHeightRef: React.RefObject<number>;
|
|
31
|
-
spacerRef: React.RefObject<HTMLDivElement | null>;
|
|
32
|
-
stateFrameRef: React.RefObject<number | null>;
|
|
33
|
-
stateStore: MessageScrollerStore<MessageScrollerScrollable>;
|
|
34
|
-
viewportRef: React.RefObject<HTMLDivElement | null>;
|
|
35
|
-
visibilityFrameRef: React.RefObject<number | null>;
|
|
36
|
-
visibilityObserverRef: React.RefObject<IntersectionObserver | null>;
|
|
37
|
-
visibilityStore: MessageScrollerVisibilityStore;
|
|
38
|
-
visibleMessageIdsRef: React.RefObject<Set<string>>;
|
|
39
|
-
handledScrollAnchorsRef: React.RefObject<WeakSet<HTMLElement>>;
|
|
40
|
-
};
|
|
41
|
-
declare function useMessageScrollerRefs({ autoScroll, scrollEdgeThreshold, scrollMargin, scrollPreviousItemPeek, }: {
|
|
42
|
-
autoScroll: boolean;
|
|
43
|
-
scrollEdgeThreshold: number;
|
|
44
|
-
scrollMargin: number;
|
|
45
|
-
scrollPreviousItemPeek: number;
|
|
46
|
-
}): MessageScrollerRefs;
|
|
47
|
-
export { useMessageScrollerRefs };
|
|
48
|
-
export type { MessageScrollerRefs };
|
|
49
|
-
//# sourceMappingURL=use-message-scroller-refs.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"use-message-scroller-refs.d.ts","sourceRoot":"","sources":["../../../src/lib/message-scroller-primitive/use-message-scroller-refs.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
// Vendored from @shadcn/react 0.2.1 (upstream github.com/shadcn-ui).
|
|
2
|
-
// Adapted only for repo import paths and formatting; not added as an npm dependency.
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { areScrollStatesEqual, createMessageScrollerStore, createMessageScrollerVisibilityStore, } from "./stores.js";
|
|
5
|
-
import { EMPTY_MESSAGE_SCROLLER_SCROLLABLE } from "./types.js";
|
|
6
|
-
// Builds the per-instance ref bag: the two external stores constructed once, and
|
|
7
|
-
// the latest prop values mirrored onto refs each render so callbacks stay stable.
|
|
8
|
-
function useMessageScrollerRefs({ autoScroll, scrollEdgeThreshold, scrollMargin, scrollPreviousItemPeek, }) {
|
|
9
|
-
const autoScrollRef = React.useRef(autoScroll);
|
|
10
|
-
const autoscrollingRef = React.useRef(false);
|
|
11
|
-
const contentRef = React.useRef(null);
|
|
12
|
-
const defaultScrollPositionAppliedRef = React.useRef(false);
|
|
13
|
-
const scrollEdgeThresholdRef = React.useRef(scrollEdgeThreshold);
|
|
14
|
-
const itemCountRef = React.useRef(0);
|
|
15
|
-
const firstItemRef = React.useRef(null);
|
|
16
|
-
// The scrollTop seen by the previous state commit, so follow-release can tell
|
|
17
|
-
// a reader scrolling up from content growing past the live edge.
|
|
18
|
-
const lastScrollTopRef = React.useRef(0);
|
|
19
|
-
const modeRef = React.useRef(autoScroll ? "following-bottom" : "free-scrolling");
|
|
20
|
-
const messageElementsRef = React.useRef(new Map());
|
|
21
|
-
const pendingScrollToMessageRef = React.useRef(null);
|
|
22
|
-
// The row to hold steady on the next prepend: the first visible row, or a jump
|
|
23
|
-
// target seeded by scrollToElement. restorePrependedAnchor reads only this.
|
|
24
|
-
const prependRestoreRef = React.useRef(null);
|
|
25
|
-
// The turn held at the reading line so a reply streaming in below it can re-pin
|
|
26
|
-
// it instead of letting scrollTop clamp it loose.
|
|
27
|
-
const streamingTurnRef = React.useRef(null);
|
|
28
|
-
const scrollPreviousItemPeekRef = React.useRef(scrollPreviousItemPeek);
|
|
29
|
-
const preserveScrollOnPrependRef = React.useRef(true);
|
|
30
|
-
const rootRef = React.useRef(null);
|
|
31
|
-
const scrollMarginRef = React.useRef(scrollMargin);
|
|
32
|
-
const pendingScrollFrameRef = React.useRef(null);
|
|
33
|
-
const spacerGapRef = React.useRef(0);
|
|
34
|
-
const spacerHeightRef = React.useRef(0);
|
|
35
|
-
const spacerRef = React.useRef(null);
|
|
36
|
-
const stateFrameRef = React.useRef(null);
|
|
37
|
-
const stateStoreRef = React.useRef(null);
|
|
38
|
-
const autoscrollingTimeoutRef = React.useRef(null);
|
|
39
|
-
const viewportRef = React.useRef(null);
|
|
40
|
-
const visibilityFrameRef = React.useRef(null);
|
|
41
|
-
const visibilityObserverRef = React.useRef(null);
|
|
42
|
-
const visibilityStoreRef = React.useRef(null);
|
|
43
|
-
const visibleMessageIdsRef = React.useRef(new Set());
|
|
44
|
-
const handledScrollAnchorsRef = React.useRef(new WeakSet());
|
|
45
|
-
if (stateStoreRef.current === null) {
|
|
46
|
-
stateStoreRef.current = createMessageScrollerStore(EMPTY_MESSAGE_SCROLLER_SCROLLABLE, areScrollStatesEqual);
|
|
47
|
-
}
|
|
48
|
-
if (visibilityStoreRef.current === null) {
|
|
49
|
-
visibilityStoreRef.current = createMessageScrollerVisibilityStore();
|
|
50
|
-
}
|
|
51
|
-
// Track the latest prop values on every render so callbacks read fresh values
|
|
52
|
-
// without being recreated (the useLatest pattern).
|
|
53
|
-
autoScrollRef.current = autoScroll;
|
|
54
|
-
scrollEdgeThresholdRef.current = scrollEdgeThreshold;
|
|
55
|
-
scrollMarginRef.current = scrollMargin;
|
|
56
|
-
scrollPreviousItemPeekRef.current = scrollPreviousItemPeek;
|
|
57
|
-
return {
|
|
58
|
-
autoScrollRef,
|
|
59
|
-
autoscrollingRef,
|
|
60
|
-
autoscrollingTimeoutRef,
|
|
61
|
-
streamingTurnRef,
|
|
62
|
-
contentRef,
|
|
63
|
-
defaultScrollPositionAppliedRef,
|
|
64
|
-
firstItemRef,
|
|
65
|
-
itemCountRef,
|
|
66
|
-
lastScrollTopRef,
|
|
67
|
-
messageElementsRef,
|
|
68
|
-
modeRef,
|
|
69
|
-
pendingScrollFrameRef,
|
|
70
|
-
pendingScrollToMessageRef,
|
|
71
|
-
prependRestoreRef,
|
|
72
|
-
preserveScrollOnPrependRef,
|
|
73
|
-
rootRef,
|
|
74
|
-
scrollEdgeThresholdRef,
|
|
75
|
-
scrollMarginRef,
|
|
76
|
-
scrollPreviousItemPeekRef,
|
|
77
|
-
spacerGapRef,
|
|
78
|
-
spacerHeightRef,
|
|
79
|
-
spacerRef,
|
|
80
|
-
stateFrameRef,
|
|
81
|
-
stateStore: stateStoreRef.current,
|
|
82
|
-
viewportRef,
|
|
83
|
-
visibilityFrameRef,
|
|
84
|
-
visibilityObserverRef,
|
|
85
|
-
visibilityStore: visibilityStoreRef.current,
|
|
86
|
-
visibleMessageIdsRef,
|
|
87
|
-
handledScrollAnchorsRef,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
export { useMessageScrollerRefs };
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
type RenderState = Record<string, unknown>;
|
|
3
|
-
type RenderFunction<TState extends RenderState> = (props: Record<string, unknown>, state: TState) => React.ReactElement | null;
|
|
4
|
-
type RenderProp<TState extends RenderState> = React.ReactElement | RenderFunction<TState>;
|
|
5
|
-
type StateAttributesMapping<TState extends RenderState> = Partial<{
|
|
6
|
-
[K in keyof TState]: (value: TState[K]) => Record<string, string | undefined> | null | undefined;
|
|
7
|
-
}>;
|
|
8
|
-
type UseRenderComponentProps<TElement extends React.ElementType, TState extends RenderState = RenderState> = React.ComponentPropsWithRef<TElement> & {
|
|
9
|
-
render?: RenderProp<TState>;
|
|
10
|
-
};
|
|
11
|
-
type UseRenderOptions<TElement extends React.ElementType, TState extends RenderState> = {
|
|
12
|
-
defaultTagName: TElement;
|
|
13
|
-
props?: React.ComponentPropsWithRef<TElement>;
|
|
14
|
-
render?: RenderProp<TState>;
|
|
15
|
-
state?: TState;
|
|
16
|
-
stateAttributesMapping?: StateAttributesMapping<TState>;
|
|
17
|
-
};
|
|
18
|
-
declare function useRender<TElement extends React.ElementType, TState extends RenderState = RenderState>({ defaultTagName, props, render, state, stateAttributesMapping, }: UseRenderOptions<TElement, TState>): React.ReactElement<any, string | React.JSXElementConstructor<any>> | null;
|
|
19
|
-
declare function mergeProps<TElement extends React.ElementType>(...sources: Array<React.ComponentPropsWithRef<TElement> | Record<string, unknown> | undefined>): React.ComponentPropsWithRef<TElement>;
|
|
20
|
-
declare function composeRefs<T>(...refs: Array<React.Ref<T> | undefined>): React.RefCallback<T> | undefined;
|
|
21
|
-
export { composeRefs, mergeProps, useRender, type UseRenderComponentProps };
|
|
22
|
-
//# sourceMappingURL=use-render.d.ts.map
|