@pixerate/editor-react 0.30.0 → 0.32.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.
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/vector/index.ts
@@ -22,6 +32,7 @@ var vector_exports = {};
22
32
  __export(vector_exports, {
23
33
  VectorCanvas: () => VectorCanvas,
24
34
  VectorEditor: () => VectorEditor,
35
+ VectorEffectsPanel: () => VectorEffectsPanel,
25
36
  VectorGradientDefs: () => VectorGradientDefs,
26
37
  VectorSceneNodes: () => VectorSceneNodes,
27
38
  VectorStylePanel: () => VectorStylePanel,
@@ -373,6 +384,27 @@ function VectorCanvas({
373
384
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: w.x, cy: w.y, r: import_vector3.VECTOR_OVERLAY_THEME.cornerWidgetRadius + (w.hovered ? 1 : 0), fill: import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }),
374
385
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: w.x, cy: w.y, r: 1.5, fill: accentColor })
375
386
  ] }, `corner:${w.ref.nodeId}:${w.ref.subpath}:${w.ref.index}`)),
387
+ overlay.blend.spines.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-blend-spine": sp.id, "data-replaced": sp.replaced || void 0, children: [
388
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: sp.d, fill: "none", stroke: accentColor, strokeDasharray: "4 3" }),
389
+ sp.keys.map((k, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: k.x, cy: k.y, r: import_vector3.VECTOR_OVERLAY_THEME.handleRadius, fill: import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }, i))
390
+ ] }, `blend:${sp.id}`)),
391
+ overlay.blend.start && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-blend-start": "", children: [
392
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: overlay.blend.start.d, fill: "none", stroke: accentColor, strokeWidth: 2 }),
393
+ overlay.blend.start.anchor && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: overlay.blend.start.anchor.x, cy: overlay.blend.start.anchor.y, r: import_vector3.VECTOR_OVERLAY_THEME.handleRadius + 1, fill: accentColor })
394
+ ] }),
395
+ overlay.blend.anchors.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
396
+ "rect",
397
+ {
398
+ "data-vector-blend-anchor": i,
399
+ x: a.x - as / 2,
400
+ y: a.y - as / 2,
401
+ width: as,
402
+ height: as,
403
+ fill: a.hovered ? accentColor : import_vector3.VECTOR_OVERLAY_THEME.handleFill,
404
+ stroke: accentColor
405
+ },
406
+ `blend-anchor:${i}`
407
+ )),
376
408
  overlay.widthSpot && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-width-spot": "", children: [
377
409
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: overlay.widthSpot.left.x, y1: overlay.widthSpot.left.y, x2: overlay.widthSpot.right.x, y2: overlay.widthSpot.right.y, stroke: accentColor, strokeDasharray: "3 2" }),
378
410
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: (0, import_vector3.vectorDiamondPath)(overlay.widthSpot.x, overlay.widthSpot.y), fill: "none", stroke: accentColor })
@@ -478,43 +510,197 @@ function VectorCanvas({
478
510
  }
479
511
 
480
512
  // src/vector/VectorToolbar.tsx
513
+ var import_react4 = __toESM(require("react"), 1);
514
+ var import_vector5 = require("@pixerate/editor/vector");
515
+
516
+ // src/vector/VectorEffectsPanel.tsx
481
517
  var import_vector4 = require("@pixerate/editor/vector");
482
518
  var import_jsx_runtime3 = require("react/jsx-runtime");
519
+ var buttonClass = "inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40";
520
+ var numberClass = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
521
+ var selectClass = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
522
+ var labelClass = "w-full truncate text-[11px] text-muted-foreground";
523
+ function VectorEffectsPanel({ controller, className }) {
524
+ const state = useVectorEditorState(controller);
525
+ const panel = (0, import_vector4.getVectorEffectsPanel)(state);
526
+ if (!panel) return null;
527
+ const nodeId = panel.nodeId;
528
+ const end = () => controller.endMergedEdit();
529
+ const set = (e, key, value, merge) => {
530
+ if (nodeId) controller.updateEffect(nodeId, e.id, { params: { [key]: value } }, { merge });
531
+ };
532
+ const number = (e, spec) => {
533
+ const value = Number(e.params[spec.key] ?? 0);
534
+ const label = `${e.label} ${spec.label}`;
535
+ const onChange = (ev) => {
536
+ const n = parseFloat(ev.target.value);
537
+ if (Number.isFinite(n)) set(e, spec.key, n, true);
538
+ };
539
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-foreground", "data-vector-effect-param": spec.key, children: [
540
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: labelClass, children: spec.label }),
541
+ spec.slider && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
542
+ "input",
543
+ {
544
+ type: "range",
545
+ min: spec.min,
546
+ max: spec.max,
547
+ step: spec.step,
548
+ "aria-label": `${label} slider`,
549
+ className: "h-4 min-w-0 flex-1 accent-primary",
550
+ value,
551
+ onChange,
552
+ onPointerUp: end,
553
+ onKeyUp: end,
554
+ onBlur: end
555
+ }
556
+ ),
557
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
558
+ "input",
559
+ {
560
+ type: "number",
561
+ min: spec.min,
562
+ max: spec.max,
563
+ step: spec.step,
564
+ "aria-label": label,
565
+ className: numberClass,
566
+ value: Math.round(value * 1e3) / 1e3,
567
+ onChange,
568
+ onBlur: end
569
+ }
570
+ )
571
+ ] }, spec.key);
572
+ };
573
+ const vec2 = (e, spec) => {
574
+ const v = e.params[spec.key] ?? { x: 0, y: 0 };
575
+ const field = (axis) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
576
+ "input",
577
+ {
578
+ type: "number",
579
+ step: spec.kind === "vec2" ? spec.step : 1,
580
+ "aria-label": `${e.label} ${spec.label} ${axis}`,
581
+ className: numberClass,
582
+ value: Math.round(v[axis] * 1e3) / 1e3,
583
+ onChange: (ev) => {
584
+ const n = parseFloat(ev.target.value);
585
+ if (Number.isFinite(n)) set(e, spec.key, { ...v, [axis]: n }, true);
586
+ },
587
+ onBlur: end
588
+ }
589
+ );
590
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-foreground", "data-vector-effect-param": spec.key, children: [
591
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: labelClass, children: spec.label }),
592
+ field("x"),
593
+ field("y")
594
+ ] }, spec.key);
595
+ };
596
+ const param = (e, spec) => {
597
+ if (spec.kind === "number") return number(e, spec);
598
+ if (spec.kind === "vec2") return vec2(e, spec);
599
+ if (spec.kind === "boolean")
600
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-effect-param": spec.key, children: [
601
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "min-w-0 flex-1 truncate", children: spec.label }),
602
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { type: "checkbox", "aria-label": `${e.label} ${spec.label}`, checked: !!e.params[spec.key], onChange: (ev) => set(e, spec.key, ev.target.checked, false) })
603
+ ] }, spec.key);
604
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-foreground", "data-vector-effect-param": spec.key, children: [
605
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: labelClass, children: spec.label }),
606
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("select", { "aria-label": `${e.label} ${spec.label}`, className: selectClass, value: String(e.params[spec.key] ?? ""), onChange: (ev) => set(e, spec.key, ev.target.value, false), children: spec.options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: o.value, children: o.label }, o.value)) })
607
+ ] }, spec.key);
608
+ };
609
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-effects": "", "aria-label": "Effects", children: [
610
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", children: [
611
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Effects" }),
612
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
613
+ "select",
614
+ {
615
+ "aria-label": "Add effect",
616
+ className: selectClass,
617
+ value: "",
618
+ onChange: (ev) => {
619
+ if (ev.target.value) controller.addEffect(ev.target.value);
620
+ },
621
+ children: [
622
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "Add effect\u2026" }),
623
+ import_vector4.VECTOR_EFFECT_MENU.map((m) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: m.type, children: m.label }, m.type))
624
+ ]
625
+ }
626
+ ),
627
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: buttonClass, title: "Expand Appearance", "aria-label": "Expand Appearance", disabled: !panel.expandable, onClick: () => controller.expandAppearance(), children: "Expand" })
628
+ ] }),
629
+ panel.count > 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "Select one object to edit its effects." }),
630
+ nodeId && panel.effects.map((e, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "space-y-1 rounded border border-border p-1.5", "data-vector-effect": e.id, "data-vector-effect-type": e.type, children: [
631
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-1", children: [
632
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex min-w-0 flex-1 items-center gap-1.5 text-xs font-medium text-foreground", children: [
633
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { type: "checkbox", "aria-label": `${e.label} on`, checked: e.enabled, onChange: (ev) => controller.updateEffect(nodeId, e.id, { enabled: ev.target.checked }) }),
634
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "truncate", children: e.label })
635
+ ] }),
636
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: buttonClass, title: "Move up", "aria-label": `Move ${e.label} up`, disabled: i === 0, onClick: () => controller.moveEffect(nodeId, e.id, i - 1), children: "\u2191" }),
637
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
638
+ "button",
639
+ {
640
+ type: "button",
641
+ className: buttonClass,
642
+ title: "Move down",
643
+ "aria-label": `Move ${e.label} down`,
644
+ disabled: i === panel.effects.length - 1,
645
+ onClick: () => controller.moveEffect(nodeId, e.id, i + 1),
646
+ children: "\u2193"
647
+ }
648
+ ),
649
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: buttonClass, title: "Remove", "aria-label": `Remove ${e.label}`, onClick: () => controller.removeEffect(nodeId, e.id), children: "\xD7" })
650
+ ] }),
651
+ e.specs.map((spec) => param(e, spec))
652
+ ] }, e.id))
653
+ ] });
654
+ }
655
+
656
+ // src/vector/VectorToolbar.tsx
657
+ var import_jsx_runtime4 = require("react/jsx-runtime");
483
658
  var VECTOR_SNAP_TITLE = "Snap to objects";
484
659
  var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
485
- var buttonClass = "inline-flex h-8 min-w-8 items-center justify-center rounded-md border border-border bg-background px-2 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
486
- function VectorToolbar({ controller, tools = import_vector4.VECTOR_TOOLS, showHistory = true, showArrange = true, showSnap = true, showPathEdit = true, className }) {
660
+ var buttonClass2 = "inline-flex h-8 min-w-8 items-center justify-center rounded-md border border-border bg-background px-2 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
661
+ function VectorToolbar({
662
+ controller,
663
+ tools = import_vector5.VECTOR_TOOLS,
664
+ showHistory = true,
665
+ showArrange = true,
666
+ showSnap = true,
667
+ showPathEdit = true,
668
+ showBlend = true,
669
+ className
670
+ }) {
487
671
  const state = useVectorEditorState(controller);
488
672
  const count = state.selection.length;
489
673
  const points = state.tool === "direct" ? state.selectedAnchors.length : 0;
490
674
  const editing = state.editing;
491
675
  const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
492
- const act = (label, title, run, disabled = false) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: buttonClass, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
493
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
494
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
676
+ const [live, setLive] = import_react4.default.useState(false);
677
+ const booleanDisabled = live ? count < 1 : count < 2;
678
+ const act = (label, title, run, disabled = false) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: buttonClass2, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
679
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
680
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
495
681
  "button",
496
682
  {
497
683
  type: "button",
498
684
  "data-vector-tool": tool,
499
- className: buttonClass,
685
+ className: buttonClass2,
500
686
  "aria-pressed": state.tool === tool,
501
- title: `${(0, import_vector4.vectorToolLabel)(tool)} (${import_vector4.VECTOR_TOOL_SHORTCUTS[tool]})`,
502
- "aria-label": (0, import_vector4.vectorToolLabel)(tool),
687
+ title: `${(0, import_vector5.vectorToolLabel)(tool)} (${import_vector5.VECTOR_TOOL_SHORTCUTS[tool]})`,
688
+ "aria-label": (0, import_vector5.vectorToolLabel)(tool),
503
689
  onClick: () => controller.setTool(tool),
504
- children: import_vector4.VECTOR_TOOL_GLYPHS[tool]
690
+ children: import_vector5.VECTOR_TOOL_GLYPHS[tool]
505
691
  },
506
692
  tool
507
693
  )) }),
508
- showHistory && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
694
+ showHistory && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
509
695
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
510
696
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
511
697
  ] }),
512
- showSnap && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
698
+ showSnap && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
513
699
  "button",
514
700
  {
515
701
  type: "button",
516
702
  "data-vector-snap": "",
517
- className: buttonClass,
703
+ className: buttonClass2,
518
704
  "aria-pressed": state.snap.objects,
519
705
  title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
520
706
  "aria-label": VECTOR_SNAP_TITLE,
@@ -522,13 +708,13 @@ function VectorToolbar({ controller, tools = import_vector4.VECTOR_TOOLS, showHi
522
708
  children: "Snap"
523
709
  }
524
710
  ) }),
525
- showPathEdit && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
526
- import_vector4.VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
711
+ showPathEdit && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
712
+ import_vector5.VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
527
713
  "button",
528
714
  {
529
715
  type: "button",
530
716
  "data-vector-path-action": a.id,
531
- className: buttonClass,
717
+ className: buttonClass2,
532
718
  title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
533
719
  "aria-label": a.title,
534
720
  disabled: !a.enabled(count, points),
@@ -537,12 +723,12 @@ function VectorToolbar({ controller, tools = import_vector4.VECTOR_TOOLS, showHi
537
723
  },
538
724
  a.id
539
725
  )),
540
- import_vector4.VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
726
+ import_vector5.VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
541
727
  "button",
542
728
  {
543
729
  type: "button",
544
730
  "data-vector-edit": t.key,
545
- className: buttonClass,
731
+ className: buttonClass2,
546
732
  "aria-pressed": editing[t.key],
547
733
  title: t.title,
548
734
  "aria-label": t.title,
@@ -551,9 +737,9 @@ function VectorToolbar({ controller, tools = import_vector4.VECTOR_TOOLS, showHi
551
737
  },
552
738
  t.key
553
739
  )),
554
- state.tool === "pencil" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
740
+ state.tool === "pencil" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
555
741
  "Smoothing",
556
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
742
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
557
743
  "input",
558
744
  {
559
745
  type: "range",
@@ -567,9 +753,9 @@ function VectorToolbar({ controller, tools = import_vector4.VECTOR_TOOLS, showHi
567
753
  }
568
754
  )
569
755
  ] }),
570
- state.tool === "eraser" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
756
+ state.tool === "eraser" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
571
757
  "Size",
572
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
758
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
573
759
  "input",
574
760
  {
575
761
  type: "number",
@@ -585,35 +771,49 @@ function VectorToolbar({ controller, tools = import_vector4.VECTOR_TOOLS, showHi
585
771
  }
586
772
  )
587
773
  ] }),
588
- state.tool === "paintBucket" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
774
+ state.tool === "paintBucket" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
589
775
  "button",
590
776
  {
591
777
  type: "button",
592
778
  "data-vector-fill-gap": "",
593
- className: buttonClass,
779
+ className: buttonClass2,
594
780
  "aria-pressed": editing.fillGap > 0,
595
781
  title: "Close small gaps between path ends when filling",
596
782
  "aria-label": "Close gaps",
597
- onClick: () => controller.setEditing({ fillGap: editing.fillGap > 0 ? 0 : import_vector4.VECTOR_FILL_GAP }),
783
+ onClick: () => controller.setEditing({ fillGap: editing.fillGap > 0 ? 0 : import_vector5.VECTOR_FILL_GAP }),
598
784
  children: "Gaps"
599
785
  }
600
786
  )
601
787
  ] }),
602
- showArrange && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
603
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
604
- act("\u222A", "Union", () => controller.booleanOp("union"), count < 2),
605
- act("\u2212", "Subtract", () => controller.booleanOp("subtract"), count < 2),
606
- act("\u2229", "Intersect", () => controller.booleanOp("intersect"), count < 2),
607
- act("\u22BB", "Exclude", () => controller.booleanOp("exclude"), count < 2),
788
+ showBlend && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(VectorBlendControls, { controller }),
789
+ showArrange && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
790
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
791
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
792
+ "button",
793
+ {
794
+ type: "button",
795
+ "data-vector-live-boolean": "",
796
+ className: buttonClass2,
797
+ "aria-pressed": live,
798
+ title: "Live booleans: keep the shapes editable",
799
+ "aria-label": "Live booleans",
800
+ onClick: () => setLive(!live),
801
+ children: "Live"
802
+ }
803
+ ),
804
+ act("\u222A", "Union", () => controller.booleanOp("union", { live }), booleanDisabled),
805
+ act("\u2212", "Subtract", () => controller.booleanOp("subtract", { live }), booleanDisabled),
806
+ act("\u2229", "Intersect", () => controller.booleanOp("intersect", { live }), booleanDisabled),
807
+ act("\u22BB", "Exclude", () => controller.booleanOp("exclude", { live }), booleanDisabled),
608
808
  act("G", "Group", () => controller.group(), count < 1),
609
809
  act("U", "Ungroup", () => controller.ungroup(), !groupSelected)
610
810
  ] }),
611
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: import_vector4.VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
811
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: import_vector5.VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
612
812
  "button",
613
813
  {
614
814
  type: "button",
615
815
  "data-vector-pathfinder": a.id,
616
- className: buttonClass,
816
+ className: buttonClass2,
617
817
  title: a.title,
618
818
  "aria-label": a.title,
619
819
  disabled: count < a.min,
@@ -622,7 +822,7 @@ function VectorToolbar({ controller, tools = import_vector4.VECTOR_TOOLS, showHi
622
822
  },
623
823
  a.id
624
824
  )) }),
625
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
825
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
626
826
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
627
827
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
628
828
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -635,11 +835,74 @@ function VectorToolbar({ controller, tools = import_vector4.VECTOR_TOOLS, showHi
635
835
  ] })
636
836
  ] });
637
837
  }
638
- var selectClass = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
639
- var numberClass = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
838
+ function VectorBlendControls({ controller }) {
839
+ const state = useVectorEditorState(controller);
840
+ const blend = (0, import_vector5.getVectorBlendControls)(state);
841
+ const o = blend.options;
842
+ const selected = blend.ids.length > 0;
843
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
844
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass2, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
845
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
846
+ "select",
847
+ {
848
+ "aria-label": "Blend spacing",
849
+ className: "h-7 rounded border border-border bg-background px-1 text-xs",
850
+ value: o.spacing,
851
+ onChange: (e) => (0, import_vector5.applyVectorBlendOptions)(controller, { spacing: e.target.value }),
852
+ children: import_vector5.VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: sp, children: import_vector5.VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
853
+ }
854
+ ),
855
+ o.spacing !== "smoothColor" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
856
+ "input",
857
+ {
858
+ type: "number",
859
+ min: o.spacing === "steps" ? 1 : 0.5,
860
+ step: 1,
861
+ "aria-label": o.spacing === "steps" ? "Blend steps" : "Blend distance",
862
+ className: "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs",
863
+ value: o.spacing === "steps" ? o.steps : o.distance,
864
+ onChange: (e) => {
865
+ const n = parseFloat(e.target.value);
866
+ if (!Number.isFinite(n) || n <= 0) return;
867
+ (0, import_vector5.applyVectorBlendOptions)(controller, o.spacing === "steps" ? { steps: n } : { distance: n });
868
+ }
869
+ }
870
+ ),
871
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
872
+ "button",
873
+ {
874
+ type: "button",
875
+ "data-vector-blend-action": "orient",
876
+ className: buttonClass2,
877
+ "aria-pressed": o.orientToSpine,
878
+ title: "Orient steps to the spine",
879
+ "aria-label": "Orient to spine",
880
+ onClick: () => (0, import_vector5.applyVectorBlendOptions)(controller, { orientToSpine: !o.orientToSpine }),
881
+ children: "\u293F"
882
+ }
883
+ ),
884
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
885
+ "button",
886
+ {
887
+ type: "button",
888
+ "data-vector-blend-action": "spine",
889
+ className: buttonClass2,
890
+ title: "Replace Spine (select a blend and a path)",
891
+ "aria-label": "Replace Spine",
892
+ disabled: !blend.canReplaceSpine,
893
+ onClick: () => controller.replaceBlendSpine(),
894
+ children: "\u2307"
895
+ }
896
+ ),
897
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass2, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
898
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass2, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
899
+ ] });
900
+ }
901
+ var selectClass2 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
902
+ var numberClass2 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
640
903
  function VectorStylePanel({ controller, className }) {
641
904
  const state = useVectorEditorState(controller);
642
- const style = (0, import_vector4.getVectorSelectionStyle)(state);
905
+ const style = (0, import_vector5.getVectorSelectionStyle)(state);
643
906
  const d = state.defaults;
644
907
  const source = style ?? {
645
908
  fill: d.fill ?? null,
@@ -652,7 +915,7 @@ function VectorStylePanel({ controller, className }) {
652
915
  arrowAlign: d.arrowAlign ?? "extend",
653
916
  startArrowScale: d.startArrowScale ?? 1,
654
917
  endArrowScale: d.endArrowScale ?? 1,
655
- widthProfile: (0, import_vector4.widthProfilePresetId)(d.strokeWidthProfile),
918
+ widthProfile: (0, import_vector5.widthProfilePresetId)(d.strokeWidthProfile),
656
919
  cornerRadius: d.cornerRadius,
657
920
  cornerKind: d.cornerKind ?? "round",
658
921
  corners: 1,
@@ -665,19 +928,19 @@ function VectorStylePanel({ controller, className }) {
665
928
  };
666
929
  const setProfile = (id) => {
667
930
  if (style) controller.setWidthProfile(id);
668
- else controller.setDefaults({ strokeWidthProfile: (0, import_vector4.widthProfilePreset)(id)?.length ? (0, import_vector4.widthProfilePreset)(id) : null });
931
+ else controller.setDefaults({ strokeWidthProfile: (0, import_vector5.widthProfilePreset)(id)?.length ? (0, import_vector5.widthProfilePreset)(id) : null });
669
932
  };
670
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
933
+ const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
671
934
  const arrowRow = (end, label) => {
672
935
  const key = end === "start" ? "startArrow" : "endArrow";
673
936
  const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
674
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
675
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "w-16 text-xs text-foreground", children: label }),
676
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("select", { "aria-label": `${label} arrowhead`, className: selectClass, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
677
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "None" }),
678
- import_vector4.VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: h, children: import_vector4.VECTOR_ARROWHEAD_LABELS[h] }, h))
937
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
938
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "w-16 text-xs text-foreground", children: label }),
939
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("select", { "aria-label": `${label} arrowhead`, className: selectClass2, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
940
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "", children: "None" }),
941
+ import_vector5.VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: h, children: import_vector5.VECTOR_ARROWHEAD_LABELS[h] }, h))
679
942
  ] }),
680
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
943
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
681
944
  "input",
682
945
  {
683
946
  type: "number",
@@ -685,7 +948,7 @@ function VectorStylePanel({ controller, className }) {
685
948
  step: 0.25,
686
949
  "aria-label": `${label} arrowhead scale`,
687
950
  title: "Scale (\xD7 stroke width)",
688
- className: numberClass,
951
+ className: numberClass2,
689
952
  value: source[scaleKey],
690
953
  onChange: (e) => {
691
954
  const n = parseFloat(e.target.value);
@@ -701,14 +964,14 @@ function VectorStylePanel({ controller, className }) {
701
964
  };
702
965
  const paintRow = (key, label) => {
703
966
  const paint = source[key];
704
- const hex = (0, import_vector4.vectorPaintToHex)(paint);
967
+ const hex = (0, import_vector5.vectorPaintToHex)(paint);
705
968
  const on = paint !== null && paint !== "none";
706
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
707
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
708
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
969
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
970
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
971
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
709
972
  label
710
973
  ] }),
711
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
974
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
712
975
  "input",
713
976
  {
714
977
  type: "color",
@@ -719,17 +982,17 @@ function VectorStylePanel({ controller, className }) {
719
982
  onChange: (e) => apply({ [key]: e.target.value })
720
983
  }
721
984
  ),
722
- on && !hex && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
723
- style?.mixed[key] && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
985
+ on && !hex && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
986
+ style?.mixed[key] && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
724
987
  ] });
725
988
  };
726
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
727
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
989
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
990
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
728
991
  paintRow("fill", "Fill"),
729
992
  paintRow("stroke", "Stroke"),
730
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
731
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "w-16", children: "Width" }),
732
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
993
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
994
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "w-16", children: "Width" }),
995
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
733
996
  "input",
734
997
  {
735
998
  type: "number",
@@ -745,33 +1008,33 @@ function VectorStylePanel({ controller, className }) {
745
1008
  }
746
1009
  )
747
1010
  ] }),
748
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
749
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "w-16", children: "Align" }),
750
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("select", { "aria-label": "Stroke alignment", className: selectClass, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(import_vector4.VECTOR_STROKE_ALIGN_LABELS).map((a) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: a, children: import_vector4.VECTOR_STROKE_ALIGN_LABELS[a] }, a)) }),
1011
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
1012
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "w-16", children: "Align" }),
1013
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { "aria-label": "Stroke alignment", className: selectClass2, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(import_vector5.VECTOR_STROKE_ALIGN_LABELS).map((a) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: a, children: import_vector5.VECTOR_STROKE_ALIGN_LABELS[a] }, a)) }),
751
1014
  mixed("strokeAlign")
752
1015
  ] }),
753
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
754
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "w-16", children: "Profile" }),
755
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1016
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
1017
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "w-16", children: "Profile" }),
1018
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
756
1019
  "select",
757
1020
  {
758
1021
  "aria-label": "Width profile",
759
- className: selectClass,
1022
+ className: selectClass2,
760
1023
  value: source.widthProfile,
761
1024
  onChange: (e) => {
762
1025
  if (e.target.value !== "custom") setProfile(e.target.value);
763
1026
  },
764
1027
  children: [
765
- import_vector4.VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: p.id, children: p.label }, p.id)),
766
- source.widthProfile === "custom" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "custom", disabled: true, children: "Custom" })
1028
+ import_vector5.VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: p.id, children: p.label }, p.id)),
1029
+ source.widthProfile === "custom" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "custom", disabled: true, children: "Custom" })
767
1030
  ]
768
1031
  }
769
1032
  ),
770
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1033
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
771
1034
  "button",
772
1035
  {
773
1036
  type: "button",
774
- className: buttonClass,
1037
+ className: buttonClass2,
775
1038
  title: "Reset to uniform width",
776
1039
  "aria-label": "Reset to uniform width",
777
1040
  disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
@@ -781,27 +1044,27 @@ function VectorStylePanel({ controller, className }) {
781
1044
  ),
782
1045
  mixed("widthProfile")
783
1046
  ] }),
784
- source.hasOpenPath && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1047
+ source.hasOpenPath && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
785
1048
  arrowRow("start", "Start"),
786
1049
  arrowRow("end", "End"),
787
- (source.startArrow || source.endArrow) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
788
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "w-16", children: "Heads" }),
789
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("select", { "aria-label": "Arrowhead alignment", className: selectClass, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
790
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "extend", children: "Tip past end" }),
791
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "tip", children: "Tip at end" })
1050
+ (source.startArrow || source.endArrow) && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
1051
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "w-16", children: "Heads" }),
1052
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("select", { "aria-label": "Arrowhead alignment", className: selectClass2, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
1053
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "extend", children: "Tip past end" }),
1054
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "tip", children: "Tip at end" })
792
1055
  ] })
793
1056
  ] })
794
1057
  ] }),
795
- source.corners > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
796
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
797
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1058
+ source.corners > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
1059
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
1060
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
798
1061
  "input",
799
1062
  {
800
1063
  type: "number",
801
1064
  min: 0,
802
1065
  step: 1,
803
1066
  "aria-label": "Corner radius",
804
- className: numberClass,
1067
+ className: numberClass2,
805
1068
  value: Math.round(source.cornerRadius * 100) / 100,
806
1069
  onChange: (e) => {
807
1070
  const n = parseFloat(e.target.value);
@@ -809,12 +1072,12 @@ function VectorStylePanel({ controller, className }) {
809
1072
  }
810
1073
  }
811
1074
  ),
812
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("select", { "aria-label": "Corner kind", className: selectClass, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: import_vector4.VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: k, children: import_vector4.VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
1075
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { "aria-label": "Corner kind", className: selectClass2, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: import_vector5.VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: k, children: import_vector5.VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
813
1076
  mixed("cornerRadius")
814
1077
  ] }),
815
- style && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
816
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "w-16", children: "Opacity" }),
817
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1078
+ style && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
1079
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "w-16", children: "Opacity" }),
1080
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
818
1081
  "input",
819
1082
  {
820
1083
  type: "range",
@@ -827,18 +1090,19 @@ function VectorStylePanel({ controller, className }) {
827
1090
  onChange: (e) => apply({ opacity: parseFloat(e.target.value) })
828
1091
  }
829
1092
  )
830
- ] })
1093
+ ] }),
1094
+ style && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(VectorEffectsPanel, { controller })
831
1095
  ] });
832
1096
  }
833
1097
 
834
1098
  // src/vector/VectorEditor.tsx
835
- var import_jsx_runtime4 = require("react/jsx-runtime");
1099
+ var import_jsx_runtime5 = require("react/jsx-runtime");
836
1100
  function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }) {
837
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
838
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(VectorToolbar, { controller, tools }),
839
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
840
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(VectorCanvas, { controller, ...canvasProps }) }),
841
- showStylePanel && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
1101
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
1102
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(VectorToolbar, { controller, tools }),
1103
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
1104
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(VectorCanvas, { controller, ...canvasProps }) }),
1105
+ showStylePanel && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
842
1106
  ] })
843
1107
  ] });
844
1108
  }
@@ -846,6 +1110,7 @@ function VectorEditor({ controller, tools, showStylePanel = true, height = 520,
846
1110
  0 && (module.exports = {
847
1111
  VectorCanvas,
848
1112
  VectorEditor,
1113
+ VectorEffectsPanel,
849
1114
  VectorGradientDefs,
850
1115
  VectorSceneNodes,
851
1116
  VectorStylePanel,