@intentface/chat 0.1.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/LICENSE +21 -0
- package/README.md +108 -0
- package/dist/ask-user/ask-user.d.ts +104 -0
- package/dist/ask-user/ask-user.js +226 -0
- package/dist/ask-user/index.d.ts +3 -0
- package/dist/ask-user/index.js +3 -0
- package/dist/ask-user/index.parts.d.ts +1 -0
- package/dist/ask-user/index.parts.js +5 -0
- package/dist/attachments/attachments.d.ts +41 -0
- package/dist/attachments/attachments.js +91 -0
- package/dist/attachments/index.d.ts +3 -0
- package/dist/attachments/index.js +3 -0
- package/dist/attachments/index.parts.d.ts +1 -0
- package/dist/attachments/index.parts.js +5 -0
- package/dist/chip/chip.d.ts +25 -0
- package/dist/chip/chip.js +25 -0
- package/dist/chip/index.d.ts +2 -0
- package/dist/chip/index.js +2 -0
- package/dist/chip/index.parts.d.ts +1 -0
- package/dist/chip/index.parts.js +5 -0
- package/dist/chip-markdown.d.ts +25 -0
- package/dist/chip-markdown.js +52 -0
- package/dist/composer/actions.d.ts +35 -0
- package/dist/composer/actions.js +75 -0
- package/dist/composer/ask-user-machine.d.ts +52 -0
- package/dist/composer/ask-user-machine.js +164 -0
- package/dist/composer/attachments-machine.d.ts +34 -0
- package/dist/composer/attachments-machine.js +91 -0
- package/dist/composer/attachments.d.ts +16 -0
- package/dist/composer/attachments.js +32 -0
- package/dist/composer/command-list.d.ts +49 -0
- package/dist/composer/command-list.js +357 -0
- package/dist/composer/commands.d.ts +28 -0
- package/dist/composer/commands.js +56 -0
- package/dist/composer/container.d.ts +3 -0
- package/dist/composer/container.js +32 -0
- package/dist/composer/controller.d.ts +3 -0
- package/dist/composer/controller.js +6 -0
- package/dist/composer/editor-dom.d.ts +55 -0
- package/dist/composer/editor-dom.js +431 -0
- package/dist/composer/fuzzy.d.ts +4 -0
- package/dist/composer/fuzzy.js +49 -0
- package/dist/composer/index.d.ts +25 -0
- package/dist/composer/index.js +13 -0
- package/dist/composer/index.parts.d.ts +11 -0
- package/dist/composer/index.parts.js +15 -0
- package/dist/composer/internals.d.ts +29 -0
- package/dist/composer/internals.js +102 -0
- package/dist/composer/keyboard.d.ts +61 -0
- package/dist/composer/keyboard.js +75 -0
- package/dist/composer/panel.d.ts +28 -0
- package/dist/composer/panel.js +80 -0
- package/dist/composer/placeholder.d.ts +10 -0
- package/dist/composer/placeholder.js +8 -0
- package/dist/composer/popover.d.ts +12 -0
- package/dist/composer/popover.js +63 -0
- package/dist/composer/prefix-detection.d.ts +23 -0
- package/dist/composer/prefix-detection.js +58 -0
- package/dist/composer/root.d.ts +21 -0
- package/dist/composer/root.js +107 -0
- package/dist/composer/segments.d.ts +108 -0
- package/dist/composer/segments.js +366 -0
- package/dist/composer/store.d.ts +97 -0
- package/dist/composer/store.js +451 -0
- package/dist/composer/textarea.d.ts +39 -0
- package/dist/composer/textarea.js +64 -0
- package/dist/composer/trigger-tracker.d.ts +12 -0
- package/dist/composer/trigger-tracker.js +90 -0
- package/dist/composer/types.d.ts +126 -0
- package/dist/composer/types.js +4 -0
- package/dist/composer/use-composer-editor.d.ts +43 -0
- package/dist/composer/use-composer-editor.js +649 -0
- package/dist/internal/collapsible.d.ts +22 -0
- package/dist/internal/collapsible.js +90 -0
- package/dist/internal/primitive-props.d.ts +4 -0
- package/dist/internal/primitive-props.js +4 -0
- package/dist/internal/render/baseTypes.d.ts +18 -0
- package/dist/internal/render/baseTypes.js +4 -0
- package/dist/internal/render/empty.d.ts +3 -0
- package/dist/internal/render/empty.js +5 -0
- package/dist/internal/render/getReactElementRef.d.ts +5 -0
- package/dist/internal/render/getReactElementRef.js +15 -0
- package/dist/internal/render/getStateAttributesProps.d.ts +4 -0
- package/dist/internal/render/getStateAttributesProps.js +23 -0
- package/dist/internal/render/mergeObjects.d.ts +1 -0
- package/dist/internal/render/mergeObjects.js +14 -0
- package/dist/internal/render/mergeProps.d.ts +55 -0
- package/dist/internal/render/mergeProps.js +183 -0
- package/dist/internal/render/positioning.d.ts +22 -0
- package/dist/internal/render/positioning.js +78 -0
- package/dist/internal/render/reactVersion.d.ts +3 -0
- package/dist/internal/render/reactVersion.js +7 -0
- package/dist/internal/render/resolveClassName.d.ts +8 -0
- package/dist/internal/render/resolveClassName.js +12 -0
- package/dist/internal/render/resolveStyle.d.ts +8 -0
- package/dist/internal/render/resolveStyle.js +12 -0
- package/dist/internal/render/transition.d.ts +48 -0
- package/dist/internal/render/transition.js +203 -0
- package/dist/internal/render/types.d.ts +74 -0
- package/dist/internal/render/types.js +3 -0
- package/dist/internal/render/useMergedRefs.d.ts +21 -0
- package/dist/internal/render/useMergedRefs.js +102 -0
- package/dist/internal/render/useRefWithInit.d.ts +10 -0
- package/dist/internal/render/useRefWithInit.js +12 -0
- package/dist/internal/render/useRender.d.ts +71 -0
- package/dist/internal/render/useRender.js +12 -0
- package/dist/internal/render/useRenderElement.d.ts +62 -0
- package/dist/internal/render/useRenderElement.js +157 -0
- package/dist/internal/render/warn.d.ts +1 -0
- package/dist/internal/render/warn.js +15 -0
- package/dist/internal/state-mappings.d.ts +8 -0
- package/dist/internal/state-mappings.js +17 -0
- package/dist/message/index.d.ts +3 -0
- package/dist/message/index.js +3 -0
- package/dist/message/index.parts.d.ts +1 -0
- package/dist/message/index.parts.js +5 -0
- package/dist/message/message.d.ts +43 -0
- package/dist/message/message.js +78 -0
- package/dist/message-utils.d.ts +55 -0
- package/dist/message-utils.js +79 -0
- package/dist/reasoning/index.d.ts +3 -0
- package/dist/reasoning/index.js +3 -0
- package/dist/reasoning/index.parts.d.ts +1 -0
- package/dist/reasoning/index.parts.js +5 -0
- package/dist/reasoning/reasoning.d.ts +20 -0
- package/dist/reasoning/reasoning.js +49 -0
- package/dist/steps/index.d.ts +2 -0
- package/dist/steps/index.js +2 -0
- package/dist/steps/index.parts.d.ts +1 -0
- package/dist/steps/index.parts.js +5 -0
- package/dist/steps/steps.d.ts +53 -0
- package/dist/steps/steps.js +79 -0
- package/dist/thread/geometry.d.ts +18 -0
- package/dist/thread/geometry.js +59 -0
- package/dist/thread/index.d.ts +4 -0
- package/dist/thread/index.js +3 -0
- package/dist/thread/index.parts.d.ts +1 -0
- package/dist/thread/index.parts.js +5 -0
- package/dist/thread/stores.d.ts +21 -0
- package/dist/thread/stores.js +63 -0
- package/dist/thread/thread.d.ts +66 -0
- package/dist/thread/thread.js +639 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.js +17 -0
- package/package.json +106 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
// Headless steps timeline — a recursive disclosure tree. Every node is the
|
|
4
|
+
// same part (Item = a Collapsible; Trigger/Panel scope to the nearest Item by
|
|
5
|
+
// nesting), so timelines nest arbitrarily: a panel can contain further items.
|
|
6
|
+
// State surfaces as data attributes only: data-status per item, data-open /
|
|
7
|
+
// data-closed from the disclosure, and data-nested on items that sit inside
|
|
8
|
+
// another item (for rail/connector styling; sibling facts are CSS's job via
|
|
9
|
+
// :last-child / :only-child). Icons, connectors, markdown, and all classes
|
|
10
|
+
// belong to the styled layer; tool derivation belongs to the app layer.
|
|
11
|
+
import { createContext, use } from "react";
|
|
12
|
+
import { Collapsible } from "../internal/collapsible.js";
|
|
13
|
+
import { useRenderElement } from "../internal/render/useRenderElement.js";
|
|
14
|
+
// Visually-hidden-but-announced (screen-reader-only) default for Steps.Status.
|
|
15
|
+
const visuallyHiddenStyle = {
|
|
16
|
+
position: "absolute",
|
|
17
|
+
width: 1,
|
|
18
|
+
height: 1,
|
|
19
|
+
margin: -1,
|
|
20
|
+
padding: 0,
|
|
21
|
+
overflow: "hidden",
|
|
22
|
+
clipPath: "inset(50%)",
|
|
23
|
+
whiteSpace: "nowrap",
|
|
24
|
+
border: 0,
|
|
25
|
+
};
|
|
26
|
+
export const StepsRoot = ({ className, render, style, ...elementProps }) => useRenderElement("div", { className, render, style }, { props: [{ "data-steps": "" }, elementProps] });
|
|
27
|
+
StepsRoot.displayName = "Steps";
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Item — one disclosure node. Presence-nested: items inside another item
|
|
30
|
+
// carry data-nested.
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// True inside any Item, so deeper items can surface data-nested.
|
|
33
|
+
const NestedContext = createContext(false);
|
|
34
|
+
// Status inherited by Icon / Label from the nearest Item.
|
|
35
|
+
const StepStatusContext = createContext(null);
|
|
36
|
+
const useStepStatus = (status) => {
|
|
37
|
+
const inherited = use(StepStatusContext);
|
|
38
|
+
return status ?? inherited ?? "complete";
|
|
39
|
+
};
|
|
40
|
+
export const StepsItem = ({ status = "complete", defaultOpen, ...props }) => {
|
|
41
|
+
const isNested = use(NestedContext);
|
|
42
|
+
return (_jsx(NestedContext, { value: true, children: _jsx(StepStatusContext, { value: status, children: _jsx(Collapsible, { defaultOpen: defaultOpen ?? status === "active", "aria-current": status === "active" ? "step" : undefined, "data-steps-item": "", "data-status": status, "data-nested": isNested || undefined, ...props }) }) }));
|
|
43
|
+
};
|
|
44
|
+
StepsItem.displayName = "StepsItem";
|
|
45
|
+
export const StepsTrigger = (props) => (_jsx(Collapsible.Trigger, { "data-steps-trigger": "", ...props }));
|
|
46
|
+
StepsTrigger.displayName = "StepsTrigger";
|
|
47
|
+
export const StepsPanel = (props) => (_jsx(Collapsible.Panel, { "data-steps-panel": "", ...props }));
|
|
48
|
+
StepsPanel.displayName = "StepsPanel";
|
|
49
|
+
export const StepsIcon = ({ status, className, render, style, ...elementProps }) => {
|
|
50
|
+
const resolvedStatus = useStepStatus(status);
|
|
51
|
+
return useRenderElement("span", { className, render, style }, {
|
|
52
|
+
state: { status: resolvedStatus },
|
|
53
|
+
props: [{ "data-steps-icon": "", "aria-hidden": true }, elementProps],
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
StepsIcon.displayName = "StepsIcon";
|
|
57
|
+
export const StepsLabel = ({ status, className, render, style, ...elementProps }) => {
|
|
58
|
+
const resolvedStatus = useStepStatus(status);
|
|
59
|
+
return useRenderElement("span", { className, render, style }, {
|
|
60
|
+
state: { status: resolvedStatus },
|
|
61
|
+
props: [{ "data-steps-label": "" }, elementProps],
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
StepsLabel.displayName = "StepsLabel";
|
|
65
|
+
export const StepsStatus = ({ status, children, className, render, style, ...elementProps }) => {
|
|
66
|
+
const resolvedStatus = useStepStatus(status);
|
|
67
|
+
return useRenderElement("span", { className, render, style }, {
|
|
68
|
+
state: { status: resolvedStatus },
|
|
69
|
+
props: [
|
|
70
|
+
{
|
|
71
|
+
"data-steps-status": "",
|
|
72
|
+
style: visuallyHiddenStyle,
|
|
73
|
+
children: children ?? resolvedStatus,
|
|
74
|
+
},
|
|
75
|
+
elementProps,
|
|
76
|
+
],
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
StepsStatus.displayName = "StepsStatus";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const prefersReducedMotion: () => boolean;
|
|
2
|
+
export declare const resolveScrollBehavior: (behavior: ScrollBehavior) => ScrollBehavior;
|
|
3
|
+
export declare const scrollContainerTo: (el: HTMLElement, top: number, behavior: ScrollBehavior) => void;
|
|
4
|
+
export declare const wasPrepended: (mutations: MutationRecord[], previousFirst: Element | null, content: HTMLElement) => boolean;
|
|
5
|
+
export declare const DEFAULT_BOTTOM_OFFSET = 128;
|
|
6
|
+
export declare const DEFAULT_DOCK_SELECTOR = "[data-composer-context-window], [data-composer-container]";
|
|
7
|
+
export declare const queryDockParts: (root: HTMLElement, dockSelector: string) => Element[];
|
|
8
|
+
/**
|
|
9
|
+
* Height (px) to reserve at the bottom for the dock parts matching
|
|
10
|
+
* `dockSelector` — but NOT the command-list / ask-user panel. The dock is
|
|
11
|
+
* bottom-anchored, so it sits in a fixed region while the panel grows upward
|
|
12
|
+
* above it. The inset is measured from a single reference — the bottom-most
|
|
13
|
+
* match's top to the root's bottom — not a sum of matches, so a taller part
|
|
14
|
+
* stacked above must fit within COMPOSER_GAP. Returns null when no dock is
|
|
15
|
+
* mounted yet.
|
|
16
|
+
*/
|
|
17
|
+
export declare const measureDockInset: (root: HTMLElement, dockSelector: string) => number | null;
|
|
18
|
+
export declare const measureTopInset: (root: HTMLElement) => number;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Pure DOM geometry + measurement for the thread scroll subsystem. No React —
|
|
2
|
+
// unit-testable with stubbed elements/rects.
|
|
3
|
+
// Queried per call, not cached — the OS setting can change mid-session and
|
|
4
|
+
// none of these scrolls are hot paths. Guarded for non-DOM (pure-test) callers.
|
|
5
|
+
export const prefersReducedMotion = () => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
6
|
+
// "smooth" downgrades to "auto" under prefers-reduced-motion — deliberate
|
|
7
|
+
// movements still happen, they just don't animate.
|
|
8
|
+
export const resolveScrollBehavior = (behavior) => behavior === "smooth" && prefersReducedMotion() ? "auto" : behavior;
|
|
9
|
+
// Single place that performs the scroll, so callers just choose the behavior:
|
|
10
|
+
// 'instant' for jumps that must not animate, 'smooth' for deliberate movements.
|
|
11
|
+
export const scrollContainerTo = (el, top, behavior) => {
|
|
12
|
+
el.scrollTo({ top, behavior: resolveScrollBehavior(behavior) });
|
|
13
|
+
};
|
|
14
|
+
// A prepend = rows were added, nothing removed, and the previously-first row
|
|
15
|
+
// is still connected but no longer first (older history loading in above).
|
|
16
|
+
export const wasPrepended = (mutations, previousFirst, content) => previousFirst?.isConnected === true &&
|
|
17
|
+
content.firstElementChild !== previousFirst &&
|
|
18
|
+
mutations.some((m) => m.addedNodes.length > 0) &&
|
|
19
|
+
mutations.every((m) => m.removedNodes.length === 0);
|
|
20
|
+
// Fallback (px) until the composer is measured.
|
|
21
|
+
export const DEFAULT_BOTTOM_OFFSET = 128;
|
|
22
|
+
// Breathing room between the last line of content and the composer dock. The
|
|
23
|
+
// bottom overlay spans it in the styled layer.
|
|
24
|
+
const COMPOSER_GAP = 32;
|
|
25
|
+
export const DEFAULT_DOCK_SELECTOR = "[data-composer-context-window], [data-composer-container]";
|
|
26
|
+
// `dockSelector` is public API, so it may be an invalid selector string.
|
|
27
|
+
// Degrade to "no dock parts" rather than letting querySelectorAll throw a
|
|
28
|
+
// SyntaxError inside the layout effect (which would crash the render).
|
|
29
|
+
export const queryDockParts = (root, dockSelector) => {
|
|
30
|
+
try {
|
|
31
|
+
return [...root.querySelectorAll(dockSelector)];
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Height (px) to reserve at the bottom for the dock parts matching
|
|
39
|
+
* `dockSelector` — but NOT the command-list / ask-user panel. The dock is
|
|
40
|
+
* bottom-anchored, so it sits in a fixed region while the panel grows upward
|
|
41
|
+
* above it. The inset is measured from a single reference — the bottom-most
|
|
42
|
+
* match's top to the root's bottom — not a sum of matches, so a taller part
|
|
43
|
+
* stacked above must fit within COMPOSER_GAP. Returns null when no dock is
|
|
44
|
+
* mounted yet.
|
|
45
|
+
*/
|
|
46
|
+
export const measureDockInset = (root, dockSelector) => {
|
|
47
|
+
let dockTop = null;
|
|
48
|
+
for (const part of queryDockParts(root, dockSelector)) {
|
|
49
|
+
const top = part.getBoundingClientRect().top;
|
|
50
|
+
if (dockTop === null || top > dockTop)
|
|
51
|
+
dockTop = top;
|
|
52
|
+
}
|
|
53
|
+
if (dockTop === null)
|
|
54
|
+
return null;
|
|
55
|
+
return Math.round(root.getBoundingClientRect().bottom - dockTop + COMPOSER_GAP);
|
|
56
|
+
};
|
|
57
|
+
// Top inset reserved by the top overlay, measured straight off the rendered
|
|
58
|
+
// element (px) — no getComputedStyle / rem→px conversion. 0 if no top overlay.
|
|
59
|
+
export const measureTopInset = (root) => root.querySelector('[data-thread-overlay="top"]')?.getBoundingClientRect().height ?? 0;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export * as Thread from "./index.parts.js";
|
|
2
|
+
export type { ThreadVisibilityState } from "./stores.js";
|
|
3
|
+
export type { ThreadAutoScrollMode, ThreadComposerProps, ThreadContentProps, ThreadOverlayProps, ThreadOverlayState, ThreadPlaceholderProps, ThreadRootProps, ThreadScrollToMessageOptions, ThreadViewportProps, } from "./thread.js";
|
|
4
|
+
export { useThread, useThreadVisibility } from "./thread.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ThreadComposer as Composer, ThreadContent as Content, ThreadOverlay as Overlay, ThreadPlaceholder as Placeholder, ThreadRoot as Root, ThreadViewport as Viewport, } from "./thread.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Part names for the `Thread` namespace. Deliberately carries no
|
|
2
|
+
// "use client" directive: this module and index.ts must stay server-resolvable
|
|
3
|
+
// so a React Server Component can reach Thread.Root and its siblings
|
|
4
|
+
// through them. The directive lives one level down, on thread.tsx.
|
|
5
|
+
export { ThreadComposer as Composer, ThreadContent as Content, ThreadOverlay as Overlay, ThreadPlaceholder as Placeholder, ThreadRoot as Root, ThreadViewport as Viewport, } from "./thread.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const createEdgeStore: () => {
|
|
2
|
+
getSnapshot: () => boolean;
|
|
3
|
+
setSnapshot: (next: boolean) => void;
|
|
4
|
+
subscribe: (listener: () => void) => () => void;
|
|
5
|
+
};
|
|
6
|
+
export type EdgeStore = ReturnType<typeof createEdgeStore>;
|
|
7
|
+
export type ThreadVisibilityState = {
|
|
8
|
+
/** data-message-id values intersecting the viewport, in document order. */
|
|
9
|
+
visibleMessageIds: string[];
|
|
10
|
+
/** The topmost visible row — the one being read. */
|
|
11
|
+
currentMessageId: string | null;
|
|
12
|
+
};
|
|
13
|
+
export declare const EMPTY_VISIBILITY: ThreadVisibilityState;
|
|
14
|
+
export declare const visibilityStatesEqual: (a: ThreadVisibilityState, b: ThreadVisibilityState) => boolean;
|
|
15
|
+
export declare const createVisibilityStore: () => {
|
|
16
|
+
getSnapshot: () => ThreadVisibilityState;
|
|
17
|
+
hasListeners: () => boolean;
|
|
18
|
+
setSnapshot: (next: ThreadVisibilityState) => void;
|
|
19
|
+
subscribe: (listener: () => void, onFirstSubscribe: () => void, onLastUnsubscribe: () => void) => () => void;
|
|
20
|
+
};
|
|
21
|
+
export type VisibilityStore = ReturnType<typeof createVisibilityStore>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Scroll-edge + visibility stores for the thread. External (hand-rolled
|
|
2
|
+
// useSyncExternalStore stores) so a change re-renders only the components that
|
|
3
|
+
// read it, never the Thread tree — the context value stays referentially stable
|
|
4
|
+
// for the thread's lifetime. Pure; no React, no DOM.
|
|
5
|
+
// One boolean store per edge (top / bottom). Two independent stores rather than
|
|
6
|
+
// a single { atTop, atBottom } snapshot, so a flip at one edge never notifies
|
|
7
|
+
// the other's readers.
|
|
8
|
+
export const createEdgeStore = () => {
|
|
9
|
+
let snapshot = true;
|
|
10
|
+
const listeners = new Set();
|
|
11
|
+
return {
|
|
12
|
+
getSnapshot: () => snapshot,
|
|
13
|
+
setSnapshot: (next) => {
|
|
14
|
+
if (snapshot === next)
|
|
15
|
+
return;
|
|
16
|
+
snapshot = next;
|
|
17
|
+
for (const listener of listeners)
|
|
18
|
+
listener();
|
|
19
|
+
},
|
|
20
|
+
subscribe: (listener) => {
|
|
21
|
+
listeners.add(listener);
|
|
22
|
+
return () => {
|
|
23
|
+
listeners.delete(listener);
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export const EMPTY_VISIBILITY = {
|
|
29
|
+
visibleMessageIds: [],
|
|
30
|
+
currentMessageId: null,
|
|
31
|
+
};
|
|
32
|
+
export const visibilityStatesEqual = (a, b) => a.currentMessageId === b.currentMessageId &&
|
|
33
|
+
a.visibleMessageIds.length === b.visibleMessageIds.length &&
|
|
34
|
+
a.visibleMessageIds.every((id, index) => id === b.visibleMessageIds[index]);
|
|
35
|
+
// Which data-message-id rows intersect the viewport. Lazy + ref-counted: the
|
|
36
|
+
// observers behind it exist only while at least one useThreadVisibility
|
|
37
|
+
// subscriber is mounted, so unsubscribed threads pay nothing.
|
|
38
|
+
export const createVisibilityStore = () => {
|
|
39
|
+
let snapshot = EMPTY_VISIBILITY;
|
|
40
|
+
const listeners = new Set();
|
|
41
|
+
return {
|
|
42
|
+
getSnapshot: () => snapshot,
|
|
43
|
+
hasListeners: () => listeners.size > 0,
|
|
44
|
+
setSnapshot: (next) => {
|
|
45
|
+
if (visibilityStatesEqual(snapshot, next))
|
|
46
|
+
return;
|
|
47
|
+
snapshot = next;
|
|
48
|
+
for (const listener of listeners)
|
|
49
|
+
listener();
|
|
50
|
+
},
|
|
51
|
+
subscribe: (listener, onFirstSubscribe, onLastUnsubscribe) => {
|
|
52
|
+
const wasEmpty = listeners.size === 0;
|
|
53
|
+
listeners.add(listener);
|
|
54
|
+
if (wasEmpty)
|
|
55
|
+
onFirstSubscribe();
|
|
56
|
+
return () => {
|
|
57
|
+
listeners.delete(listener);
|
|
58
|
+
if (listeners.size === 0)
|
|
59
|
+
onLastUnsubscribe();
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type ReactNode, type RefObject } from "react";
|
|
2
|
+
import type { PrimitiveProps } from "../internal/primitive-props.js";
|
|
3
|
+
import { type ThreadVisibilityState } from "./stores.js";
|
|
4
|
+
export type { ThreadVisibilityState } from "./stores.js";
|
|
5
|
+
export type ThreadScrollToMessageOptions = {
|
|
6
|
+
/** Viewport edge (or center) to align the row to; scrollIntoView's block. */
|
|
7
|
+
align?: "start" | "center" | "end" | "nearest";
|
|
8
|
+
behavior?: ScrollBehavior;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Public thread surface. Subscribes to the scroll edges, so call it where
|
|
12
|
+
* isAtTop / isAtBottom are actually read (e.g. a scroll button or a load-older
|
|
13
|
+
* trigger); the commands and refs are stable and never cause re-renders.
|
|
14
|
+
*/
|
|
15
|
+
export declare const useThread: () => {
|
|
16
|
+
isAtTop: boolean;
|
|
17
|
+
isAtBottom: boolean;
|
|
18
|
+
scrollToBottom: (behavior?: ScrollBehavior) => void;
|
|
19
|
+
scrollToTop: (behavior?: ScrollBehavior) => void;
|
|
20
|
+
scrollToMessage: (messageId: string, options?: ThreadScrollToMessageOptions) => boolean;
|
|
21
|
+
scrollRef: RefObject<HTMLDivElement | null>;
|
|
22
|
+
contentRef: RefObject<HTMLDivElement | null>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Which data-message-id rows are in view, plus the topmost one (the row being
|
|
26
|
+
* read). Subscribing lazily spins up the tracking observers; when the last
|
|
27
|
+
* subscriber unmounts they are torn down, so unused threads pay nothing.
|
|
28
|
+
*/
|
|
29
|
+
export declare const useThreadVisibility: () => ThreadVisibilityState;
|
|
30
|
+
export type ThreadAutoScrollMode = "off" | "bottom" | "jump" | "follow";
|
|
31
|
+
export type ThreadRootProps = PrimitiveProps<"div"> & {
|
|
32
|
+
children?: ReactNode;
|
|
33
|
+
autoScroll?: ThreadAutoScrollMode;
|
|
34
|
+
/**
|
|
35
|
+
* Hold the reading position when rows are prepended (history pagination).
|
|
36
|
+
* Opt-in: enabling it attaches a passive scroll listener to keep the anchor
|
|
37
|
+
* current, so leave it off unless older content actually loads in above.
|
|
38
|
+
*/
|
|
39
|
+
preserveScrollOnPrepend?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* CSS selector for the bottom-docked parts the thread reserves space for.
|
|
42
|
+
* Every match is observed for resize, but the reserved inset is measured
|
|
43
|
+
* from a single reference — the bottom-most match's top edge, not a sum of
|
|
44
|
+
* matches — so a taller part stacked above it must fit within the content
|
|
45
|
+
* gap. Must be a valid CSS selector. Defaults to the styled composer's dock
|
|
46
|
+
* slots.
|
|
47
|
+
*/
|
|
48
|
+
dockSelector?: string;
|
|
49
|
+
};
|
|
50
|
+
export declare const ThreadRoot: ({ autoScroll, preserveScrollOnPrepend, dockSelector, className, render, style, ...elementProps }: ThreadRootProps) => import("react").JSX.Element;
|
|
51
|
+
export type ThreadOverlayState = {
|
|
52
|
+
/** Which edge this overlay marks, surfaced as data-thread-overlay. */
|
|
53
|
+
direction: "top" | "bottom";
|
|
54
|
+
};
|
|
55
|
+
export type ThreadOverlayProps = PrimitiveProps<"div", ThreadOverlayState> & {
|
|
56
|
+
direction: "top" | "bottom";
|
|
57
|
+
};
|
|
58
|
+
export declare const ThreadOverlay: import("react").MemoExoticComponent<({ direction, className, render, style, ...elementProps }: ThreadOverlayProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>>;
|
|
59
|
+
export type ThreadViewportProps = PrimitiveProps<"div">;
|
|
60
|
+
export declare const ThreadViewport: ({ className, render, style, ...elementProps }: ThreadViewportProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
61
|
+
export type ThreadContentProps = PrimitiveProps<"div">;
|
|
62
|
+
export declare const ThreadContent: ({ className, render, style, ...elementProps }: ThreadContentProps) => import("react").JSX.Element;
|
|
63
|
+
export type ThreadComposerProps = PrimitiveProps<"div">;
|
|
64
|
+
export declare const ThreadComposer: ({ className, render, style, ...elementProps }: ThreadComposerProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
65
|
+
export type ThreadPlaceholderProps = PrimitiveProps<"div">;
|
|
66
|
+
export declare const ThreadPlaceholder: ({ className, render, style, ...elementProps }: ThreadPlaceholderProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|