@tangle-network/ui 11.32.2 → 11.34.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/CHANGELOG.md +23 -0
  2. package/dist/editor/document-editor-pane.d.ts +7 -1
  3. package/dist/editor/document-editor-pane.js +27 -12
  4. package/dist/editor/markdown-document-editor.js +30 -12
  5. package/dist/editor/source-markdown-editor.js +256 -0
  6. package/dist/editor/source-markdown.js +495 -0
  7. package/dist/files/file-artifact-pane.d.ts +2 -0
  8. package/dist/files/file-artifact-pane.js +1 -0
  9. package/dist/index.d.ts +3 -2
  10. package/dist/index.js +3 -2
  11. package/dist/lib/control-presentation.js +2 -1
  12. package/dist/lib/focus.d.ts +5 -1
  13. package/dist/lib/focus.js +6 -1
  14. package/dist/lib/interaction.d.ts +41 -0
  15. package/dist/lib/interaction.js +42 -0
  16. package/dist/markdown/markdown-table.js +1 -1
  17. package/dist/primitives/button.js +19 -18
  18. package/dist/primitives/card.d.ts +14 -1
  19. package/dist/primitives/card.js +15 -5
  20. package/dist/primitives/dropdown-menu.js +7 -6
  21. package/dist/primitives/segmented-control.d.ts +5 -1
  22. package/dist/primitives/segmented-control.js +12 -5
  23. package/dist/primitives/select.js +4 -3
  24. package/dist/primitives/tabs.js +4 -3
  25. package/dist/run/inline-thinking-item.d.ts +7 -1
  26. package/dist/run/inline-thinking-item.js +6 -5
  27. package/dist/utils/format.d.ts +6 -0
  28. package/dist/utils/format.js +9 -1
  29. package/dist/utils.d.ts +3 -2
  30. package/dist/utils.js +3 -2
  31. package/package.json +2 -2
  32. package/src/editor/document-editor-pane.tsx +54 -16
  33. package/src/editor/markdown-document-editor.tsx +33 -18
  34. package/src/editor/source-markdown-editor.test.tsx +156 -0
  35. package/src/editor/source-markdown-editor.ts +296 -0
  36. package/src/editor/source-markdown.fixtures.ts +90 -0
  37. package/src/editor/source-markdown.test.ts +235 -0
  38. package/src/editor/source-markdown.ts +627 -0
  39. package/src/files/file-artifact-pane.test.tsx +38 -3
  40. package/src/files/file-artifact-pane.tsx +3 -0
  41. package/src/lib/control-presentation.ts +10 -4
  42. package/src/lib/focus.ts +7 -1
  43. package/src/lib/interaction.ts +64 -0
  44. package/src/markdown/markdown-table.test.tsx +9 -0
  45. package/src/markdown/markdown-table.tsx +5 -1
  46. package/src/primitives/button.tsx +29 -10
  47. package/src/primitives/card.tsx +23 -6
  48. package/src/primitives/control-presentation.test.tsx +6 -3
  49. package/src/primitives/date-picker.test.tsx +1 -1
  50. package/src/primitives/dropdown-menu.tsx +15 -10
  51. package/src/primitives/interaction-states.test.tsx +285 -0
  52. package/src/primitives/segmented-control.test.tsx +4 -2
  53. package/src/primitives/segmented-control.tsx +13 -14
  54. package/src/primitives/select.tsx +11 -5
  55. package/src/primitives/tabs.tsx +7 -3
  56. package/src/run/inline-thinking-item.test.tsx +29 -0
  57. package/src/run/inline-thinking-item.tsx +11 -4
  58. package/src/utils/format.test.ts +16 -1
  59. package/src/utils/format.ts +17 -0
  60. package/src/utils/index.ts +10 -1
@@ -0,0 +1,495 @@
1
+ import { marked } from "marked";
2
+ import TurndownService from "turndown";
3
+ //#region src/editor/source-markdown.ts
4
+ /**
5
+ * Source-preserving Markdown for the rich editor.
6
+ *
7
+ * The rich editor holds a ProseMirror document, and a document can only be
8
+ * turned back into Markdown by re-serializing it. Re-serializing rewrites
9
+ * whatever the reader did not touch: list markers, emphasis delimiters,
10
+ * escapes, blank lines, and every construct the editor has no node for (a GFM
11
+ * table became one run-on paragraph). This module keeps the original bytes
12
+ * instead.
13
+ *
14
+ * The file is split into its top-level blocks with `marked`'s lexer, whose
15
+ * token `raw` fields concatenate back to the source. Each block the editor can
16
+ * represent is rendered to HTML carrying a block id; every other block (a
17
+ * table, raw HTML, an image, a task list, a link definition) becomes one atomic
18
+ * source node that holds its raw text. On save:
19
+ *
20
+ * - a block whose node still equals the node it was parsed into is written
21
+ * back as its original bytes, with its original separator;
22
+ * - a block the reader changed is re-serialized, and the change is merged
23
+ * into the original bytes (a three-way merge against the block's own
24
+ * re-serialization), so only the edited span differs; the merge is kept only
25
+ * when it parses back to exactly what the reader sees, otherwise the block's
26
+ * re-serialization is written;
27
+ * - a block the reader added is re-serialized.
28
+ *
29
+ * This module is pure: the editor-specific half (ProseMirror nodes, schema,
30
+ * HTML) arrives through `SourceNode` callbacks, so it runs without tiptap.
31
+ */
32
+ /** HTML attribute that carries a block id from the source HTML into the editor document. */
33
+ const BLOCK_ID_ATTRIBUTE = "data-md-block";
34
+ /** HTML attribute that carries a read-only block's raw Markdown. */
35
+ const SOURCE_RAW_ATTRIBUTE = "data-md-raw";
36
+ /** HTML attribute that carries a read-only block's kind, which picks its caption. */
37
+ const SOURCE_KIND_ATTRIBUTE = "data-md-kind";
38
+ const INLINE_TYPES = /* @__PURE__ */ new Set([
39
+ "text",
40
+ "strong",
41
+ "em",
42
+ "codespan",
43
+ "del",
44
+ "link",
45
+ "br",
46
+ "escape"
47
+ ]);
48
+ let documentCounter = 0;
49
+ function readOnlyKind(token) {
50
+ switch (token.type) {
51
+ case "paragraph":
52
+ case "heading": return inlineReadOnlyKind(token.tokens);
53
+ case "code":
54
+ case "hr": return null;
55
+ case "blockquote": return blocksReadOnlyKind(token.tokens);
56
+ case "list":
57
+ for (const item of token.items) {
58
+ if (item.task) return "checklist";
59
+ const kind = blocksReadOnlyKind(item.tokens);
60
+ if (kind) return kind;
61
+ }
62
+ return null;
63
+ case "table": return "table";
64
+ case "html": return "html";
65
+ case "def": return "definition";
66
+ default: return "markdown";
67
+ }
68
+ }
69
+ function blocksReadOnlyKind(tokens) {
70
+ for (const token of tokens) {
71
+ if (token.type === "space") continue;
72
+ if (token.type === "text") {
73
+ const kind = inlineReadOnlyKind(token.tokens ?? []);
74
+ if (kind) return kind;
75
+ continue;
76
+ }
77
+ const kind = readOnlyKind(token);
78
+ if (kind) return kind;
79
+ }
80
+ return null;
81
+ }
82
+ function inlineReadOnlyKind(tokens) {
83
+ for (const token of tokens ?? []) {
84
+ if (token.type === "image") return "image";
85
+ if (token.type === "html") return "html";
86
+ if (!INLINE_TYPES.has(token.type)) return "markdown";
87
+ const children = token.tokens;
88
+ if (children) {
89
+ const kind = inlineReadOnlyKind(children);
90
+ if (kind) return kind;
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+ function escapeAttribute(value) {
96
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
97
+ }
98
+ function sourceNodeHtml(block) {
99
+ return `<div ${BLOCK_ID_ATTRIBUTE}="${block.id}" ${SOURCE_KIND_ATTRIBUTE}="${block.kind}" ${SOURCE_RAW_ATTRIBUTE}="${escapeAttribute(block.raw)}"></div>`;
100
+ }
101
+ /** Renders one editable block, tagging its root element with the block id. */
102
+ function editableBlockHtml(token, id) {
103
+ const html = String(marked.parser([token], {
104
+ async: false,
105
+ gfm: true
106
+ })).trim();
107
+ if (!/^<[a-z][a-z0-9]*/i.test(html)) return null;
108
+ return html.replace(/^<([a-z][a-z0-9]*)/i, `<$1 ${BLOCK_ID_ATTRIBUTE}="${id}"`);
109
+ }
110
+ /**
111
+ * Maps offsets in the line-feed-normalized text back to the original text, so
112
+ * a file with CRLF line endings keeps them.
113
+ */
114
+ function originalOffsets(original, normalized) {
115
+ if (original === normalized) return (offset) => offset;
116
+ const map = new Int32Array(normalized.length + 1);
117
+ let o = 0;
118
+ for (let n = 0; n < normalized.length; n++) {
119
+ map[n] = o;
120
+ o += original[o] === "\r" && original[o + 1] === "\n" ? 2 : 1;
121
+ }
122
+ map[normalized.length] = original.length;
123
+ return (offset) => map[offset];
124
+ }
125
+ function buildHtml(blocks, tokens) {
126
+ return blocks.map((block) => {
127
+ if (!block.editable) return sourceNodeHtml(block);
128
+ const token = tokens.get(block.id);
129
+ return (token && editableBlockHtml(token, block.id)) ?? sourceNodeHtml(block);
130
+ }).join("\n");
131
+ }
132
+ function finish(prefix, trailer, blocks, tokens, links) {
133
+ return {
134
+ prefix,
135
+ trailer,
136
+ blocks,
137
+ byId: new Map(blocks.map((block) => [block.id, block])),
138
+ html: buildHtml(blocks, tokens),
139
+ links,
140
+ tokens,
141
+ lineEnding: [
142
+ prefix,
143
+ trailer,
144
+ ...blocks.map((block) => block.raw + block.gap)
145
+ ].some((text) => text.includes("\r\n")) ? "\r\n" : "\n"
146
+ };
147
+ }
148
+ /** The whole file as one read-only block: the fallback when it cannot be split faithfully. */
149
+ function wholeFileSource(markdown, nonce) {
150
+ const blocks = markdown.trim() ? [{
151
+ id: `${nonce}:0`,
152
+ index: 0,
153
+ raw: markdown,
154
+ gap: "",
155
+ editable: false,
156
+ kind: "markdown"
157
+ }] : [];
158
+ return finish(blocks.length ? "" : markdown, "", blocks, /* @__PURE__ */ new Map(), {});
159
+ }
160
+ /** Splits a Markdown file into source blocks and the editor HTML that carries them. */
161
+ function parseSourceMarkdown(markdown) {
162
+ const nonce = `m${(documentCounter += 1).toString(36)}`;
163
+ const normalized = markdown.replace(/\r\n?/g, "\n");
164
+ if (/\r(?!\n)/.test(markdown)) return wholeFileSource(markdown, nonce);
165
+ const tokens = marked.lexer(normalized, { gfm: true });
166
+ if (tokens.map((token) => token.raw).join("") !== normalized) return wholeFileSource(markdown, nonce);
167
+ const toOriginal = originalOffsets(markdown, normalized);
168
+ const slice = (start, end) => markdown.slice(toOriginal(start), toOriginal(end));
169
+ const blocks = [];
170
+ const blockTokens = /* @__PURE__ */ new Map();
171
+ let prefix = "";
172
+ let offset = 0;
173
+ for (const token of tokens) {
174
+ const start = offset;
175
+ offset += token.raw.length;
176
+ if (token.type === "space") {
177
+ if (blocks.length === 0) prefix += slice(start, offset);
178
+ else blocks[blocks.length - 1].gap += slice(start, offset);
179
+ continue;
180
+ }
181
+ const body = token.raw.replace(/\n+$/, "");
182
+ const id = `${nonce}:${blocks.length}`;
183
+ const kind = readOnlyKind(token);
184
+ blocks.push({
185
+ id,
186
+ index: blocks.length,
187
+ raw: slice(start, start + body.length),
188
+ gap: slice(start + body.length, offset),
189
+ editable: kind === null,
190
+ kind: kind ?? token.type
191
+ });
192
+ blockTokens.set(id, token);
193
+ }
194
+ const last = blocks[blocks.length - 1];
195
+ const trailer = last ? last.gap : "";
196
+ return finish(prefix, trailer, blocks, blockTokens, { ...tokens.links });
197
+ }
198
+ /**
199
+ * Returns a copy of `source` with the given blocks shown read-only. The editor
200
+ * calls this when its document could not hold a block faithfully.
201
+ */
202
+ function withReadOnlyBlocks(source, ids) {
203
+ const blocks = source.blocks.map((block) => block.editable && ids.has(block.id) ? {
204
+ ...block,
205
+ editable: false,
206
+ kind: "markdown"
207
+ } : block);
208
+ return finish(source.prefix, source.trailer, blocks, source.tokens, source.links);
209
+ }
210
+ /** The whole document read-only: the last fallback when the editor cannot map it. */
211
+ function asReadOnlyDocument(markdown) {
212
+ return wholeFileSource(markdown, `m${(documentCounter += 1).toString(36)}`);
213
+ }
214
+ /**
215
+ * HTML for one block of Markdown, with no block ids, or null when the text is
216
+ * not a single editable block. `links` resolves reference links defined
217
+ * elsewhere in the file. Used to check that a merged block reads back as the
218
+ * reader's edit.
219
+ */
220
+ function blockHtml(markdown, links = {}) {
221
+ const lexer = new marked.Lexer({ gfm: true });
222
+ Object.assign(lexer.tokens.links, links);
223
+ const tokens = lexer.lex(markdown.replace(/\r\n?/g, "\n")).filter((t) => t.type !== "space");
224
+ if (tokens.length !== 1 || readOnlyKind(tokens[0]) !== null) return null;
225
+ return String(marked.parser(tokens, {
226
+ async: false,
227
+ gfm: true
228
+ }));
229
+ }
230
+ function styleOf(raw) {
231
+ const bullet = /^[ \t]*([*+-])[ \t]+\S/m.exec(raw)?.[1];
232
+ const ordered = /^[ \t]*\d{1,9}([.)])[ \t]+\S/m.exec(raw)?.[1];
233
+ return {
234
+ bullet: bullet ?? "-",
235
+ orderedDelimiter: ordered ?? ".",
236
+ em: /(^|[^\w*_\\])_[^_\s]/.test(raw) && !/(^|[^\w*\\])\*[^*\s]/.test(raw) ? "_" : "*",
237
+ strong: /(^|[^\w\\])__[^_\s]/.test(raw) && !/\*\*[^*\s]/.test(raw) ? "__" : "**",
238
+ fence: /^[ \t]*~~~/m.test(raw) && !/^[ \t]*```/m.test(raw) ? "~~~" : "```"
239
+ };
240
+ }
241
+ const turndownCache = /* @__PURE__ */ new Map();
242
+ function turndownFor(style) {
243
+ const key = `${style.bullet}${style.orderedDelimiter}${style.em}${style.strong}${style.fence}`;
244
+ const cached = turndownCache.get(key);
245
+ if (cached) return cached;
246
+ const service = new TurndownService({
247
+ bulletListMarker: style.bullet,
248
+ codeBlockStyle: "fenced",
249
+ emDelimiter: style.em,
250
+ strongDelimiter: style.strong,
251
+ fence: style.fence,
252
+ headingStyle: "atx",
253
+ hr: "---"
254
+ });
255
+ service.addRule("listParagraph", {
256
+ filter: (node) => node.nodeName === "P" && node.parentNode?.nodeName === "LI" && Array.from(node.parentNode.children).filter((child) => child.nodeName === "P").length === 1,
257
+ replacement: (content) => `\n${content}\n`
258
+ });
259
+ service.addRule("listItem", {
260
+ filter: "li",
261
+ replacement(content, node) {
262
+ const element = node;
263
+ const parent = element.parentNode;
264
+ let prefix = `${style.bullet} `;
265
+ if (parent?.nodeName === "OL") {
266
+ const start = parent.getAttribute("start");
267
+ const index = Array.prototype.indexOf.call(parent.children, element);
268
+ prefix = `${(start ? Number(start) : 1) + index}${style.orderedDelimiter} `;
269
+ }
270
+ const indented = content.replace(/^\n+|\n+$/g, "").replace(/\n(?=[^\n])/g, `\n${" ".repeat(prefix.length)}`);
271
+ return prefix + indented + (element.nextSibling ? "\n" : "");
272
+ }
273
+ });
274
+ service.addRule("strikethrough", {
275
+ filter: [
276
+ "del",
277
+ "s",
278
+ "strike"
279
+ ],
280
+ replacement: (content) => `~~${content}~~`
281
+ });
282
+ turndownCache.set(key, service);
283
+ return service;
284
+ }
285
+ /** Markdown for one block of editor HTML, written in the style of `sourceRaw`. */
286
+ function blockMarkdown(html, sourceRaw = "") {
287
+ return turndownFor(styleOf(sourceRaw)).turndown(html).replace(/^\n+|\n+$/g, "");
288
+ }
289
+ /** Edit cost past which a diff is abandoned, bounding the trace to a few MB. */
290
+ const MAX_DIFF_COST = 1200;
291
+ function pushOp(ops, op, text) {
292
+ if (!text) return;
293
+ const last = ops[ops.length - 1];
294
+ if (last && last[0] === op) last[1] += text;
295
+ else ops.push([op, text]);
296
+ }
297
+ /** Myers' O((N+M)D) diff of two strings with a common prefix and suffix removed. */
298
+ function myers(a, b, maxCost) {
299
+ const n = a.length;
300
+ const m = b.length;
301
+ if (n === 0) return m ? [[1, b]] : [];
302
+ if (m === 0) return [[-1, a]];
303
+ const limit = Math.min(n + m, maxCost);
304
+ const offset = limit + 1;
305
+ const v = new Int32Array(2 * limit + 3);
306
+ v[offset + 1] = 0;
307
+ const trace = [];
308
+ let found = -1;
309
+ for (let d = 0; d <= limit && found < 0; d++) {
310
+ trace.push(v.slice(offset - d - 1, offset + d + 2));
311
+ for (let k = -d; k <= d; k += 2) {
312
+ let x = k === -d || k !== d && v[offset + k - 1] < v[offset + k + 1] ? v[offset + k + 1] : v[offset + k - 1] + 1;
313
+ let y = x - k;
314
+ while (x < n && y < m && a.charCodeAt(x) === b.charCodeAt(y)) {
315
+ x++;
316
+ y++;
317
+ }
318
+ v[offset + k] = x;
319
+ if (x >= n && y >= m) {
320
+ found = d;
321
+ break;
322
+ }
323
+ }
324
+ }
325
+ if (found < 0) return null;
326
+ const runs = [];
327
+ let x = n;
328
+ let y = m;
329
+ for (let d = found; d > 0; d--) {
330
+ const snapshot = trace[d];
331
+ const at = (k) => snapshot[k + d + 1];
332
+ const k = x - y;
333
+ const prevK = k === -d || k !== d && at(k - 1) < at(k + 1) ? k + 1 : k - 1;
334
+ const prevX = at(prevK);
335
+ const prevY = prevX - prevK;
336
+ const moveEndX = prevK === k + 1 ? prevX : prevX + 1;
337
+ if (x > moveEndX) runs.push([
338
+ 0,
339
+ moveEndX,
340
+ x
341
+ ]);
342
+ if (prevK === k + 1) runs.push([
343
+ 1,
344
+ prevY,
345
+ prevY + 1
346
+ ]);
347
+ else runs.push([
348
+ -1,
349
+ prevX,
350
+ prevX + 1
351
+ ]);
352
+ x = prevX;
353
+ y = prevY;
354
+ }
355
+ if (x > 0) runs.push([
356
+ 0,
357
+ 0,
358
+ x
359
+ ]);
360
+ const ops = [];
361
+ for (let i = runs.length - 1; i >= 0; i--) {
362
+ const [op, from, to] = runs[i];
363
+ pushOp(ops, op, op === 1 ? b.slice(from, to) : a.slice(from, to));
364
+ }
365
+ return ops;
366
+ }
367
+ /** Character diff from `a` to `b`, or null when it would cost more than the bound. */
368
+ function diffText(a, b, maxCost = MAX_DIFF_COST) {
369
+ let start = 0;
370
+ const shorter = Math.min(a.length, b.length);
371
+ while (start < shorter && a.charCodeAt(start) === b.charCodeAt(start)) start++;
372
+ let endA = a.length;
373
+ let endB = b.length;
374
+ while (endA > start && endB > start && a.charCodeAt(endA - 1) === b.charCodeAt(endB - 1)) {
375
+ endA--;
376
+ endB--;
377
+ }
378
+ const middle = myers(a.slice(start, endA), b.slice(start, endB), maxCost);
379
+ if (!middle) return null;
380
+ const ops = [];
381
+ pushOp(ops, 0, a.slice(0, start));
382
+ for (const [op, text] of middle) pushOp(ops, op, text);
383
+ pushOp(ops, 0, a.slice(endA));
384
+ return ops;
385
+ }
386
+ /**
387
+ * For each boundary between characters of `canonical`, the matching boundary
388
+ * in `raw`, or -1 where the two texts disagree around it. A boundary that two
389
+ * matched runs both claim keeps the earlier one, so text typed at the end of a
390
+ * word lands before any syntax that only the source spells out.
391
+ */
392
+ function boundaryMap(ops, canonicalLength, rawLength) {
393
+ const map = new Int32Array(canonicalLength + 1).fill(-1);
394
+ let c = 0;
395
+ let r = 0;
396
+ for (const [op, text] of ops) if (op === 0) {
397
+ for (let j = 0; j <= text.length; j++) if (map[c + j] < 0) map[c + j] = r + j;
398
+ c += text.length;
399
+ r += text.length;
400
+ } else if (op === -1) c += text.length;
401
+ else r += text.length;
402
+ if (map[0] < 0) map[0] = 0;
403
+ if (map[canonicalLength] < 0) map[canonicalLength] = rawLength;
404
+ return map;
405
+ }
406
+ /**
407
+ * Merges edits of one block into its original bytes. `raw` is the block as
408
+ * written; `canonical` is the Markdown of the node first parsed from it. The
409
+ * alignment of the two is computed once and reused for every later edit.
410
+ */
411
+ function createBlockMerger(raw, canonical) {
412
+ let map;
413
+ const alignment = () => {
414
+ if (map === void 0) {
415
+ const ops = diffText(canonical, raw);
416
+ map = ops ? boundaryMap(ops, canonical.length, raw.length) : null;
417
+ }
418
+ return map;
419
+ };
420
+ return (edited) => {
421
+ if (edited === canonical) return raw;
422
+ if (raw === canonical) return edited;
423
+ const edit = diffText(canonical, edited);
424
+ const boundaries = alignment();
425
+ if (!edit || !boundaries) return null;
426
+ let out = "";
427
+ let rawCursor = 0;
428
+ let c = 0;
429
+ let hunkStart = -1;
430
+ let inserted = "";
431
+ const flush = () => {
432
+ if (hunkStart < 0) return true;
433
+ const from = boundaries[hunkStart];
434
+ const to = boundaries[c];
435
+ if (from < 0 || to < 0 || to < from || from < rawCursor) return false;
436
+ out += raw.slice(rawCursor, from) + inserted;
437
+ rawCursor = to;
438
+ hunkStart = -1;
439
+ inserted = "";
440
+ return true;
441
+ };
442
+ for (const [op, text] of edit) {
443
+ if (op === 0) {
444
+ if (!flush()) return null;
445
+ c += text.length;
446
+ continue;
447
+ }
448
+ if (hunkStart < 0) hunkStart = c;
449
+ if (op === -1) c += text.length;
450
+ else inserted += text;
451
+ }
452
+ if (!flush()) return null;
453
+ return out + raw.slice(rawCursor);
454
+ };
455
+ }
456
+ function withLineEnding(text, lineEnding) {
457
+ return lineEnding === "\n" ? text : text.replace(/\r?\n/g, lineEnding);
458
+ }
459
+ function editedBlockText(node) {
460
+ const edited = node.markdown();
461
+ const merged = node.merge(edited);
462
+ if (merged !== null && (merged === edited || node.reparses(merged))) return merged;
463
+ return edited;
464
+ }
465
+ /** Writes the editor document back as Markdown, keeping every untouched byte of `source`. */
466
+ function serializeSourceMarkdown(source, nodes) {
467
+ const pieces = [];
468
+ for (const node of nodes) {
469
+ const block = node.block;
470
+ let text;
471
+ if (block && (node.unchanged || !block.editable)) text = block.raw;
472
+ else if (block) text = withLineEnding(editedBlockText(node), source.lineEnding);
473
+ else text = withLineEnding(node.markdown(), source.lineEnding);
474
+ if (!text.trim()) continue;
475
+ pieces.push({
476
+ text,
477
+ block,
478
+ keepsSeparator: Boolean(block) && (node.unchanged || node.sameType)
479
+ });
480
+ }
481
+ let out = source.prefix;
482
+ pieces.forEach((piece, i) => {
483
+ out += piece.text;
484
+ const next = pieces[i + 1];
485
+ if (!next) {
486
+ out += source.blocks.length ? source.trailer : source.lineEnding;
487
+ return;
488
+ }
489
+ const adjacent = piece.block && next.block && next.block.index === piece.block.index + 1;
490
+ out += adjacent && piece.keepsSeparator && next.keepsSeparator ? piece.block.gap : source.lineEnding + source.lineEnding;
491
+ });
492
+ return out;
493
+ }
494
+ //#endregion
495
+ export { BLOCK_ID_ATTRIBUTE, SOURCE_KIND_ATTRIBUTE, SOURCE_RAW_ATTRIBUTE, asReadOnlyDocument, blockHtml, blockMarkdown, createBlockMerger, diffText, parseSourceMarkdown, serializeSourceMarkdown, withReadOnlyBlocks };
@@ -7,6 +7,8 @@ export interface FileArtifactPaneEditorOptions {
7
7
  enabled?: boolean;
8
8
  /** Persisted baseline when content is the current draft. */
9
9
  savedContent?: string;
10
+ /** Whether the draft has unsaved edits, when the host tracks that itself. Overrides `savedContent`. */
11
+ dirty?: boolean;
10
12
  mode?: DocumentEditorMode;
11
13
  defaultMode?: DocumentEditorMode;
12
14
  onModeChange?: (mode: DocumentEditorMode) => void;
@@ -69,6 +69,7 @@ function FileArtifactPane({ filename, content, blobUrl, mimeType, size, onClose,
69
69
  hideTitleBlock,
70
70
  markdown: content ?? "",
71
71
  savedMarkdown: editor.savedContent,
72
+ dirty: editor.dirty,
72
73
  urlTransform,
73
74
  mode: editor.mode,
74
75
  defaultMode: editor.defaultMode,
package/dist/index.d.ts CHANGED
@@ -115,11 +115,12 @@ import "./markdown.js";
115
115
  import { addMessage, addParts, clearChat, isStreamingAtom, messagesAtom, partMapAtom, updatePart } from "./stores/chat-store.js";
116
116
  import "./types.js";
117
117
  import { copyText } from "./utils/copy-text.js";
118
- import { formatBytes, formatDuration, formatUptime, truncateText } from "./utils/format.js";
118
+ import { formatBytes, formatDuration, formatUptime, plainTextFromMarkdown, truncateText } from "./utils/format.js";
119
119
  import { timeAgo } from "./utils/time-ago.js";
120
120
  import { TOOL_CATEGORY_ICONS, getToolCategory, getToolDisplayMetadata, getToolErrorText } from "./utils/tool-display.js";
121
121
  import { cn } from "./lib/utils.js";
122
122
  import { focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset } from "./lib/focus.js";
123
+ import { ixControl, ixControlAfter, ixControlSoft, ixField, ixRow, ixSelected, ixTabUnderline } from "./lib/interaction.js";
123
124
  import "./utils.js";
124
125
  import { CommandPreview, CommandPreviewProps } from "./tool-previews/command-preview.js";
125
126
  import { WriteFilePreview, WriteFilePreviewProps } from "./tool-previews/write-file-preview.js";
@@ -130,4 +131,4 @@ import { QuestionPreview, QuestionPreviewProps } from "./tool-previews/question-
130
131
  import { DiffPreview, DiffPreviewProps } from "./tool-previews/diff-preview.js";
131
132
  import { RedactedDocSegment, RedactedDocument, RedactedDocumentData, RedactedDocumentProps, RevealResult } from "./redaction/redacted-document.js";
132
133
  import "./redaction.js";
133
- export { ActiveProjectActivity, ActiveSessionActivityOptions, ActiveSessionConnectionOptions, ActiveSessionConnectionState, ActiveSessionReconnectState, ActiveSessionRecord, ActiveSessionStatus, ActiveSessionTransportMode, ActiveSessionsState, AgentBranding, AgentStreamEvent, AgentTimeline, type AgentTimelineArtifactItem, type AgentTimelineCustomItem, type AgentTimelineItem, type AgentTimelineMessageItem, type AgentTimelineProps, type AgentTimelineStatusItem, type AgentTimelineTone, type AgentTimelineToolGroupItem, type AgentTimelineToolItem, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, type AlertTone, AppendUserMessageOptions, ApplySdkEventOptions, ApprovalCard, type ApprovalCardProps, type ApprovalDiffFile, ApprovalDiffSummary, type ApprovalPlanStep, ArtifactPane, type ArtifactPaneProps, AuthHeader, type AuthHeaderProps, AuthUser, AutomationStreamEvent, Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, BeginAssistantMessageOptions, BotStreamEvent, Button, type ButtonProps, CATEGORY_TONES, CSV_PREVIEW_ROW_LIMIT, Calendar, type CalendarDate, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CardTitleProps, type CategoryTone, ChatAuthor, ChatContainer, type ChatContainerProps, ChatMessage, type ChatMessageProps, Checkbox, Chip, type ChipProps, CodeBlock, type CodeBlockProps, Collapsible, CollapsibleContent, CollapsibleTrigger, CommandPreview, type CommandPreviewProps, CompleteAssistantMessageOptions, ConnectionState, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, CustomToolRenderer, DatePicker, type DatePickerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffPreview, type DiffPreviewProps, DisplayVariant, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, ExpandedToolDetail, type ExpandedToolDetailProps, type FeedSegment, FileArtifactPane, type FileArtifactPaneProps, FileCard, type FileCardProps, type FileDetails, type FileFormat, FileList, type FileListItem, type FileListProps, type FileNode, FilePreview, type FilePreviewKind, type FilePreviewProps, type FileTabData, FileTabs, type FileTabsProps, FileTree, type FileTreeProps, type FileTreeVisibilityOptions, type FileUploader, FilterField, type FilterFieldProps, FinalTextPart, GitHubLoginButton, type GitHubLoginButtonProps, GlobResultsPreview, type GlobResultsPreviewProps, GrepResultsPreview, type GrepResultsPreviewProps, GroupedMessage, Heading, type HeadingProps, type HeadingVariant, HelpText, type HelpTextProps, IconTile, type IconTileProps, InlineCode, type InlineCodeProps, InlineThinkingItem, type InlineThinkingItemProps, InlineToolItem, type InlineToolItemProps, Input, type InputProps, Label, LiveDuration, LoginLayout, type LoginLayoutProps, Logo, type LogoProps, Markdown, type MarkdownLinkChip, type MarkdownLinkChipResolver, type MarkdownProps, MessageAuthor, type MessageAuthorProps, MessageList, type MessageListProps, type MessageRole, MessageRun, MessageUser, Metric, type MetricProps, MetricStrip, type MetricStripProps, OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, type OpenUIAction, type OpenUIActionsNode, OpenUIArtifactRenderer, type OpenUIArtifactRendererProps, type OpenUIBadgeNode, type OpenUICardNode, type OpenUICodeNode, type OpenUIComponentNode, type OpenUIGridNode, type OpenUIHeadingNode, type OpenUIKeyValueNode, type OpenUIMarkdownNode, type OpenUIPrimitive, type OpenUISchemaIssue, type OpenUISchemaValidation, type OpenUISeparatorNode, type OpenUIStackNode, type OpenUIStatNode, type OpenUITableNode, type OpenUITextNode, PageHeader, type PageHeaderProps, PageShell, type PageShellProps, Pagination, type PaginationProps, PdfViewer, type PdfViewerConfig, type PdfViewerProps, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, QuestionPreview, type QuestionPreviewProps, RadioGroup, RadioGroupItem, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, ReasoningPart, type RedactedDocSegment, RedactedDocument, type RedactedDocumentData, type RedactedDocumentProps, RegisterActiveSessionOptions, type RevealResult, RichFileTree, type RichFileTreeGitEntry, type RichFileTreeGitStatus, type RichFileTreeProps, type RichFileTreeThemeVars, Run, RunGroup, type RunGroupProps, type RunPhase, RunPhaseList, RunRowShell, type RunRowShellProps, type RunRowStatus, RunRowStatusDot, RunStats, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SessionMessage, SessionPart, SessionProjectKey, type SessionUser, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, type StatCardProps, StatusPill, type StatusPillProps, type StatusTone, Switch, TONE_CLASSES, TOOL_CATEGORY_ICONS, TRANSCRIPT_TEXT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, type TagProps, TangleKnot, TaskList, type TaskListItem, type TaskListProps, type TaskStatus, TaskStreamEvent, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TerminalStreamEvent, TextPart, TextShimmer, type TextShimmerProps, Textarea, type TextareaProps, ThemeToggle, ThinkingIndicator, type ThinkingIndicatorProps, TimeSelect, type TimeSelectProps, type Toast, ToastContainer, ToastProvider, type Tone, type ToneClasses, type ToolCallData, ToolCallFeed, type ToolCallFeedProps, type ToolCallStatus, type ToolCallType, ToolCategory, ToolDisplayMetadata, ToolPart, ToolState, ToolStatus, ToolTime, Toolbar, type ToolbarProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, UploadProgress, type UploadProgressProps, UseAuthOptions, UseAuthResult, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, UserMenu, type UserMenuProps, UserMessage, type UserMessageProps, WebSearchPreview, type WebSearchPreviewProps, WriteFilePreview, type WriteFilePreviewProps, activeSessionsAtom, addMessage, addParts, badgeVariants, bumpActiveSessionActivity, buttonVariants, clearChat, cn, codeBlockGrammar, configurePdfViewer, copyText, createAuthFetcher, detectFileFormat, fenceInfo, fileExtension, filterFileTree, findUnsupportedOpenUINode, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, formatBytes, formatCalendarDate, formatDuration, formatUptime, getActiveSession, getAllActiveSessions, getAllProjectActivity, getCodeLanguage, getFormatLabel, getSanitizedMarkdownHeadingIdFromRawFragment, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getSyntaxLanguage, getToolCategory, getToolDisplayMetadata, getToolErrorText, getTotalRunningSessionCount, hasBackgroundRunningSessions, initialsOf, isStreamingAtom, isViewerMessage, localToday, messagesAtom, numericColumns, parseCalendarDate, parseCsv, parseToolEvent, partMapAtom, registerActiveSession, resetActiveSessions, resolveFilePreviewKind, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, showsTaskPriority, tableCsv, taskProgress, taskStatus, timeAgo, toneFor, truncateText, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useHasBackgroundRunningSessions, useLiveTime, useNavbarSessions, useProjectActivity, useProjectSessions, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useSessionsByActivity, useTheme, useToast, useToolCallStream, useTotalRunningSessions, validateOpenUIJsonArtifact, validateOpenUIJsonNode };
134
+ export { ActiveProjectActivity, ActiveSessionActivityOptions, ActiveSessionConnectionOptions, ActiveSessionConnectionState, ActiveSessionReconnectState, ActiveSessionRecord, ActiveSessionStatus, ActiveSessionTransportMode, ActiveSessionsState, AgentBranding, AgentStreamEvent, AgentTimeline, type AgentTimelineArtifactItem, type AgentTimelineCustomItem, type AgentTimelineItem, type AgentTimelineMessageItem, type AgentTimelineProps, type AgentTimelineStatusItem, type AgentTimelineTone, type AgentTimelineToolGroupItem, type AgentTimelineToolItem, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, type AlertProps, AlertTitle, type AlertTone, AppendUserMessageOptions, ApplySdkEventOptions, ApprovalCard, type ApprovalCardProps, type ApprovalDiffFile, ApprovalDiffSummary, type ApprovalPlanStep, ArtifactPane, type ArtifactPaneProps, AuthHeader, type AuthHeaderProps, AuthUser, AutomationStreamEvent, Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, BeginAssistantMessageOptions, BotStreamEvent, Button, type ButtonProps, CATEGORY_TONES, CSV_PREVIEW_ROW_LIMIT, Calendar, type CalendarDate, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CardTitleProps, type CategoryTone, ChatAuthor, ChatContainer, type ChatContainerProps, ChatMessage, type ChatMessageProps, Checkbox, Chip, type ChipProps, CodeBlock, type CodeBlockProps, Collapsible, CollapsibleContent, CollapsibleTrigger, CommandPreview, type CommandPreviewProps, CompleteAssistantMessageOptions, ConnectionState, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, CustomToolRenderer, DatePicker, type DatePickerProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffPreview, type DiffPreviewProps, DisplayVariant, DropZone, type DropZoneProps, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, ExpandedToolDetail, type ExpandedToolDetailProps, type FeedSegment, FileArtifactPane, type FileArtifactPaneProps, FileCard, type FileCardProps, type FileDetails, type FileFormat, FileList, type FileListItem, type FileListProps, type FileNode, FilePreview, type FilePreviewKind, type FilePreviewProps, type FileTabData, FileTabs, type FileTabsProps, FileTree, type FileTreeProps, type FileTreeVisibilityOptions, type FileUploader, FilterField, type FilterFieldProps, FinalTextPart, GitHubLoginButton, type GitHubLoginButtonProps, GlobResultsPreview, type GlobResultsPreviewProps, GrepResultsPreview, type GrepResultsPreviewProps, GroupedMessage, Heading, type HeadingProps, type HeadingVariant, HelpText, type HelpTextProps, IconTile, type IconTileProps, InlineCode, type InlineCodeProps, InlineThinkingItem, type InlineThinkingItemProps, InlineToolItem, type InlineToolItemProps, Input, type InputProps, Label, LiveDuration, LoginLayout, type LoginLayoutProps, Logo, type LogoProps, Markdown, type MarkdownLinkChip, type MarkdownLinkChipResolver, type MarkdownProps, MessageAuthor, type MessageAuthorProps, MessageList, type MessageListProps, type MessageRole, MessageRun, MessageUser, Metric, type MetricProps, MetricStrip, type MetricStripProps, OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, type OpenUIAction, type OpenUIActionsNode, OpenUIArtifactRenderer, type OpenUIArtifactRendererProps, type OpenUIBadgeNode, type OpenUICardNode, type OpenUICodeNode, type OpenUIComponentNode, type OpenUIGridNode, type OpenUIHeadingNode, type OpenUIKeyValueNode, type OpenUIMarkdownNode, type OpenUIPrimitive, type OpenUISchemaIssue, type OpenUISchemaValidation, type OpenUISeparatorNode, type OpenUIStackNode, type OpenUIStatNode, type OpenUITableNode, type OpenUITextNode, PageHeader, type PageHeaderProps, PageShell, type PageShellProps, Pagination, type PaginationProps, PdfViewer, type PdfViewerConfig, type PdfViewerProps, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, QuestionPreview, type QuestionPreviewProps, RadioGroup, RadioGroupItem, RealtimeSessionOptions, RealtimeSessionRegistry, RealtimeSessionRegistryProps, RealtimeSessionState, RealtimeSessionTarget, ReasoningPart, type RedactedDocSegment, RedactedDocument, type RedactedDocumentData, type RedactedDocumentProps, RegisterActiveSessionOptions, type RevealResult, RichFileTree, type RichFileTreeGitEntry, type RichFileTreeGitStatus, type RichFileTreeProps, type RichFileTreeThemeVars, Run, RunGroup, type RunGroupProps, type RunPhase, RunPhaseList, RunRowShell, type RunRowShellProps, type RunRowStatus, RunRowStatusDot, RunStats, SSEEvent, SdkSessionAttachment, SdkSessionEvent, SdkSessionSeed, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, SessionMessage, SessionPart, SessionProjectKey, type SessionUser, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, type SidebarDropZoneProps, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, type StatCardProps, StatusPill, type StatusPillProps, type StatusTone, Switch, TONE_CLASSES, TOOL_CATEGORY_ICONS, TRANSCRIPT_TEXT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, type TableProps, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, type TagProps, TangleKnot, TaskList, type TaskListItem, type TaskListProps, type TaskStatus, TaskStreamEvent, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TerminalStreamEvent, TextPart, TextShimmer, type TextShimmerProps, Textarea, type TextareaProps, ThemeToggle, ThinkingIndicator, type ThinkingIndicatorProps, TimeSelect, type TimeSelectProps, type Toast, ToastContainer, ToastProvider, type Tone, type ToneClasses, type ToolCallData, ToolCallFeed, type ToolCallFeedProps, type ToolCallStatus, type ToolCallType, ToolCategory, ToolDisplayMetadata, ToolPart, ToolState, ToolStatus, ToolTime, Toolbar, type ToolbarProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UploadFile, UploadProgress, type UploadProgressProps, UseAuthOptions, UseAuthResult, UseRunGroupsOptions, UseSSEStreamOptions, UseSSEStreamResult, UseSdkSessionOptions, UseSdkSessionReturn, UseToolCallStreamReturn, UserMenu, type UserMenuProps, UserMessage, type UserMessageProps, WebSearchPreview, type WebSearchPreviewProps, WriteFilePreview, type WriteFilePreviewProps, activeSessionsAtom, addMessage, addParts, badgeVariants, bumpActiveSessionActivity, buttonVariants, clearChat, cn, codeBlockGrammar, configurePdfViewer, copyText, createAuthFetcher, detectFileFormat, fenceInfo, fileExtension, filterFileTree, findUnsupportedOpenUINode, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, formatBytes, formatCalendarDate, formatDuration, formatUptime, getActiveSession, getAllActiveSessions, getAllProjectActivity, getCodeLanguage, getFormatLabel, getSanitizedMarkdownHeadingIdFromRawFragment, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getSyntaxLanguage, getToolCategory, getToolDisplayMetadata, getToolErrorText, getTotalRunningSessionCount, hasBackgroundRunningSessions, initialsOf, isStreamingAtom, isViewerMessage, ixControl, ixControlAfter, ixControlSoft, ixField, ixRow, ixSelected, ixTabUnderline, localToday, messagesAtom, numericColumns, parseCalendarDate, parseCsv, parseToolEvent, partMapAtom, plainTextFromMarkdown, registerActiveSession, resetActiveSessions, resolveFilePreviewKind, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, showsTaskPriority, tableCsv, taskProgress, taskStatus, timeAgo, toneFor, truncateText, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useHasBackgroundRunningSessions, useLiveTime, useNavbarSessions, useProjectActivity, useProjectSessions, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useSessionsByActivity, useTheme, useToast, useToolCallStream, useTotalRunningSessions, validateOpenUIJsonArtifact, validateOpenUIJsonNode };
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { ixControl, ixControlAfter, ixControlSoft, ixField, ixRow, ixSelected, ixTabUnderline } from "./lib/interaction.js";
1
2
  import { focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset } from "./lib/focus.js";
2
3
  import { cn } from "./lib/utils.js";
3
4
  import { Button, buttonVariants } from "./primitives/button.js";
@@ -58,7 +59,7 @@ import { TOOL_CATEGORY_ICONS, getToolCategory, getToolDisplayMetadata, getToolEr
58
59
  import { useRunGroups } from "./hooks/use-run-groups.js";
59
60
  import { useRunCollapseState } from "./hooks/use-run-collapse-state.js";
60
61
  import { useAutoScroll } from "./hooks/use-auto-scroll.js";
61
- import { formatBytes, formatDuration, formatUptime, truncateText } from "./utils/format.js";
62
+ import { formatBytes, formatDuration, formatUptime, plainTextFromMarkdown, truncateText } from "./utils/format.js";
62
63
  import { CommandPreview } from "./tool-previews/command-preview.js";
63
64
  import { detectFileFormat, fileExtension, getCodeLanguage, getFormatLabel, getSyntaxLanguage, resolveFilePreviewKind } from "./files/file-format.js";
64
65
  import { WriteFilePreview } from "./tool-previews/write-file-preview.js";
@@ -114,4 +115,4 @@ import { addMessage, addParts, clearChat, isStreamingAtom, messagesAtom, partMap
114
115
  import { copyText } from "./utils/copy-text.js";
115
116
  import { timeAgo } from "./utils/time-ago.js";
116
117
  import { RedactedDocument } from "./redaction/redacted-document.js";
117
- export { AgentTimeline, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, ApprovalCard, ApprovalDiffSummary, ArtifactPane, AuthHeader, Avatar, AvatarFallback, AvatarImage, Badge, Button, CATEGORY_TONES, CSV_PREVIEW_ROW_LIMIT, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ChatContainer, ChatMessage, Checkbox, Chip, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, CommandPreview, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffPreview, DropZone, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, ExpandedToolDetail, FileArtifactPane, FileCard, FileList, FilePreview, FileTabs, FileTree, FilterField, GitHubLoginButton, GlobResultsPreview, GrepResultsPreview, Heading, HelpText, IconTile, InlineCode, InlineThinkingItem, InlineToolItem, Input, Label, LiveDuration, LoginLayout, Logo, Markdown, MessageAuthor, MessageList, Metric, MetricStrip, OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, OpenUIArtifactRenderer, PageHeader, PageShell, Pagination, PdfViewer, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, QuestionPreview, RadioGroup, RadioGroupItem, RealtimeSessionRegistry, RedactedDocument, RichFileTree, RunGroup, RunPhaseList, RunRowShell, RunRowStatusDot, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, StatusPill, Switch, TONE_CLASSES, TOOL_CATEGORY_ICONS, TRANSCRIPT_TEXT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TangleKnot, TaskList, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, Textarea, ThemeToggle, ThinkingIndicator, TimeSelect, ToastContainer, ToastProvider, ToolCallFeed, Toolbar, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadProgress, UserMenu, UserMessage, WebSearchPreview, WriteFilePreview, activeSessionsAtom, addMessage, addParts, badgeVariants, bumpActiveSessionActivity, buttonVariants, clearChat, cn, codeBlockGrammar, configurePdfViewer, copyText, createAuthFetcher, detectFileFormat, fenceInfo, fileExtension, filterFileTree, findUnsupportedOpenUINode, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, formatBytes, formatCalendarDate, formatDuration, formatUptime, getActiveSession, getAllActiveSessions, getAllProjectActivity, getCodeLanguage, getFormatLabel, getSanitizedMarkdownHeadingIdFromRawFragment, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getSyntaxLanguage, getToolCategory, getToolDisplayMetadata, getToolErrorText, getTotalRunningSessionCount, hasBackgroundRunningSessions, initialsOf, isStreamingAtom, isViewerMessage, localToday, messagesAtom, numericColumns, parseCalendarDate, parseCsv, parseToolEvent, partMapAtom, registerActiveSession, resetActiveSessions, resolveFilePreviewKind, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, showsTaskPriority, tableCsv, taskProgress, taskStatus, timeAgo, toneFor, truncateText, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useHasBackgroundRunningSessions, useLiveTime, useNavbarSessions, useProjectActivity, useProjectSessions, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useSessionsByActivity, useTheme, useToast, useToolCallStream, useTotalRunningSessions, validateOpenUIJsonArtifact, validateOpenUIJsonNode };
118
+ export { AgentTimeline, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, ApprovalCard, ApprovalDiffSummary, ArtifactPane, AuthHeader, Avatar, AvatarFallback, AvatarImage, Badge, Button, CATEGORY_TONES, CSV_PREVIEW_ROW_LIMIT, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ChatContainer, ChatMessage, Checkbox, Chip, CodeBlock, Collapsible, CollapsibleContent, CollapsibleTrigger, CommandPreview, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, DatePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DiffPreview, DropZone, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, ExpandedToolDetail, FileArtifactPane, FileCard, FileList, FilePreview, FileTabs, FileTree, FilterField, GitHubLoginButton, GlobResultsPreview, GrepResultsPreview, Heading, HelpText, IconTile, InlineCode, InlineThinkingItem, InlineToolItem, Input, Label, LiveDuration, LoginLayout, Logo, Markdown, MessageAuthor, MessageList, Metric, MetricStrip, OPENUI_NODE_JSON_SCHEMA, OPENUI_NODE_TYPES, OpenUIArtifactRenderer, PageHeader, PageShell, Pagination, PdfViewer, Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger, Progress, QuestionPreview, RadioGroup, RadioGroupItem, RealtimeSessionRegistry, RedactedDocument, RichFileTree, RunGroup, RunPhaseList, RunRowShell, RunRowStatusDot, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarDropZone, Skeleton, SkeletonCard, SkeletonTable, Slider, StatCard, StatusPill, Switch, TONE_CLASSES, TOOL_CATEGORY_ICONS, TRANSCRIPT_TEXT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TangleKnot, TaskList, TerminalCursor, TerminalDisplay, TerminalInput, TerminalLine, TextShimmer, Textarea, ThemeToggle, ThinkingIndicator, TimeSelect, ToastContainer, ToastProvider, ToolCallFeed, Toolbar, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UploadProgress, UserMenu, UserMessage, WebSearchPreview, WriteFilePreview, activeSessionsAtom, addMessage, addParts, badgeVariants, bumpActiveSessionActivity, buttonVariants, clearChat, cn, codeBlockGrammar, configurePdfViewer, copyText, createAuthFetcher, detectFileFormat, fenceInfo, fileExtension, filterFileTree, findUnsupportedOpenUINode, focusField, focusFieldInvalid, focusFieldWithin, focusRing, focusRingInset, formatBytes, formatCalendarDate, formatDuration, formatUptime, getActiveSession, getAllActiveSessions, getAllProjectActivity, getCodeLanguage, getFormatLabel, getSanitizedMarkdownHeadingIdFromRawFragment, getSessionsByActivity, getSessionsForNavbar, getSessionsForProject, getSyntaxLanguage, getToolCategory, getToolDisplayMetadata, getToolErrorText, getTotalRunningSessionCount, hasBackgroundRunningSessions, initialsOf, isStreamingAtom, isViewerMessage, ixControl, ixControlAfter, ixControlSoft, ixField, ixRow, ixSelected, ixTabUnderline, localToday, messagesAtom, numericColumns, parseCalendarDate, parseCsv, parseToolEvent, partMapAtom, plainTextFromMarkdown, registerActiveSession, resetActiveSessions, resolveFilePreviewKind, setActiveSessionAttention, setActiveSessionConnection, setActiveSessionError, setActiveSessionRunning, setForegroundActiveSession, showsTaskPriority, tableCsv, taskProgress, taskStatus, timeAgo, toneFor, truncateText, unregisterActiveSession, updateActiveSessionMeta, updatePart, useActiveSession, useActiveSessions, useActiveSessionsState, useApiKey, useAuth, useAutoScroll, useDropdownMenu, useHasBackgroundRunningSessions, useLiveTime, useNavbarSessions, useProjectActivity, useProjectSessions, useRealtimeSession, useRunCollapseState, useRunGroups, useSSEStream, useSdkSession, useSessionsByActivity, useTheme, useToast, useToolCallStream, useTotalRunningSessions, validateOpenUIJsonArtifact, validateOpenUIJsonNode };
@@ -1,3 +1,4 @@
1
+ import { ixField } from "./interaction.js";
1
2
  import { focusField } from "./focus.js";
2
3
  import { cn } from "./utils.js";
3
4
  //#region src/lib/control-presentation.ts
@@ -43,6 +44,6 @@ const controlSizes = {
43
44
  compact: "h-[var(--control-height)] px-3 py-1 text-sm",
44
45
  touch: "h-11 min-h-11 min-w-11 px-4 py-2 text-base"
45
46
  };
46
- const fieldPresentation = cn(focusField, controlMotion, "min-w-0 bg-[var(--field-surface,var(--bg-input))] text-foreground", "aria-invalid:border-[var(--surface-danger-border)] aria-invalid:hover:border-[var(--surface-danger-border)] aria-invalid:focus:border-[var(--focus-border-danger)] aria-invalid:focus:ring-[var(--focus-halo-danger)]", "autofill:shadow-[inset_0_0_0_1000px_var(--bg-input)] autofill:[-webkit-text-fill-color:var(--text-primary)] autofill:caret-[var(--text-primary)]");
47
+ const fieldPresentation = cn(focusField, ixField, controlMotion, "min-w-0 text-foreground", "aria-invalid:border-[var(--surface-danger-border)] aria-invalid:hover:border-[var(--surface-danger-border)] aria-invalid:focus:border-[var(--focus-border-danger)] aria-invalid:focus:ring-[var(--focus-halo-danger)]", "autofill:shadow-[inset_0_0_0_1000px_var(--field-surface,var(--well))] autofill:[-webkit-text-fill-color:var(--text-primary)] autofill:caret-[var(--text-primary)]");
47
48
  //#endregion
48
49
  export { controlHeight, controlMotion, controlRadius, controlSizes, controlSquare, controlText, fieldPresentation, fieldSizes, fieldTouchText, touchTarget };
@@ -40,8 +40,12 @@ export declare const focusField = "border-border transition-[border-color,box-sh
40
40
  * compose your own classes instead of this helper. The field selector is
41
41
  * written out in full because Tailwind reads class names statically.
42
42
  * Base styles use its marker class to clear the inner editor fallback outline.
43
+ *
44
+ * The container is the field, so it takes `ixField` (lib/interaction.ts): its
45
+ * own well fill, the `--field-border` hairline and the green hover edge. The
46
+ * borderless field inside stays transparent.
43
47
  */
44
- export declare const focusFieldWithin = "focus-field-within border-border transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--border-strong)] has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:border-[var(--focus-border)] has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:ring-3 has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:ring-[var(--focus-halo)]";
48
+ export declare const focusFieldWithin = "focus-field-within transition-[border-color,box-shadow] duration-150 ease-out bg-[var(--field-surface,var(--well))] border-[var(--field-border)] shadow-[var(--shadow-well)] hover:border-[var(--ix-hover-border)] hover:shadow-[var(--shadow-well),var(--ix-hover)] has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:border-[var(--focus-border)] has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:ring-3 has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:ring-[var(--focus-halo)]";
45
49
  /** Error state for a field: a danger hairline and the same focus shape in danger. Compose after `focusField`. */
46
50
  export declare const focusFieldInvalid = "border-[var(--surface-danger-border)] hover:border-[var(--surface-danger-border)] focus:border-[var(--focus-border-danger)] focus:ring-[var(--focus-halo-danger)]";
47
51
  /** Keyboard-only focus for a control: a 1px focus line with the halo beyond it. */
package/dist/lib/focus.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { ixField } from "./interaction.js";
1
2
  //#region src/lib/focus.ts
2
3
  /**
3
4
  * The focus treatment every control shares, in two shapes over one palette
@@ -40,8 +41,12 @@ const focusField = "border-border transition-[border-color,box-shadow] duration-
40
41
  * compose your own classes instead of this helper. The field selector is
41
42
  * written out in full because Tailwind reads class names statically.
42
43
  * Base styles use its marker class to clear the inner editor fallback outline.
44
+ *
45
+ * The container is the field, so it takes `ixField` (lib/interaction.ts): its
46
+ * own well fill, the `--field-border` hairline and the green hover edge. The
47
+ * borderless field inside stays transparent.
43
48
  */
44
- const focusFieldWithin = "focus-field-within border-border transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--border-strong)] has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:border-[var(--focus-border)] has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:ring-3 has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:ring-[var(--focus-halo)]";
49
+ const focusFieldWithin = `focus-field-within transition-[border-color,box-shadow] duration-150 ease-out ${ixField} has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:border-[var(--focus-border)] has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:ring-3 has-[:is(textarea,select,[contenteditable]:not([contenteditable=false]),input:not([type=button],[type=submit],[type=reset],[type=image],[type=checkbox],[type=radio],[type=range],[type=color],[type=file])):focus]:ring-[var(--focus-halo)]`;
45
50
  /** Error state for a field: a danger hairline and the same focus shape in danger. Compose after `focusField`. */
46
51
  const focusFieldInvalid = "border-[var(--surface-danger-border)] hover:border-[var(--surface-danger-border)] focus:border-[var(--focus-border-danger)] focus:ring-[var(--focus-halo-danger)]";
47
52
  const focusRingBase = "focus-visible:outline-1 focus-visible:outline-[var(--focus-border)] focus-visible:ring-4 focus-visible:ring-[var(--focus-halo)]";