@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
|
@@ -341,27 +341,18 @@ export const createAttachmentControls = (config?: AgentWidgetConfig): Attachment
|
|
|
341
341
|
"aria-label": attachmentsConfig.buttonTooltipText ?? "Attach file",
|
|
342
342
|
},
|
|
343
343
|
style: {
|
|
344
|
+
// Appearance is themed from the CSS rule for `.persona-attachment-button`
|
|
345
|
+
// via the `--persona-button-ghost-*` tokens (components.button.ghost).
|
|
346
|
+
// Only config-driven sizing is inline.
|
|
344
347
|
width: attachIconSize,
|
|
345
348
|
height: attachIconSize,
|
|
346
349
|
minWidth: attachIconSize,
|
|
347
350
|
minHeight: attachIconSize,
|
|
348
351
|
fontSize: "18px",
|
|
349
352
|
lineHeight: "1",
|
|
350
|
-
backgroundColor: "transparent",
|
|
351
|
-
color: "var(--persona-primary, #111827)",
|
|
352
|
-
border: "none",
|
|
353
|
-
borderRadius: "6px",
|
|
354
|
-
transition: "background-color 0.15s ease",
|
|
355
353
|
},
|
|
356
354
|
});
|
|
357
355
|
|
|
358
|
-
button.addEventListener("mouseenter", () => {
|
|
359
|
-
button.style.backgroundColor = "var(--persona-palette-colors-black-alpha-50, rgba(0, 0, 0, 0.05))";
|
|
360
|
-
});
|
|
361
|
-
button.addEventListener("mouseleave", () => {
|
|
362
|
-
button.style.backgroundColor = "transparent";
|
|
363
|
-
});
|
|
364
|
-
|
|
365
356
|
const attachIconSvg = renderLucideIcon(attachIconName, attachIconSizeNum, "currentColor", 1.5);
|
|
366
357
|
if (attachIconSvg) {
|
|
367
358
|
button.appendChild(attachIconSvg);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, vi } from "vitest";
|
|
4
|
+
import { createMentionButton } from "./context-mention-button";
|
|
5
|
+
import { renderLucideIcon } from "../utils/icons";
|
|
6
|
+
import type { AgentWidgetContextMentionConfig } from "../types";
|
|
7
|
+
|
|
8
|
+
// Icon rendered inside the default 40px button: iconSize = round(40 * 0.6) = 24,
|
|
9
|
+
// strokeWidth 1.5 (see createMentionButton). Compare against the registry's own
|
|
10
|
+
// output so the assertion tracks lucide glyph changes instead of hard-coding SVG.
|
|
11
|
+
const refIcon = (name: string) =>
|
|
12
|
+
renderLucideIcon(name, 24, "currentColor", 1.5)!.innerHTML;
|
|
13
|
+
|
|
14
|
+
const config = (
|
|
15
|
+
overrides: Partial<AgentWidgetContextMentionConfig> = {}
|
|
16
|
+
): AgentWidgetContextMentionConfig => ({ enabled: true, sources: [], ...overrides });
|
|
17
|
+
|
|
18
|
+
describe("createMentionButton", () => {
|
|
19
|
+
it("invokes onOpen on click and labels itself from config", () => {
|
|
20
|
+
const onOpen = vi.fn();
|
|
21
|
+
const { button, wrapper } = createMentionButton({
|
|
22
|
+
config: config({ buttonTooltipText: "Add context" }),
|
|
23
|
+
onOpen,
|
|
24
|
+
});
|
|
25
|
+
expect(button.getAttribute("aria-label")).toBe("Add context");
|
|
26
|
+
expect(wrapper.querySelector(".persona-send-button-tooltip")?.textContent).toBe(
|
|
27
|
+
"Add context"
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
button.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
|
|
31
|
+
expect(onOpen).toHaveBeenCalledTimes(1);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("defaults to a '+' add-context icon, not an '@' glyph", () => {
|
|
35
|
+
const { button } = createMentionButton({ config: config(), onOpen: vi.fn() });
|
|
36
|
+
const svg = button.querySelector("svg");
|
|
37
|
+
expect(svg).not.toBeNull();
|
|
38
|
+
// "@" reads as power-user; the consumer-recognized affordance is "+".
|
|
39
|
+
expect(svg!.innerHTML).toBe(refIcon("plus"));
|
|
40
|
+
expect(svg!.innerHTML).not.toBe(refIcon("at-sign"));
|
|
41
|
+
expect(button.textContent).not.toContain("@");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("honors a buttonIconName override for power-user surfaces", () => {
|
|
45
|
+
const { button } = createMentionButton({
|
|
46
|
+
config: config({ buttonIconName: "at-sign" }),
|
|
47
|
+
onOpen: vi.fn(),
|
|
48
|
+
});
|
|
49
|
+
expect(button.querySelector("svg")!.innerHTML).toBe(refIcon("at-sign"));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("stops the click from bubbling so the composer's focus-textarea handler never runs", () => {
|
|
53
|
+
const parent = document.createElement("form");
|
|
54
|
+
const parentClick = vi.fn();
|
|
55
|
+
parent.addEventListener("click", parentClick);
|
|
56
|
+
document.body.appendChild(parent);
|
|
57
|
+
|
|
58
|
+
const { wrapper } = createMentionButton({ config: config(), onOpen: vi.fn() });
|
|
59
|
+
parent.appendChild(wrapper);
|
|
60
|
+
|
|
61
|
+
wrapper
|
|
62
|
+
.querySelector("button")!
|
|
63
|
+
.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
|
|
64
|
+
|
|
65
|
+
// The button's handler calls stopPropagation, so the parent (which stands in
|
|
66
|
+
// for the composer form's "click anywhere → focus textarea" listener) is
|
|
67
|
+
// never reached — the picker's search field keeps focus.
|
|
68
|
+
expect(parentClick).not.toHaveBeenCalled();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createElement, createNode } from "../utils/dom";
|
|
2
|
+
import { renderLucideIcon } from "../utils/icons";
|
|
3
|
+
import type { AgentWidgetContextMentionConfig } from "../types";
|
|
4
|
+
|
|
5
|
+
export interface MentionButtonParts {
|
|
6
|
+
button: HTMLButtonElement;
|
|
7
|
+
wrapper: HTMLElement;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The visible composer affordance ("add context") — the discoverable entry
|
|
12
|
+
* point that opens the mention menu. Lives in the CORE bundle (not the lazy
|
|
13
|
+
* chunk) so the button paints immediately when `contextMentions.enabled`, before
|
|
14
|
+
* the heavy menu runtime loads on first interaction.
|
|
15
|
+
*
|
|
16
|
+
* Styled to match `createAttachmentControls`' icon button for visual parity.
|
|
17
|
+
*/
|
|
18
|
+
export function createMentionButton(opts: {
|
|
19
|
+
config: AgentWidgetContextMentionConfig;
|
|
20
|
+
buttonSize?: string;
|
|
21
|
+
onOpen: () => void;
|
|
22
|
+
}): MentionButtonParts {
|
|
23
|
+
const { config, onOpen } = opts;
|
|
24
|
+
const size = opts.buttonSize ?? "40px";
|
|
25
|
+
const sizeNum = parseFloat(size) || 40;
|
|
26
|
+
const iconSize = Math.round(sizeNum * 0.6);
|
|
27
|
+
// Default to a "+" signifier, not an "@" glyph: no major consumer chat app
|
|
28
|
+
// (ChatGPT, Claude, Gemini, Perplexity) puts a literal "@" button in the
|
|
29
|
+
// composer — "@" is a typed power-user accelerator, while "+"/"add context"
|
|
30
|
+
// is the recognized consumer affordance. Override via `buttonIconName`
|
|
31
|
+
// (e.g. "at-sign") on a power-user surface.
|
|
32
|
+
const iconName = config.buttonIconName ?? "plus";
|
|
33
|
+
const tooltipText = config.buttonTooltipText ?? "Add context";
|
|
34
|
+
|
|
35
|
+
const wrapper = createElement("div", "persona-send-button-wrapper");
|
|
36
|
+
const button = createNode("button", {
|
|
37
|
+
className:
|
|
38
|
+
"persona-rounded-button persona-flex persona-items-center persona-justify-center disabled:persona-opacity-50 persona-cursor-pointer persona-mention-button",
|
|
39
|
+
attrs: {
|
|
40
|
+
type: "button",
|
|
41
|
+
"data-persona-composer-mention-button": "",
|
|
42
|
+
"aria-label": tooltipText,
|
|
43
|
+
"aria-haspopup": "listbox",
|
|
44
|
+
// Reflects the picker's open state: the orchestrator flips aria-expanded
|
|
45
|
+
// and adds aria-controls (the menu's listbox id) via `onPickerOpenChange`
|
|
46
|
+
// when this button opens/closes the picker. Starts collapsed.
|
|
47
|
+
"aria-expanded": "false",
|
|
48
|
+
},
|
|
49
|
+
style: {
|
|
50
|
+
// Appearance (bg / fg / border / radius / hover) is themed from the CSS
|
|
51
|
+
// rule for `.persona-mention-button` via the `--persona-button-ghost-*`
|
|
52
|
+
// tokens (components.button.ghost). Only config-driven sizing is inline.
|
|
53
|
+
width: size,
|
|
54
|
+
height: size,
|
|
55
|
+
minWidth: size,
|
|
56
|
+
minHeight: size,
|
|
57
|
+
fontSize: "18px",
|
|
58
|
+
lineHeight: "1",
|
|
59
|
+
},
|
|
60
|
+
}) as HTMLButtonElement;
|
|
61
|
+
|
|
62
|
+
const icon = renderLucideIcon(iconName, iconSize, "currentColor", 1.5);
|
|
63
|
+
if (icon) button.appendChild(icon);
|
|
64
|
+
else button.textContent = "+";
|
|
65
|
+
|
|
66
|
+
button.addEventListener("click", (e) => {
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
// Stop the click from bubbling to the composer form's "click anywhere →
|
|
69
|
+
// focus textarea" handler, which would otherwise steal focus from the
|
|
70
|
+
// picker's search field the moment it opens.
|
|
71
|
+
e.stopPropagation();
|
|
72
|
+
onOpen();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
wrapper.appendChild(button);
|
|
76
|
+
|
|
77
|
+
const tooltip = createElement("div", "persona-send-button-tooltip");
|
|
78
|
+
tooltip.textContent = tooltipText;
|
|
79
|
+
wrapper.appendChild(tooltip);
|
|
80
|
+
|
|
81
|
+
return { button, wrapper };
|
|
82
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { createElement, createNode } from "../utils/dom";
|
|
2
|
+
import { renderLucideIcon } from "../utils/icons";
|
|
3
|
+
import type {
|
|
4
|
+
AgentWidgetContextMentionConfig,
|
|
5
|
+
AgentWidgetContextMentionPayload,
|
|
6
|
+
AgentWidgetContextMentionRef,
|
|
7
|
+
} from "../types";
|
|
8
|
+
|
|
9
|
+
export type MentionChipStatus = "resolving" | "ready" | "error";
|
|
10
|
+
|
|
11
|
+
export interface MentionChipParts {
|
|
12
|
+
el: HTMLElement;
|
|
13
|
+
/** Update lifecycle state; `payload` is forwarded to `renderMentionChip` once resolved. */
|
|
14
|
+
setStatus: (
|
|
15
|
+
status: MentionChipStatus,
|
|
16
|
+
payload?: AgentWidgetContextMentionPayload
|
|
17
|
+
) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A single compact pill chip (icon + label + ×, ~26px) for the composer context
|
|
22
|
+
* row. Mirrors the attachment chip's remove affordance but is a text pill, not a
|
|
23
|
+
* 48×48 thumbnail. Honors `config.renderMentionChip` when provided.
|
|
24
|
+
*/
|
|
25
|
+
export function createMentionChip(opts: {
|
|
26
|
+
ref: AgentWidgetContextMentionRef;
|
|
27
|
+
config: AgentWidgetContextMentionConfig;
|
|
28
|
+
onRemove: () => void;
|
|
29
|
+
}): MentionChipParts {
|
|
30
|
+
const { ref, config, onRemove } = opts;
|
|
31
|
+
|
|
32
|
+
if (config.renderMentionChip) {
|
|
33
|
+
// Mid-level override: the host owns the markup. Status (and the resolved
|
|
34
|
+
// payload, once available) is reflected by re-invoking the renderer.
|
|
35
|
+
let status: MentionChipStatus = "resolving";
|
|
36
|
+
let payload: AgentWidgetContextMentionPayload | undefined;
|
|
37
|
+
let el = config.renderMentionChip({ ref, status, payload, remove: onRemove });
|
|
38
|
+
const setStatus = (
|
|
39
|
+
next: MentionChipStatus,
|
|
40
|
+
nextPayload?: AgentWidgetContextMentionPayload
|
|
41
|
+
) => {
|
|
42
|
+
if (next === status && nextPayload === payload) return;
|
|
43
|
+
status = next;
|
|
44
|
+
payload = nextPayload;
|
|
45
|
+
const replacement = config.renderMentionChip!({
|
|
46
|
+
ref,
|
|
47
|
+
status,
|
|
48
|
+
payload,
|
|
49
|
+
remove: onRemove,
|
|
50
|
+
});
|
|
51
|
+
el.replaceWith(replacement);
|
|
52
|
+
el = replacement;
|
|
53
|
+
};
|
|
54
|
+
// Getter, not a snapshot: setStatus swaps the node, and the manager's
|
|
55
|
+
// remove()/clear() must target the live element, not the detached original.
|
|
56
|
+
return {
|
|
57
|
+
get el() {
|
|
58
|
+
return el;
|
|
59
|
+
},
|
|
60
|
+
setStatus,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const iconName = ref.iconName ?? config.chipIconName ?? "at-sign";
|
|
65
|
+
|
|
66
|
+
const chip = createNode("div", {
|
|
67
|
+
className: "persona-mention-chip",
|
|
68
|
+
attrs: {
|
|
69
|
+
"data-persona-mention-chip": "",
|
|
70
|
+
"data-status": "resolving",
|
|
71
|
+
// Full label on hover, since the pill truncates long labels with ellipsis.
|
|
72
|
+
title: ref.label,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const iconHost = createElement("span", "persona-mention-chip-icon");
|
|
77
|
+
const setIcon = (name: string) => {
|
|
78
|
+
iconHost.replaceChildren();
|
|
79
|
+
const svg = renderLucideIcon(name, 13, "currentColor", 2);
|
|
80
|
+
if (svg) iconHost.appendChild(svg);
|
|
81
|
+
};
|
|
82
|
+
setIcon(iconName);
|
|
83
|
+
|
|
84
|
+
// The spinner shown while the mention resolves; swapped for the icon on ready.
|
|
85
|
+
const spinner = createElement("span", "persona-mention-chip-spinner");
|
|
86
|
+
|
|
87
|
+
const label = createNode("span", {
|
|
88
|
+
className: "persona-mention-chip-label",
|
|
89
|
+
text: ref.label,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const removeBtn = createNode("button", {
|
|
93
|
+
className: "persona-mention-chip-remove",
|
|
94
|
+
attrs: {
|
|
95
|
+
type: "button",
|
|
96
|
+
"aria-label": `Remove ${ref.label} context`,
|
|
97
|
+
},
|
|
98
|
+
}) as HTMLButtonElement;
|
|
99
|
+
const x = renderLucideIcon("x", 11, "currentColor", 2.5);
|
|
100
|
+
if (x) removeBtn.appendChild(x);
|
|
101
|
+
else removeBtn.textContent = "×";
|
|
102
|
+
removeBtn.addEventListener("click", (e) => {
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
e.stopPropagation();
|
|
105
|
+
onRemove();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
chip.appendChild(spinner);
|
|
109
|
+
chip.appendChild(label);
|
|
110
|
+
chip.appendChild(removeBtn);
|
|
111
|
+
|
|
112
|
+
const setStatus = (status: MentionChipStatus) => {
|
|
113
|
+
chip.setAttribute("data-status", status);
|
|
114
|
+
if (status === "resolving") {
|
|
115
|
+
if (spinner.parentNode !== chip) chip.insertBefore(spinner, label);
|
|
116
|
+
if (iconHost.parentNode === chip) iconHost.remove();
|
|
117
|
+
chip.setAttribute("title", ref.label);
|
|
118
|
+
} else {
|
|
119
|
+
if (spinner.parentNode === chip) spinner.remove();
|
|
120
|
+
if (iconHost.parentNode !== chip) chip.insertBefore(iconHost, label);
|
|
121
|
+
if (status === "error") {
|
|
122
|
+
// Error is not color-only: swap to an alert icon and explain in the
|
|
123
|
+
// hover title (and the aria-label reflects it via the title too).
|
|
124
|
+
setIcon("triangle-alert");
|
|
125
|
+
chip.setAttribute("title", `Couldn't add ${ref.label} to context`);
|
|
126
|
+
} else {
|
|
127
|
+
setIcon(iconName);
|
|
128
|
+
chip.setAttribute("title", ref.label);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return { el: chip, setStatus };
|
|
134
|
+
}
|