@ai-composer/dom 0.0.0-stage → 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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -2
  3. package/dist/icons.d.ts +11 -0
  4. package/dist/icons.d.ts.map +1 -0
  5. package/dist/icons.js +46 -0
  6. package/dist/icons.js.map +1 -0
  7. package/dist/index.d.ts +20 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +20 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/mount.d.ts +41 -0
  12. package/dist/mount.d.ts.map +1 -0
  13. package/dist/mount.js +168 -0
  14. package/dist/mount.js.map +1 -0
  15. package/dist/package.json +25 -0
  16. package/dist/parse.d.ts +13 -0
  17. package/dist/parse.d.ts.map +1 -0
  18. package/dist/parse.js +133 -0
  19. package/dist/parse.js.map +1 -0
  20. package/dist/popup.d.ts +34 -0
  21. package/dist/popup.d.ts.map +1 -0
  22. package/dist/popup.js +121 -0
  23. package/dist/popup.js.map +1 -0
  24. package/dist/render.d.ts +17 -0
  25. package/dist/render.d.ts.map +1 -0
  26. package/dist/render.js +84 -0
  27. package/dist/render.js.map +1 -0
  28. package/dist/selection.d.ts +13 -0
  29. package/dist/selection.d.ts.map +1 -0
  30. package/dist/selection.js +110 -0
  31. package/dist/selection.js.map +1 -0
  32. package/dist/suggestions.d.ts +23 -0
  33. package/dist/suggestions.d.ts.map +1 -0
  34. package/dist/suggestions.js +113 -0
  35. package/dist/suggestions.js.map +1 -0
  36. package/dist/surface.d.ts +40 -0
  37. package/dist/surface.d.ts.map +1 -0
  38. package/dist/surface.js +372 -0
  39. package/dist/surface.js.map +1 -0
  40. package/dist/template.d.ts +26 -0
  41. package/dist/template.d.ts.map +1 -0
  42. package/dist/template.js +47 -0
  43. package/dist/template.js.map +1 -0
  44. package/package.json +21 -3
  45. package/project.json +56 -0
  46. package/src/icons.ts +51 -0
  47. package/src/index.ts +50 -0
  48. package/src/mount.ts +218 -0
  49. package/src/parse.ts +135 -0
  50. package/src/popup.ts +153 -0
  51. package/src/render.ts +88 -0
  52. package/src/selection.ts +141 -0
  53. package/src/suggestions.ts +146 -0
  54. package/src/surface.test.ts +266 -0
  55. package/src/surface.ts +433 -0
  56. package/src/template.ts +80 -0
  57. package/tsconfig.json +15 -0
  58. package/tsconfig.lib.json +15 -0
  59. package/tsconfig.spec.json +16 -0
  60. package/vitest.config.ts +16 -0
package/dist/popup.js ADDED
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Caret-anchored popup positioning. The suggestion menu opens where the
3
+ * trigger key (@, /, …) was hit — not just above the box — and reflows:
4
+ *
5
+ * - horizontal: popup's left edge follows the caret, clamped into the
6
+ * viewport so it never opens out of the window
7
+ * - vertical: prefers `placement` (Copilot-style: above), flips to the
8
+ * other side when the viewport lacks room, caps its max-height to the
9
+ * available space and scrolls inside
10
+ * - repositions on window resize and on any scroll (capture) while open
11
+ *
12
+ * The popup must live inside a positioned ancestor (`.aic-input-wrap`) —
13
+ * coordinates are computed relative to that offset parent each update.
14
+ */
15
+ const GAP = 6;
16
+ const EDGE = 8;
17
+ /** Measure the caret inside a contenteditable host (viewport coordinates). */
18
+ export function measureCaret(host) {
19
+ const doc = host.ownerDocument;
20
+ const win = doc.defaultView;
21
+ if (!win)
22
+ return null;
23
+ const selection = win.getSelection();
24
+ if (selection && selection.rangeCount > 0 && host.contains(selection.focusNode)) {
25
+ const range = selection.getRangeAt(0).cloneRange();
26
+ const rects = range.getClientRects();
27
+ if (rects.length > 0) {
28
+ const rect = rects[0];
29
+ return { x: rect.left, top: rect.top, bottom: rect.bottom };
30
+ }
31
+ // Collapsed caret at an element boundary reports no rects — probe with a
32
+ // zero-width marker and remove it synchronously (no input event fires,
33
+ // so the editor's view pipeline is never involved).
34
+ const marker = doc.createElement('span');
35
+ marker.textContent = '​';
36
+ try {
37
+ range.insertNode(marker);
38
+ const rect = marker.getBoundingClientRect();
39
+ if (rect.width === 0 && rect.height === 0)
40
+ return null;
41
+ return { x: rect.left, top: rect.top, bottom: rect.bottom };
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ finally {
47
+ marker.remove();
48
+ }
49
+ }
50
+ return null;
51
+ }
52
+ /** Fallback: the first text line's start (empty host, no selection). */
53
+ function firstLine(host) {
54
+ const rect = host.getBoundingClientRect();
55
+ const style = host.ownerDocument.defaultView?.getComputedStyle(host);
56
+ const padLeft = Number.parseFloat(style?.paddingLeft ?? '0') || 0;
57
+ const padTop = Number.parseFloat(style?.paddingTop ?? '0') || 0;
58
+ return { x: rect.left + padLeft, top: rect.top + padTop, bottom: rect.top + padTop + 22 };
59
+ }
60
+ export function attachCaretAnchoredPopup(popup, host, options = {}) {
61
+ const win = host.ownerDocument.defaultView ?? window;
62
+ const prefer = options.placement ?? 'above';
63
+ const scrollOptions = {
64
+ capture: true,
65
+ passive: true,
66
+ };
67
+ const update = () => {
68
+ if (popup.hidden)
69
+ return;
70
+ const viewportWidth = win.innerWidth;
71
+ const viewportHeight = win.innerHeight;
72
+ const width = Math.max(180, Math.min(360, viewportWidth - EDGE * 2));
73
+ popup.style.position = 'absolute';
74
+ popup.style.width = `${Math.round(width)}px`;
75
+ popup.style.margin = '0';
76
+ popup.style.zIndex = 'var(--aic-z-suggestions)';
77
+ // Measure the anchor AFTER taking the popup out of flow — otherwise the
78
+ // wrap's rect still includes the freshly-opened list and the popup
79
+ // detaches by exactly its own height.
80
+ const wrap = popup.parentElement ?? host;
81
+ const wrapRect = wrap.getBoundingClientRect();
82
+ const point = measureCaret(host) ?? firstLine(host);
83
+ // Horizontal — under the caret, clamped so the popup stays on screen.
84
+ const desiredLeft = point.x - wrapRect.left - 4;
85
+ const absoluteLeft = Math.min(Math.max(wrapRect.left + desiredLeft, EDGE), viewportWidth - width - EDGE);
86
+ popup.style.left = `${Math.round(absoluteLeft - wrapRect.left)}px`;
87
+ popup.style.right = 'auto';
88
+ // Vertical — preferred side first, flip when the viewport lacks room.
89
+ popup.style.maxHeight = '';
90
+ const needed = Math.min(popup.offsetHeight || 200, 260);
91
+ const spaceAbove = point.top - EDGE;
92
+ const spaceBelow = viewportHeight - point.bottom - EDGE;
93
+ let side = prefer;
94
+ const wanted = Math.min(needed, 140);
95
+ if (side === 'above' && spaceAbove < wanted && spaceBelow > spaceAbove)
96
+ side = 'below';
97
+ else if (side === 'below' && spaceBelow < wanted && spaceAbove > spaceBelow)
98
+ side = 'above';
99
+ const available = Math.max(96, Math.min(260, (side === 'above' ? spaceAbove : spaceBelow) - GAP));
100
+ popup.style.maxHeight = `${Math.round(available)}px`;
101
+ if (side === 'above') {
102
+ popup.style.bottom = `${Math.round(wrapRect.bottom - point.top + GAP)}px`;
103
+ popup.style.top = 'auto';
104
+ }
105
+ else {
106
+ popup.style.top = `${Math.round(point.bottom - wrapRect.top + GAP)}px`;
107
+ popup.style.bottom = 'auto';
108
+ }
109
+ };
110
+ const onReflow = () => update();
111
+ win.addEventListener('resize', onReflow);
112
+ win.addEventListener('scroll', onReflow, scrollOptions);
113
+ return {
114
+ update,
115
+ destroy() {
116
+ win.removeEventListener('resize', onReflow);
117
+ win.removeEventListener('scroll', onReflow, scrollOptions);
118
+ },
119
+ };
120
+ }
121
+ //# sourceMappingURL=popup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popup.js","sourceRoot":"","sources":["../src/popup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Model → DOM rendering. The DOM is a projection of the authoritative
3
+ * document (ADR-0001). Atomic nodes render as non-editable chips carrying
4
+ * data-aic-* attributes so parse.ts can round-trip them safely.
5
+ */
6
+ import type { AIComposerDocument, AIComposerNode } from '@ai-composer/core';
7
+ import type { NodeRegistry } from '@ai-composer/core';
8
+ /** Render one atomic node as a non-editable chip element. */
9
+ export declare function renderChip(node: AIComposerNode, registry: NodeRegistry): HTMLElement;
10
+ /** Render a text run; newlines become <br> so the editable area shows line breaks. */
11
+ export declare function renderTextRun(text: string): Node;
12
+ /**
13
+ * Render the document into `host`, replacing previous content.
14
+ * The placeholder <br> keeps the host clickable when empty.
15
+ */
16
+ export declare function renderDocument(host: HTMLElement, document_: AIComposerDocument, registry: NodeRegistry): void;
17
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAOtD,6DAA6D;AAC7D,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,GAAG,WAAW,CAsCpF;AAUD,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAShD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,kBAAkB,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI,CAQ7G"}
package/dist/render.js ADDED
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Model → DOM rendering. The DOM is a projection of the authoritative
3
+ * document (ADR-0001). Atomic nodes render as non-editable chips carrying
4
+ * data-aic-* attributes so parse.ts can round-trip them safely.
5
+ */
6
+ function displayOf(node, registry) {
7
+ const definition = registry.get(node.type);
8
+ return definition?.toDisplay?.(node) ?? '';
9
+ }
10
+ /** Render one atomic node as a non-editable chip element. */
11
+ export function renderChip(node, registry) {
12
+ const chip = document.createElement('span');
13
+ chip.setAttribute('contenteditable', 'false');
14
+ chip.className = 'aic-chip';
15
+ chip.setAttribute('data-aic-key', node.key);
16
+ chip.setAttribute('data-aic-node', node.type);
17
+ chip.hidden = node.type === 'attachment';
18
+ chip.setAttribute('aria-label', displayOf(node, registry));
19
+ switch (node.type) {
20
+ case 'mention':
21
+ chip.setAttribute('data-aic-id', node.id);
22
+ chip.setAttribute('data-aic-label', node.label);
23
+ break;
24
+ case 'command':
25
+ chip.setAttribute('data-aic-id', node.id);
26
+ chip.setAttribute('data-aic-label', node.label);
27
+ break;
28
+ case 'variable':
29
+ chip.setAttribute('data-aic-name', node.name);
30
+ break;
31
+ case 'attachment':
32
+ chip.setAttribute('data-aic-id', node.id);
33
+ chip.setAttribute('data-aic-name', node.name);
34
+ chip.setAttribute('data-aic-mime', node.mimeType);
35
+ if (node.size !== undefined)
36
+ chip.setAttribute('data-aic-size', String(node.size));
37
+ break;
38
+ case 'custom':
39
+ chip.setAttribute('data-aic-plugin', node.plugin);
40
+ chip.setAttribute('data-aic-type', node.nodeType);
41
+ chip.setAttribute('data-aic-data', safeJson(node.data));
42
+ break;
43
+ default:
44
+ break;
45
+ }
46
+ chip.textContent = displayOf(node, registry);
47
+ return chip;
48
+ }
49
+ function safeJson(value) {
50
+ try {
51
+ return JSON.stringify(value ?? null);
52
+ }
53
+ catch {
54
+ return 'null';
55
+ }
56
+ }
57
+ /** Render a text run; newlines become <br> so the editable area shows line breaks. */
58
+ export function renderTextRun(text) {
59
+ if (!text.includes('\n'))
60
+ return document.createTextNode(text);
61
+ const fragment = document.createDocumentFragment();
62
+ const parts = text.split('\n');
63
+ parts.forEach((part, index) => {
64
+ if (part)
65
+ fragment.appendChild(document.createTextNode(part));
66
+ if (index < parts.length - 1)
67
+ fragment.appendChild(document.createElement('br'));
68
+ });
69
+ return fragment;
70
+ }
71
+ /**
72
+ * Render the document into `host`, replacing previous content.
73
+ * The placeholder <br> keeps the host clickable when empty.
74
+ */
75
+ export function renderDocument(host, document_, registry) {
76
+ host.textContent = '';
77
+ for (const node of document_.nodes) {
78
+ host.appendChild(node.type === 'text' ? renderTextRun(node.text) : renderChip(node, registry));
79
+ }
80
+ if (document_.nodes.length === 0) {
81
+ host.appendChild(document.createElement('br'));
82
+ }
83
+ }
84
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * DOM selection ↔ model selection mapping. All coordinates are computed as
3
+ * document-wide offsets over the same segment order the renderer produces:
4
+ * text characters, `<br>` = 1, chip = 1.
5
+ */
6
+ import { type AIComposerDocument, type SelectionState } from '@ai-composer/core';
7
+ /** Read the DOM selection inside `host` as a model selection. */
8
+ export declare function domSelectionToModel(host: HTMLElement, document_: AIComposerDocument): SelectionState | null;
9
+ /** Apply a model selection to the DOM (caret restore after renders). */
10
+ export declare function applyModelSelection(host: HTMLElement, document_: AIComposerDocument, selection: SelectionState): void;
11
+ /** Caret to the end of the host content. */
12
+ export declare function caretToEnd(host: HTMLElement, document_: AIComposerDocument): SelectionState;
13
+ //# sourceMappingURL=selection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../src/selection.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAKL,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAwE3B,iEAAiE;AACjE,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,WAAW,EACjB,SAAS,EAAE,kBAAkB,GAC5B,cAAc,GAAG,IAAI,CAiBvB;AAED,wEAAwE;AACxE,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,WAAW,EACjB,SAAS,EAAE,kBAAkB,EAC7B,SAAS,EAAE,cAAc,GACxB,IAAI,CAkBN;AAED,4CAA4C;AAC5C,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,kBAAkB,GAAG,cAAc,CAM3F"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * DOM selection ↔ model selection mapping. All coordinates are computed as
3
+ * document-wide offsets over the same segment order the renderer produces:
4
+ * text characters, `<br>` = 1, chip = 1.
5
+ */
6
+ import { clampSelection, createSelection, fromGlobalOffset, globalRange, } from '@ai-composer/core';
7
+ function isChip(node) {
8
+ return node instanceof HTMLElement && node.hasAttribute('data-aic-node');
9
+ }
10
+ /** Global offset of a DOM point inside the host, or null when outside. */
11
+ function globalOffsetOfPoint(host, target, targetOffset) {
12
+ let offset = 0;
13
+ let found = false;
14
+ const visit = (current) => {
15
+ if (found)
16
+ return true;
17
+ if (current === target) {
18
+ offset += targetOffset;
19
+ found = true;
20
+ return true;
21
+ }
22
+ if (current.nodeType === Node.TEXT_NODE) {
23
+ offset += current.textContent?.length ?? 0;
24
+ return false;
25
+ }
26
+ if (current.nodeName === 'BR' || isChip(current)) {
27
+ offset += 1;
28
+ return false;
29
+ }
30
+ for (const child of Array.from(current.childNodes)) {
31
+ if (visit(child))
32
+ return true;
33
+ }
34
+ return false;
35
+ };
36
+ visit(host);
37
+ return found ? offset : null;
38
+ }
39
+ /** DOM point at a global offset, or the host end as fallback. */
40
+ function domPointAtGlobal(host, globalOffset) {
41
+ let remaining = globalOffset;
42
+ const pointBefore = (node) => ({ node: node.parentNode ?? host, offset: indexOfChild(node) });
43
+ const indexOfChild = (node) => node.parentNode ? Array.prototype.indexOf.call(node.parentNode.childNodes, node) : 0;
44
+ const visit = (parent) => {
45
+ for (const child of Array.from(parent.childNodes)) {
46
+ if (child.nodeType === Node.TEXT_NODE) {
47
+ const length = child.textContent?.length ?? 0;
48
+ if (remaining <= length)
49
+ return { node: child, offset: remaining };
50
+ remaining -= length;
51
+ continue;
52
+ }
53
+ if (child.nodeName === 'BR' || isChip(child)) {
54
+ if (remaining <= 0)
55
+ return pointBefore(child);
56
+ remaining -= 1;
57
+ if (remaining <= 0)
58
+ return { node: child.parentNode ?? host, offset: indexOfChild(child) + 1 };
59
+ continue;
60
+ }
61
+ const nested = visit(child);
62
+ if (nested)
63
+ return nested;
64
+ }
65
+ return null;
66
+ };
67
+ return visit(host) ?? { node: host, offset: host.childNodes.length };
68
+ }
69
+ /** Read the DOM selection inside `host` as a model selection. */
70
+ export function domSelectionToModel(host, document_) {
71
+ const selection = host.ownerDocument.getSelection();
72
+ if (!selection || selection.rangeCount === 0)
73
+ return null;
74
+ const anchorNode = selection.anchorNode;
75
+ const focusNode = selection.focusNode;
76
+ if (!anchorNode || !focusNode || !host.contains(anchorNode) || !host.contains(focusNode)) {
77
+ return null;
78
+ }
79
+ const anchorOffset = globalOffsetOfPoint(host, anchorNode, selection.anchorOffset);
80
+ const focusOffset = globalOffsetOfPoint(host, focusNode, selection.focusOffset);
81
+ if (anchorOffset === null || focusOffset === null)
82
+ return null;
83
+ return clampSelection(document_, createSelection(fromGlobalOffset(document_, anchorOffset), fromGlobalOffset(document_, focusOffset)));
84
+ }
85
+ /** Apply a model selection to the DOM (caret restore after renders). */
86
+ export function applyModelSelection(host, document_, selection) {
87
+ const ownerDocument = host.ownerDocument;
88
+ const domSelection = ownerDocument.getSelection();
89
+ if (!domSelection)
90
+ return;
91
+ const { start, end } = globalRange(document_, selection);
92
+ const startPoint = domPointAtGlobal(host, start);
93
+ const endPoint = start === end ? startPoint : domPointAtGlobal(host, end);
94
+ try {
95
+ const range = ownerDocument.createRange();
96
+ range.setStart(startPoint.node, startPoint.offset);
97
+ range.setEnd(endPoint.node, endPoint.offset);
98
+ domSelection.removeAllRanges();
99
+ domSelection.addRange(range);
100
+ }
101
+ catch {
102
+ // Browsers can reject boundary points mid-render; the next render retries.
103
+ }
104
+ }
105
+ /** Caret to the end of the host content. */
106
+ export function caretToEnd(host, document_) {
107
+ const length = document_.nodes.reduce((sum, node) => sum + (node.type === 'text' ? node.text.length : 1), 0);
108
+ return createSelection(fromGlobalOffset(document_, length));
109
+ }
110
+ //# sourceMappingURL=selection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.js","sourceRoot":"","sources":["../src/selection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Suggestion menu — a true popup (ADR-0001 style projection): it opens at the
3
+ * caret where the trigger key was hit, never reserving DOM space, and flips /
4
+ * clamps itself to the viewport (see ./popup). Keyboard navigation lives in
5
+ * the editable surface; this module renders and handles clicks.
6
+ */
7
+ import type { AIComposer, SuggestionItem } from '@ai-composer/core';
8
+ export interface SuggestionListOptions {
9
+ /** Custom item renderer; default is a simple label/description row. */
10
+ renderItem?: (item: SuggestionItem, active: boolean) => HTMLElement;
11
+ /** The editable host to wire aria-controls / aria-activedescendant onto. */
12
+ inputHost?: HTMLElement;
13
+ /** Preferred popup placement relative to the caret; flips to fit the viewport. */
14
+ placement?: 'above' | 'below';
15
+ /** Decorative keyboard-hints footer (default true). */
16
+ showKbdHints?: boolean;
17
+ }
18
+ export interface SuggestionList {
19
+ readonly element: HTMLElement;
20
+ destroy(): void;
21
+ }
22
+ export declare function createSuggestionList(editor: AIComposer, element?: HTMLElement, options?: SuggestionListOptions): SuggestionList;
23
+ //# sourceMappingURL=suggestions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggestions.d.ts","sourceRoot":"","sources":["../src/suggestions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAe,MAAM,mBAAmB,CAAC;AAGjF,MAAM,WAAW,qBAAqB;IACpC,uEAAuE;IACvE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,KAAK,WAAW,CAAC;IACpE,4EAA4E;IAC5E,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,kFAAkF;IAClF,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAC9B,uDAAuD;IACvD,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,OAAO,IAAI,IAAI,CAAC;CACjB;AAwBD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE,WAAW,EACrB,OAAO,GAAE,qBAA0B,GAClC,cAAc,CA6FhB"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Suggestion menu — a true popup (ADR-0001 style projection): it opens at the
3
+ * caret where the trigger key was hit, never reserving DOM space, and flips /
4
+ * clamps itself to the viewport (see ./popup). Keyboard navigation lives in
5
+ * the editable surface; this module renders and handles clicks.
6
+ */
7
+ import { attachCaretAnchoredPopup } from './popup';
8
+ let listCounter = 0;
9
+ /** Decorative footer: ↑↓ navigate · ↵ select · esc close (aria-hidden). */
10
+ function kbdHintsFooter() {
11
+ const li = document.createElement('li');
12
+ li.className = 'aic-suggestion-footer';
13
+ li.setAttribute('role', 'presentation');
14
+ li.setAttribute('aria-hidden', 'true');
15
+ const hints = [
16
+ ['↑↓', 'navigate'],
17
+ ['↵', 'select'],
18
+ ['esc', 'close'],
19
+ ];
20
+ hints.forEach(([key, action], index) => {
21
+ const kbd = document.createElement('kbd');
22
+ kbd.textContent = key;
23
+ li.append(kbd, document.createTextNode(action));
24
+ if (index < hints.length - 1)
25
+ li.append(document.createElement('span'));
26
+ });
27
+ return li;
28
+ }
29
+ export function createSuggestionList(editor, element, options = {}) {
30
+ const list = element ?? document.createElement('ul');
31
+ if (!list.id)
32
+ list.id = `aic-suggestions-${(listCounter += 1)}`;
33
+ list.className = 'aic-suggestions';
34
+ list.setAttribute('role', 'listbox');
35
+ list.setAttribute('aria-hidden', 'true');
36
+ list.hidden = true;
37
+ let anchor = null;
38
+ const defaultItem = (item, active) => {
39
+ const li = document.createElement('li');
40
+ li.className = 'aic-suggestion' + (active ? ' aic-active' : '');
41
+ li.setAttribute('role', 'option');
42
+ if (item.description) {
43
+ const label = document.createElement('span');
44
+ label.className = 'aic-suggestion-label';
45
+ label.textContent = item.label;
46
+ const description = document.createElement('span');
47
+ description.className = 'aic-suggestion-description';
48
+ description.textContent = item.description;
49
+ li.append(label, description);
50
+ }
51
+ else {
52
+ li.textContent = item.label;
53
+ }
54
+ return li;
55
+ };
56
+ const renderItem = options.renderItem ?? defaultItem;
57
+ const sync = (state) => {
58
+ const open = state.activeTrigger !== null && state.suggestions.length > 0;
59
+ list.hidden = !open;
60
+ list.setAttribute('aria-hidden', String(!open));
61
+ const input = options.inputHost;
62
+ if (input) {
63
+ input.setAttribute('aria-controls', list.id);
64
+ input.setAttribute('aria-expanded', String(open));
65
+ if (open && state.activeSuggestionIndex >= 0) {
66
+ input.setAttribute('aria-activedescendant', `${list.id}-option-${state.activeSuggestionIndex}`);
67
+ }
68
+ else {
69
+ input.removeAttribute('aria-activedescendant');
70
+ }
71
+ }
72
+ list.textContent = '';
73
+ if (!open)
74
+ return;
75
+ if (input && !anchor) {
76
+ // Anchors to the caret inside the input host; the popup itself must be
77
+ // attached to a positioned ancestor (`.aic-input-wrap`) — by the time it
78
+ // opens it has been appended there.
79
+ anchor = attachCaretAnchoredPopup(list, input, { placement: options.placement });
80
+ }
81
+ state.suggestions.forEach((item, index) => {
82
+ const active = index === state.activeSuggestionIndex;
83
+ const li = renderItem(item, active);
84
+ li.id = `${list.id}-option-${index}`;
85
+ li.setAttribute('role', 'option');
86
+ li.setAttribute('aria-selected', String(active));
87
+ li.addEventListener('mousedown', (event) => event.preventDefault()); // keep caret in place
88
+ li.addEventListener('click', () => {
89
+ void editor.acceptSuggestion(item);
90
+ });
91
+ list.appendChild(li);
92
+ });
93
+ if (options.showKbdHints !== false) {
94
+ list.appendChild(kbdHintsFooter());
95
+ }
96
+ // Position after items exist so the measured height is real.
97
+ anchor?.update();
98
+ };
99
+ const unsubscribe = editor.subscribe(sync);
100
+ const offDestroy = editor.on('destroy', () => destroy());
101
+ function destroy() {
102
+ unsubscribe();
103
+ offDestroy();
104
+ anchor?.destroy();
105
+ anchor = null;
106
+ list.textContent = '';
107
+ list.hidden = true;
108
+ list.remove();
109
+ }
110
+ sync(editor.getState());
111
+ return { element: list, destroy };
112
+ }
113
+ //# sourceMappingURL=suggestions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggestions.js","sourceRoot":"","sources":["../src/suggestions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The editable surface: binds a contentEditable host to an editor using the
3
+ * model-first pipeline:
4
+ *
5
+ * user input → parse DOM → editor.applyViewUpdate() (model is authoritative)
6
+ * model change → render → restore caret from model selection
7
+ *
8
+ * All programmatic APIs (setValue/insertNode/undo…) write the model; the
9
+ * surface re-projects it. See ADR-0001.
10
+ */
11
+ import { type AIComposer, type SubmitKey } from '@ai-composer/core';
12
+ export interface EditableSurfaceOptions {
13
+ /** Multiline hint for a11y and Enter handling (chat/expanded modes). */
14
+ multiline?: boolean;
15
+ /** Override the editor config's submit key behavior. */
16
+ submitKey?: SubmitKey;
17
+ /** Explicit id (used for aria-controls wiring). */
18
+ id?: string;
19
+ /** Additional class names for the host. */
20
+ className?: string;
21
+ /** Accessible label; defaults to the placeholder. */
22
+ ariaLabel?: string;
23
+ }
24
+ export interface EditableSurface {
25
+ readonly host: HTMLElement;
26
+ /** Manually re-render from the model (used by undo/redo view hook). */
27
+ render(): void;
28
+ focus(options?: {
29
+ at?: 'start' | 'end';
30
+ }): void;
31
+ blur(): void;
32
+ /** Multiline hint (a11y + newline handling) — updated on mode switches. */
33
+ setMultiline(multiline: boolean): void;
34
+ /** Submit-key override (mode presets) — cleared with `undefined`. */
35
+ setSubmitKey(key: SubmitKey | undefined): void;
36
+ destroy(): void;
37
+ }
38
+ export declare const EDITABLE_SURFACE_CLASS = "aic-input";
39
+ export declare function createEditableSurface(editor: AIComposer, host: HTMLElement, options?: EditableSurfaceOptions): EditableSurface;
40
+ //# sourceMappingURL=surface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../src/surface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EASL,KAAK,UAAU,EAEf,KAAK,SAAS,EAEf,MAAM,mBAAmB,CAAC;AAK3B,MAAM,WAAW,sBAAsB;IACrC,wEAAwE;IACxE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wDAAwD;IACxD,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,mDAAmD;IACnD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,uEAAuE;IACvE,MAAM,IAAI,IAAI,CAAC;IACf,KAAK,CAAC,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,OAAO,GAAG,KAAK,CAAA;KAAE,GAAG,IAAI,CAAC;IAChD,IAAI,IAAI,IAAI,CAAC;IACb,2EAA2E;IAC3E,YAAY,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC,qEAAqE;IACrE,YAAY,CAAC,GAAG,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI,CAAC;IAC/C,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,eAAO,MAAM,sBAAsB,cAAc,CAAC;AAIlD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,WAAW,EACjB,OAAO,GAAE,sBAA2B,GACnC,eAAe,CAgUjB"}