@zuilib/text-editor 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,8 +1,6 @@
1
1
  // src/EditorRoot.tsx
2
2
  import {
3
- createContext,
4
3
  useCallback as useCallback2,
5
- useContext,
6
4
  useEffect as useEffect8,
7
5
  useMemo as useMemo2,
8
6
  useRef as useRef5,
@@ -308,6 +306,18 @@ import { $getNodeByKey as $getNodeByKey2 } from "lexical";
308
306
  import { useLexicalComposerContext as useLexicalComposerContext5 } from "@lexical/react/LexicalComposerContext";
309
307
  import { useLexicalEditable } from "@lexical/react/useLexicalEditable";
310
308
 
309
+ // src/drawing/focusCommand.ts
310
+ import { createCommand } from "lexical";
311
+ var DRAWING_FOCUS_COMMAND = createCommand(
312
+ "DRAWING_FOCUS_COMMAND"
313
+ );
314
+
315
+ // src/blockWidth.ts
316
+ var BLOCK_WIDTHS = ["full", "text", "content"];
317
+ function isBlockWidth(value) {
318
+ return typeof value === "string" && BLOCK_WIDTHS.includes(value);
319
+ }
320
+
311
321
  // src/drawing/types.ts
312
322
  var BOX_TYPES = [
313
323
  "rect",
@@ -393,8 +403,9 @@ function normalizeDrawingData(parsed) {
393
403
  version: 2,
394
404
  canvasHeight: Number.isFinite(height) && height >= MIN_CANVAS_HEIGHT ? height : DEFAULT_CANVAS_HEIGHT,
395
405
  ...Number.isFinite(canvasWidth) && canvasWidth >= 120 ? { canvasWidth } : {},
396
- // `full` is the default and stays implicit so payloads stay compact
397
- ...parsed.width === "content" ? { width: "content" } : {},
406
+ // `full` stays implicit unless the payload spells it out; unknown
407
+ // values (newer versions) are dropped rather than rejected
408
+ ...isBlockWidth(parsed.width) ? { width: parsed.width } : {},
398
409
  // Bindings to ids that don't exist (or aren't boxes) are dropped
399
410
  shapes: shapes.map((shape) => {
400
411
  if (!isConnectorType(shape.type)) return shape;
@@ -1641,7 +1652,6 @@ function PropertyBar({
1641
1652
 
1642
1653
  // src/drawing/canvas/SelectionOverlay.tsx
1643
1654
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1644
- var SELECTION_COLOR = "#6965db";
1645
1655
  var HANDLE = 7;
1646
1656
  function Handle({
1647
1657
  x,
@@ -1653,8 +1663,6 @@ function Handle({
1653
1663
  }) {
1654
1664
  const props = {
1655
1665
  className: "zui-drawing-handle",
1656
- fill: "#fff",
1657
- stroke: SELECTION_COLOR,
1658
1666
  strokeWidth: 1.5,
1659
1667
  style: { cursor },
1660
1668
  onPointerDown
@@ -1683,12 +1691,10 @@ function SelectionOverlay({
1683
1691
  ghosts.map(({ index, x, y }) => /* @__PURE__ */ jsx3(
1684
1692
  "circle",
1685
1693
  {
1686
- className: "zui-drawing-ghost",
1694
+ className: "zui-drawing-handle zui-drawing-ghost",
1687
1695
  cx: x,
1688
1696
  cy: y,
1689
1697
  r: 4.5,
1690
- fill: "#fff",
1691
- stroke: SELECTION_COLOR,
1692
1698
  strokeWidth: 1.5,
1693
1699
  strokeDasharray: "2 2",
1694
1700
  style: { cursor: "copy" },
@@ -1784,7 +1790,7 @@ function SelectionFrame({ rect, pad }) {
1784
1790
  height: rect.h + pad * 2,
1785
1791
  rx: 6,
1786
1792
  fill: "none",
1787
- stroke: SELECTION_COLOR,
1793
+ stroke: "currentColor",
1788
1794
  strokeWidth: 1,
1789
1795
  strokeDasharray: "5 4",
1790
1796
  pointerEvents: "none"
@@ -1815,7 +1821,7 @@ function GroupSelectionOverlay({
1815
1821
  height: r.h + 6,
1816
1822
  rx: 4,
1817
1823
  fill: "none",
1818
- stroke: SELECTION_COLOR,
1824
+ stroke: "currentColor",
1819
1825
  strokeWidth: 1,
1820
1826
  opacity: 0.45
1821
1827
  },
@@ -1828,14 +1834,15 @@ function MarqueeOverlay({ rect }) {
1828
1834
  return /* @__PURE__ */ jsx3(
1829
1835
  "rect",
1830
1836
  {
1837
+ className: "zui-drawing-selection",
1831
1838
  x: rect.x,
1832
1839
  y: rect.y,
1833
1840
  width: rect.w,
1834
1841
  height: rect.h,
1835
1842
  rx: 3,
1836
- fill: SELECTION_COLOR,
1843
+ fill: "currentColor",
1837
1844
  fillOpacity: 0.08,
1838
- stroke: SELECTION_COLOR,
1845
+ stroke: "currentColor",
1839
1846
  strokeWidth: 1,
1840
1847
  pointerEvents: "none"
1841
1848
  }
@@ -1989,7 +1996,6 @@ function BoxGeometry({
1989
1996
  // src/drawing/canvas/ShapeView.tsx
1990
1997
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1991
1998
  import { createElement } from "react";
1992
- var FONT_FAMILY = "ui-sans-serif, system-ui, sans-serif";
1993
1999
  var CONNECTOR_FONT_SIZE = 12;
1994
2000
  var CONNECTOR_LABEL_MAX_WIDTH = 160;
1995
2001
  function slotLayout(shape) {
@@ -2025,7 +2031,6 @@ function BoxTexts({
2025
2031
  const mainStart = area.y + area.h / 2 - (mainLines.length - 1) * mainLineH / 2 + FONT_SIZE * 0.35;
2026
2032
  const common = {
2027
2033
  fill: shape.stroke,
2028
- fontFamily: FONT_FAMILY,
2029
2034
  textAnchor: "middle"
2030
2035
  };
2031
2036
  return /* @__PURE__ */ jsxs5("g", { children: [
@@ -2123,7 +2128,7 @@ function ConnectorLabel({
2123
2128
  width: size.w + 10,
2124
2129
  height: size.h + 6,
2125
2130
  rx: 5,
2126
- fill: "#ffffff",
2131
+ className: "zui-drawing-label-plate",
2127
2132
  opacity: 0.94
2128
2133
  }
2129
2134
  ),
@@ -2134,7 +2139,6 @@ function ConnectorLabel({
2134
2139
  y: startY + i * lineH,
2135
2140
  fill: shape.stroke,
2136
2141
  fontSize: CONNECTOR_FONT_SIZE,
2137
- fontFamily: FONT_FAMILY,
2138
2142
  textAnchor: "middle",
2139
2143
  style: textStyle,
2140
2144
  children: line
@@ -2179,7 +2183,6 @@ function ShapeView({
2179
2183
  y: shape.y + FONT_SIZE,
2180
2184
  fill: shape.stroke,
2181
2185
  fontSize: FONT_SIZE,
2182
- fontFamily: FONT_FAMILY,
2183
2186
  style: { userSelect: "none", whiteSpace: "pre" },
2184
2187
  children: (shape.text ?? "").split("\n").map((line, i) => /* @__PURE__ */ jsx5("tspan", { x: shape.x, dy: i === 0 ? 0 : FONT_SIZE * LINE_HEIGHT, children: line }, i))
2185
2188
  }
@@ -2251,7 +2254,7 @@ function TextEditOverlay({
2251
2254
  }, []);
2252
2255
  const style = {
2253
2256
  color: shape.stroke,
2254
- fontFamily: FONT_FAMILY,
2257
+ fontFamily: "var(--zui-drawing-font)",
2255
2258
  lineHeight: LINE_HEIGHT
2256
2259
  };
2257
2260
  if (shape.type === "text") {
@@ -2335,6 +2338,14 @@ var BLOCK_WIDTH_OPTIONS = [
2335
2338
  /* @__PURE__ */ jsx7("path", { d: "M6 10h8M8.5 7.5L6 10l2.5 2.5M11.5 7.5L14 10l-2.5 2.5" })
2336
2339
  ] })
2337
2340
  },
2341
+ {
2342
+ width: "text",
2343
+ label: "Text width",
2344
+ icon: /* @__PURE__ */ jsxs6(Fragment, { children: [
2345
+ /* @__PURE__ */ jsx7("path", { d: "M3 4v12M17 4v12" }),
2346
+ /* @__PURE__ */ jsx7("path", { d: "M6.5 7h7M6.5 10h7M6.5 13h4.5" })
2347
+ ] })
2348
+ },
2338
2349
  {
2339
2350
  width: "content",
2340
2351
  label: "Content width",
@@ -2482,6 +2493,11 @@ function DrawingToolbar({
2482
2493
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2483
2494
  var MIN_HEIGHT = 120;
2484
2495
  var MAX_HEIGHT = 1200;
2496
+ var WIDTH_CLASS = {
2497
+ full: "",
2498
+ text: "is-text-width",
2499
+ content: "is-content-width"
2500
+ };
2485
2501
  var MIN_CONTENT_WIDTH = 240;
2486
2502
  var CONTENT_WIDTH_PADDING = 40;
2487
2503
  var EMPTY_SET = /* @__PURE__ */ new Set();
@@ -2564,11 +2580,12 @@ function DrawingCanvas({ nodeKey, data }) {
2564
2580
  const commit = useCallback(
2565
2581
  (nextShapes, options) => {
2566
2582
  const nextWidth = options?.width ?? widthRef.current;
2583
+ const explicitFull = options?.width === void 0 && data.width !== void 0;
2567
2584
  const payload = {
2568
2585
  version: 2,
2569
2586
  ...canvasWidth ? { canvasWidth } : {},
2570
2587
  canvasHeight: options?.height ?? heightRef.current,
2571
- ...nextWidth === "content" ? { width: "content" } : {},
2588
+ ...nextWidth !== "full" || explicitFull ? { width: nextWidth } : {},
2572
2589
  shapes: nextShapes
2573
2590
  };
2574
2591
  const json = serializeDrawingData(payload);
@@ -2579,7 +2596,7 @@ function DrawingCanvas({ nodeKey, data }) {
2579
2596
  if ($isDrawingNode(node)) node.setData(payload);
2580
2597
  });
2581
2598
  },
2582
- [editor, nodeKey, canvasWidth]
2599
+ [editor, nodeKey, canvasWidth, data.width]
2583
2600
  );
2584
2601
  const updateShapes = useCallback(
2585
2602
  (updater, options) => {
@@ -2965,8 +2982,13 @@ function DrawingCanvas({ nodeKey, data }) {
2965
2982
  "div",
2966
2983
  {
2967
2984
  ref: rootRef,
2968
- className: `zui-drawing-canvas ${width === "content" ? "is-content-width" : ""} ${isEditable ? "is-editable" : ""}`,
2985
+ className: `zui-drawing-canvas ${WIDTH_CLASS[width]} ${isEditable ? "is-editable" : ""}`,
2969
2986
  tabIndex: isEditable ? 0 : void 0,
2987
+ onFocus: () => editor.dispatchCommand(DRAWING_FOCUS_COMMAND, nodeKey),
2988
+ onBlur: (e) => {
2989
+ if (rootRef.current?.contains(e.relatedTarget)) return;
2990
+ editor.dispatchCommand(DRAWING_FOCUS_COMMAND, null);
2991
+ },
2970
2992
  children: [
2971
2993
  isEditable && /* @__PURE__ */ jsxs8("div", { className: "zui-drawing-toolbar", onPointerDown: (e) => e.stopPropagation(), children: [
2972
2994
  /* @__PURE__ */ jsx9(
@@ -2997,118 +3019,120 @@ function DrawingCanvas({ nodeKey, data }) {
2997
3019
  }
2998
3020
  ) })
2999
3021
  ] }),
3000
- /* @__PURE__ */ jsxs8(
3001
- "svg",
3002
- {
3003
- ref: svgRef,
3004
- className: "zui-drawing-surface",
3005
- style: {
3006
- height: logicalWidth ? void 0 : canvasHeight,
3007
- width: logicalWidth ?? void 0,
3008
- maxWidth: "100%",
3009
- aspectRatio: logicalWidth ? `${logicalWidth} / ${canvasHeight}` : void 0,
3010
- cursor: canvasCursor
3011
- },
3012
- viewBox: logicalWidth ? `0 0 ${logicalWidth} ${canvasHeight}` : void 0,
3013
- preserveAspectRatio: "xMinYMin meet",
3014
- onPointerDown: handleBackgroundPointerDown,
3015
- onPointerMove: handlePointerMove,
3016
- onPointerUp: handlePointerUp,
3017
- onPointerCancel: handlePointerUp,
3018
- onDoubleClick: (e) => {
3019
- if (!isEditable) return;
3020
- const target = e.target.closest("[data-shape-id]");
3021
- const id = target?.getAttribute("data-shape-id");
3022
- const shape = id ? shapes.find((s) => s.id === id) : null;
3023
- if (!shape) return;
3024
- if (shape.type === "text" || isConnectorType(shape.type)) {
3025
- startTextEditing(shape.id, "text");
3026
- } else {
3027
- startTextEditing(shape.id, slotAt(shape, getPoint(e).y));
3028
- }
3029
- },
3030
- children: [
3031
- shapes.map((shape) => /* @__PURE__ */ jsxs8(
3032
- "g",
3033
- {
3034
- "data-shape-id": shape.id,
3035
- className: `zui-drawing-shape ${selectedIds.has(shape.id) ? "is-selected" : ""}`,
3036
- style: { cursor: isEditable && tool === "select" ? "move" : void 0 },
3037
- onPointerDown: (e) => handleShapePointerDown(e, shape),
3038
- children: [
3039
- /* @__PURE__ */ jsx9(HitArea, { shape, points: paths.get(shape.id) }),
3040
- shape.id === editingText?.id && shape.type === "text" ? null : /* @__PURE__ */ jsx9(
3041
- ShapeView,
3042
- {
3043
- shape,
3044
- points: paths.get(shape.id),
3045
- hideField: shape.id === editingText?.id ? editingText.field : null,
3046
- showHints: isEditable && tool === "select" && single?.id === shape.id && shape.id !== editingText?.id
3047
- }
3048
- )
3049
- ]
3050
- },
3051
- shape.id
3052
- )),
3053
- hoverBox && /* @__PURE__ */ jsx9(
3054
- "polygon",
3055
- {
3056
- className: "zui-drawing-bind-target",
3057
- points: boxOutline(hoverBox).map((p) => `${p.x},${p.y}`).join(" "),
3058
- fill: SELECTION_COLOR,
3059
- fillOpacity: 0.06,
3060
- stroke: SELECTION_COLOR,
3061
- strokeWidth: 2,
3062
- strokeLinejoin: "round",
3063
- pointerEvents: "none"
3064
- }
3065
- ),
3066
- single && isEditable && !editingText && /* @__PURE__ */ jsx9(
3067
- SelectionOverlay,
3068
- {
3069
- shape: single,
3070
- points: paths.get(single.id) ?? [],
3071
- onHandlePointerDown: handleHandlePointerDown,
3072
- onWaypointAdd: addWaypoint,
3073
- onWaypointRemove: removeWaypoint
3022
+ /* @__PURE__ */ jsxs8("div", { className: "zui-drawing-stage", children: [
3023
+ /* @__PURE__ */ jsxs8(
3024
+ "svg",
3025
+ {
3026
+ ref: svgRef,
3027
+ className: "zui-drawing-surface",
3028
+ style: {
3029
+ height: logicalWidth ? void 0 : canvasHeight,
3030
+ width: logicalWidth ?? void 0,
3031
+ maxWidth: "100%",
3032
+ aspectRatio: logicalWidth ? `${logicalWidth} / ${canvasHeight}` : void 0,
3033
+ cursor: canvasCursor
3034
+ },
3035
+ viewBox: logicalWidth ? `0 0 ${logicalWidth} ${canvasHeight}` : void 0,
3036
+ preserveAspectRatio: "xMinYMin meet",
3037
+ onPointerDown: handleBackgroundPointerDown,
3038
+ onPointerMove: handlePointerMove,
3039
+ onPointerUp: handlePointerUp,
3040
+ onPointerCancel: handlePointerUp,
3041
+ onDoubleClick: (e) => {
3042
+ if (!isEditable) return;
3043
+ const target = e.target.closest("[data-shape-id]");
3044
+ const id = target?.getAttribute("data-shape-id");
3045
+ const shape = id ? shapes.find((s) => s.id === id) : null;
3046
+ if (!shape) return;
3047
+ if (shape.type === "text" || isConnectorType(shape.type)) {
3048
+ startTextEditing(shape.id, "text");
3049
+ } else {
3050
+ startTextEditing(shape.id, slotAt(shape, getPoint(e).y));
3074
3051
  }
3075
- ),
3076
- selection.length > 1 && isEditable && /* @__PURE__ */ jsx9(GroupSelectionOverlay, { shapes: selection, paths }),
3077
- marquee && /* @__PURE__ */ jsx9(MarqueeOverlay, { rect: marqueeRect(marquee.origin, marquee.current) })
3078
- ]
3079
- }
3080
- ),
3081
- isEditable && shapes.length === 0 && /* @__PURE__ */ jsx9("div", { className: "zui-drawing-empty", "aria-hidden": "true", children: "Pick a shape above, then click or drag on the canvas" }),
3082
- editingShape && editingText && /* @__PURE__ */ jsx9(
3083
- "div",
3084
- {
3085
- className: "zui-drawing-overlay",
3086
- style: {
3087
- transform: `scale(${scale})`,
3088
- width: logicalWidth ?? void 0,
3089
- height: logicalWidth ? canvasHeight : void 0
3090
- },
3091
- children: /* @__PURE__ */ jsx9(
3092
- TextEditOverlay,
3093
- {
3094
- shape: editingShape,
3095
- field: editingText.field,
3096
- points: paths.get(editingShape.id),
3097
- onCommit: commitText
3098
3052
  },
3099
- `${editingShape.id}:${editingText.field}`
3100
- )
3101
- }
3102
- ),
3103
- isEditable && /* @__PURE__ */ jsx9(
3104
- HeightHandle,
3105
- {
3106
- height: canvasHeight,
3107
- scale,
3108
- onChange: setCanvasHeight,
3109
- onCommit: (h) => commit(shapesRef.current, { height: h })
3110
- }
3111
- )
3053
+ children: [
3054
+ shapes.map((shape) => /* @__PURE__ */ jsxs8(
3055
+ "g",
3056
+ {
3057
+ "data-shape-id": shape.id,
3058
+ className: `zui-drawing-shape ${selectedIds.has(shape.id) ? "is-selected" : ""}`,
3059
+ style: { cursor: isEditable && tool === "select" ? "move" : void 0 },
3060
+ onPointerDown: (e) => handleShapePointerDown(e, shape),
3061
+ children: [
3062
+ /* @__PURE__ */ jsx9(HitArea, { shape, points: paths.get(shape.id) }),
3063
+ shape.id === editingText?.id && shape.type === "text" ? null : /* @__PURE__ */ jsx9(
3064
+ ShapeView,
3065
+ {
3066
+ shape,
3067
+ points: paths.get(shape.id),
3068
+ hideField: shape.id === editingText?.id ? editingText.field : null,
3069
+ showHints: isEditable && tool === "select" && single?.id === shape.id && shape.id !== editingText?.id
3070
+ }
3071
+ )
3072
+ ]
3073
+ },
3074
+ shape.id
3075
+ )),
3076
+ hoverBox && /* @__PURE__ */ jsx9(
3077
+ "polygon",
3078
+ {
3079
+ className: "zui-drawing-selection zui-drawing-bind-target",
3080
+ points: boxOutline(hoverBox).map((p) => `${p.x},${p.y}`).join(" "),
3081
+ fill: "currentColor",
3082
+ fillOpacity: 0.06,
3083
+ stroke: "currentColor",
3084
+ strokeWidth: 2,
3085
+ strokeLinejoin: "round",
3086
+ pointerEvents: "none"
3087
+ }
3088
+ ),
3089
+ single && isEditable && !editingText && /* @__PURE__ */ jsx9(
3090
+ SelectionOverlay,
3091
+ {
3092
+ shape: single,
3093
+ points: paths.get(single.id) ?? [],
3094
+ onHandlePointerDown: handleHandlePointerDown,
3095
+ onWaypointAdd: addWaypoint,
3096
+ onWaypointRemove: removeWaypoint
3097
+ }
3098
+ ),
3099
+ selection.length > 1 && isEditable && /* @__PURE__ */ jsx9(GroupSelectionOverlay, { shapes: selection, paths }),
3100
+ marquee && /* @__PURE__ */ jsx9(MarqueeOverlay, { rect: marqueeRect(marquee.origin, marquee.current) })
3101
+ ]
3102
+ }
3103
+ ),
3104
+ isEditable && shapes.length === 0 && /* @__PURE__ */ jsx9("div", { className: "zui-drawing-empty", "aria-hidden": "true", children: "Pick a shape above, then click or drag on the canvas" }),
3105
+ editingShape && editingText && /* @__PURE__ */ jsx9(
3106
+ "div",
3107
+ {
3108
+ className: "zui-drawing-overlay",
3109
+ style: {
3110
+ transform: `scale(${scale})`,
3111
+ width: logicalWidth ?? void 0,
3112
+ height: logicalWidth ? canvasHeight : void 0
3113
+ },
3114
+ children: /* @__PURE__ */ jsx9(
3115
+ TextEditOverlay,
3116
+ {
3117
+ shape: editingShape,
3118
+ field: editingText.field,
3119
+ points: paths.get(editingShape.id),
3120
+ onCommit: commitText
3121
+ },
3122
+ `${editingShape.id}:${editingText.field}`
3123
+ )
3124
+ }
3125
+ ),
3126
+ isEditable && /* @__PURE__ */ jsx9(
3127
+ HeightHandle,
3128
+ {
3129
+ height: canvasHeight,
3130
+ scale,
3131
+ onChange: setCanvasHeight,
3132
+ onCommit: (h) => commit(shapesRef.current, { height: h })
3133
+ }
3134
+ )
3135
+ ] })
3112
3136
  ]
3113
3137
  }
3114
3138
  );
@@ -3214,7 +3238,7 @@ function expandSkeleton(skeleton) {
3214
3238
  version: 2,
3215
3239
  ...skeleton.canvasWidth !== void 0 ? { canvasWidth: skeleton.canvasWidth } : {},
3216
3240
  canvasHeight: skeleton.canvasHeight ?? Math.max(MIN_AUTO_CANVAS_HEIGHT, Math.ceil(bottom + CANVAS_MARGIN)),
3217
- ...skeleton.width === "content" ? { width: "content" } : {},
3241
+ ...skeleton.width !== void 0 ? { width: skeleton.width } : {},
3218
3242
  shapes
3219
3243
  });
3220
3244
  }
@@ -3418,7 +3442,7 @@ function normalizeSkeleton(raw) {
3418
3442
  direction: value.direction === "down" ? "down" : "right",
3419
3443
  ...canvasWidth !== void 0 ? { canvasWidth } : {},
3420
3444
  ...canvasHeight !== void 0 ? { canvasHeight } : {},
3421
- ...value.width === "content" ? { width: "content" } : {},
3445
+ ...isBlockWidth(value.width) ? { width: value.width } : {},
3422
3446
  boxes,
3423
3447
  connectors,
3424
3448
  texts
@@ -3551,8 +3575,8 @@ var DRAWING_SKELETON_JSON_SCHEMA = {
3551
3575
  description: "Canvas height in px. Omit to fit the content"
3552
3576
  },
3553
3577
  width: {
3554
- enum: ["content"],
3555
- description: 'Block width: "content" fits the drawing to its shapes and left-aligns it with the text; omit to span the editor'
3578
+ enum: ["full", "text", "content"],
3579
+ description: 'Block width: "text" aligns the drawing with the text column (same as "full" unless the app sets a text measure); "content" fits the drawing to its shapes and left-aligns it with the text; omit to span the editor'
3556
3580
  },
3557
3581
  boxes: {
3558
3582
  type: "array",
@@ -3788,10 +3812,12 @@ var DEFAULT_TABLE_SETTINGS = {
3788
3812
  width: "full",
3789
3813
  density: "comfortable"
3790
3814
  };
3815
+ var BLOCK_BLEED_PROPERTY = "--zui-text-editor-block-bleed";
3791
3816
  var OPTION_DECLARATIONS = {
3792
3817
  width: {
3793
3818
  full: {},
3794
- content: { width: "auto", "table-layout": "auto" }
3819
+ content: { width: "auto", "table-layout": "auto", [BLOCK_BLEED_PROPERTY]: "0px" },
3820
+ text: { [BLOCK_BLEED_PROPERTY]: "0px" }
3795
3821
  },
3796
3822
  density: {
3797
3823
  comfortable: {},
@@ -3806,6 +3832,7 @@ var OPTION_DECLARATIONS = {
3806
3832
  }
3807
3833
  };
3808
3834
  var SETTING_KEYS = Object.keys(OPTION_DECLARATIONS);
3835
+ var EXPLICIT_WIDTH_PROPERTY = "--zui-table-width";
3809
3836
  function parseStyle(style) {
3810
3837
  const declarations = /* @__PURE__ */ new Map();
3811
3838
  for (const part of style.split(";")) {
@@ -3848,14 +3875,22 @@ function $getTableSettings(table) {
3848
3875
  density: readSetting(declarations, "density")
3849
3876
  };
3850
3877
  }
3851
- function $setTableSettings(table, settings) {
3878
+ function $setTableSettings(table, settings, options = {}) {
3852
3879
  const declarations = parseStyle(table.getStyle());
3853
3880
  for (const key of SETTING_KEYS) {
3854
3881
  const option = settings[key];
3855
3882
  if (option !== void 0) writeSetting(declarations, key, option);
3856
3883
  }
3884
+ if (settings.width !== void 0) {
3885
+ if (options.explicitWidth) declarations.set(EXPLICIT_WIDTH_PROPERTY, settings.width);
3886
+ else declarations.delete(EXPLICIT_WIDTH_PROPERTY);
3887
+ }
3857
3888
  table.setStyle(serializeStyle(declarations));
3858
3889
  }
3890
+ function $isTableWidthExplicit(table) {
3891
+ const declarations = parseStyle(table.getStyle());
3892
+ return declarations.get(EXPLICIT_WIDTH_PROPERTY) === readSetting(declarations, "width");
3893
+ }
3859
3894
  function $getTableWidth(table) {
3860
3895
  return $getTableSettings(table).width;
3861
3896
  }
@@ -3890,16 +3925,19 @@ function parseTableSettingsMarker(text) {
3890
3925
  const match = MARKER_REG_EXP.exec(text);
3891
3926
  if (!match) return null;
3892
3927
  const settings = {};
3928
+ let recognised = false;
3893
3929
  for (const pair of match[1].split(";")) {
3894
3930
  const [key, value] = pair.split(":").map((s) => s.trim());
3931
+ if (key !== "width" && key !== "density") continue;
3932
+ recognised = true;
3895
3933
  if (key === "width" && isOption("width", value)) settings.width = value;
3896
3934
  if (key === "density" && isOption("density", value)) settings.density = value;
3897
3935
  }
3898
- return Object.keys(settings).length > 0 ? settings : null;
3936
+ return recognised ? settings : null;
3899
3937
  }
3900
- function formatTableSettingsMarker(settings) {
3938
+ function formatTableSettingsMarker(settings, options = {}) {
3901
3939
  const pairs = SETTING_KEYS.filter(
3902
- (key) => settings[key] !== DEFAULT_TABLE_SETTINGS[key]
3940
+ (key) => settings[key] !== DEFAULT_TABLE_SETTINGS[key] || key === "width" && options.explicitWidth
3903
3941
  ).map((key) => `${key}: ${settings[key]}`);
3904
3942
  return pairs.length > 0 ? `<!-- ${pairs.join("; ")} -->` : null;
3905
3943
  }
@@ -3932,7 +3970,9 @@ var TABLE = {
3932
3970
  export: (node) => {
3933
3971
  if (!$isTableNode2(node)) return null;
3934
3972
  const output = [];
3935
- const marker = formatTableSettingsMarker($getTableSettings(node));
3973
+ const marker = formatTableSettingsMarker($getTableSettings(node), {
3974
+ explicitWidth: $isTableWidthExplicit(node)
3975
+ });
3936
3976
  if (marker) output.push(marker);
3937
3977
  for (const row of node.getChildren()) {
3938
3978
  if (!$isTableRowNode(row)) continue;
@@ -4001,7 +4041,7 @@ var TABLE = {
4001
4041
  } else {
4002
4042
  const markerSettings = $getMarkerSettings(previousSibling);
4003
4043
  if (markerSettings) {
4004
- $setTableSettings(table, markerSettings);
4044
+ $setTableSettings(table, markerSettings, { explicitWidth: true });
4005
4045
  previousSibling?.remove();
4006
4046
  }
4007
4047
  parentNode.replace(table);
@@ -4016,14 +4056,14 @@ var editorTheme = {
4016
4056
  ltr: "text-left",
4017
4057
  rtl: "text-right",
4018
4058
  placeholder: "text-muted-foreground opacity-50",
4019
- paragraph: "mb-2",
4059
+ paragraph: "zui-paragraph mb-2",
4020
4060
  heading: {
4021
- h1: "text-4xl font-bold mb-4",
4022
- h2: "text-3xl font-bold mb-3",
4023
- h3: "text-2xl font-bold mb-2",
4024
- h4: "text-xl font-bold mb-2",
4025
- h5: "text-lg font-bold mb-1",
4026
- h6: "text-base font-bold mb-1"
4061
+ h1: "zui-heading zui-heading-1 text-4xl font-bold mb-4",
4062
+ h2: "zui-heading zui-heading-2 text-3xl font-bold mb-3",
4063
+ h3: "zui-heading zui-heading-3 text-2xl font-bold mb-2",
4064
+ h4: "zui-heading zui-heading-4 text-xl font-bold mb-2",
4065
+ h5: "zui-heading zui-heading-5 text-lg font-bold mb-1",
4066
+ h6: "zui-heading zui-heading-6 text-base font-bold mb-1"
4027
4067
  },
4028
4068
  list: {
4029
4069
  listitem: "ml-8",
@@ -4033,8 +4073,8 @@ var editorTheme = {
4033
4073
  listitem: "ml-8"
4034
4074
  },
4035
4075
  checklist: "zui-checklist",
4036
- ol: "list-decimal ml-4",
4037
- ul: "list-disc ml-4"
4076
+ ol: "zui-list zui-list-ordered list-decimal ml-4",
4077
+ ul: "zui-list zui-list-unordered list-disc ml-4"
4038
4078
  },
4039
4079
  link: "text-primary underline hover:opacity-80 cursor-pointer",
4040
4080
  text: {
@@ -4047,7 +4087,7 @@ var editorTheme = {
4047
4087
  underline: "underline",
4048
4088
  underlineStrikethrough: "underline line-through"
4049
4089
  },
4050
- code: "bg-muted rounded-md p-4 font-mono text-sm block my-2 overflow-x-auto",
4090
+ code: "zui-code bg-muted rounded-md p-4 font-mono text-sm block my-2 overflow-x-auto",
4051
4091
  codeHighlight: {
4052
4092
  atrule: "text-blue-500",
4053
4093
  attr: "text-yellow-500",
@@ -4080,7 +4120,7 @@ var editorTheme = {
4080
4120
  url: "text-blue-500",
4081
4121
  variable: "text-blue-500"
4082
4122
  },
4083
- quote: "border-l-4 border-border pl-4 italic",
4123
+ quote: "zui-quote border-l-4 border-border pl-4 italic",
4084
4124
  frontmatter: "zui-frontmatter",
4085
4125
  table: "zui-table",
4086
4126
  tableCell: "zui-table-cell",
@@ -4090,10 +4130,8 @@ var editorTheme = {
4090
4130
  drawing: "zui-drawing"
4091
4131
  };
4092
4132
 
4093
- // src/EditorRoot.tsx
4094
- import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
4095
- var SYNC_TRANSFORMERS = [FRONTMATTER, DRAWING, DIAGRAM, CHECK_LIST, TABLE, ...TRANSFORMERS2];
4096
- var SHORTCUT_TRANSFORMERS = [TABLE, ...TRANSFORMERS2.filter((t) => t !== CODE)];
4133
+ // src/editorContext.ts
4134
+ import { createContext, useContext } from "react";
4097
4135
  var EditorContext = createContext(null);
4098
4136
  function useEditorContext() {
4099
4137
  const ctx = useContext(EditorContext);
@@ -4104,6 +4142,12 @@ function useEditorContext() {
4104
4142
  }
4105
4143
  return ctx;
4106
4144
  }
4145
+
4146
+ // src/EditorRoot.tsx
4147
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
4148
+ var SYNC_TRANSFORMERS = [FRONTMATTER, DRAWING, DIAGRAM, CHECK_LIST, TABLE, ...TRANSFORMERS2];
4149
+ var SHORTCUT_TRANSFORMERS = [TABLE, ...TRANSFORMERS2.filter((t) => t !== CODE)];
4150
+ var NO_DEFAULT_BLOCK_WIDTH = {};
4107
4151
  function onError(error) {
4108
4152
  console.error(error);
4109
4153
  }
@@ -4136,6 +4180,8 @@ function EditorRoot({
4136
4180
  className,
4137
4181
  mode = "edit-md",
4138
4182
  autoFocus = false,
4183
+ measure,
4184
+ defaultBlockWidth = NO_DEFAULT_BLOCK_WIDTH,
4139
4185
  children
4140
4186
  }) {
4141
4187
  const latestValueRef = useRef5(value ?? "");
@@ -4176,12 +4222,17 @@ function EditorRoot({
4176
4222
  readOnly,
4177
4223
  autoFocus,
4178
4224
  rawValue: value ?? "",
4179
- onRawChange: handleChange
4225
+ onRawChange: handleChange,
4226
+ defaultBlockWidth
4180
4227
  }),
4181
- [mode, readOnly, autoFocus, value, handleChange]
4228
+ [mode, readOnly, autoFocus, value, handleChange, defaultBlockWidth]
4182
4229
  );
4183
4230
  const isRaw = mode === "edit-raw";
4184
- return /* @__PURE__ */ jsx11(LexicalComposer, { initialConfig, children: /* @__PURE__ */ jsx11(EditorContext.Provider, { value: context, children: /* @__PURE__ */ jsxs9("div", { className: `zui-text-editor ${className ?? ""}`, children: [
4231
+ const rootStyle = useMemo2(
4232
+ () => measure !== void 0 ? { "--zui-text-editor-measure": measure } : void 0,
4233
+ [measure]
4234
+ );
4235
+ return /* @__PURE__ */ jsx11(LexicalComposer, { initialConfig, children: /* @__PURE__ */ jsx11(EditorContext.Provider, { value: context, children: /* @__PURE__ */ jsxs9("div", { className: `zui-text-editor ${className ?? ""}`, style: rootStyle, children: [
4185
4236
  children,
4186
4237
  !isRaw && /* @__PURE__ */ jsxs9(Fragment3, { children: [
4187
4238
  /* @__PURE__ */ jsx11(
@@ -4355,8 +4406,9 @@ import { useLexicalComposerContext as useLexicalComposerContext9 } from "@lexica
4355
4406
  import { useLexicalEditable as useLexicalEditable2 } from "@lexical/react/useLexicalEditable";
4356
4407
 
4357
4408
  // src/useMarkdownEditor.ts
4358
- import { useCallback as useCallback4, useEffect as useEffect10, useState as useState6 } from "react";
4409
+ import { useCallback as useCallback4, useEffect as useEffect10, useRef as useRef7, useState as useState6 } from "react";
4359
4410
  import {
4411
+ $getNodeByKey as $getNodeByKey4,
4360
4412
  $getSelection as $getSelection4,
4361
4413
  $isRangeSelection as $isRangeSelection4,
4362
4414
  CAN_REDO_COMMAND,
@@ -4376,20 +4428,29 @@ var TRACKED_FORMATS = [
4376
4428
  "strikethrough",
4377
4429
  "code"
4378
4430
  ];
4431
+ function $readDrawingWidth(key) {
4432
+ if (key === null) return null;
4433
+ const node = $getNodeByKey4(key);
4434
+ return $isDrawingNode(node) ? node.getData().width ?? "full" : null;
4435
+ }
4379
4436
  function useMarkdownEditor() {
4380
4437
  const [editor] = useLexicalComposerContext8();
4438
+ const { defaultBlockWidth } = useEditorContext();
4381
4439
  const [activeFormats, setActiveFormats] = useState6(
4382
4440
  /* @__PURE__ */ new Set()
4383
4441
  );
4384
4442
  const [canUndo, setCanUndo] = useState6(false);
4385
4443
  const [canRedo, setCanRedo] = useState6(false);
4386
4444
  const [tableSettings, setTableSettingsState] = useState6(null);
4445
+ const activeDrawingRef = useRef7(null);
4446
+ const [drawingWidth, setDrawingWidth] = useState6(null);
4387
4447
  useEffect10(
4388
4448
  () => mergeRegister(
4389
4449
  editor.registerUpdateListener(({ editorState }) => {
4390
4450
  editorState.read(() => {
4391
4451
  const table = $getSelectedTable();
4392
4452
  setTableSettingsState(table ? $getTableSettings(table) : null);
4453
+ setDrawingWidth($readDrawingWidth(activeDrawingRef.current));
4393
4454
  const selection = $getSelection4();
4394
4455
  if (!$isRangeSelection4(selection)) {
4395
4456
  setActiveFormats(/* @__PURE__ */ new Set());
@@ -4402,6 +4463,15 @@ function useMarkdownEditor() {
4402
4463
  setActiveFormats(next);
4403
4464
  });
4404
4465
  }),
4466
+ editor.registerCommand(
4467
+ DRAWING_FOCUS_COMMAND,
4468
+ (key) => {
4469
+ activeDrawingRef.current = key;
4470
+ setDrawingWidth(editor.getEditorState().read(() => $readDrawingWidth(key)));
4471
+ return false;
4472
+ },
4473
+ COMMAND_PRIORITY_LOW2
4474
+ ),
4405
4475
  editor.registerCommand(
4406
4476
  CAN_UNDO_COMMAND,
4407
4477
  (payload) => {
@@ -4428,20 +4498,36 @@ function useMarkdownEditor() {
4428
4498
  [editor]
4429
4499
  );
4430
4500
  const insertTable = useCallback4(
4431
- ({ rows = 3, columns = 3 } = {}) => {
4501
+ ({
4502
+ rows = 3,
4503
+ columns = 3,
4504
+ width = defaultBlockWidth.table
4505
+ } = {}) => {
4506
+ const before = editor.getEditorState().read(() => $getSelectedTable()?.getKey());
4432
4507
  editor.dispatchCommand(INSERT_TABLE_COMMAND, {
4433
4508
  columns: String(columns),
4434
4509
  rows: String(rows),
4435
4510
  includeHeaders: { rows: true, columns: false }
4436
4511
  });
4512
+ if (width === void 0) return;
4513
+ editor.update(() => {
4514
+ const table = $getSelectedTable();
4515
+ if (table && table.getKey() !== before) {
4516
+ $setTableSettings(table, { width }, { explicitWidth: true });
4517
+ }
4518
+ });
4437
4519
  },
4438
- [editor]
4520
+ [editor, defaultBlockWidth.table]
4521
+ );
4522
+ const insertDrawing = useCallback4(
4523
+ ({ width = defaultBlockWidth.drawing } = {}) => {
4524
+ editor.update(() => {
4525
+ const data = width === void 0 ? EMPTY_DRAWING : { version: 2, canvasHeight: EMPTY_DRAWING.canvasHeight, width, shapes: [] };
4526
+ $insertNodeToNearestRoot($createDrawingNode(serializeDrawingData(data)));
4527
+ });
4528
+ },
4529
+ [editor, defaultBlockWidth.drawing]
4439
4530
  );
4440
- const insertDrawing = useCallback4(() => {
4441
- editor.update(() => {
4442
- $insertNodeToNearestRoot($createDrawingNode());
4443
- });
4444
- }, [editor]);
4445
4531
  const updateTableSettings = useCallback4(
4446
4532
  (settings) => {
4447
4533
  editor.update(() => {
@@ -4459,6 +4545,22 @@ function useMarkdownEditor() {
4459
4545
  (density) => updateTableSettings({ density }),
4460
4546
  [updateTableSettings]
4461
4547
  );
4548
+ const setBlockWidth = useCallback4(
4549
+ (width) => {
4550
+ const key = activeDrawingRef.current;
4551
+ if (key === null) {
4552
+ updateTableSettings({ width });
4553
+ return;
4554
+ }
4555
+ editor.update(() => {
4556
+ const node = $getNodeByKey4(key);
4557
+ if (!$isDrawingNode(node)) return;
4558
+ const { width: _previous, ...data } = node.getData();
4559
+ node.setData(width === "full" ? data : { ...data, width });
4560
+ });
4561
+ },
4562
+ [editor, updateTableSettings]
4563
+ );
4462
4564
  const undo = useCallback4(() => {
4463
4565
  editor.dispatchCommand(UNDO_COMMAND, void 0);
4464
4566
  }, [editor]);
@@ -4471,6 +4573,8 @@ function useMarkdownEditor() {
4471
4573
  toggleFormat,
4472
4574
  insertTable,
4473
4575
  insertDrawing,
4576
+ blockWidth: drawingWidth ?? tableSettings?.width ?? null,
4577
+ setBlockWidth,
4474
4578
  tableWidth: tableSettings?.width ?? null,
4475
4579
  setTableWidth,
4476
4580
  tableDensity: tableSettings?.density ?? null,
@@ -4641,14 +4745,18 @@ function Icon3({ children }) {
4641
4745
  function TableSettingsPlugin() {
4642
4746
  const [editor] = useLexicalComposerContext9();
4643
4747
  const isEditable = useLexicalEditable2();
4644
- const { tableWidth, setTableWidth, tableDensity, setTableDensity } = useMarkdownEditor();
4645
4748
  const [anchor, setAnchor] = useState7(null);
4749
+ const [settings, setSettings] = useState7(null);
4646
4750
  const sync = useCallback5(() => {
4647
- const tableKey = editor.getEditorState().read(() => $getSelectedTable()?.getKey());
4648
- const element = tableKey ? editor.getElementByKey(tableKey) : null;
4751
+ const table = editor.getEditorState().read(() => {
4752
+ const node = $getSelectedTable();
4753
+ return node ? { key: node.getKey(), settings: $getTableSettings(node) } : null;
4754
+ });
4755
+ const element = table ? editor.getElementByKey(table.key) : null;
4649
4756
  const main = editor.getRootElement()?.parentElement;
4650
- if (!element || !main) {
4757
+ if (!table || !element || !main) {
4651
4758
  setAnchor(null);
4759
+ setSettings(null);
4652
4760
  return;
4653
4761
  }
4654
4762
  const rect = element.getBoundingClientRect();
@@ -4657,6 +4765,7 @@ function TableSettingsPlugin() {
4657
4765
  top: rect.top - mainRect.top,
4658
4766
  right: mainRect.right - rect.right + 8
4659
4767
  });
4768
+ setSettings(table.settings);
4660
4769
  }, [editor]);
4661
4770
  useEffect11(() => {
4662
4771
  sync();
@@ -4669,7 +4778,16 @@ function TableSettingsPlugin() {
4669
4778
  observer?.disconnect();
4670
4779
  };
4671
4780
  }, [editor, sync]);
4672
- if (!isEditable || !anchor || !tableWidth || !tableDensity) return null;
4781
+ const update = useCallback5(
4782
+ (next) => {
4783
+ editor.update(() => {
4784
+ const table = $getSelectedTable();
4785
+ if (table) $setTableSettings(table, next);
4786
+ });
4787
+ },
4788
+ [editor]
4789
+ );
4790
+ if (!isEditable || !anchor || !settings) return null;
4673
4791
  return /* @__PURE__ */ jsxs11(
4674
4792
  "div",
4675
4793
  {
@@ -4682,8 +4800,8 @@ function TableSettingsPlugin() {
4682
4800
  ToolbarButton,
4683
4801
  {
4684
4802
  label,
4685
- active: tableWidth === width,
4686
- onClick: () => setTableWidth(width),
4803
+ active: settings.width === width,
4804
+ onClick: () => update({ width }),
4687
4805
  children: /* @__PURE__ */ jsx14(Icon3, { children: icon })
4688
4806
  },
4689
4807
  width
@@ -4693,8 +4811,8 @@ function TableSettingsPlugin() {
4693
4811
  ToolbarButton,
4694
4812
  {
4695
4813
  label,
4696
- active: tableDensity === density,
4697
- onClick: () => setTableDensity(density),
4814
+ active: settings.density === density,
4815
+ onClick: () => update({ density }),
4698
4816
  children: /* @__PURE__ */ jsx14(Icon3, { children: icon })
4699
4817
  },
4700
4818
  density
@@ -4923,8 +5041,8 @@ var DRAWING_DATA_JSON_SCHEMA = {
4923
5041
  description: "Optional logical canvas width in pixels. When set, the drawing is scaled down to fit narrower layouts (SVG viewBox) so coordinates can assume this width. When omitted the canvas is fluid and coordinates are CSS pixels."
4924
5042
  },
4925
5043
  width: {
4926
- enum: ["full", "content"],
4927
- description: `Block width: "full" (default, omit it) spans the editor; "content" fits the shapes' horizontal extent and left-aligns with the text`
5044
+ enum: ["full", "text", "content"],
5045
+ description: `Block width: "full" (default, omit it) spans the editor; "text" aligns the block with the text column (same as "full" unless the app sets a text measure); "content" fits the shapes' horizontal extent and left-aligns with the text`
4928
5046
  },
4929
5047
  shapes: {
4930
5048
  type: "array",
@@ -5067,9 +5185,11 @@ export {
5067
5185
  $getTableWidth,
5068
5186
  $isDrawingNode,
5069
5187
  $isFrontmatterNode,
5188
+ $isTableWidthExplicit,
5070
5189
  $setTableDensity,
5071
5190
  $setTableSettings,
5072
5191
  $setTableWidth,
5192
+ BLOCK_WIDTHS,
5073
5193
  BOX_DEFINITIONS,
5074
5194
  BOX_TYPES,
5075
5195
  COLOR_PRESETS,
@@ -5105,6 +5225,7 @@ export {
5105
5225
  findBoxAt,
5106
5226
  fixedPointFor,
5107
5227
  formatTableSettingsMarker,
5228
+ isBlockWidth,
5108
5229
  isDrawingSkeleton,
5109
5230
  makeBinding,
5110
5231
  normalizeDrawingData,