@zuilib/text-editor 0.2.1 → 0.3.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.
package/dist/index.js CHANGED
@@ -1,10 +1,15 @@
1
- // src/MarkdownEditor.tsx
2
- import { useCallback as useCallback5, useEffect as useEffect9, useMemo, useRef as useRef4, useState as useState5 } from "react";
1
+ // src/EditorRoot.tsx
2
+ import {
3
+ createContext,
4
+ useCallback as useCallback2,
5
+ useContext,
6
+ useEffect as useEffect6,
7
+ useMemo,
8
+ useRef as useRef3,
9
+ useState as useState2
10
+ } from "react";
3
11
  import { LexicalComposer } from "@lexical/react/LexicalComposer";
4
- import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
5
- import { ContentEditable } from "@lexical/react/LexicalContentEditable";
6
12
  import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
7
- import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
8
13
  import { ListPlugin } from "@lexical/react/LexicalListPlugin";
9
14
  import { CheckListPlugin } from "@lexical/react/LexicalCheckListPlugin";
10
15
  import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
@@ -12,7 +17,7 @@ import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPl
12
17
  import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin";
13
18
  import { TablePlugin } from "@lexical/react/LexicalTablePlugin";
14
19
  import { CHECK_LIST, CODE, TRANSFORMERS as TRANSFORMERS2 } from "@lexical/markdown";
15
- import { useLexicalComposerContext as useLexicalComposerContext9 } from "@lexical/react/LexicalComposerContext";
20
+ import { useLexicalComposerContext as useLexicalComposerContext6 } from "@lexical/react/LexicalComposerContext";
16
21
  import { HeadingNode, QuoteNode } from "@lexical/rich-text";
17
22
  import { ListItemNode, ListNode as ListNode2 } from "@lexical/list";
18
23
  import { CodeHighlightNode, CodeNode } from "@lexical/code";
@@ -166,146 +171,9 @@ function CodeHighlightPlugin() {
166
171
  return null;
167
172
  }
168
173
 
169
- // src/plugins/FoldingPlugin.tsx
170
- import {
171
- useCallback,
172
- useEffect as useEffect4,
173
- useRef,
174
- useState
175
- } from "react";
176
- import { $getRoot, $getSelection as $getSelection2, $isRangeSelection as $isRangeSelection2 } from "lexical";
177
- import { useLexicalComposerContext as useLexicalComposerContext4 } from "@lexical/react/LexicalComposerContext";
178
- import { $isHeadingNode } from "@lexical/rich-text";
179
- import { jsx } from "react/jsx-runtime";
180
- var HEADING_LEVELS = {
181
- h1: 1,
182
- h2: 2,
183
- h3: 3,
184
- h4: 4,
185
- h5: 5,
186
- h6: 6
187
- };
188
- function FoldingPlugin() {
189
- const [editor] = useLexicalComposerContext4();
190
- const [foldedKeys, setFoldedKeys] = useState(/* @__PURE__ */ new Set());
191
- const [buttons, setButtons] = useState([]);
192
- const foldedRef = useRef(foldedKeys);
193
- foldedRef.current = foldedKeys;
194
- const sync = useCallback(() => {
195
- editor.getEditorState().read(() => {
196
- const children = $getRoot().getChildren();
197
- const folded = foldedRef.current;
198
- const hidden = /* @__PURE__ */ new Set();
199
- const headings = [];
200
- for (let i = 0; i < children.length; i++) {
201
- const node = children[i];
202
- if (!$isHeadingNode(node)) continue;
203
- const level = HEADING_LEVELS[node.getTag()] ?? 6;
204
- const isFolded = folded.has(node.getKey());
205
- headings.push({ key: node.getKey(), folded: isFolded });
206
- if (!isFolded) continue;
207
- for (let j = i + 1; j < children.length; j++) {
208
- const sibling = children[j];
209
- if ($isHeadingNode(sibling) && (HEADING_LEVELS[sibling.getTag()] ?? 6) <= level) {
210
- break;
211
- }
212
- hidden.add(sibling.getKey());
213
- }
214
- }
215
- const selection = $getSelection2();
216
- if ($isRangeSelection2(selection)) {
217
- const topLevel = selection.anchor.getNode().getTopLevelElement();
218
- if (topLevel && hidden.has(topLevel.getKey())) {
219
- let node = topLevel.getPreviousSibling();
220
- while (node) {
221
- if ($isHeadingNode(node) && folded.has(node.getKey())) {
222
- const unfoldKey = node.getKey();
223
- setFoldedKeys((prev) => {
224
- const next = new Set(prev);
225
- next.delete(unfoldKey);
226
- return next;
227
- });
228
- return;
229
- }
230
- node = node.getPreviousSibling();
231
- }
232
- }
233
- }
234
- for (const child of children) {
235
- const element = editor.getElementByKey(child.getKey());
236
- if (!element) continue;
237
- const shouldHide = hidden.has(child.getKey());
238
- if (shouldHide) {
239
- element.style.display = "none";
240
- } else if (element.style.display === "none") {
241
- element.style.display = "";
242
- }
243
- }
244
- setButtons(
245
- headings.map(({ key, folded: isFolded }) => {
246
- const element = editor.getElementByKey(key);
247
- element?.classList.toggle("zui-heading-folded", isFolded);
248
- return {
249
- key,
250
- folded: isFolded,
251
- top: element?.offsetTop ?? 0,
252
- height: element?.offsetHeight ?? 0
253
- };
254
- })
255
- );
256
- });
257
- }, [editor]);
258
- useEffect4(() => {
259
- sync();
260
- const unregister = editor.registerUpdateListener(sync);
261
- const rootElement = editor.getRootElement();
262
- const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(sync) : null;
263
- if (rootElement && observer) observer.observe(rootElement);
264
- return () => {
265
- unregister();
266
- observer?.disconnect();
267
- };
268
- }, [editor, sync, foldedKeys]);
269
- const toggle = useCallback((key) => {
270
- setFoldedKeys((prev) => {
271
- const next = new Set(prev);
272
- if (next.has(key)) next.delete(key);
273
- else next.add(key);
274
- return next;
275
- });
276
- }, []);
277
- return /* @__PURE__ */ jsx("div", { className: "zui-text-editor-fold-gutter", children: buttons.map(({ key, folded, top, height }) => /* @__PURE__ */ jsx(
278
- "button",
279
- {
280
- type: "button",
281
- className: `zui-text-editor-fold ${folded ? "is-folded" : ""}`,
282
- style: { top: top + Math.max(0, (Math.min(height, 44) - 18) / 2) },
283
- title: folded ? "Expand section" : "Collapse section",
284
- "aria-label": folded ? "Expand section" : "Collapse section",
285
- "aria-expanded": !folded,
286
- onClick: () => toggle(key),
287
- children: /* @__PURE__ */ jsx(
288
- "svg",
289
- {
290
- viewBox: "0 0 20 20",
291
- width: "12",
292
- height: "12",
293
- fill: "none",
294
- stroke: "currentColor",
295
- strokeWidth: "2",
296
- strokeLinecap: "round",
297
- strokeLinejoin: "round",
298
- children: /* @__PURE__ */ jsx("path", { d: "M6 8l4 4 4-4" })
299
- }
300
- )
301
- },
302
- key
303
- )) });
304
- }
305
-
306
174
  // src/plugins/MarkdownSyncPlugin.tsx
307
- import { useEffect as useEffect5, useRef as useRef2 } from "react";
308
- import { useLexicalComposerContext as useLexicalComposerContext5 } from "@lexical/react/LexicalComposerContext";
175
+ import { useEffect as useEffect4, useRef } from "react";
176
+ import { useLexicalComposerContext as useLexicalComposerContext4 } from "@lexical/react/LexicalComposerContext";
309
177
  import {
310
178
  $convertFromMarkdownString,
311
179
  $convertToMarkdownString
@@ -315,11 +183,11 @@ function MarkdownSyncPlugin({
315
183
  onChange,
316
184
  transformers
317
185
  }) {
318
- const [editor] = useLexicalComposerContext5();
319
- const lastMarkdownRef = useRef2("");
320
- const onChangeRef = useRef2(onChange);
186
+ const [editor] = useLexicalComposerContext4();
187
+ const lastMarkdownRef = useRef("");
188
+ const onChangeRef = useRef(onChange);
321
189
  onChangeRef.current = onChange;
322
- useEffect5(() => {
190
+ useEffect4(() => {
323
191
  if (initialMarkdown && initialMarkdown !== lastMarkdownRef.current) {
324
192
  lastMarkdownRef.current = initialMarkdown;
325
193
  editor.update(() => {
@@ -327,7 +195,7 @@ function MarkdownSyncPlugin({
327
195
  }, { tag: "initial-load" });
328
196
  }
329
197
  }, [editor, initialMarkdown]);
330
- useEffect5(() => {
198
+ useEffect4(() => {
331
199
  return editor.registerUpdateListener(
332
200
  ({ editorState, dirtyElements, dirtyLeaves, tags }) => {
333
201
  if (dirtyElements.size === 0 && dirtyLeaves.size === 0) return;
@@ -342,145 +210,101 @@ function MarkdownSyncPlugin({
342
210
  return null;
343
211
  }
344
212
 
345
- // src/plugins/OutlinePlugin.tsx
346
- import { useCallback as useCallback2, useEffect as useEffect6, useState as useState2 } from "react";
347
- import { useLexicalComposerContext as useLexicalComposerContext6 } from "@lexical/react/LexicalComposerContext";
348
- import {
349
- TableOfContentsPlugin
350
- } from "@lexical/react/LexicalTableOfContentsPlugin";
351
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
352
- var INDENT_PER_LEVEL = {
353
- h1: 0,
354
- h2: 1,
355
- h3: 2,
356
- h4: 3,
357
- h5: 4,
358
- h6: 5
359
- };
360
- function OutlinePlugin() {
361
- const [editor] = useLexicalComposerContext6();
362
- const [collapsed, setCollapsed] = useState2(false);
363
- const [activeKey, setActiveKey] = useState2(null);
364
- useEffect6(() => {
365
- const rootElement = editor.getRootElement();
366
- const scroller = rootElement?.closest(".zui-text-editor");
367
- if (!rootElement || !(scroller instanceof HTMLElement)) return;
368
- let frame = 0;
369
- const updateActive = () => {
370
- cancelAnimationFrame(frame);
371
- frame = requestAnimationFrame(() => {
372
- const headings = rootElement.querySelectorAll(
373
- "h1, h2, h3, h4, h5, h6"
374
- );
375
- const threshold = scroller.scrollTop + 80;
376
- let current = null;
377
- for (const el of headings) {
378
- if (el.offsetTop <= threshold) {
379
- current = el.getAttribute("data-outline-key");
380
- }
381
- }
382
- setActiveKey(current);
383
- });
384
- };
385
- updateActive();
386
- scroller.addEventListener("scroll", updateActive, { passive: true });
387
- const unregister = editor.registerUpdateListener(updateActive);
388
- return () => {
389
- cancelAnimationFrame(frame);
390
- scroller.removeEventListener("scroll", updateActive);
391
- unregister();
392
- };
393
- }, [editor]);
394
- const scrollTo = useCallback2(
395
- (key) => {
396
- editor.getEditorState().read(() => {
397
- const element = editor.getElementByKey(key);
398
- element?.scrollIntoView({ behavior: "smooth", block: "start" });
399
- });
400
- },
401
- [editor]
402
- );
403
- return /* @__PURE__ */ jsx2(TableOfContentsPlugin, { children: (entries) => {
404
- for (const [key] of entries) {
405
- editor.getElementByKey(key)?.setAttribute("data-outline-key", key);
406
- }
407
- return /* @__PURE__ */ jsxs(
408
- "div",
409
- {
410
- className: `zui-text-editor-outline ${collapsed ? "is-collapsed" : ""}`,
411
- children: [
412
- /* @__PURE__ */ jsx2(
413
- "button",
414
- {
415
- type: "button",
416
- className: "zui-text-editor-outline-toggle",
417
- title: collapsed ? "Show outline" : "Hide outline",
418
- "aria-label": collapsed ? "Show outline" : "Hide outline",
419
- "aria-expanded": !collapsed,
420
- onClick: () => setCollapsed((c) => !c),
421
- children: /* @__PURE__ */ jsx2(
422
- "svg",
423
- {
424
- viewBox: "0 0 20 20",
425
- width: "14",
426
- height: "14",
427
- fill: "none",
428
- stroke: "currentColor",
429
- strokeWidth: "1.8",
430
- strokeLinecap: "round",
431
- children: /* @__PURE__ */ jsx2("path", { d: "M4 5h12M4 10h8M4 15h10" })
432
- }
433
- )
434
- }
435
- ),
436
- !collapsed && /* @__PURE__ */ jsxs("nav", { className: "zui-text-editor-outline-list", "aria-label": "Table of contents", children: [
437
- entries.length === 0 && /* @__PURE__ */ jsx2("div", { className: "zui-text-editor-outline-empty", children: "No headings" }),
438
- entries.map(([key, text, tag]) => /* @__PURE__ */ jsx2(
439
- "button",
440
- {
441
- type: "button",
442
- className: `zui-text-editor-outline-item ${key === activeKey ? "is-active" : ""}`,
443
- style: {
444
- paddingLeft: `${0.5 + INDENT_PER_LEVEL[tag] * 0.75}rem`
445
- },
446
- onClick: () => scrollTo(key),
447
- children: text || "Untitled"
448
- },
449
- key
450
- ))
451
- ] })
452
- ]
453
- }
454
- );
455
- } });
456
- }
457
-
458
- // src/plugins/ToolbarPlugin.tsx
459
- import { useCallback as useCallback4, useEffect as useEffect8, useState as useState4 } from "react";
460
- import {
461
- $getSelection as $getSelection3,
462
- $isRangeSelection as $isRangeSelection3,
463
- FORMAT_TEXT_COMMAND
464
- } from "lexical";
465
- import { useLexicalComposerContext as useLexicalComposerContext8 } from "@lexical/react/LexicalComposerContext";
466
- import { INSERT_TABLE_COMMAND } from "@lexical/table";
467
- import { $insertNodeToNearestRoot } from "@lexical/utils";
468
-
469
- // src/nodes/DrawingNode.tsx
213
+ // src/nodes/FrontmatterNode.ts
470
214
  import {
471
215
  $applyNodeReplacement,
472
- DecoratorNode
216
+ $createLineBreakNode,
217
+ $createTextNode,
218
+ ElementNode
473
219
  } from "lexical";
474
-
475
- // src/components/DrawingCanvas.tsx
476
- import {
477
- useCallback as useCallback3,
478
- useEffect as useEffect7,
479
- useRef as useRef3,
480
- useState as useState3
220
+ var FrontmatterNode = class _FrontmatterNode extends ElementNode {
221
+ static getType() {
222
+ return "frontmatter";
223
+ }
224
+ static clone(node) {
225
+ return new _FrontmatterNode(node.__key);
226
+ }
227
+ constructor(key) {
228
+ super(key);
229
+ }
230
+ createDOM(config) {
231
+ const dom = document.createElement("div");
232
+ const className = config.theme.frontmatter;
233
+ dom.className = typeof className === "string" ? className : "zui-frontmatter";
234
+ return dom;
235
+ }
236
+ updateDOM() {
237
+ return false;
238
+ }
239
+ static importJSON(_serializedNode) {
240
+ return $createFrontmatterNode();
241
+ }
242
+ exportJSON() {
243
+ return {
244
+ ...super.exportJSON(),
245
+ type: "frontmatter",
246
+ version: 1
247
+ };
248
+ }
249
+ isInline() {
250
+ return false;
251
+ }
252
+ canBeEmpty() {
253
+ return false;
254
+ }
255
+ canIndent() {
256
+ return false;
257
+ }
258
+ };
259
+ function $createFrontmatterNode() {
260
+ return $applyNodeReplacement(new FrontmatterNode());
261
+ }
262
+ function $isFrontmatterNode(node) {
263
+ return node instanceof FrontmatterNode;
264
+ }
265
+ var FRONTMATTER_DELIMITER = /^---\s*$/;
266
+ var FRONTMATTER = {
267
+ dependencies: [FrontmatterNode],
268
+ export: (node) => {
269
+ if (!$isFrontmatterNode(node)) return null;
270
+ const body = node.getTextContent();
271
+ return body ? `---
272
+ ${body}
273
+ ---` : "---\n---";
274
+ },
275
+ regExpStart: FRONTMATTER_DELIMITER,
276
+ regExpEnd: FRONTMATTER_DELIMITER,
277
+ replace: (rootNode, children, _startMatch, _endMatch, linesInBetween, isImport) => {
278
+ if (!isImport || children != null || linesInBetween == null) return false;
279
+ if (rootNode.getChildrenSize() !== 0) return false;
280
+ const lines = [...linesInBetween];
281
+ while (lines.length > 0 && lines[0].length === 0) lines.shift();
282
+ while (lines.length > 0 && lines[lines.length - 1].length === 0) lines.pop();
283
+ const node = $createFrontmatterNode();
284
+ lines.forEach((line, index) => {
285
+ if (index > 0) node.append($createLineBreakNode());
286
+ node.append($createTextNode(line));
287
+ });
288
+ rootNode.append(node);
289
+ },
290
+ type: "multiline-element"
291
+ };
292
+
293
+ // src/nodes/DrawingNode.tsx
294
+ import {
295
+ $applyNodeReplacement as $applyNodeReplacement2,
296
+ DecoratorNode
297
+ } from "lexical";
298
+
299
+ // src/components/DrawingCanvas.tsx
300
+ import {
301
+ useCallback,
302
+ useEffect as useEffect5,
303
+ useRef as useRef2,
304
+ useState
481
305
  } from "react";
482
306
  import { $getNodeByKey as $getNodeByKey2 } from "lexical";
483
- import { useLexicalComposerContext as useLexicalComposerContext7 } from "@lexical/react/LexicalComposerContext";
307
+ import { useLexicalComposerContext as useLexicalComposerContext5 } from "@lexical/react/LexicalComposerContext";
484
308
  import { useLexicalEditable } from "@lexical/react/useLexicalEditable";
485
309
 
486
310
  // src/components/drawingTypes.ts
@@ -533,6 +357,9 @@ function parseDrawingData(json) {
533
357
  return {
534
358
  version: 1,
535
359
  height: typeof raw.height === "number" && raw.height >= 80 ? raw.height : DEFAULT_DRAWING_HEIGHT,
360
+ // `full` is the default and stays implicit so existing payloads are
361
+ // serialized byte-for-byte unchanged
362
+ width: raw.width === "content" ? "content" : void 0,
536
363
  shapes
537
364
  };
538
365
  } catch {
@@ -549,6 +376,27 @@ function isValidShape(value) {
549
376
  ));
550
377
  }
551
378
 
379
+ // src/components/blockWidthOptions.tsx
380
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
381
+ var BLOCK_WIDTH_OPTIONS = [
382
+ {
383
+ width: "full",
384
+ label: "Full width",
385
+ icon: /* @__PURE__ */ jsxs(Fragment, { children: [
386
+ /* @__PURE__ */ jsx("path", { d: "M3 4v12M17 4v12" }),
387
+ /* @__PURE__ */ jsx("path", { d: "M6 10h8M8.5 7.5L6 10l2.5 2.5M11.5 7.5L14 10l-2.5 2.5" })
388
+ ] })
389
+ },
390
+ {
391
+ width: "content",
392
+ label: "Content width",
393
+ icon: /* @__PURE__ */ jsxs(Fragment, { children: [
394
+ /* @__PURE__ */ jsx("path", { d: "M3 4v12M17 4v12" }),
395
+ /* @__PURE__ */ jsx("path", { d: "M5.5 10h3M11.5 10h3M6.5 7.5L9 10l-2.5 2.5M13.5 7.5L11 10l2.5 2.5" })
396
+ ] })
397
+ }
398
+ ];
399
+
552
400
  // src/components/drawingGeometry.ts
553
401
  var BINDING_GAP = 6;
554
402
  var BINDING_TOLERANCE = 8;
@@ -791,10 +639,12 @@ function resolveBindings(shapes) {
791
639
  }
792
640
 
793
641
  // src/components/DrawingCanvas.tsx
794
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
642
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
795
643
  import { createElement } from "react";
796
644
  var MIN_HEIGHT = 120;
797
645
  var MAX_HEIGHT = 900;
646
+ var MIN_CONTENT_WIDTH = 240;
647
+ var CONTENT_WIDTH_PADDING = 40;
798
648
  var SELECTION_COLOR = "#6965db";
799
649
  var CONNECTOR_FONT_SIZE = 12;
800
650
  var CONNECTOR_LABEL_MAX_WIDTH = 160;
@@ -838,7 +688,7 @@ function BoxTexts({
838
688
  opacity: 0.85
839
689
  },
840
690
  line
841
- )) : showHints && /* @__PURE__ */ jsx3("text", { ...hint, x: cx, y: b.y + SMALL_FONT_SIZE + 7, fontSize: SMALL_FONT_SIZE, children: "Label" })),
691
+ )) : showHints && /* @__PURE__ */ jsx2("text", { ...hint, x: cx, y: b.y + SMALL_FONT_SIZE + 7, fontSize: SMALL_FONT_SIZE, children: "Label" })),
842
692
  hideField !== "text" && (shape.text ? mainLines.map((line, i) => /* @__PURE__ */ createElement(
843
693
  "text",
844
694
  {
@@ -849,7 +699,7 @@ function BoxTexts({
849
699
  fontSize: FONT_SIZE
850
700
  },
851
701
  line
852
- )) : showHints && /* @__PURE__ */ jsx3("text", { ...hint, x: cx, y: mainStart, fontSize: FONT_SIZE, children: "Text" })),
702
+ )) : showHints && /* @__PURE__ */ jsx2("text", { ...hint, x: cx, y: mainStart, fontSize: FONT_SIZE, children: "Text" })),
853
703
  hideField !== "footer" && (shape.footer ? footerLines.map((line, i) => /* @__PURE__ */ createElement(
854
704
  "text",
855
705
  {
@@ -861,7 +711,7 @@ function BoxTexts({
861
711
  opacity: 0.65
862
712
  },
863
713
  line
864
- )) : showHints && /* @__PURE__ */ jsx3("text", { ...hint, x: cx, y: b.y + b.h - 9, fontSize: SMALL_FONT_SIZE, children: "Footer" }))
714
+ )) : showHints && /* @__PURE__ */ jsx2("text", { ...hint, x: cx, y: b.y + b.h - 9, fontSize: SMALL_FONT_SIZE, children: "Footer" }))
865
715
  ] });
866
716
  }
867
717
  function ShapeView({
@@ -879,7 +729,7 @@ function ShapeView({
879
729
  case "rect": {
880
730
  const b = bbox(shape);
881
731
  return /* @__PURE__ */ jsxs2("g", { children: [
882
- /* @__PURE__ */ jsx3(
732
+ /* @__PURE__ */ jsx2(
883
733
  "rect",
884
734
  {
885
735
  ...common,
@@ -891,13 +741,13 @@ function ShapeView({
891
741
  fill: shape.fill
892
742
  }
893
743
  ),
894
- /* @__PURE__ */ jsx3(BoxTexts, { shape, hideField, showHints })
744
+ /* @__PURE__ */ jsx2(BoxTexts, { shape, hideField, showHints })
895
745
  ] });
896
746
  }
897
747
  case "ellipse": {
898
748
  const b = bbox(shape);
899
749
  return /* @__PURE__ */ jsxs2("g", { children: [
900
- /* @__PURE__ */ jsx3(
750
+ /* @__PURE__ */ jsx2(
901
751
  "ellipse",
902
752
  {
903
753
  ...common,
@@ -908,13 +758,13 @@ function ShapeView({
908
758
  fill: shape.fill
909
759
  }
910
760
  ),
911
- /* @__PURE__ */ jsx3(BoxTexts, { shape, hideField, showHints })
761
+ /* @__PURE__ */ jsx2(BoxTexts, { shape, hideField, showHints })
912
762
  ] });
913
763
  }
914
764
  case "triangle":
915
765
  case "pentagon":
916
766
  return /* @__PURE__ */ jsxs2("g", { children: [
917
- /* @__PURE__ */ jsx3(
767
+ /* @__PURE__ */ jsx2(
918
768
  "polygon",
919
769
  {
920
770
  ...common,
@@ -922,12 +772,12 @@ function ShapeView({
922
772
  fill: shape.fill
923
773
  }
924
774
  ),
925
- /* @__PURE__ */ jsx3(BoxTexts, { shape, hideField, showHints })
775
+ /* @__PURE__ */ jsx2(BoxTexts, { shape, hideField, showHints })
926
776
  ] });
927
777
  case "line":
928
778
  case "arrow":
929
779
  return /* @__PURE__ */ jsxs2("g", { children: [
930
- /* @__PURE__ */ jsx3(
780
+ /* @__PURE__ */ jsx2(
931
781
  "path",
932
782
  {
933
783
  ...common,
@@ -935,10 +785,10 @@ function ShapeView({
935
785
  fill: "none"
936
786
  }
937
787
  ),
938
- shape.text && hideField !== "text" && /* @__PURE__ */ jsx3(ConnectorLabel, { shape })
788
+ shape.text && hideField !== "text" && /* @__PURE__ */ jsx2(ConnectorLabel, { shape })
939
789
  ] });
940
790
  case "text":
941
- return /* @__PURE__ */ jsx3(
791
+ return /* @__PURE__ */ jsx2(
942
792
  "text",
943
793
  {
944
794
  x: shape.x,
@@ -947,7 +797,7 @@ function ShapeView({
947
797
  fontSize: FONT_SIZE,
948
798
  fontFamily: "ui-sans-serif, system-ui, sans-serif",
949
799
  style: { userSelect: "none", whiteSpace: "pre" },
950
- children: (shape.text ?? "").split("\n").map((line, i) => /* @__PURE__ */ jsx3("tspan", { x: shape.x, dy: i === 0 ? 0 : FONT_SIZE * 1.35, children: line }, i))
800
+ children: (shape.text ?? "").split("\n").map((line, i) => /* @__PURE__ */ jsx2("tspan", { x: shape.x, dy: i === 0 ? 0 : FONT_SIZE * 1.35, children: line }, i))
951
801
  }
952
802
  );
953
803
  }
@@ -960,7 +810,7 @@ function ConnectorLabel({ shape }) {
960
810
  const lineH = CONNECTOR_FONT_SIZE * LINE_HEIGHT;
961
811
  const startY = midY - (lines.length - 1) * lineH / 2 + CONNECTOR_FONT_SIZE * 0.35;
962
812
  return /* @__PURE__ */ jsxs2("g", { children: [
963
- /* @__PURE__ */ jsx3(
813
+ /* @__PURE__ */ jsx2(
964
814
  "rect",
965
815
  {
966
816
  x: midX - size.w / 2 - 5,
@@ -972,7 +822,7 @@ function ConnectorLabel({ shape }) {
972
822
  opacity: 0.92
973
823
  }
974
824
  ),
975
- lines.map((line, i) => /* @__PURE__ */ jsx3(
825
+ lines.map((line, i) => /* @__PURE__ */ jsx2(
976
826
  "text",
977
827
  {
978
828
  x: midX,
@@ -989,20 +839,20 @@ function ConnectorLabel({ shape }) {
989
839
  ] });
990
840
  }
991
841
  var TOOL_ICONS = {
992
- select: /* @__PURE__ */ jsx3("path", { d: "M4 2l12 6.5-5.2 1.6L8 16z", fill: "currentColor", stroke: "none" }),
993
- rect: /* @__PURE__ */ jsx3("rect", { x: "3", y: "4.5", width: "14", height: "11", rx: "2" }),
994
- ellipse: /* @__PURE__ */ jsx3("ellipse", { cx: "10", cy: "10", rx: "7", ry: "5.5" }),
995
- triangle: /* @__PURE__ */ jsx3("path", { d: "M10 3.5L17 16.5H3z", strokeLinejoin: "round" }),
996
- pentagon: /* @__PURE__ */ jsx3("path", { d: "M10 3L16.8 8.1L14.2 16.2H5.8L3.2 8.1z", strokeLinejoin: "round" }),
842
+ select: /* @__PURE__ */ jsx2("path", { d: "M4 2l12 6.5-5.2 1.6L8 16z", fill: "currentColor", stroke: "none" }),
843
+ rect: /* @__PURE__ */ jsx2("rect", { x: "3", y: "4.5", width: "14", height: "11", rx: "2" }),
844
+ ellipse: /* @__PURE__ */ jsx2("ellipse", { cx: "10", cy: "10", rx: "7", ry: "5.5" }),
845
+ triangle: /* @__PURE__ */ jsx2("path", { d: "M10 3.5L17 16.5H3z", strokeLinejoin: "round" }),
846
+ pentagon: /* @__PURE__ */ jsx2("path", { d: "M10 3L16.8 8.1L14.2 16.2H5.8L3.2 8.1z", strokeLinejoin: "round" }),
997
847
  arrow: /* @__PURE__ */ jsxs2("g", { children: [
998
- /* @__PURE__ */ jsx3("path", { d: "M4 16L16 4" }),
999
- /* @__PURE__ */ jsx3("path", { d: "M9 4h7v7" })
848
+ /* @__PURE__ */ jsx2("path", { d: "M4 16L16 4" }),
849
+ /* @__PURE__ */ jsx2("path", { d: "M9 4h7v7" })
1000
850
  ] }),
1001
- line: /* @__PURE__ */ jsx3("path", { d: "M4 16L16 4" }),
851
+ line: /* @__PURE__ */ jsx2("path", { d: "M4 16L16 4" }),
1002
852
  text: /* @__PURE__ */ jsxs2("g", { children: [
1003
- /* @__PURE__ */ jsx3("path", { d: "M4 5V3.5h12V5" }),
1004
- /* @__PURE__ */ jsx3("path", { d: "M10 3.5V16.5" }),
1005
- /* @__PURE__ */ jsx3("path", { d: "M7 16.5h6" })
853
+ /* @__PURE__ */ jsx2("path", { d: "M4 5V3.5h12V5" }),
854
+ /* @__PURE__ */ jsx2("path", { d: "M10 3.5V16.5" }),
855
+ /* @__PURE__ */ jsx2("path", { d: "M7 16.5h6" })
1006
856
  ] })
1007
857
  };
1008
858
  function bindEndpoints(shape, shapes) {
@@ -1025,38 +875,55 @@ var TOOLS = [
1025
875
  { tool: "line", label: "Line" },
1026
876
  { tool: "text", label: "Text" }
1027
877
  ];
878
+ function contentWidth(shapes) {
879
+ let right = 0;
880
+ for (const shape of shapes) {
881
+ if (isConnectorType(shape.type)) {
882
+ for (const p of connectorPoints(shape)) right = Math.max(right, p.x);
883
+ } else {
884
+ const b = bbox(shape);
885
+ right = Math.max(right, b.x + b.w);
886
+ }
887
+ }
888
+ return Math.max(MIN_CONTENT_WIDTH, Math.ceil(right + CONTENT_WIDTH_PADDING));
889
+ }
1028
890
  function DrawingCanvas({ nodeKey, data }) {
1029
- const [editor] = useLexicalComposerContext7();
891
+ const [editor] = useLexicalComposerContext5();
1030
892
  const isEditable = useLexicalEditable();
1031
- const [shapes, setShapes] = useState3(data.shapes);
1032
- const [height, setHeight] = useState3(data.height);
1033
- const [tool, setTool] = useState3("select");
1034
- const [selectedId, setSelectedId] = useState3(null);
1035
- const [editingText, setEditingText] = useState3(null);
1036
- const [stroke, setStroke] = useState3(STROKE_COLORS[0]);
1037
- const [fill, setFill] = useState3(FILL_COLORS[0]);
1038
- const svgRef = useRef3(null);
1039
- const dragRef = useRef3(null);
1040
- const lastCommittedRef = useRef3(serializeDrawingData(data));
1041
- const shapesRef = useRef3(shapes);
893
+ const [shapes, setShapes] = useState(data.shapes);
894
+ const [height, setHeight] = useState(data.height);
895
+ const [width, setWidth] = useState(data.width ?? "full");
896
+ const [tool, setTool] = useState("select");
897
+ const [selectedId, setSelectedId] = useState(null);
898
+ const [editingText, setEditingText] = useState(null);
899
+ const [stroke, setStroke] = useState(STROKE_COLORS[0]);
900
+ const [fill, setFill] = useState(FILL_COLORS[0]);
901
+ const svgRef = useRef2(null);
902
+ const dragRef = useRef2(null);
903
+ const lastCommittedRef = useRef2(serializeDrawingData(data));
904
+ const shapesRef = useRef2(shapes);
1042
905
  shapesRef.current = shapes;
1043
- const heightRef = useRef3(height);
906
+ const heightRef = useRef2(height);
1044
907
  heightRef.current = height;
1045
- useEffect7(() => {
908
+ const widthRef = useRef2(width);
909
+ widthRef.current = width;
910
+ useEffect5(() => {
1046
911
  const incoming = serializeDrawingData(data);
1047
912
  if (incoming !== lastCommittedRef.current) {
1048
913
  lastCommittedRef.current = incoming;
1049
914
  setShapes(data.shapes);
1050
915
  setHeight(data.height);
916
+ setWidth(data.width ?? "full");
1051
917
  setSelectedId(null);
1052
918
  setEditingText(null);
1053
919
  }
1054
920
  }, [data]);
1055
- const commit = useCallback3(
1056
- (nextShapes, nextHeight) => {
921
+ const commit = useCallback(
922
+ (nextShapes, nextHeight, nextWidth) => {
1057
923
  const payload = {
1058
924
  version: 1,
1059
925
  height: nextHeight ?? heightRef.current,
926
+ width: (nextWidth ?? widthRef.current) === "content" ? "content" : void 0,
1060
927
  shapes: nextShapes
1061
928
  };
1062
929
  const json = serializeDrawingData(payload);
@@ -1071,7 +938,14 @@ function DrawingCanvas({ nodeKey, data }) {
1071
938
  },
1072
939
  [editor, nodeKey]
1073
940
  );
1074
- const updateShapes = useCallback3(
941
+ const applyWidth = useCallback(
942
+ (next) => {
943
+ setWidth(next);
944
+ commit(shapesRef.current, void 0, next);
945
+ },
946
+ [commit]
947
+ );
948
+ const updateShapes = useCallback(
1075
949
  (updater, options) => {
1076
950
  setShapes((prev) => {
1077
951
  const next = resolveBindings(updater(prev));
@@ -1081,17 +955,17 @@ function DrawingCanvas({ nodeKey, data }) {
1081
955
  },
1082
956
  [commit]
1083
957
  );
1084
- const getPoint = useCallback3((e) => {
958
+ const getPoint = useCallback((e) => {
1085
959
  const rect = svgRef.current?.getBoundingClientRect();
1086
960
  if (!rect) return { x: 0, y: 0 };
1087
961
  return { x: e.clientX - rect.left, y: e.clientY - rect.top };
1088
962
  }, []);
1089
963
  const selectedShape = shapes.find((s) => s.id === selectedId) ?? null;
1090
- const startTextEditing = useCallback3((id, field) => {
964
+ const startTextEditing = useCallback((id, field) => {
1091
965
  setEditingText({ id, field });
1092
966
  setSelectedId(id);
1093
967
  }, []);
1094
- const handleBackgroundPointerDown = useCallback3(
968
+ const handleBackgroundPointerDown = useCallback(
1095
969
  (e) => {
1096
970
  if (!isEditable || editingText) return;
1097
971
  if (e.button !== 0) return;
@@ -1151,7 +1025,7 @@ function DrawingCanvas({ nodeKey, data }) {
1151
1025
  startTextEditing
1152
1026
  ]
1153
1027
  );
1154
- const handleShapePointerDown = useCallback3(
1028
+ const handleShapePointerDown = useCallback(
1155
1029
  (e, shape) => {
1156
1030
  if (!isEditable || tool !== "select" || editingText) return;
1157
1031
  if (e.button !== 0) return;
@@ -1170,7 +1044,7 @@ function DrawingCanvas({ nodeKey, data }) {
1170
1044
  },
1171
1045
  [isEditable, tool, editingText, getPoint, selectedId]
1172
1046
  );
1173
- const handleHandlePointerDown = useCallback3(
1047
+ const handleHandlePointerDown = useCallback(
1174
1048
  (e, drag) => {
1175
1049
  if (!isEditable) return;
1176
1050
  if (e.button !== 0) return;
@@ -1186,7 +1060,7 @@ function DrawingCanvas({ nodeKey, data }) {
1186
1060
  },
1187
1061
  [isEditable, updateShapes]
1188
1062
  );
1189
- const handlePointerMove = useCallback3(
1063
+ const handlePointerMove = useCallback(
1190
1064
  (e) => {
1191
1065
  const drag = dragRef.current;
1192
1066
  if (!drag) return;
@@ -1277,7 +1151,7 @@ function DrawingCanvas({ nodeKey, data }) {
1277
1151
  },
1278
1152
  [getPoint, updateShapes]
1279
1153
  );
1280
- const handlePointerUp = useCallback3(() => {
1154
+ const handlePointerUp = useCallback(() => {
1281
1155
  const drag = dragRef.current;
1282
1156
  dragRef.current = null;
1283
1157
  if (!drag) return;
@@ -1330,7 +1204,7 @@ function DrawingCanvas({ nodeKey, data }) {
1330
1204
  }
1331
1205
  updateShapes((prev) => prev.map(normalize), { commit: true });
1332
1206
  }, [commit, updateShapes, startTextEditing]);
1333
- const deleteSelected = useCallback3(() => {
1207
+ const deleteSelected = useCallback(() => {
1334
1208
  if (!selectedId) return;
1335
1209
  setEditingText(null);
1336
1210
  setSelectedId(null);
@@ -1338,7 +1212,7 @@ function DrawingCanvas({ nodeKey, data }) {
1338
1212
  commit: true
1339
1213
  });
1340
1214
  }, [selectedId, updateShapes]);
1341
- const handleKeyDown = useCallback3(
1215
+ const handleKeyDown = useCallback(
1342
1216
  (e) => {
1343
1217
  if (!isEditable || editingText) return;
1344
1218
  if ((e.key === "Delete" || e.key === "Backspace") && selectedId) {
@@ -1366,7 +1240,7 @@ function DrawingCanvas({ nodeKey, data }) {
1366
1240
  startTextEditing
1367
1241
  ]
1368
1242
  );
1369
- const applyStroke = useCallback3(
1243
+ const applyStroke = useCallback(
1370
1244
  (color) => {
1371
1245
  setStroke(color);
1372
1246
  if (selectedId) {
@@ -1378,7 +1252,7 @@ function DrawingCanvas({ nodeKey, data }) {
1378
1252
  },
1379
1253
  [selectedId, updateShapes]
1380
1254
  );
1381
- const applyFill = useCallback3(
1255
+ const applyFill = useCallback(
1382
1256
  (color) => {
1383
1257
  setFill(color);
1384
1258
  if (selectedId) {
@@ -1390,7 +1264,7 @@ function DrawingCanvas({ nodeKey, data }) {
1390
1264
  },
1391
1265
  [selectedId, updateShapes]
1392
1266
  );
1393
- const applyRouting = useCallback3(
1267
+ const applyRouting = useCallback(
1394
1268
  (elbow) => {
1395
1269
  if (!selectedId) return;
1396
1270
  updateShapes(
@@ -1407,7 +1281,7 @@ function DrawingCanvas({ nodeKey, data }) {
1407
1281
  },
1408
1282
  [selectedId, updateShapes]
1409
1283
  );
1410
- const addWaypoint = useCallback3(
1284
+ const addWaypoint = useCallback(
1411
1285
  (e, shapeId, segmentIndex, point) => {
1412
1286
  if (!isEditable) return;
1413
1287
  if (e.button !== 0) return;
@@ -1429,7 +1303,7 @@ function DrawingCanvas({ nodeKey, data }) {
1429
1303
  },
1430
1304
  [isEditable, updateShapes]
1431
1305
  );
1432
- const removeWaypoint = useCallback3(
1306
+ const removeWaypoint = useCallback(
1433
1307
  (shapeId, index) => {
1434
1308
  updateShapes(
1435
1309
  (prev) => prev.map((s) => {
@@ -1442,7 +1316,7 @@ function DrawingCanvas({ nodeKey, data }) {
1442
1316
  },
1443
1317
  [updateShapes]
1444
1318
  );
1445
- const applyDirection = useCallback3(
1319
+ const applyDirection = useCallback(
1446
1320
  (bidirectional) => {
1447
1321
  if (!selectedId) return;
1448
1322
  updateShapes(
@@ -1454,7 +1328,7 @@ function DrawingCanvas({ nodeKey, data }) {
1454
1328
  },
1455
1329
  [selectedId, updateShapes]
1456
1330
  );
1457
- const commitText = useCallback3(
1331
+ const commitText = useCallback(
1458
1332
  (id, field, value) => {
1459
1333
  setEditingText(null);
1460
1334
  updateShapes(
@@ -1478,12 +1352,12 @@ function DrawingCanvas({ nodeKey, data }) {
1478
1352
  return /* @__PURE__ */ jsxs2(
1479
1353
  "div",
1480
1354
  {
1481
- className: "zui-drawing-canvas",
1355
+ className: `zui-drawing-canvas ${width === "content" ? "is-content-width" : ""}`,
1482
1356
  tabIndex: isEditable ? 0 : void 0,
1483
1357
  onKeyDown: handleKeyDown,
1484
1358
  children: [
1485
1359
  isEditable && /* @__PURE__ */ jsxs2("div", { className: "zui-drawing-toolbar", onPointerDown: (e) => e.stopPropagation(), children: [
1486
- /* @__PURE__ */ jsx3("div", { className: "zui-drawing-toolbar-group", children: TOOLS.map(({ tool: t, label }) => /* @__PURE__ */ jsx3(
1360
+ /* @__PURE__ */ jsx2("div", { className: "zui-drawing-toolbar-group", children: TOOLS.map(({ tool: t, label }) => /* @__PURE__ */ jsx2(
1487
1361
  "button",
1488
1362
  {
1489
1363
  type: "button",
@@ -1494,7 +1368,7 @@ function DrawingCanvas({ nodeKey, data }) {
1494
1368
  setTool(t);
1495
1369
  if (t !== "select") setSelectedId(null);
1496
1370
  },
1497
- children: /* @__PURE__ */ jsx3(
1371
+ children: /* @__PURE__ */ jsx2(
1498
1372
  "svg",
1499
1373
  {
1500
1374
  viewBox: "0 0 20 20",
@@ -1512,8 +1386,8 @@ function DrawingCanvas({ nodeKey, data }) {
1512
1386
  t
1513
1387
  )) }),
1514
1388
  /* @__PURE__ */ jsxs2("div", { className: "zui-drawing-toolbar-group", children: [
1515
- /* @__PURE__ */ jsx3("span", { className: "zui-drawing-swatch-label", children: "Stroke" }),
1516
- STROKE_COLORS.map((color) => /* @__PURE__ */ jsx3(
1389
+ /* @__PURE__ */ jsx2("span", { className: "zui-drawing-swatch-label", children: "Stroke" }),
1390
+ STROKE_COLORS.map((color) => /* @__PURE__ */ jsx2(
1517
1391
  "button",
1518
1392
  {
1519
1393
  type: "button",
@@ -1527,8 +1401,8 @@ function DrawingCanvas({ nodeKey, data }) {
1527
1401
  ))
1528
1402
  ] }),
1529
1403
  showFill && /* @__PURE__ */ jsxs2("div", { className: "zui-drawing-toolbar-group", children: [
1530
- /* @__PURE__ */ jsx3("span", { className: "zui-drawing-swatch-label", children: "Fill" }),
1531
- FILL_COLORS.map((color) => /* @__PURE__ */ jsx3(
1404
+ /* @__PURE__ */ jsx2("span", { className: "zui-drawing-swatch-label", children: "Fill" }),
1405
+ FILL_COLORS.map((color) => /* @__PURE__ */ jsx2(
1532
1406
  "button",
1533
1407
  {
1534
1408
  type: "button",
@@ -1542,7 +1416,7 @@ function DrawingCanvas({ nodeKey, data }) {
1542
1416
  ))
1543
1417
  ] }),
1544
1418
  selectedShape && isConnectorType(selectedShape.type) && /* @__PURE__ */ jsxs2("div", { className: "zui-drawing-toolbar-group", children: [
1545
- /* @__PURE__ */ jsx3(
1419
+ /* @__PURE__ */ jsx2(
1546
1420
  "button",
1547
1421
  {
1548
1422
  type: "button",
@@ -1550,7 +1424,7 @@ function DrawingCanvas({ nodeKey, data }) {
1550
1424
  "aria-label": "Straight connector",
1551
1425
  className: `zui-drawing-tool ${selectedShape.routing !== "elbow" ? "is-active" : ""}`,
1552
1426
  onClick: () => applyRouting(false),
1553
- children: /* @__PURE__ */ jsx3(
1427
+ children: /* @__PURE__ */ jsx2(
1554
1428
  "svg",
1555
1429
  {
1556
1430
  viewBox: "0 0 20 20",
@@ -1560,12 +1434,12 @@ function DrawingCanvas({ nodeKey, data }) {
1560
1434
  stroke: "currentColor",
1561
1435
  strokeWidth: "1.6",
1562
1436
  strokeLinecap: "round",
1563
- children: /* @__PURE__ */ jsx3("path", { d: "M4 16L16 4" })
1437
+ children: /* @__PURE__ */ jsx2("path", { d: "M4 16L16 4" })
1564
1438
  }
1565
1439
  )
1566
1440
  }
1567
1441
  ),
1568
- /* @__PURE__ */ jsx3(
1442
+ /* @__PURE__ */ jsx2(
1569
1443
  "button",
1570
1444
  {
1571
1445
  type: "button",
@@ -1573,7 +1447,7 @@ function DrawingCanvas({ nodeKey, data }) {
1573
1447
  "aria-label": "Elbow connector",
1574
1448
  className: `zui-drawing-tool ${selectedShape.routing === "elbow" ? "is-active" : ""}`,
1575
1449
  onClick: () => applyRouting(true),
1576
- children: /* @__PURE__ */ jsx3(
1450
+ children: /* @__PURE__ */ jsx2(
1577
1451
  "svg",
1578
1452
  {
1579
1453
  viewBox: "0 0 20 20",
@@ -1584,14 +1458,14 @@ function DrawingCanvas({ nodeKey, data }) {
1584
1458
  strokeWidth: "1.6",
1585
1459
  strokeLinecap: "round",
1586
1460
  strokeLinejoin: "round",
1587
- children: /* @__PURE__ */ jsx3("path", { d: "M4 16v-6h12V4" })
1461
+ children: /* @__PURE__ */ jsx2("path", { d: "M4 16v-6h12V4" })
1588
1462
  }
1589
1463
  )
1590
1464
  }
1591
1465
  )
1592
1466
  ] }),
1593
1467
  selectedShape?.type === "arrow" && /* @__PURE__ */ jsxs2("div", { className: "zui-drawing-toolbar-group", children: [
1594
- /* @__PURE__ */ jsx3(
1468
+ /* @__PURE__ */ jsx2(
1595
1469
  "button",
1596
1470
  {
1597
1471
  type: "button",
@@ -1599,7 +1473,7 @@ function DrawingCanvas({ nodeKey, data }) {
1599
1473
  "aria-label": "One-way arrow",
1600
1474
  className: `zui-drawing-tool ${!selectedShape.bidirectional ? "is-active" : ""}`,
1601
1475
  onClick: () => applyDirection(false),
1602
- children: /* @__PURE__ */ jsx3(
1476
+ children: /* @__PURE__ */ jsx2(
1603
1477
  "svg",
1604
1478
  {
1605
1479
  viewBox: "0 0 20 20",
@@ -1610,12 +1484,12 @@ function DrawingCanvas({ nodeKey, data }) {
1610
1484
  strokeWidth: "1.6",
1611
1485
  strokeLinecap: "round",
1612
1486
  strokeLinejoin: "round",
1613
- children: /* @__PURE__ */ jsx3("path", { d: "M3 10h13M12 5.5L16.5 10L12 14.5" })
1487
+ children: /* @__PURE__ */ jsx2("path", { d: "M3 10h13M12 5.5L16.5 10L12 14.5" })
1614
1488
  }
1615
1489
  )
1616
1490
  }
1617
1491
  ),
1618
- /* @__PURE__ */ jsx3(
1492
+ /* @__PURE__ */ jsx2(
1619
1493
  "button",
1620
1494
  {
1621
1495
  type: "button",
@@ -1623,7 +1497,7 @@ function DrawingCanvas({ nodeKey, data }) {
1623
1497
  "aria-label": "Two-way arrow",
1624
1498
  className: `zui-drawing-tool ${selectedShape.bidirectional ? "is-active" : ""}`,
1625
1499
  onClick: () => applyDirection(true),
1626
- children: /* @__PURE__ */ jsx3(
1500
+ children: /* @__PURE__ */ jsx2(
1627
1501
  "svg",
1628
1502
  {
1629
1503
  viewBox: "0 0 20 20",
@@ -1634,13 +1508,13 @@ function DrawingCanvas({ nodeKey, data }) {
1634
1508
  strokeWidth: "1.6",
1635
1509
  strokeLinecap: "round",
1636
1510
  strokeLinejoin: "round",
1637
- children: /* @__PURE__ */ jsx3("path", { d: "M3.5 10h13M8 5.5L3.5 10L8 14.5M12 5.5L16.5 10L12 14.5" })
1511
+ children: /* @__PURE__ */ jsx2("path", { d: "M3.5 10h13M8 5.5L3.5 10L8 14.5M12 5.5L16.5 10L12 14.5" })
1638
1512
  }
1639
1513
  )
1640
1514
  }
1641
1515
  )
1642
1516
  ] }),
1643
- selectedShape && /* @__PURE__ */ jsx3("div", { className: "zui-drawing-toolbar-group", children: /* @__PURE__ */ jsx3(
1517
+ selectedShape && /* @__PURE__ */ jsx2("div", { className: "zui-drawing-toolbar-group", children: /* @__PURE__ */ jsx2(
1644
1518
  "button",
1645
1519
  {
1646
1520
  type: "button",
@@ -1648,7 +1522,7 @@ function DrawingCanvas({ nodeKey, data }) {
1648
1522
  "aria-label": "Delete shape",
1649
1523
  className: "zui-drawing-tool zui-drawing-tool-danger",
1650
1524
  onClick: deleteSelected,
1651
- children: /* @__PURE__ */ jsx3(
1525
+ children: /* @__PURE__ */ jsx2(
1652
1526
  "svg",
1653
1527
  {
1654
1528
  viewBox: "0 0 20 20",
@@ -1658,18 +1532,48 @@ function DrawingCanvas({ nodeKey, data }) {
1658
1532
  stroke: "currentColor",
1659
1533
  strokeWidth: "1.6",
1660
1534
  strokeLinecap: "round",
1661
- children: /* @__PURE__ */ jsx3("path", { d: "M4 6h12M8 6V4h4v2M6 6l1 10h6l1-10M8.5 9v4M11.5 9v4" })
1535
+ children: /* @__PURE__ */ jsx2("path", { d: "M4 6h12M8 6V4h4v2M6 6l1 10h6l1-10M8.5 9v4M11.5 9v4" })
1662
1536
  }
1663
1537
  )
1664
1538
  }
1665
- ) })
1539
+ ) }),
1540
+ /* @__PURE__ */ jsx2("div", { className: "zui-drawing-toolbar-group zui-drawing-toolbar-group-end", children: BLOCK_WIDTH_OPTIONS.map(({ width: option, label, icon }) => /* @__PURE__ */ jsx2(
1541
+ "button",
1542
+ {
1543
+ type: "button",
1544
+ title: label,
1545
+ "aria-label": label,
1546
+ "aria-pressed": width === option,
1547
+ className: `zui-drawing-tool ${width === option ? "is-active" : ""}`,
1548
+ onClick: () => applyWidth(option),
1549
+ children: /* @__PURE__ */ jsx2(
1550
+ "svg",
1551
+ {
1552
+ viewBox: "0 0 20 20",
1553
+ width: "16",
1554
+ height: "16",
1555
+ fill: "none",
1556
+ stroke: "currentColor",
1557
+ strokeWidth: "1.6",
1558
+ strokeLinecap: "round",
1559
+ strokeLinejoin: "round",
1560
+ children: icon
1561
+ }
1562
+ )
1563
+ },
1564
+ option
1565
+ )) })
1666
1566
  ] }),
1667
1567
  /* @__PURE__ */ jsxs2(
1668
1568
  "svg",
1669
1569
  {
1670
1570
  ref: svgRef,
1671
1571
  className: "zui-drawing-surface",
1672
- style: { height, cursor: canvasCursor },
1572
+ style: {
1573
+ height,
1574
+ width: width === "content" ? contentWidth(shapes) : void 0,
1575
+ cursor: canvasCursor
1576
+ },
1673
1577
  onPointerDown: handleBackgroundPointerDown,
1674
1578
  onPointerMove: handlePointerMove,
1675
1579
  onPointerUp: handlePointerUp,
@@ -1700,7 +1604,7 @@ function DrawingCanvas({ nodeKey, data }) {
1700
1604
  },
1701
1605
  onPointerDown: (e) => handleShapePointerDown(e, shape),
1702
1606
  children: [
1703
- isConnectorType(shape.type) && /* @__PURE__ */ jsx3(
1607
+ isConnectorType(shape.type) && /* @__PURE__ */ jsx2(
1704
1608
  "path",
1705
1609
  {
1706
1610
  d: connectorPath(shape),
@@ -1709,8 +1613,8 @@ function DrawingCanvas({ nodeKey, data }) {
1709
1613
  strokeWidth: 14
1710
1614
  }
1711
1615
  ),
1712
- (isBoxType(shape.type) || shape.type === "text") && /* @__PURE__ */ jsx3(HitArea, { shape }),
1713
- shape.id === editingText?.id && shape.type === "text" ? null : /* @__PURE__ */ jsx3(
1616
+ (isBoxType(shape.type) || shape.type === "text") && /* @__PURE__ */ jsx2(HitArea, { shape }),
1617
+ shape.id === editingText?.id && shape.type === "text" ? null : /* @__PURE__ */ jsx2(
1714
1618
  ShapeView,
1715
1619
  {
1716
1620
  shape,
@@ -1722,7 +1626,7 @@ function DrawingCanvas({ nodeKey, data }) {
1722
1626
  },
1723
1627
  shape.id
1724
1628
  )),
1725
- selectedShape && isEditable && !editingText && /* @__PURE__ */ jsx3(
1629
+ selectedShape && isEditable && !editingText && /* @__PURE__ */ jsx2(
1726
1630
  SelectionOverlay,
1727
1631
  {
1728
1632
  shape: selectedShape,
@@ -1734,7 +1638,7 @@ function DrawingCanvas({ nodeKey, data }) {
1734
1638
  ]
1735
1639
  }
1736
1640
  ),
1737
- editingShape && editingText && /* @__PURE__ */ jsx3(
1641
+ editingShape && editingText && /* @__PURE__ */ jsx2(
1738
1642
  TextEditOverlay,
1739
1643
  {
1740
1644
  shape: editingShape,
@@ -1743,7 +1647,7 @@ function DrawingCanvas({ nodeKey, data }) {
1743
1647
  },
1744
1648
  `${editingShape.id}:${editingText.field}`
1745
1649
  ),
1746
- isEditable && /* @__PURE__ */ jsx3(
1650
+ isEditable && /* @__PURE__ */ jsx2(
1747
1651
  "div",
1748
1652
  {
1749
1653
  className: "zui-drawing-resize",
@@ -1782,7 +1686,7 @@ function DrawingCanvas({ nodeKey, data }) {
1782
1686
  }
1783
1687
  function HitArea({ shape }) {
1784
1688
  const b = bbox(shape);
1785
- return /* @__PURE__ */ jsx3(
1689
+ return /* @__PURE__ */ jsx2(
1786
1690
  "rect",
1787
1691
  {
1788
1692
  x: b.x,
@@ -1817,7 +1721,7 @@ function SelectionOverlay({
1817
1721
  return [{ index: i, x: (p.x + q.x) / 2, y: (p.y + q.y) / 2 }];
1818
1722
  });
1819
1723
  return /* @__PURE__ */ jsxs2("g", { children: [
1820
- ghosts.map(({ index, x, y }) => /* @__PURE__ */ jsx3(
1724
+ ghosts.map(({ index, x, y }) => /* @__PURE__ */ jsx2(
1821
1725
  "circle",
1822
1726
  {
1823
1727
  cx: x,
@@ -1830,11 +1734,11 @@ function SelectionOverlay({
1830
1734
  opacity: 0.6,
1831
1735
  style: { cursor: "copy" },
1832
1736
  onPointerDown: (e) => onWaypointAdd(e, shape.id, index, { x, y }),
1833
- children: /* @__PURE__ */ jsx3("title", { children: "Drag to add a point" })
1737
+ children: /* @__PURE__ */ jsx2("title", { children: "Drag to add a point" })
1834
1738
  },
1835
1739
  `ghost-${index}`
1836
1740
  )),
1837
- waypoints.map((p, i) => /* @__PURE__ */ jsx3(
1741
+ waypoints.map((p, i) => /* @__PURE__ */ jsx2(
1838
1742
  "rect",
1839
1743
  {
1840
1744
  x: p.x - 4,
@@ -1855,11 +1759,11 @@ function SelectionOverlay({
1855
1759
  e.stopPropagation();
1856
1760
  onWaypointRemove(shape.id, i);
1857
1761
  },
1858
- children: /* @__PURE__ */ jsx3("title", { children: "Drag to move, double-click to remove" })
1762
+ children: /* @__PURE__ */ jsx2("title", { children: "Drag to move, double-click to remove" })
1859
1763
  },
1860
1764
  `wp-${i}`
1861
1765
  )),
1862
- ends.map(({ end, x, y }) => /* @__PURE__ */ jsx3(
1766
+ ends.map(({ end, x, y }) => /* @__PURE__ */ jsx2(
1863
1767
  "circle",
1864
1768
  {
1865
1769
  cx: x,
@@ -1878,7 +1782,7 @@ function SelectionOverlay({
1878
1782
  },
1879
1783
  end
1880
1784
  )),
1881
- elbowMid && /* @__PURE__ */ jsx3(
1785
+ elbowMid && /* @__PURE__ */ jsx2(
1882
1786
  "rect",
1883
1787
  {
1884
1788
  x: elbowMid.x - 4,
@@ -1909,7 +1813,7 @@ function SelectionOverlay({
1909
1813
  ];
1910
1814
  const resizable = shape.type !== "text";
1911
1815
  return /* @__PURE__ */ jsxs2("g", { children: [
1912
- /* @__PURE__ */ jsx3(
1816
+ /* @__PURE__ */ jsx2(
1913
1817
  "rect",
1914
1818
  {
1915
1819
  x: b.x - pad,
@@ -1923,7 +1827,7 @@ function SelectionOverlay({
1923
1827
  pointerEvents: "none"
1924
1828
  }
1925
1829
  ),
1926
- resizable && corners.map(({ corner, x, y }) => /* @__PURE__ */ jsx3(
1830
+ resizable && corners.map(({ corner, x, y }) => /* @__PURE__ */ jsx2(
1927
1831
  "rect",
1928
1832
  {
1929
1833
  x: x - 4,
@@ -1949,9 +1853,9 @@ function TextEditOverlay({
1949
1853
  onCommit
1950
1854
  }) {
1951
1855
  const original = shape[field] ?? "";
1952
- const [value, setValue] = useState3(original);
1953
- const ref = useRef3(null);
1954
- useEffect7(() => {
1856
+ const [value, setValue] = useState(original);
1857
+ const ref = useRef2(null);
1858
+ useEffect5(() => {
1955
1859
  ref.current?.focus();
1956
1860
  ref.current?.select();
1957
1861
  }, []);
@@ -1999,7 +1903,7 @@ function TextEditOverlay({
1999
1903
  });
2000
1904
  }
2001
1905
  const placeholder = field === "label" ? "Label" : field === "footer" ? "Footer" : "Text";
2002
- return /* @__PURE__ */ jsx3(
1906
+ return /* @__PURE__ */ jsx2(
2003
1907
  "textarea",
2004
1908
  {
2005
1909
  ref,
@@ -2025,7 +1929,7 @@ function TextEditOverlay({
2025
1929
  }
2026
1930
 
2027
1931
  // src/nodes/DrawingNode.tsx
2028
- import { jsx as jsx4 } from "react/jsx-runtime";
1932
+ import { jsx as jsx3 } from "react/jsx-runtime";
2029
1933
  var DrawingNode = class _DrawingNode extends DecoratorNode {
2030
1934
  static getType() {
2031
1935
  return "drawing";
@@ -2089,11 +1993,11 @@ var DrawingNode = class _DrawingNode extends DecoratorNode {
2089
1993
  return false;
2090
1994
  }
2091
1995
  decorate(_editor, _config) {
2092
- return /* @__PURE__ */ jsx4(DrawingCanvas, { nodeKey: this.getKey(), data: this.getData() });
1996
+ return /* @__PURE__ */ jsx3(DrawingCanvas, { nodeKey: this.getKey(), data: this.getData() });
2093
1997
  }
2094
1998
  };
2095
1999
  function $createDrawingNode(data = serializeDrawingData(EMPTY_DRAWING)) {
2096
- return $applyNodeReplacement(new DrawingNode(data));
2000
+ return $applyNodeReplacement2(new DrawingNode(data));
2097
2001
  }
2098
2002
  function $isDrawingNode(node) {
2099
2003
  return node instanceof DrawingNode;
@@ -2119,303 +2023,106 @@ ${node.getLatest().__data}
2119
2023
  type: "multiline-element"
2120
2024
  };
2121
2025
 
2122
- // src/plugins/ToolbarPlugin.tsx
2123
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
2124
- var FORMAT_BUTTONS = [
2125
- {
2126
- format: "bold",
2127
- label: "Bold",
2128
- icon: /* @__PURE__ */ jsx5(
2129
- "path",
2130
- {
2131
- d: "M6 3.5h5a3 3 0 0 1 0 6H6zm0 6h6a3 3 0 0 1 0 6H6z",
2132
- strokeWidth: "1.8"
2133
- }
2134
- )
2135
- },
2136
- {
2137
- format: "italic",
2138
- label: "Italic",
2139
- icon: /* @__PURE__ */ jsx5("path", { d: "M8.5 3.5h6M5.5 16.5h6M11.5 3.5l-3 13", strokeWidth: "1.6" })
2140
- },
2141
- {
2142
- format: "strikethrough",
2143
- label: "Strikethrough",
2144
- icon: /* @__PURE__ */ jsx5(
2145
- "path",
2146
- {
2147
- d: "M4 10h12M13.5 5.5c-.6-1.2-2-2-3.5-2-2 0-3.5 1.2-3.5 2.8 0 .5.1.9.4 1.3m-.4 5c.5 1.6 2 2.9 3.9 2.9 2 0 3.6-1.2 3.6-2.9 0-.4-.1-.8-.2-1.1",
2148
- strokeWidth: "1.6"
2149
- }
2150
- )
2151
- },
2152
- {
2153
- format: "code",
2154
- label: "Inline code",
2155
- icon: /* @__PURE__ */ jsx5("path", { d: "M7.5 6L4 10l3.5 4M12.5 6L16 10l-3.5 4", strokeWidth: "1.6" })
2026
+ // src/transformers/tableTransformer.ts
2027
+ import {
2028
+ $isParagraphNode as $isParagraphNode2,
2029
+ $isTextNode
2030
+ } from "lexical";
2031
+ import {
2032
+ $convertFromMarkdownString as $convertFromMarkdownString2,
2033
+ $convertToMarkdownString as $convertToMarkdownString2,
2034
+ TRANSFORMERS
2035
+ } from "@lexical/markdown";
2036
+ import {
2037
+ $createTableCellNode,
2038
+ $createTableNode,
2039
+ $createTableRowNode,
2040
+ $isTableCellNode,
2041
+ $isTableNode as $isTableNode2,
2042
+ $isTableRowNode,
2043
+ TableCellHeaderStates,
2044
+ TableCellNode,
2045
+ TableNode,
2046
+ TableRowNode
2047
+ } from "@lexical/table";
2048
+
2049
+ // src/transformers/tableWidth.ts
2050
+ import {
2051
+ $getNodeByKey as $getNodeByKey3,
2052
+ $getSelection as $getSelection2,
2053
+ $isRangeSelection as $isRangeSelection2
2054
+ } from "lexical";
2055
+ import { $findMatchingParent } from "@lexical/utils";
2056
+ import { $isTableNode, $isTableSelection } from "@lexical/table";
2057
+ var TABLE_WIDTH_MARKER = "<!-- width: content -->";
2058
+ var TABLE_WIDTH_MARKER_REG_EXP = /^<!--\s*width:\s*content\s*-->\s*$/;
2059
+ var CONTENT_WIDTH_STYLE = "width: auto; table-layout: auto";
2060
+ var CONTENT_WIDTH_REG_EXP = /(^|;)\s*width:\s*auto\s*(;|$)/;
2061
+ function $getTableWidth(table) {
2062
+ return CONTENT_WIDTH_REG_EXP.test(table.getStyle()) ? "content" : "full";
2063
+ }
2064
+ function $setTableWidth(table, width) {
2065
+ table.setStyle(width === "content" ? CONTENT_WIDTH_STYLE : "");
2066
+ }
2067
+ function $getSelectedTable() {
2068
+ const selection = $getSelection2();
2069
+ if ($isTableSelection(selection)) {
2070
+ const node = $getNodeByKey3(selection.tableKey);
2071
+ return $isTableNode(node) ? node : null;
2156
2072
  }
2157
- ];
2158
- function ToolbarPlugin() {
2159
- const [editor] = useLexicalComposerContext8();
2160
- const [activeFormats, setActiveFormats] = useState4(
2161
- /* @__PURE__ */ new Set()
2073
+ if (!$isRangeSelection2(selection)) return null;
2074
+ const table = $findMatchingParent(
2075
+ selection.anchor.getNode(),
2076
+ (node) => $isTableNode(node)
2162
2077
  );
2163
- useEffect8(() => {
2164
- return editor.registerUpdateListener(({ editorState }) => {
2165
- editorState.read(() => {
2166
- const selection = $getSelection3();
2167
- if (!$isRangeSelection3(selection)) {
2168
- setActiveFormats(/* @__PURE__ */ new Set());
2169
- return;
2170
- }
2171
- const next = /* @__PURE__ */ new Set();
2172
- for (const { format } of FORMAT_BUTTONS) {
2173
- if (selection.hasFormat(format)) next.add(format);
2078
+ return $isTableNode(table) ? table : null;
2079
+ }
2080
+
2081
+ // src/transformers/tableTransformer.ts
2082
+ var TABLE_ROW_REG_EXP = /^\|(.+)\|\s?$/;
2083
+ var TABLE_ROW_DIVIDER_REG_EXP = /^(\| ?:?-*:? ?)+\|\s?$/;
2084
+ function $isTableWidthMarker(node) {
2085
+ if (!$isParagraphNode2(node) || node.getChildrenSize() !== 1) return false;
2086
+ const child = node.getFirstChild();
2087
+ return $isTextNode(child) && TABLE_WIDTH_MARKER_REG_EXP.test(child.getTextContent());
2088
+ }
2089
+ function getTableColumnsSize(table) {
2090
+ const row = table.getFirstChild();
2091
+ return $isTableRowNode(row) ? row.getChildrenSize() : 0;
2092
+ }
2093
+ function createTableCell(textContent) {
2094
+ const unescaped = textContent.replace(/\\n/g, "\n");
2095
+ const cell = $createTableCellNode(TableCellHeaderStates.NO_STATUS);
2096
+ $convertFromMarkdownString2(unescaped, TRANSFORMERS, cell);
2097
+ return cell;
2098
+ }
2099
+ function mapToTableCells(textContent) {
2100
+ const match = textContent.match(TABLE_ROW_REG_EXP);
2101
+ if (!match || !match[1]) return null;
2102
+ return match[1].split("|").map((text) => createTableCell(text.trim()));
2103
+ }
2104
+ var TABLE = {
2105
+ dependencies: [TableNode, TableRowNode, TableCellNode],
2106
+ export: (node) => {
2107
+ if (!$isTableNode2(node)) return null;
2108
+ const output = [];
2109
+ if ($getTableWidth(node) === "content") output.push(TABLE_WIDTH_MARKER);
2110
+ for (const row of node.getChildren()) {
2111
+ if (!$isTableRowNode(row)) continue;
2112
+ const rowOutput = [];
2113
+ let isHeaderRow = false;
2114
+ for (const cell of row.getChildren()) {
2115
+ if (!$isTableCellNode(cell)) continue;
2116
+ rowOutput.push(
2117
+ $convertToMarkdownString2(TRANSFORMERS, cell).replace(/\n/g, "\\n")
2118
+ );
2119
+ if (cell.__headerState === TableCellHeaderStates.ROW) {
2120
+ isHeaderRow = true;
2174
2121
  }
2175
- setActiveFormats(next);
2176
- });
2177
- });
2178
- }, [editor]);
2179
- const insertTable = useCallback4(() => {
2180
- editor.dispatchCommand(INSERT_TABLE_COMMAND, {
2181
- columns: "3",
2182
- rows: "3",
2183
- includeHeaders: { rows: true, columns: false }
2184
- });
2185
- }, [editor]);
2186
- const insertDrawing = useCallback4(() => {
2187
- editor.update(() => {
2188
- const drawing = $createDrawingNode();
2189
- $insertNodeToNearestRoot(drawing);
2190
- });
2191
- }, [editor]);
2192
- return /* @__PURE__ */ jsxs3("div", { className: "zui-text-editor-toolbar", children: [
2193
- FORMAT_BUTTONS.map(({ format, label, icon }) => /* @__PURE__ */ jsx5(
2194
- "button",
2195
- {
2196
- type: "button",
2197
- title: label,
2198
- "aria-label": label,
2199
- "aria-pressed": activeFormats.has(format),
2200
- className: `zui-text-editor-toolbar-button ${activeFormats.has(format) ? "is-active" : ""}`,
2201
- onMouseDown: (e) => e.preventDefault(),
2202
- onClick: () => editor.dispatchCommand(FORMAT_TEXT_COMMAND, format),
2203
- children: /* @__PURE__ */ jsx5(
2204
- "svg",
2205
- {
2206
- viewBox: "0 0 20 20",
2207
- width: "16",
2208
- height: "16",
2209
- fill: "none",
2210
- stroke: "currentColor",
2211
- strokeLinecap: "round",
2212
- strokeLinejoin: "round",
2213
- children: icon
2214
- }
2215
- )
2216
- },
2217
- format
2218
- )),
2219
- /* @__PURE__ */ jsx5("div", { className: "zui-text-editor-toolbar-divider" }),
2220
- /* @__PURE__ */ jsx5(
2221
- "button",
2222
- {
2223
- type: "button",
2224
- title: "Insert table",
2225
- "aria-label": "Insert table",
2226
- className: "zui-text-editor-toolbar-button",
2227
- onMouseDown: (e) => e.preventDefault(),
2228
- onClick: insertTable,
2229
- children: /* @__PURE__ */ jsxs3(
2230
- "svg",
2231
- {
2232
- viewBox: "0 0 20 20",
2233
- width: "16",
2234
- height: "16",
2235
- fill: "none",
2236
- stroke: "currentColor",
2237
- strokeWidth: "1.5",
2238
- strokeLinecap: "round",
2239
- children: [
2240
- /* @__PURE__ */ jsx5("rect", { x: "3", y: "3.5", width: "14", height: "13", rx: "1.5" }),
2241
- /* @__PURE__ */ jsx5("path", { d: "M3 8h14M8 8v8.5M13 8v8.5" })
2242
- ]
2243
- }
2244
- )
2245
2122
  }
2246
- ),
2247
- /* @__PURE__ */ jsx5(
2248
- "button",
2249
- {
2250
- type: "button",
2251
- title: "Insert drawing",
2252
- "aria-label": "Insert drawing",
2253
- className: "zui-text-editor-toolbar-button",
2254
- onMouseDown: (e) => e.preventDefault(),
2255
- onClick: insertDrawing,
2256
- children: /* @__PURE__ */ jsxs3(
2257
- "svg",
2258
- {
2259
- viewBox: "0 0 20 20",
2260
- width: "16",
2261
- height: "16",
2262
- fill: "none",
2263
- stroke: "currentColor",
2264
- strokeWidth: "1.5",
2265
- strokeLinecap: "round",
2266
- strokeLinejoin: "round",
2267
- children: [
2268
- /* @__PURE__ */ jsx5("rect", { x: "3", y: "5", width: "8", height: "6", rx: "1.5" }),
2269
- /* @__PURE__ */ jsx5("path", { d: "M13.5 8h3M14.5 6.5L17.5 8l-3 1.5" }),
2270
- /* @__PURE__ */ jsx5("ellipse", { cx: "13", cy: "14.5", rx: "4", ry: "2.8" })
2271
- ]
2272
- }
2273
- )
2274
- }
2275
- )
2276
- ] });
2277
- }
2278
-
2279
- // src/nodes/FrontmatterNode.ts
2280
- import {
2281
- $applyNodeReplacement as $applyNodeReplacement2,
2282
- $createLineBreakNode,
2283
- $createTextNode,
2284
- ElementNode
2285
- } from "lexical";
2286
- var FrontmatterNode = class _FrontmatterNode extends ElementNode {
2287
- static getType() {
2288
- return "frontmatter";
2289
- }
2290
- static clone(node) {
2291
- return new _FrontmatterNode(node.__key);
2292
- }
2293
- constructor(key) {
2294
- super(key);
2295
- }
2296
- createDOM(config) {
2297
- const dom = document.createElement("div");
2298
- const className = config.theme.frontmatter;
2299
- dom.className = typeof className === "string" ? className : "zui-frontmatter";
2300
- return dom;
2301
- }
2302
- updateDOM() {
2303
- return false;
2304
- }
2305
- static importJSON(_serializedNode) {
2306
- return $createFrontmatterNode();
2307
- }
2308
- exportJSON() {
2309
- return {
2310
- ...super.exportJSON(),
2311
- type: "frontmatter",
2312
- version: 1
2313
- };
2314
- }
2315
- isInline() {
2316
- return false;
2317
- }
2318
- canBeEmpty() {
2319
- return false;
2320
- }
2321
- canIndent() {
2322
- return false;
2323
- }
2324
- };
2325
- function $createFrontmatterNode() {
2326
- return $applyNodeReplacement2(new FrontmatterNode());
2327
- }
2328
- function $isFrontmatterNode(node) {
2329
- return node instanceof FrontmatterNode;
2330
- }
2331
- var FRONTMATTER_DELIMITER = /^---\s*$/;
2332
- var FRONTMATTER = {
2333
- dependencies: [FrontmatterNode],
2334
- export: (node) => {
2335
- if (!$isFrontmatterNode(node)) return null;
2336
- const body = node.getTextContent();
2337
- return body ? `---
2338
- ${body}
2339
- ---` : "---\n---";
2340
- },
2341
- regExpStart: FRONTMATTER_DELIMITER,
2342
- regExpEnd: FRONTMATTER_DELIMITER,
2343
- replace: (rootNode, children, _startMatch, _endMatch, linesInBetween, isImport) => {
2344
- if (!isImport || children != null || linesInBetween == null) return false;
2345
- if (rootNode.getChildrenSize() !== 0) return false;
2346
- const lines = [...linesInBetween];
2347
- while (lines.length > 0 && lines[0].length === 0) lines.shift();
2348
- while (lines.length > 0 && lines[lines.length - 1].length === 0) lines.pop();
2349
- const node = $createFrontmatterNode();
2350
- lines.forEach((line, index) => {
2351
- if (index > 0) node.append($createLineBreakNode());
2352
- node.append($createTextNode(line));
2353
- });
2354
- rootNode.append(node);
2355
- },
2356
- type: "multiline-element"
2357
- };
2358
-
2359
- // src/transformers/tableTransformer.ts
2360
- import {
2361
- $isParagraphNode as $isParagraphNode2,
2362
- $isTextNode
2363
- } from "lexical";
2364
- import {
2365
- $convertFromMarkdownString as $convertFromMarkdownString2,
2366
- $convertToMarkdownString as $convertToMarkdownString2,
2367
- TRANSFORMERS
2368
- } from "@lexical/markdown";
2369
- import {
2370
- $createTableCellNode,
2371
- $createTableNode,
2372
- $createTableRowNode,
2373
- $isTableCellNode,
2374
- $isTableNode,
2375
- $isTableRowNode,
2376
- TableCellHeaderStates,
2377
- TableCellNode,
2378
- TableNode,
2379
- TableRowNode
2380
- } from "@lexical/table";
2381
- var TABLE_ROW_REG_EXP = /^\|(.+)\|\s?$/;
2382
- var TABLE_ROW_DIVIDER_REG_EXP = /^(\| ?:?-*:? ?)+\|\s?$/;
2383
- function getTableColumnsSize(table) {
2384
- const row = table.getFirstChild();
2385
- return $isTableRowNode(row) ? row.getChildrenSize() : 0;
2386
- }
2387
- function createTableCell(textContent) {
2388
- const unescaped = textContent.replace(/\\n/g, "\n");
2389
- const cell = $createTableCellNode(TableCellHeaderStates.NO_STATUS);
2390
- $convertFromMarkdownString2(unescaped, TRANSFORMERS, cell);
2391
- return cell;
2392
- }
2393
- function mapToTableCells(textContent) {
2394
- const match = textContent.match(TABLE_ROW_REG_EXP);
2395
- if (!match || !match[1]) return null;
2396
- return match[1].split("|").map((text) => createTableCell(text.trim()));
2397
- }
2398
- var TABLE = {
2399
- dependencies: [TableNode, TableRowNode, TableCellNode],
2400
- export: (node) => {
2401
- if (!$isTableNode(node)) return null;
2402
- const output = [];
2403
- for (const row of node.getChildren()) {
2404
- if (!$isTableRowNode(row)) continue;
2405
- const rowOutput = [];
2406
- let isHeaderRow = false;
2407
- for (const cell of row.getChildren()) {
2408
- if (!$isTableCellNode(cell)) continue;
2409
- rowOutput.push(
2410
- $convertToMarkdownString2(TRANSFORMERS, cell).replace(/\n/g, "\\n")
2411
- );
2412
- if (cell.__headerState === TableCellHeaderStates.ROW) {
2413
- isHeaderRow = true;
2414
- }
2415
- }
2416
- output.push(`| ${rowOutput.join(" | ")} |`);
2417
- if (isHeaderRow) {
2418
- output.push(`| ${rowOutput.map(() => "---").join(" | ")} |`);
2123
+ output.push(`| ${rowOutput.join(" | ")} |`);
2124
+ if (isHeaderRow) {
2125
+ output.push(`| ${rowOutput.map(() => "---").join(" | ")} |`);
2419
2126
  }
2420
2127
  }
2421
2128
  return output.join("\n");
@@ -2424,7 +2131,7 @@ var TABLE = {
2424
2131
  replace: (parentNode, _children, match) => {
2425
2132
  if (TABLE_ROW_DIVIDER_REG_EXP.test(match[0])) {
2426
2133
  const table2 = parentNode.getPreviousSibling();
2427
- if (!table2 || !$isTableNode(table2)) return;
2134
+ if (!table2 || !$isTableNode2(table2)) return;
2428
2135
  const lastRow = table2.getLastChild();
2429
2136
  if (!lastRow || !$isTableRowNode(lastRow)) return;
2430
2137
  lastRow.getChildren().forEach((cell) => {
@@ -2461,10 +2168,14 @@ var TABLE = {
2461
2168
  }
2462
2169
  }
2463
2170
  const previousSibling = parentNode.getPreviousSibling();
2464
- if ($isTableNode(previousSibling) && getTableColumnsSize(previousSibling) === maxCells) {
2171
+ if ($isTableNode2(previousSibling) && getTableColumnsSize(previousSibling) === maxCells) {
2465
2172
  previousSibling.append(...table.getChildren());
2466
2173
  parentNode.remove();
2467
2174
  } else {
2175
+ if ($isTableWidthMarker(previousSibling)) {
2176
+ $setTableWidth(table, "content");
2177
+ previousSibling?.remove();
2178
+ }
2468
2179
  parentNode.replace(table);
2469
2180
  }
2470
2181
  table.selectEnd();
@@ -2551,10 +2262,20 @@ var editorTheme = {
2551
2262
  drawing: "zui-drawing"
2552
2263
  };
2553
2264
 
2554
- // src/MarkdownEditor.tsx
2555
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
2265
+ // src/EditorRoot.tsx
2266
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2556
2267
  var SYNC_TRANSFORMERS = [FRONTMATTER, DRAWING, CHECK_LIST, TABLE, ...TRANSFORMERS2];
2557
2268
  var SHORTCUT_TRANSFORMERS = [TABLE, ...TRANSFORMERS2.filter((t) => t !== CODE)];
2269
+ var EditorContext = createContext(null);
2270
+ function useEditorContext() {
2271
+ const ctx = useContext(EditorContext);
2272
+ if (!ctx) {
2273
+ throw new Error(
2274
+ "@zuilib/text-editor: this component must be rendered inside <MarkdownEditor> or <MarkdownEditor.Root>"
2275
+ );
2276
+ }
2277
+ return ctx;
2278
+ }
2558
2279
  function onError(error) {
2559
2280
  console.error(error);
2560
2281
  }
@@ -2574,49 +2295,38 @@ var editorNodes = [
2574
2295
  DrawingNode
2575
2296
  ];
2576
2297
  function ReadOnlyPlugin({ readOnly }) {
2577
- const [editor] = useLexicalComposerContext9();
2578
- useEffect9(() => {
2298
+ const [editor] = useLexicalComposerContext6();
2299
+ useEffect6(() => {
2579
2300
  editor.setEditable(!readOnly);
2580
2301
  }, [editor, readOnly]);
2581
2302
  return null;
2582
2303
  }
2583
- function MarkdownEditor({
2304
+ function EditorRoot({
2584
2305
  value,
2585
2306
  onChange,
2586
- placeholder = "Start writing...",
2587
2307
  readOnly = false,
2588
2308
  className,
2589
2309
  mode = "edit-md",
2590
2310
  autoFocus = false,
2591
- toolbar = true,
2592
- outline = false,
2593
- foldable = true
2311
+ children
2594
2312
  }) {
2595
- const latestValueRef = useRef4(value ?? "");
2596
- const [mountKey, setMountKey] = useState5(0);
2597
- const [capturedMarkdown, setCapturedMarkdown] = useState5(value ?? "");
2598
- useEffect9(() => {
2313
+ const latestValueRef = useRef3(value ?? "");
2314
+ const [mountKey, setMountKey] = useState2(0);
2315
+ const [capturedMarkdown, setCapturedMarkdown] = useState2(value ?? "");
2316
+ useEffect6(() => {
2599
2317
  if (value !== void 0) {
2600
2318
  latestValueRef.current = value;
2601
2319
  }
2602
2320
  }, [value]);
2603
- const handleTextChange = useCallback5(
2604
- (e) => {
2605
- const newValue = e.target.value;
2606
- latestValueRef.current = newValue;
2607
- onChange?.(newValue);
2608
- },
2609
- [onChange]
2610
- );
2611
- const handleLexicalChange = useCallback5(
2321
+ const handleChange = useCallback2(
2612
2322
  (newValue) => {
2613
2323
  latestValueRef.current = newValue;
2614
2324
  onChange?.(newValue);
2615
2325
  },
2616
2326
  [onChange]
2617
2327
  );
2618
- const prevModeRef = useRef4(mode);
2619
- useEffect9(() => {
2328
+ const prevModeRef = useRef3(mode);
2329
+ useEffect6(() => {
2620
2330
  if (prevModeRef.current === "edit-raw" && mode !== "edit-raw") {
2621
2331
  setCapturedMarkdown(latestValueRef.current);
2622
2332
  setMountKey((k) => k + 1);
@@ -2632,78 +2342,713 @@ function MarkdownEditor({
2632
2342
  }),
2633
2343
  []
2634
2344
  );
2635
- if (mode === "edit-raw") {
2636
- return /* @__PURE__ */ jsx6("div", { className: `zui-text-editor ${className ?? ""}`, children: /* @__PURE__ */ jsx6(
2637
- "textarea",
2638
- {
2639
- className: "zui-text-editor-textarea",
2640
- value: value ?? "",
2641
- onChange: handleTextChange,
2642
- placeholder,
2643
- readOnly,
2644
- autoFocus,
2645
- spellCheck: false
2345
+ const context = useMemo(
2346
+ () => ({
2347
+ mode,
2348
+ readOnly,
2349
+ autoFocus,
2350
+ rawValue: value ?? "",
2351
+ onRawChange: handleChange
2352
+ }),
2353
+ [mode, readOnly, autoFocus, value, handleChange]
2354
+ );
2355
+ const isRaw = mode === "edit-raw";
2356
+ return /* @__PURE__ */ jsx4(LexicalComposer, { initialConfig, children: /* @__PURE__ */ jsx4(EditorContext.Provider, { value: context, children: /* @__PURE__ */ jsxs3("div", { className: `zui-text-editor ${className ?? ""}`, children: [
2357
+ children,
2358
+ !isRaw && /* @__PURE__ */ jsxs3(Fragment2, { children: [
2359
+ /* @__PURE__ */ jsx4(
2360
+ MarkdownSyncPlugin,
2361
+ {
2362
+ initialMarkdown: mode === "view" ? value ?? "" : capturedMarkdown,
2363
+ onChange: handleChange,
2364
+ transformers: SYNC_TRANSFORMERS
2365
+ }
2366
+ ),
2367
+ /* @__PURE__ */ jsx4(HistoryPlugin, {}),
2368
+ /* @__PURE__ */ jsx4(ListPlugin, {}),
2369
+ /* @__PURE__ */ jsx4(CheckListPlugin, {}),
2370
+ /* @__PURE__ */ jsx4(ChecklistShortcutPlugin, {}),
2371
+ /* @__PURE__ */ jsx4(CodeBlockShortcutPlugin, {}),
2372
+ /* @__PURE__ */ jsx4(CodeHighlightPlugin, {}),
2373
+ /* @__PURE__ */ jsx4(TablePlugin, {}),
2374
+ /* @__PURE__ */ jsx4(LinkPlugin, {}),
2375
+ /* @__PURE__ */ jsx4(MarkdownShortcutPlugin, { transformers: SHORTCUT_TRANSFORMERS }),
2376
+ /* @__PURE__ */ jsx4(ReadOnlyPlugin, { readOnly: readOnly || mode === "view" }),
2377
+ autoFocus && /* @__PURE__ */ jsx4(AutoFocusPlugin, {})
2378
+ ] })
2379
+ ] }) }) }, mountKey);
2380
+ }
2381
+
2382
+ // src/EditorContent.tsx
2383
+ import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
2384
+ import { ContentEditable } from "@lexical/react/LexicalContentEditable";
2385
+ import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
2386
+
2387
+ // src/plugins/FoldingPlugin.tsx
2388
+ import {
2389
+ useCallback as useCallback3,
2390
+ useEffect as useEffect7,
2391
+ useRef as useRef4,
2392
+ useState as useState3
2393
+ } from "react";
2394
+ import { $getRoot, $getSelection as $getSelection3, $isRangeSelection as $isRangeSelection3 } from "lexical";
2395
+ import { useLexicalComposerContext as useLexicalComposerContext7 } from "@lexical/react/LexicalComposerContext";
2396
+ import { $isHeadingNode } from "@lexical/rich-text";
2397
+ import { jsx as jsx5 } from "react/jsx-runtime";
2398
+ var HEADING_LEVELS = {
2399
+ h1: 1,
2400
+ h2: 2,
2401
+ h3: 3,
2402
+ h4: 4,
2403
+ h5: 5,
2404
+ h6: 6
2405
+ };
2406
+ function FoldingPlugin() {
2407
+ const [editor] = useLexicalComposerContext7();
2408
+ const [foldedKeys, setFoldedKeys] = useState3(/* @__PURE__ */ new Set());
2409
+ const [buttons, setButtons] = useState3([]);
2410
+ const foldedRef = useRef4(foldedKeys);
2411
+ foldedRef.current = foldedKeys;
2412
+ const sync = useCallback3(() => {
2413
+ editor.getEditorState().read(() => {
2414
+ const children = $getRoot().getChildren();
2415
+ const folded = foldedRef.current;
2416
+ const hidden = /* @__PURE__ */ new Set();
2417
+ const headings = [];
2418
+ for (let i = 0; i < children.length; i++) {
2419
+ const node = children[i];
2420
+ if (!$isHeadingNode(node)) continue;
2421
+ const level = HEADING_LEVELS[node.getTag()] ?? 6;
2422
+ const isFolded = folded.has(node.getKey());
2423
+ headings.push({ key: node.getKey(), folded: isFolded });
2424
+ if (!isFolded) continue;
2425
+ for (let j = i + 1; j < children.length; j++) {
2426
+ const sibling = children[j];
2427
+ if ($isHeadingNode(sibling) && (HEADING_LEVELS[sibling.getTag()] ?? 6) <= level) {
2428
+ break;
2429
+ }
2430
+ hidden.add(sibling.getKey());
2431
+ }
2646
2432
  }
2647
- ) });
2648
- }
2649
- return /* @__PURE__ */ jsx6(LexicalComposer, { initialConfig, children: /* @__PURE__ */ jsxs4("div", { className: `zui-text-editor ${className ?? ""}`, children: [
2650
- toolbar && mode === "edit-md" && !readOnly && /* @__PURE__ */ jsx6(ToolbarPlugin, {}),
2651
- /* @__PURE__ */ jsxs4("div", { className: "zui-text-editor-body", children: [
2652
- /* @__PURE__ */ jsxs4("div", { className: "zui-text-editor-main", children: [
2653
- /* @__PURE__ */ jsx6(
2654
- RichTextPlugin,
2655
- {
2656
- contentEditable: /* @__PURE__ */ jsx6(
2657
- ContentEditable,
2658
- {
2659
- className: "zui-text-editor-content",
2660
- "aria-placeholder": placeholder,
2661
- placeholder: /* @__PURE__ */ jsx6("div", { className: "zui-text-editor-placeholder", children: placeholder })
2662
- }
2663
- ),
2664
- ErrorBoundary: LexicalErrorBoundary
2433
+ const selection = $getSelection3();
2434
+ if ($isRangeSelection3(selection)) {
2435
+ const topLevel = selection.anchor.getNode().getTopLevelElement();
2436
+ if (topLevel && hidden.has(topLevel.getKey())) {
2437
+ let node = topLevel.getPreviousSibling();
2438
+ while (node) {
2439
+ if ($isHeadingNode(node) && folded.has(node.getKey())) {
2440
+ const unfoldKey = node.getKey();
2441
+ setFoldedKeys((prev) => {
2442
+ const next = new Set(prev);
2443
+ next.delete(unfoldKey);
2444
+ return next;
2445
+ });
2446
+ return;
2447
+ }
2448
+ node = node.getPreviousSibling();
2665
2449
  }
2666
- ),
2667
- foldable && /* @__PURE__ */ jsx6(FoldingPlugin, {})
2668
- ] }),
2669
- outline && /* @__PURE__ */ jsx6(OutlinePlugin, {})
2670
- ] }),
2671
- /* @__PURE__ */ jsx6(
2672
- MarkdownSyncPlugin,
2673
- {
2674
- initialMarkdown: mode === "view" ? value ?? "" : capturedMarkdown,
2675
- onChange: handleLexicalChange,
2676
- transformers: SYNC_TRANSFORMERS
2450
+ }
2677
2451
  }
2678
- ),
2679
- /* @__PURE__ */ jsx6(HistoryPlugin, {}),
2680
- /* @__PURE__ */ jsx6(ListPlugin, {}),
2681
- /* @__PURE__ */ jsx6(CheckListPlugin, {}),
2682
- /* @__PURE__ */ jsx6(ChecklistShortcutPlugin, {}),
2683
- /* @__PURE__ */ jsx6(CodeBlockShortcutPlugin, {}),
2684
- /* @__PURE__ */ jsx6(CodeHighlightPlugin, {}),
2685
- /* @__PURE__ */ jsx6(TablePlugin, {}),
2686
- /* @__PURE__ */ jsx6(LinkPlugin, {}),
2687
- /* @__PURE__ */ jsx6(MarkdownShortcutPlugin, { transformers: SHORTCUT_TRANSFORMERS }),
2688
- /* @__PURE__ */ jsx6(ReadOnlyPlugin, { readOnly: readOnly || mode === "view" }),
2689
- autoFocus && /* @__PURE__ */ jsx6(AutoFocusPlugin, {})
2690
- ] }) }, mountKey);
2691
- }
2692
-
2693
- // src/components/drawingSchema.ts
2694
- var DRAWING_DATA_JSON_SCHEMA = {
2695
- $schema: "http://json-schema.org/draft-07/schema#",
2696
- title: "DrawingData",
2697
- description: "Vector drawing embedded in markdown as a ```drawing fenced code block",
2698
- type: "object",
2699
- required: ["version", "height", "shapes"],
2700
- additionalProperties: false,
2701
- properties: {
2702
- version: { const: 1 },
2703
- height: {
2452
+ for (const child of children) {
2453
+ const element = editor.getElementByKey(child.getKey());
2454
+ if (!element) continue;
2455
+ const shouldHide = hidden.has(child.getKey());
2456
+ if (shouldHide) {
2457
+ element.style.display = "none";
2458
+ } else if (element.style.display === "none") {
2459
+ element.style.display = "";
2460
+ }
2461
+ }
2462
+ setButtons(
2463
+ headings.map(({ key, folded: isFolded }) => {
2464
+ const element = editor.getElementByKey(key);
2465
+ element?.classList.toggle("zui-heading-folded", isFolded);
2466
+ return {
2467
+ key,
2468
+ folded: isFolded,
2469
+ top: element?.offsetTop ?? 0,
2470
+ height: element?.offsetHeight ?? 0
2471
+ };
2472
+ })
2473
+ );
2474
+ });
2475
+ }, [editor]);
2476
+ useEffect7(() => {
2477
+ sync();
2478
+ const unregister = editor.registerUpdateListener(sync);
2479
+ const rootElement = editor.getRootElement();
2480
+ const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(sync) : null;
2481
+ if (rootElement && observer) observer.observe(rootElement);
2482
+ return () => {
2483
+ unregister();
2484
+ observer?.disconnect();
2485
+ };
2486
+ }, [editor, sync, foldedKeys]);
2487
+ const toggle = useCallback3((key) => {
2488
+ setFoldedKeys((prev) => {
2489
+ const next = new Set(prev);
2490
+ if (next.has(key)) next.delete(key);
2491
+ else next.add(key);
2492
+ return next;
2493
+ });
2494
+ }, []);
2495
+ return /* @__PURE__ */ jsx5("div", { className: "zui-text-editor-fold-gutter", children: buttons.map(({ key, folded, top, height }) => /* @__PURE__ */ jsx5(
2496
+ "button",
2497
+ {
2498
+ type: "button",
2499
+ className: `zui-text-editor-fold ${folded ? "is-folded" : ""}`,
2500
+ style: { top: top + Math.max(0, (Math.min(height, 44) - 18) / 2) },
2501
+ title: folded ? "Expand section" : "Collapse section",
2502
+ "aria-label": folded ? "Expand section" : "Collapse section",
2503
+ "aria-expanded": !folded,
2504
+ onClick: () => toggle(key),
2505
+ children: /* @__PURE__ */ jsx5(
2506
+ "svg",
2507
+ {
2508
+ viewBox: "0 0 20 20",
2509
+ width: "12",
2510
+ height: "12",
2511
+ fill: "none",
2512
+ stroke: "currentColor",
2513
+ strokeWidth: "2",
2514
+ strokeLinecap: "round",
2515
+ strokeLinejoin: "round",
2516
+ children: /* @__PURE__ */ jsx5("path", { d: "M6 8l4 4 4-4" })
2517
+ }
2518
+ )
2519
+ },
2520
+ key
2521
+ )) });
2522
+ }
2523
+
2524
+ // src/plugins/TableWidthPlugin.tsx
2525
+ import { useCallback as useCallback5, useEffect as useEffect9, useState as useState5 } from "react";
2526
+ import { useLexicalComposerContext as useLexicalComposerContext9 } from "@lexical/react/LexicalComposerContext";
2527
+ import { useLexicalEditable as useLexicalEditable2 } from "@lexical/react/useLexicalEditable";
2528
+
2529
+ // src/useMarkdownEditor.ts
2530
+ import { useCallback as useCallback4, useEffect as useEffect8, useState as useState4 } from "react";
2531
+ import {
2532
+ $getSelection as $getSelection4,
2533
+ $isRangeSelection as $isRangeSelection4,
2534
+ CAN_REDO_COMMAND,
2535
+ CAN_UNDO_COMMAND,
2536
+ COMMAND_PRIORITY_LOW as COMMAND_PRIORITY_LOW2,
2537
+ FORMAT_TEXT_COMMAND,
2538
+ REDO_COMMAND,
2539
+ UNDO_COMMAND
2540
+ } from "lexical";
2541
+ import { useLexicalComposerContext as useLexicalComposerContext8 } from "@lexical/react/LexicalComposerContext";
2542
+ import { INSERT_TABLE_COMMAND } from "@lexical/table";
2543
+ import { $insertNodeToNearestRoot, mergeRegister } from "@lexical/utils";
2544
+ var TRACKED_FORMATS = [
2545
+ "bold",
2546
+ "italic",
2547
+ "underline",
2548
+ "strikethrough",
2549
+ "code"
2550
+ ];
2551
+ function useMarkdownEditor() {
2552
+ const [editor] = useLexicalComposerContext8();
2553
+ const [activeFormats, setActiveFormats] = useState4(
2554
+ /* @__PURE__ */ new Set()
2555
+ );
2556
+ const [canUndo, setCanUndo] = useState4(false);
2557
+ const [canRedo, setCanRedo] = useState4(false);
2558
+ const [tableWidth, setTableWidthState] = useState4(null);
2559
+ useEffect8(
2560
+ () => mergeRegister(
2561
+ editor.registerUpdateListener(({ editorState }) => {
2562
+ editorState.read(() => {
2563
+ const table = $getSelectedTable();
2564
+ setTableWidthState(table ? $getTableWidth(table) : null);
2565
+ const selection = $getSelection4();
2566
+ if (!$isRangeSelection4(selection)) {
2567
+ setActiveFormats(/* @__PURE__ */ new Set());
2568
+ return;
2569
+ }
2570
+ const next = /* @__PURE__ */ new Set();
2571
+ for (const format of TRACKED_FORMATS) {
2572
+ if (selection.hasFormat(format)) next.add(format);
2573
+ }
2574
+ setActiveFormats(next);
2575
+ });
2576
+ }),
2577
+ editor.registerCommand(
2578
+ CAN_UNDO_COMMAND,
2579
+ (payload) => {
2580
+ setCanUndo(payload);
2581
+ return false;
2582
+ },
2583
+ COMMAND_PRIORITY_LOW2
2584
+ ),
2585
+ editor.registerCommand(
2586
+ CAN_REDO_COMMAND,
2587
+ (payload) => {
2588
+ setCanRedo(payload);
2589
+ return false;
2590
+ },
2591
+ COMMAND_PRIORITY_LOW2
2592
+ )
2593
+ ),
2594
+ [editor]
2595
+ );
2596
+ const toggleFormat = useCallback4(
2597
+ (format) => {
2598
+ editor.dispatchCommand(FORMAT_TEXT_COMMAND, format);
2599
+ },
2600
+ [editor]
2601
+ );
2602
+ const insertTable = useCallback4(
2603
+ ({ rows = 3, columns = 3 } = {}) => {
2604
+ editor.dispatchCommand(INSERT_TABLE_COMMAND, {
2605
+ columns: String(columns),
2606
+ rows: String(rows),
2607
+ includeHeaders: { rows: true, columns: false }
2608
+ });
2609
+ },
2610
+ [editor]
2611
+ );
2612
+ const insertDrawing = useCallback4(() => {
2613
+ editor.update(() => {
2614
+ $insertNodeToNearestRoot($createDrawingNode());
2615
+ });
2616
+ }, [editor]);
2617
+ const setTableWidth = useCallback4(
2618
+ (width) => {
2619
+ editor.update(() => {
2620
+ const table = $getSelectedTable();
2621
+ if (table) $setTableWidth(table, width);
2622
+ });
2623
+ },
2624
+ [editor]
2625
+ );
2626
+ const undo = useCallback4(() => {
2627
+ editor.dispatchCommand(UNDO_COMMAND, void 0);
2628
+ }, [editor]);
2629
+ const redo = useCallback4(() => {
2630
+ editor.dispatchCommand(REDO_COMMAND, void 0);
2631
+ }, [editor]);
2632
+ return {
2633
+ editor,
2634
+ activeFormats,
2635
+ toggleFormat,
2636
+ insertTable,
2637
+ insertDrawing,
2638
+ tableWidth,
2639
+ setTableWidth,
2640
+ canUndo,
2641
+ canRedo,
2642
+ undo,
2643
+ redo
2644
+ };
2645
+ }
2646
+
2647
+ // src/components/Toolbar.tsx
2648
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
2649
+ function Toolbar({ children, className }) {
2650
+ return /* @__PURE__ */ jsx6(
2651
+ "div",
2652
+ {
2653
+ className: `zui-text-editor-toolbar ${className ?? ""}`,
2654
+ role: "toolbar",
2655
+ children: children ?? /* @__PURE__ */ jsxs4(Fragment3, { children: [
2656
+ /* @__PURE__ */ jsx6(FormatButtons, {}),
2657
+ /* @__PURE__ */ jsx6(ToolbarDivider, {}),
2658
+ /* @__PURE__ */ jsx6(InsertButtons, {})
2659
+ ] })
2660
+ }
2661
+ );
2662
+ }
2663
+ function ToolbarDivider() {
2664
+ return /* @__PURE__ */ jsx6("div", { className: "zui-text-editor-toolbar-divider" });
2665
+ }
2666
+ function ToolbarButton({
2667
+ label,
2668
+ onClick,
2669
+ children,
2670
+ active = false,
2671
+ disabled = false,
2672
+ className
2673
+ }) {
2674
+ return /* @__PURE__ */ jsx6(
2675
+ "button",
2676
+ {
2677
+ type: "button",
2678
+ title: label,
2679
+ "aria-label": label,
2680
+ "aria-pressed": active,
2681
+ disabled,
2682
+ className: `zui-text-editor-toolbar-button ${active ? "is-active" : ""} ${className ?? ""}`,
2683
+ onMouseDown: (e) => e.preventDefault(),
2684
+ onClick,
2685
+ children
2686
+ }
2687
+ );
2688
+ }
2689
+ function Icon({ children, strokeWidth = 1.5 }) {
2690
+ return /* @__PURE__ */ jsx6(
2691
+ "svg",
2692
+ {
2693
+ viewBox: "0 0 20 20",
2694
+ width: "16",
2695
+ height: "16",
2696
+ fill: "none",
2697
+ stroke: "currentColor",
2698
+ strokeWidth,
2699
+ strokeLinecap: "round",
2700
+ strokeLinejoin: "round",
2701
+ children
2702
+ }
2703
+ );
2704
+ }
2705
+ var FORMAT_BUTTONS = [
2706
+ {
2707
+ format: "bold",
2708
+ label: "Bold",
2709
+ icon: /* @__PURE__ */ jsx6(Icon, { strokeWidth: 1.8, children: /* @__PURE__ */ jsx6("path", { d: "M6 3.5h5a3 3 0 0 1 0 6H6zm0 6h6a3 3 0 0 1 0 6H6z" }) })
2710
+ },
2711
+ {
2712
+ format: "italic",
2713
+ label: "Italic",
2714
+ icon: /* @__PURE__ */ jsx6(Icon, { strokeWidth: 1.6, children: /* @__PURE__ */ jsx6("path", { d: "M8.5 3.5h6M5.5 16.5h6M11.5 3.5l-3 13" }) })
2715
+ },
2716
+ {
2717
+ format: "strikethrough",
2718
+ label: "Strikethrough",
2719
+ icon: /* @__PURE__ */ jsx6(Icon, { strokeWidth: 1.6, children: /* @__PURE__ */ jsx6("path", { d: "M4 10h12M13.5 5.5c-.6-1.2-2-2-3.5-2-2 0-3.5 1.2-3.5 2.8 0 .5.1.9.4 1.3m-.4 5c.5 1.6 2 2.9 3.9 2.9 2 0 3.6-1.2 3.6-2.9 0-.4-.1-.8-.2-1.1" }) })
2720
+ },
2721
+ {
2722
+ format: "code",
2723
+ label: "Inline code",
2724
+ icon: /* @__PURE__ */ jsx6(Icon, { strokeWidth: 1.6, children: /* @__PURE__ */ jsx6("path", { d: "M7.5 6L4 10l3.5 4M12.5 6L16 10l-3.5 4" }) })
2725
+ }
2726
+ ];
2727
+ function FormatButtons() {
2728
+ const { activeFormats, toggleFormat } = useMarkdownEditor();
2729
+ return /* @__PURE__ */ jsx6(Fragment3, { children: FORMAT_BUTTONS.map(({ format, label, icon }) => /* @__PURE__ */ jsx6(
2730
+ ToolbarButton,
2731
+ {
2732
+ label,
2733
+ active: activeFormats.has(format),
2734
+ onClick: () => toggleFormat(format),
2735
+ children: icon
2736
+ },
2737
+ format
2738
+ )) });
2739
+ }
2740
+ function InsertButtons() {
2741
+ const { insertTable, insertDrawing } = useMarkdownEditor();
2742
+ return /* @__PURE__ */ jsxs4(Fragment3, { children: [
2743
+ /* @__PURE__ */ jsx6(ToolbarButton, { label: "Insert table", onClick: () => insertTable(), children: /* @__PURE__ */ jsxs4(Icon, { children: [
2744
+ /* @__PURE__ */ jsx6("rect", { x: "3", y: "3.5", width: "14", height: "13", rx: "1.5" }),
2745
+ /* @__PURE__ */ jsx6("path", { d: "M3 8h14M8 8v8.5M13 8v8.5" })
2746
+ ] }) }),
2747
+ /* @__PURE__ */ jsx6(ToolbarButton, { label: "Insert drawing", onClick: insertDrawing, children: /* @__PURE__ */ jsxs4(Icon, { children: [
2748
+ /* @__PURE__ */ jsx6("rect", { x: "3", y: "5", width: "8", height: "6", rx: "1.5" }),
2749
+ /* @__PURE__ */ jsx6("path", { d: "M13.5 8h3M14.5 6.5L17.5 8l-3 1.5" }),
2750
+ /* @__PURE__ */ jsx6("ellipse", { cx: "13", cy: "14.5", rx: "4", ry: "2.8" })
2751
+ ] }) })
2752
+ ] });
2753
+ }
2754
+ function HistoryButtons() {
2755
+ const { canUndo, canRedo, undo, redo } = useMarkdownEditor();
2756
+ return /* @__PURE__ */ jsxs4(Fragment3, { children: [
2757
+ /* @__PURE__ */ jsx6(ToolbarButton, { label: "Undo", onClick: undo, disabled: !canUndo, children: /* @__PURE__ */ jsxs4(Icon, { children: [
2758
+ /* @__PURE__ */ jsx6("path", { d: "M7 6L3.5 9.5 7 13" }),
2759
+ /* @__PURE__ */ jsx6("path", { d: "M3.5 9.5H12a4 4 0 0 1 0 8h-1" })
2760
+ ] }) }),
2761
+ /* @__PURE__ */ jsx6(ToolbarButton, { label: "Redo", onClick: redo, disabled: !canRedo, children: /* @__PURE__ */ jsxs4(Icon, { children: [
2762
+ /* @__PURE__ */ jsx6("path", { d: "M13 6l3.5 3.5L13 13" }),
2763
+ /* @__PURE__ */ jsx6("path", { d: "M16.5 9.5H8a4 4 0 0 0 0 8h1" })
2764
+ ] }) })
2765
+ ] });
2766
+ }
2767
+
2768
+ // src/plugins/TableWidthPlugin.tsx
2769
+ import { jsx as jsx7 } from "react/jsx-runtime";
2770
+ function TableWidthPlugin() {
2771
+ const [editor] = useLexicalComposerContext9();
2772
+ const isEditable = useLexicalEditable2();
2773
+ const { tableWidth, setTableWidth } = useMarkdownEditor();
2774
+ const [anchor, setAnchor] = useState5(null);
2775
+ const sync = useCallback5(() => {
2776
+ const tableKey = editor.getEditorState().read(() => $getSelectedTable()?.getKey());
2777
+ const element = tableKey ? editor.getElementByKey(tableKey) : null;
2778
+ const main = editor.getRootElement()?.parentElement;
2779
+ if (!element || !main) {
2780
+ setAnchor(null);
2781
+ return;
2782
+ }
2783
+ const rect = element.getBoundingClientRect();
2784
+ const mainRect = main.getBoundingClientRect();
2785
+ setAnchor({
2786
+ top: rect.top - mainRect.top,
2787
+ right: mainRect.right - rect.right + 8
2788
+ });
2789
+ }, [editor]);
2790
+ useEffect9(() => {
2791
+ sync();
2792
+ const unregister = editor.registerUpdateListener(sync);
2793
+ const rootElement = editor.getRootElement();
2794
+ const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(sync) : null;
2795
+ if (rootElement && observer) observer.observe(rootElement);
2796
+ return () => {
2797
+ unregister();
2798
+ observer?.disconnect();
2799
+ };
2800
+ }, [editor, sync]);
2801
+ if (!isEditable || !anchor || !tableWidth) return null;
2802
+ return /* @__PURE__ */ jsx7(
2803
+ "div",
2804
+ {
2805
+ className: "zui-table-width",
2806
+ role: "group",
2807
+ "aria-label": "Table width",
2808
+ style: { top: anchor.top, right: anchor.right },
2809
+ children: BLOCK_WIDTH_OPTIONS.map(({ width, label, icon }) => /* @__PURE__ */ jsx7(
2810
+ ToolbarButton,
2811
+ {
2812
+ label,
2813
+ active: tableWidth === width,
2814
+ onClick: () => setTableWidth(width),
2815
+ children: /* @__PURE__ */ jsx7(
2816
+ "svg",
2817
+ {
2818
+ viewBox: "0 0 20 20",
2819
+ width: "16",
2820
+ height: "16",
2821
+ fill: "none",
2822
+ stroke: "currentColor",
2823
+ strokeWidth: "1.6",
2824
+ strokeLinecap: "round",
2825
+ strokeLinejoin: "round",
2826
+ children: icon
2827
+ }
2828
+ )
2829
+ },
2830
+ width
2831
+ ))
2832
+ }
2833
+ );
2834
+ }
2835
+
2836
+ // src/EditorContent.tsx
2837
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
2838
+ function EditorContent({
2839
+ placeholder = "Start writing...",
2840
+ foldable = true,
2841
+ children
2842
+ }) {
2843
+ const { mode, readOnly, autoFocus, rawValue, onRawChange } = useEditorContext();
2844
+ if (mode === "edit-raw") {
2845
+ return /* @__PURE__ */ jsx8(
2846
+ "textarea",
2847
+ {
2848
+ className: "zui-text-editor-textarea",
2849
+ value: rawValue,
2850
+ onChange: (e) => onRawChange(e.target.value),
2851
+ placeholder,
2852
+ readOnly,
2853
+ autoFocus,
2854
+ spellCheck: false
2855
+ }
2856
+ );
2857
+ }
2858
+ return /* @__PURE__ */ jsxs5("div", { className: "zui-text-editor-body", children: [
2859
+ /* @__PURE__ */ jsxs5("div", { className: "zui-text-editor-main", children: [
2860
+ /* @__PURE__ */ jsx8(
2861
+ RichTextPlugin,
2862
+ {
2863
+ contentEditable: /* @__PURE__ */ jsx8(
2864
+ ContentEditable,
2865
+ {
2866
+ className: "zui-text-editor-content",
2867
+ "aria-placeholder": placeholder,
2868
+ placeholder: /* @__PURE__ */ jsx8("div", { className: "zui-text-editor-placeholder", children: placeholder })
2869
+ }
2870
+ ),
2871
+ ErrorBoundary: LexicalErrorBoundary
2872
+ }
2873
+ ),
2874
+ foldable && /* @__PURE__ */ jsx8(FoldingPlugin, {}),
2875
+ /* @__PURE__ */ jsx8(TableWidthPlugin, {})
2876
+ ] }),
2877
+ children
2878
+ ] });
2879
+ }
2880
+
2881
+ // src/plugins/OutlinePlugin.tsx
2882
+ import { useCallback as useCallback6, useEffect as useEffect10, useState as useState6 } from "react";
2883
+ import { useLexicalComposerContext as useLexicalComposerContext10 } from "@lexical/react/LexicalComposerContext";
2884
+ import {
2885
+ TableOfContentsPlugin
2886
+ } from "@lexical/react/LexicalTableOfContentsPlugin";
2887
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
2888
+ var INDENT_PER_LEVEL = {
2889
+ h1: 0,
2890
+ h2: 1,
2891
+ h3: 2,
2892
+ h4: 3,
2893
+ h5: 4,
2894
+ h6: 5
2895
+ };
2896
+ function OutlinePlugin() {
2897
+ const [editor] = useLexicalComposerContext10();
2898
+ const [collapsed, setCollapsed] = useState6(false);
2899
+ const [activeKey, setActiveKey] = useState6(null);
2900
+ useEffect10(() => {
2901
+ const rootElement = editor.getRootElement();
2902
+ const scroller = rootElement?.closest(".zui-text-editor");
2903
+ if (!rootElement || !(scroller instanceof HTMLElement)) return;
2904
+ let frame = 0;
2905
+ const updateActive = () => {
2906
+ cancelAnimationFrame(frame);
2907
+ frame = requestAnimationFrame(() => {
2908
+ const headings = rootElement.querySelectorAll(
2909
+ "h1, h2, h3, h4, h5, h6"
2910
+ );
2911
+ const threshold = scroller.scrollTop + 80;
2912
+ let current = null;
2913
+ for (const el of headings) {
2914
+ if (el.offsetTop <= threshold) {
2915
+ current = el.getAttribute("data-outline-key");
2916
+ }
2917
+ }
2918
+ setActiveKey(current);
2919
+ });
2920
+ };
2921
+ updateActive();
2922
+ scroller.addEventListener("scroll", updateActive, { passive: true });
2923
+ const unregister = editor.registerUpdateListener(updateActive);
2924
+ return () => {
2925
+ cancelAnimationFrame(frame);
2926
+ scroller.removeEventListener("scroll", updateActive);
2927
+ unregister();
2928
+ };
2929
+ }, [editor]);
2930
+ const scrollTo = useCallback6(
2931
+ (key) => {
2932
+ editor.getEditorState().read(() => {
2933
+ const element = editor.getElementByKey(key);
2934
+ element?.scrollIntoView({ behavior: "smooth", block: "start" });
2935
+ });
2936
+ },
2937
+ [editor]
2938
+ );
2939
+ return /* @__PURE__ */ jsx9(TableOfContentsPlugin, { children: (entries) => {
2940
+ for (const [key] of entries) {
2941
+ editor.getElementByKey(key)?.setAttribute("data-outline-key", key);
2942
+ }
2943
+ return /* @__PURE__ */ jsxs6(
2944
+ "div",
2945
+ {
2946
+ className: `zui-text-editor-outline ${collapsed ? "is-collapsed" : ""}`,
2947
+ children: [
2948
+ /* @__PURE__ */ jsx9(
2949
+ "button",
2950
+ {
2951
+ type: "button",
2952
+ className: "zui-text-editor-outline-toggle",
2953
+ title: collapsed ? "Show outline" : "Hide outline",
2954
+ "aria-label": collapsed ? "Show outline" : "Hide outline",
2955
+ "aria-expanded": !collapsed,
2956
+ onClick: () => setCollapsed((c) => !c),
2957
+ children: /* @__PURE__ */ jsx9(
2958
+ "svg",
2959
+ {
2960
+ viewBox: "0 0 20 20",
2961
+ width: "14",
2962
+ height: "14",
2963
+ fill: "none",
2964
+ stroke: "currentColor",
2965
+ strokeWidth: "1.8",
2966
+ strokeLinecap: "round",
2967
+ children: /* @__PURE__ */ jsx9("path", { d: "M4 5h12M4 10h8M4 15h10" })
2968
+ }
2969
+ )
2970
+ }
2971
+ ),
2972
+ !collapsed && /* @__PURE__ */ jsxs6("nav", { className: "zui-text-editor-outline-list", "aria-label": "Table of contents", children: [
2973
+ entries.length === 0 && /* @__PURE__ */ jsx9("div", { className: "zui-text-editor-outline-empty", children: "No headings" }),
2974
+ entries.map(([key, text, tag]) => /* @__PURE__ */ jsx9(
2975
+ "button",
2976
+ {
2977
+ type: "button",
2978
+ className: `zui-text-editor-outline-item ${key === activeKey ? "is-active" : ""}`,
2979
+ style: {
2980
+ paddingLeft: `${0.5 + INDENT_PER_LEVEL[tag] * 0.75}rem`
2981
+ },
2982
+ onClick: () => scrollTo(key),
2983
+ children: text || "Untitled"
2984
+ },
2985
+ key
2986
+ ))
2987
+ ] })
2988
+ ]
2989
+ }
2990
+ );
2991
+ } });
2992
+ }
2993
+
2994
+ // src/MarkdownEditor.tsx
2995
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
2996
+ var DEFAULT_ITEMS = {
2997
+ format: /* @__PURE__ */ jsx10(FormatButtons, {}),
2998
+ insert: /* @__PURE__ */ jsx10(InsertButtons, {}),
2999
+ history: /* @__PURE__ */ jsx10(HistoryButtons, {})
3000
+ };
3001
+ function EditorToolbar({
3002
+ children,
3003
+ className
3004
+ }) {
3005
+ const { mode, readOnly } = useEditorContext();
3006
+ if (mode !== "edit-md" || readOnly) return null;
3007
+ return /* @__PURE__ */ jsx10(Toolbar, { className, children });
3008
+ }
3009
+ function MarkdownEditor({
3010
+ placeholder,
3011
+ toolbar = true,
3012
+ outline = false,
3013
+ foldable = true,
3014
+ ...rootProps
3015
+ }) {
3016
+ return /* @__PURE__ */ jsxs7(EditorRoot, { ...rootProps, children: [
3017
+ toolbar === true && /* @__PURE__ */ jsx10(EditorToolbar, {}),
3018
+ typeof toolbar === "function" && toolbar(DEFAULT_ITEMS),
3019
+ /* @__PURE__ */ jsx10(EditorContent, { placeholder, foldable, children: outline && /* @__PURE__ */ jsx10(OutlinePlugin, {}) })
3020
+ ] });
3021
+ }
3022
+ MarkdownEditor.Root = EditorRoot;
3023
+ MarkdownEditor.Content = EditorContent;
3024
+ MarkdownEditor.Toolbar = EditorToolbar;
3025
+ MarkdownEditor.ToolbarButton = ToolbarButton;
3026
+ MarkdownEditor.ToolbarDivider = ToolbarDivider;
3027
+ MarkdownEditor.FormatButtons = FormatButtons;
3028
+ MarkdownEditor.InsertButtons = InsertButtons;
3029
+ MarkdownEditor.HistoryButtons = HistoryButtons;
3030
+ MarkdownEditor.Outline = OutlinePlugin;
3031
+ MarkdownEditor.useEditor = useMarkdownEditor;
3032
+ var MarkdownEditor_default = MarkdownEditor;
3033
+
3034
+ // src/components/drawingSchema.ts
3035
+ var DRAWING_DATA_JSON_SCHEMA = {
3036
+ $schema: "http://json-schema.org/draft-07/schema#",
3037
+ title: "DrawingData",
3038
+ description: "Vector drawing embedded in markdown as a ```drawing fenced code block",
3039
+ type: "object",
3040
+ required: ["version", "height", "shapes"],
3041
+ additionalProperties: false,
3042
+ properties: {
3043
+ version: { const: 1 },
3044
+ height: {
2704
3045
  type: "number",
2705
3046
  minimum: 80,
2706
- description: "Canvas height in pixels (width is fluid)"
3047
+ description: "Canvas height in pixels"
3048
+ },
3049
+ width: {
3050
+ enum: ["full", "content"],
3051
+ description: `Canvas width: "full" (default) spans the editor; "content" fits the shapes' horizontal extent and left-aligns with the text`
2707
3052
  },
2708
3053
  shapes: {
2709
3054
  type: "array",
@@ -2801,15 +3146,26 @@ var DRAWING_DATA_JSON_SCHEMA = {
2801
3146
  export {
2802
3147
  $createDrawingNode,
2803
3148
  $createFrontmatterNode,
3149
+ $getSelectedTable,
3150
+ $getTableWidth,
2804
3151
  $isDrawingNode,
2805
3152
  $isFrontmatterNode,
3153
+ $setTableWidth,
2806
3154
  DRAWING,
2807
3155
  DRAWING_DATA_JSON_SCHEMA,
2808
3156
  DrawingNode,
2809
3157
  FRONTMATTER,
3158
+ FormatButtons,
2810
3159
  FrontmatterNode,
2811
- MarkdownEditor,
3160
+ HistoryButtons,
3161
+ InsertButtons,
3162
+ MarkdownEditor_default as MarkdownEditor,
2812
3163
  TABLE,
3164
+ TABLE_WIDTH_MARKER,
3165
+ Toolbar,
3166
+ ToolbarButton,
3167
+ ToolbarDivider,
2813
3168
  parseDrawingData,
2814
- serializeDrawingData
3169
+ serializeDrawingData,
3170
+ useMarkdownEditor
2815
3171
  };