@zuilib/text-editor 0.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog — @zuilib/text-editor
2
2
 
3
+ ## 0.7.0
4
+
5
+ **Themed drawing canvas.** The canvas chrome now follows the host theme
6
+ instead of shipping its own look.
7
+
8
+ - Tool row and contextual property row are a flat header in the editor's
9
+ own toolbar idiom (in flow, no floating panels over the drawing)
10
+ - Every color, radius, shadow and font of the canvas is a
11
+ `--zui-drawing-*` custom property defaulting to the core theme tokens
12
+ (`--primary`, `--foreground`, `--border`, `--popover`, `--radius`,
13
+ `--shadow-medium`, …), so light and dark mode come from the tokens;
14
+ override them on `.zui-drawing-canvas` or an ancestor. See README,
15
+ "Theming the canvas"
16
+ - Selection frame, handles, marquee, bind highlight and connector label
17
+ plates are drawn in `currentColor`/theme variables; the table selection
18
+ tint uses `--primary` too, so the editor shares one accent
19
+ - `--zui-drawing-dark-filter` exposes the dark-mode inversion of the
20
+ drawing area (`none` opts out)
21
+
3
22
  ## 0.6.0
4
23
 
5
24
  **Text measure and `text` block width.** Additive: markdown, JSON, class
package/README.md CHANGED
@@ -353,6 +353,41 @@ The full skeleton spec (attach sides, explicit positions, free texts) is
353
353
  in [DRAWING_FORMAT.md](./DRAWING_FORMAT.md). Programmatic access:
354
354
  `parseDrawingSkeleton`, `expandSkeleton`, `DRAWING_SKELETON_JSON_SCHEMA`.
355
355
 
356
+ ### Theming the canvas
357
+
358
+ The canvas chrome (header, property row, popover, selection handles) is
359
+ styled entirely through `--zui-drawing-*` custom properties. They default
360
+ to the ZUI core tokens (`--primary`, `--foreground`, `--border`,
361
+ `--popover`, `--radius`, `--shadow-medium`, …), so the canvas follows the
362
+ host theme in light and dark mode. Override any of them on
363
+ `.zui-drawing-canvas` or an ancestor:
364
+
365
+ ```css
366
+ .my-app .zui-drawing-canvas {
367
+ --zui-drawing-accent: #0f766e; /* selection, active tool, handles */
368
+ --zui-drawing-surface: #fbfbf7; /* drawing area (pre dark-mode filter) */
369
+ --zui-drawing-grid: rgba(0, 0, 0, 0.12);
370
+ --zui-drawing-grid-size: 16px;
371
+ --zui-drawing-font: 'Inter', sans-serif;
372
+ }
373
+ ```
374
+
375
+ | Variable | Default | Used for |
376
+ |----------|---------|----------|
377
+ | `--zui-drawing-accent` | `var(--primary)` | Selection frame and handles, active tool, focus ring, bind highlight |
378
+ | `--zui-drawing-foreground` | `var(--foreground)` | Header text and icons |
379
+ | `--zui-drawing-muted-foreground` | `var(--muted-foreground)` | Swatch labels, empty-state hint |
380
+ | `--zui-drawing-border` | `var(--border)` | Block border, header divider, popover border |
381
+ | `--zui-drawing-chrome` | `var(--background)` | Header and property row background |
382
+ | `--zui-drawing-popover` / `-foreground` | `var(--popover)` / `var(--popover-foreground)` | "More shapes" menu |
383
+ | `--zui-drawing-danger` / `--zui-drawing-success` | `var(--destructive)` / `var(--success)` | Delete button, "copied" state |
384
+ | `--zui-drawing-radius` / `--zui-drawing-control-radius` | `var(--radius-lg)` / `var(--radius)` | Block corners / buttons and menus |
385
+ | `--zui-drawing-shadow` | `var(--shadow-medium)` | Popover |
386
+ | `--zui-drawing-font` | system sans | Shape text and the inline editor |
387
+ | `--zui-drawing-surface` / `--zui-drawing-grid` / `--zui-drawing-grid-size` | white / 9% black / `20px` | Drawing area and its dot grid, before the dark filter |
388
+ | `--zui-drawing-dark-filter` | `invert(93%) hue-rotate(180deg)` | Applied to the drawing area under `.dark`; set to `none` to opt out |
389
+ | `--zui-drawing-hover` / `--zui-drawing-active` | `currentColor` 8% / 12% | Button hover and pressed backgrounds |
390
+
356
391
  ## Outline & section folding
357
392
 
358
393
  `outline` docks a collapsible table-of-contents sidebar: live heading list
package/dist/index.js CHANGED
@@ -1652,7 +1652,6 @@ function PropertyBar({
1652
1652
 
1653
1653
  // src/drawing/canvas/SelectionOverlay.tsx
1654
1654
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1655
- var SELECTION_COLOR = "#6965db";
1656
1655
  var HANDLE = 7;
1657
1656
  function Handle({
1658
1657
  x,
@@ -1664,8 +1663,6 @@ function Handle({
1664
1663
  }) {
1665
1664
  const props = {
1666
1665
  className: "zui-drawing-handle",
1667
- fill: "#fff",
1668
- stroke: SELECTION_COLOR,
1669
1666
  strokeWidth: 1.5,
1670
1667
  style: { cursor },
1671
1668
  onPointerDown
@@ -1694,12 +1691,10 @@ function SelectionOverlay({
1694
1691
  ghosts.map(({ index, x, y }) => /* @__PURE__ */ jsx3(
1695
1692
  "circle",
1696
1693
  {
1697
- className: "zui-drawing-ghost",
1694
+ className: "zui-drawing-handle zui-drawing-ghost",
1698
1695
  cx: x,
1699
1696
  cy: y,
1700
1697
  r: 4.5,
1701
- fill: "#fff",
1702
- stroke: SELECTION_COLOR,
1703
1698
  strokeWidth: 1.5,
1704
1699
  strokeDasharray: "2 2",
1705
1700
  style: { cursor: "copy" },
@@ -1795,7 +1790,7 @@ function SelectionFrame({ rect, pad }) {
1795
1790
  height: rect.h + pad * 2,
1796
1791
  rx: 6,
1797
1792
  fill: "none",
1798
- stroke: SELECTION_COLOR,
1793
+ stroke: "currentColor",
1799
1794
  strokeWidth: 1,
1800
1795
  strokeDasharray: "5 4",
1801
1796
  pointerEvents: "none"
@@ -1826,7 +1821,7 @@ function GroupSelectionOverlay({
1826
1821
  height: r.h + 6,
1827
1822
  rx: 4,
1828
1823
  fill: "none",
1829
- stroke: SELECTION_COLOR,
1824
+ stroke: "currentColor",
1830
1825
  strokeWidth: 1,
1831
1826
  opacity: 0.45
1832
1827
  },
@@ -1839,14 +1834,15 @@ function MarqueeOverlay({ rect }) {
1839
1834
  return /* @__PURE__ */ jsx3(
1840
1835
  "rect",
1841
1836
  {
1837
+ className: "zui-drawing-selection",
1842
1838
  x: rect.x,
1843
1839
  y: rect.y,
1844
1840
  width: rect.w,
1845
1841
  height: rect.h,
1846
1842
  rx: 3,
1847
- fill: SELECTION_COLOR,
1843
+ fill: "currentColor",
1848
1844
  fillOpacity: 0.08,
1849
- stroke: SELECTION_COLOR,
1845
+ stroke: "currentColor",
1850
1846
  strokeWidth: 1,
1851
1847
  pointerEvents: "none"
1852
1848
  }
@@ -2000,7 +1996,6 @@ function BoxGeometry({
2000
1996
  // src/drawing/canvas/ShapeView.tsx
2001
1997
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2002
1998
  import { createElement } from "react";
2003
- var FONT_FAMILY = "ui-sans-serif, system-ui, sans-serif";
2004
1999
  var CONNECTOR_FONT_SIZE = 12;
2005
2000
  var CONNECTOR_LABEL_MAX_WIDTH = 160;
2006
2001
  function slotLayout(shape) {
@@ -2036,7 +2031,6 @@ function BoxTexts({
2036
2031
  const mainStart = area.y + area.h / 2 - (mainLines.length - 1) * mainLineH / 2 + FONT_SIZE * 0.35;
2037
2032
  const common = {
2038
2033
  fill: shape.stroke,
2039
- fontFamily: FONT_FAMILY,
2040
2034
  textAnchor: "middle"
2041
2035
  };
2042
2036
  return /* @__PURE__ */ jsxs5("g", { children: [
@@ -2134,7 +2128,7 @@ function ConnectorLabel({
2134
2128
  width: size.w + 10,
2135
2129
  height: size.h + 6,
2136
2130
  rx: 5,
2137
- fill: "#ffffff",
2131
+ className: "zui-drawing-label-plate",
2138
2132
  opacity: 0.94
2139
2133
  }
2140
2134
  ),
@@ -2145,7 +2139,6 @@ function ConnectorLabel({
2145
2139
  y: startY + i * lineH,
2146
2140
  fill: shape.stroke,
2147
2141
  fontSize: CONNECTOR_FONT_SIZE,
2148
- fontFamily: FONT_FAMILY,
2149
2142
  textAnchor: "middle",
2150
2143
  style: textStyle,
2151
2144
  children: line
@@ -2190,7 +2183,6 @@ function ShapeView({
2190
2183
  y: shape.y + FONT_SIZE,
2191
2184
  fill: shape.stroke,
2192
2185
  fontSize: FONT_SIZE,
2193
- fontFamily: FONT_FAMILY,
2194
2186
  style: { userSelect: "none", whiteSpace: "pre" },
2195
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))
2196
2188
  }
@@ -2262,7 +2254,7 @@ function TextEditOverlay({
2262
2254
  }, []);
2263
2255
  const style = {
2264
2256
  color: shape.stroke,
2265
- fontFamily: FONT_FAMILY,
2257
+ fontFamily: "var(--zui-drawing-font)",
2266
2258
  lineHeight: LINE_HEIGHT
2267
2259
  };
2268
2260
  if (shape.type === "text") {
@@ -3027,118 +3019,120 @@ function DrawingCanvas({ nodeKey, data }) {
3027
3019
  }
3028
3020
  ) })
3029
3021
  ] }),
3030
- /* @__PURE__ */ jsxs8(
3031
- "svg",
3032
- {
3033
- ref: svgRef,
3034
- className: "zui-drawing-surface",
3035
- style: {
3036
- height: logicalWidth ? void 0 : canvasHeight,
3037
- width: logicalWidth ?? void 0,
3038
- maxWidth: "100%",
3039
- aspectRatio: logicalWidth ? `${logicalWidth} / ${canvasHeight}` : void 0,
3040
- cursor: canvasCursor
3041
- },
3042
- viewBox: logicalWidth ? `0 0 ${logicalWidth} ${canvasHeight}` : void 0,
3043
- preserveAspectRatio: "xMinYMin meet",
3044
- onPointerDown: handleBackgroundPointerDown,
3045
- onPointerMove: handlePointerMove,
3046
- onPointerUp: handlePointerUp,
3047
- onPointerCancel: handlePointerUp,
3048
- onDoubleClick: (e) => {
3049
- if (!isEditable) return;
3050
- const target = e.target.closest("[data-shape-id]");
3051
- const id = target?.getAttribute("data-shape-id");
3052
- const shape = id ? shapes.find((s) => s.id === id) : null;
3053
- if (!shape) return;
3054
- if (shape.type === "text" || isConnectorType(shape.type)) {
3055
- startTextEditing(shape.id, "text");
3056
- } else {
3057
- startTextEditing(shape.id, slotAt(shape, getPoint(e).y));
3058
- }
3059
- },
3060
- children: [
3061
- shapes.map((shape) => /* @__PURE__ */ jsxs8(
3062
- "g",
3063
- {
3064
- "data-shape-id": shape.id,
3065
- className: `zui-drawing-shape ${selectedIds.has(shape.id) ? "is-selected" : ""}`,
3066
- style: { cursor: isEditable && tool === "select" ? "move" : void 0 },
3067
- onPointerDown: (e) => handleShapePointerDown(e, shape),
3068
- children: [
3069
- /* @__PURE__ */ jsx9(HitArea, { shape, points: paths.get(shape.id) }),
3070
- shape.id === editingText?.id && shape.type === "text" ? null : /* @__PURE__ */ jsx9(
3071
- ShapeView,
3072
- {
3073
- shape,
3074
- points: paths.get(shape.id),
3075
- hideField: shape.id === editingText?.id ? editingText.field : null,
3076
- showHints: isEditable && tool === "select" && single?.id === shape.id && shape.id !== editingText?.id
3077
- }
3078
- )
3079
- ]
3080
- },
3081
- shape.id
3082
- )),
3083
- hoverBox && /* @__PURE__ */ jsx9(
3084
- "polygon",
3085
- {
3086
- className: "zui-drawing-bind-target",
3087
- points: boxOutline(hoverBox).map((p) => `${p.x},${p.y}`).join(" "),
3088
- fill: SELECTION_COLOR,
3089
- fillOpacity: 0.06,
3090
- stroke: SELECTION_COLOR,
3091
- strokeWidth: 2,
3092
- strokeLinejoin: "round",
3093
- pointerEvents: "none"
3094
- }
3095
- ),
3096
- single && isEditable && !editingText && /* @__PURE__ */ jsx9(
3097
- SelectionOverlay,
3098
- {
3099
- shape: single,
3100
- points: paths.get(single.id) ?? [],
3101
- onHandlePointerDown: handleHandlePointerDown,
3102
- onWaypointAdd: addWaypoint,
3103
- 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));
3104
3051
  }
3105
- ),
3106
- selection.length > 1 && isEditable && /* @__PURE__ */ jsx9(GroupSelectionOverlay, { shapes: selection, paths }),
3107
- marquee && /* @__PURE__ */ jsx9(MarqueeOverlay, { rect: marqueeRect(marquee.origin, marquee.current) })
3108
- ]
3109
- }
3110
- ),
3111
- 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" }),
3112
- editingShape && editingText && /* @__PURE__ */ jsx9(
3113
- "div",
3114
- {
3115
- className: "zui-drawing-overlay",
3116
- style: {
3117
- transform: `scale(${scale})`,
3118
- width: logicalWidth ?? void 0,
3119
- height: logicalWidth ? canvasHeight : void 0
3120
- },
3121
- children: /* @__PURE__ */ jsx9(
3122
- TextEditOverlay,
3123
- {
3124
- shape: editingShape,
3125
- field: editingText.field,
3126
- points: paths.get(editingShape.id),
3127
- onCommit: commitText
3128
3052
  },
3129
- `${editingShape.id}:${editingText.field}`
3130
- )
3131
- }
3132
- ),
3133
- isEditable && /* @__PURE__ */ jsx9(
3134
- HeightHandle,
3135
- {
3136
- height: canvasHeight,
3137
- scale,
3138
- onChange: setCanvasHeight,
3139
- onCommit: (h) => commit(shapesRef.current, { height: h })
3140
- }
3141
- )
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
+ ] })
3142
3136
  ]
3143
3137
  }
3144
3138
  );
package/dist/styles.css CHANGED
@@ -362,7 +362,7 @@
362
362
  .zui-table-selected,
363
363
  .zui-table-cell.zui-table-selected {
364
364
  outline: none;
365
- background: color-mix(in srgb, #6965db 14%, transparent);
365
+ background: color-mix(in srgb, var(--primary, #6965db) 14%, transparent);
366
366
  }
367
367
 
368
368
  /*
@@ -391,24 +391,57 @@
391
391
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
392
392
  }
393
393
 
394
- /* Drawing canvas */
394
+ /*
395
+ * Drawing canvas.
396
+ *
397
+ * Every color, radius and shadow of the canvas chrome comes from a
398
+ * `--zui-drawing-*` custom property. They default to the ZUI core theme
399
+ * tokens (`--primary`, `--foreground`, `--border`, …) so the canvas follows
400
+ * the host theme, light and dark, and consumers override them on
401
+ * `.zui-drawing-canvas` (or any ancestor) to restyle without touching
402
+ * selectors. `--zui-drawing-surface` and `--zui-drawing-grid` are the
403
+ * pre-filter colors of the drawing area: in dark mode the whole area is
404
+ * passed through `--zui-drawing-dark-filter` so stored shape colors stay
405
+ * theme-agnostic (set the filter to `none` to opt out).
406
+ */
395
407
  .zui-drawing {
396
408
  margin-block: 1rem;
397
409
  }
398
410
 
399
411
  .zui-drawing-canvas {
400
- --zui-drawing-accent: #6965db;
412
+ --zui-drawing-accent: var(--primary, #3b82f6);
413
+ --zui-drawing-foreground: var(--foreground, #0a0a0a);
414
+ --zui-drawing-muted-foreground: var(--muted-foreground, #71717a);
415
+ --zui-drawing-border: var(--border, #e4e4e7);
416
+ --zui-drawing-chrome: var(--background, #ffffff);
417
+ --zui-drawing-popover: var(--popover, #ffffff);
418
+ --zui-drawing-popover-foreground: var(--popover-foreground, var(--zui-drawing-foreground));
419
+ --zui-drawing-danger: var(--destructive, #ef4444);
420
+ --zui-drawing-success: var(--success, #22c55e);
421
+ --zui-drawing-radius: var(--radius-lg, 0.75rem);
422
+ --zui-drawing-control-radius: var(--radius, 0.375rem);
423
+ --zui-drawing-shadow: var(--shadow-medium, 0 4px 6px -1px rgba(0, 0, 0, 0.1));
424
+ --zui-drawing-font: ui-sans-serif, system-ui, sans-serif;
425
+ --zui-drawing-surface: #ffffff;
426
+ --zui-drawing-grid: rgba(0, 0, 0, 0.09);
427
+ --zui-drawing-grid-size: 20px;
428
+ --zui-drawing-dark-filter: invert(93%) hue-rotate(180deg);
429
+ --zui-drawing-hover: color-mix(in srgb, currentColor 8%, transparent);
430
+ --zui-drawing-active: color-mix(in srgb, currentColor 12%, transparent);
431
+
401
432
  position: relative;
402
- border: 1px solid color-mix(in srgb, currentColor 10%, transparent);
403
- border-radius: 0.875rem;
404
- background: #ffffff;
433
+ border: 1px solid var(--zui-drawing-border);
434
+ border-radius: var(--zui-drawing-radius);
435
+ background: var(--zui-drawing-chrome);
436
+ color: var(--zui-drawing-foreground);
437
+ font-family: var(--zui-drawing-font);
405
438
  outline: none;
406
439
  overflow: hidden;
407
- transition: box-shadow 0.18s ease, border-color 0.18s ease;
440
+ transition: box-shadow 0.15s ease, border-color 0.15s ease;
408
441
  }
409
442
 
410
443
  .zui-drawing-canvas.is-editable:focus-within {
411
- border-color: color-mix(in srgb, var(--zui-drawing-accent) 45%, transparent);
444
+ border-color: color-mix(in srgb, var(--zui-drawing-accent) 50%, var(--zui-drawing-border));
412
445
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--zui-drawing-accent) 12%, transparent);
413
446
  }
414
447
 
@@ -417,25 +450,30 @@
417
450
  max-width: 100%;
418
451
  }
419
452
 
453
+ .zui-drawing-stage {
454
+ position: relative;
455
+ }
456
+
420
457
  .zui-drawing-surface {
421
458
  display: block;
422
459
  width: 100%;
423
460
  max-width: 100%;
424
461
  touch-action: none;
425
- background-color: #ffffff;
426
- background-image: radial-gradient(circle, rgba(0, 0, 0, 0.09) 1px, transparent 1px);
427
- background-size: 20px 20px;
428
- }
429
-
430
- .zui-drawing-shape {
431
- transition: filter 0.12s ease;
462
+ font-family: var(--zui-drawing-font);
463
+ background-color: var(--zui-drawing-surface);
464
+ background-image: radial-gradient(circle, var(--zui-drawing-grid) 1px, transparent 1px);
465
+ background-size: var(--zui-drawing-grid-size) var(--zui-drawing-grid-size);
432
466
  }
433
467
 
434
- .zui-drawing-canvas.is-editable .zui-drawing-shape:hover:not(.is-selected) {
435
- filter: drop-shadow(0 0 2.5px color-mix(in srgb, var(--zui-drawing-accent) 55%, transparent));
468
+ /* Selection chrome inside the SVG: everything is drawn in `currentColor` */
469
+ .zui-drawing-selection,
470
+ .zui-drawing-bind-target {
471
+ color: var(--zui-drawing-accent);
436
472
  }
437
473
 
438
474
  .zui-drawing-handle {
475
+ fill: var(--zui-drawing-surface);
476
+ stroke: currentColor;
439
477
  filter: drop-shadow(0 1px 1.5px rgba(0, 0, 0, 0.25));
440
478
  }
441
479
 
@@ -448,6 +486,18 @@
448
486
  opacity: 1;
449
487
  }
450
488
 
489
+ .zui-drawing-label-plate {
490
+ fill: var(--zui-drawing-surface);
491
+ }
492
+
493
+ .zui-drawing-shape {
494
+ transition: filter 0.12s ease;
495
+ }
496
+
497
+ .zui-drawing-canvas.is-editable .zui-drawing-shape:hover:not(.is-selected) {
498
+ filter: drop-shadow(0 0 2.5px color-mix(in srgb, var(--zui-drawing-accent) 55%, transparent));
499
+ }
500
+
451
501
  .zui-drawing-bind-target {
452
502
  animation: zui-drawing-pulse 0.9s ease-in-out infinite alternate;
453
503
  }
@@ -464,43 +514,29 @@
464
514
  @keyframes zui-drawing-pop {
465
515
  from {
466
516
  opacity: 0;
467
- transform: translateY(4px) scale(0.98);
517
+ transform: translateY(-3px);
468
518
  }
469
519
  to {
470
520
  opacity: 1;
471
- transform: translateY(0) scale(1);
521
+ transform: translateY(0);
472
522
  }
473
523
  }
474
524
 
475
- /* Floating toolbar */
525
+ /* Header: tools row (+ contextual properties row), same idiom as the editor toolbar */
476
526
  .zui-drawing-toolbar {
477
- position: absolute;
478
- top: 0.625rem;
479
- left: 0.625rem;
480
- right: 0.625rem;
481
- z-index: 5;
482
527
  display: flex;
483
528
  flex-wrap: wrap;
484
- align-items: flex-start;
485
- gap: 0.5rem;
486
- pointer-events: none;
529
+ align-items: center;
530
+ gap: 0.125rem 0.5rem;
531
+ padding: 0.375rem 0.625rem;
532
+ border-bottom: 1px solid var(--zui-drawing-border);
533
+ background: var(--zui-drawing-chrome);
487
534
  }
488
535
 
489
- .zui-drawing-toolbar-group,
490
- .zui-drawing-props {
536
+ .zui-drawing-toolbar-group {
491
537
  display: flex;
492
538
  align-items: center;
493
539
  gap: 0.125rem;
494
- padding: 0.25rem;
495
- border: 1px solid rgba(0, 0, 0, 0.07);
496
- border-radius: 0.625rem;
497
- background: rgba(255, 255, 255, 0.88);
498
- backdrop-filter: blur(10px);
499
- -webkit-backdrop-filter: blur(10px);
500
- box-shadow:
501
- 0 1px 2px rgba(0, 0, 0, 0.05),
502
- 0 6px 20px -8px rgba(0, 0, 0, 0.18);
503
- pointer-events: auto;
504
540
  }
505
541
 
506
542
  .zui-drawing-toolbar-group-end {
@@ -510,8 +546,8 @@
510
546
  .zui-drawing-toolbar-divider {
511
547
  width: 1px;
512
548
  height: 1.125rem;
513
- margin: 0 0.25rem;
514
- background: rgba(0, 0, 0, 0.1);
549
+ margin: 0 0.375rem;
550
+ background: color-mix(in srgb, currentColor 15%, transparent);
515
551
  }
516
552
 
517
553
  .zui-drawing-tool {
@@ -519,57 +555,56 @@
519
555
  display: inline-flex;
520
556
  align-items: center;
521
557
  justify-content: center;
522
- width: 1.875rem;
523
- height: 1.875rem;
558
+ width: 1.75rem;
559
+ height: 1.75rem;
524
560
  border: none;
525
- border-radius: 0.5rem;
561
+ border-radius: var(--zui-drawing-control-radius);
526
562
  background: transparent;
527
- color: #1e1e1e;
563
+ color: inherit;
564
+ opacity: 0.65;
528
565
  cursor: pointer;
529
566
  transition:
530
567
  background-color 0.12s ease,
531
- color 0.12s ease,
532
- transform 0.12s ease;
568
+ opacity 0.12s ease;
533
569
  }
534
570
 
535
571
  .zui-drawing-tool:hover {
536
- background: rgba(0, 0, 0, 0.06);
537
- }
538
-
539
- .zui-drawing-tool:active {
540
- transform: scale(0.94);
572
+ opacity: 1;
573
+ background: var(--zui-drawing-hover);
541
574
  }
542
575
 
543
576
  .zui-drawing-tool.is-active {
544
- background: color-mix(in srgb, var(--zui-drawing-accent) 16%, transparent);
545
- color: #4a47b1;
577
+ opacity: 1;
578
+ color: var(--zui-drawing-accent);
579
+ background: color-mix(in srgb, var(--zui-drawing-accent) 14%, transparent);
546
580
  }
547
581
 
548
582
  .zui-drawing-tool.is-success {
549
- color: #2f9e44;
583
+ opacity: 1;
584
+ color: var(--zui-drawing-success);
550
585
  }
551
586
 
552
587
  .zui-drawing-tool-danger {
553
- color: #e03131;
588
+ color: var(--zui-drawing-danger);
554
589
  }
555
590
 
556
591
  .zui-drawing-tool-danger:hover {
557
- background: rgba(224, 49, 49, 0.1);
592
+ background: color-mix(in srgb, var(--zui-drawing-danger) 12%, transparent);
558
593
  }
559
594
 
560
595
  .zui-drawing-tool-more {
561
- width: 2.375rem;
596
+ width: 2.25rem;
562
597
  }
563
598
 
564
599
  .zui-drawing-caret {
565
600
  position: absolute;
566
- right: 0.3rem;
567
- bottom: 0.3rem;
601
+ right: 0.25rem;
602
+ bottom: 0.25rem;
568
603
  width: 0;
569
604
  height: 0;
570
605
  border-left: 4px solid transparent;
571
606
  border-top: 4px solid currentColor;
572
- opacity: 0.6;
607
+ opacity: 0.7;
573
608
  }
574
609
 
575
610
  .zui-drawing-more {
@@ -578,7 +613,7 @@
578
613
 
579
614
  .zui-drawing-popover {
580
615
  position: absolute;
581
- top: calc(100% + 0.375rem);
616
+ top: calc(100% + 0.25rem);
582
617
  left: 0;
583
618
  z-index: 7;
584
619
  display: flex;
@@ -586,14 +621,12 @@
586
621
  gap: 0.125rem;
587
622
  min-width: 9.5rem;
588
623
  padding: 0.25rem;
589
- border: 1px solid rgba(0, 0, 0, 0.08);
590
- border-radius: 0.625rem;
591
- background: #ffffff;
592
- box-shadow:
593
- 0 1px 2px rgba(0, 0, 0, 0.06),
594
- 0 10px 28px -8px rgba(0, 0, 0, 0.25);
595
- animation: zui-drawing-pop 0.14s ease-out;
596
- transform-origin: top left;
624
+ border: 1px solid var(--zui-drawing-border);
625
+ border-radius: var(--zui-drawing-control-radius);
626
+ background: var(--zui-drawing-popover);
627
+ color: var(--zui-drawing-popover-foreground);
628
+ box-shadow: var(--zui-drawing-shadow);
629
+ animation: zui-drawing-pop 0.12s ease-out;
597
630
  }
598
631
 
599
632
  .zui-drawing-popover-item {
@@ -602,9 +635,9 @@
602
635
  gap: 0.5rem;
603
636
  padding: 0.375rem 0.5rem;
604
637
  border: none;
605
- border-radius: 0.375rem;
638
+ border-radius: var(--zui-drawing-control-radius);
606
639
  background: transparent;
607
- color: #1e1e1e;
640
+ color: inherit;
608
641
  font-size: 0.8125rem;
609
642
  text-align: left;
610
643
  cursor: pointer;
@@ -612,36 +645,39 @@
612
645
  }
613
646
 
614
647
  .zui-drawing-popover-item:hover {
615
- background: rgba(0, 0, 0, 0.06);
648
+ background: var(--zui-drawing-hover);
616
649
  }
617
650
 
618
651
  .zui-drawing-popover-item.is-active {
619
- background: color-mix(in srgb, var(--zui-drawing-accent) 16%, transparent);
620
- color: #4a47b1;
652
+ color: var(--zui-drawing-accent);
653
+ background: color-mix(in srgb, var(--zui-drawing-accent) 14%, transparent);
621
654
  }
622
655
 
623
- /* Contextual property bar: its own row under the tools */
656
+ /* Contextual properties: a second row that expands under the tools */
624
657
  .zui-drawing-props-host {
625
658
  flex-basis: 100%;
626
659
  display: flex;
660
+ max-height: 0;
661
+ overflow: hidden;
627
662
  opacity: 0;
628
- transform: translateY(-4px);
629
663
  pointer-events: none;
630
664
  transition:
631
- opacity 0.16s ease,
632
- transform 0.16s ease;
665
+ max-height 0.18s ease,
666
+ opacity 0.18s ease;
633
667
  }
634
668
 
635
669
  .zui-drawing-props-host.is-visible {
670
+ max-height: 6rem;
636
671
  opacity: 1;
637
- transform: translateY(0);
638
672
  pointer-events: auto;
639
673
  }
640
674
 
641
675
  .zui-drawing-props {
676
+ display: flex;
642
677
  flex-wrap: wrap;
678
+ align-items: center;
643
679
  gap: 0.25rem;
644
- padding: 0.25rem 0.375rem;
680
+ padding: 0.25rem 0 0.125rem;
645
681
  }
646
682
 
647
683
  .zui-drawing-props-group {
@@ -651,24 +687,24 @@
651
687
  }
652
688
 
653
689
  .zui-drawing-props-group + .zui-drawing-props-group {
654
- padding-left: 0.375rem;
655
- border-left: 1px solid rgba(0, 0, 0, 0.1);
690
+ padding-left: 0.5rem;
691
+ border-left: 1px solid color-mix(in srgb, currentColor 15%, transparent);
656
692
  }
657
693
 
658
694
  .zui-drawing-swatch-label {
659
695
  margin: 0 0.25rem 0 0.125rem;
660
696
  font-size: 0.6875rem;
661
697
  letter-spacing: 0.02em;
662
- color: rgba(0, 0, 0, 0.5);
698
+ color: var(--zui-drawing-muted-foreground);
663
699
  user-select: none;
664
700
  }
665
701
 
666
702
  .zui-drawing-swatch {
667
- width: 1.125rem;
668
- height: 1.125rem;
703
+ width: 1rem;
704
+ height: 1rem;
669
705
  margin: 0 0.125rem;
670
706
  padding: 0;
671
- border: 1px solid rgba(0, 0, 0, 0.14);
707
+ border: 1px solid rgba(0, 0, 0, 0.18);
672
708
  border-radius: 999px;
673
709
  cursor: pointer;
674
710
  transition:
@@ -682,7 +718,7 @@
682
718
 
683
719
  .zui-drawing-swatch.is-active {
684
720
  box-shadow:
685
- 0 0 0 2px #ffffff,
721
+ 0 0 0 2px var(--zui-drawing-chrome),
686
722
  0 0 0 3.5px var(--zui-drawing-accent);
687
723
  }
688
724
 
@@ -703,9 +739,8 @@
703
739
  display: flex;
704
740
  align-items: center;
705
741
  justify-content: center;
706
- padding-top: 2rem;
707
742
  font-size: 0.8125rem;
708
- color: rgba(0, 0, 0, 0.4);
743
+ color: var(--zui-drawing-muted-foreground);
709
744
  pointer-events: none;
710
745
  user-select: none;
711
746
  }
@@ -728,10 +763,11 @@
728
763
  padding: 0;
729
764
  border: 0;
730
765
  border-radius: 3px;
731
- outline: 1.5px dashed var(--zui-drawing-accent, #6965db);
766
+ outline: 1.5px dashed var(--zui-drawing-accent);
732
767
  outline-offset: 3px;
733
- background: rgba(255, 255, 255, 0.85);
768
+ background: color-mix(in srgb, var(--zui-drawing-surface) 85%, transparent);
734
769
  color: inherit;
770
+ font-family: var(--zui-drawing-font);
735
771
  resize: none;
736
772
  overflow: hidden;
737
773
  box-sizing: content-box;
@@ -753,7 +789,7 @@
753
789
  justify-content: center;
754
790
  width: 1.375rem;
755
791
  height: 1.375rem;
756
- border-radius: 0.375rem;
792
+ border-radius: var(--zui-drawing-control-radius);
757
793
  color: #1e1e1e;
758
794
  opacity: 0.35;
759
795
  cursor: ns-resize;
@@ -769,70 +805,15 @@
769
805
  }
770
806
 
771
807
  /*
772
- * Dark mode (zui `.dark` class convention). The drawing surface, the color
773
- * swatches, and the inline text input are darkened with a color-inversion
774
- * filter, so stored shape colors stay theme-agnostic: the default
775
- * near-black stroke renders light on the dark canvas, and the swatches
776
- * preview exactly what will be drawn.
808
+ * Dark mode (zui `.dark` class convention). The chrome follows the theme
809
+ * tokens on its own; the drawing area, the swatches and the inline text
810
+ * input are inverted so stored shape colors stay theme-agnostic.
777
811
  */
778
- .dark .zui-drawing-canvas {
779
- background: #18181b;
780
- border-color: rgba(255, 255, 255, 0.1);
781
- }
782
-
783
812
  .dark .zui-drawing-surface,
784
813
  .dark .zui-drawing-swatch,
785
- .dark .zui-drawing-text-input {
786
- filter: invert(93%) hue-rotate(180deg);
787
- }
788
-
789
- .dark .zui-drawing-toolbar-group,
790
- .dark .zui-drawing-props,
791
- .dark .zui-drawing-popover {
792
- background: rgba(35, 35, 41, 0.9);
793
- border-color: rgba(255, 255, 255, 0.1);
794
- box-shadow:
795
- 0 1px 2px rgba(0, 0, 0, 0.3),
796
- 0 8px 24px -8px rgba(0, 0, 0, 0.6);
797
- }
798
-
799
- .dark .zui-drawing-tool,
800
- .dark .zui-drawing-popover-item,
814
+ .dark .zui-drawing-text-input,
801
815
  .dark .zui-drawing-resize {
802
- color: #e3e3e8;
803
- }
804
-
805
- .dark .zui-drawing-tool:hover,
806
- .dark .zui-drawing-popover-item:hover,
807
- .dark .zui-drawing-resize:hover {
808
- background: rgba(255, 255, 255, 0.08);
809
- }
810
-
811
- .dark .zui-drawing-tool.is-active,
812
- .dark .zui-drawing-popover-item.is-active {
813
- background: #45437a;
814
- color: #dcdaff;
815
- }
816
-
817
- .dark .zui-drawing-toolbar-divider,
818
- .dark .zui-drawing-props-group + .zui-drawing-props-group {
819
- border-color: rgba(255, 255, 255, 0.12);
820
- background-color: rgba(255, 255, 255, 0.12);
821
- }
822
-
823
- .dark .zui-drawing-props-group + .zui-drawing-props-group {
824
- background-color: transparent;
825
- }
826
-
827
- .dark .zui-drawing-swatch-label,
828
- .dark .zui-drawing-empty {
829
- color: rgba(255, 255, 255, 0.55);
830
- }
831
-
832
- .dark .zui-drawing-swatch.is-active {
833
- box-shadow:
834
- 0 0 0 2px #232329,
835
- 0 0 0 3.5px #8f8bff;
816
+ filter: var(--zui-drawing-dark-filter);
836
817
  }
837
818
 
838
819
  .zui-text-editor-textarea {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zuilib/text-editor",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "ZUI — A markdown editor component wrapping Lexical",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",