@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,49 @@
|
|
|
1
|
+
import { type ComponentProps, type ReactNode } from "react";
|
|
2
|
+
import type { PrimitiveProps } from "../internal/primitive-props.js";
|
|
3
|
+
import { Commands } from "./commands.js";
|
|
4
|
+
import type { CommandItemData } from "./types.js";
|
|
5
|
+
export type CommandListState = "loading" | "empty" | "ready";
|
|
6
|
+
export declare const optionDomId: (listboxId: string, value: string) => string;
|
|
7
|
+
export type ComposerCommandState = {
|
|
8
|
+
/** Present as data-loading while an async items callback is in flight. */
|
|
9
|
+
loading: boolean;
|
|
10
|
+
/** Present as data-empty when nothing matches the query. */
|
|
11
|
+
empty: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type ComposerCommandProps = PrimitiveProps<"div", ComposerCommandState> & {
|
|
14
|
+
prefix: string;
|
|
15
|
+
};
|
|
16
|
+
export declare const ComposerCommand: ({ prefix, className, render, style, children, ...elementProps }: ComposerCommandProps) => import("react").JSX.Element | null;
|
|
17
|
+
export declare const useCommandListItems: <Item extends CommandItemData = CommandItemData>() => {
|
|
18
|
+
items: Item[];
|
|
19
|
+
state: CommandListState;
|
|
20
|
+
};
|
|
21
|
+
export type ComposerCommandListProps<Item extends CommandItemData> = Omit<PrimitiveProps<"div">, "children"> & {
|
|
22
|
+
children: (item: Item) => ReactNode;
|
|
23
|
+
};
|
|
24
|
+
export declare const ComposerCommandList: <Item extends CommandItemData>({ className, render, style, children: renderItem, ...elementProps }: ComposerCommandListProps<Item>) => ReactNode;
|
|
25
|
+
export type ComposerCommandLoadingProps = PrimitiveProps<"div">;
|
|
26
|
+
export declare const ComposerCommandLoading: ({ children, className, render, style, ...elementProps }: ComposerCommandLoadingProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
27
|
+
export type ComposerCommandEmptyProps = PrimitiveProps<"div">;
|
|
28
|
+
export declare const ComposerCommandEmpty: ({ children, className, render, style, ...elementProps }: ComposerCommandEmptyProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
29
|
+
export type ComposerCommandDismissProps = PrimitiveProps<"button">;
|
|
30
|
+
export declare const ComposerCommandDismiss: ({ children, className, render, style, ...elementProps }: ComposerCommandDismissProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
31
|
+
export type ComposerCommandItemProps = Omit<ComponentProps<typeof Commands.Item>, "highlighted" | "disabled"> & {
|
|
32
|
+
value: string;
|
|
33
|
+
};
|
|
34
|
+
export declare const ComposerCommandItem: ({ value, ...props }: ComposerCommandItemProps) => import("react").JSX.Element;
|
|
35
|
+
export type ComposerCommandItemIconProps = PrimitiveProps<"span">;
|
|
36
|
+
export declare const ComposerCommandItemIcon: ({ className, render, style, ...elementProps }: ComposerCommandItemIconProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
37
|
+
export type ComposerCommandItemLabelProps = PrimitiveProps<"span">;
|
|
38
|
+
export declare const ComposerCommandItemLabel: ({ className, render, style, ...elementProps }: ComposerCommandItemLabelProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
39
|
+
export type ComposerCommandItemDescriptionProps = PrimitiveProps<"span">;
|
|
40
|
+
export declare const ComposerCommandItemDescription: ({ className, render, style, ...elementProps }: ComposerCommandItemDescriptionProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
41
|
+
export type ComposerCommandGroupProps<Item extends CommandItemData = CommandItemData> = {
|
|
42
|
+
/** Bucket the resolved items by this key (defines the app's grouping taxonomy). */
|
|
43
|
+
groupBy: (item: Item) => string;
|
|
44
|
+
/** Render one group: its key + that group's already-filtered items. */
|
|
45
|
+
children: (group: string, items: Item[]) => ReactNode;
|
|
46
|
+
};
|
|
47
|
+
export declare const ComposerCommandGroup: <Item extends CommandItemData = CommandItemData>({ groupBy, children: renderGroup, }: ComposerCommandGroupProps<Item>) => ReactNode;
|
|
48
|
+
export type ComposerCommandGroupLabelProps = PrimitiveProps<"div">;
|
|
49
|
+
export declare const ComposerCommandGroupLabel: ({ className, render, style, ...elementProps }: ComposerCommandGroupLabelProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
// Composer.Command family — resolution (sync/async items, fuzzy filter),
|
|
4
|
+
// keyboard/mouse selection, chip insertion, dismissal, and the nav/items
|
|
5
|
+
// context split (highlight changes don't re-render the items map, and items
|
|
6
|
+
// mutations don't re-render every row). All unstyled; state panels as
|
|
7
|
+
// data-state / data-highlighted.
|
|
8
|
+
import { createContext, Fragment, use, useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
9
|
+
import { useRenderElement } from "../internal/render/useRenderElement.js";
|
|
10
|
+
import { Commands } from "./commands.js";
|
|
11
|
+
import { filterArrayItems, suggestionRemainder } from "./fuzzy.js";
|
|
12
|
+
import { useAsRef, useComposerInternals, useIsomorphicLayoutEffect } from "./internals.js";
|
|
13
|
+
import { useComposer, useComposerContextStore } from "./store.js";
|
|
14
|
+
const CommandListNavContext = createContext(null);
|
|
15
|
+
const CommandListItemsContext = createContext(null);
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Item resolution — turn the prefix's items config (array or async callback)
|
|
18
|
+
// into the current filtered list + loading/empty/ready state.
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
const useResolvedItems = (itemsProp, query, isActive) => {
|
|
21
|
+
const isCallback = typeof itemsProp === "function";
|
|
22
|
+
const [asyncState, setAsyncState] = useState(() => ({ items: [], loading: isCallback }));
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (typeof itemsProp !== "function")
|
|
25
|
+
return;
|
|
26
|
+
if (!isActive)
|
|
27
|
+
return;
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
let cancelled = false;
|
|
30
|
+
let result;
|
|
31
|
+
try {
|
|
32
|
+
result = itemsProp(query, { signal: controller.signal });
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
console.warn("Composer.commands items callback threw:", error);
|
|
36
|
+
setAsyncState({ items: [], loading: false });
|
|
37
|
+
return () => {
|
|
38
|
+
cancelled = true;
|
|
39
|
+
controller.abort();
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (result instanceof Promise) {
|
|
43
|
+
setAsyncState((previous) => ({ ...previous, loading: true }));
|
|
44
|
+
result.then((resolved) => {
|
|
45
|
+
if (cancelled)
|
|
46
|
+
return;
|
|
47
|
+
setAsyncState({ items: resolved, loading: false });
|
|
48
|
+
}, (error) => {
|
|
49
|
+
if (cancelled)
|
|
50
|
+
return;
|
|
51
|
+
if (error?.name === "AbortError")
|
|
52
|
+
return;
|
|
53
|
+
console.warn("Composer.commands items callback rejected:", error);
|
|
54
|
+
setAsyncState((previous) => ({ ...previous, loading: false }));
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
setAsyncState({ items: result, loading: false });
|
|
59
|
+
}
|
|
60
|
+
return () => {
|
|
61
|
+
cancelled = true;
|
|
62
|
+
controller.abort();
|
|
63
|
+
};
|
|
64
|
+
}, [itemsProp, query, isActive]);
|
|
65
|
+
if (Array.isArray(itemsProp)) {
|
|
66
|
+
const items = filterArrayItems(itemsProp, query);
|
|
67
|
+
return { items, state: items.length === 0 ? "empty" : "ready" };
|
|
68
|
+
}
|
|
69
|
+
if (asyncState.loading) {
|
|
70
|
+
// Locally filter the most-recent resolved set so typing feels instant
|
|
71
|
+
// while the new fetch is in flight. The server result replaces this once
|
|
72
|
+
// it lands.
|
|
73
|
+
return {
|
|
74
|
+
items: filterArrayItems(asyncState.items, query),
|
|
75
|
+
state: "loading",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
items: asyncState.items,
|
|
80
|
+
state: asyncState.items.length === 0 ? "empty" : "ready",
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Composer.Command — the orchestrator: resolves items for the active
|
|
85
|
+
// prefix, owns the highlight/selection/dismiss logic, and provides the two
|
|
86
|
+
// contexts. Renders null unless this prefix is the active one.
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
const EMPTY_ITEMS = [];
|
|
89
|
+
// Deterministic option-row id shared by the row itself and the editor's
|
|
90
|
+
// aria-activedescendant. Values sanitize to valid id tokens.
|
|
91
|
+
export const optionDomId = (listboxId, value) => `${listboxId}-option-${value.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
92
|
+
// The badge's hint chrome: non-editable so the caret can't enter it, no chip
|
|
93
|
+
// id so the reader/mappers treat it as zero-width presentation, aria-hidden
|
|
94
|
+
// so screen readers skip the ghost. Styled via its part attribute like any other part.
|
|
95
|
+
const createHintElement = (badge) => {
|
|
96
|
+
const hint = badge.ownerDocument.createElement("span");
|
|
97
|
+
hint.setAttribute("data-command-hint", "");
|
|
98
|
+
hint.setAttribute("contenteditable", "false");
|
|
99
|
+
hint.setAttribute("aria-hidden", "true");
|
|
100
|
+
badge.appendChild(hint);
|
|
101
|
+
return hint;
|
|
102
|
+
};
|
|
103
|
+
export const ComposerCommand = ({ prefix, className, render, style, children, ...elementProps }) => {
|
|
104
|
+
const store = useComposerContextStore();
|
|
105
|
+
const internals = useComposerInternals();
|
|
106
|
+
// Renders while this prefix is the active one AND through its close animation. `active`
|
|
107
|
+
// flips false the instant the trigger closes, but the store keeps `present` sticky until
|
|
108
|
+
// the Panel's exit animation finishes (finalizePanelClose). `wasActiveRef` marks that this
|
|
109
|
+
// prefix — not a sibling — is the one closing, so only it keeps rendering during the window.
|
|
110
|
+
const isActive = useComposer((composer) => composer.commands.active && composer.commands.trigger === prefix);
|
|
111
|
+
const present = useComposer((composer) => composer.commands.present);
|
|
112
|
+
const query = useComposer((composer) => composer.commands.query);
|
|
113
|
+
const wasActiveRef = useRef(false);
|
|
114
|
+
if (isActive)
|
|
115
|
+
wasActiveRef.current = true;
|
|
116
|
+
else if (!present)
|
|
117
|
+
wasActiveRef.current = false;
|
|
118
|
+
const isPresent = isActive || (present && wasActiveRef.current);
|
|
119
|
+
const config = internals.commands[prefix];
|
|
120
|
+
const itemsProp = config?.items ?? EMPTY_ITEMS;
|
|
121
|
+
const kind = config?.kind ?? "execute";
|
|
122
|
+
// Freeze the last resolved items while closing so the exiting panel shows what it had,
|
|
123
|
+
// not a re-filter/re-fetch against the now-empty query.
|
|
124
|
+
const resolved = useResolvedItems(itemsProp, query, isActive);
|
|
125
|
+
const lastResolvedRef = useRef(resolved);
|
|
126
|
+
if (isActive)
|
|
127
|
+
lastResolvedRef.current = resolved;
|
|
128
|
+
const { items, state } = isActive ? resolved : lastResolvedRef.current;
|
|
129
|
+
// The highlight index lives in the store (so the editor's keydown handler can
|
|
130
|
+
// move it). It's raw/unbounded; wrap it by the current item count here. The
|
|
131
|
+
// wrap runs over the enabled subset only, so disabled rows render but never
|
|
132
|
+
// take the highlight — arrow keys hop over them for free. An empty list has
|
|
133
|
+
// no highlighted row — the "No results" row stands in as the sole selectable.
|
|
134
|
+
const enabledItems = useMemo(() => items.filter((item) => !item.disabled), [items]);
|
|
135
|
+
const highlightIndex = useComposer((composer) => composer.commands.highlightIndex);
|
|
136
|
+
const activeIndex = enabledItems.length > 0
|
|
137
|
+
? ((highlightIndex % enabledItems.length) + enabledItems.length) % enabledItems.length
|
|
138
|
+
: -1;
|
|
139
|
+
const highlightedItem = enabledItems[activeIndex] ?? null;
|
|
140
|
+
const effectiveHighlight = highlightedItem?.value ?? null;
|
|
141
|
+
// Badge hint — one real element (span[data-command-hint], appended
|
|
142
|
+
// inside the badge) carrying either the ghost-text completion of the
|
|
143
|
+
// highlighted item or the per-prefix empty-query placeholder. One slot, one
|
|
144
|
+
// value: the suggestion wins by a plain ?? chain, so exclusivity is code,
|
|
145
|
+
// not CSS specificity. The reader and position mappers treat non-editable,
|
|
146
|
+
// non-chip elements as zero-width presentation, so the hint never reaches
|
|
147
|
+
// the model. The badge is engine-owned DOM out of JSX reach — same access
|
|
148
|
+
// pattern as the popover's anchor query — so this is a true DOM-integration
|
|
149
|
+
// effect. No deps: badge identity changes on token rewraps, which always
|
|
150
|
+
// coincide with a re-render here (query/highlight subscribed).
|
|
151
|
+
const suggestion = isActive && highlightedItem && (config?.suggestion ?? true)
|
|
152
|
+
? suggestionRemainder(query, highlightedItem.label)
|
|
153
|
+
: null;
|
|
154
|
+
const hintText = suggestion ?? (isActive && query === "" ? (config?.placeholder ?? "Type to filter") : null);
|
|
155
|
+
useIsomorphicLayoutEffect(() => {
|
|
156
|
+
const badge = store.editorRef.current?.getRootElement()?.querySelector("[data-command-badge]");
|
|
157
|
+
const existingHint = badge?.querySelector("[data-command-hint]") ?? null;
|
|
158
|
+
if (!badge || !hintText) {
|
|
159
|
+
existingHint?.remove();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const hint = existingHint ?? createHintElement(badge);
|
|
163
|
+
if (hint.textContent !== hintText)
|
|
164
|
+
hint.textContent = hintText;
|
|
165
|
+
});
|
|
166
|
+
// Mirror the highlighted row's DOM id into the store — the editor renders
|
|
167
|
+
// it as aria-activedescendant. This is where the raw highlight index
|
|
168
|
+
// resolves against the actual item list, so the id can only be derived here.
|
|
169
|
+
// Only the ACTIVE command writes: with several Commands mounted, an inactive
|
|
170
|
+
// sibling writing null would ping-pong against the active one's id forever
|
|
171
|
+
// (max-update-depth). Clearing on close is setCommands' job.
|
|
172
|
+
useIsomorphicLayoutEffect(() => {
|
|
173
|
+
if (!isActive)
|
|
174
|
+
return;
|
|
175
|
+
store.setActiveOptionId(highlightedItem ? optionDomId(store.listboxId, highlightedItem.value) : null);
|
|
176
|
+
});
|
|
177
|
+
const selectByValue = useCallback((value) => {
|
|
178
|
+
const editor = store.editorRef.current;
|
|
179
|
+
if (!editor)
|
|
180
|
+
return;
|
|
181
|
+
const item = items.find((candidate) => candidate.value === value);
|
|
182
|
+
if (!item || item.disabled)
|
|
183
|
+
return;
|
|
184
|
+
if (kind === "insert") {
|
|
185
|
+
editor.insertChipAtTrigger({
|
|
186
|
+
prefix,
|
|
187
|
+
value: item.value,
|
|
188
|
+
label: item.label ?? item.value,
|
|
189
|
+
icon: item.icon,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
editor.deleteTrigger();
|
|
194
|
+
// Read the attachment actions lazily at selection time — their
|
|
195
|
+
// identities are store-stable, so subscribing would only re-render the
|
|
196
|
+
// list on unrelated attachment changes.
|
|
197
|
+
const { add, remove, openFileDialog } = store.getSnapshot().attachments;
|
|
198
|
+
const onSelectContext = {
|
|
199
|
+
editor: store.controller,
|
|
200
|
+
attachments: { add, remove, openFileDialog },
|
|
201
|
+
};
|
|
202
|
+
item.onSelect?.(onSelectContext);
|
|
203
|
+
}
|
|
204
|
+
editor.closeCommands();
|
|
205
|
+
}, [store, items, kind, prefix]);
|
|
206
|
+
const dismiss = useCallback(() => {
|
|
207
|
+
store.editorRef.current?.dismissCommands();
|
|
208
|
+
}, [store]);
|
|
209
|
+
// The editor's keydown handler invokes the current selection through
|
|
210
|
+
// commandSelectRef. Keep the latest values in a ref so the callback stays
|
|
211
|
+
// stable, and register it via the root's callback ref (commit-time) — not an
|
|
212
|
+
// effect, not a render-phase assignment.
|
|
213
|
+
const selectState = useAsRef({ effectiveHighlight, selectByValue, dismiss, state });
|
|
214
|
+
const runSelect = useCallback(() => {
|
|
215
|
+
const { effectiveHighlight, selectByValue, dismiss, state } = selectState.current;
|
|
216
|
+
// With a match, select it. When empty, the "No results" row is the sole
|
|
217
|
+
// option and selecting it dismisses — so Tab/Enter aren't dead (Linear-style).
|
|
218
|
+
if (effectiveHighlight)
|
|
219
|
+
selectByValue(effectiveHighlight);
|
|
220
|
+
else if (state === "empty")
|
|
221
|
+
dismiss();
|
|
222
|
+
}, []);
|
|
223
|
+
const registerSelect = useCallback((node) => {
|
|
224
|
+
store.commandSelectRef.current = node ? runSelect : null;
|
|
225
|
+
}, [store, runSelect]);
|
|
226
|
+
// Scroll the highlighted row into view as it becomes the highlight. Stable, so
|
|
227
|
+
// React calls it only on highlight change — no per-render scroll, no effect.
|
|
228
|
+
//
|
|
229
|
+
// container:"nearest" hard-scopes the scroll to the list on Chromium.
|
|
230
|
+
// Safari/Firefox silently ignore the member (unknown dictionary keys) and
|
|
231
|
+
// run an UNSCOPED block:"nearest" — scrollIntoView then scrolls every
|
|
232
|
+
// ancestor scrolling box, the window included, whenever the row isn't
|
|
233
|
+
// viewport-visible.
|
|
234
|
+
//
|
|
235
|
+
// KNOWN BUG (accepted 2026-07-10): on Safari, arrowing through the list
|
|
236
|
+
// while the popup opens can scroll the page behind it (reproduced; the
|
|
237
|
+
// positioner's viewport containment is not sufficient there). Accepted in
|
|
238
|
+
// exchange for native scroll-padding support and −24 lines; goes away when
|
|
239
|
+
// WebKit ships `container`. If it starts to hurt sooner, the scoped manual
|
|
240
|
+
// implementation lives in this function's git history (PR #39, first
|
|
241
|
+
// commit).
|
|
242
|
+
const scrollHighlightedIntoView = useCallback((node) => {
|
|
243
|
+
// Cast: lib.dom doesn't know the `container` member yet (Chromium-only).
|
|
244
|
+
node?.scrollIntoView({ block: "nearest", container: "nearest" });
|
|
245
|
+
}, []);
|
|
246
|
+
const navContext = useMemo(() => ({
|
|
247
|
+
highlightedValue: effectiveHighlight,
|
|
248
|
+
setHighlightedValue: (value) => {
|
|
249
|
+
const index = value === null ? -1 : enabledItems.findIndex((item) => item.value === value);
|
|
250
|
+
if (index >= 0)
|
|
251
|
+
store.setHighlight(index);
|
|
252
|
+
},
|
|
253
|
+
selectByValue,
|
|
254
|
+
dismiss,
|
|
255
|
+
scrollHighlightedIntoView,
|
|
256
|
+
}), [effectiveHighlight, enabledItems, selectByValue, dismiss, scrollHighlightedIntoView, store]);
|
|
257
|
+
const itemsContext = useMemo(() => ({ items, state }), [items, state]);
|
|
258
|
+
const element = useRenderElement("div", { className, render, style }, {
|
|
259
|
+
enabled: isPresent,
|
|
260
|
+
state: { loading: state === "loading", empty: state === "empty" },
|
|
261
|
+
ref: registerSelect,
|
|
262
|
+
props: [{ "data-composer-command-list": "", children }, elementProps],
|
|
263
|
+
});
|
|
264
|
+
if (!isPresent)
|
|
265
|
+
return null;
|
|
266
|
+
return (_jsx(CommandListItemsContext, { value: itemsContext, children: _jsx(CommandListNavContext, { value: navContext, children: element }) }));
|
|
267
|
+
};
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
// List hooks — the consumer-facing items hook and the internal nav accessor
|
|
270
|
+
// the slot primitives read.
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
export const useCommandListItems = () => {
|
|
273
|
+
const context = use(CommandListItemsContext);
|
|
274
|
+
if (!context) {
|
|
275
|
+
throw new Error("<Composer.CommandList> must be rendered inside <Composer.Command>.");
|
|
276
|
+
}
|
|
277
|
+
return context;
|
|
278
|
+
};
|
|
279
|
+
const useCommandListNav = (componentName) => {
|
|
280
|
+
const context = use(CommandListNavContext);
|
|
281
|
+
if (!context) {
|
|
282
|
+
throw new Error(`<Composer.${componentName}> must be rendered inside <Composer.Command>.`);
|
|
283
|
+
}
|
|
284
|
+
return context;
|
|
285
|
+
};
|
|
286
|
+
export const ComposerCommandList = ({ className, render, style, children: renderItem, ...elementProps }) => {
|
|
287
|
+
const { items, state } = useCommandListItems();
|
|
288
|
+
const store = useComposerContextStore();
|
|
289
|
+
return useRenderElement("div", { className, render, style }, {
|
|
290
|
+
props: [
|
|
291
|
+
{
|
|
292
|
+
// The combobox popup: the editor references this element via
|
|
293
|
+
// aria-controls and points aria-activedescendant at its rows.
|
|
294
|
+
role: "listbox",
|
|
295
|
+
id: store.listboxId,
|
|
296
|
+
"aria-label": "Suggestions",
|
|
297
|
+
"aria-busy": state === "loading" || undefined,
|
|
298
|
+
"data-composer-command-items": "",
|
|
299
|
+
children: items.map((item, index) => (_jsx(Fragment, { children: renderItem(item) }, item.value ?? `__cmd_${index}`))),
|
|
300
|
+
},
|
|
301
|
+
elementProps,
|
|
302
|
+
],
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
export const ComposerCommandLoading = ({ children, className, render, style, ...elementProps }) => useRenderElement("div", { className, render, style }, {
|
|
306
|
+
props: [{ "data-composer-command-loading": "", children }, elementProps],
|
|
307
|
+
});
|
|
308
|
+
export const ComposerCommandEmpty = ({ children, className, render, style, ...elementProps }) => useRenderElement("div", { className, render, style }, {
|
|
309
|
+
props: [{ role: "status", "data-composer-command-empty": "", children }, elementProps],
|
|
310
|
+
});
|
|
311
|
+
export const ComposerCommandDismiss = ({ children, className, render, style, ...elementProps }) => {
|
|
312
|
+
const navContext = useCommandListNav("CommandDismiss");
|
|
313
|
+
return useRenderElement("button", { className, render, style }, {
|
|
314
|
+
props: [
|
|
315
|
+
{
|
|
316
|
+
tabIndex: -1,
|
|
317
|
+
"data-composer-command-dismiss": "",
|
|
318
|
+
onMouseDown: (event) => {
|
|
319
|
+
event.preventDefault();
|
|
320
|
+
navContext.dismiss();
|
|
321
|
+
},
|
|
322
|
+
children,
|
|
323
|
+
},
|
|
324
|
+
elementProps,
|
|
325
|
+
],
|
|
326
|
+
});
|
|
327
|
+
};
|
|
328
|
+
export const ComposerCommandItem = ({ value, ...props }) => {
|
|
329
|
+
const navContext = useCommandListNav("CommandItem");
|
|
330
|
+
const itemsContext = use(CommandListItemsContext);
|
|
331
|
+
const store = useComposerContextStore();
|
|
332
|
+
const isHighlighted = navContext.highlightedValue === value;
|
|
333
|
+
// Disabled comes from the item data (CommandItemData.disabled) — the same
|
|
334
|
+
// source the nav wrap skips — so the row can never disagree with the
|
|
335
|
+
// keyboard behavior. Not a prop: two writers would let them drift.
|
|
336
|
+
const isDisabled = itemsContext?.items.some((item) => item.value === value && item.disabled) ?? false;
|
|
337
|
+
return (_jsx(Commands.Item, { ref: isHighlighted ? navContext.scrollHighlightedIntoView : undefined, id: optionDomId(store.listboxId, value), "aria-selected": isHighlighted, "data-composer-command-item": "", highlighted: isHighlighted, disabled: isDisabled, onMouseDown: (event) => {
|
|
338
|
+
// preventDefault even when disabled — the click must never steal
|
|
339
|
+
// focus from the editor.
|
|
340
|
+
event.preventDefault();
|
|
341
|
+
if (!isDisabled)
|
|
342
|
+
navContext.selectByValue(value);
|
|
343
|
+
}, onMouseEnter: isDisabled ? undefined : () => navContext.setHighlightedValue(value), ...props }));
|
|
344
|
+
};
|
|
345
|
+
export const ComposerCommandItemIcon = ({ className, render, style, ...elementProps }) => useRenderElement("span", { className, render, style }, { props: [{ "data-composer-command-item-icon": "" }, elementProps] });
|
|
346
|
+
export const ComposerCommandItemLabel = ({ className, render, style, ...elementProps }) => useRenderElement("span", { className, render, style }, { props: [{ "data-composer-command-item-label": "" }, elementProps] });
|
|
347
|
+
export const ComposerCommandItemDescription = ({ className, render, style, ...elementProps }) => useRenderElement("span", { className, render, style }, { props: [{ "data-composer-command-item-description": "" }, elementProps] });
|
|
348
|
+
// Grouping is a render concern: this reads the resolved items, buckets them by `groupBy`
|
|
349
|
+
// with `Map.groupBy` (which preserves first-appearance order — Object.groupBy would reorder
|
|
350
|
+
// integer-like keys), and renders the child once per group inside a `command-group`
|
|
351
|
+
// container. Nav order stays store-driven, so a group whose items all filter out just
|
|
352
|
+
// doesn't render. Annotate `groupBy`'s param to type the items (no inline JSX generic).
|
|
353
|
+
export const ComposerCommandGroup = ({ groupBy, children: renderGroup, }) => {
|
|
354
|
+
const { items } = useCommandListItems();
|
|
355
|
+
return (_jsx(_Fragment, { children: [...Map.groupBy(items, groupBy)].map(([group, groupItems]) => (_jsx(Commands.Group, { children: renderGroup(group, groupItems) }, group))) }));
|
|
356
|
+
};
|
|
357
|
+
export const ComposerCommandGroupLabel = ({ className, render, style, ...elementProps }) => useRenderElement("div", { className, render, style }, { props: [{ "data-composer-command-group-label": "" }, elementProps] });
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PrimitiveProps } from "../internal/primitive-props.js";
|
|
2
|
+
type CommandsRootProps = PrimitiveProps<"div">;
|
|
3
|
+
type CommandsGroupProps = PrimitiveProps<"div">;
|
|
4
|
+
type CommandsGroupLabelProps = PrimitiveProps<"div">;
|
|
5
|
+
export type CommandsItemState = {
|
|
6
|
+
/** Present as data-highlighted while keyboard/hover highlighted. */
|
|
7
|
+
highlighted: boolean;
|
|
8
|
+
/** Present as data-disabled while the row is inert. */
|
|
9
|
+
disabled: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type CommandsItemProps = PrimitiveProps<"button", CommandsItemState> & {
|
|
12
|
+
highlighted?: boolean;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
};
|
|
15
|
+
type CommandsItemLabelProps = PrimitiveProps<"span">;
|
|
16
|
+
type CommandsItemDescriptionProps = PrimitiveProps<"span">;
|
|
17
|
+
type CommandsEmptyProps = PrimitiveProps<"div">;
|
|
18
|
+
type CommandsFooterProps = PrimitiveProps<"div">;
|
|
19
|
+
export declare const Commands: (({ className, render, style, ...elementProps }: CommandsRootProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>) & {
|
|
20
|
+
Group: ({ className, render, style, ...elementProps }: CommandsGroupProps) => import("react").JSX.Element;
|
|
21
|
+
GroupLabel: ({ className, render, style, ...elementProps }: CommandsGroupLabelProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
22
|
+
Item: ({ highlighted, disabled, className, render, style, ...elementProps }: CommandsItemProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
23
|
+
ItemLabel: ({ className, render, style, ...elementProps }: CommandsItemLabelProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
24
|
+
ItemDescription: ({ className, render, style, ...elementProps }: CommandsItemDescriptionProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
25
|
+
Empty: ({ children, className, render, style, ...elementProps }: CommandsEmptyProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
26
|
+
Footer: ({ className, render, style, ...elementProps }: CommandsFooterProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
27
|
+
};
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
// Command-list structural primitives used internally by the composer's command
|
|
4
|
+
// palette (see command-list.tsx). Pure markup with part-attribute hooks — selection
|
|
5
|
+
// and keyboard behavior live in the composer's command plumbing; styling belongs
|
|
6
|
+
// to the styled layer. Not a public export.
|
|
7
|
+
import { createContext, use, useId } from "react";
|
|
8
|
+
import { useRenderElement } from "../internal/render/useRenderElement.js";
|
|
9
|
+
const CommandsRoot = ({ className, render, style, ...elementProps }) => useRenderElement("div", { className, render, style }, { props: [{ "data-command-list": "" }, elementProps] });
|
|
10
|
+
// Group → its label association: the group mints the id, the label stamps it.
|
|
11
|
+
// role="group" keeps listbox→option ownership intact through the wrapper div.
|
|
12
|
+
const GroupLabelIdContext = createContext("");
|
|
13
|
+
const CommandsGroup = ({ className, render, style, ...elementProps }) => {
|
|
14
|
+
const labelId = useId();
|
|
15
|
+
const element = useRenderElement("div", { className, render, style }, {
|
|
16
|
+
props: [
|
|
17
|
+
{ role: "group", "aria-labelledby": labelId, "data-command-group": "" },
|
|
18
|
+
elementProps,
|
|
19
|
+
],
|
|
20
|
+
});
|
|
21
|
+
return _jsx(GroupLabelIdContext, { value: labelId, children: element });
|
|
22
|
+
};
|
|
23
|
+
const CommandsGroupLabel = ({ className, render, style, ...elementProps }) => {
|
|
24
|
+
const labelId = use(GroupLabelIdContext);
|
|
25
|
+
return useRenderElement("div", { className, render, style }, { props: [{ id: labelId || undefined, "data-command-group-label": "" }, elementProps] });
|
|
26
|
+
};
|
|
27
|
+
const CommandsItem = ({ highlighted = false, disabled = false, className, render, style, ...elementProps }) => useRenderElement("button", { className, render, style }, {
|
|
28
|
+
state: { highlighted, disabled },
|
|
29
|
+
// Options, not buttons: keyboard selection stays in the editor
|
|
30
|
+
// (activedescendant pattern), so rows must never be tab stops or
|
|
31
|
+
// announce as "button". Disabled is aria-disabled, never the native
|
|
32
|
+
// attribute — a natively disabled button swallows the mousedown that
|
|
33
|
+
// keeps focus in the editor.
|
|
34
|
+
props: [
|
|
35
|
+
{
|
|
36
|
+
role: "option",
|
|
37
|
+
tabIndex: -1,
|
|
38
|
+
"aria-disabled": disabled || undefined,
|
|
39
|
+
"data-command-item": "",
|
|
40
|
+
},
|
|
41
|
+
elementProps,
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
const CommandsItemLabel = ({ className, render, style, ...elementProps }) => useRenderElement("span", { className, render, style }, { props: [{ "data-command-item-label": "" }, elementProps] });
|
|
45
|
+
const CommandsItemDescription = ({ className, render, style, ...elementProps }) => useRenderElement("span", { className, render, style }, { props: [{ "data-command-item-description": "" }, elementProps] });
|
|
46
|
+
const CommandsEmpty = ({ children, className, render, style, ...elementProps }) => useRenderElement("div", { className, render, style }, { props: [{ "data-command-empty": "", children }, elementProps] });
|
|
47
|
+
const CommandsFooter = ({ className, render, style, ...elementProps }) => useRenderElement("div", { className, render, style }, { props: [{ "data-command-footer": "" }, elementProps] });
|
|
48
|
+
export const Commands = Object.assign(CommandsRoot, {
|
|
49
|
+
Group: CommandsGroup,
|
|
50
|
+
GroupLabel: CommandsGroupLabel,
|
|
51
|
+
Item: CommandsItem,
|
|
52
|
+
ItemLabel: CommandsItemLabel,
|
|
53
|
+
ItemDescription: CommandsItemDescription,
|
|
54
|
+
Empty: CommandsEmpty,
|
|
55
|
+
Footer: CommandsFooter,
|
|
56
|
+
});
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { PrimitiveProps } from "../internal/primitive-props.js";
|
|
2
|
+
export type ComposerContainerProps = PrimitiveProps<"div">;
|
|
3
|
+
export declare const ComposerContainer: ({ className, render, style, ...elementProps }: ComposerContainerProps) => import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useRenderElement } from "../internal/render/useRenderElement.js";
|
|
3
|
+
import { useComposerContextStore } from "./store.js";
|
|
4
|
+
export const ComposerContainer = ({ className, render, style, ...elementProps }) => {
|
|
5
|
+
const store = useComposerContextStore();
|
|
6
|
+
const handleMouseDown = (event) => {
|
|
7
|
+
const target = event.target;
|
|
8
|
+
// The editor and interactive controls own their mouse behavior. Calling
|
|
9
|
+
// preventDefault on a mousedown inside the editable area would cancel the
|
|
10
|
+
// browser's text-selection drag, so only hijack focus when the click lands
|
|
11
|
+
// on the surrounding chrome.
|
|
12
|
+
if (target.closest("button, a, input, [data-composer-editor]")) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
event.preventDefault();
|
|
16
|
+
store.controller.ensureFocus();
|
|
17
|
+
};
|
|
18
|
+
return useRenderElement("div", { className, render, style }, {
|
|
19
|
+
// Register the element so Composer.Popover can observe it for repositioning.
|
|
20
|
+
ref: store.containerRef,
|
|
21
|
+
// No role/tabIndex: the chrome is a mouse-only focus passthrough, not a
|
|
22
|
+
// control — keyboard users tab straight to the editor, and an unnamed
|
|
23
|
+
// focusable "button" would only add a broken tab stop.
|
|
24
|
+
props: [
|
|
25
|
+
{
|
|
26
|
+
"data-composer-container": "",
|
|
27
|
+
onMouseDown: handleMouseDown,
|
|
28
|
+
},
|
|
29
|
+
elementProps,
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
// Imperative composer access from inside the tree. From outside it, the
|
|
3
|
+
// Composer.createStore() handle carries the same surface directly:
|
|
4
|
+
// store.controller.focus() — no hook, no context.
|
|
5
|
+
import { useComposerContextStore } from "./store.js";
|
|
6
|
+
export const useComposerController = () => useComposerContextStore().controller;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ChipData } from "../chip-markdown.js";
|
|
2
|
+
import { type SegmentDoc } from "./segments.js";
|
|
3
|
+
export type ReadableNode = {
|
|
4
|
+
nodeType: number;
|
|
5
|
+
nodeName: string;
|
|
6
|
+
/** Text node content (nodeType 3). */
|
|
7
|
+
data?: string;
|
|
8
|
+
childNodes: ArrayLike<ReadableNode>;
|
|
9
|
+
getAttribute?: (name: string) => string | null;
|
|
10
|
+
};
|
|
11
|
+
export declare const toReadable: (node: Node) => ReadableNode;
|
|
12
|
+
export declare const readDocumentFromDom: (root: ReadableNode, resolveChip: (id: string) => ChipData | null, makeId?: () => string) => {
|
|
13
|
+
doc: SegmentDoc;
|
|
14
|
+
dirty: boolean;
|
|
15
|
+
};
|
|
16
|
+
/** Logical position of (node, offset), or null when the point isn't inside root. */
|
|
17
|
+
export declare const logicalRangeFromDom: (root: ReadableNode, targetNode: ReadableNode, targetOffset: number) => number | null;
|
|
18
|
+
export type DomNodeSpec = {
|
|
19
|
+
kind: "text";
|
|
20
|
+
text: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: "br";
|
|
23
|
+
padding: boolean;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "chip";
|
|
26
|
+
id: string;
|
|
27
|
+
};
|
|
28
|
+
export declare const documentToDomSpec: (doc: SegmentDoc) => DomNodeSpec[];
|
|
29
|
+
/**
|
|
30
|
+
* Render the canonical child list. Existing chip spans are reused by id —
|
|
31
|
+
* moving a node preserves its React portal; recreating it would remount the
|
|
32
|
+
* chip content. Returns the live id → element map for the portal layer.
|
|
33
|
+
*/
|
|
34
|
+
export declare const renderDocumentToDom: (root: HTMLElement, doc: SegmentDoc) => Map<string, HTMLElement>;
|
|
35
|
+
/** DOM point for a logical position, preferring text-node points. */
|
|
36
|
+
export declare const domPointFromLogical: (root: HTMLElement, position: number) => {
|
|
37
|
+
node: Node;
|
|
38
|
+
offset: number;
|
|
39
|
+
};
|
|
40
|
+
export declare const readSelectionRange: (root: HTMLElement) => {
|
|
41
|
+
start: number;
|
|
42
|
+
end: number;
|
|
43
|
+
} | null;
|
|
44
|
+
export declare const writeCaretToDom: (root: HTMLElement, start: number, end?: number) => void;
|
|
45
|
+
export type BadgeToken = {
|
|
46
|
+
start: number;
|
|
47
|
+
end: number;
|
|
48
|
+
showPlaceholder: boolean;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Make the DOM match the token (or its absence). Returns true when the DOM
|
|
52
|
+
* changed — the caller must restore the caret afterwards.
|
|
53
|
+
*/
|
|
54
|
+
export declare const syncBadge: (root: HTMLElement, token: BadgeToken | null) => boolean;
|
|
55
|
+
export declare const scrollCaretIntoView: (root: HTMLElement) => void;
|