typewright 0.2.1

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 (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +168 -0
  3. package/SPEC.md +327 -0
  4. package/dist/chunk-CZQO7TTL.js +1533 -0
  5. package/dist/chunk-CZQO7TTL.js.map +1 -0
  6. package/dist/chunk-KNEKNZXK.js +286 -0
  7. package/dist/chunk-KNEKNZXK.js.map +1 -0
  8. package/dist/chunk-M6IVEMXO.js +1398 -0
  9. package/dist/chunk-M6IVEMXO.js.map +1 -0
  10. package/dist/chunk-NJ7PJKHN.js +652 -0
  11. package/dist/chunk-NJ7PJKHN.js.map +1 -0
  12. package/dist/commands-Z3ndUSza.d.cts +221 -0
  13. package/dist/commands-Z3ndUSza.d.ts +221 -0
  14. package/dist/core/index.cjs +2952 -0
  15. package/dist/core/index.cjs.map +1 -0
  16. package/dist/core/index.d.cts +410 -0
  17. package/dist/core/index.d.ts +410 -0
  18. package/dist/core/index.js +4 -0
  19. package/dist/core/index.js.map +1 -0
  20. package/dist/mdx/index.cjs +662 -0
  21. package/dist/mdx/index.cjs.map +1 -0
  22. package/dist/mdx/index.d.cts +252 -0
  23. package/dist/mdx/index.d.ts +252 -0
  24. package/dist/mdx/index.js +3 -0
  25. package/dist/mdx/index.js.map +1 -0
  26. package/dist/react/index.cjs +7439 -0
  27. package/dist/react/index.cjs.map +1 -0
  28. package/dist/react/index.d.cts +140 -0
  29. package/dist/react/index.d.ts +140 -0
  30. package/dist/react/index.js +3644 -0
  31. package/dist/react/index.js.map +1 -0
  32. package/dist/streaming/index.cjs +1494 -0
  33. package/dist/streaming/index.cjs.map +1 -0
  34. package/dist/streaming/index.d.cts +64 -0
  35. package/dist/streaming/index.d.ts +64 -0
  36. package/dist/streaming/index.js +4 -0
  37. package/dist/streaming/index.js.map +1 -0
  38. package/dist/types-CX1GOx0Y.d.cts +319 -0
  39. package/dist/types-CX1GOx0Y.d.ts +319 -0
  40. package/package.json +128 -0
@@ -0,0 +1,3644 @@
1
+ import { COMMANDS, highlightToHtml, mapAnchor, applyCommand, setAlignment, removeColumn, removeRow, addColumn, addRow, cellSourceRange, collectMarkers, hiddenMarkers } from '../chunk-CZQO7TTL.js';
2
+ import { createStreamController, pipeStream, anticipate } from '../chunk-KNEKNZXK.js';
3
+ import { parse, renderToHtml, renderNode, parseIncremental, renderInline, walk, safeUrl } from '../chunk-M6IVEMXO.js';
4
+ import { createSandbox, resolveTransform } from '../chunk-NJ7PJKHN.js';
5
+ import * as React7 from 'react';
6
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
+
8
+ function wrapBlock(block) {
9
+ return { type: "document", from: block.from, to: block.to, children: [block] };
10
+ }
11
+ function firstTextIn(node) {
12
+ if (node.nodeType === 3) return node;
13
+ const w = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_TEXT);
14
+ return w.nextNode() ?? null;
15
+ }
16
+ function lastTextIn(node) {
17
+ if (node.nodeType === 3) return node;
18
+ const w = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_TEXT);
19
+ let last = null;
20
+ let n = w.nextNode();
21
+ while (n) {
22
+ last = n;
23
+ n = w.nextNode();
24
+ }
25
+ return last;
26
+ }
27
+ function collectFmtTags(doc) {
28
+ const out = [];
29
+ walk(doc, (n) => {
30
+ switch (n.type) {
31
+ case "emphasis":
32
+ case "strong":
33
+ case "strikethrough": {
34
+ const kids = n.children;
35
+ const first = kids[0];
36
+ const last = kids[kids.length - 1];
37
+ if (first && last) {
38
+ const tag = n.type === "emphasis" ? "em" : n.type === "strong" ? "strong" : "del";
39
+ out.push({ node: n, tag, from: first.from, to: last.to });
40
+ }
41
+ break;
42
+ }
43
+ case "inlineCode": {
44
+ const ticks = Math.max(1, n.ticks);
45
+ const from = Math.min(n.from + ticks, n.to);
46
+ const to = Math.max(n.to - ticks, from);
47
+ out.push({ node: n, tag: "code", from, to });
48
+ break;
49
+ }
50
+ case "link":
51
+ case "autolink":
52
+ out.push({ node: n, tag: "a", from: n.from, to: n.to, href: safeUrl(n.url) });
53
+ break;
54
+ case "image":
55
+ out.push({ node: n, tag: "span", className: "tw-cr-img", from: n.from, to: n.to });
56
+ break;
57
+ case "math":
58
+ out.push({ node: n, tag: "span", className: "tw-math-src", from: n.from, to: n.to });
59
+ break;
60
+ case "footnoteRef":
61
+ out.push({ node: n, tag: "sup", className: "tw-fnref", from: n.from, to: n.to });
62
+ break;
63
+ }
64
+ return true;
65
+ });
66
+ return out;
67
+ }
68
+ function tagsAt(fmts, from, to) {
69
+ return fmts.filter((f) => f.from <= from && to <= f.to && f.to > f.from).sort((a, b) => a.from - b.from || b.to - a.to);
70
+ }
71
+ function buildSegments(block, source) {
72
+ const doc = wrapBlock(block);
73
+ const markers = collectMarkers(doc, source).filter((m) => m.from >= block.from && m.to <= block.to && m.to > m.from).sort((a, b) => a.from - b.from || a.to - b.to);
74
+ const fmts = collectFmtTags(doc);
75
+ const segs = [];
76
+ let cur = block.from;
77
+ const pushContent = (from, to) => {
78
+ if (to > from) segs.push({ from, to, text: source.slice(from, to), kind: "content", tags: tagsAt(fmts, from, to) });
79
+ };
80
+ for (const m of markers) {
81
+ if (m.to <= cur) continue;
82
+ const from = Math.max(m.from, cur);
83
+ if (from > cur) pushContent(cur, from);
84
+ segs.push({ from, to: m.to, text: source.slice(from, m.to), kind: "marker", markerKind: m.kind, tags: tagsAt(fmts, from, m.to) });
85
+ cur = m.to;
86
+ }
87
+ pushContent(cur, block.to);
88
+ return segs;
89
+ }
90
+ function markerKeyLocal(from, to, blockFrom) {
91
+ return `${from - blockFrom}:${to - blockFrom}`;
92
+ }
93
+ function hiddenKeySet(block, source, sel) {
94
+ const doc = wrapBlock(block);
95
+ const set = /* @__PURE__ */ new Set();
96
+ if (!sel) {
97
+ for (const m of collectMarkers(doc, source)) {
98
+ if (m.from >= block.from && m.to <= block.to && m.to > m.from) set.add(markerKeyLocal(m.from, m.to, block.from));
99
+ }
100
+ return set;
101
+ }
102
+ for (const m of hiddenMarkers(doc, sel, source)) {
103
+ if (m.from >= block.from && m.to <= block.to && m.to > m.from) set.add(markerKeyLocal(m.from, m.to, block.from));
104
+ }
105
+ return set;
106
+ }
107
+ function flattenText(root) {
108
+ const out = [];
109
+ const w = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_TEXT);
110
+ let n = w.nextNode();
111
+ while (n) {
112
+ out.push(n);
113
+ n = w.nextNode();
114
+ }
115
+ return out;
116
+ }
117
+ function reconstructSource(root) {
118
+ let s = "";
119
+ for (const t of flattenText(root)) s += t.data;
120
+ return s;
121
+ }
122
+ function offsetOfPoint(root, node, offset) {
123
+ const texts = flattenText(root);
124
+ let target = null;
125
+ let local = 0;
126
+ if (node.nodeType === 3) {
127
+ target = node;
128
+ local = offset;
129
+ } else {
130
+ const child = node.childNodes[offset] ?? null;
131
+ if (child) {
132
+ target = firstTextIn(child);
133
+ local = 0;
134
+ if (!target) {
135
+ const prev = node.childNodes[offset - 1];
136
+ target = prev ? lastTextIn(prev) : null;
137
+ local = target ? target.data.length : 0;
138
+ }
139
+ } else {
140
+ const prev = node.childNodes[node.childNodes.length - 1];
141
+ target = prev ? lastTextIn(prev) : null;
142
+ local = target ? target.data.length : 0;
143
+ }
144
+ }
145
+ if (!target) return 0;
146
+ let acc = 0;
147
+ for (const t of texts) {
148
+ if (t === target) return acc + Math.min(local, t.data.length);
149
+ acc += t.data.length;
150
+ }
151
+ return acc;
152
+ }
153
+ function pointAtOffset(root, offset) {
154
+ const texts = flattenText(root);
155
+ let acc = 0;
156
+ for (const t of texts) {
157
+ const len = t.data.length;
158
+ if (offset <= acc + len) return { node: t, offset: Math.max(0, offset - acc) };
159
+ acc += len;
160
+ }
161
+ const last = texts[texts.length - 1];
162
+ return last ? { node: last, offset: last.data.length } : { node: root, offset: 0 };
163
+ }
164
+ function computeSplice(oldStr, newStr) {
165
+ let p = 0;
166
+ const maxP = Math.min(oldStr.length, newStr.length);
167
+ while (p < maxP && oldStr.charCodeAt(p) === newStr.charCodeAt(p)) p++;
168
+ let s = 0;
169
+ const maxS = Math.min(oldStr.length - p, newStr.length - p);
170
+ while (s < maxS && oldStr.charCodeAt(oldStr.length - 1 - s) === newStr.charCodeAt(newStr.length - 1 - s)) s++;
171
+ return { from: p, to: oldStr.length - s, insert: newStr.slice(p, newStr.length - s) };
172
+ }
173
+ function isHidden(hidden, key) {
174
+ return hidden === "all" || hidden.has(key);
175
+ }
176
+ function paintSegments(root, segs, hidden, blockFrom) {
177
+ const doc = root.ownerDocument;
178
+ while (root.firstChild) root.removeChild(root.firstChild);
179
+ const stack = [];
180
+ for (const seg of segs) {
181
+ let common = 0;
182
+ while (common < stack.length && common < seg.tags.length && stack[common].node === seg.tags[common].node) common++;
183
+ stack.length = common;
184
+ let parent = common > 0 ? stack[common - 1].el : root;
185
+ for (let k = common; k < seg.tags.length; k++) {
186
+ const t = seg.tags[k];
187
+ const el = doc.createElement(t.tag);
188
+ if (t.href !== void 0) el.setAttribute("href", t.href);
189
+ if (t.className) el.className = t.className;
190
+ parent.appendChild(el);
191
+ stack.push({ node: t.node, el });
192
+ parent = el;
193
+ }
194
+ if (seg.kind === "marker") {
195
+ const span = doc.createElement("span");
196
+ span.className = "tw-syntax";
197
+ span.setAttribute("data-mark", seg.markerKind ?? "");
198
+ const key = markerKeyLocal(seg.from, seg.to, blockFrom);
199
+ span.setAttribute("data-from", String(seg.from - blockFrom));
200
+ span.setAttribute("data-to", String(seg.to - blockFrom));
201
+ span.appendChild(doc.createTextNode(seg.text));
202
+ if (isHidden(hidden, key)) span.classList.add("tw-syntax--hidden");
203
+ parent.appendChild(span);
204
+ } else {
205
+ parent.appendChild(doc.createTextNode(seg.text));
206
+ }
207
+ }
208
+ }
209
+ function applyReveal(root, hidden) {
210
+ const spans = root.querySelectorAll("span.tw-syntax[data-from]");
211
+ spans.forEach((span) => {
212
+ const key = `${span.getAttribute("data-from")}:${span.getAttribute("data-to")}`;
213
+ span.classList.toggle("tw-syntax--hidden", isHidden(hidden, key));
214
+ });
215
+ }
216
+ var CLICK_SETTLE_MS = 40;
217
+ var CLICK_WINDOW_MS = 250;
218
+ var SETTLE_MS = 140;
219
+ function blockClass(block) {
220
+ let c = "tw-caret-block";
221
+ switch (block.type) {
222
+ case "heading":
223
+ c += ` tw-cr-heading tw-cr-h${block.level}`;
224
+ break;
225
+ case "blockquote":
226
+ c += " tw-cr-blockquote";
227
+ break;
228
+ case "list":
229
+ c += " tw-cr-list";
230
+ break;
231
+ default:
232
+ c += " tw-cr-paragraph";
233
+ }
234
+ return c;
235
+ }
236
+ function CaretRevealBlock(props) {
237
+ const { block, source, onChange, readOnly = false } = props;
238
+ const rootRef = React7.useRef(null);
239
+ const propsRef = React7.useRef(props);
240
+ propsRef.current = props;
241
+ const editingRef = React7.useRef(false);
242
+ const composingRef = React7.useRef(false);
243
+ const blockSrcRef = React7.useRef(source.slice(block.from, block.to));
244
+ const blockFromRef = React7.useRef(block.from);
245
+ const builtKeyRef = React7.useRef(null);
246
+ const lastPointerRef = React7.useRef(0);
247
+ const settleTimer = React7.useRef(null);
248
+ const revealTimer = React7.useRef(null);
249
+ const onChangeRef = React7.useRef(onChange);
250
+ onChangeRef.current = onChange;
251
+ const readSelection = React7.useCallback(() => {
252
+ const root = rootRef.current;
253
+ if (!root) return null;
254
+ const win = root.ownerDocument?.defaultView;
255
+ const sel = win?.getSelection();
256
+ if (!sel || sel.rangeCount === 0 || !sel.anchorNode || !root.contains(sel.anchorNode)) return null;
257
+ const base = blockFromRef.current;
258
+ const a = offsetOfPoint(root, sel.anchorNode, sel.anchorOffset) + base;
259
+ const f = sel.focusNode && root.contains(sel.focusNode) ? offsetOfPoint(root, sel.focusNode, sel.focusOffset) + base : a;
260
+ return { from: Math.min(a, f), to: Math.max(a, f) };
261
+ }, []);
262
+ const restoreCaret = React7.useCallback((docOffset) => {
263
+ const root = rootRef.current;
264
+ if (!root) return;
265
+ const win = root.ownerDocument?.defaultView;
266
+ if (!win) return;
267
+ let { node, offset } = pointAtOffset(root, docOffset - blockFromRef.current);
268
+ const inHidden = (n) => (n.nodeType === 3 ? n.parentElement : n)?.closest(".tw-syntax--hidden") != null;
269
+ if (inHidden(node)) {
270
+ const texts = flattenText(root);
271
+ const idx = texts.indexOf(node);
272
+ let j = idx + 1;
273
+ while (j < texts.length && inHidden(texts[j])) j++;
274
+ if (j < texts.length) {
275
+ node = texts[j];
276
+ offset = 0;
277
+ } else {
278
+ let k = idx - 1;
279
+ while (k >= 0 && inHidden(texts[k])) k--;
280
+ if (k >= 0) {
281
+ node = texts[k];
282
+ offset = texts[k].data.length;
283
+ }
284
+ }
285
+ }
286
+ try {
287
+ const range = root.ownerDocument.createRange();
288
+ range.setStart(node, Math.min(offset, node.nodeType === 3 ? node.data.length : node.childNodes.length));
289
+ range.collapse(true);
290
+ const sel = win.getSelection();
291
+ sel?.removeAllRanges();
292
+ sel?.addRange(range);
293
+ } catch {
294
+ }
295
+ }, []);
296
+ const paint = React7.useCallback((b, src, sel) => {
297
+ const root = rootRef.current;
298
+ if (!root) return;
299
+ const segs = buildSegments(b, src);
300
+ const hidden = sel ? hiddenKeySet(b, src, sel) : "all";
301
+ paintSegments(root, segs, hidden, b.from);
302
+ blockSrcRef.current = src.slice(b.from, b.to);
303
+ blockFromRef.current = b.from;
304
+ builtKeyRef.current = `${b.from}:${blockSrcRef.current}`;
305
+ }, []);
306
+ const updateReveal = React7.useCallback(() => {
307
+ const root = rootRef.current;
308
+ if (!root) return;
309
+ const { block: b, source: src } = propsRef.current;
310
+ const sel = readSelection();
311
+ applyReveal(root, sel ? hiddenKeySet(b, src, sel) : "all");
312
+ }, [readSelection]);
313
+ const syncModel = React7.useCallback(() => {
314
+ const root = rootRef.current;
315
+ if (!root) return;
316
+ const next = reconstructSource(root);
317
+ const prev = blockSrcRef.current;
318
+ if (next === prev) return;
319
+ const sp = computeSplice(prev, next);
320
+ blockSrcRef.current = next;
321
+ const base = blockFromRef.current;
322
+ onChangeRef.current({ from: base + sp.from, to: base + sp.to, insert: sp.insert });
323
+ }, []);
324
+ const settleRetokenize = React7.useCallback(() => {
325
+ const root = rootRef.current;
326
+ if (!root || composingRef.current || !editingRef.current) return;
327
+ const { block: b, source: src } = propsRef.current;
328
+ const committed = src.slice(b.from, b.to);
329
+ const domSrc = reconstructSource(root);
330
+ if (committed === domSrc) {
331
+ const sel = readSelection();
332
+ paint(b, src, sel);
333
+ if (sel) restoreCaret(sel.to);
334
+ return;
335
+ }
336
+ if (domSrc.length > committed.length && domSrc.startsWith(committed) && src.startsWith(domSrc, b.from)) {
337
+ const sel = readSelection();
338
+ paint(b, src, null);
339
+ if (sel) restoreCaret(Math.min(sel.to, b.to));
340
+ }
341
+ }, [paint, readSelection, restoreCaret]);
342
+ const scheduleSettle = React7.useCallback(() => {
343
+ const win = rootRef.current?.ownerDocument?.defaultView;
344
+ if (!win) return;
345
+ if (settleTimer.current != null) win.clearTimeout(settleTimer.current);
346
+ settleTimer.current = win.setTimeout(() => {
347
+ settleTimer.current = null;
348
+ settleRetokenize();
349
+ }, SETTLE_MS);
350
+ }, [settleRetokenize]);
351
+ React7.useLayoutEffect(() => {
352
+ if (editingRef.current) return;
353
+ const key = `${block.from}:${source.slice(block.from, block.to)}`;
354
+ if (builtKeyRef.current === key) {
355
+ blockSrcRef.current = source.slice(block.from, block.to);
356
+ blockFromRef.current = block.from;
357
+ return;
358
+ }
359
+ paint(block, source, null);
360
+ }, [block, source, readOnly, paint]);
361
+ React7.useEffect(() => {
362
+ const root = rootRef.current;
363
+ const doc = root?.ownerDocument;
364
+ if (!doc) return void 0;
365
+ const win = doc.defaultView;
366
+ const onSelectionChange = () => {
367
+ if (!editingRef.current || composingRef.current) return;
368
+ const sel = win?.getSelection();
369
+ if (!sel || !sel.anchorNode || !root.contains(sel.anchorNode)) return;
370
+ const clickInitiated = Date.now() - lastPointerRef.current < CLICK_WINDOW_MS;
371
+ if (revealTimer.current != null) win?.clearTimeout(revealTimer.current);
372
+ if (clickInitiated && win) {
373
+ revealTimer.current = win.setTimeout(() => {
374
+ revealTimer.current = null;
375
+ updateReveal();
376
+ }, CLICK_SETTLE_MS);
377
+ } else {
378
+ updateReveal();
379
+ }
380
+ };
381
+ doc.addEventListener("selectionchange", onSelectionChange);
382
+ return () => {
383
+ doc.removeEventListener("selectionchange", onSelectionChange);
384
+ if (win) {
385
+ if (revealTimer.current != null) win.clearTimeout(revealTimer.current);
386
+ if (settleTimer.current != null) win.clearTimeout(settleTimer.current);
387
+ }
388
+ };
389
+ }, [updateReveal]);
390
+ const onFocus = React7.useCallback(() => {
391
+ editingRef.current = true;
392
+ updateReveal();
393
+ }, [updateReveal]);
394
+ const onBlur = React7.useCallback(() => {
395
+ editingRef.current = false;
396
+ const win = rootRef.current?.ownerDocument?.defaultView;
397
+ if (win) {
398
+ if (settleTimer.current != null) win.clearTimeout(settleTimer.current);
399
+ if (revealTimer.current != null) win.clearTimeout(revealTimer.current);
400
+ }
401
+ syncModel();
402
+ composingRef.current = false;
403
+ const { block: b, source: src } = propsRef.current;
404
+ builtKeyRef.current = null;
405
+ paint(b, src, null);
406
+ }, [paint, syncModel]);
407
+ const onInput = React7.useCallback(() => {
408
+ if (composingRef.current) return;
409
+ syncModel();
410
+ scheduleSettle();
411
+ }, [syncModel, scheduleSettle]);
412
+ const onCompositionStart = React7.useCallback(() => {
413
+ composingRef.current = true;
414
+ }, []);
415
+ const onCompositionEnd = React7.useCallback(() => {
416
+ composingRef.current = false;
417
+ syncModel();
418
+ scheduleSettle();
419
+ }, [syncModel, scheduleSettle]);
420
+ const onPointerDown = React7.useCallback(() => {
421
+ lastPointerRef.current = Date.now();
422
+ }, []);
423
+ const onKeyDown = React7.useCallback((e) => {
424
+ if (readOnly) return;
425
+ if (e.key === "Enter") {
426
+ e.preventDefault();
427
+ insertPlainText(e.currentTarget, "\n");
428
+ return;
429
+ }
430
+ if (e.key === "Backspace") {
431
+ const sel = readSelection();
432
+ const { block: b, source: src } = propsRef.current;
433
+ if (sel && sel.from === sel.to && sel.from === b.from && b.from > 0 && !composingRef.current) {
434
+ let sepStart = b.from;
435
+ while (sepStart > 0 && (src[sepStart - 1] === "\n" || src[sepStart - 1] === " " || src[sepStart - 1] === " ")) sepStart--;
436
+ if (sepStart < b.from) {
437
+ e.preventDefault();
438
+ onChangeRef.current({ from: sepStart, to: b.from, insert: "" });
439
+ }
440
+ }
441
+ }
442
+ }, [readOnly, readSelection]);
443
+ const onPaste = React7.useCallback((e) => {
444
+ if (readOnly) return;
445
+ e.preventDefault();
446
+ const text = e.clipboardData.getData("text/plain");
447
+ if (text) insertPlainText(e.currentTarget, text);
448
+ }, [readOnly]);
449
+ const label = block.type === "heading" ? `Heading ${block.level}` : block.type === "blockquote" ? "Blockquote" : block.type === "list" ? "List" : "Paragraph";
450
+ return /* @__PURE__ */ jsx(
451
+ "div",
452
+ {
453
+ ref: rootRef,
454
+ className: blockClass(block),
455
+ "data-typewright": "caret-block",
456
+ "data-block-type": block.type,
457
+ "data-tw-from": block.from,
458
+ "data-tw-to": block.to,
459
+ role: "textbox",
460
+ "aria-multiline": "true",
461
+ "aria-label": `${label} (Markdown, source reveals at the caret)`,
462
+ contentEditable: !readOnly,
463
+ suppressContentEditableWarning: true,
464
+ spellCheck: false,
465
+ tabIndex: readOnly ? void 0 : 0,
466
+ onFocus: readOnly ? void 0 : onFocus,
467
+ onBlur: readOnly ? void 0 : onBlur,
468
+ onInput: readOnly ? void 0 : onInput,
469
+ onCompositionStart: readOnly ? void 0 : onCompositionStart,
470
+ onCompositionEnd: readOnly ? void 0 : onCompositionEnd,
471
+ onPointerDown: readOnly ? void 0 : onPointerDown,
472
+ onKeyDown: readOnly ? void 0 : onKeyDown,
473
+ onPaste: readOnly ? void 0 : onPaste
474
+ }
475
+ );
476
+ }
477
+ function insertPlainText(root, text) {
478
+ const doc = root.ownerDocument;
479
+ const win = doc?.defaultView;
480
+ const exec = doc?.execCommand;
481
+ if (!text.includes("\n") && doc && typeof exec === "function") {
482
+ try {
483
+ if (exec.call(doc, "insertText", false, text)) return;
484
+ } catch {
485
+ }
486
+ }
487
+ const sel = win?.getSelection();
488
+ if (!sel || sel.rangeCount === 0) return;
489
+ const range = sel.getRangeAt(0);
490
+ range.deleteContents();
491
+ const node = doc.createTextNode(text);
492
+ range.insertNode(node);
493
+ range.setStartAfter(node);
494
+ range.collapse(true);
495
+ sel.removeAllRanges();
496
+ sel.addRange(range);
497
+ root.dispatchEvent(new Event("input", { bubbles: true }));
498
+ }
499
+ var CARET_REVEAL_CSS = `
500
+ .tw-caret-block{white-space:pre-wrap;overflow-wrap:break-word;outline:none;cursor:text;border-radius:6px;padding:1px 4px;margin-left:-4px;transition:background .15s}
501
+ .tw-mode-unified .tw-caret-block:hover{background:var(--tw-accent-soft)}
502
+ .tw-caret-block:focus-visible{outline:2px solid var(--tw-accent);outline-offset:2px}
503
+ .tw-caret-block.tw-cr-heading{font-weight:680;letter-spacing:-.02em;line-height:1.25}
504
+ .tw-caret-block.tw-cr-h1{font-size:1.8em} .tw-caret-block.tw-cr-h2{font-size:1.45em} .tw-caret-block.tw-cr-h3{font-size:1.2em} .tw-caret-block.tw-cr-h4{font-size:1.05em} .tw-caret-block.tw-cr-h5{font-size:1em} .tw-caret-block.tw-cr-h6{font-size:.92em;color:var(--tw-muted)}
505
+ .tw-caret-block.tw-cr-blockquote{border-left:3px solid var(--tw-line);padding-left:14px;color:var(--tw-muted)}
506
+ .tw-caret-block.tw-cr-list{padding-left:6px}
507
+ .tw-caret-block a{color:var(--tw-accent);text-decoration:none;border-bottom:1px solid var(--tw-accent-soft)}
508
+ .tw-caret-block code{font-family:"SF Mono",ui-monospace,Menlo,monospace;font-size:.88em;background:var(--tw-chip);border:1px solid var(--tw-line);border-radius:5px;padding:1px 5px}
509
+ .tw-caret-block .tw-cr-img{color:var(--tw-muted);font-style:italic}
510
+ .tw-caret-block .tw-fnref{font-size:.82em;color:var(--tw-accent)}
511
+ /* Revealed marker: subtle, monospace, "you are editing raw syntax". */
512
+ .tw-syntax{font-family:"SF Mono",ui-monospace,Menlo,monospace;color:var(--tw-faint);opacity:.85;font-weight:400;font-style:normal}
513
+ /* Hidden marker: collapsed out of layout, but its source chars stay in the DOM
514
+ (and in this component's offset mapping) \u2014 never removed from the string. */
515
+ .tw-syntax--hidden{display:none}
516
+ @media (prefers-reduced-motion: reduce){ .tw-caret-block{transition:none} }
517
+ `;
518
+ var REACT_EMOJI = ["\u{1F44D}", "\u{1F3AF}", "\u{1F440}", "\u{1F389}"];
519
+ function CommentsSidebar(props) {
520
+ const {
521
+ threads,
522
+ me,
523
+ open,
524
+ onClose,
525
+ onReply,
526
+ onReact,
527
+ onResolve,
528
+ onDelete,
529
+ activeThreadId,
530
+ onSelectThread
531
+ } = props;
532
+ const activeElRef = React7.useRef(null);
533
+ const [flashId, setFlashId] = React7.useState(void 0);
534
+ React7.useEffect(() => {
535
+ if (!open || !activeThreadId) return;
536
+ activeElRef.current?.scrollIntoView({ block: "nearest", behavior: "smooth" });
537
+ setFlashId(activeThreadId);
538
+ const t = setTimeout(() => setFlashId(void 0), 1400);
539
+ return () => clearTimeout(t);
540
+ }, [open, activeThreadId]);
541
+ if (!open) return null;
542
+ const count = threads.length;
543
+ return /* @__PURE__ */ jsxs(
544
+ "aside",
545
+ {
546
+ className: "tw-comments-sidebar",
547
+ role: "complementary",
548
+ "aria-label": "Comments",
549
+ "data-typewright": "comments",
550
+ children: [
551
+ /* @__PURE__ */ jsxs("header", { className: "tw-comments-head", children: [
552
+ /* @__PURE__ */ jsx("span", { className: "tw-comments-title", children: "Comments" }),
553
+ /* @__PURE__ */ jsx("span", { className: "tw-comments-count", "aria-label": `${count} comment${count === 1 ? "" : "s"}`, children: count }),
554
+ /* @__PURE__ */ jsx(
555
+ "button",
556
+ {
557
+ type: "button",
558
+ className: "tw-comments-close",
559
+ "aria-label": "Close comments panel",
560
+ onClick: onClose,
561
+ children: /* @__PURE__ */ jsx("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", strokeLinecap: "round", children: /* @__PURE__ */ jsx("path", { d: "M6 6l12 12M18 6L6 18" }) })
562
+ }
563
+ )
564
+ ] }),
565
+ /* @__PURE__ */ jsx("div", { className: "tw-comments-body", children: count === 0 ? /* @__PURE__ */ jsxs("div", { className: "tw-comments-empty", children: [
566
+ "No comments yet.",
567
+ /* @__PURE__ */ jsx("br", {}),
568
+ "Select text in the editor to start a thread."
569
+ ] }) : threads.map((thread) => /* @__PURE__ */ jsx(
570
+ ThreadCard,
571
+ {
572
+ thread,
573
+ me,
574
+ active: thread.id === activeThreadId,
575
+ flash: thread.id === flashId,
576
+ innerRef: thread.id === activeThreadId ? (el) => {
577
+ activeElRef.current = el;
578
+ } : void 0,
579
+ onReply,
580
+ onReact,
581
+ onResolve,
582
+ onDelete,
583
+ onSelect: onSelectThread
584
+ },
585
+ thread.id
586
+ )) })
587
+ ]
588
+ }
589
+ );
590
+ }
591
+ function ThreadCard(props) {
592
+ const { thread, me, active, flash, innerRef, onReply, onReact, onResolve, onDelete, onSelect } = props;
593
+ const [draft, setDraft] = React7.useState("");
594
+ const submitReply = React7.useCallback(() => {
595
+ const body = draft.trim();
596
+ if (!body) return;
597
+ onReply(thread.id, body);
598
+ setDraft("");
599
+ }, [draft, onReply, thread.id]);
600
+ const cls = [
601
+ "tw-comment-thread",
602
+ thread.resolved ? "resolved" : "",
603
+ active ? "tw-comment-thread--active" : "",
604
+ flash ? "tw-comment-thread--flash" : ""
605
+ ].filter(Boolean).join(" ");
606
+ const selectFromCard = (e) => {
607
+ if (!onSelect) return;
608
+ if (e.target.closest("button, textarea, input, a")) return;
609
+ onSelect(thread.id);
610
+ };
611
+ return /* @__PURE__ */ jsxs("article", { ref: innerRef, className: cls, onClick: selectFromCard, children: [
612
+ thread.quote ? /* @__PURE__ */ jsxs("div", { className: "tw-comment-quote", children: [
613
+ "\u201C",
614
+ thread.quote,
615
+ "\u201D"
616
+ ] }) : null,
617
+ /* @__PURE__ */ jsx(CommentRow, { author: thread.author, body: thread.body, createdAt: thread.createdAt }),
618
+ /* @__PURE__ */ jsx("div", { className: "tw-comment-reacts", children: REACT_EMOJI.map((emoji) => {
619
+ const users = thread.reactions?.[emoji];
620
+ const n = users?.length ?? 0;
621
+ const mine = !!(me && users?.includes(me.id));
622
+ return /* @__PURE__ */ jsxs(
623
+ "button",
624
+ {
625
+ type: "button",
626
+ className: "tw-comment-react",
627
+ "aria-pressed": mine,
628
+ "aria-label": `React ${emoji}${n ? `, ${n}` : ""}`,
629
+ onClick: () => onReact(thread.id, emoji),
630
+ children: [
631
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: emoji }),
632
+ n > 0 ? /* @__PURE__ */ jsx("span", { className: "tw-comment-react-n", children: n }) : null
633
+ ]
634
+ },
635
+ emoji
636
+ );
637
+ }) }),
638
+ thread.replies.length > 0 ? /* @__PURE__ */ jsx("div", { className: "tw-comment-replies", children: thread.replies.map((r) => /* @__PURE__ */ jsx(CommentRow, { author: r.author, body: r.body, createdAt: r.createdAt }, r.id)) }) : null,
639
+ /* @__PURE__ */ jsxs("div", { className: "tw-comment-reply", children: [
640
+ /* @__PURE__ */ jsx(
641
+ "textarea",
642
+ {
643
+ value: draft,
644
+ onChange: (e) => setDraft(e.currentTarget.value),
645
+ onKeyDown: (e) => {
646
+ if (e.key === "Enter" && !e.shiftKey) {
647
+ e.preventDefault();
648
+ submitReply();
649
+ }
650
+ },
651
+ placeholder: "Reply\u2026",
652
+ "aria-label": "Reply to thread",
653
+ rows: 1
654
+ }
655
+ ),
656
+ /* @__PURE__ */ jsx(
657
+ "button",
658
+ {
659
+ type: "button",
660
+ className: "tw-comment-btn",
661
+ "aria-label": "Send reply",
662
+ disabled: !draft.trim(),
663
+ onClick: submitReply,
664
+ children: "Send"
665
+ }
666
+ )
667
+ ] }),
668
+ /* @__PURE__ */ jsxs("div", { className: "tw-comment-actions", children: [
669
+ onDelete ? /* @__PURE__ */ jsx(
670
+ "button",
671
+ {
672
+ type: "button",
673
+ className: "tw-comment-btn tw-comment-del",
674
+ "aria-label": "Delete thread",
675
+ onClick: () => onDelete(thread.id),
676
+ children: "Delete"
677
+ }
678
+ ) : null,
679
+ /* @__PURE__ */ jsx(
680
+ "button",
681
+ {
682
+ type: "button",
683
+ className: "tw-comment-btn",
684
+ "aria-label": thread.resolved ? "Reopen thread" : "Resolve thread",
685
+ "aria-pressed": !!thread.resolved,
686
+ onClick: () => onResolve(thread.id, !thread.resolved),
687
+ children: thread.resolved ? "Reopen" : "Resolve"
688
+ }
689
+ )
690
+ ] })
691
+ ] });
692
+ }
693
+ function CommentRow(props) {
694
+ const { author, body, createdAt } = props;
695
+ const when = formatWhen(createdAt);
696
+ return /* @__PURE__ */ jsxs("div", { className: "tw-comment-row", children: [
697
+ /* @__PURE__ */ jsx("span", { className: "tw-comment-av", style: { background: avatarColor(author) }, "aria-hidden": "true", children: initials(author) }),
698
+ /* @__PURE__ */ jsxs("div", { children: [
699
+ /* @__PURE__ */ jsxs("div", { className: "tw-comment-who", children: [
700
+ author,
701
+ when ? /* @__PURE__ */ jsx("span", { className: "tw-comment-when", children: when }) : null
702
+ ] }),
703
+ /* @__PURE__ */ jsx("div", { className: "tw-comment-text", children: body })
704
+ ] })
705
+ ] });
706
+ }
707
+ function initials(name) {
708
+ const parts = name.trim().split(/\s+/).filter(Boolean);
709
+ if (parts.length === 0) return "?";
710
+ const first = parts[0]?.[0] ?? "";
711
+ const last = parts.length > 1 ? parts[parts.length - 1]?.[0] ?? "" : "";
712
+ return (first + last).toUpperCase() || "?";
713
+ }
714
+ function avatarColor(name) {
715
+ let h = 0;
716
+ for (let i = 0; i < name.length; i++) h = h * 31 + name.charCodeAt(i) >>> 0;
717
+ return `hsl(${h % 360} 58% 60%)`;
718
+ }
719
+ function formatWhen(iso) {
720
+ if (!iso) return "";
721
+ const t = Date.parse(iso);
722
+ if (Number.isNaN(t)) return "";
723
+ const s = Math.max(0, (Date.now() - t) / 1e3);
724
+ if (s < 45) return "now";
725
+ const m = s / 60;
726
+ if (m < 45) return `${Math.round(m)}m`;
727
+ const hrs = m / 60;
728
+ if (hrs < 24) return `${Math.round(hrs)}h`;
729
+ const d = hrs / 24;
730
+ if (d < 7) return `${Math.round(d)}d`;
731
+ const w = d / 7;
732
+ if (w < 5) return `${Math.round(w)}w`;
733
+ return new Date(t).toLocaleDateString();
734
+ }
735
+ var COMMENTS_CSS = `
736
+ .tw-comments-sidebar { --tw-fg:#1a1d20; --tw-muted:#5a6169; --tw-faint:#8b929a; --tw-bg:#ffffff; --tw-chip:#f0f1f3; --tw-line:rgba(18,22,27,.1); --tw-accent:#2f6fed; --tw-accent-soft:rgba(47,111,237,.1); position:absolute; top:0; right:0; bottom:0; width:min(340px,82vw); display:flex; flex-direction:column; box-sizing:border-box; background:var(--tw-bg); color:var(--tw-fg); border-left:1px solid var(--tw-line); font-family:-apple-system,"SF Pro Text",system-ui,sans-serif; font-size:15px; line-height:1.5; z-index:20; box-shadow:-8px 0 24px -18px rgba(0,0,0,.4); animation:tw-comments-in .26s cubic-bezier(.32,.72,0,1) both; }
737
+ @media (prefers-color-scheme: dark) { .tw-comments-sidebar:not(.tw-theme-light) { --tw-fg:#e8eaed; --tw-muted:#a3abb2; --tw-faint:#6a727a; --tw-bg:#0f1215; --tw-chip:#1e242b; --tw-line:rgba(255,255,255,.1); --tw-accent:#6ea3ff; --tw-accent-soft:rgba(110,163,255,.14); } }
738
+ .tw-comments-sidebar.tw-theme-dark, :root[data-theme="dark"] .tw-comments-sidebar { --tw-fg:#e8eaed; --tw-muted:#a3abb2; --tw-faint:#6a727a; --tw-bg:#0f1215; --tw-chip:#1e242b; --tw-line:rgba(255,255,255,.1); --tw-accent:#6ea3ff; --tw-accent-soft:rgba(110,163,255,.14); }
739
+ :root[data-theme="light"] .tw-comments-sidebar { --tw-fg:#1a1d20; --tw-muted:#5a6169; --tw-faint:#8b929a; --tw-bg:#ffffff; --tw-chip:#f0f1f3; --tw-line:rgba(18,22,27,.1); --tw-accent:#2f6fed; --tw-accent-soft:rgba(47,111,237,.1); }
740
+ @keyframes tw-comments-in { from { transform:translateX(16px); opacity:0 } to { transform:none; opacity:1 } }
741
+ .tw-comments-head { display:flex; align-items:center; gap:8px; padding:11px 14px; border-bottom:1px solid var(--tw-line); background:color-mix(in srgb, var(--tw-bg) 80%, transparent); backdrop-filter:blur(18px) saturate(1.6); -webkit-backdrop-filter:blur(18px) saturate(1.6); position:sticky; top:0; z-index:2; }
742
+ .tw-comments-title { font-size:14px; font-weight:660; letter-spacing:-.01em; }
743
+ .tw-comments-count { font-size:11.5px; font-weight:640; color:var(--tw-muted); background:var(--tw-chip); border:1px solid var(--tw-line); border-radius:999px; padding:1px 8px; line-height:1.5; }
744
+ .tw-comments-close { margin-left:auto; width:28px; height:28px; padding:0; border:1px solid transparent; background:transparent; color:var(--tw-muted); border-radius:7px; cursor:pointer; display:grid; place-items:center; transition:color .15s, background .15s; }
745
+ .tw-comments-close:hover { color:var(--tw-fg); background:var(--tw-accent-soft); }
746
+ .tw-comments-close:focus-visible { outline:2px solid var(--tw-accent); outline-offset:2px; }
747
+ .tw-comments-body { flex:1; overflow-y:auto; padding:12px; }
748
+ .tw-comments-empty { color:var(--tw-faint); font-size:13px; text-align:center; padding:34px 14px; line-height:1.55; }
749
+ .tw-comment-thread { border:1px solid var(--tw-line); border-radius:12px; padding:12px; margin-bottom:12px; background:var(--tw-bg); transition:box-shadow .2s, border-color .2s, opacity .2s; }
750
+ .tw-comment-thread:last-child { margin-bottom:0; }
751
+ .tw-comment-thread.resolved { opacity:.58; }
752
+ .tw-comment-thread--active { border-color:var(--tw-accent); }
753
+ .tw-comment-thread--flash { animation:tw-comment-flash 1.4s ease; }
754
+ @keyframes tw-comment-flash { 0%,100% { box-shadow:0 0 0 0 transparent } 12%,70% { box-shadow:0 0 0 3px var(--tw-accent-soft) } }
755
+ .tw-comment-quote { font-size:12px; color:var(--tw-muted); border-left:2px solid var(--tw-accent-soft); padding:1px 0 1px 8px; margin-bottom:8px; font-style:italic; }
756
+ .tw-comment-row { display:flex; gap:8px; margin-bottom:8px; }
757
+ .tw-comment-av { width:24px; height:24px; border-radius:50%; display:grid; place-items:center; font-size:10px; font-weight:640; color:#0b0d0f; flex:none; }
758
+ .tw-comment-who { font-size:12.5px; font-weight:600; }
759
+ .tw-comment-when { font-size:11px; color:var(--tw-faint); margin-left:6px; font-weight:400; }
760
+ .tw-comment-text { font-size:13px; color:var(--tw-fg); margin-top:1px; white-space:pre-wrap; overflow-wrap:anywhere; }
761
+ .tw-comment-reacts { display:flex; gap:5px; flex-wrap:wrap; margin:6px 0 4px 32px; }
762
+ .tw-comment-react { font-size:12px; border:1px solid var(--tw-line); background:var(--tw-chip); color:var(--tw-muted); border-radius:999px; padding:2px 8px; display:inline-flex; gap:4px; align-items:center; cursor:pointer; transition:border-color .15s, background .15s, color .15s; }
763
+ .tw-comment-react:hover { border-color:var(--tw-accent); }
764
+ .tw-comment-react[aria-pressed="true"] { border-color:var(--tw-accent); background:var(--tw-accent-soft); color:var(--tw-accent); }
765
+ .tw-comment-react:focus-visible { outline:2px solid var(--tw-accent); outline-offset:2px; }
766
+ .tw-comment-react-n { font-variant-numeric:tabular-nums; font-size:11px; }
767
+ .tw-comment-replies { margin-left:32px; border-left:1px solid var(--tw-line); padding-left:10px; margin-top:6px; }
768
+ .tw-comment-replies .tw-comment-row:last-child { margin-bottom:0; }
769
+ .tw-comment-reply { display:flex; gap:6px; align-items:flex-end; margin:8px 0 2px 32px; }
770
+ .tw-comment-reply textarea { flex:1; min-width:0; min-height:33px; max-height:120px; box-sizing:border-box; resize:none; background:var(--tw-bg); border:1px solid var(--tw-line); border-radius:8px; padding:7px 9px; font:inherit; font-size:12.5px; line-height:1.4; color:var(--tw-fg); outline:none; transition:border-color .15s; }
771
+ .tw-comment-reply textarea:focus { border-color:var(--tw-accent); }
772
+ .tw-comment-reply textarea::placeholder { color:var(--tw-faint); }
773
+ .tw-comment-btn { border:1px solid var(--tw-line); background:var(--tw-chip); color:var(--tw-muted); border-radius:8px; padding:5px 10px; font:inherit; font-size:12px; cursor:pointer; white-space:nowrap; transition:color .15s, border-color .15s, background .15s; }
774
+ .tw-comment-btn:hover { color:var(--tw-fg); border-color:var(--tw-accent); }
775
+ .tw-comment-btn:focus-visible { outline:2px solid var(--tw-accent); outline-offset:2px; }
776
+ .tw-comment-btn:disabled { opacity:.5; cursor:default; }
777
+ .tw-comment-btn:disabled:hover { color:var(--tw-muted); border-color:var(--tw-line); }
778
+ .tw-comment-actions { display:flex; justify-content:flex-end; align-items:center; gap:6px; margin-top:8px; }
779
+ .tw-comment-del { margin-right:auto; }
780
+ .tw-comment-del:hover { color:#e5484d; border-color:#e5484d; }
781
+ @media (prefers-reduced-transparency: reduce) { .tw-comments-head { backdrop-filter:none; -webkit-backdrop-filter:none; background:var(--tw-bg); } }
782
+ @media (prefers-reduced-motion: reduce) { .tw-comments-sidebar, .tw-comment-thread--flash { animation:none !important; } }
783
+ `;
784
+ var LEVELS = [1, 2, 3, 4, 5, 6];
785
+ var ITEM_COUNT = LEVELS.length + 4;
786
+ var MENU_WIDTH = 250;
787
+ var CheckIcon = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 3, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) });
788
+ function FoldMenu(props) {
789
+ const {
790
+ open,
791
+ level,
792
+ folded,
793
+ anchorRect,
794
+ onSetLevel,
795
+ onToggleFold,
796
+ onFoldAll,
797
+ onUnfoldAll,
798
+ onCopyLink,
799
+ onClose
800
+ } = props;
801
+ const menuRef = React7.useRef(null);
802
+ const itemRefs = React7.useRef([]);
803
+ const focusItem = React7.useCallback((index) => {
804
+ const i = (index % ITEM_COUNT + ITEM_COUNT) % ITEM_COUNT;
805
+ itemRefs.current[i]?.focus();
806
+ }, []);
807
+ React7.useEffect(() => {
808
+ if (!open) return void 0;
809
+ const onKeyDown = (e) => {
810
+ if (e.key === "Escape") {
811
+ e.stopPropagation();
812
+ onClose();
813
+ }
814
+ };
815
+ const onPointerDown = (e) => {
816
+ const node = menuRef.current;
817
+ if (node && e.target instanceof Node && !node.contains(e.target)) onClose();
818
+ };
819
+ document.addEventListener("keydown", onKeyDown);
820
+ document.addEventListener("pointerdown", onPointerDown, true);
821
+ return () => {
822
+ document.removeEventListener("keydown", onKeyDown);
823
+ document.removeEventListener("pointerdown", onPointerDown, true);
824
+ };
825
+ }, [open, onClose]);
826
+ React7.useEffect(() => {
827
+ if (!open) return;
828
+ const start = level >= 1 && level <= LEVELS.length ? level - 1 : 0;
829
+ itemRefs.current[start]?.focus();
830
+ }, [open, level]);
831
+ if (!open) return null;
832
+ const onMenuKeyDown = (e) => {
833
+ const current = itemRefs.current.indexOf(document.activeElement);
834
+ switch (e.key) {
835
+ case "ArrowDown":
836
+ e.preventDefault();
837
+ focusItem(current < 0 ? 0 : current + 1);
838
+ break;
839
+ case "ArrowUp":
840
+ e.preventDefault();
841
+ focusItem(current < 0 ? ITEM_COUNT - 1 : current - 1);
842
+ break;
843
+ case "Home":
844
+ e.preventDefault();
845
+ focusItem(0);
846
+ break;
847
+ case "End":
848
+ e.preventDefault();
849
+ focusItem(ITEM_COUNT - 1);
850
+ break;
851
+ case "Tab":
852
+ e.preventDefault();
853
+ onClose();
854
+ break;
855
+ }
856
+ };
857
+ const run = (fn) => () => {
858
+ fn();
859
+ onClose();
860
+ };
861
+ const viewportW = typeof window === "undefined" ? MENU_WIDTH * 4 : window.innerWidth;
862
+ const left = anchorRect ? Math.max(8, Math.min(anchorRect.left, viewportW - MENU_WIDTH)) : 16;
863
+ const top = anchorRect ? anchorRect.bottom + 6 : 16;
864
+ const setRef = (index) => (el) => {
865
+ itemRefs.current[index] = el;
866
+ };
867
+ return /* @__PURE__ */ jsxs(
868
+ "div",
869
+ {
870
+ ref: menuRef,
871
+ className: "tw-fold-menu",
872
+ role: "menu",
873
+ "aria-label": "Heading options",
874
+ "aria-orientation": "vertical",
875
+ style: { left, top },
876
+ onKeyDown: onMenuKeyDown,
877
+ children: [
878
+ LEVELS.map((l, i) => {
879
+ const on = level === l;
880
+ return /* @__PURE__ */ jsxs(
881
+ "button",
882
+ {
883
+ ref: setRef(i),
884
+ type: "button",
885
+ className: `tw-fold-menu-item${on ? " tw-on" : ""}`,
886
+ role: "menuitemradio",
887
+ "aria-checked": on,
888
+ tabIndex: -1,
889
+ onClick: run(() => onSetLevel(l)),
890
+ children: [
891
+ /* @__PURE__ */ jsx("span", { className: "tw-fm-chk", "aria-hidden": "true", children: CheckIcon }),
892
+ /* @__PURE__ */ jsx("span", { className: "tw-fm-num", "aria-hidden": "true", children: l }),
893
+ /* @__PURE__ */ jsxs("span", { className: "tw-fm-lab", children: [
894
+ "Heading ",
895
+ l
896
+ ] }),
897
+ /* @__PURE__ */ jsxs("span", { className: "tw-fm-kbd", "aria-hidden": "true", children: [
898
+ "\u2318",
899
+ l
900
+ ] })
901
+ ]
902
+ },
903
+ l
904
+ );
905
+ }),
906
+ /* @__PURE__ */ jsx("div", { className: "tw-fold-menu-sep", role: "separator" }),
907
+ /* @__PURE__ */ jsxs(
908
+ "button",
909
+ {
910
+ ref: setRef(LEVELS.length),
911
+ type: "button",
912
+ className: "tw-fold-menu-item",
913
+ role: "menuitem",
914
+ tabIndex: -1,
915
+ onClick: run(onToggleFold),
916
+ children: [
917
+ /* @__PURE__ */ jsx("span", { className: "tw-fm-chk", "aria-hidden": "true" }),
918
+ /* @__PURE__ */ jsx("span", { className: "tw-fm-lab", children: "Toggle Folding" })
919
+ ]
920
+ }
921
+ ),
922
+ /* @__PURE__ */ jsxs(
923
+ "button",
924
+ {
925
+ ref: setRef(LEVELS.length + 1),
926
+ type: "button",
927
+ className: "tw-fold-menu-item",
928
+ role: "menuitem",
929
+ tabIndex: -1,
930
+ onClick: run(onFoldAll),
931
+ children: [
932
+ /* @__PURE__ */ jsx("span", { className: "tw-fm-chk", "aria-hidden": "true" }),
933
+ /* @__PURE__ */ jsx("span", { className: "tw-fm-lab", children: "Fold All Headers" })
934
+ ]
935
+ }
936
+ ),
937
+ /* @__PURE__ */ jsxs(
938
+ "button",
939
+ {
940
+ ref: setRef(LEVELS.length + 2),
941
+ type: "button",
942
+ className: "tw-fold-menu-item",
943
+ role: "menuitem",
944
+ tabIndex: -1,
945
+ onClick: run(onUnfoldAll),
946
+ children: [
947
+ /* @__PURE__ */ jsx("span", { className: "tw-fm-chk", "aria-hidden": "true" }),
948
+ /* @__PURE__ */ jsx("span", { className: "tw-fm-lab", children: "Unfold All Headers" })
949
+ ]
950
+ }
951
+ ),
952
+ /* @__PURE__ */ jsx("div", { className: "tw-fold-menu-sep", role: "separator" }),
953
+ /* @__PURE__ */ jsxs(
954
+ "button",
955
+ {
956
+ ref: setRef(LEVELS.length + 3),
957
+ type: "button",
958
+ className: "tw-fold-menu-item",
959
+ role: "menuitem",
960
+ tabIndex: -1,
961
+ onClick: run(onCopyLink),
962
+ children: [
963
+ /* @__PURE__ */ jsx("span", { className: "tw-fm-chk", "aria-hidden": "true" }),
964
+ /* @__PURE__ */ jsx("span", { className: "tw-fm-lab", children: "Copy Link to here" })
965
+ ]
966
+ }
967
+ )
968
+ ]
969
+ }
970
+ );
971
+ }
972
+ var FOLDMENU_CSS = `
973
+ .tw-fold-menu{position:fixed; z-index:100; min-width:234px; padding:6px; background:color-mix(in srgb, var(--tw-bg) 90%, transparent); backdrop-filter:blur(24px) saturate(1.7); -webkit-backdrop-filter:blur(24px) saturate(1.7); border:1px solid var(--tw-line); border-radius:14px; box-shadow:0 24px 60px -12px rgba(0,0,0,.5), 0 8px 24px -8px rgba(0,0,0,.4), inset 0 1px 0 rgba(255,255,255,.06); color:var(--tw-fg); font-family:-apple-system,"SF Pro Text",system-ui,sans-serif; animation:tw-fold-menu-in .16s cubic-bezier(.32,.72,0,1) both}
974
+ @keyframes tw-fold-menu-in{from{opacity:0; transform:translateY(-6px) scale(.98)}to{opacity:1; transform:none}}
975
+ .tw-fold-menu-item{display:flex; align-items:center; gap:10px; width:100%; padding:7px 9px; border:0; border-radius:9px; background:transparent; color:var(--tw-fg); font:inherit; font-size:13.5px; line-height:1.2; text-align:left; cursor:pointer; transition:background .13s}
976
+ .tw-fold-menu-item:hover{background:var(--tw-accent-soft)}
977
+ .tw-fold-menu-item:focus{outline:none; background:var(--tw-accent-soft)}
978
+ .tw-fold-menu-item:focus-visible{outline:2px solid var(--tw-accent); outline-offset:-2px}
979
+ .tw-fold-menu-item .tw-fm-chk{width:14px; height:14px; flex:none; opacity:0; color:var(--tw-accent)}
980
+ .tw-fold-menu-item .tw-fm-chk svg{width:14px; height:14px; display:block}
981
+ .tw-fold-menu-item.tw-on .tw-fm-chk{opacity:1}
982
+ .tw-fold-menu-item .tw-fm-num{width:18px; height:18px; flex:none; border:1px solid var(--tw-line); border-radius:5px; display:grid; place-items:center; font-family:"SF Mono",ui-monospace,Menlo,monospace; font-size:10.5px; color:var(--tw-muted)}
983
+ .tw-fold-menu-item .tw-fm-lab{flex:1; min-width:0}
984
+ .tw-fold-menu-item .tw-fm-kbd{flex:none; font-family:"SF Mono",ui-monospace,Menlo,monospace; font-size:11px; color:var(--tw-faint)}
985
+ .tw-fold-menu-sep{height:1px; margin:5px 4px; background:var(--tw-line)}
986
+ @media (prefers-reduced-transparency: reduce){ .tw-fold-menu{background:var(--tw-bg); backdrop-filter:none; -webkit-backdrop-filter:none} }
987
+ @media (prefers-reduced-motion: reduce){ .tw-fold-menu{animation:none !important} .tw-fold-menu-item{transition:none !important} }
988
+ `;
989
+ var DEBOUNCE_MS = 250;
990
+ function frameOf(host) {
991
+ return host?.querySelector("iframe") ?? null;
992
+ }
993
+ function showFrame(iframe, height, theme) {
994
+ iframe.style.position = "static";
995
+ iframe.style.visibility = "visible";
996
+ iframe.style.pointerEvents = "auto";
997
+ iframe.style.width = "100%";
998
+ iframe.style.height = `${Math.max(1, Math.ceil(height ?? 0))}px`;
999
+ iframe.style.colorScheme = theme;
1000
+ }
1001
+ function hideFrame(iframe) {
1002
+ iframe.style.visibility = "hidden";
1003
+ iframe.style.height = "0";
1004
+ iframe.style.pointerEvents = "none";
1005
+ }
1006
+ function errorText(err) {
1007
+ if (typeof err === "string") return err;
1008
+ if (err && typeof err === "object" && "message" in err) {
1009
+ return String(err.message);
1010
+ }
1011
+ return String(err);
1012
+ }
1013
+ function SandboxIslandBase(props) {
1014
+ const { kind, code, transform, components, sandbox, mermaidEngine, theme = "light" } = props;
1015
+ const hostRef = React7.useRef(null);
1016
+ const [controller, setController] = React7.useState(null);
1017
+ const [error, setError] = React7.useState(null);
1018
+ const themeRef = React7.useRef(theme);
1019
+ themeRef.current = theme;
1020
+ const needsSandbox = !(kind === "mdx" && transform === void 0);
1021
+ const sandboxCsp = sandbox?.csp;
1022
+ React7.useEffect(() => {
1023
+ if (!needsSandbox) return void 0;
1024
+ if (typeof document === "undefined") return void 0;
1025
+ const container = hostRef.current;
1026
+ if (!container) return void 0;
1027
+ let ctrl = null;
1028
+ try {
1029
+ ctrl = createSandbox({
1030
+ ...sandbox,
1031
+ container,
1032
+ mermaidEngine: kind === "mermaid" ? mermaidEngine : void 0
1033
+ });
1034
+ } catch (err) {
1035
+ setError(errorText(err));
1036
+ return void 0;
1037
+ }
1038
+ setController(ctrl);
1039
+ return () => {
1040
+ ctrl?.destroy();
1041
+ setController(null);
1042
+ };
1043
+ }, [kind, needsSandbox, mermaidEngine, sandboxCsp]);
1044
+ React7.useEffect(() => {
1045
+ if (!controller) return void 0;
1046
+ const iframe = frameOf(hostRef.current);
1047
+ if (!code.trim()) {
1048
+ setError(null);
1049
+ if (iframe) hideFrame(iframe);
1050
+ return void 0;
1051
+ }
1052
+ let canceled = false;
1053
+ const succeed = (height) => {
1054
+ if (canceled) return;
1055
+ setError(null);
1056
+ const f = frameOf(hostRef.current);
1057
+ if (f) showFrame(f, height, themeRef.current);
1058
+ };
1059
+ const fail = (err) => {
1060
+ if (canceled) return;
1061
+ setError(errorText(err));
1062
+ const f = frameOf(hostRef.current);
1063
+ if (f) hideFrame(f);
1064
+ };
1065
+ const timer = setTimeout(() => {
1066
+ void (async () => {
1067
+ try {
1068
+ if (kind === "mdx") {
1069
+ let js;
1070
+ try {
1071
+ js = await resolveTransform(transform)(code);
1072
+ } catch (err) {
1073
+ fail(err);
1074
+ return;
1075
+ }
1076
+ const result = await controller.evaluate(js, { components });
1077
+ if (result.error) fail(result.error);
1078
+ else succeed(result.height);
1079
+ } else {
1080
+ const result = await controller.renderMermaid(code);
1081
+ if (result.error) fail(result.error);
1082
+ else succeed(result.height);
1083
+ }
1084
+ } catch (err) {
1085
+ fail(err);
1086
+ }
1087
+ })();
1088
+ }, DEBOUNCE_MS);
1089
+ return () => {
1090
+ canceled = true;
1091
+ clearTimeout(timer);
1092
+ };
1093
+ }, [controller, kind, code, transform, components]);
1094
+ React7.useEffect(() => {
1095
+ const iframe = frameOf(hostRef.current);
1096
+ if (iframe && iframe.style.visibility === "visible") iframe.style.colorScheme = theme;
1097
+ }, [theme]);
1098
+ if (kind === "mdx" && transform === void 0) {
1099
+ return /* @__PURE__ */ jsx("pre", { className: "tw-island-src", children: code });
1100
+ }
1101
+ const title = kind === "mdx" ? "Component failed" : "Diagram failed";
1102
+ return /* @__PURE__ */ jsxs("div", { className: `tw-island tw-island-${kind}${error !== null ? " tw-island-failed" : ""}`, "data-tw-island": kind, children: [
1103
+ /* @__PURE__ */ jsx("div", { className: "tw-island-frame", ref: hostRef, "aria-hidden": error !== null }),
1104
+ error !== null && /* @__PURE__ */ jsxs("div", { className: "tw-island-error", role: "alert", children: [
1105
+ /* @__PURE__ */ jsxs("div", { className: "tw-island-error-title", children: [
1106
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u26A0" }),
1107
+ " ",
1108
+ title
1109
+ ] }),
1110
+ /* @__PURE__ */ jsx("pre", { className: "tw-island-error-msg", children: error })
1111
+ ] })
1112
+ ] });
1113
+ }
1114
+ var SandboxIsland = React7.memo(SandboxIslandBase);
1115
+ var SANDBOX_ISLAND_CSS = `
1116
+ .tw-island{position:relative;margin:.6em 0;width:100%}
1117
+ .tw-island-frame{display:block;width:100%}
1118
+ .tw-island-frame iframe{display:block;border:0;width:100%;background:transparent;color-scheme:inherit}
1119
+ .tw-island-src{white-space:pre-wrap}
1120
+ .tw-island-error{border:1px solid color-mix(in srgb, #e5484d 42%, var(--tw-line));border-left:3px solid #e5484d;border-radius:9px;background:color-mix(in srgb, #e5484d 8%, var(--tw-bg));padding:9px 12px;margin:.2em 0}
1121
+ .tw-island-error-title{display:flex;align-items:center;gap:6px;font-size:12.5px;font-weight:640;color:#e5484d}
1122
+ .tw-island-error-msg{margin:5px 0 0;padding:0;border:0;background:none;font-family:"SF Mono",ui-monospace,Menlo,monospace;font-size:11.5px;line-height:1.5;color:var(--tw-muted);white-space:pre-wrap;overflow-wrap:anywhere}
1123
+ `;
1124
+ var MODE_OPTIONS = [
1125
+ { value: "edit", label: "Edit" },
1126
+ { value: "unified", label: "Unified" },
1127
+ { value: "preview", label: "Preview" },
1128
+ { value: "read", label: "Read" }
1129
+ ];
1130
+ var TOOLBAR_OPTIONS = [
1131
+ { value: "off", label: "Off" },
1132
+ { value: "docked", label: "Docked" },
1133
+ { value: "floating", label: "Floating" }
1134
+ ];
1135
+ var THEME_OPTIONS = [
1136
+ { value: "light", label: "Light" },
1137
+ { value: "dark", label: "Dark" },
1138
+ { value: "auto", label: "Auto" }
1139
+ ];
1140
+ var EXTENSION_OPTIONS = [
1141
+ { key: "gfm", label: "GitHub Flavored MD", kbd: "gfm" },
1142
+ { key: "mdx", label: "MDX v3", kbd: "mdx" },
1143
+ { key: "mermaid", label: "Mermaid", kbd: "mermaid" },
1144
+ { key: "math", label: "Math (KaTeX)", kbd: "math" },
1145
+ { key: "syntaxHighlight", label: "Syntax highlight", kbd: "syntaxHl" }
1146
+ ];
1147
+ function Segmented(props) {
1148
+ const { label, value, options, onSelect } = props;
1149
+ return /* @__PURE__ */ jsx("div", { className: "tw-seg", role: "group", "aria-label": label, children: options.map((o) => /* @__PURE__ */ jsx(
1150
+ "button",
1151
+ {
1152
+ type: "button",
1153
+ className: "tw-seg-btn",
1154
+ "aria-pressed": o.value === value,
1155
+ onClick: () => onSelect(o.value),
1156
+ children: o.label
1157
+ },
1158
+ o.value
1159
+ )) });
1160
+ }
1161
+ function ToggleRow(props) {
1162
+ const { label, kbd, checked, onToggle } = props;
1163
+ const id = React7.useId();
1164
+ return /* @__PURE__ */ jsxs("div", { className: "tw-set-toggle", children: [
1165
+ /* @__PURE__ */ jsxs("span", { className: "tw-set-toggle-label", id, children: [
1166
+ label,
1167
+ kbd ? /* @__PURE__ */ jsx("span", { className: "tw-set-toggle-kbd", children: kbd }) : null
1168
+ ] }),
1169
+ /* @__PURE__ */ jsx(
1170
+ "button",
1171
+ {
1172
+ type: "button",
1173
+ className: "tw-sw",
1174
+ role: "switch",
1175
+ "aria-checked": checked,
1176
+ "aria-labelledby": id,
1177
+ onClick: onToggle
1178
+ }
1179
+ )
1180
+ ] });
1181
+ }
1182
+ function trapTab(e, container) {
1183
+ if (e.key !== "Tab" || !container) return;
1184
+ const nodes = Array.from(
1185
+ container.querySelectorAll(
1186
+ 'button, [href], input, textarea, select, [tabindex]:not([tabindex="-1"])'
1187
+ )
1188
+ ).filter((n) => !n.disabled && n.offsetParent !== null);
1189
+ if (nodes.length === 0) {
1190
+ e.preventDefault();
1191
+ return;
1192
+ }
1193
+ const first = nodes[0];
1194
+ const last = nodes[nodes.length - 1];
1195
+ const active = document.activeElement;
1196
+ if (e.shiftKey && active === first) {
1197
+ e.preventDefault();
1198
+ last.focus();
1199
+ } else if (!e.shiftKey && active === last) {
1200
+ e.preventDefault();
1201
+ first.focus();
1202
+ }
1203
+ }
1204
+ function SettingsPanel(props) {
1205
+ const { open, onClose, state, onChange } = props;
1206
+ const panelRef = React7.useRef(null);
1207
+ React7.useEffect(() => {
1208
+ if (!open) return;
1209
+ const el = panelRef.current;
1210
+ if (!el) return;
1211
+ const first = el.querySelector("button, input, [tabindex]");
1212
+ const raf = requestAnimationFrame(() => (first ?? el).focus());
1213
+ return () => cancelAnimationFrame(raf);
1214
+ }, [open]);
1215
+ if (!open) return null;
1216
+ const toolbarValue = state.toolbar === false ? "off" : state.toolbar === "floating" ? "floating" : "docked";
1217
+ const themeClass = state.theme === "dark" ? "tw-theme-dark" : state.theme === "light" ? "tw-theme-light" : "";
1218
+ return /* @__PURE__ */ jsx(
1219
+ "div",
1220
+ {
1221
+ className: `tw-settings-overlay${themeClass ? " " + themeClass : ""}`,
1222
+ onMouseDown: (e) => {
1223
+ if (e.target === e.currentTarget) onClose();
1224
+ },
1225
+ children: /* @__PURE__ */ jsxs(
1226
+ "div",
1227
+ {
1228
+ className: "tw-settings-panel",
1229
+ role: "dialog",
1230
+ "aria-modal": "true",
1231
+ "aria-label": "Editor settings",
1232
+ tabIndex: -1,
1233
+ ref: panelRef,
1234
+ onKeyDown: (e) => {
1235
+ if (e.key === "Escape") {
1236
+ e.preventDefault();
1237
+ onClose();
1238
+ return;
1239
+ }
1240
+ trapTab(e, panelRef.current);
1241
+ },
1242
+ children: [
1243
+ /* @__PURE__ */ jsxs("div", { className: "tw-settings-head", children: [
1244
+ /* @__PURE__ */ jsx("span", { className: "tw-settings-title", children: "Settings" }),
1245
+ /* @__PURE__ */ jsx("button", { type: "button", className: "tw-settings-close", "aria-label": "Close settings", onClick: onClose, children: "\xD7" })
1246
+ ] }),
1247
+ /* @__PURE__ */ jsxs("div", { className: "tw-settings-body", children: [
1248
+ /* @__PURE__ */ jsxs("section", { className: "tw-set-section", children: [
1249
+ /* @__PURE__ */ jsx("h4", { className: "tw-set-h4", children: "Mode" }),
1250
+ /* @__PURE__ */ jsx(
1251
+ Segmented,
1252
+ {
1253
+ label: "Editor mode",
1254
+ value: state.mode,
1255
+ options: MODE_OPTIONS,
1256
+ onSelect: (mode) => onChange({ mode })
1257
+ }
1258
+ )
1259
+ ] }),
1260
+ /* @__PURE__ */ jsxs("section", { className: "tw-set-section", children: [
1261
+ /* @__PURE__ */ jsx("h4", { className: "tw-set-h4", children: "Toolbar" }),
1262
+ /* @__PURE__ */ jsx(
1263
+ Segmented,
1264
+ {
1265
+ label: "Toolbar mode",
1266
+ value: toolbarValue,
1267
+ options: TOOLBAR_OPTIONS,
1268
+ onSelect: (v) => onChange({ toolbar: v === "off" ? false : v })
1269
+ }
1270
+ )
1271
+ ] }),
1272
+ /* @__PURE__ */ jsxs("section", { className: "tw-set-section", children: [
1273
+ /* @__PURE__ */ jsx("h4", { className: "tw-set-h4", children: "Theme" }),
1274
+ /* @__PURE__ */ jsx(
1275
+ Segmented,
1276
+ {
1277
+ label: "Theme appearance",
1278
+ value: state.theme,
1279
+ options: THEME_OPTIONS,
1280
+ onSelect: (theme) => onChange({ theme })
1281
+ }
1282
+ )
1283
+ ] }),
1284
+ /* @__PURE__ */ jsxs("section", { className: "tw-set-section", children: [
1285
+ /* @__PURE__ */ jsx("h4", { className: "tw-set-h4", children: "Section folding" }),
1286
+ /* @__PURE__ */ jsx("div", { className: "tw-toggle-list", children: /* @__PURE__ */ jsx(
1287
+ ToggleRow,
1288
+ {
1289
+ label: "Fold headings",
1290
+ checked: state.folding,
1291
+ onToggle: () => onChange({ folding: !state.folding })
1292
+ }
1293
+ ) })
1294
+ ] }),
1295
+ /* @__PURE__ */ jsxs("section", { className: "tw-set-section", children: [
1296
+ /* @__PURE__ */ jsx("h4", { className: "tw-set-h4", children: "Extensions" }),
1297
+ /* @__PURE__ */ jsx("div", { className: "tw-toggle-list", children: EXTENSION_OPTIONS.map((ext) => /* @__PURE__ */ jsx(
1298
+ ToggleRow,
1299
+ {
1300
+ label: ext.label,
1301
+ kbd: ext.kbd,
1302
+ checked: !!state.extensions[ext.key],
1303
+ onToggle: () => onChange({
1304
+ extensions: { [ext.key]: !state.extensions[ext.key] }
1305
+ })
1306
+ },
1307
+ ext.key
1308
+ )) })
1309
+ ] })
1310
+ ] })
1311
+ ]
1312
+ }
1313
+ )
1314
+ }
1315
+ );
1316
+ }
1317
+ function fuzzyScore(query, text) {
1318
+ const q = query.toLowerCase();
1319
+ const t = text.toLowerCase();
1320
+ let qi = 0;
1321
+ let run = 0;
1322
+ let score = 0;
1323
+ for (let ti = 0; ti < t.length && qi < q.length; ti++) {
1324
+ if (t[ti] === q[qi]) {
1325
+ run++;
1326
+ score += run * 2;
1327
+ const prev = t[ti - 1];
1328
+ if (ti === 0 || prev === " " || prev === "-" || prev === "/") score += 6;
1329
+ qi++;
1330
+ } else {
1331
+ run = 0;
1332
+ }
1333
+ }
1334
+ if (qi < q.length) return null;
1335
+ return score - t.length * 0.1;
1336
+ }
1337
+ function CommandPalette(props) {
1338
+ const { open, onClose, commands } = props;
1339
+ const [query, setQuery] = React7.useState("");
1340
+ const [highlight, setHighlight] = React7.useState(0);
1341
+ const inputRef = React7.useRef(null);
1342
+ const dialogRef = React7.useRef(null);
1343
+ const activeItemRef = React7.useRef(null);
1344
+ const { groups, flat } = React7.useMemo(() => {
1345
+ const q = query.trim();
1346
+ const order = [];
1347
+ const byGroup = /* @__PURE__ */ new Map();
1348
+ for (const cmd of commands) {
1349
+ const score = q ? fuzzyScore(q, cmd.label) : 0;
1350
+ if (score === null) continue;
1351
+ const g = cmd.group ?? "";
1352
+ let bucket = byGroup.get(g);
1353
+ if (!bucket) {
1354
+ bucket = [];
1355
+ byGroup.set(g, bucket);
1356
+ order.push(g);
1357
+ }
1358
+ bucket.push({ cmd, score });
1359
+ }
1360
+ let idx = 0;
1361
+ const grouped = order.map((name) => {
1362
+ const bucket = byGroup.get(name);
1363
+ if (q) bucket.sort((a, b) => b.score - a.score);
1364
+ return { name, items: bucket.map(({ cmd }) => ({ cmd, flatIndex: idx++ })) };
1365
+ });
1366
+ const flatList = [];
1367
+ for (const gr of grouped) for (const it of gr.items) flatList.push(it.cmd);
1368
+ return { groups: grouped, flat: flatList };
1369
+ }, [commands, query]);
1370
+ React7.useEffect(() => {
1371
+ if (!open) return;
1372
+ setQuery("");
1373
+ setHighlight(0);
1374
+ const raf = requestAnimationFrame(() => inputRef.current?.focus());
1375
+ return () => cancelAnimationFrame(raf);
1376
+ }, [open]);
1377
+ React7.useEffect(() => {
1378
+ setHighlight((h) => flat.length === 0 ? 0 : Math.min(h, flat.length - 1));
1379
+ }, [flat.length]);
1380
+ React7.useEffect(() => {
1381
+ activeItemRef.current?.scrollIntoView({ block: "nearest" });
1382
+ }, [highlight]);
1383
+ if (!open) return null;
1384
+ const runAt = (i) => {
1385
+ const cmd = flat[i];
1386
+ if (!cmd) return;
1387
+ cmd.run();
1388
+ onClose();
1389
+ };
1390
+ const onKeyDown = (e) => {
1391
+ switch (e.key) {
1392
+ case "ArrowDown":
1393
+ e.preventDefault();
1394
+ setHighlight((h) => flat.length === 0 ? 0 : (h + 1) % flat.length);
1395
+ break;
1396
+ case "ArrowUp":
1397
+ e.preventDefault();
1398
+ setHighlight((h) => flat.length === 0 ? 0 : (h - 1 + flat.length) % flat.length);
1399
+ break;
1400
+ case "Home":
1401
+ e.preventDefault();
1402
+ setHighlight(0);
1403
+ break;
1404
+ case "End":
1405
+ e.preventDefault();
1406
+ setHighlight(flat.length === 0 ? 0 : flat.length - 1);
1407
+ break;
1408
+ case "Enter":
1409
+ e.preventDefault();
1410
+ runAt(highlight);
1411
+ break;
1412
+ case "Escape":
1413
+ e.preventDefault();
1414
+ onClose();
1415
+ break;
1416
+ case "Tab":
1417
+ trapTab(e, dialogRef.current);
1418
+ break;
1419
+ }
1420
+ };
1421
+ const activeId = flat[highlight] ? `tw-pcmd-${highlight}` : void 0;
1422
+ return /* @__PURE__ */ jsx(
1423
+ "div",
1424
+ {
1425
+ className: "tw-palette-overlay",
1426
+ onMouseDown: (e) => {
1427
+ if (e.target === e.currentTarget) onClose();
1428
+ },
1429
+ children: /* @__PURE__ */ jsxs(
1430
+ "div",
1431
+ {
1432
+ className: "tw-palette",
1433
+ role: "dialog",
1434
+ "aria-modal": "true",
1435
+ "aria-label": "Command palette",
1436
+ ref: dialogRef,
1437
+ onKeyDown,
1438
+ children: [
1439
+ /* @__PURE__ */ jsxs("div", { className: "tw-palette-input-wrap", children: [
1440
+ /* @__PURE__ */ jsxs(
1441
+ "svg",
1442
+ {
1443
+ className: "tw-palette-search",
1444
+ viewBox: "0 0 24 24",
1445
+ fill: "none",
1446
+ stroke: "currentColor",
1447
+ strokeWidth: "1.9",
1448
+ strokeLinecap: "round",
1449
+ "aria-hidden": "true",
1450
+ children: [
1451
+ /* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
1452
+ /* @__PURE__ */ jsx("path", { d: "m20 20-3.5-3.5" })
1453
+ ]
1454
+ }
1455
+ ),
1456
+ /* @__PURE__ */ jsx(
1457
+ "input",
1458
+ {
1459
+ ref: inputRef,
1460
+ className: "tw-palette-input",
1461
+ type: "text",
1462
+ role: "combobox",
1463
+ "aria-expanded": "true",
1464
+ "aria-controls": "tw-palette-list",
1465
+ "aria-activedescendant": activeId,
1466
+ "aria-autocomplete": "list",
1467
+ placeholder: "Type a command\u2026",
1468
+ spellCheck: false,
1469
+ value: query,
1470
+ onChange: (e) => setQuery(e.target.value)
1471
+ }
1472
+ ),
1473
+ /* @__PURE__ */ jsx("kbd", { className: "tw-palette-esc", children: "esc" })
1474
+ ] }),
1475
+ /* @__PURE__ */ jsxs("ul", { className: "tw-palette-list", id: "tw-palette-list", role: "listbox", "aria-label": "Commands", children: [
1476
+ groups.map((group) => /* @__PURE__ */ jsxs(React7.Fragment, { children: [
1477
+ group.name ? /* @__PURE__ */ jsx("li", { className: "tw-palette-group", role: "presentation", children: group.name }) : null,
1478
+ group.items.map((it) => /* @__PURE__ */ jsxs(
1479
+ "li",
1480
+ {
1481
+ id: `tw-pcmd-${it.flatIndex}`,
1482
+ role: "option",
1483
+ "aria-selected": it.flatIndex === highlight,
1484
+ className: `tw-palette-item${it.flatIndex === highlight ? " tw-active" : ""}`,
1485
+ ref: it.flatIndex === highlight ? activeItemRef : void 0,
1486
+ onMouseMove: () => setHighlight(it.flatIndex),
1487
+ onMouseDown: (e) => {
1488
+ e.preventDefault();
1489
+ runAt(it.flatIndex);
1490
+ },
1491
+ children: [
1492
+ /* @__PURE__ */ jsx("span", { className: "tw-palette-label", children: it.cmd.label }),
1493
+ it.cmd.kbd ? /* @__PURE__ */ jsx("kbd", { className: "tw-palette-kbd", children: it.cmd.kbd }) : null
1494
+ ]
1495
+ },
1496
+ it.cmd.id
1497
+ ))
1498
+ ] }, group.name || "_")),
1499
+ flat.length === 0 ? /* @__PURE__ */ jsx("li", { className: "tw-palette-empty", role: "presentation", children: "No matching commands" }) : null
1500
+ ] })
1501
+ ]
1502
+ }
1503
+ )
1504
+ }
1505
+ );
1506
+ }
1507
+ var LIGHT_TOKENS = "--tw-fg:#1a1d20;--tw-muted:#5a6169;--tw-faint:#8b929a;--tw-bg:#ffffff;--tw-chip:#f0f1f3;--tw-line:rgba(18,22,27,.1);--tw-accent:#2f6fed;--tw-accent-soft:rgba(47,111,237,.1);--tw-code-bg:#f6f7f9;";
1508
+ var DARK_TOKENS = "--tw-fg:#e8eaed;--tw-muted:#a3abb2;--tw-faint:#6a727a;--tw-bg:#0f1215;--tw-chip:#1e242b;--tw-line:rgba(255,255,255,.1);--tw-accent:#6ea3ff;--tw-accent-soft:rgba(110,163,255,.14);--tw-code-bg:#13171b;";
1509
+ var SETTINGS_CSS = `
1510
+ .tw-settings-overlay,.tw-palette-overlay{${LIGHT_TOKENS}position:fixed;inset:0;z-index:1000;display:flex;font-family:-apple-system,"SF Pro Text",system-ui,sans-serif;color:var(--tw-fg);line-height:1.5;font-size:15px}
1511
+ @media (prefers-color-scheme: dark){.tw-settings-overlay:not(.tw-theme-light),.tw-palette-overlay:not(.tw-theme-light){${DARK_TOKENS}}}
1512
+ .tw-settings-overlay.tw-theme-dark,.tw-palette-overlay.tw-theme-dark{${DARK_TOKENS}}
1513
+ :root[data-theme="dark"] .tw-settings-overlay:not(.tw-theme-light),:root[data-theme="dark"] .tw-palette-overlay:not(.tw-theme-light){${DARK_TOKENS}}
1514
+ .tw-settings-overlay.tw-theme-light,.tw-palette-overlay.tw-theme-light,:root[data-theme="light"] .tw-settings-overlay,:root[data-theme="light"] .tw-palette-overlay{${LIGHT_TOKENS}}
1515
+
1516
+ .tw-settings-overlay{justify-content:flex-end;background:color-mix(in srgb,#0b0d0f 30%,transparent);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);animation:tw-set-fade .18s ease}
1517
+ .tw-palette-overlay{align-items:flex-start;justify-content:center;padding:14vh 16px 16px;background:color-mix(in srgb,#0b0d0f 34%,transparent);backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);animation:tw-set-fade .16s ease}
1518
+
1519
+ .tw-settings-panel{width:340px;max-width:92vw;height:100%;box-sizing:border-box;background:var(--tw-bg);border-left:1px solid var(--tw-line);box-shadow:-18px 0 48px -20px rgba(0,0,0,.5);display:flex;flex-direction:column;outline:none;animation:tw-set-slide .26s cubic-bezier(.32,.72,0,1)}
1520
+ .tw-settings-head{display:flex;align-items:center;gap:10px;padding:15px 16px;border-bottom:1px solid var(--tw-line)}
1521
+ .tw-settings-title{flex:1;font-size:15px;font-weight:640;letter-spacing:-.01em}
1522
+ .tw-settings-close{width:28px;height:28px;flex:none;border:1px solid var(--tw-line);background:var(--tw-chip);border-radius:8px;color:var(--tw-muted);font-size:18px;line-height:1;cursor:pointer;display:grid;place-items:center;transition:color .15s,border-color .15s}
1523
+ .tw-settings-close:hover{color:var(--tw-fg);border-color:var(--tw-accent)}
1524
+ .tw-settings-close:focus-visible{outline:2px solid var(--tw-accent);outline-offset:2px}
1525
+ .tw-settings-body{flex:1;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:18px}
1526
+ .tw-set-section{display:flex;flex-direction:column;gap:8px}
1527
+ .tw-set-h4{margin:0;font-size:11px;letter-spacing:.07em;text-transform:uppercase;color:var(--tw-faint);font-weight:600}
1528
+ .tw-toggle-list{display:flex;flex-direction:column;gap:1px}
1529
+
1530
+ .tw-seg{display:flex;width:100%;background:color-mix(in srgb,var(--tw-chip) 60%,transparent);border:1px solid var(--tw-line);border-radius:10px;padding:3px;gap:2px}
1531
+ .tw-seg-btn{flex:1;border:0;background:transparent;color:var(--tw-muted);font-size:12.5px;font-weight:540;padding:6px 12px;border-radius:7px;cursor:pointer;white-space:nowrap;transition:color .18s,background .18s}
1532
+ .tw-seg-btn[aria-pressed="true"]{color:var(--tw-fg);background:var(--tw-bg);box-shadow:0 1px 2px rgba(0,0,0,.18)}
1533
+ .tw-seg-btn:hover:not([aria-pressed="true"]){color:var(--tw-fg)}
1534
+ .tw-seg-btn:focus-visible{outline:2px solid var(--tw-accent);outline-offset:1px}
1535
+
1536
+ .tw-set-toggle{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:7px 8px;border-radius:8px;transition:background .15s}
1537
+ .tw-set-toggle:hover{background:var(--tw-chip)}
1538
+ .tw-set-toggle-label{display:flex;align-items:center;gap:9px;font-size:13.5px}
1539
+ .tw-set-toggle-kbd{font-family:"SF Mono",ui-monospace,Menlo,monospace;font-size:11px;color:var(--tw-faint)}
1540
+ .tw-sw{width:38px;height:22px;flex:none;padding:0;border-radius:999px;background:var(--tw-chip);border:1px solid var(--tw-line);position:relative;cursor:pointer;transition:background .2s}
1541
+ .tw-sw::after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.4);transition:transform .2s cubic-bezier(.32,.72,0,1)}
1542
+ .tw-sw[aria-checked="true"]{background:var(--tw-accent)}
1543
+ .tw-sw[aria-checked="true"]::after{transform:translateX(16px)}
1544
+ .tw-sw:focus-visible{outline:2px solid var(--tw-accent);outline-offset:2px}
1545
+
1546
+ .tw-palette{width:560px;max-width:100%;max-height:60vh;box-sizing:border-box;background:var(--tw-bg);border:1px solid var(--tw-line);border-radius:14px;box-shadow:0 24px 64px -18px rgba(0,0,0,.55);display:flex;flex-direction:column;overflow:hidden;animation:tw-pal-in .2s cubic-bezier(.32,.72,0,1)}
1547
+ .tw-palette-input-wrap{display:flex;align-items:center;gap:10px;padding:13px 15px;border-bottom:1px solid var(--tw-line)}
1548
+ .tw-palette-search{width:17px;height:17px;flex:none;color:var(--tw-faint)}
1549
+ .tw-palette-input{flex:1;min-width:0;border:0;background:transparent;outline:none;color:var(--tw-fg);font-size:15px;font-family:inherit}
1550
+ .tw-palette-input::placeholder{color:var(--tw-faint)}
1551
+ .tw-palette-esc{flex:none;font-family:"SF Mono",ui-monospace,Menlo,monospace;font-size:11px;color:var(--tw-faint);border:1px solid var(--tw-line);border-radius:5px;padding:2px 6px}
1552
+ .tw-palette-list{list-style:none;margin:0;padding:6px;overflow:auto;flex:1}
1553
+ .tw-palette-group{padding:8px 10px 4px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--tw-faint);font-weight:600}
1554
+ .tw-palette-item{display:flex;align-items:center;gap:10px;padding:9px 10px;border-radius:8px;cursor:pointer;color:var(--tw-fg)}
1555
+ .tw-palette-item.tw-active{background:var(--tw-accent-soft)}
1556
+ .tw-palette-label{flex:1;min-width:0;font-size:13.5px}
1557
+ .tw-palette-kbd{flex:none;font-family:"SF Mono",ui-monospace,Menlo,monospace;font-size:11px;color:var(--tw-muted);background:var(--tw-chip);border:1px solid var(--tw-line);border-radius:5px;padding:2px 6px}
1558
+ .tw-palette-item.tw-active .tw-palette-kbd{color:var(--tw-accent)}
1559
+ .tw-palette-empty{padding:22px;text-align:center;color:var(--tw-faint);font-size:13px}
1560
+
1561
+ @keyframes tw-set-fade{from{opacity:0}to{opacity:1}}
1562
+ @keyframes tw-set-slide{from{transform:translateX(18px);opacity:.4}to{transform:none;opacity:1}}
1563
+ @keyframes tw-pal-in{from{transform:translateY(-8px) scale(.98);opacity:0}to{transform:none;opacity:1}}
1564
+ @media (prefers-reduced-motion: reduce){.tw-settings-overlay,.tw-palette-overlay,.tw-settings-panel,.tw-palette,.tw-sw::after{animation:none !important;transition:none !important}}
1565
+ @media (prefers-reduced-transparency: reduce){.tw-settings-overlay{backdrop-filter:none;-webkit-backdrop-filter:none;background:color-mix(in srgb,#0b0d0f 60%,transparent)}.tw-palette-overlay{backdrop-filter:none;-webkit-backdrop-filter:none;background:color-mix(in srgb,#0b0d0f 64%,transparent)}}
1566
+ `;
1567
+ var cellKey = (row, col) => `${row}:${col}`;
1568
+ function caretToEnd(el) {
1569
+ const sel = typeof window !== "undefined" ? window.getSelection() : null;
1570
+ if (!sel) return;
1571
+ const range = document.createRange();
1572
+ range.selectNodeContents(el);
1573
+ range.collapse(false);
1574
+ sel.removeAllRanges();
1575
+ sel.addRange(range);
1576
+ }
1577
+ function caretAtEdge(el) {
1578
+ const sel = typeof window !== "undefined" ? window.getSelection() : null;
1579
+ if (!sel || sel.rangeCount === 0) return { start: true, end: true };
1580
+ const range = sel.getRangeAt(0);
1581
+ if (!el.contains(range.startContainer)) return { start: false, end: false };
1582
+ const pre = document.createRange();
1583
+ pre.selectNodeContents(el);
1584
+ pre.setEnd(range.startContainer, range.startOffset);
1585
+ const before = pre.toString().length;
1586
+ const total = el.textContent?.length ?? 0;
1587
+ return { start: range.collapsed && before === 0, end: range.collapsed && before === total };
1588
+ }
1589
+ function Svg({ children }) {
1590
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "15", height: "15", fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children });
1591
+ }
1592
+ var ICON = {
1593
+ addRow: /* @__PURE__ */ jsxs(Svg, { children: [
1594
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "7", rx: "1.5" }),
1595
+ /* @__PURE__ */ jsx("path", { d: "M12 15v6M9 18h6" })
1596
+ ] }),
1597
+ addCol: /* @__PURE__ */ jsxs(Svg, { children: [
1598
+ /* @__PURE__ */ jsx("rect", { x: "4", y: "3", width: "7", height: "18", rx: "1.5" }),
1599
+ /* @__PURE__ */ jsx("path", { d: "M15 12h6M18 9v6" })
1600
+ ] }),
1601
+ delRow: /* @__PURE__ */ jsxs(Svg, { children: [
1602
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "7", rx: "1.5" }),
1603
+ /* @__PURE__ */ jsx("path", { d: "M9 18h6" })
1604
+ ] }),
1605
+ delCol: /* @__PURE__ */ jsxs(Svg, { children: [
1606
+ /* @__PURE__ */ jsx("rect", { x: "4", y: "3", width: "7", height: "18", rx: "1.5" }),
1607
+ /* @__PURE__ */ jsx("path", { d: "M15 12h6" })
1608
+ ] }),
1609
+ alignL: /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "M4 6h16M4 12h10M4 18h13" }) }),
1610
+ alignC: /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "M4 6h16M7 12h10M6 18h12" }) }),
1611
+ alignR: /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "M4 6h16M10 12h10M7 18h13" }) })
1612
+ };
1613
+ function TableGrid(props) {
1614
+ const { table, source, onChange, onExit, readOnly = false } = props;
1615
+ const ncols = Math.max(1, table.header.length);
1616
+ const lastRow = table.rows.length;
1617
+ const wrapRef = React7.useRef(null);
1618
+ const cellRefs = React7.useRef(/* @__PURE__ */ new Map());
1619
+ const [selected, setSelected] = React7.useState(null);
1620
+ const [pendingFocus, setPendingFocus] = React7.useState(null);
1621
+ const [pendingOp, setPendingOp] = React7.useState(null);
1622
+ const getCell = React7.useCallback(
1623
+ (row, col) => row === 0 ? table.header[col] : table.rows[row - 1]?.[col],
1624
+ [table]
1625
+ );
1626
+ React7.useLayoutEffect(() => {
1627
+ const paint = (row, col, cell) => {
1628
+ const el = cellRefs.current.get(cellKey(row, col));
1629
+ if (!el || el === document.activeElement) return;
1630
+ el.innerHTML = cell ? renderInline(cell.children) : "";
1631
+ };
1632
+ for (let c = 0; c < ncols; c++) paint(0, c, table.header[c]);
1633
+ table.rows.forEach((row, ri) => {
1634
+ for (let c = 0; c < ncols; c++) paint(ri + 1, c, row[c]);
1635
+ });
1636
+ }, [table, source, ncols, readOnly]);
1637
+ React7.useEffect(() => {
1638
+ if (!pendingOp || readOnly) {
1639
+ if (pendingOp) setPendingOp(null);
1640
+ return;
1641
+ }
1642
+ const op = pendingOp;
1643
+ setPendingOp(null);
1644
+ let result;
1645
+ switch (op.kind) {
1646
+ case "add-row":
1647
+ result = addRow(source, table, op.row + 1);
1648
+ break;
1649
+ case "add-col":
1650
+ result = addColumn(source, table, op.col + 1);
1651
+ break;
1652
+ case "del-row":
1653
+ result = removeRow(source, table, op.row);
1654
+ break;
1655
+ case "del-col":
1656
+ result = removeColumn(source, table, op.col);
1657
+ break;
1658
+ case "align":
1659
+ result = setAlignment(source, table, op.col, op.align);
1660
+ break;
1661
+ default:
1662
+ return;
1663
+ }
1664
+ const insert = result.text.slice(table.from, result.text.length - source.length + table.to);
1665
+ const oldBlock = source.slice(table.from, table.to);
1666
+ if (insert !== oldBlock) {
1667
+ onChange({ from: table.from, to: table.to, insert });
1668
+ }
1669
+ }, [pendingOp, table, source, onChange, readOnly]);
1670
+ React7.useEffect(() => {
1671
+ if (!pendingFocus) return;
1672
+ const el = cellRefs.current.get(cellKey(pendingFocus.row, pendingFocus.col));
1673
+ setPendingFocus(null);
1674
+ if (el) el.focus();
1675
+ }, [pendingFocus, table]);
1676
+ const commitCell = React7.useCallback(
1677
+ (row, col) => {
1678
+ const el = cellRefs.current.get(cellKey(row, col));
1679
+ const cell = getCell(row, col);
1680
+ const range = cellSourceRange(table, row, col);
1681
+ if (!el || !cell || !range) return;
1682
+ const oldText = source.slice(range.from, range.to);
1683
+ const next = (el.textContent ?? "").replace(/\r?\n/g, " ");
1684
+ if (next !== oldText) {
1685
+ onChange({ from: range.from, to: range.to, insert: next });
1686
+ } else {
1687
+ el.innerHTML = renderInline(cell.children);
1688
+ }
1689
+ },
1690
+ [table, source, onChange, getCell]
1691
+ );
1692
+ const onCellFocus = React7.useCallback(
1693
+ (row, col) => {
1694
+ if (readOnly) return;
1695
+ const cell = getCell(row, col);
1696
+ const el = cellRefs.current.get(cellKey(row, col));
1697
+ setSelected({ row, col });
1698
+ if (el && cell) {
1699
+ const raw = source.slice(cell.from, cell.to);
1700
+ if (el.textContent !== raw) el.textContent = raw;
1701
+ caretToEnd(el);
1702
+ }
1703
+ },
1704
+ [readOnly, getCell, source]
1705
+ );
1706
+ const nextCell = React7.useCallback(
1707
+ (row, col) => {
1708
+ if (col < ncols - 1) return { row, col: col + 1 };
1709
+ if (row < lastRow) return { row: row + 1, col: 0 };
1710
+ return null;
1711
+ },
1712
+ [ncols, lastRow]
1713
+ );
1714
+ const prevCell = React7.useCallback(
1715
+ (row, col) => {
1716
+ if (col > 0) return { row, col: col - 1 };
1717
+ if (row > 0) return { row: row - 1, col: ncols - 1 };
1718
+ return null;
1719
+ },
1720
+ [ncols]
1721
+ );
1722
+ const exitPastEdge = React7.useCallback(
1723
+ (row, col, dir) => {
1724
+ cellRefs.current.get(cellKey(row, col))?.blur();
1725
+ onExit?.(dir);
1726
+ },
1727
+ [onExit]
1728
+ );
1729
+ const onCellKeyDown = React7.useCallback(
1730
+ (e, row, col) => {
1731
+ if (readOnly) return;
1732
+ const el = e.currentTarget;
1733
+ switch (e.key) {
1734
+ case "Enter": {
1735
+ if (e.shiftKey) return;
1736
+ e.preventDefault();
1737
+ if (row < lastRow) setPendingFocus({ row: row + 1, col });
1738
+ else exitPastEdge(row, col, "down");
1739
+ return;
1740
+ }
1741
+ case "Tab": {
1742
+ e.preventDefault();
1743
+ const target = e.shiftKey ? prevCell(row, col) : nextCell(row, col);
1744
+ if (target) setPendingFocus(target);
1745
+ else exitPastEdge(row, col, e.shiftKey ? "up" : "down");
1746
+ return;
1747
+ }
1748
+ case "ArrowUp": {
1749
+ e.preventDefault();
1750
+ if (row > 0) setPendingFocus({ row: row - 1, col });
1751
+ else exitPastEdge(row, col, "up");
1752
+ return;
1753
+ }
1754
+ case "ArrowDown": {
1755
+ e.preventDefault();
1756
+ if (row < lastRow) setPendingFocus({ row: row + 1, col });
1757
+ else exitPastEdge(row, col, "down");
1758
+ return;
1759
+ }
1760
+ case "ArrowLeft": {
1761
+ if (!caretAtEdge(el).start) return;
1762
+ e.preventDefault();
1763
+ const target = prevCell(row, col);
1764
+ if (target) setPendingFocus(target);
1765
+ else exitPastEdge(row, col, "up");
1766
+ return;
1767
+ }
1768
+ case "ArrowRight": {
1769
+ if (!caretAtEdge(el).end) return;
1770
+ e.preventDefault();
1771
+ const target = nextCell(row, col);
1772
+ if (target) setPendingFocus(target);
1773
+ else exitPastEdge(row, col, "down");
1774
+ return;
1775
+ }
1776
+ default:
1777
+ return;
1778
+ }
1779
+ },
1780
+ [readOnly, lastRow, nextCell, prevCell, exitPastEdge]
1781
+ );
1782
+ const onWrapBlur = React7.useCallback(
1783
+ (e) => {
1784
+ const next = e.relatedTarget;
1785
+ if (next && wrapRef.current?.contains(next)) return;
1786
+ setSelected(null);
1787
+ },
1788
+ []
1789
+ );
1790
+ const sel = selected ? { row: Math.min(selected.row, lastRow), col: Math.min(selected.col, ncols - 1) } : null;
1791
+ const colAlign = (col) => table.align[col] ?? null;
1792
+ const cellProps = (row, col) => {
1793
+ const align = colAlign(col);
1794
+ const isSel = sel !== null && sel.row === row && sel.col === col;
1795
+ const p = {
1796
+ ref: (el) => {
1797
+ if (el) cellRefs.current.set(cellKey(row, col), el);
1798
+ else cellRefs.current.delete(cellKey(row, col));
1799
+ },
1800
+ className: `tw-tg-cell${isSel ? " tw-tg-sel" : ""}`,
1801
+ role: readOnly ? void 0 : "gridcell",
1802
+ style: align ? { textAlign: align } : void 0,
1803
+ contentEditable: readOnly ? void 0 : true,
1804
+ suppressContentEditableWarning: true,
1805
+ spellCheck: false
1806
+ };
1807
+ if (!readOnly) {
1808
+ p.onFocus = () => onCellFocus(row, col);
1809
+ p.onBlur = () => commitCell(row, col);
1810
+ p.onKeyDown = (e) => onCellKeyDown(e, row, col);
1811
+ }
1812
+ return p;
1813
+ };
1814
+ const runOp = (op) => {
1815
+ if (readOnly) return;
1816
+ setPendingOp(op);
1817
+ };
1818
+ const showToolbar = !readOnly && sel !== null;
1819
+ const toolbar = showToolbar ? /* @__PURE__ */ jsxs("div", { className: "tw-tg-toolbar", role: "toolbar", "aria-label": "Table", children: [
1820
+ /* @__PURE__ */ jsx(
1821
+ "button",
1822
+ {
1823
+ type: "button",
1824
+ className: "tw-tg-btn",
1825
+ title: "Add row",
1826
+ "aria-label": "Add row",
1827
+ onClick: () => runOp({ kind: "add-row", row: sel.row, col: sel.col }),
1828
+ children: ICON.addRow
1829
+ }
1830
+ ),
1831
+ /* @__PURE__ */ jsx(
1832
+ "button",
1833
+ {
1834
+ type: "button",
1835
+ className: "tw-tg-btn",
1836
+ title: "Add column",
1837
+ "aria-label": "Add column",
1838
+ onClick: () => runOp({ kind: "add-col", row: sel.row, col: sel.col }),
1839
+ children: ICON.addCol
1840
+ }
1841
+ ),
1842
+ /* @__PURE__ */ jsx(
1843
+ "button",
1844
+ {
1845
+ type: "button",
1846
+ className: "tw-tg-btn",
1847
+ title: "Delete row",
1848
+ "aria-label": "Delete row",
1849
+ disabled: sel.row === 0 || lastRow === 0,
1850
+ onClick: () => runOp({ kind: "del-row", row: sel.row, col: sel.col }),
1851
+ children: ICON.delRow
1852
+ }
1853
+ ),
1854
+ /* @__PURE__ */ jsx(
1855
+ "button",
1856
+ {
1857
+ type: "button",
1858
+ className: "tw-tg-btn",
1859
+ title: "Delete column",
1860
+ "aria-label": "Delete column",
1861
+ disabled: ncols <= 1,
1862
+ onClick: () => runOp({ kind: "del-col", row: sel.row, col: sel.col }),
1863
+ children: ICON.delCol
1864
+ }
1865
+ ),
1866
+ /* @__PURE__ */ jsx("span", { className: "tw-tg-sep", "aria-hidden": "true" }),
1867
+ /* @__PURE__ */ jsx(
1868
+ "button",
1869
+ {
1870
+ type: "button",
1871
+ className: "tw-tg-btn",
1872
+ title: "Align left",
1873
+ "aria-label": "Align left",
1874
+ "aria-pressed": colAlign(sel.col) === "left",
1875
+ onClick: () => runOp({ kind: "align", row: sel.row, col: sel.col, align: "left" }),
1876
+ children: ICON.alignL
1877
+ }
1878
+ ),
1879
+ /* @__PURE__ */ jsx(
1880
+ "button",
1881
+ {
1882
+ type: "button",
1883
+ className: "tw-tg-btn",
1884
+ title: "Align center",
1885
+ "aria-label": "Align center",
1886
+ "aria-pressed": colAlign(sel.col) === "center",
1887
+ onClick: () => runOp({ kind: "align", row: sel.row, col: sel.col, align: "center" }),
1888
+ children: ICON.alignC
1889
+ }
1890
+ ),
1891
+ /* @__PURE__ */ jsx(
1892
+ "button",
1893
+ {
1894
+ type: "button",
1895
+ className: "tw-tg-btn",
1896
+ title: "Align right",
1897
+ "aria-label": "Align right",
1898
+ "aria-pressed": colAlign(sel.col) === "right",
1899
+ onClick: () => runOp({ kind: "align", row: sel.row, col: sel.col, align: "right" }),
1900
+ children: ICON.alignR
1901
+ }
1902
+ )
1903
+ ] }) : null;
1904
+ const headerCols = Array.from({ length: ncols }, (_, c) => c);
1905
+ return /* @__PURE__ */ jsxs("div", { className: "tw-table-grid", ref: wrapRef, onBlur: readOnly ? void 0 : onWrapBlur, children: [
1906
+ toolbar,
1907
+ /* @__PURE__ */ jsx("div", { className: "tw-table-grid-scroll", children: /* @__PURE__ */ jsxs("table", { className: "tw-tg-table", children: [
1908
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: headerCols.map((c) => /* @__PURE__ */ jsx("th", { ...cellProps(0, c) }, c)) }) }),
1909
+ /* @__PURE__ */ jsx("tbody", { children: table.rows.map((_row, ri) => /* @__PURE__ */ jsx("tr", { children: headerCols.map((c) => /* @__PURE__ */ jsx("td", { ...cellProps(ri + 1, c) }, c)) }, ri)) })
1910
+ ] }) })
1911
+ ] });
1912
+ }
1913
+ var TABLEGRID_CSS = `
1914
+ .tw-table-grid{position:relative;margin:.6em 0;width:fit-content;max-width:100%}
1915
+ .tw-table-grid-scroll{overflow-x:auto;border:1px solid var(--tw-line);border-radius:10px}
1916
+ .tw-tg-table{border-collapse:collapse;font-size:14px}
1917
+ .tw-tg-table th,.tw-tg-table td{border:1px solid var(--tw-line);padding:8px 13px;text-align:left;vertical-align:top;min-width:52px}
1918
+ .tw-tg-table th{background:var(--tw-chip);font-weight:600;color:var(--tw-muted);font-size:12px;letter-spacing:.02em;text-transform:uppercase}
1919
+ .tw-tg-table td{color:var(--tw-fg)}
1920
+ .tw-tg-cell{outline:none}
1921
+ .tw-tg-cell:empty::after{content:"";display:inline-block;min-height:1.15em}
1922
+ .tw-tg-table th.tw-tg-sel,.tw-tg-table td.tw-tg-sel{box-shadow:inset 0 0 0 2px var(--tw-accent);background:var(--tw-accent-soft)}
1923
+ .tw-tg-toolbar{position:absolute;top:0;right:0;z-index:6;transform:translateY(calc(-100% - 7px));display:flex;align-items:center;gap:3px;padding:4px 5px;border:1px solid var(--tw-line);border-radius:11px;background:color-mix(in srgb, var(--tw-bg) 82%, transparent);backdrop-filter:blur(18px) saturate(1.6);-webkit-backdrop-filter:blur(18px) saturate(1.6);box-shadow:0 6px 20px -10px rgba(0,0,0,.4), inset 0 1px 0 rgba(255,255,255,.06);animation:tw-tg-pop .18s cubic-bezier(.32,.72,0,1)}
1924
+ @keyframes tw-tg-pop{from{opacity:0;transform:translateY(calc(-100% + 2px))}to{opacity:1;transform:translateY(calc(-100% - 7px))}}
1925
+ .tw-tg-sep{width:1px;height:18px;background:var(--tw-line);margin:0 3px}
1926
+ .tw-tg-btn{min-width:28px;height:28px;padding:0 6px;border:1px solid transparent;background:transparent;border-radius:7px;color:var(--tw-muted);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:color .15s,background .15s,border-color .15s}
1927
+ .tw-tg-btn:hover{color:var(--tw-fg);background:var(--tw-accent-soft)}
1928
+ .tw-tg-btn:disabled{opacity:.4;cursor:default}
1929
+ .tw-tg-btn:disabled:hover{color:var(--tw-muted);background:transparent}
1930
+ .tw-tg-btn[aria-pressed="true"]{color:var(--tw-accent);background:var(--tw-accent-soft);border-color:var(--tw-line)}
1931
+ .tw-tg-btn svg{width:15px;height:15px}
1932
+ @media (prefers-reduced-transparency: reduce){.tw-tg-toolbar{background:var(--tw-bg);backdrop-filter:none;-webkit-backdrop-filter:none}}
1933
+ @media (prefers-reduced-motion: reduce){.tw-tg-toolbar{animation:none} .tw-tg-btn{transition:none}}
1934
+ `;
1935
+ var STYLE_ID = "typewright-styles";
1936
+ function useInjectStyles() {
1937
+ React7.useEffect(() => {
1938
+ if (typeof document === "undefined") return;
1939
+ if (document.getElementById(STYLE_ID)) return;
1940
+ const el = document.createElement("style");
1941
+ el.id = STYLE_ID;
1942
+ el.textContent = TYPEWRIGHT_CSS + TABLEGRID_CSS + FOLDMENU_CSS + COMMENTS_CSS + SETTINGS_CSS + SANDBOX_ISLAND_CSS + CARET_REVEAL_CSS;
1943
+ document.head.appendChild(el);
1944
+ }, []);
1945
+ }
1946
+ function extEnabled(x, dflt = false) {
1947
+ if (x === void 0) return dflt;
1948
+ if (typeof x === "boolean") return x;
1949
+ return x.enabled !== false;
1950
+ }
1951
+ function extOptions(x) {
1952
+ if (!extEnabled(x)) return null;
1953
+ return typeof x === "object" ? x : { enabled: true };
1954
+ }
1955
+ function fenceLang(info) {
1956
+ return (info.trim().split(/\s+/)[0] ?? "").toLowerCase();
1957
+ }
1958
+ function isMdxIslandBlock(b, ic) {
1959
+ return ic.mdxActive && b.type === "htmlBlock" && b.variant === "mdxFlow";
1960
+ }
1961
+ function isMermaidIslandBlock(b, ic) {
1962
+ return ic.mermaidActive && b.type === "codeBlock" && fenceLang(b.lang) === "mermaid";
1963
+ }
1964
+ function isCaretEligible(b) {
1965
+ return b.type === "heading" || b.type === "paragraph" || b.type === "list" || b.type === "blockquote";
1966
+ }
1967
+ function withA11yHints(html) {
1968
+ return html.replace(/<input type="checkbox" disabled/g, '<input type="checkbox" aria-label="Task item" disabled');
1969
+ }
1970
+ var HAS_INTERACTIVE_HTML = /<a[\s>]|<input\b/i;
1971
+ function renderIsland(b, ic) {
1972
+ if (isMdxIslandBlock(b, ic)) {
1973
+ const code = b.value;
1974
+ return /* @__PURE__ */ jsx(
1975
+ SandboxIsland,
1976
+ {
1977
+ kind: "mdx",
1978
+ code,
1979
+ transform: ic.mdxTransform,
1980
+ components: ic.mdxComponents,
1981
+ sandbox: ic.mdxSandbox,
1982
+ theme: ic.theme
1983
+ }
1984
+ );
1985
+ }
1986
+ if (isMermaidIslandBlock(b, ic)) {
1987
+ const code = b.value;
1988
+ return /* @__PURE__ */ jsx(SandboxIsland, { kind: "mermaid", code, mermaidEngine: ic.mermaidEngine, theme: ic.theme });
1989
+ }
1990
+ return null;
1991
+ }
1992
+ var COMMAND_IDS = new Set(COMMANDS.map((c) => c.id));
1993
+ function canonKey(raw) {
1994
+ const toks = raw.toLowerCase().replace(/-/g, "+").split("+").map((t) => t.trim()).filter(Boolean);
1995
+ let mod = false;
1996
+ let alt = false;
1997
+ let shift = false;
1998
+ let key = "";
1999
+ for (const t of toks) {
2000
+ if (t === "mod" || t === "cmd" || t === "meta" || t === "ctrl" || t === "control") mod = true;
2001
+ else if (t === "alt" || t === "option" || t === "opt") alt = true;
2002
+ else if (t === "shift") shift = true;
2003
+ else key = t;
2004
+ }
2005
+ const parts = [];
2006
+ if (mod) parts.push("mod");
2007
+ if (alt) parts.push("alt");
2008
+ if (shift) parts.push("shift");
2009
+ parts.push(key);
2010
+ return parts.join("+");
2011
+ }
2012
+ function kbdToBinding(kbd) {
2013
+ let key = "";
2014
+ const mods = [];
2015
+ for (const ch of kbd) {
2016
+ if (ch === "\u2318" || ch === "\u2303") mods.push("mod");
2017
+ else if (ch === "\u21E7") mods.push("shift");
2018
+ else if (ch === "\u2325") mods.push("alt");
2019
+ else if (ch.trim()) key += ch;
2020
+ }
2021
+ key = key.toLowerCase();
2022
+ if (!key) return null;
2023
+ return canonKey([...mods, key].join("+"));
2024
+ }
2025
+ function eventCanon(e) {
2026
+ const parts = [];
2027
+ if (e.metaKey || e.ctrlKey) parts.push("mod");
2028
+ if (e.altKey) parts.push("alt");
2029
+ if (e.shiftKey) parts.push("shift");
2030
+ parts.push(e.key.toLowerCase());
2031
+ return parts.join("+");
2032
+ }
2033
+ function buildKeymap(keymap) {
2034
+ const map = /* @__PURE__ */ new Map();
2035
+ if ((keymap?.preset ?? "default") !== "none") {
2036
+ for (const c of COMMANDS) {
2037
+ if (!c.kbd) continue;
2038
+ const b = kbdToBinding(c.kbd);
2039
+ if (b) map.set(b, c.id);
2040
+ }
2041
+ }
2042
+ if (keymap?.bindings) {
2043
+ for (const [raw, cmd] of Object.entries(keymap.bindings)) {
2044
+ if (COMMAND_IDS.has(cmd)) map.set(canonKey(raw), cmd);
2045
+ }
2046
+ }
2047
+ return map;
2048
+ }
2049
+ function normalizeComments(comments) {
2050
+ if (!comments) return null;
2051
+ if (comments === true) return { enabled: true, threads: [] };
2052
+ return comments;
2053
+ }
2054
+ function normalizeSettings(settings) {
2055
+ if (!settings) return null;
2056
+ if (settings === true) return { enabled: true };
2057
+ return settings;
2058
+ }
2059
+ function plainify(src) {
2060
+ let s = src;
2061
+ s = s.replace(/^\s*#{1,6}\s+/, "");
2062
+ s = s.replace(/^\s*>\s?/gm, "");
2063
+ s = s.replace(/^\s*(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s+)?/gm, "");
2064
+ s = s.replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1");
2065
+ s = s.replace(/(\*\*|__|~~|\*|_|`)/g, "");
2066
+ return s.replace(/\s+/g, " ").trim();
2067
+ }
2068
+ function renderedCaretIndex(source, scopeFrom, cursorFrom, renderedLen) {
2069
+ return Math.min(plainify(source.slice(scopeFrom, cursorFrom)).length, renderedLen);
2070
+ }
2071
+ function peerInitials(name) {
2072
+ const parts = name.trim().split(/\s+/).filter(Boolean);
2073
+ if (parts.length === 0) return "?";
2074
+ const first = parts[0]?.[0] ?? "";
2075
+ const last = parts.length > 1 ? parts[parts.length - 1]?.[0] ?? "" : "";
2076
+ return (first + last).toUpperCase() || "?";
2077
+ }
2078
+ function mergeThreads(host, local) {
2079
+ if (local.length === 0) return host;
2080
+ const echoed = (l) => host.some((h) => h.quote === l.quote && h.body === l.body);
2081
+ return [...host, ...local.filter((l) => !echoed(l))];
2082
+ }
2083
+ function minimizeChange(change, prevDoc) {
2084
+ const { from, to } = change;
2085
+ const removed = prevDoc.slice(from, to);
2086
+ const inserted = change.insert;
2087
+ let p = 0;
2088
+ const maxP = Math.min(removed.length, inserted.length);
2089
+ while (p < maxP && removed.charCodeAt(p) === inserted.charCodeAt(p)) p++;
2090
+ let s = 0;
2091
+ const maxS = Math.min(removed.length - p, inserted.length - p);
2092
+ while (s < maxS && removed.charCodeAt(removed.length - 1 - s) === inserted.charCodeAt(inserted.length - 1 - s)) s++;
2093
+ return { from: from + p, to: to - s, insert: inserted.slice(p, inserted.length - s) };
2094
+ }
2095
+ function clearDecorations(scope) {
2096
+ scope.querySelectorAll("mark.tw-comment").forEach((mark) => {
2097
+ const parent = mark.parentNode;
2098
+ if (!parent) return;
2099
+ while (mark.firstChild) parent.insertBefore(mark.firstChild, mark);
2100
+ parent.removeChild(mark);
2101
+ });
2102
+ scope.querySelectorAll(".tw-remote-cursor").forEach((c) => c.remove());
2103
+ scope.normalize();
2104
+ }
2105
+ function textNodesOf(scope) {
2106
+ const walker = document.createTreeWalker(scope, NodeFilter.SHOW_TEXT);
2107
+ const nodes = [];
2108
+ let text = "";
2109
+ let n = walker.nextNode();
2110
+ while (n) {
2111
+ const t = n;
2112
+ nodes.push(t);
2113
+ text += t.nodeValue ?? "";
2114
+ n = walker.nextNode();
2115
+ }
2116
+ return { nodes, text };
2117
+ }
2118
+ function locateCharIndex(nodes, index) {
2119
+ let pos = 0;
2120
+ for (const node of nodes) {
2121
+ const len = node.nodeValue?.length ?? 0;
2122
+ if (index <= pos + len) return { node, offset: Math.max(0, index - pos) };
2123
+ pos += len;
2124
+ }
2125
+ const last = nodes[nodes.length - 1];
2126
+ return last ? { node: last, offset: last.nodeValue?.length ?? 0 } : null;
2127
+ }
2128
+ function wrapCharRange(scope, start, end, id, flash) {
2129
+ if (end <= start) return null;
2130
+ const { nodes } = textNodesOf(scope);
2131
+ const segments = [];
2132
+ let pos = 0;
2133
+ for (const node of nodes) {
2134
+ const len = node.nodeValue?.length ?? 0;
2135
+ const nodeStart = pos;
2136
+ const nodeEnd = pos + len;
2137
+ const s = Math.max(start, nodeStart);
2138
+ const e = Math.min(end, nodeEnd);
2139
+ if (s < e) segments.push({ node, s: s - nodeStart, e: e - nodeStart });
2140
+ pos = nodeEnd;
2141
+ if (pos >= end) break;
2142
+ }
2143
+ let first = null;
2144
+ for (const seg of segments) {
2145
+ const range = document.createRange();
2146
+ range.setStart(seg.node, seg.s);
2147
+ range.setEnd(seg.node, seg.e);
2148
+ const mark = document.createElement("mark");
2149
+ mark.className = flash ? "tw-comment tw-comment-flash" : "tw-comment";
2150
+ mark.setAttribute("data-thread", id);
2151
+ try {
2152
+ range.surroundContents(mark);
2153
+ } catch {
2154
+ continue;
2155
+ }
2156
+ if (!first) first = mark;
2157
+ }
2158
+ return first;
2159
+ }
2160
+ function insertRemoteCursor(scope, index, peer) {
2161
+ const { nodes } = textNodesOf(scope);
2162
+ const loc = locateCharIndex(nodes, index);
2163
+ if (!loc) return;
2164
+ const range = document.createRange();
2165
+ range.setStart(loc.node, loc.offset);
2166
+ range.collapse(true);
2167
+ const caret = document.createElement("span");
2168
+ caret.className = "tw-remote-cursor";
2169
+ caret.setAttribute("data-peer", peer.name);
2170
+ if (peer.color) caret.style.setProperty("--tw-remote", peer.color);
2171
+ const label = document.createElement("span");
2172
+ label.className = "tw-remote-flag";
2173
+ label.textContent = peer.name;
2174
+ caret.appendChild(label);
2175
+ range.insertNode(caret);
2176
+ }
2177
+ function applyDecorations(container, source, decorations, peers, activeThreadId) {
2178
+ clearDecorations(container);
2179
+ const blockEls = Array.from(
2180
+ container.querySelectorAll(".tw-block[data-tw-from], .tw-caret-block[data-tw-from]")
2181
+ );
2182
+ const scopes = blockEls.length > 0 ? blockEls.map((el) => ({
2183
+ el,
2184
+ from: Number(el.getAttribute("data-tw-from")) || 0,
2185
+ to: Number(el.getAttribute("data-tw-to")) || 0
2186
+ })) : [{ el: container, from: 0, to: source.length }];
2187
+ let flashTarget = null;
2188
+ for (const scope of scopes) {
2189
+ if (scope.el.getAttribute("contenteditable") === "true" || scope.el.isContentEditable) continue;
2190
+ const scopeText = scope.el.textContent ?? "";
2191
+ for (const dec of decorations) {
2192
+ if (dec.to <= scope.from || dec.from >= scope.to) continue;
2193
+ const rawFrom = Math.max(dec.from, scope.from) - scope.from;
2194
+ const rawTo = Math.min(dec.to, scope.to) - scope.from;
2195
+ const rawSlice = source.slice(scope.from + rawFrom, scope.from + rawTo);
2196
+ const target = plainify(rawSlice) || plainify(dec.quote);
2197
+ if (!target) continue;
2198
+ let at = scopeText.indexOf(target);
2199
+ if (at < 0) at = scopeText.indexOf(dec.quote.trim());
2200
+ if (at < 0) continue;
2201
+ const mark = wrapCharRange(scope.el, at, at + target.length, dec.id, dec.id === activeThreadId);
2202
+ if (mark && dec.id === activeThreadId) flashTarget = mark;
2203
+ }
2204
+ for (const peer of peers) {
2205
+ const cur = peer.cursor;
2206
+ if (!cur) continue;
2207
+ if (cur.from < scope.from || cur.from > scope.to) continue;
2208
+ const rendered = scope.el.textContent?.length ?? 0;
2209
+ const idx = renderedCaretIndex(source, scope.from, cur.from, rendered);
2210
+ insertRemoteCursor(scope.el, idx, peer);
2211
+ }
2212
+ }
2213
+ if (flashTarget) flashTarget.scrollIntoView({ block: "nearest", behavior: "smooth" });
2214
+ }
2215
+ function useCollab(comments, presence, md, mode, settingsControl, visibleRev = 0) {
2216
+ const opts = normalizeComments(comments);
2217
+ const commentsActive = !!opts && opts.enabled !== false;
2218
+ const peers = React7.useMemo(() => presence ?? [], [presence]);
2219
+ const active = commentsActive || peers.length > 0;
2220
+ const settingsPresent = !!settingsControl;
2221
+ const shellActive = active || settingsPresent;
2222
+ const hostThreads = opts?.threads ?? EMPTY_THREADS;
2223
+ const me = opts?.me;
2224
+ const contentRef = React7.useRef(null);
2225
+ const pointerRef = React7.useRef({ x: 0, y: 0 });
2226
+ const srcRef = React7.useRef(md);
2227
+ srcRef.current = md;
2228
+ const [localThreads, setLocalThreads] = React7.useState([]);
2229
+ const [liveAnchors, setLiveAnchors] = React7.useState(
2230
+ () => /* @__PURE__ */ new Map()
2231
+ );
2232
+ const [open, setOpen] = React7.useState(false);
2233
+ const [activeThreadId, setActiveThreadId] = React7.useState(void 0);
2234
+ const [pending, setPending] = React7.useState(null);
2235
+ const [composing, setComposing] = React7.useState(null);
2236
+ const threads = React7.useMemo(() => mergeThreads(hostThreads, localThreads), [hostThreads, localThreads]);
2237
+ React7.useEffect(() => {
2238
+ if (!commentsActive) return;
2239
+ setLiveAnchors((prev) => {
2240
+ const next = new Map(prev);
2241
+ const valid = new Set(threads.map((t) => t.id));
2242
+ for (const t of threads) if (!next.has(t.id)) next.set(t.id, t.anchor);
2243
+ for (const id of [...next.keys()]) if (!valid.has(id)) next.delete(id);
2244
+ return next;
2245
+ });
2246
+ }, [threads, commentsActive]);
2247
+ const commentsActiveRef = React7.useRef(commentsActive);
2248
+ commentsActiveRef.current = commentsActive;
2249
+ const onCommit = React7.useCallback((change) => {
2250
+ if (!commentsActiveRef.current) return;
2251
+ const minimal = minimizeChange(change, srcRef.current);
2252
+ setLiveAnchors((prev) => {
2253
+ const next = /* @__PURE__ */ new Map();
2254
+ prev.forEach((a, id) => {
2255
+ const mapped = mapAnchor(a, minimal);
2256
+ if (mapped) next.set(id, mapped);
2257
+ });
2258
+ return next;
2259
+ });
2260
+ }, []);
2261
+ const onPointer = React7.useCallback((x, y) => {
2262
+ pointerRef.current = { x, y };
2263
+ }, []);
2264
+ const onSourceSelect = React7.useCallback(
2265
+ (docFrom, docTo, quote) => {
2266
+ if (!commentsActive || docFrom >= docTo || !quote.trim()) {
2267
+ setPending(null);
2268
+ return;
2269
+ }
2270
+ const { x, y } = pointerRef.current;
2271
+ setPending({ anchor: { from: docFrom, to: docTo }, quote, x, y });
2272
+ },
2273
+ [commentsActive]
2274
+ );
2275
+ React7.useEffect(() => {
2276
+ if (!commentsActive) return void 0;
2277
+ const onMouseUp = (e) => {
2278
+ pointerRef.current = { x: e.clientX, y: e.clientY };
2279
+ const container = contentRef.current;
2280
+ if (!container) return;
2281
+ const sel = window.getSelection();
2282
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
2283
+ const quote = sel.toString();
2284
+ if (!quote.trim()) return;
2285
+ const range = sel.getRangeAt(0);
2286
+ const anchorNode = range.commonAncestorContainer;
2287
+ if (!(anchorNode instanceof Node) || !container.contains(anchorNode)) return;
2288
+ const host = anchorNode instanceof Element ? anchorNode : anchorNode.parentElement;
2289
+ if (host?.closest(".tw-selpop, .tw-composer, .tw-comments-sidebar")) return;
2290
+ const blockEl = host?.closest(".tw-block[data-tw-from], .tw-caret-block[data-tw-from]");
2291
+ const from = blockEl ? Number(blockEl.getAttribute("data-tw-from")) || 0 : 0;
2292
+ const to = blockEl ? Number(blockEl.getAttribute("data-tw-to")) || 0 : srcRef.current.length;
2293
+ const blockSrc = srcRef.current.slice(from, to);
2294
+ const idx = blockSrc.indexOf(quote.trim());
2295
+ const anchor = idx >= 0 ? { from: from + idx, to: from + idx + quote.trim().length } : { from, to };
2296
+ const rect = range.getBoundingClientRect();
2297
+ setPending({
2298
+ anchor,
2299
+ quote: quote.trim(),
2300
+ x: rect.left + rect.width / 2,
2301
+ y: rect.top
2302
+ });
2303
+ };
2304
+ document.addEventListener("mouseup", onMouseUp);
2305
+ return () => document.removeEventListener("mouseup", onMouseUp);
2306
+ }, [commentsActive]);
2307
+ const decorations = React7.useMemo(() => {
2308
+ if (!commentsActive) return [];
2309
+ const out = [];
2310
+ for (const t of threads) {
2311
+ if (t.resolved) continue;
2312
+ const pos = liveAnchors.get(t.id) ?? t.anchor;
2313
+ out.push({ id: t.id, from: pos.from, to: pos.to, quote: t.quote ?? "" });
2314
+ }
2315
+ return out;
2316
+ }, [threads, liveAnchors, commentsActive]);
2317
+ React7.useLayoutEffect(() => {
2318
+ const container = contentRef.current;
2319
+ if (!container || !active) return;
2320
+ applyDecorations(container, md, decorations, peers, activeThreadId);
2321
+ }, [md, decorations, peers, activeThreadId, active, mode, visibleRev, open]);
2322
+ React7.useEffect(() => {
2323
+ const container = contentRef.current;
2324
+ if (!container || !commentsActive) return void 0;
2325
+ const onClick = (e) => {
2326
+ const mark = e.target?.closest?.("mark.tw-comment");
2327
+ const id = mark?.getAttribute("data-thread");
2328
+ if (!id) return;
2329
+ e.preventDefault();
2330
+ e.stopPropagation();
2331
+ setActiveThreadId(id);
2332
+ setOpen(true);
2333
+ };
2334
+ container.addEventListener("click", onClick, true);
2335
+ return () => container.removeEventListener("click", onClick, true);
2336
+ }, [commentsActive]);
2337
+ const createThread = React7.useCallback(
2338
+ (sel, body) => {
2339
+ const id = `tw-comment-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
2340
+ opts?.onCreate?.({ anchor: sel.anchor, quote: sel.quote, body });
2341
+ const thread = {
2342
+ id,
2343
+ anchor: sel.anchor,
2344
+ quote: sel.quote,
2345
+ author: me?.name ?? "You",
2346
+ body,
2347
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2348
+ replies: []
2349
+ };
2350
+ setLocalThreads((prev) => [...prev, thread]);
2351
+ setLiveAnchors((prev) => new Map(prev).set(id, sel.anchor));
2352
+ setActiveThreadId(id);
2353
+ setOpen(true);
2354
+ setComposing(null);
2355
+ setPending(null);
2356
+ window.getSelection()?.removeAllRanges();
2357
+ },
2358
+ [opts, me]
2359
+ );
2360
+ const overlay = shellActive ? /* @__PURE__ */ jsxs(Fragment, { children: [
2361
+ (peers.length > 0 || commentsActive || settingsPresent) && /* @__PURE__ */ jsxs("div", { className: "tw-collab-bar", children: [
2362
+ peers.length > 0 && /* @__PURE__ */ jsx("div", { className: "tw-presence", "aria-label": "Collaborators", children: peers.map((p) => /* @__PURE__ */ jsx(
2363
+ "span",
2364
+ {
2365
+ className: "tw-presence-av",
2366
+ style: { background: p.color ?? "#888" },
2367
+ title: p.name,
2368
+ "aria-label": p.name,
2369
+ children: peerInitials(p.name)
2370
+ },
2371
+ p.id
2372
+ )) }),
2373
+ commentsActive && /* @__PURE__ */ jsxs(
2374
+ "button",
2375
+ {
2376
+ type: "button",
2377
+ className: `tw-comments-toggle${open ? " tw-on" : ""}`,
2378
+ "aria-label": open ? "Hide comments" : "Show comments",
2379
+ "aria-expanded": open,
2380
+ onClick: () => setOpen((o) => !o),
2381
+ children: [
2382
+ /* @__PURE__ */ jsx("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" }) }),
2383
+ /* @__PURE__ */ jsx("span", { className: "tw-comments-toggle-n", children: threads.length })
2384
+ ]
2385
+ }
2386
+ ),
2387
+ settingsControl
2388
+ ] }),
2389
+ commentsActive && pending && !composing && /* @__PURE__ */ jsx("div", { className: "tw-selpop show", style: { left: clampX(pending.x), top: Math.max(8, pending.y - 44) }, role: "menu", children: /* @__PURE__ */ jsx(
2390
+ "button",
2391
+ {
2392
+ type: "button",
2393
+ className: "tw-selpop-btn",
2394
+ onMouseDown: (e) => e.preventDefault(),
2395
+ onClick: () => {
2396
+ setComposing(pending);
2397
+ setPending(null);
2398
+ },
2399
+ children: "\u{1F4AC} Comment"
2400
+ }
2401
+ ) }),
2402
+ commentsActive && composing && /* @__PURE__ */ jsx(
2403
+ Composer,
2404
+ {
2405
+ selection: composing,
2406
+ onSubmit: (body) => createThread(composing, body),
2407
+ onCancel: () => {
2408
+ setComposing(null);
2409
+ window.getSelection()?.removeAllRanges();
2410
+ }
2411
+ }
2412
+ ),
2413
+ commentsActive && /* @__PURE__ */ jsx(
2414
+ CommentsSidebar,
2415
+ {
2416
+ threads,
2417
+ me,
2418
+ open,
2419
+ onClose: () => setOpen(false),
2420
+ onReply: (threadId, body) => opts?.onReply?.(threadId, body),
2421
+ onReact: (threadId, emoji) => opts?.onReact?.(threadId, emoji),
2422
+ onResolve: (threadId, resolved) => opts?.onResolve?.(threadId, resolved),
2423
+ onDelete: opts?.onDelete ? (threadId) => opts?.onDelete?.(threadId) : void 0,
2424
+ activeThreadId,
2425
+ onSelectThread: (threadId) => setActiveThreadId(threadId)
2426
+ }
2427
+ )
2428
+ ] }) : null;
2429
+ return {
2430
+ commentsActive,
2431
+ active,
2432
+ shellActive,
2433
+ sidebarOpen: open && commentsActive,
2434
+ peers,
2435
+ threads,
2436
+ contentRef,
2437
+ onCommit,
2438
+ onSourceSelect,
2439
+ onPointer,
2440
+ overlay
2441
+ };
2442
+ }
2443
+ var EMPTY_THREADS = [];
2444
+ function clampX(x) {
2445
+ const w = typeof window === "undefined" ? 1024 : window.innerWidth;
2446
+ return Math.max(8, Math.min(x, w - 288));
2447
+ }
2448
+ function Composer(props) {
2449
+ const { selection, onSubmit, onCancel } = props;
2450
+ const [body, setBody] = React7.useState("");
2451
+ const taRef = React7.useRef(null);
2452
+ React7.useEffect(() => {
2453
+ taRef.current?.focus();
2454
+ }, []);
2455
+ const submit = () => {
2456
+ const v = body.trim();
2457
+ if (v) onSubmit(v);
2458
+ };
2459
+ return /* @__PURE__ */ jsxs(
2460
+ "div",
2461
+ {
2462
+ className: "tw-composer show",
2463
+ style: { left: clampX(selection.x), top: Math.max(8, selection.y - 8) },
2464
+ onMouseDown: (e) => e.stopPropagation(),
2465
+ children: [
2466
+ /* @__PURE__ */ jsxs("div", { className: "tw-composer-quote", children: [
2467
+ "\u201C",
2468
+ selection.quote,
2469
+ "\u201D"
2470
+ ] }),
2471
+ /* @__PURE__ */ jsx(
2472
+ "textarea",
2473
+ {
2474
+ ref: taRef,
2475
+ value: body,
2476
+ placeholder: "Add a comment\u2026",
2477
+ "aria-label": "Comment",
2478
+ onChange: (e) => setBody(e.currentTarget.value),
2479
+ onKeyDown: (e) => {
2480
+ if (e.key === "Escape") {
2481
+ e.preventDefault();
2482
+ onCancel();
2483
+ } else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
2484
+ e.preventDefault();
2485
+ submit();
2486
+ }
2487
+ }
2488
+ }
2489
+ ),
2490
+ /* @__PURE__ */ jsxs("div", { className: "tw-composer-row", children: [
2491
+ /* @__PURE__ */ jsx("button", { type: "button", className: "tw-composer-btn", onClick: onCancel, children: "Cancel" }),
2492
+ /* @__PURE__ */ jsx("button", { type: "button", className: "tw-composer-btn tw-composer-primary", disabled: !body.trim(), onClick: submit, children: "Comment" })
2493
+ ] })
2494
+ ]
2495
+ }
2496
+ );
2497
+ }
2498
+ var TypewrightEditor = React7.forwardRef(
2499
+ function TypewrightEditor2(props, forwardedRef) {
2500
+ useInjectStyles();
2501
+ const {
2502
+ value,
2503
+ defaultValue,
2504
+ onChange,
2505
+ onSelectionChange,
2506
+ onModeChange,
2507
+ mode: modeProp = "unified",
2508
+ unifiedReveal = "block",
2509
+ extensions,
2510
+ folding,
2511
+ keymap,
2512
+ comments,
2513
+ presence,
2514
+ settings,
2515
+ readOnly = false,
2516
+ placeholder = "Write Markdown\u2026",
2517
+ theme,
2518
+ toolbar,
2519
+ overscan,
2520
+ className,
2521
+ style
2522
+ } = props;
2523
+ const [modeState, setModeState] = React7.useState(modeProp);
2524
+ const lastModeProp = React7.useRef(modeProp);
2525
+ if (modeProp !== lastModeProp.current) {
2526
+ lastModeProp.current = modeProp;
2527
+ setModeState(modeProp);
2528
+ }
2529
+ const mode = modeState;
2530
+ const setMode = React7.useCallback(
2531
+ (m) => {
2532
+ setModeState(m);
2533
+ onModeChange?.(m);
2534
+ },
2535
+ [onModeChange]
2536
+ );
2537
+ const settingsOpts = React7.useMemo(() => normalizeSettings(settings), [settings]);
2538
+ const settingsActive = !!settingsOpts && settingsOpts.enabled !== false;
2539
+ const [toolbarOverride, setToolbarOverride] = React7.useState(void 0);
2540
+ const [themeOverride, setThemeOverride] = React7.useState(void 0);
2541
+ const [foldingOverride, setFoldingOverride] = React7.useState(void 0);
2542
+ const [extensionsOverride, setExtensionsOverride] = React7.useState({});
2543
+ const [settingsOpen, setSettingsOpen] = React7.useState(false);
2544
+ const [paletteOpen, setPaletteOpen] = React7.useState(false);
2545
+ const settingsControl = settingsActive ? /* @__PURE__ */ jsx(
2546
+ "button",
2547
+ {
2548
+ type: "button",
2549
+ className: `tw-settings-gear${settingsOpen ? " tw-on" : ""}`,
2550
+ "aria-label": settingsOpen ? "Hide settings" : "Show settings",
2551
+ "aria-expanded": settingsOpen,
2552
+ onClick: () => setSettingsOpen((o) => !o),
2553
+ children: /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
2554
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
2555
+ /* @__PURE__ */ jsx("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" })
2556
+ ] })
2557
+ }
2558
+ ) : null;
2559
+ React7.useEffect(() => {
2560
+ if (!settingsActive) return void 0;
2561
+ const onKey = (e) => {
2562
+ if ((e.metaKey || e.ctrlKey) && !e.altKey && !e.shiftKey && (e.key === "k" || e.key === "K")) {
2563
+ e.preventDefault();
2564
+ e.stopPropagation();
2565
+ setPaletteOpen(true);
2566
+ }
2567
+ };
2568
+ document.addEventListener("keydown", onKey, true);
2569
+ return () => document.removeEventListener("keydown", onKey, true);
2570
+ }, [settingsActive]);
2571
+ const effExtensions = React7.useMemo(
2572
+ () => ({ ...extensions, ...extensionsOverride }),
2573
+ [extensions, extensionsOverride]
2574
+ );
2575
+ const mathOn = extEnabled(effExtensions.math);
2576
+ const highlightOn = extEnabled(effExtensions.syntaxHighlight);
2577
+ const mdxOpts = React7.useMemo(() => extOptions(effExtensions.mdx), [effExtensions.mdx]);
2578
+ const mermaidOpts = React7.useMemo(
2579
+ () => extOptions(effExtensions.mermaid),
2580
+ [effExtensions.mermaid]
2581
+ );
2582
+ const mathOpts = React7.useMemo(() => extOptions(effExtensions.math), [effExtensions.math]);
2583
+ const parseOpts = React7.useMemo(
2584
+ () => ({ footnotes: true, defLists: true, math: mathOn }),
2585
+ [mathOn]
2586
+ );
2587
+ const renderOpts = React7.useMemo(
2588
+ () => ({ highlight: highlightOn ? highlightToHtml : void 0, math: mathOpts?.render }),
2589
+ [highlightOn, mathOpts]
2590
+ );
2591
+ const keymapBindings = React7.useMemo(() => buildKeymap(keymap), [keymap]);
2592
+ const mdxIslandActive = !!mdxOpts && mdxOpts.transform !== void 0;
2593
+ const [mermaidEngineSrc, setMermaidEngineSrc] = React7.useState(void 0);
2594
+ React7.useEffect(() => {
2595
+ const getEngine = mermaidOpts?.getEngine;
2596
+ if (!getEngine) {
2597
+ setMermaidEngineSrc(void 0);
2598
+ return void 0;
2599
+ }
2600
+ let canceled = false;
2601
+ Promise.resolve().then(() => getEngine()).then((src) => {
2602
+ if (!canceled && typeof src === "string") setMermaidEngineSrc(src);
2603
+ }).catch(() => {
2604
+ if (!canceled) setMermaidEngineSrc(void 0);
2605
+ });
2606
+ return () => {
2607
+ canceled = true;
2608
+ };
2609
+ }, [mermaidOpts]);
2610
+ const mermaidIslandActive = !!mermaidOpts && !!mermaidOpts.getEngine && mermaidEngineSrc !== void 0;
2611
+ const isControlled = value !== void 0;
2612
+ const [internal, setInternal] = React7.useState(defaultValue ?? "");
2613
+ const md = isControlled ? value ?? "" : internal;
2614
+ const mdRef = React7.useRef(md);
2615
+ mdRef.current = md;
2616
+ const lastCommitRef = React7.useRef(null);
2617
+ const [visibleRev, setVisibleRev] = React7.useState(0);
2618
+ const bumpVisible = React7.useCallback(() => setVisibleRev((r) => r + 1), []);
2619
+ const collab = useCollab(comments, presence, md, mode, settingsControl, visibleRev);
2620
+ const { onCommit } = collab;
2621
+ const commitValue = React7.useCallback(
2622
+ (next, change) => {
2623
+ lastCommitRef.current = { prev: mdRef.current, change, next };
2624
+ if (!isControlled) setInternal(next);
2625
+ onChange?.(next, change);
2626
+ onCommit(change);
2627
+ },
2628
+ [isControlled, onChange, onCommit]
2629
+ );
2630
+ const activeSource = React7.useRef(null);
2631
+ const register = React7.useCallback((api) => {
2632
+ if (api || activeSource.current) activeSource.current = api;
2633
+ }, []);
2634
+ const applyCmd = React7.useCallback((cmd) => {
2635
+ activeSource.current?.apply(cmd);
2636
+ }, []);
2637
+ React7.useImperativeHandle(forwardedRef, () => ({ applyCommand: applyCmd, setMode }), [applyCmd, setMode]);
2638
+ const foldOpts = folding && typeof folding === "object" ? folding : null;
2639
+ const foldingEnabled = folding === void 0 ? true : foldOpts ? foldOpts.enabled !== false : folding !== false;
2640
+ const showGutter = foldOpts ? foldOpts.showGutter !== false : true;
2641
+ const persistKey = foldOpts ? foldOpts.persistKey : void 0;
2642
+ const effFolding = foldingOverride ?? foldingEnabled;
2643
+ const effAppearance = themeOverride ?? (theme?.appearance ?? "auto");
2644
+ const effToolbar = toolbarOverride ?? toolbar;
2645
+ const islands = React7.useMemo(
2646
+ () => ({
2647
+ mdxActive: mdxIslandActive,
2648
+ mdxTransform: mdxOpts?.transform,
2649
+ mdxComponents: mdxOpts?.components,
2650
+ mdxSandbox: mdxOpts?.sandbox,
2651
+ mermaidActive: mermaidIslandActive,
2652
+ mermaidEngine: mermaidEngineSrc,
2653
+ theme: effAppearance === "dark" ? "dark" : "light"
2654
+ }),
2655
+ [mdxIslandActive, mdxOpts, mermaidIslandActive, mermaidEngineSrc, effAppearance]
2656
+ );
2657
+ const rootClass = ["tw-editor", `tw-mode-${mode}`, effAppearance !== "auto" ? `tw-theme-${effAppearance}` : "", className].filter(Boolean).join(" ");
2658
+ const toolbarMode = effToolbar === "floating" ? "floating" : "docked";
2659
+ const showToolbar = !!effToolbar && !readOnly && (mode === "edit" || mode === "unified");
2660
+ const toolbarEl = showToolbar ? /* @__PURE__ */ jsx(Toolbar, { mode: toolbarMode, onCommand: applyCmd }) : null;
2661
+ const shellClass = [
2662
+ "tw-editor-shell",
2663
+ effAppearance !== "auto" ? `tw-theme-${effAppearance}` : "",
2664
+ collab.sidebarOpen ? "tw-comments-open" : ""
2665
+ ].filter(Boolean).join(" ");
2666
+ const shell = (content) => collab.shellActive ? /* @__PURE__ */ jsxs("div", { className: shellClass, onMouseUp: (e) => collab.onPointer(e.clientX, e.clientY), children: [
2667
+ content,
2668
+ collab.overlay
2669
+ ] }) : content;
2670
+ const settingsState = {
2671
+ mode,
2672
+ toolbar: effToolbar ?? false,
2673
+ folding: effFolding,
2674
+ theme: effAppearance,
2675
+ extensions: {
2676
+ // gfm is `boolean | Partial<GfmFeatures>` (no `enabled` field): any object
2677
+ // presence counts as on. The rest carry an `enabled` flag → extEnabled.
2678
+ gfm: effExtensions.gfm !== void 0 && effExtensions.gfm !== false,
2679
+ mdx: extEnabled(effExtensions.mdx),
2680
+ mermaid: extEnabled(effExtensions.mermaid),
2681
+ math: extEnabled(effExtensions.math),
2682
+ syntaxHighlight: extEnabled(effExtensions.syntaxHighlight)
2683
+ }
2684
+ };
2685
+ const applySettingsPatch = React7.useCallback(
2686
+ (patch) => {
2687
+ if (patch.mode !== void 0) setMode(patch.mode);
2688
+ if (patch.toolbar !== void 0) setToolbarOverride(patch.toolbar);
2689
+ if (patch.folding !== void 0) setFoldingOverride(patch.folding);
2690
+ if (patch.theme !== void 0) setThemeOverride(patch.theme);
2691
+ if (patch.extensions !== void 0) {
2692
+ setExtensionsOverride((prev) => ({ ...prev, ...patch.extensions }));
2693
+ }
2694
+ },
2695
+ [setMode]
2696
+ );
2697
+ const paletteCommands = React7.useMemo(() => {
2698
+ const built = COMMANDS.map((c) => ({ id: c.id, label: c.label, kbd: c.kbd, group: c.group, run: () => applyCmd(c.id) }));
2699
+ const host = (settingsOpts?.commands ?? []).map((c) => ({ id: c.id, label: c.label, run: c.run }));
2700
+ return [...built, ...host];
2701
+ }, [applyCmd, settingsOpts]);
2702
+ const settingsSurfaces = settingsActive ? /* @__PURE__ */ jsxs(Fragment, { children: [
2703
+ /* @__PURE__ */ jsx(SettingsPanel, { open: settingsOpen, onClose: () => setSettingsOpen(false), state: settingsState, onChange: applySettingsPatch }),
2704
+ /* @__PURE__ */ jsx(CommandPalette, { open: paletteOpen, onClose: () => setPaletteOpen(false), commands: paletteCommands })
2705
+ ] }) : null;
2706
+ const finish = (content) => settingsActive ? /* @__PURE__ */ jsxs(Fragment, { children: [
2707
+ shell(content),
2708
+ settingsSurfaces
2709
+ ] }) : shell(content);
2710
+ if (mode === "edit") {
2711
+ return finish(
2712
+ /* @__PURE__ */ jsxs("div", { className: rootClass, style, "data-typewright": "edit", children: [
2713
+ toolbarEl,
2714
+ /* @__PURE__ */ jsx(
2715
+ SourceArea,
2716
+ {
2717
+ value: md,
2718
+ readOnly,
2719
+ placeholder,
2720
+ onChange: (next, change) => commitValue(next, change),
2721
+ onSelect: onSelectionChange,
2722
+ register,
2723
+ bindings: keymapBindings,
2724
+ commentBase: collab.commentsActive ? 0 : void 0,
2725
+ onCommentSelect: collab.commentsActive ? collab.onSourceSelect : void 0,
2726
+ full: true
2727
+ }
2728
+ )
2729
+ ] })
2730
+ );
2731
+ }
2732
+ if (mode === "read") {
2733
+ const readDoc = parse(md, parseOpts);
2734
+ const hasIslands = (islands.mdxActive || islands.mermaidActive) && readDoc.children.some((b) => isMdxIslandBlock(b, islands) || isMermaidIslandBlock(b, islands));
2735
+ if (!hasIslands) {
2736
+ const html = renderToHtml(readDoc, renderOpts);
2737
+ return finish(
2738
+ /* @__PURE__ */ jsx(
2739
+ "div",
2740
+ {
2741
+ className: rootClass,
2742
+ style,
2743
+ "data-typewright": "read",
2744
+ ref: collab.active ? collab.contentRef : void 0,
2745
+ dangerouslySetInnerHTML: { __html: withA11yHints(html) || `<p class="tw-placeholder">${escapeText(placeholder)}</p>` }
2746
+ }
2747
+ )
2748
+ );
2749
+ }
2750
+ return finish(
2751
+ /* @__PURE__ */ jsx(
2752
+ "div",
2753
+ {
2754
+ className: rootClass,
2755
+ style,
2756
+ "data-typewright": "read",
2757
+ ref: collab.active ? collab.contentRef : void 0,
2758
+ children: readDoc.children.map((b, i) => {
2759
+ if (b.type === "footnoteDef") return null;
2760
+ const island = renderIsland(b, islands);
2761
+ if (island) return /* @__PURE__ */ jsx(React7.Fragment, { children: island }, `${i}-${b.from}`);
2762
+ return /* @__PURE__ */ jsx(
2763
+ "div",
2764
+ {
2765
+ className: "tw-block",
2766
+ "data-tw-from": b.from,
2767
+ "data-tw-to": b.to,
2768
+ dangerouslySetInnerHTML: { __html: withA11yHints(renderNode(b, renderOpts)) }
2769
+ },
2770
+ `${i}-${b.from}`
2771
+ );
2772
+ })
2773
+ }
2774
+ )
2775
+ );
2776
+ }
2777
+ const caretReveal = mode === "unified" && unifiedReveal === "caret" && !readOnly;
2778
+ return finish(
2779
+ /* @__PURE__ */ jsx(
2780
+ UnifiedEditor,
2781
+ {
2782
+ md,
2783
+ mdRef,
2784
+ rootClass,
2785
+ style,
2786
+ readOnly,
2787
+ caretReveal,
2788
+ placeholder,
2789
+ foldingEnabled: effFolding,
2790
+ showGutter,
2791
+ persistKey,
2792
+ commitValue,
2793
+ register,
2794
+ toolbar: toolbarEl,
2795
+ parseOpts,
2796
+ renderOpts,
2797
+ bindings: keymapBindings,
2798
+ islands,
2799
+ contentRef: collab.active ? collab.contentRef : void 0,
2800
+ commentsActive: collab.commentsActive,
2801
+ onCommentSelect: collab.commentsActive ? collab.onSourceSelect : void 0,
2802
+ overscan,
2803
+ lastCommitRef,
2804
+ onVisibleChange: collab.active ? bumpVisible : void 0
2805
+ }
2806
+ )
2807
+ );
2808
+ }
2809
+ );
2810
+ var TB_GROUPS = [
2811
+ [
2812
+ { cmd: "bold", label: "Bold \u2318B", text: "B", cls: "b" },
2813
+ { cmd: "italic", label: "Italic \u2318I", text: "I", cls: "i" },
2814
+ { cmd: "strikethrough", label: "Strikethrough", text: "S", cls: "s" },
2815
+ { cmd: "inlineCode", label: "Inline code \u2318E", text: "\u2039\u203A" },
2816
+ { cmd: "link", label: "Link \u2318K", text: "\u2197" }
2817
+ ],
2818
+ [
2819
+ { cmd: "heading1", label: "Heading 1", text: "H1" },
2820
+ { cmd: "heading2", label: "Heading 2", text: "H2" },
2821
+ { cmd: "bulletList", label: "Bullet list", text: "\u2022" },
2822
+ { cmd: "orderedList", label: "Numbered list", text: "1." },
2823
+ { cmd: "taskList", label: "Task list", text: "\u2611" },
2824
+ { cmd: "quote", label: "Blockquote", text: "\u275D" }
2825
+ ],
2826
+ [
2827
+ { cmd: "horizontalRule", label: "Divider", text: "\u2015" },
2828
+ { cmd: "codeBlock", label: "Code block", text: "{ }" },
2829
+ { cmd: "table", label: "Table", text: "\u25A6" }
2830
+ ]
2831
+ ];
2832
+ function Toolbar({ mode, onCommand }) {
2833
+ return /* @__PURE__ */ jsx("div", { className: `tw-toolbar tw-toolbar-${mode}`, role: "toolbar", "aria-label": "Formatting", children: TB_GROUPS.map((group, gi) => /* @__PURE__ */ jsxs(React7.Fragment, { children: [
2834
+ gi > 0 && /* @__PURE__ */ jsx("span", { className: "tw-tb-sep", "aria-hidden": "true" }),
2835
+ group.map((it) => /* @__PURE__ */ jsx(
2836
+ "button",
2837
+ {
2838
+ type: "button",
2839
+ className: `tw-tb-btn${it.cls ? " tw-tb-" + it.cls : ""}`,
2840
+ title: it.label,
2841
+ "aria-label": it.label,
2842
+ "data-cmd": it.cmd,
2843
+ onMouseDown: (e) => e.preventDefault(),
2844
+ onClick: () => onCommand(it.cmd),
2845
+ children: it.text
2846
+ },
2847
+ it.cmd
2848
+ ))
2849
+ ] }, gi)) });
2850
+ }
2851
+ var VIRT_THRESHOLD = 150;
2852
+ var VIRT_EST_BLOCK = 40;
2853
+ var VIRT_OVERSCAN_BLOCKS = 6;
2854
+ var VIRT_INITIAL = 50;
2855
+ function getScrollParent(el) {
2856
+ let node = el.parentElement;
2857
+ while (node) {
2858
+ const s = getComputedStyle(node);
2859
+ if (/(auto|scroll|overlay)/.test(s.overflowY + " " + s.overflow)) return node;
2860
+ node = node.parentElement;
2861
+ }
2862
+ return null;
2863
+ }
2864
+ function UnifiedEditor(props) {
2865
+ const { md, mdRef, rootClass, style, readOnly, placeholder, foldingEnabled, showGutter, persistKey, commitValue, register, toolbar, parseOpts, renderOpts, bindings, islands, caretReveal, contentRef, commentsActive, onCommentSelect, overscan, lastCommitRef, onVisibleChange } = props;
2866
+ const [active, setActive] = React7.useState(null);
2867
+ const [draft, setDraft] = React7.useState("");
2868
+ const [folds, setFolds] = React7.useState(
2869
+ () => persistKey ? loadFoldSet(persistKey) : /* @__PURE__ */ new Set()
2870
+ );
2871
+ const [typing, setTyping] = React7.useState(false);
2872
+ const [foldMenuFor, setFoldMenuFor] = React7.useState(null);
2873
+ const [foldMenuRect, setFoldMenuRect] = React7.useState(null);
2874
+ const activeRef = React7.useRef(null);
2875
+ const draftRef = React7.useRef("");
2876
+ activeRef.current = active;
2877
+ draftRef.current = draft;
2878
+ const rowEls = React7.useRef(/* @__PURE__ */ new Map());
2879
+ const flipFrom = React7.useRef(null);
2880
+ const captureFlip = React7.useCallback(() => {
2881
+ const m = /* @__PURE__ */ new Map();
2882
+ rowEls.current.forEach((el, k) => m.set(k, el.getBoundingClientRect().top));
2883
+ flipFrom.current = m;
2884
+ }, []);
2885
+ React7.useLayoutEffect(() => {
2886
+ const from = flipFrom.current;
2887
+ if (!from) return;
2888
+ flipFrom.current = null;
2889
+ rowEls.current.forEach((el, k) => {
2890
+ const prev = from.get(k);
2891
+ if (prev == null) return;
2892
+ const dy = prev - el.getBoundingClientRect().top;
2893
+ if (Math.abs(dy) > 0.5) {
2894
+ el.style.transition = "none";
2895
+ el.style.transform = `translateY(${dy}px)`;
2896
+ requestAnimationFrame(() => {
2897
+ el.style.transition = "transform .3s cubic-bezier(.32,.72,0,1)";
2898
+ el.style.transform = "";
2899
+ });
2900
+ }
2901
+ });
2902
+ });
2903
+ const prevParse = React7.useRef(null);
2904
+ const doc = React7.useMemo(() => {
2905
+ const prev = prevParse.current;
2906
+ const lc = lastCommitRef?.current ?? null;
2907
+ let result;
2908
+ if (prev && lc && prev.opts === parseOpts && lc.next === md && lc.prev === prev.src && lc.prev !== md) {
2909
+ result = parseIncremental(prev.doc, prev.src, lc.change, md, parseOpts);
2910
+ } else {
2911
+ result = parse(md, parseOpts);
2912
+ }
2913
+ prevParse.current = { src: md, opts: parseOpts, doc: result };
2914
+ return result;
2915
+ }, [md, parseOpts, lastCommitRef]);
2916
+ const blocks = doc.children;
2917
+ const blocksRef = React7.useRef(blocks);
2918
+ blocksRef.current = blocks;
2919
+ const foldsRef = React7.useRef(folds);
2920
+ foldsRef.current = folds;
2921
+ const commitFolds = React7.useCallback(
2922
+ (next) => {
2923
+ setFolds(next);
2924
+ if (persistKey) saveFoldSet(persistKey, next);
2925
+ },
2926
+ [persistKey]
2927
+ );
2928
+ const toggleFold = React7.useCallback(
2929
+ (i) => {
2930
+ const key = headingKeyMap(blocksRef.current, mdRef.current).get(i);
2931
+ if (!key) return;
2932
+ const next = new Set(foldsRef.current);
2933
+ if (next.has(key)) next.delete(key);
2934
+ else next.add(key);
2935
+ commitFolds(next);
2936
+ },
2937
+ [commitFolds, mdRef]
2938
+ );
2939
+ const foldAll = React7.useCallback(() => {
2940
+ const next = /* @__PURE__ */ new Set();
2941
+ headingKeyMap(blocksRef.current, mdRef.current).forEach((key) => next.add(key));
2942
+ commitFolds(next);
2943
+ }, [commitFolds, mdRef]);
2944
+ const unfoldAll = React7.useCallback(() => commitFolds(/* @__PURE__ */ new Set()), [commitFolds]);
2945
+ const setHeadingLevel = React7.useCallback(
2946
+ (i, level) => {
2947
+ const b = blocksRef.current[i];
2948
+ if (!b || b.type !== "heading") return;
2949
+ const src = mdRef.current;
2950
+ const marker = "#".repeat(level) + " ";
2951
+ const change = { from: b.from, to: b.contentFrom, insert: marker };
2952
+ const next = src.slice(0, change.from) + marker + src.slice(change.to);
2953
+ if (next !== src) {
2954
+ commitValue(next, change);
2955
+ mdRef.current = next;
2956
+ }
2957
+ },
2958
+ [mdRef, commitValue]
2959
+ );
2960
+ const copyHeadingLink = React7.useCallback(
2961
+ (i) => {
2962
+ const b = blocksRef.current[i];
2963
+ if (!b || b.type !== "heading") return;
2964
+ const slug = slugify(mdRef.current.slice(b.contentFrom, b.to));
2965
+ try {
2966
+ void navigator.clipboard?.writeText("#" + slug);
2967
+ } catch {
2968
+ }
2969
+ },
2970
+ [mdRef]
2971
+ );
2972
+ const handleTableChange = React7.useCallback(
2973
+ (change) => {
2974
+ const src = mdRef.current;
2975
+ const next = src.slice(0, change.from) + change.insert + src.slice(change.to);
2976
+ if (next !== src) {
2977
+ commitValue(next, { from: change.from, to: change.to, insert: change.insert });
2978
+ mdRef.current = next;
2979
+ }
2980
+ },
2981
+ [mdRef, commitValue]
2982
+ );
2983
+ const commit = React7.useCallback(() => {
2984
+ const a = activeRef.current;
2985
+ const src = mdRef.current;
2986
+ activeRef.current = null;
2987
+ setActive(null);
2988
+ if (a === null) return { next: src, change: null };
2989
+ const b = parse(src, parseOpts).children[a];
2990
+ if (!b) return { next: src, change: null };
2991
+ const next = src.slice(0, b.from) + draftRef.current + src.slice(b.to);
2992
+ const change = { from: b.from, to: b.to, insert: draftRef.current };
2993
+ if (next !== src) commitValue(next, change);
2994
+ mdRef.current = next;
2995
+ return { next, change };
2996
+ }, [mdRef, commitValue, parseOpts]);
2997
+ const activate = React7.useCallback(
2998
+ (clickedFrom) => {
2999
+ if (readOnly) return;
3000
+ captureFlip();
3001
+ const { next, change } = commit();
3002
+ let mapped = clickedFrom;
3003
+ if (change && clickedFrom >= change.to) {
3004
+ mapped = clickedFrom + (change.insert.length - (change.to - change.from));
3005
+ }
3006
+ const nextBlocks = parse(next, parseOpts).children;
3007
+ let idx = nextBlocks.findIndex((bl) => mapped >= bl.from && mapped < bl.to);
3008
+ if (idx < 0) idx = nextBlocks.findIndex((bl) => bl.from === mapped);
3009
+ const b = nextBlocks[idx];
3010
+ if (b) {
3011
+ const d = next.slice(b.from, b.to);
3012
+ draftRef.current = d;
3013
+ setDraft(d);
3014
+ activeRef.current = idx;
3015
+ setActive(idx);
3016
+ }
3017
+ },
3018
+ [commit, readOnly, captureFlip, parseOpts]
3019
+ );
3020
+ const foldedHeadings = React7.useMemo(() => {
3021
+ const set = /* @__PURE__ */ new Set();
3022
+ if (!foldingEnabled) return set;
3023
+ headingKeyMap(blocks, md).forEach((key, idx) => {
3024
+ if (folds.has(key)) set.add(idx);
3025
+ });
3026
+ return set;
3027
+ }, [blocks, md, folds, foldingEnabled]);
3028
+ const hidden = React7.useMemo(() => {
3029
+ const set = /* @__PURE__ */ new Set();
3030
+ if (!foldingEnabled) return set;
3031
+ for (let i = 0; i < blocks.length; i++) {
3032
+ const b = blocks[i];
3033
+ if (b.type === "heading" && foldedHeadings.has(i)) {
3034
+ for (let j = i + 1; j < blocks.length; j++) {
3035
+ const n = blocks[j];
3036
+ if (n.type === "heading" && n.level <= b.level) break;
3037
+ set.add(j);
3038
+ }
3039
+ }
3040
+ }
3041
+ return set;
3042
+ }, [blocks, foldedHeadings, foldingEnabled]);
3043
+ const visible = React7.useMemo(() => {
3044
+ const arr = [];
3045
+ for (let i = 0; i < blocks.length; i++) if (!hidden.has(i)) arr.push({ b: blocks[i], i });
3046
+ return arr;
3047
+ }, [blocks, hidden]);
3048
+ const virtualize = visible.length > VIRT_THRESHOLD;
3049
+ const overscanPx = Math.max(overscan ?? VIRT_OVERSCAN_BLOCKS, 0) * VIRT_EST_BLOCK;
3050
+ const heights = React7.useRef(/* @__PURE__ */ new Map());
3051
+ const heightOf = (b) => heights.current.get(b.from) ?? VIRT_EST_BLOCK;
3052
+ const virtInnerRef = React7.useRef(null);
3053
+ const virtScrollRef = React7.useRef(null);
3054
+ const scrollerRef = React7.useRef(null);
3055
+ const [ownScroller, setOwnScroller] = React7.useState(false);
3056
+ const [range, setRange] = React7.useState({ start: 0, end: 0 });
3057
+ const computeRange = () => {
3058
+ const n = visible.length;
3059
+ const scroller = scrollerRef.current;
3060
+ const content = virtInnerRef.current;
3061
+ if (!scroller || !content || n === 0) return { start: 0, end: Math.min(n, VIRT_INITIAL) };
3062
+ const scRect = scroller.getBoundingClientRect();
3063
+ const coRect = content.getBoundingClientRect();
3064
+ const contentOffset = scroller.scrollTop + (coRect.top - scRect.top);
3065
+ const winTop = scroller.scrollTop - contentOffset - overscanPx;
3066
+ const winBottom = scroller.scrollTop - contentOffset + scroller.clientHeight + overscanPx;
3067
+ let cum = 0;
3068
+ let start2 = -1;
3069
+ let end2 = 0;
3070
+ for (let k = 0; k < n; k++) {
3071
+ const top = cum;
3072
+ const bottom = cum + heightOf(visible[k].b);
3073
+ if (bottom > winTop && top < winBottom) {
3074
+ if (start2 < 0) start2 = k;
3075
+ end2 = k + 1;
3076
+ }
3077
+ cum = bottom;
3078
+ if (top >= winBottom) break;
3079
+ }
3080
+ if (start2 < 0) {
3081
+ start2 = 0;
3082
+ end2 = 0;
3083
+ }
3084
+ const a = activeRef.current;
3085
+ if (a !== null) {
3086
+ const av = visible.findIndex((v) => v.i === a);
3087
+ if (av >= 0) {
3088
+ if (av < start2) start2 = av;
3089
+ if (av + 1 > end2) end2 = av + 1;
3090
+ }
3091
+ }
3092
+ return { start: start2, end: end2 };
3093
+ };
3094
+ const computeRangeRef = React7.useRef(computeRange);
3095
+ computeRangeRef.current = computeRange;
3096
+ const setRangeIfChanged = React7.useCallback((r) => {
3097
+ setRange((prev) => prev.start === r.start && prev.end === r.end ? prev : r);
3098
+ }, []);
3099
+ React7.useLayoutEffect(() => {
3100
+ if (!virtualize) return;
3101
+ const content = virtInnerRef.current;
3102
+ if (!content) return;
3103
+ let scroller;
3104
+ if (ownScroller) {
3105
+ scroller = virtScrollRef.current;
3106
+ } else {
3107
+ scroller = getScrollParent(content);
3108
+ if (!scroller) {
3109
+ setOwnScroller(true);
3110
+ return;
3111
+ }
3112
+ }
3113
+ if (!scroller) return;
3114
+ scrollerRef.current = scroller;
3115
+ let raf = 0;
3116
+ const onScroll = () => {
3117
+ if (raf) return;
3118
+ raf = requestAnimationFrame(() => {
3119
+ raf = 0;
3120
+ setRangeIfChanged(computeRangeRef.current());
3121
+ });
3122
+ };
3123
+ scroller.addEventListener("scroll", onScroll, { passive: true });
3124
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(onScroll) : null;
3125
+ ro?.observe(scroller);
3126
+ if (typeof window !== "undefined") window.addEventListener("resize", onScroll);
3127
+ setRangeIfChanged(computeRangeRef.current());
3128
+ return () => {
3129
+ scroller.removeEventListener("scroll", onScroll);
3130
+ ro?.disconnect();
3131
+ if (typeof window !== "undefined") window.removeEventListener("resize", onScroll);
3132
+ if (raf) cancelAnimationFrame(raf);
3133
+ };
3134
+ }, [virtualize, ownScroller, setRangeIfChanged]);
3135
+ React7.useLayoutEffect(() => {
3136
+ if (!virtualize) return;
3137
+ rowEls.current.forEach((el, idx) => {
3138
+ const b = blocks[idx];
3139
+ if (!b) return;
3140
+ const h = el.offsetHeight;
3141
+ if (h > 0) heights.current.set(b.from, h);
3142
+ });
3143
+ setRangeIfChanged(computeRangeRef.current());
3144
+ });
3145
+ React7.useLayoutEffect(() => {
3146
+ if (virtualize) onVisibleChange?.();
3147
+ }, [range.start, range.end, virtualize, onVisibleChange]);
3148
+ if (readOnly && !md.trim()) {
3149
+ return /* @__PURE__ */ jsx("div", { className: rootClass, style, "data-typewright": "unified", ref: contentRef, children: /* @__PURE__ */ jsx("p", { className: "tw-placeholder", children: placeholder }) });
3150
+ }
3151
+ if (!md.trim() || typing) {
3152
+ return /* @__PURE__ */ jsxs("div", { className: rootClass, style, "data-typewright": "unified", ref: contentRef, children: [
3153
+ toolbar,
3154
+ /* @__PURE__ */ jsx(
3155
+ SourceArea,
3156
+ {
3157
+ value: md,
3158
+ full: true,
3159
+ placeholder,
3160
+ register,
3161
+ bindings,
3162
+ commentBase: commentsActive ? 0 : void 0,
3163
+ onCommentSelect: commentsActive ? onCommentSelect : void 0,
3164
+ onFocus: () => setTyping(true),
3165
+ onBlur: () => setTyping(false),
3166
+ onChange: (next, change) => commitValue(next, change)
3167
+ }
3168
+ )
3169
+ ] });
3170
+ }
3171
+ const renderRow = (i, b) => {
3172
+ const isHeading = b.type === "heading";
3173
+ const folded = isHeading && foldedHeadings.has(i);
3174
+ const islandEl = renderIsland(b, islands);
3175
+ return /* @__PURE__ */ jsxs(
3176
+ "div",
3177
+ {
3178
+ className: "tw-row",
3179
+ "data-block-type": b.type,
3180
+ ref: (el) => {
3181
+ if (el) rowEls.current.set(i, el);
3182
+ else rowEls.current.delete(i);
3183
+ },
3184
+ children: [
3185
+ foldingEnabled && b.type === "heading" && !readOnly && showGutter && /* @__PURE__ */ jsxs(Fragment, { children: [
3186
+ /* @__PURE__ */ jsx(
3187
+ "button",
3188
+ {
3189
+ type: "button",
3190
+ className: `tw-fold${folded ? " tw-folded" : ""}`,
3191
+ "aria-label": folded ? "Unfold section" : "Fold section",
3192
+ "aria-expanded": !folded,
3193
+ onClick: () => toggleFold(i),
3194
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "12", height: "12", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" }) })
3195
+ }
3196
+ ),
3197
+ /* @__PURE__ */ jsx(
3198
+ "button",
3199
+ {
3200
+ type: "button",
3201
+ className: "tw-fold-more",
3202
+ "aria-label": "Heading options",
3203
+ "aria-haspopup": "menu",
3204
+ "aria-expanded": foldMenuFor === i,
3205
+ onClick: (e) => {
3206
+ setFoldMenuRect(e.currentTarget.getBoundingClientRect());
3207
+ setFoldMenuFor(i);
3208
+ },
3209
+ children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "13", height: "13", fill: "currentColor", stroke: "none", "aria-hidden": "true", children: [
3210
+ /* @__PURE__ */ jsx("circle", { cx: "5", cy: "12", r: "1.7" }),
3211
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "1.7" }),
3212
+ /* @__PURE__ */ jsx("circle", { cx: "19", cy: "12", r: "1.7" })
3213
+ ] })
3214
+ }
3215
+ ),
3216
+ foldMenuFor === i && /* @__PURE__ */ jsx(
3217
+ FoldMenu,
3218
+ {
3219
+ open: true,
3220
+ level: b.level,
3221
+ folded,
3222
+ anchorRect: foldMenuRect,
3223
+ onSetLevel: (n) => setHeadingLevel(i, n),
3224
+ onToggleFold: () => toggleFold(i),
3225
+ onFoldAll: foldAll,
3226
+ onUnfoldAll: unfoldAll,
3227
+ onCopyLink: () => copyHeadingLink(i),
3228
+ onClose: () => setFoldMenuFor(null)
3229
+ }
3230
+ )
3231
+ ] }),
3232
+ active === i && !readOnly ? /* @__PURE__ */ jsx(
3233
+ SourceArea,
3234
+ {
3235
+ value: draft,
3236
+ autoFocus: true,
3237
+ register,
3238
+ bindings,
3239
+ commentBase: commentsActive ? b.from : void 0,
3240
+ onCommentSelect: commentsActive ? onCommentSelect : void 0,
3241
+ onChange: (next) => setDraft(next),
3242
+ onBlur: () => {
3243
+ captureFlip();
3244
+ commit();
3245
+ },
3246
+ onEscape: () => {
3247
+ captureFlip();
3248
+ commit();
3249
+ }
3250
+ }
3251
+ ) : islandEl ? islandEl : b.type === "table" && !readOnly ? (
3252
+ // A table is edited in its grid, not via a click-to-reveal source
3253
+ // textarea; the grid emits already-scoped splices we apply verbatim.
3254
+ /* @__PURE__ */ jsx(TableGrid, { table: b, source: mdRef.current, onChange: handleTableChange, readOnly })
3255
+ ) : caretReveal && isCaretEligible(b) ? (
3256
+ // Caret-level reveal (SPEC §5.2): edited in place via a managed
3257
+ // contentEditable that emits scoped splices — same commit path as
3258
+ // the table grid (apply the splice + commit → anchors/folds/parse
3259
+ // all keep working). Opt-in; never on the default block path.
3260
+ /* @__PURE__ */ jsx(CaretRevealBlock, { block: b, source: mdRef.current, onChange: handleTableChange, readOnly })
3261
+ ) : (() => {
3262
+ const html = withA11yHints(renderNode(b, renderOpts));
3263
+ const hasInteractive = HAS_INTERACTIVE_HTML.test(html);
3264
+ return /* @__PURE__ */ jsx(
3265
+ "div",
3266
+ {
3267
+ className: "tw-block",
3268
+ role: readOnly || hasInteractive ? void 0 : "button",
3269
+ "aria-label": !readOnly && hasInteractive ? "Edit block source" : void 0,
3270
+ tabIndex: readOnly ? void 0 : 0,
3271
+ "data-tw-from": b.from,
3272
+ "data-tw-to": b.to,
3273
+ onMouseDown: (e) => {
3274
+ if (readOnly) return;
3275
+ if (commentsActive) return;
3276
+ e.preventDefault();
3277
+ activate(b.from);
3278
+ },
3279
+ onClick: () => {
3280
+ if (readOnly || !commentsActive) return;
3281
+ const sel = window.getSelection();
3282
+ if (sel && !sel.isCollapsed && sel.toString().trim()) return;
3283
+ activate(b.from);
3284
+ },
3285
+ onKeyDown: (e) => {
3286
+ if (!readOnly && (e.key === "Enter" || e.key === " ")) {
3287
+ e.preventDefault();
3288
+ activate(b.from);
3289
+ }
3290
+ },
3291
+ dangerouslySetInnerHTML: { __html: html }
3292
+ }
3293
+ );
3294
+ })(),
3295
+ folded && /* @__PURE__ */ jsxs("button", { type: "button", className: "tw-foldchip", onClick: () => toggleFold(i), children: [
3296
+ "\u2026 ",
3297
+ foldedSummary(blocks, i)
3298
+ ] })
3299
+ ]
3300
+ },
3301
+ `${i}-${b.from}`
3302
+ );
3303
+ };
3304
+ if (!virtualize) {
3305
+ return /* @__PURE__ */ jsxs("div", { className: rootClass, style, "data-typewright": "unified", ref: contentRef, children: [
3306
+ toolbar,
3307
+ blocks.map((b, i) => hidden.has(i) ? null : renderRow(i, b))
3308
+ ] });
3309
+ }
3310
+ const start = Math.min(range.start, visible.length);
3311
+ const end = Math.min(range.end, visible.length);
3312
+ let topPx = 0;
3313
+ for (let k = 0; k < start; k++) topPx += heightOf(visible[k].b);
3314
+ let bottomPx = 0;
3315
+ for (let k = end; k < visible.length; k++) bottomPx += heightOf(visible[k].b);
3316
+ return /* @__PURE__ */ jsxs("div", { className: rootClass, style, "data-typewright": "unified", ref: contentRef, children: [
3317
+ toolbar,
3318
+ /* @__PURE__ */ jsx(
3319
+ "div",
3320
+ {
3321
+ ref: virtScrollRef,
3322
+ className: ownScroller ? "tw-virt-scroll" : void 0,
3323
+ style: ownScroller ? { overflow: "auto", maxHeight: "70vh" } : void 0,
3324
+ children: /* @__PURE__ */ jsxs("div", { ref: virtInnerRef, className: "tw-virt-content", children: [
3325
+ /* @__PURE__ */ jsx("div", { className: "tw-virt-spacer", "aria-hidden": "true", style: { height: topPx } }),
3326
+ visible.slice(start, end).map(({ b, i }) => renderRow(i, b)),
3327
+ /* @__PURE__ */ jsx("div", { className: "tw-virt-spacer", "aria-hidden": "true", style: { height: bottomPx } })
3328
+ ] })
3329
+ }
3330
+ )
3331
+ ] });
3332
+ }
3333
+ var FOLD_STORE_PREFIX = "typewright-folds:";
3334
+ function slugify(text) {
3335
+ return text.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
3336
+ }
3337
+ function headingKeyMap(blocks, src) {
3338
+ const seen = /* @__PURE__ */ new Map();
3339
+ const out = /* @__PURE__ */ new Map();
3340
+ blocks.forEach((b, i) => {
3341
+ if (b.type !== "heading") return;
3342
+ const base = slugify(src.slice(b.contentFrom, b.to)) || "section";
3343
+ const n = seen.get(base) ?? 0;
3344
+ seen.set(base, n + 1);
3345
+ out.set(i, n === 0 ? base : `${base}-${n}`);
3346
+ });
3347
+ return out;
3348
+ }
3349
+ function loadFoldSet(persistKey) {
3350
+ const set = /* @__PURE__ */ new Set();
3351
+ if (typeof window === "undefined") return set;
3352
+ let raw = null;
3353
+ try {
3354
+ raw = window.localStorage.getItem(FOLD_STORE_PREFIX + persistKey);
3355
+ } catch {
3356
+ return set;
3357
+ }
3358
+ if (!raw) return set;
3359
+ let keys;
3360
+ try {
3361
+ keys = JSON.parse(raw);
3362
+ } catch {
3363
+ return set;
3364
+ }
3365
+ if (!Array.isArray(keys)) return set;
3366
+ for (const k of keys) if (typeof k === "string") set.add(k);
3367
+ return set;
3368
+ }
3369
+ function saveFoldSet(persistKey, folds) {
3370
+ if (typeof window === "undefined") return;
3371
+ try {
3372
+ window.localStorage.setItem(FOLD_STORE_PREFIX + persistKey, JSON.stringify([...folds]));
3373
+ } catch {
3374
+ }
3375
+ }
3376
+ function foldedSummary(blocks, idx) {
3377
+ const b = blocks[idx];
3378
+ let lines = 0;
3379
+ let subs = 0;
3380
+ for (let j = idx + 1; j < blocks.length; j++) {
3381
+ const n = blocks[j];
3382
+ if (n.type === "heading" && n.level <= b.level) break;
3383
+ if (n.type === "heading") subs++;
3384
+ lines++;
3385
+ }
3386
+ return `${lines} block${lines === 1 ? "" : "s"}${subs ? ` \xB7 ${subs} subsection${subs === 1 ? "" : "s"}` : ""}`;
3387
+ }
3388
+ function SourceArea(props) {
3389
+ const { value, onChange, onBlur, onEscape, onFocus, onSelect, autoFocus, readOnly, placeholder, full, register, bindings, commentBase, onCommentSelect } = props;
3390
+ const ref = React7.useRef(null);
3391
+ const pendingSel = React7.useRef(null);
3392
+ const apply = React7.useCallback(
3393
+ (cmd) => {
3394
+ const el = ref.current;
3395
+ if (!el) return;
3396
+ const val = el.value;
3397
+ const r = applyCommand(val, { from: el.selectionStart, to: el.selectionEnd }, cmd);
3398
+ pendingSel.current = [r.selection.from, r.selection.to];
3399
+ onChange(r.text, { from: 0, to: val.length, insert: r.text });
3400
+ },
3401
+ [onChange]
3402
+ );
3403
+ React7.useEffect(() => {
3404
+ if (pendingSel.current && ref.current) {
3405
+ ref.current.selectionStart = pendingSel.current[0];
3406
+ ref.current.selectionEnd = pendingSel.current[1];
3407
+ pendingSel.current = null;
3408
+ }
3409
+ });
3410
+ const autoGrow = (el) => {
3411
+ if (full) return;
3412
+ el.style.height = "auto";
3413
+ el.style.height = `${el.scrollHeight}px`;
3414
+ };
3415
+ return /* @__PURE__ */ jsx(
3416
+ "textarea",
3417
+ {
3418
+ ref,
3419
+ className: `tw-source${full ? " tw-source-full" : ""}`,
3420
+ value,
3421
+ placeholder,
3422
+ readOnly,
3423
+ autoFocus,
3424
+ spellCheck: false,
3425
+ rows: full ? void 0 : 1,
3426
+ onChange: (e) => {
3427
+ onChange(e.target.value, { from: 0, to: value.length, insert: e.target.value });
3428
+ autoGrow(e.currentTarget);
3429
+ },
3430
+ onFocus: (e) => {
3431
+ autoGrow(e.currentTarget);
3432
+ onFocus?.();
3433
+ register?.({ apply });
3434
+ },
3435
+ onBlur: () => {
3436
+ onBlur?.();
3437
+ },
3438
+ onSelect: (e) => {
3439
+ const el = e.currentTarget;
3440
+ const from = el.selectionStart;
3441
+ const to = el.selectionEnd;
3442
+ onSelect?.({ main: { from, to }, ranges: [{ from, to }] });
3443
+ if (onCommentSelect && commentBase !== void 0 && from !== to) {
3444
+ onCommentSelect(commentBase + from, commentBase + to, el.value.slice(from, to));
3445
+ }
3446
+ },
3447
+ onKeyDown: (e) => {
3448
+ if (e.key === "Escape" && onEscape) {
3449
+ e.preventDefault();
3450
+ onEscape();
3451
+ return;
3452
+ }
3453
+ const cmd = bindings?.get(eventCanon(e));
3454
+ if (cmd) {
3455
+ e.preventDefault();
3456
+ apply(cmd);
3457
+ }
3458
+ }
3459
+ }
3460
+ );
3461
+ }
3462
+ function escapeText(s) {
3463
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3464
+ }
3465
+ var TYPEWRIGHT_CSS = `
3466
+ .tw-editor { --tw-fg:#1a1d20; --tw-muted:#5a6169; --tw-faint:#8b929a; --tw-bg:#ffffff; --tw-chip:#f0f1f3; --tw-line:rgba(18,22,27,.1); --tw-accent:#2f6fed; --tw-accent-soft:rgba(47,111,237,.1); --tw-code-bg:#f6f7f9; font-family:-apple-system,"SF Pro Text",system-ui,sans-serif; color:var(--tw-fg); line-height:1.6; font-size:15px; }
3467
+ @media (prefers-color-scheme: dark) { .tw-editor:not(.tw-theme-light) { --tw-fg:#e8eaed; --tw-muted:#a3abb2; --tw-faint:#6a727a; --tw-bg:#0f1215; --tw-chip:#1e242b; --tw-line:rgba(255,255,255,.1); --tw-accent:#6ea3ff; --tw-accent-soft:rgba(110,163,255,.14); --tw-code-bg:#13171b; } }
3468
+ .tw-editor.tw-theme-dark { --tw-fg:#e8eaed; --tw-muted:#a3abb2; --tw-faint:#6a727a; --tw-bg:#0f1215; --tw-chip:#1e242b; --tw-line:rgba(255,255,255,.1); --tw-accent:#6ea3ff; --tw-accent-soft:rgba(110,163,255,.14); --tw-code-bg:#13171b; }
3469
+ .tw-editor { background:var(--tw-bg); border-radius:10px; }
3470
+ .tw-editor h1,.tw-editor h2,.tw-editor h3,.tw-editor h4,.tw-editor h5,.tw-editor h6 { font-weight:680; letter-spacing:-.02em; margin:.7em 0 .3em; line-height:1.25; }
3471
+ .tw-editor h1{font-size:1.8em} .tw-editor h2{font-size:1.45em} .tw-editor h3{font-size:1.2em} .tw-editor h4{font-size:1.05em}
3472
+ .tw-editor p{margin:.5em 0} .tw-editor ul,.tw-editor ol{margin:.4em 0; padding-left:1.5em} .tw-editor li{margin:.15em 0}
3473
+ .tw-editor a{color:var(--tw-accent); text-decoration:none; border-bottom:1px solid var(--tw-accent-soft)}
3474
+ .tw-editor code{font-family:"SF Mono",ui-monospace,Menlo,monospace; font-size:.88em; background:var(--tw-chip); border:1px solid var(--tw-line); border-radius:5px; padding:1px 5px}
3475
+ .tw-editor pre{background:var(--tw-code-bg); border:1px solid var(--tw-line); border-radius:9px; padding:12px 14px; overflow-x:auto; margin:.6em 0}
3476
+ .tw-editor pre code{background:none; border:0; padding:0; font-size:13px; line-height:1.55}
3477
+ .tw-editor blockquote{border-left:3px solid var(--tw-line); margin:.6em 0; padding:.1em 0 .1em 14px; color:var(--tw-muted)}
3478
+ .tw-editor table{border-collapse:collapse; margin:.6em 0; font-size:.95em} .tw-editor th,.tw-editor td{border:1px solid var(--tw-line); padding:6px 12px} .tw-editor th{background:var(--tw-chip)}
3479
+ .tw-editor hr{border:0; border-top:1px solid var(--tw-line); margin:1em 0}
3480
+ .tw-editor img{max-width:100%}
3481
+ .tw-editor input[type=checkbox]{margin-right:6px; vertical-align:middle}
3482
+ .tw-placeholder{color:var(--tw-faint)}
3483
+ .tw-mode-preview,.tw-mode-read,.tw-mode-unified{padding:14px 18px}
3484
+ .tw-mode-edit{padding:0}
3485
+ .tw-toolbar{display:flex; align-items:center; justify-content:center; gap:3px; flex-wrap:wrap; padding:5px 7px; margin-bottom:9px; border:1px solid var(--tw-line); border-radius:12px; background:color-mix(in srgb, var(--tw-bg) 80%, transparent); backdrop-filter:blur(18px) saturate(1.6); -webkit-backdrop-filter:blur(18px) saturate(1.6); position:sticky; top:0; z-index:5; box-shadow:0 4px 14px -8px rgba(0,0,0,.3), inset 0 1px 0 rgba(255,255,255,.06)}
3486
+ .tw-toolbar-floating{max-height:0; padding-top:0; padding-bottom:0; margin-bottom:0; opacity:0; overflow:hidden; border-color:transparent; box-shadow:none; transform:translateY(-7px); transition:max-height .28s cubic-bezier(.32,.72,0,1), opacity .2s, margin .28s cubic-bezier(.32,.72,0,1), padding .28s, transform .24s cubic-bezier(.32,.72,0,1)}
3487
+ .tw-editor:hover .tw-toolbar-floating,.tw-editor:focus-within .tw-toolbar-floating{max-height:84px; padding-top:5px; padding-bottom:5px; margin-bottom:9px; opacity:1; border-color:var(--tw-line); box-shadow:0 4px 14px -8px rgba(0,0,0,.3), inset 0 1px 0 rgba(255,255,255,.06); transform:none}
3488
+ .tw-tb-sep{width:1px; height:18px; background:var(--tw-line); margin:0 3px}
3489
+ .tw-tb-btn{min-width:28px; height:28px; padding:0 7px; border:1px solid transparent; background:transparent; border-radius:7px; color:var(--tw-muted); font-size:13px; line-height:1; cursor:pointer; display:inline-flex; align-items:center; justify-content:center; transition:color .15s,background .15s}
3490
+ .tw-tb-btn:hover{color:var(--tw-fg); background:var(--tw-accent-soft)}
3491
+ .tw-tb-b{font-weight:800} .tw-tb-i{font-style:italic; font-family:Georgia,serif} .tw-tb-s{text-decoration:line-through}
3492
+ .tw-row{transition:transform .3s cubic-bezier(.32,.72,0,1)}
3493
+ @keyframes tw-stream-in{from{opacity:0; transform:translateY(7px)}to{opacity:1; transform:none}}
3494
+ .tw-streamblk.tw-stream-in{animation:tw-stream-in .3s cubic-bezier(.32,.72,0,1) both}
3495
+ .tw-row{position:relative; display:flex; align-items:flex-start; gap:2px}
3496
+ .tw-block{flex:1; min-width:0; border-radius:6px; cursor:text; padding:1px 4px; margin-left:-4px; transition:background .15s}
3497
+ .tw-mode-unified .tw-block:hover,.tw-mode-preview .tw-block:hover{background:var(--tw-accent-soft)}
3498
+ .tw-block:focus-visible{outline:2px solid var(--tw-accent); outline-offset:2px}
3499
+ .tw-block>:first-child{margin-top:.15em} .tw-block>:last-child{margin-bottom:.15em}
3500
+ .tw-fold{flex:none; width:20px; height:24px; margin-top:.35em; border:0; background:none; color:var(--tw-faint); border-radius:5px; display:grid; place-items:center; cursor:pointer; opacity:.55; transition:opacity .15s,color .15s,transform .15s}
3501
+ .tw-fold:hover{opacity:1; color:var(--tw-accent)} .tw-fold.tw-folded svg{transform:rotate(-90deg)}
3502
+ .tw-fold-more{flex:none; width:18px; height:24px; margin-top:.35em; border:0; background:none; color:var(--tw-faint); border-radius:5px; display:grid; place-items:center; cursor:pointer; opacity:0; transition:opacity .15s,color .15s}
3503
+ .tw-row:hover .tw-fold-more,.tw-fold-more:focus-visible,.tw-fold-more[aria-expanded="true"]{opacity:.6}
3504
+ .tw-fold-more:hover,.tw-fold-more[aria-expanded="true"]{opacity:1; color:var(--tw-accent)}
3505
+ .tw-foldchip{margin-left:6px; font-size:12.5px; color:var(--tw-faint); background:none; border:1px dashed var(--tw-line); border-radius:7px; padding:2px 9px; cursor:pointer}
3506
+ .tw-foldchip:hover{color:var(--tw-muted); border-color:var(--tw-accent)}
3507
+ .tw-source{width:100%; box-sizing:border-box; font-family:"SF Mono",ui-monospace,Menlo,monospace; font-size:13.5px; line-height:1.6; color:var(--tw-fg); background:var(--tw-accent-soft); border:1px solid var(--tw-accent); border-radius:7px; padding:6px 9px; resize:none; outline:none}
3508
+ .tw-source-full{min-height:280px; height:100%; background:var(--tw-bg); border:0; border-radius:0; padding:16px 18px; font-size:14px}
3509
+ .tw-caret{display:inline-block; width:2px; height:1.05em; background:var(--tw-accent); vertical-align:text-bottom; margin-left:1px; border-radius:1px; animation:tw-blink 1.06s steps(1) infinite}
3510
+ @keyframes tw-blink{50%{opacity:0}}
3511
+ .tw-pending{opacity:.62; border-bottom:1.5px dashed var(--tw-accent); border-radius:1px}
3512
+ .tw-pending-strong{font-weight:680} .tw-pending-em{font-style:italic} .tw-pending-code{font-family:"SF Mono",ui-monospace,monospace}
3513
+ .tw-skeleton{border:1px solid var(--tw-line); border-radius:10px; padding:12px; margin:.6em 0; background:linear-gradient(100deg, var(--tw-chip) 30%, var(--tw-code-bg) 50%, var(--tw-chip) 70%); background-size:200% 100%; animation:tw-shimmer 1.3s infinite}
3514
+ .tw-skeleton-label{font-family:"SF Mono",ui-monospace,monospace; font-size:11px; color:var(--tw-accent)}
3515
+ .tw-skeleton-bar{height:8px; border-radius:4px; background:var(--tw-line); margin-top:9px} .tw-skeleton-bar.two{width:70%} .tw-skeleton-bar.three{width:45%}
3516
+ @keyframes tw-shimmer{to{background-position:-200% 0}}
3517
+ @media (prefers-reduced-motion: reduce){ .tw-caret,.tw-skeleton,.tw-streamblk.tw-stream-in,.tw-row{animation:none !important; transition:none !important} }
3518
+ .tw-editor .tw-tok-keyword{color:#cf222e} .tw-editor .tw-tok-string{color:#0a3069} .tw-editor .tw-tok-comment{color:#6e7781; font-style:italic} .tw-editor .tw-tok-number{color:#0550ae} .tw-editor .tw-tok-punct{color:var(--tw-muted)} .tw-editor .tw-tok-fn{color:#8250df} .tw-editor .tw-tok-type{color:#953800} .tw-editor .tw-tok-prop{color:#116329}
3519
+ @media (prefers-color-scheme: dark){ .tw-editor:not(.tw-theme-light) .tw-tok-keyword{color:#ff7b72} .tw-editor:not(.tw-theme-light) .tw-tok-string{color:#a5d6ff} .tw-editor:not(.tw-theme-light) .tw-tok-comment{color:#8b949e} .tw-editor:not(.tw-theme-light) .tw-tok-number{color:#79c0ff} .tw-editor:not(.tw-theme-light) .tw-tok-fn{color:#d2a8ff} .tw-editor:not(.tw-theme-light) .tw-tok-type{color:#ffa657} .tw-editor:not(.tw-theme-light) .tw-tok-prop{color:#7ee787} }
3520
+ .tw-editor.tw-theme-dark .tw-tok-keyword{color:#ff7b72} .tw-editor.tw-theme-dark .tw-tok-string{color:#a5d6ff} .tw-editor.tw-theme-dark .tw-tok-comment{color:#8b949e} .tw-editor.tw-theme-dark .tw-tok-number{color:#79c0ff} .tw-editor.tw-theme-dark .tw-tok-fn{color:#d2a8ff} .tw-editor.tw-theme-dark .tw-tok-type{color:#ffa657} .tw-editor.tw-theme-dark .tw-tok-prop{color:#7ee787}
3521
+ .tw-editor .tw-math-src{font-family:"SF Mono",ui-monospace,Menlo,monospace; font-size:.9em; background:var(--tw-code-bg); border:1px solid var(--tw-line); border-radius:5px; padding:1px 5px}
3522
+ .tw-editor div.tw-math-src{display:block; padding:8px 12px; margin:.6em 0; text-align:center; overflow-x:auto}
3523
+ .tw-editor .tw-footnotes{border-top:1px solid var(--tw-line); margin-top:1.4em; padding-top:.6em; font-size:.9em; color:var(--tw-muted)}
3524
+ .tw-editor .tw-fnref{font-size:.82em} .tw-editor .tw-fnref a{color:var(--tw-accent); border-bottom:0} .tw-editor .tw-fn-back{color:var(--tw-accent); border-bottom:0; margin-left:4px; text-decoration:none}
3525
+ .tw-editor dl{margin:.5em 0} .tw-editor dt{font-weight:680; margin-top:.4em} .tw-editor dd{margin:.15em 0 .35em 1.4em; color:var(--tw-muted)}
3526
+ /* ---- Collaboration: shell, comment highlights, presence, popup, composer ---- */
3527
+ .tw-editor-shell{ --tw-fg:#1a1d20; --tw-muted:#5a6169; --tw-faint:#8b929a; --tw-bg:#ffffff; --tw-chip:#f0f1f3; --tw-line:rgba(18,22,27,.1); --tw-accent:#2f6fed; --tw-accent-soft:rgba(47,111,237,.1); --tw-code-bg:#f6f7f9; position:relative; font-family:-apple-system,"SF Pro Text",system-ui,sans-serif }
3528
+ @media (prefers-color-scheme: dark){ .tw-editor-shell:not(.tw-theme-light){ --tw-fg:#e8eaed; --tw-muted:#a3abb2; --tw-faint:#6a727a; --tw-bg:#0f1215; --tw-chip:#1e242b; --tw-line:rgba(255,255,255,.1); --tw-accent:#6ea3ff; --tw-accent-soft:rgba(110,163,255,.14); --tw-code-bg:#13171b } }
3529
+ .tw-editor-shell.tw-theme-dark{ --tw-fg:#e8eaed; --tw-muted:#a3abb2; --tw-faint:#6a727a; --tw-bg:#0f1215; --tw-chip:#1e242b; --tw-line:rgba(255,255,255,.1); --tw-accent:#6ea3ff; --tw-accent-soft:rgba(110,163,255,.14); --tw-code-bg:#13171b }
3530
+ .tw-editor-shell.tw-comments-open>.tw-editor{margin-right:min(340px,82vw); transition:margin .26s cubic-bezier(.32,.72,0,1)}
3531
+ .tw-collab-bar{position:absolute; top:8px; right:10px; z-index:22; display:flex; align-items:center; gap:8px; pointer-events:none}
3532
+ .tw-collab-bar>*{pointer-events:auto}
3533
+ .tw-presence{display:flex; align-items:center}
3534
+ .tw-presence-av{width:26px; height:26px; border-radius:50%; display:grid; place-items:center; font-size:11px; font-weight:640; color:#0b0d0f; border:2px solid var(--tw-bg); margin-left:-7px; box-shadow:0 1px 3px rgba(0,0,0,.3)}
3535
+ .tw-presence-av:first-child{margin-left:0}
3536
+ .tw-comments-toggle{display:inline-flex; align-items:center; gap:6px; height:28px; padding:0 9px; border:1px solid var(--tw-line); border-radius:9px; background:color-mix(in srgb, var(--tw-bg) 82%, transparent); backdrop-filter:blur(16px) saturate(1.5); -webkit-backdrop-filter:blur(16px) saturate(1.5); color:var(--tw-muted); font:inherit; font-size:12.5px; cursor:pointer; transition:color .15s, border-color .15s, background .15s}
3537
+ .tw-comments-toggle:hover{color:var(--tw-fg); border-color:var(--tw-accent)}
3538
+ .tw-comments-toggle.tw-on{color:var(--tw-accent); border-color:var(--tw-accent); background:var(--tw-accent-soft)}
3539
+ .tw-comments-toggle:focus-visible{outline:2px solid var(--tw-accent); outline-offset:2px}
3540
+ .tw-comments-toggle svg{width:15px; height:15px; flex:none}
3541
+ .tw-comments-toggle-n{font-variant-numeric:tabular-nums; font-size:11px; font-weight:640; background:var(--tw-chip); border-radius:999px; padding:0 6px; line-height:1.6}
3542
+ .tw-settings-gear{display:inline-flex; align-items:center; justify-content:center; width:28px; height:28px; flex:none; border:1px solid var(--tw-line); border-radius:9px; background:color-mix(in srgb, var(--tw-bg) 82%, transparent); backdrop-filter:blur(16px) saturate(1.5); -webkit-backdrop-filter:blur(16px) saturate(1.5); color:var(--tw-muted); cursor:pointer; transition:color .15s, border-color .15s, background .15s}
3543
+ .tw-settings-gear:hover{color:var(--tw-fg); border-color:var(--tw-accent)}
3544
+ .tw-settings-gear.tw-on{color:var(--tw-accent); border-color:var(--tw-accent); background:var(--tw-accent-soft)}
3545
+ .tw-settings-gear:focus-visible{outline:2px solid var(--tw-accent); outline-offset:2px}
3546
+ .tw-settings-gear svg{width:15px; height:15px; flex:none}
3547
+ .tw-comment{background:var(--tw-accent-soft); border-bottom:2px solid var(--tw-accent); border-radius:3px; padding:0 1px; cursor:pointer}
3548
+ .tw-comment:hover{background:color-mix(in srgb, var(--tw-accent) 24%, transparent)}
3549
+ @keyframes tw-comment-hl-flash{0%,100%{background:var(--tw-accent-soft)} 25%,70%{background:color-mix(in srgb, var(--tw-accent) 38%, transparent)}}
3550
+ .tw-comment-flash{animation:tw-comment-hl-flash 1.4s ease}
3551
+ .tw-remote-cursor{position:relative; display:inline-block; width:0; border-left:2px solid var(--tw-remote, var(--tw-accent)); margin:0 -1px; vertical-align:text-bottom; height:1.05em}
3552
+ .tw-remote-flag{position:absolute; top:-1.15em; left:-1px; white-space:nowrap; font-size:9.5px; line-height:1.3; font-weight:640; color:#0b0d0f; background:var(--tw-remote, var(--tw-accent)); border-radius:4px 4px 4px 0; padding:0 4px; opacity:0; transform:translateY(2px); transition:opacity .15s, transform .15s; pointer-events:none}
3553
+ .tw-remote-cursor:hover .tw-remote-flag{opacity:1; transform:none}
3554
+ .tw-selpop{position:fixed; z-index:120; display:flex; align-items:center; gap:3px; padding:4px; background:color-mix(in srgb, var(--tw-bg) 86%, transparent); backdrop-filter:blur(22px) saturate(1.7); -webkit-backdrop-filter:blur(22px) saturate(1.7); border:1px solid var(--tw-line); border-radius:11px; box-shadow:0 12px 34px -10px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.06); opacity:0; transform:translateY(4px); transition:opacity .14s, transform .14s cubic-bezier(.32,.72,0,1)}
3555
+ .tw-selpop.show{opacity:1; transform:none}
3556
+ .tw-selpop-btn{height:27px; padding:0 10px; border:0; border-radius:8px; background:transparent; color:var(--tw-accent); font:inherit; font-size:12.5px; font-weight:560; cursor:pointer; white-space:nowrap}
3557
+ .tw-selpop-btn:hover{background:var(--tw-accent-soft)}
3558
+ .tw-composer{position:fixed; z-index:121; width:268px; padding:10px; background:color-mix(in srgb, var(--tw-bg) 90%, transparent); backdrop-filter:blur(22px) saturate(1.7); -webkit-backdrop-filter:blur(22px) saturate(1.7); border:1px solid var(--tw-line); border-radius:13px; box-shadow:0 20px 48px -12px rgba(0,0,0,.5), inset 0 1px 0 rgba(255,255,255,.06); color:var(--tw-fg); opacity:0; transform:translateY(6px) scale(.98); transition:opacity .16s, transform .16s cubic-bezier(.32,.72,0,1)}
3559
+ .tw-composer.show{opacity:1; transform:none}
3560
+ .tw-composer-quote{font-size:11.5px; color:var(--tw-muted); font-style:italic; border-left:2px solid var(--tw-accent-soft); padding-left:7px; margin-bottom:8px; overflow-wrap:anywhere}
3561
+ .tw-composer textarea{width:100%; box-sizing:border-box; height:62px; resize:none; background:var(--tw-bg); border:1px solid var(--tw-line); border-radius:9px; padding:8px 9px; font:inherit; font-size:13px; color:var(--tw-fg); outline:none; transition:border-color .15s}
3562
+ .tw-composer textarea:focus{border-color:var(--tw-accent)}
3563
+ .tw-composer textarea::placeholder{color:var(--tw-faint)}
3564
+ .tw-composer-row{display:flex; justify-content:flex-end; gap:6px; margin-top:8px}
3565
+ .tw-composer-btn{border:1px solid var(--tw-line); background:var(--tw-chip); color:var(--tw-muted); border-radius:8px; padding:5px 11px; font:inherit; font-size:12px; cursor:pointer; transition:color .15s, border-color .15s, background .15s}
3566
+ .tw-composer-btn:hover{color:var(--tw-fg); border-color:var(--tw-accent)}
3567
+ .tw-composer-btn:focus-visible{outline:2px solid var(--tw-accent); outline-offset:2px}
3568
+ .tw-composer-primary{background:var(--tw-accent); border-color:var(--tw-accent); color:#fff}
3569
+ .tw-composer-primary:hover{color:#fff; filter:brightness(1.06)}
3570
+ .tw-composer-btn:disabled{opacity:.5; cursor:default}
3571
+ @media (prefers-reduced-transparency: reduce){ .tw-selpop,.tw-composer,.tw-comments-toggle,.tw-settings-gear{backdrop-filter:none; -webkit-backdrop-filter:none; background:var(--tw-bg)} }
3572
+ @media (prefers-reduced-motion: reduce){ .tw-selpop,.tw-composer,.tw-comment-flash,.tw-editor-shell.tw-comments-open>.tw-editor{animation:none !important; transition:none !important} }
3573
+ `;
3574
+ function StreamingPreview(props) {
3575
+ useInjectStyles();
3576
+ const {
3577
+ text: controlledText,
3578
+ stream,
3579
+ anticipate: ant = true,
3580
+ smooth,
3581
+ componentFallback,
3582
+ className,
3583
+ style
3584
+ } = props;
3585
+ const [streamed, setStreamed] = React7.useState("");
3586
+ const value = controlledText !== void 0 ? controlledText : streamed;
3587
+ const containerRef = React7.useRef(null);
3588
+ React7.useEffect(() => {
3589
+ if (!stream) return;
3590
+ let cancelled = false;
3591
+ setStreamed("");
3592
+ const controller = createStreamController(
3593
+ (t) => {
3594
+ if (!cancelled) setStreamed(t);
3595
+ },
3596
+ { smooth }
3597
+ );
3598
+ void pipeStream(stream, controller).catch(() => {
3599
+ });
3600
+ return () => {
3601
+ cancelled = true;
3602
+ controller.reset();
3603
+ };
3604
+ }, [stream, smooth]);
3605
+ React7.useEffect(() => {
3606
+ const container = containerRef.current;
3607
+ if (!container || typeof document === "undefined") return;
3608
+ if (!value.trim()) {
3609
+ container.replaceChildren();
3610
+ return;
3611
+ }
3612
+ const { html } = anticipate(value, ant, componentFallback);
3613
+ const tmp = document.createElement("div");
3614
+ tmp.innerHTML = html;
3615
+ const next = Array.from(tmp.children);
3616
+ for (let i = 0; i < next.length; i++) {
3617
+ const nb = next[i];
3618
+ const wrapper = container.children[i];
3619
+ if (!wrapper) {
3620
+ const w = document.createElement("div");
3621
+ w.className = "tw-streamblk tw-stream-in";
3622
+ w.appendChild(nb);
3623
+ container.appendChild(w);
3624
+ continue;
3625
+ }
3626
+ const cur = wrapper.firstElementChild;
3627
+ if (!cur || cur.outerHTML === nb.outerHTML) continue;
3628
+ const typeChanged = cur.tagName !== nb.tagName || cur.className !== nb.className;
3629
+ wrapper.replaceChildren(nb);
3630
+ if (typeChanged) {
3631
+ wrapper.classList.remove("tw-stream-in");
3632
+ void wrapper.offsetWidth;
3633
+ wrapper.classList.add("tw-stream-in");
3634
+ }
3635
+ }
3636
+ while (container.children.length > next.length) container.lastElementChild?.remove();
3637
+ }, [value, ant, componentFallback]);
3638
+ const cls = ["tw-editor", "tw-streaming", className].filter(Boolean).join(" ");
3639
+ return /* @__PURE__ */ jsx("div", { ref: containerRef, className: cls, style, "data-typewright": "streaming" });
3640
+ }
3641
+
3642
+ export { CARET_REVEAL_CSS, CaretRevealBlock, SandboxIsland, StreamingPreview, TypewrightEditor, useInjectStyles };
3643
+ //# sourceMappingURL=index.js.map
3644
+ //# sourceMappingURL=index.js.map