@runtypelabs/persona 4.7.0 → 4.9.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/README.md +1 -0
- package/dist/animations/glyph-cycle.cjs +2 -2
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/glyph-cycle.js +2 -2
- package/dist/animations/{types-CSmiKRVa.d.cts → types-4ROVJ1gA.d.cts} +85 -3
- package/dist/animations/{types-CSmiKRVa.d.ts → types-4ROVJ1gA.d.ts} +85 -3
- package/dist/animations/wipe.cjs +2 -2
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/chunk-5EIIHQLQ.js +1 -0
- package/dist/chunk-IO5VVUKP.js +3 -0
- package/dist/chunk-IPVK3KOM.js +1 -0
- package/dist/chunk-UPO4GUFC.js +1 -0
- package/dist/codegen.cjs +13 -13
- package/dist/codegen.js +14 -14
- package/dist/context-mentions-7S5KVUTG.js +169 -0
- package/dist/context-mentions-inline-TTCN7ZM2.js +4 -0
- package/dist/context-mentions-inline.cjs +4 -0
- package/dist/context-mentions-inline.d.cts +203 -0
- package/dist/context-mentions-inline.d.ts +203 -0
- package/dist/context-mentions-inline.js +4 -0
- package/dist/context-mentions.cjs +295 -0
- package/dist/context-mentions.d.cts +7025 -0
- package/dist/context-mentions.d.ts +7025 -0
- package/dist/context-mentions.js +295 -0
- package/dist/index.cjs +99 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1507 -182
- package/dist/index.d.ts +1507 -182
- package/dist/index.global.js +88 -56
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +99 -68
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/launcher.global.js +3 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/markdown-parsers.js +24 -24
- package/dist/plugin-kit.cjs +1 -1
- package/dist/plugin-kit.d.cts +17 -0
- package/dist/plugin-kit.d.ts +17 -0
- package/dist/plugin-kit.js +1 -1
- package/dist/runtype-tts-entry-UJAEF7NZ.js +1 -0
- package/dist/runtype-tts.js +1 -1
- package/dist/session-reconnect-JKIJBHS5.js +1 -0
- package/dist/smart-dom-reader.cjs +18 -17
- package/dist/smart-dom-reader.d.cts +1260 -16
- package/dist/smart-dom-reader.d.ts +1260 -16
- package/dist/smart-dom-reader.js +18 -17
- package/dist/testing.cjs +3 -3
- package/dist/testing.js +3 -3
- package/dist/theme-editor-preview.cjs +260 -58
- package/dist/theme-editor-preview.d.cts +1234 -15
- package/dist/theme-editor-preview.d.ts +1234 -15
- package/dist/theme-editor-preview.js +87 -58
- package/dist/theme-editor.cjs +10 -10
- package/dist/theme-editor.d.cts +1226 -15
- package/dist/theme-editor.d.ts +1226 -15
- package/dist/theme-editor.js +10 -10
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +76 -0
- package/dist/theme-reference.d.ts +76 -0
- package/dist/theme-reference.js +1 -1
- package/dist/voice-worklet-player.cjs +2 -2
- package/dist/voice-worklet-player.js +2 -2
- package/dist/webmcp-polyfill.js +2 -2
- package/dist/widget.css +1 -1
- package/package.json +16 -5
- package/src/artifacts-session.test.ts +178 -0
- package/src/client.test.ts +255 -1
- package/src/client.ts +157 -60
- package/src/components/artifact-card.test.ts +333 -0
- package/src/components/artifact-card.ts +75 -28
- package/src/components/artifact-inline.test.ts +1328 -0
- package/src/components/artifact-inline.ts +920 -0
- package/src/components/artifact-pane.test.ts +1042 -0
- package/src/components/artifact-pane.ts +440 -131
- package/src/components/artifact-preview.test.ts +1155 -0
- package/src/components/artifact-preview.ts +994 -0
- package/src/components/composer-parts.ts +3 -12
- package/src/components/context-mention-button.test.ts +70 -0
- package/src/components/context-mention-button.ts +82 -0
- package/src/components/context-mention-chip.ts +134 -0
- package/src/components/context-mention-menu.test.ts +508 -0
- package/src/components/context-mention-menu.ts +0 -0
- package/src/components/message-bubble.test.ts +175 -0
- package/src/components/message-bubble.ts +177 -19
- package/src/components/panel.ts +7 -10
- package/src/components/pill-composer-builder.test.ts +6 -2
- package/src/components/pill-composer-builder.ts +6 -6
- package/src/components/reasoning-bubble.ts +1 -13
- package/src/components/registry.ts +38 -3
- package/src/components/tool-bubble.ts +1 -13
- package/src/context-mentions-bundle.test.ts +163 -0
- package/src/context-mentions-entry.ts +185 -0
- package/src/context-mentions-inline-entry.test.ts +136 -0
- package/src/context-mentions-inline-entry.ts +226 -0
- package/src/context-mentions-inline-loader.test.ts +30 -0
- package/src/context-mentions-inline-loader.ts +36 -0
- package/src/context-mentions-inline.ts +15 -0
- package/src/context-mentions-loader.ts +32 -0
- package/src/context-mentions.ts +16 -0
- package/src/defaults.ts +1 -0
- package/src/generated/runtype-openapi-contract.ts +55 -3
- package/src/index-core.ts +46 -1
- package/src/index-global.ts +51 -0
- package/src/index.ts +8 -0
- package/src/markdown-parsers-loader.test.ts +158 -0
- package/src/markdown-parsers-loader.ts +83 -27
- package/src/plugin-kit.test.ts +40 -0
- package/src/plugin-kit.ts +39 -5
- package/src/runtime/host-layout.test.ts +163 -0
- package/src/runtime/host-layout.ts +110 -7
- package/src/runtime/init.ts +18 -61
- package/src/session.mentions.test.ts +175 -0
- package/src/session.test.ts +52 -4
- package/src/session.ts +197 -27
- package/src/smart-dom-reader.test.ts +129 -2
- package/src/smart-dom-reader.ts +127 -1
- package/src/styles/context-mention-menu-css.ts +176 -0
- package/src/styles/widget.css +1002 -152
- package/src/theme-editor/preview.ts +2 -0
- package/src/theme-editor/sections.test.ts +26 -1
- package/src/theme-editor/sections.ts +13 -5
- package/src/theme-reference.ts +2 -2
- package/src/tool-call-display-defaults.test.ts +1 -0
- package/src/types/theme.ts +79 -0
- package/src/types.ts +1041 -17
- package/src/ui.artifact-pane-gating.test.ts +636 -0
- package/src/ui.component-directive.test.ts +104 -0
- package/src/ui.composer-bar.test.ts +60 -2
- package/src/ui.detached-panel.test.ts +1049 -0
- package/src/ui.mention-submit.test.ts +235 -0
- package/src/ui.tool-display.test.ts +51 -0
- package/src/ui.ts +998 -159
- package/src/utils/artifact-custom-actions.ts +128 -0
- package/src/utils/artifact-display.test.ts +42 -0
- package/src/utils/artifact-display.ts +84 -0
- package/src/utils/artifact-file.test.ts +116 -0
- package/src/utils/artifact-file.ts +117 -0
- package/src/utils/artifact-gate.test.ts +112 -5
- package/src/utils/artifact-gate.ts +39 -14
- package/src/utils/artifact-loading-status.ts +55 -0
- package/src/utils/artifact-status-label.ts +190 -0
- package/src/utils/buttons.ts +7 -1
- package/src/utils/chunk-loader.test.ts +97 -0
- package/src/utils/chunk-loader.ts +88 -0
- package/src/utils/code-highlight.test.ts +186 -0
- package/src/utils/code-highlight.ts +400 -0
- package/src/utils/composer-contenteditable.test.ts +507 -0
- package/src/utils/composer-contenteditable.ts +626 -0
- package/src/utils/composer-document.test.ts +280 -0
- package/src/utils/composer-document.ts +293 -0
- package/src/utils/composer-history.test.ts +35 -7
- package/src/utils/composer-history.ts +30 -20
- package/src/utils/composer-input.ts +159 -0
- package/src/utils/context-mention-controller.test.ts +1215 -0
- package/src/utils/context-mention-controller.ts +1186 -0
- package/src/utils/context-mention-manager.test.ts +422 -0
- package/src/utils/context-mention-manager.ts +410 -0
- package/src/utils/context-mention-orchestrator.test.ts +538 -0
- package/src/utils/context-mention-orchestrator.ts +348 -0
- package/src/utils/icons.ts +2 -0
- package/src/utils/live-region.test.ts +108 -0
- package/src/utils/live-region.ts +94 -0
- package/src/utils/mention-channels.ts +63 -0
- package/src/utils/mention-llm-format.test.ts +91 -0
- package/src/utils/mention-llm-format.ts +79 -0
- package/src/utils/mention-matcher.test.ts +86 -0
- package/src/utils/mention-matcher.ts +221 -0
- package/src/utils/mention-token.ts +72 -0
- package/src/utils/mention-trigger.test.ts +155 -0
- package/src/utils/mention-trigger.ts +156 -0
- package/src/utils/roving-tablist.test.ts +152 -0
- package/src/utils/roving-tablist.ts +111 -0
- package/src/utils/spinner.ts +45 -0
- package/src/utils/theme.test.ts +76 -4
- package/src/utils/theme.ts +7 -0
- package/src/utils/tokens.ts +118 -11
- package/src/utils/tool-loading-animation.test.ts +32 -0
- package/src/utils/tool-loading-animation.ts +24 -0
- package/dist/chunk-DFBSCFYN.js +0 -1
- package/dist/runtype-tts-entry-HFUV2UF7.js +0 -1
- package/dist/session-reconnect-U77QFUR7.js +0 -1
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context-mentions runtime entry — the HEAVY module that assembles the menu
|
|
3
|
+
* controller + chip manager. It is NEVER statically imported on the core path:
|
|
4
|
+
* the IIFE/CDN build marks it external (`tsup.global.config.ts`) and loads the
|
|
5
|
+
* standalone `context-mentions.js` chunk from a sibling URL only when
|
|
6
|
+
* `contextMentions.enabled` and the user first interacts. The core orchestrator
|
|
7
|
+
* (`utils/context-mention-orchestrator.ts`) reaches it through
|
|
8
|
+
* `context-mentions-loader.ts`. See `docs/context-mentions-plan.md`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { ContextMentionManager } from "./utils/context-mention-manager";
|
|
12
|
+
import {
|
|
13
|
+
ContextMentionController,
|
|
14
|
+
type InlineCommandResult,
|
|
15
|
+
} from "./utils/context-mention-controller";
|
|
16
|
+
import {
|
|
17
|
+
createTextareaComposerInput,
|
|
18
|
+
type ComposerInputCapability,
|
|
19
|
+
} from "./utils/composer-input";
|
|
20
|
+
import { createLiveRegion } from "./utils/live-region";
|
|
21
|
+
import type { MentionSubmitBundle } from "./utils/context-mention-manager";
|
|
22
|
+
import type {
|
|
23
|
+
AgentWidgetConfig,
|
|
24
|
+
AgentWidgetContextMentionConfig,
|
|
25
|
+
AgentWidgetContextMentionRef,
|
|
26
|
+
AgentWidgetMessage,
|
|
27
|
+
} from "./types";
|
|
28
|
+
|
|
29
|
+
export interface ContextMentionMountContext {
|
|
30
|
+
mentionConfig: AgentWidgetContextMentionConfig;
|
|
31
|
+
textarea: HTMLTextAreaElement;
|
|
32
|
+
/**
|
|
33
|
+
* Pre-built composer input surface. Provided by the inline chunk in
|
|
34
|
+
* `display: "inline"` mode (a contenteditable adapter that replaced the
|
|
35
|
+
* textarea); omitted in chip mode, where a textarea adapter is built here.
|
|
36
|
+
*/
|
|
37
|
+
composerInput?: ComposerInputCapability;
|
|
38
|
+
/** Popover anchor — the composer form/pill (menu opens upward, full width). */
|
|
39
|
+
anchor: HTMLElement;
|
|
40
|
+
/** Chip row created by the core orchestrator. */
|
|
41
|
+
contextRow: HTMLElement;
|
|
42
|
+
getMessages: () => AgentWidgetMessage[];
|
|
43
|
+
getConfig: () => AgentWidgetConfig;
|
|
44
|
+
/**
|
|
45
|
+
* Widget container that hosts the mention live regions (created here, on
|
|
46
|
+
* engine mount, so the helper stays out of the core bundle). Announcements
|
|
47
|
+
* can't fire before the engine exists, so lazy creation loses nothing.
|
|
48
|
+
*/
|
|
49
|
+
liveRegionHost: HTMLElement | ShadowRoot;
|
|
50
|
+
popoverContainer?: HTMLElement | ShadowRoot;
|
|
51
|
+
/**
|
|
52
|
+
* Reflect the affordance-button picker's open state onto the button
|
|
53
|
+
* (`aria-expanded`/`aria-controls`). See the controller option of the same name.
|
|
54
|
+
*/
|
|
55
|
+
onPickerOpenChange?: (open: boolean, trigger: string, listboxId: string) => void;
|
|
56
|
+
/** Emit a `persona:mention:<event>` analytics DOM event. */
|
|
57
|
+
emit?: (event: string, detail: unknown) => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ContextMentionEngine {
|
|
61
|
+
/**
|
|
62
|
+
* Open the menu from an affordance button as a picker (no char inserted).
|
|
63
|
+
* Pass the channel's `trigger` to open a specific channel (e.g. `"/"`).
|
|
64
|
+
*/
|
|
65
|
+
openMenu(trigger?: string): void;
|
|
66
|
+
isMenuOpen(): boolean;
|
|
67
|
+
/** Re-parse the textarea on input and open/update/close the menu. */
|
|
68
|
+
handleInput(): void;
|
|
69
|
+
/** Returns true when the key was consumed. */
|
|
70
|
+
handleKeydown(event: KeyboardEvent): boolean;
|
|
71
|
+
hasMentions(): boolean;
|
|
72
|
+
removeLastChip(): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Dispatch a leading inline slash command in the composer `text` at submit
|
|
75
|
+
* (Slack-style). Returns null when `text` isn't an inline command. See
|
|
76
|
+
* {@link InlineCommandResult}.
|
|
77
|
+
*/
|
|
78
|
+
dispatchInlineCommand(text: string): Promise<InlineCommandResult | null>;
|
|
79
|
+
collectForSubmit():
|
|
80
|
+
| { refs: AgentWidgetContextMentionRef[]; finalize: () => Promise<MentionSubmitBundle> }
|
|
81
|
+
| null;
|
|
82
|
+
/**
|
|
83
|
+
* INLINE display only. Stop tracking a mention whose token the user deleted from
|
|
84
|
+
* the composer (aborts its in-flight resolve). Wired to the contenteditable
|
|
85
|
+
* adapter's removal detection by the orchestrator.
|
|
86
|
+
*/
|
|
87
|
+
untrackMention(id: string): void;
|
|
88
|
+
/**
|
|
89
|
+
* Rebind the engine to a new composer input surface (the inline swap landing
|
|
90
|
+
* while the engine is already live). Only the menu layer (controller) re-mounts
|
|
91
|
+
* on the new element — the manager, with any committed chips and their
|
|
92
|
+
* in-flight resolves, survives, so a mention picked pre-swap still finalizes
|
|
93
|
+
* into the submit payload. Any open menu closes. No-op when already bound.
|
|
94
|
+
*/
|
|
95
|
+
rebindComposer(input: ComposerInputCapability): void;
|
|
96
|
+
clear(): void;
|
|
97
|
+
destroy(): void;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function mountContextMentions(
|
|
101
|
+
ctx: ContextMentionMountContext
|
|
102
|
+
): ContextMentionEngine {
|
|
103
|
+
// Inline mode supplies a pre-built contenteditable capability; chip mode builds
|
|
104
|
+
// a textarea-backed one here. Mutable: `rebindComposer` swaps it when the inline
|
|
105
|
+
// composer replaces the textarea while the engine is already live — closures
|
|
106
|
+
// below read it at call time so they always drive the current surface.
|
|
107
|
+
let composerInput =
|
|
108
|
+
ctx.composerInput ?? createTextareaComposerInput(ctx.textarea);
|
|
109
|
+
|
|
110
|
+
// Polite region for result-count + add/remove announcements; assertive region
|
|
111
|
+
// for resolve failures. Hosted via the shared helper so they land in the light
|
|
112
|
+
// DOM when the widget renders inside a Shadow DOM (see live-region.ts).
|
|
113
|
+
const liveRegion = createLiveRegion("polite", ctx.liveRegionHost);
|
|
114
|
+
const errorRegion = createLiveRegion("assertive", ctx.liveRegionHost);
|
|
115
|
+
const announce = (message: string): void => liveRegion.announce(message);
|
|
116
|
+
const announceError = (message: string): void => errorRegion.announce(message);
|
|
117
|
+
|
|
118
|
+
const manager = new ContextMentionManager({
|
|
119
|
+
mentionConfig: ctx.mentionConfig,
|
|
120
|
+
contextRow: ctx.contextRow,
|
|
121
|
+
getMessages: ctx.getMessages,
|
|
122
|
+
getConfig: ctx.getConfig,
|
|
123
|
+
getComposerText: () => composerInput.getValue(),
|
|
124
|
+
announce,
|
|
125
|
+
announceError,
|
|
126
|
+
emit: ctx.emit,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const buildController = (): ContextMentionController =>
|
|
130
|
+
new ContextMentionController({
|
|
131
|
+
mentionConfig: ctx.mentionConfig,
|
|
132
|
+
composerInput,
|
|
133
|
+
anchor: ctx.anchor,
|
|
134
|
+
getMessages: ctx.getMessages,
|
|
135
|
+
getConfig: ctx.getConfig,
|
|
136
|
+
onSelect: (source, item, args) => manager.add(source, item, args),
|
|
137
|
+
// Inline commit: the controller inserts the token, then tracks its resolve
|
|
138
|
+
// keyed by the composer id. `reportStatus` reflects the resolve outcome onto
|
|
139
|
+
// the live token element (error styling — inline tokens carry no chip).
|
|
140
|
+
onInsertMention: (id, source, item, args) =>
|
|
141
|
+
manager.track(id, source, item, args, (status) =>
|
|
142
|
+
composerInput.setMentionStatus?.(id, status)
|
|
143
|
+
),
|
|
144
|
+
admitMention: (source, item) => manager.admit(source, item),
|
|
145
|
+
announce,
|
|
146
|
+
popoverContainer: ctx.popoverContainer,
|
|
147
|
+
onPickerOpenChange: ctx.onPickerOpenChange,
|
|
148
|
+
emit: ctx.emit,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
let controller = buildController();
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
openMenu: (trigger) => controller.openFromButton(trigger),
|
|
155
|
+
isMenuOpen: () => controller.isOpen(),
|
|
156
|
+
handleInput: () => controller.onInput(),
|
|
157
|
+
handleKeydown: (event) => controller.handleKeydown(event),
|
|
158
|
+
hasMentions: () => manager.hasMentions(),
|
|
159
|
+
removeLastChip: () => manager.removeLast(),
|
|
160
|
+
dispatchInlineCommand: (text) => controller.dispatchInlineCommand(text),
|
|
161
|
+
collectForSubmit: () =>
|
|
162
|
+
manager.hasMentions() ? manager.collectForSubmit() : null,
|
|
163
|
+
untrackMention: (id) => manager.remove(id),
|
|
164
|
+
rebindComposer: (input) => {
|
|
165
|
+
if (input === composerInput) return;
|
|
166
|
+
// Re-mount ONLY the menu layer on the new surface. The manager — committed
|
|
167
|
+
// chips and their in-flight resolves — must survive: a mention picked
|
|
168
|
+
// pre-swap stays a valid chip post-swap (tokens can't be retro-inserted
|
|
169
|
+
// into text the user already typed) and still finalizes at submit.
|
|
170
|
+
controller.destroy();
|
|
171
|
+
composerInput = input;
|
|
172
|
+
controller = buildController();
|
|
173
|
+
},
|
|
174
|
+
clear: () => manager.clear(),
|
|
175
|
+
destroy: () => {
|
|
176
|
+
controller.destroy();
|
|
177
|
+
manager.clear();
|
|
178
|
+
liveRegion.destroy();
|
|
179
|
+
errorRegion.destroy();
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export type { MentionSubmitBundle };
|
|
185
|
+
export type { InlineCommandResult };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, vi } from "vitest";
|
|
4
|
+
import { mountInlineComposer } from "./context-mentions-inline-entry";
|
|
5
|
+
import type { AgentWidgetContextMentionRef } from "./types";
|
|
6
|
+
|
|
7
|
+
const renderToken = (ref: AgentWidgetContextMentionRef): HTMLElement => {
|
|
8
|
+
const el = document.createElement("span");
|
|
9
|
+
el.textContent = `@${ref.label}`;
|
|
10
|
+
return el;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const makeTextarea = (): HTMLTextAreaElement => {
|
|
14
|
+
const ta = document.createElement("textarea");
|
|
15
|
+
document.body.appendChild(ta);
|
|
16
|
+
return ta;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const appRef: AgentWidgetContextMentionRef = {
|
|
20
|
+
sourceId: "files",
|
|
21
|
+
itemId: "app",
|
|
22
|
+
label: "App.tsx",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe("mountInlineComposer", () => {
|
|
26
|
+
it("mirrors the textarea's inline styles onto the contenteditable (no visual pop)", () => {
|
|
27
|
+
const ta = makeTextarea();
|
|
28
|
+
ta.className = "persona-composer-textarea";
|
|
29
|
+
ta.style.maxHeight = "60px";
|
|
30
|
+
ta.style.overflowY = "auto";
|
|
31
|
+
ta.style.fontWeight = "500";
|
|
32
|
+
ta.setAttribute("placeholder", "Ask about a file…");
|
|
33
|
+
|
|
34
|
+
const handle = mountInlineComposer({ textarea: ta, renderToken });
|
|
35
|
+
const el = handle.element;
|
|
36
|
+
|
|
37
|
+
expect(el.style.maxHeight).toBe("60px");
|
|
38
|
+
expect(el.style.overflowY).toBe("auto");
|
|
39
|
+
expect(el.style.fontWeight).toBe("500");
|
|
40
|
+
expect(el.className).toContain("persona-composer-textarea");
|
|
41
|
+
// Placeholder + accessible name mirror across.
|
|
42
|
+
expect(el.getAttribute("data-placeholder")).toBe("Ask about a file…");
|
|
43
|
+
expect(el.getAttribute("aria-label")).toBe("Ask about a file…");
|
|
44
|
+
handle.destroy();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("maps the shimmed `placeholder` property onto data-placeholder", () => {
|
|
48
|
+
const ta = makeTextarea();
|
|
49
|
+
const handle = mountInlineComposer({ textarea: ta, renderToken });
|
|
50
|
+
const el = handle.element as unknown as HTMLTextAreaElement;
|
|
51
|
+
|
|
52
|
+
el.placeholder = "Type here";
|
|
53
|
+
expect(handle.element.getAttribute("data-placeholder")).toBe("Type here");
|
|
54
|
+
expect(handle.element.getAttribute("aria-label")).toBe("Type here");
|
|
55
|
+
expect(el.placeholder).toBe("Type here");
|
|
56
|
+
handle.destroy();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("preserves an explicit host aria-label when placeholder updates land after swap", () => {
|
|
60
|
+
const ta = makeTextarea();
|
|
61
|
+
ta.setAttribute("aria-label", "Message support");
|
|
62
|
+
ta.setAttribute("placeholder", "Ask about a file…");
|
|
63
|
+
|
|
64
|
+
const handle = mountInlineComposer({ textarea: ta, renderToken });
|
|
65
|
+
const el = handle.element as unknown as HTMLTextAreaElement;
|
|
66
|
+
expect(handle.element.getAttribute("aria-label")).toBe("Message support");
|
|
67
|
+
|
|
68
|
+
// ui.ts updateCopy assigns truthy placeholder text on every config update;
|
|
69
|
+
// the host's accessible name must survive it.
|
|
70
|
+
el.placeholder = "Ask about a file…";
|
|
71
|
+
expect(handle.element.getAttribute("aria-label")).toBe("Message support");
|
|
72
|
+
expect(handle.element.getAttribute("data-placeholder")).toBe("Ask about a file…");
|
|
73
|
+
handle.destroy();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("keeps a placeholder-derived aria-label in sync and clears it on empty", () => {
|
|
77
|
+
const ta = makeTextarea();
|
|
78
|
+
ta.setAttribute("placeholder", "Type a message");
|
|
79
|
+
const handle = mountInlineComposer({ textarea: ta, renderToken });
|
|
80
|
+
const el = handle.element as unknown as HTMLTextAreaElement;
|
|
81
|
+
expect(handle.element.getAttribute("aria-label")).toBe("Type a message");
|
|
82
|
+
|
|
83
|
+
el.placeholder = "First update";
|
|
84
|
+
expect(handle.element.getAttribute("aria-label")).toBe("First update");
|
|
85
|
+
el.placeholder = "Second update";
|
|
86
|
+
expect(handle.element.getAttribute("aria-label")).toBe("Second update");
|
|
87
|
+
|
|
88
|
+
// Empty placeholder leaves no stale accessible name behind.
|
|
89
|
+
el.placeholder = "";
|
|
90
|
+
expect(handle.element.getAttribute("aria-label")).toBeNull();
|
|
91
|
+
handle.destroy();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("exposes ordered contentSegments via getInlineMessageFields", () => {
|
|
95
|
+
const ta = makeTextarea();
|
|
96
|
+
const handle = mountInlineComposer({ textarea: ta, renderToken });
|
|
97
|
+
handle.input.setValueWithCaret("Check ", 6);
|
|
98
|
+
handle.input.insertMentionAtSelection!(appRef);
|
|
99
|
+
|
|
100
|
+
const fields = (
|
|
101
|
+
handle.element as unknown as {
|
|
102
|
+
getInlineMessageFields: () => {
|
|
103
|
+
contentSegments: Array<{
|
|
104
|
+
kind: string;
|
|
105
|
+
text?: string;
|
|
106
|
+
ref?: { label?: string };
|
|
107
|
+
}>;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
).getInlineMessageFields();
|
|
111
|
+
|
|
112
|
+
const kinds = fields.contentSegments.map((s) => s.kind);
|
|
113
|
+
expect(kinds).toContain("mention");
|
|
114
|
+
const mention = fields.contentSegments.find((s) => s.kind === "mention");
|
|
115
|
+
expect(mention?.ref?.label).toBe("App.tsx");
|
|
116
|
+
handle.destroy();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("untracks tokens when the shim value setter clears the composer", () => {
|
|
120
|
+
const ta = makeTextarea();
|
|
121
|
+
const onMentionRemoved = vi.fn();
|
|
122
|
+
const handle = mountInlineComposer({
|
|
123
|
+
textarea: ta,
|
|
124
|
+
renderToken,
|
|
125
|
+
onMentionRemoved,
|
|
126
|
+
});
|
|
127
|
+
const el = handle.element as unknown as HTMLTextAreaElement;
|
|
128
|
+
const id = handle.input.insertMentionAtSelection!(appRef);
|
|
129
|
+
|
|
130
|
+
// Programmatic `value =` (voice/clear path) drops the token DOM; the shim must
|
|
131
|
+
// fire the adapter's reconcile so the manager aborts that mention's resolve.
|
|
132
|
+
el.value = "";
|
|
133
|
+
expect(onMentionRemoved).toHaveBeenCalledWith(id);
|
|
134
|
+
handle.destroy();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline-mention chunk entry — the HEAVY, DOM-bearing half of inline mode. Loaded
|
|
3
|
+
* on composer mount when `contextMentions.display === "inline"` and NEVER
|
|
4
|
+
* statically imported on the core path (the IIFE build marks
|
|
5
|
+
* `@runtypelabs/persona/context-mentions-inline` external; ESM/CJS mark the same
|
|
6
|
+
* subpath external via `build:client`). This is the only place the
|
|
7
|
+
* contenteditable engine + `composer-document` enter the widget at runtime.
|
|
8
|
+
*
|
|
9
|
+
* `mountInlineComposer` builds a contenteditable `ComposerInputCapability` that
|
|
10
|
+
* visually mirrors the live `<textarea>` and migrates its current value + caret.
|
|
11
|
+
* It does NOT touch the DOM tree or rebind listeners — the caller (`ui.ts`) swaps
|
|
12
|
+
* the element in place and re-points the composer's event handlers, because those
|
|
13
|
+
* live in the core and the chunk must stay free of that wiring.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
createContentEditableComposerInput,
|
|
18
|
+
type ContentEditableComposerInputOptions,
|
|
19
|
+
} from "./utils/composer-contenteditable";
|
|
20
|
+
import { documentToMessageFields } from "./utils/composer-document";
|
|
21
|
+
import type { ComposerInputCapability } from "./utils/composer-input";
|
|
22
|
+
import type {
|
|
23
|
+
AgentWidgetContentSegment,
|
|
24
|
+
AgentWidgetContextMentionRef,
|
|
25
|
+
} from "./types";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Display/transcript fields a sent inline-mode message carries, derived from the
|
|
29
|
+
* live composer document. Computed in THIS chunk (it owns `composer-document.ts`
|
|
30
|
+
* at runtime) and read by the core `ui.ts` submit path via `getInlineMessageFields`
|
|
31
|
+
* below, so the core never imports the document model.
|
|
32
|
+
*/
|
|
33
|
+
export interface InlineComposerMessageFields {
|
|
34
|
+
content: string;
|
|
35
|
+
contextMentions: AgentWidgetContextMentionRef[];
|
|
36
|
+
contentSegments: AgentWidgetContentSegment[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The `<textarea>`-shaped element the inline composer swaps in. Beyond the
|
|
41
|
+
* textarea API shims it also exposes `getInlineMessageFields()` so the core
|
|
42
|
+
* submit path can read the ordered document segments without importing the
|
|
43
|
+
* document model. `ui.ts` references this via a structural (type-only) shape.
|
|
44
|
+
*/
|
|
45
|
+
export type InlineComposerElement = HTMLElement & {
|
|
46
|
+
getInlineMessageFields: () => InlineComposerMessageFields;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export interface InlineComposerMountContext {
|
|
50
|
+
/** The live composer textarea to migrate from (value + caret + look). */
|
|
51
|
+
textarea: HTMLTextAreaElement;
|
|
52
|
+
/**
|
|
53
|
+
* Build a token's full element. Supplied by the CORE orchestrator (which owns
|
|
54
|
+
* `createMentionTokenElement` + the shared icon renderer) so this lazy chunk
|
|
55
|
+
* never bundles the icon set — it already ships in core. The adapter marks the
|
|
56
|
+
* returned element `contenteditable="false"` and stamps `data-mention-id`.
|
|
57
|
+
*/
|
|
58
|
+
renderToken: (ref: AgentWidgetContextMentionRef) => HTMLElement;
|
|
59
|
+
/**
|
|
60
|
+
* Fired when a token is deleted from the composer (backspace/cut). The caller
|
|
61
|
+
* routes this to `engine.untrackMention(id)` so the manager aborts its resolve.
|
|
62
|
+
*/
|
|
63
|
+
onMentionRemoved?: (id: string) => void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface InlineComposerHandle {
|
|
67
|
+
/** The contenteditable input surface; hand this to `mountContextMentions`. */
|
|
68
|
+
input: ComposerInputCapability;
|
|
69
|
+
/** The contenteditable root element to swap in for the textarea. */
|
|
70
|
+
element: HTMLElement;
|
|
71
|
+
/** Detach the adapter's listeners on unmount. */
|
|
72
|
+
destroy: () => void;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let idSeq = 0;
|
|
76
|
+
/** Page-unique mention id (composer-scoped; only needs uniqueness within a doc). */
|
|
77
|
+
const generateId = (): string => `pmention-${++idSeq}`;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Copy the visual hooks that make the contenteditable read like the textarea it
|
|
81
|
+
* replaces. The composer sets most of its look via INLINE styles at runtime (font
|
|
82
|
+
* vars from config, the `maxHeight`/`overflowY` auto-resize cap, border/outline
|
|
83
|
+
* stripping — see `composer-parts.ts` and `ui.ts` `updateCopy`), not just utility
|
|
84
|
+
* classes, so copy every inline style property across as well; otherwise the
|
|
85
|
+
* contenteditable falls back to its CSS defaults (a taller max-height, a different
|
|
86
|
+
* font) and visibly pops on swap. Also mirror the classes, the placeholder, and
|
|
87
|
+
* the accessible name.
|
|
88
|
+
*/
|
|
89
|
+
function mirrorTextareaLook(
|
|
90
|
+
textarea: HTMLTextAreaElement,
|
|
91
|
+
el: HTMLElement
|
|
92
|
+
): void {
|
|
93
|
+
el.className = `${textarea.className} persona-composer-contenteditable`.trim();
|
|
94
|
+
// Copy every inline style property (font*, lineHeight, maxHeight, overflowY,
|
|
95
|
+
// border, outline, background, padding, color, …) so the box model + typography
|
|
96
|
+
// match exactly. Iterating keeps this in sync as the composer's inline styles
|
|
97
|
+
// evolve, without re-enumerating a fixed property list here. Skip `height`: the
|
|
98
|
+
// textarea's auto-resize sets it to a fixed px on input, but the contenteditable
|
|
99
|
+
// grows with its content (capped by the copied `maxHeight`/`overflowY`), so a
|
|
100
|
+
// frozen height would clip it.
|
|
101
|
+
for (let i = 0; i < textarea.style.length; i++) {
|
|
102
|
+
const prop = textarea.style.item(i);
|
|
103
|
+
if (prop === "height") continue;
|
|
104
|
+
el.style.setProperty(
|
|
105
|
+
prop,
|
|
106
|
+
textarea.style.getPropertyValue(prop),
|
|
107
|
+
textarea.style.getPropertyPriority(prop)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const placeholder = textarea.getAttribute("placeholder");
|
|
111
|
+
if (placeholder) {
|
|
112
|
+
el.setAttribute("data-placeholder", placeholder);
|
|
113
|
+
// Carry the placeholder in aria-placeholder (its correct role) rather than
|
|
114
|
+
// overloading the accessible name with it.
|
|
115
|
+
el.setAttribute("aria-placeholder", placeholder);
|
|
116
|
+
}
|
|
117
|
+
// Stable accessible name: prefer an explicit host/config aria-label on the
|
|
118
|
+
// textarea (ui.ts stamps config input copy there); fall back to the placeholder
|
|
119
|
+
// text for parity with chip mode (the plain textarea's name is its placeholder)
|
|
120
|
+
// so inline mode never regresses to an unnamed field.
|
|
121
|
+
const label = textarea.getAttribute("aria-label") ?? placeholder;
|
|
122
|
+
if (label) el.setAttribute("aria-label", label);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Make the contenteditable element quack like a `<textarea>` for the specific API
|
|
127
|
+
* surface the host composer code touches (`value`, `selectionStart/End`,
|
|
128
|
+
* `setSelectionRange`). This lets `ui.ts` keep reading/writing the composer the
|
|
129
|
+
* same way after the swap without threading the capability through every call
|
|
130
|
+
* site (doSubmit clears `value`; history sets `value` + caret; the send gate
|
|
131
|
+
* reads `value`). Everything is delegated to the capability so the document stays
|
|
132
|
+
* the source of truth. NOTE: `value` writes render PLAIN TEXT (no tokens) — inline
|
|
133
|
+
* history recall is plain-text in v1; tokens survive only within a live session.
|
|
134
|
+
*/
|
|
135
|
+
function shimTextareaApi(
|
|
136
|
+
el: HTMLElement,
|
|
137
|
+
input: ComposerInputCapability,
|
|
138
|
+
// True when the host stamped an explicit aria-label on the textarea. Its
|
|
139
|
+
// accessible name is authoritative, so placeholder writes must not clobber it.
|
|
140
|
+
hasHostLabel: boolean
|
|
141
|
+
): void {
|
|
142
|
+
Object.defineProperty(el, "value", {
|
|
143
|
+
configurable: true,
|
|
144
|
+
get: () => input.getValue(),
|
|
145
|
+
set: (v: string) => {
|
|
146
|
+
input.setValueWithCaret(v, v.length);
|
|
147
|
+
// `setValueWithCaret` rebuilds the DOM as plain text (dropping any token
|
|
148
|
+
// spans) but does NOT itself run the adapter's removal reconcile. Fire an
|
|
149
|
+
// input event so vanished tokens untrack (the manager aborts their resolve)
|
|
150
|
+
// — otherwise a programmatic `value =` (voice transcript, clear, history
|
|
151
|
+
// recall) leaves stale resolved context tracked against the next send.
|
|
152
|
+
input.dispatchInput();
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
// The contenteditable reads its placeholder from `data-placeholder`, so a plain
|
|
156
|
+
// `.placeholder =` (e.g. `ui.ts` `updateCopy` after the swap, which assigns
|
|
157
|
+
// truthy placeholder text on every config update) would no-op. Map the textarea
|
|
158
|
+
// `placeholder` property onto the attribute. Only touch the accessible name when
|
|
159
|
+
// it is placeholder-derived — an explicit host aria-label must survive.
|
|
160
|
+
Object.defineProperty(el, "placeholder", {
|
|
161
|
+
configurable: true,
|
|
162
|
+
get: () => el.getAttribute("data-placeholder") ?? "",
|
|
163
|
+
set: (v: string) => {
|
|
164
|
+
if (v) {
|
|
165
|
+
el.setAttribute("data-placeholder", v);
|
|
166
|
+
el.setAttribute("aria-placeholder", v);
|
|
167
|
+
if (!hasHostLabel) el.setAttribute("aria-label", v);
|
|
168
|
+
} else {
|
|
169
|
+
el.removeAttribute("data-placeholder");
|
|
170
|
+
el.removeAttribute("aria-placeholder");
|
|
171
|
+
// Clear the placeholder-derived name too, so no stale label lingers.
|
|
172
|
+
if (!hasHostLabel) el.removeAttribute("aria-label");
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
Object.defineProperty(el, "selectionStart", {
|
|
177
|
+
configurable: true,
|
|
178
|
+
get: () => input.getSelection().start,
|
|
179
|
+
});
|
|
180
|
+
Object.defineProperty(el, "selectionEnd", {
|
|
181
|
+
configurable: true,
|
|
182
|
+
get: () => input.getSelection().end,
|
|
183
|
+
});
|
|
184
|
+
(el as unknown as HTMLTextAreaElement).setSelectionRange = (
|
|
185
|
+
start: number,
|
|
186
|
+
end: number
|
|
187
|
+
) => input.setSelection(start ?? 0, end ?? start ?? 0);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function mountInlineComposer(
|
|
191
|
+
ctx: InlineComposerMountContext
|
|
192
|
+
): InlineComposerHandle {
|
|
193
|
+
const { textarea } = ctx;
|
|
194
|
+
const options: ContentEditableComposerInputOptions = {
|
|
195
|
+
generateId,
|
|
196
|
+
// Token DOM (incl. icon + any host `renderMentionToken`) is built in core and
|
|
197
|
+
// passed in, so this chunk stays free of the icon renderer.
|
|
198
|
+
renderToken: ctx.renderToken,
|
|
199
|
+
onMentionRemoved: ctx.onMentionRemoved,
|
|
200
|
+
placeholder: textarea.getAttribute("placeholder") ?? undefined,
|
|
201
|
+
};
|
|
202
|
+
const capability = createContentEditableComposerInput(options);
|
|
203
|
+
const el = capability.element;
|
|
204
|
+
// Capture before mirroring/shimming: whether the accessible name is an explicit
|
|
205
|
+
// host label (authoritative) or placeholder-derived (kept in sync with copy).
|
|
206
|
+
const hasHostLabel = textarea.getAttribute("aria-label") != null;
|
|
207
|
+
mirrorTextareaLook(textarea, el);
|
|
208
|
+
shimTextareaApi(el, capability, hasHostLabel);
|
|
209
|
+
|
|
210
|
+
// Expose the ordered document as sent-message fields for the core submit path.
|
|
211
|
+
// Computed here (this chunk owns `composer-document.ts`) so `ui.ts` can attach
|
|
212
|
+
// `contentSegments` to the user message without importing the document model.
|
|
213
|
+
(el as InlineComposerElement).getInlineMessageFields = () =>
|
|
214
|
+
documentToMessageFields(capability.getDocument?.() ?? { blocks: [] });
|
|
215
|
+
|
|
216
|
+
// Migrate current text + caret. Anything typed before the swap is plain text
|
|
217
|
+
// (tokens can't exist yet — the menu chunk gates selection), so this is lossless.
|
|
218
|
+
const caret = textarea.selectionStart ?? textarea.value.length;
|
|
219
|
+
capability.setValueWithCaret(textarea.value, caret);
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
input: capability,
|
|
223
|
+
element: el,
|
|
224
|
+
destroy: capability.destroy,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
loadContextMentionsInline,
|
|
4
|
+
setContextMentionsInlineLoader,
|
|
5
|
+
type ContextMentionsInlineModule,
|
|
6
|
+
} from "./context-mentions-inline-loader";
|
|
7
|
+
|
|
8
|
+
const fakeModule = {
|
|
9
|
+
mountInlineComposer: vi.fn(),
|
|
10
|
+
} as unknown as ContextMentionsInlineModule;
|
|
11
|
+
|
|
12
|
+
describe("loadContextMentionsInline", () => {
|
|
13
|
+
it("retries after a rejected load instead of caching the failure forever", async () => {
|
|
14
|
+
const loader = vi
|
|
15
|
+
.fn<() => Promise<ContextMentionsInlineModule>>()
|
|
16
|
+
.mockRejectedValueOnce(new Error("network"))
|
|
17
|
+
.mockResolvedValueOnce(fakeModule);
|
|
18
|
+
setContextMentionsInlineLoader(loader);
|
|
19
|
+
|
|
20
|
+
// First call surfaces the rejection to the caller.
|
|
21
|
+
await expect(loadContextMentionsInline()).rejects.toThrow("network");
|
|
22
|
+
// A later call retries (the failed promise was not cached) and resolves.
|
|
23
|
+
await expect(loadContextMentionsInline()).resolves.toBe(fakeModule);
|
|
24
|
+
expect(loader).toHaveBeenCalledTimes(2);
|
|
25
|
+
|
|
26
|
+
// Once resolved, the module is cached (no third loader call).
|
|
27
|
+
await expect(loadContextMentionsInline()).resolves.toBe(fakeModule);
|
|
28
|
+
expect(loader).toHaveBeenCalledTimes(2);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader indirection for the lazy inline-mention chunk (core bundle, tiny).
|
|
3
|
+
*
|
|
4
|
+
* Sibling of `context-mentions-loader.ts`. The IIFE/CDN entry (`index-global.ts`)
|
|
5
|
+
* registers a loader that imports the standalone `context-mentions-inline.js`
|
|
6
|
+
* chunk from a sibling URL; ESM/CJS consumers fall back to the package's
|
|
7
|
+
* `./context-mentions-inline` subpath — which `build:client` marks external, so
|
|
8
|
+
* the contenteditable engine is NOT inlined into `dist/index.{js,cjs}` and stays
|
|
9
|
+
* out of the core bundle. (A relative `./context-mentions-inline-entry` import
|
|
10
|
+
* would be inlined by the no-splitting build, defeating the split.)
|
|
11
|
+
*
|
|
12
|
+
* This is loaded on composer mount when `contextMentions.display === "inline"`,
|
|
13
|
+
* distinct from the chip menu chunk which loads on the first `@`/click.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { createChunkLoader } from "./utils/chunk-loader";
|
|
17
|
+
import type {
|
|
18
|
+
InlineComposerMountContext,
|
|
19
|
+
InlineComposerHandle,
|
|
20
|
+
} from "./context-mentions-inline-entry";
|
|
21
|
+
|
|
22
|
+
export type ContextMentionsInlineModule = {
|
|
23
|
+
mountInlineComposer: (ctx: InlineComposerMountContext) => InlineComposerHandle;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Memoization + rejection-retry semantics live in `createChunkLoader`.
|
|
27
|
+
// `resetOnSetLoader` mirrors this loader's original behavior: registering a new
|
|
28
|
+
// loader source invalidates whatever the previous loader produced (so a
|
|
29
|
+
// mid-session swap for test injection takes effect deterministically).
|
|
30
|
+
const { setLoader, load } = createChunkLoader<ContextMentionsInlineModule>({
|
|
31
|
+
fallbackImport: () => import("@runtypelabs/persona/context-mentions-inline"),
|
|
32
|
+
resetOnSetLoader: true,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const setContextMentionsInlineLoader = setLoader;
|
|
36
|
+
export const loadContextMentionsInline = load;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry for the lazy inline-mention chunk (`dist/context-mentions-inline.js`).
|
|
3
|
+
*
|
|
4
|
+
* Loaded on composer mount when `contextMentions.display === "inline"`. Carries
|
|
5
|
+
* the contenteditable engine (`composer-document`, `composer-contenteditable`)
|
|
6
|
+
* so chip-only and feature-off sites never download it. See
|
|
7
|
+
* `context-mentions-inline-loader.ts` and the loader registration in
|
|
8
|
+
* `index-global.ts`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { mountInlineComposer } from "./context-mentions-inline-entry";
|
|
12
|
+
export type {
|
|
13
|
+
InlineComposerMountContext,
|
|
14
|
+
InlineComposerHandle,
|
|
15
|
+
} from "./context-mentions-inline-entry";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader indirection for the lazy context-mentions chunk (core bundle, tiny).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `markdown-parsers-loader.ts`: the IIFE/CDN entry (`index-global.ts`)
|
|
5
|
+
* registers a loader that imports the standalone `context-mentions.js` chunk
|
|
6
|
+
* from a sibling URL. ESM/CJS consumers fall back to importing the package's
|
|
7
|
+
* `./context-mentions` subpath — which `build:client` marks external, so the
|
|
8
|
+
* runtime is NOT inlined into `dist/index.{js,cjs}` and stays out of the core
|
|
9
|
+
* bundle until first use. (A relative `./context-mentions-entry` import would be
|
|
10
|
+
* inlined by the no-splitting build, defeating the whole point.)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createChunkLoader } from "./utils/chunk-loader";
|
|
14
|
+
import type {
|
|
15
|
+
ContextMentionMountContext,
|
|
16
|
+
ContextMentionEngine,
|
|
17
|
+
} from "./context-mentions-entry";
|
|
18
|
+
|
|
19
|
+
export type ContextMentionsModule = {
|
|
20
|
+
mountContextMentions: (ctx: ContextMentionMountContext) => ContextMentionEngine;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// IIFE/CDN: sibling-URL chunk via the registered loader.
|
|
24
|
+
// ESM/CJS fallback: the package's own `./context-mentions` subpath (external, so
|
|
25
|
+
// the runtime chunk is code-split out of dist/index.{js,cjs} rather than
|
|
26
|
+
// inlined). Memoization + rejection-retry semantics live in `createChunkLoader`.
|
|
27
|
+
const { setLoader, load } = createChunkLoader<ContextMentionsModule>({
|
|
28
|
+
fallbackImport: () => import("@runtypelabs/persona/context-mentions"),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const setContextMentionsLoader = setLoader;
|
|
32
|
+
export const loadContextMentions = load;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone context-mentions chunk (`dist/context-mentions.js`).
|
|
3
|
+
*
|
|
4
|
+
* The IIFE/CDN widget bundle marks `./context-mentions-entry` external so the
|
|
5
|
+
* mention runtime is excluded from the core payload; this file is built as a
|
|
6
|
+
* self-contained sibling chunk (`tsup.context-mentions.config.ts`) and loaded on
|
|
7
|
+
* demand by the loader registered in `index-global.ts`. ESM/CJS consumers import
|
|
8
|
+
* the entry directly. See `docs/context-mentions-plan.md`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { mountContextMentions } from "./context-mentions-entry";
|
|
12
|
+
export type {
|
|
13
|
+
ContextMentionMountContext,
|
|
14
|
+
ContextMentionEngine,
|
|
15
|
+
MentionSubmitBundle,
|
|
16
|
+
} from "./context-mentions-entry";
|