@pixerate/editor-react 0.31.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.
- package/dist/vector/index.cjs +277 -106
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +14 -2
- package/dist/vector/index.d.ts +14 -2
- package/dist/vector/index.js +257 -94
- package/dist/vector/index.js.map +1 -1
- package/package.json +3 -3
package/dist/vector/index.js
CHANGED
|
@@ -474,6 +474,7 @@ function VectorCanvas({
|
|
|
474
474
|
}
|
|
475
475
|
|
|
476
476
|
// src/vector/VectorToolbar.tsx
|
|
477
|
+
import React3 from "react";
|
|
477
478
|
import {
|
|
478
479
|
VECTOR_TOOLS,
|
|
479
480
|
VECTOR_TOOL_GLYPHS,
|
|
@@ -498,10 +499,155 @@ import {
|
|
|
498
499
|
vectorPaintToHex,
|
|
499
500
|
vectorToolLabel
|
|
500
501
|
} from "@pixerate/editor/vector";
|
|
501
|
-
|
|
502
|
+
|
|
503
|
+
// src/vector/VectorEffectsPanel.tsx
|
|
504
|
+
import {
|
|
505
|
+
VECTOR_EFFECT_MENU,
|
|
506
|
+
getVectorEffectsPanel
|
|
507
|
+
} from "@pixerate/editor/vector";
|
|
508
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
509
|
+
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";
|
|
510
|
+
var numberClass = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
|
|
511
|
+
var selectClass = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
|
|
512
|
+
var labelClass = "w-full truncate text-[11px] text-muted-foreground";
|
|
513
|
+
function VectorEffectsPanel({ controller, className }) {
|
|
514
|
+
const state = useVectorEditorState(controller);
|
|
515
|
+
const panel = getVectorEffectsPanel(state);
|
|
516
|
+
if (!panel) return null;
|
|
517
|
+
const nodeId = panel.nodeId;
|
|
518
|
+
const end = () => controller.endMergedEdit();
|
|
519
|
+
const set = (e, key, value, merge) => {
|
|
520
|
+
if (nodeId) controller.updateEffect(nodeId, e.id, { params: { [key]: value } }, { merge });
|
|
521
|
+
};
|
|
522
|
+
const number = (e, spec) => {
|
|
523
|
+
const value = Number(e.params[spec.key] ?? 0);
|
|
524
|
+
const label = `${e.label} ${spec.label}`;
|
|
525
|
+
const onChange = (ev) => {
|
|
526
|
+
const n = parseFloat(ev.target.value);
|
|
527
|
+
if (Number.isFinite(n)) set(e, spec.key, n, true);
|
|
528
|
+
};
|
|
529
|
+
return /* @__PURE__ */ jsxs3("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: [
|
|
530
|
+
/* @__PURE__ */ jsx3("span", { className: labelClass, children: spec.label }),
|
|
531
|
+
spec.slider && /* @__PURE__ */ jsx3(
|
|
532
|
+
"input",
|
|
533
|
+
{
|
|
534
|
+
type: "range",
|
|
535
|
+
min: spec.min,
|
|
536
|
+
max: spec.max,
|
|
537
|
+
step: spec.step,
|
|
538
|
+
"aria-label": `${label} slider`,
|
|
539
|
+
className: "h-4 min-w-0 flex-1 accent-primary",
|
|
540
|
+
value,
|
|
541
|
+
onChange,
|
|
542
|
+
onPointerUp: end,
|
|
543
|
+
onKeyUp: end,
|
|
544
|
+
onBlur: end
|
|
545
|
+
}
|
|
546
|
+
),
|
|
547
|
+
/* @__PURE__ */ jsx3(
|
|
548
|
+
"input",
|
|
549
|
+
{
|
|
550
|
+
type: "number",
|
|
551
|
+
min: spec.min,
|
|
552
|
+
max: spec.max,
|
|
553
|
+
step: spec.step,
|
|
554
|
+
"aria-label": label,
|
|
555
|
+
className: numberClass,
|
|
556
|
+
value: Math.round(value * 1e3) / 1e3,
|
|
557
|
+
onChange,
|
|
558
|
+
onBlur: end
|
|
559
|
+
}
|
|
560
|
+
)
|
|
561
|
+
] }, spec.key);
|
|
562
|
+
};
|
|
563
|
+
const vec2 = (e, spec) => {
|
|
564
|
+
const v = e.params[spec.key] ?? { x: 0, y: 0 };
|
|
565
|
+
const field = (axis) => /* @__PURE__ */ jsx3(
|
|
566
|
+
"input",
|
|
567
|
+
{
|
|
568
|
+
type: "number",
|
|
569
|
+
step: spec.kind === "vec2" ? spec.step : 1,
|
|
570
|
+
"aria-label": `${e.label} ${spec.label} ${axis}`,
|
|
571
|
+
className: numberClass,
|
|
572
|
+
value: Math.round(v[axis] * 1e3) / 1e3,
|
|
573
|
+
onChange: (ev) => {
|
|
574
|
+
const n = parseFloat(ev.target.value);
|
|
575
|
+
if (Number.isFinite(n)) set(e, spec.key, { ...v, [axis]: n }, true);
|
|
576
|
+
},
|
|
577
|
+
onBlur: end
|
|
578
|
+
}
|
|
579
|
+
);
|
|
580
|
+
return /* @__PURE__ */ jsxs3("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: [
|
|
581
|
+
/* @__PURE__ */ jsx3("span", { className: labelClass, children: spec.label }),
|
|
582
|
+
field("x"),
|
|
583
|
+
field("y")
|
|
584
|
+
] }, spec.key);
|
|
585
|
+
};
|
|
586
|
+
const param = (e, spec) => {
|
|
587
|
+
if (spec.kind === "number") return number(e, spec);
|
|
588
|
+
if (spec.kind === "vec2") return vec2(e, spec);
|
|
589
|
+
if (spec.kind === "boolean")
|
|
590
|
+
return /* @__PURE__ */ jsxs3("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-effect-param": spec.key, children: [
|
|
591
|
+
/* @__PURE__ */ jsx3("span", { className: "min-w-0 flex-1 truncate", children: spec.label }),
|
|
592
|
+
/* @__PURE__ */ jsx3("input", { type: "checkbox", "aria-label": `${e.label} ${spec.label}`, checked: !!e.params[spec.key], onChange: (ev) => set(e, spec.key, ev.target.checked, false) })
|
|
593
|
+
] }, spec.key);
|
|
594
|
+
return /* @__PURE__ */ jsxs3("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: [
|
|
595
|
+
/* @__PURE__ */ jsx3("span", { className: labelClass, children: spec.label }),
|
|
596
|
+
/* @__PURE__ */ jsx3("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__ */ jsx3("option", { value: o.value, children: o.label }, o.value)) })
|
|
597
|
+
] }, spec.key);
|
|
598
|
+
};
|
|
599
|
+
return /* @__PURE__ */ jsxs3("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-effects": "", "aria-label": "Effects", children: [
|
|
600
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
601
|
+
/* @__PURE__ */ jsx3("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Effects" }),
|
|
602
|
+
/* @__PURE__ */ jsxs3(
|
|
603
|
+
"select",
|
|
604
|
+
{
|
|
605
|
+
"aria-label": "Add effect",
|
|
606
|
+
className: selectClass,
|
|
607
|
+
value: "",
|
|
608
|
+
onChange: (ev) => {
|
|
609
|
+
if (ev.target.value) controller.addEffect(ev.target.value);
|
|
610
|
+
},
|
|
611
|
+
children: [
|
|
612
|
+
/* @__PURE__ */ jsx3("option", { value: "", children: "Add effect\u2026" }),
|
|
613
|
+
VECTOR_EFFECT_MENU.map((m) => /* @__PURE__ */ jsx3("option", { value: m.type, children: m.label }, m.type))
|
|
614
|
+
]
|
|
615
|
+
}
|
|
616
|
+
),
|
|
617
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, title: "Expand Appearance", "aria-label": "Expand Appearance", disabled: !panel.expandable, onClick: () => controller.expandAppearance(), children: "Expand" })
|
|
618
|
+
] }),
|
|
619
|
+
panel.count > 1 && /* @__PURE__ */ jsx3("p", { className: "text-[11px] text-muted-foreground", children: "Select one object to edit its effects." }),
|
|
620
|
+
nodeId && panel.effects.map((e, i) => /* @__PURE__ */ jsxs3("div", { className: "space-y-1 rounded border border-border p-1.5", "data-vector-effect": e.id, "data-vector-effect-type": e.type, children: [
|
|
621
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1", children: [
|
|
622
|
+
/* @__PURE__ */ jsxs3("label", { className: "flex min-w-0 flex-1 items-center gap-1.5 text-xs font-medium text-foreground", children: [
|
|
623
|
+
/* @__PURE__ */ jsx3("input", { type: "checkbox", "aria-label": `${e.label} on`, checked: e.enabled, onChange: (ev) => controller.updateEffect(nodeId, e.id, { enabled: ev.target.checked }) }),
|
|
624
|
+
/* @__PURE__ */ jsx3("span", { className: "truncate", children: e.label })
|
|
625
|
+
] }),
|
|
626
|
+
/* @__PURE__ */ jsx3("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" }),
|
|
627
|
+
/* @__PURE__ */ jsx3(
|
|
628
|
+
"button",
|
|
629
|
+
{
|
|
630
|
+
type: "button",
|
|
631
|
+
className: buttonClass,
|
|
632
|
+
title: "Move down",
|
|
633
|
+
"aria-label": `Move ${e.label} down`,
|
|
634
|
+
disabled: i === panel.effects.length - 1,
|
|
635
|
+
onClick: () => controller.moveEffect(nodeId, e.id, i + 1),
|
|
636
|
+
children: "\u2193"
|
|
637
|
+
}
|
|
638
|
+
),
|
|
639
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, title: "Remove", "aria-label": `Remove ${e.label}`, onClick: () => controller.removeEffect(nodeId, e.id), children: "\xD7" })
|
|
640
|
+
] }),
|
|
641
|
+
e.specs.map((spec) => param(e, spec))
|
|
642
|
+
] }, e.id))
|
|
643
|
+
] });
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// src/vector/VectorToolbar.tsx
|
|
647
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
502
648
|
var VECTOR_SNAP_TITLE = "Snap to objects";
|
|
503
649
|
var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
|
|
504
|
-
var
|
|
650
|
+
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";
|
|
505
651
|
function VectorToolbar({
|
|
506
652
|
controller,
|
|
507
653
|
tools = VECTOR_TOOLS,
|
|
@@ -517,14 +663,16 @@ function VectorToolbar({
|
|
|
517
663
|
const points = state.tool === "direct" ? state.selectedAnchors.length : 0;
|
|
518
664
|
const editing = state.editing;
|
|
519
665
|
const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
666
|
+
const [live, setLive] = React3.useState(false);
|
|
667
|
+
const booleanDisabled = live ? count < 1 : count < 2;
|
|
668
|
+
const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx4("button", { type: "button", className: buttonClass2, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
|
|
669
|
+
return /* @__PURE__ */ jsxs4("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
|
|
670
|
+
/* @__PURE__ */ jsx4("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx4(
|
|
523
671
|
"button",
|
|
524
672
|
{
|
|
525
673
|
type: "button",
|
|
526
674
|
"data-vector-tool": tool,
|
|
527
|
-
className:
|
|
675
|
+
className: buttonClass2,
|
|
528
676
|
"aria-pressed": state.tool === tool,
|
|
529
677
|
title: `${vectorToolLabel(tool)} (${VECTOR_TOOL_SHORTCUTS[tool]})`,
|
|
530
678
|
"aria-label": vectorToolLabel(tool),
|
|
@@ -533,16 +681,16 @@ function VectorToolbar({
|
|
|
533
681
|
},
|
|
534
682
|
tool
|
|
535
683
|
)) }),
|
|
536
|
-
showHistory && /* @__PURE__ */
|
|
684
|
+
showHistory && /* @__PURE__ */ jsxs4("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
|
|
537
685
|
act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
|
|
538
686
|
act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
|
|
539
687
|
] }),
|
|
540
|
-
showSnap && /* @__PURE__ */
|
|
688
|
+
showSnap && /* @__PURE__ */ jsx4("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: /* @__PURE__ */ jsx4(
|
|
541
689
|
"button",
|
|
542
690
|
{
|
|
543
691
|
type: "button",
|
|
544
692
|
"data-vector-snap": "",
|
|
545
|
-
className:
|
|
693
|
+
className: buttonClass2,
|
|
546
694
|
"aria-pressed": state.snap.objects,
|
|
547
695
|
title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
|
|
548
696
|
"aria-label": VECTOR_SNAP_TITLE,
|
|
@@ -550,13 +698,13 @@ function VectorToolbar({
|
|
|
550
698
|
children: "Snap"
|
|
551
699
|
}
|
|
552
700
|
) }),
|
|
553
|
-
showPathEdit && /* @__PURE__ */
|
|
554
|
-
VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */
|
|
701
|
+
showPathEdit && /* @__PURE__ */ jsxs4("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
|
|
702
|
+
VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx4(
|
|
555
703
|
"button",
|
|
556
704
|
{
|
|
557
705
|
type: "button",
|
|
558
706
|
"data-vector-path-action": a.id,
|
|
559
|
-
className:
|
|
707
|
+
className: buttonClass2,
|
|
560
708
|
title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
|
|
561
709
|
"aria-label": a.title,
|
|
562
710
|
disabled: !a.enabled(count, points),
|
|
@@ -565,12 +713,12 @@ function VectorToolbar({
|
|
|
565
713
|
},
|
|
566
714
|
a.id
|
|
567
715
|
)),
|
|
568
|
-
VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */
|
|
716
|
+
VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx4(
|
|
569
717
|
"button",
|
|
570
718
|
{
|
|
571
719
|
type: "button",
|
|
572
720
|
"data-vector-edit": t.key,
|
|
573
|
-
className:
|
|
721
|
+
className: buttonClass2,
|
|
574
722
|
"aria-pressed": editing[t.key],
|
|
575
723
|
title: t.title,
|
|
576
724
|
"aria-label": t.title,
|
|
@@ -579,9 +727,9 @@ function VectorToolbar({
|
|
|
579
727
|
},
|
|
580
728
|
t.key
|
|
581
729
|
)),
|
|
582
|
-
state.tool === "pencil" && /* @__PURE__ */
|
|
730
|
+
state.tool === "pencil" && /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
|
|
583
731
|
"Smoothing",
|
|
584
|
-
/* @__PURE__ */
|
|
732
|
+
/* @__PURE__ */ jsx4(
|
|
585
733
|
"input",
|
|
586
734
|
{
|
|
587
735
|
type: "range",
|
|
@@ -595,9 +743,9 @@ function VectorToolbar({
|
|
|
595
743
|
}
|
|
596
744
|
)
|
|
597
745
|
] }),
|
|
598
|
-
state.tool === "eraser" && /* @__PURE__ */
|
|
746
|
+
state.tool === "eraser" && /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
|
|
599
747
|
"Size",
|
|
600
|
-
/* @__PURE__ */
|
|
748
|
+
/* @__PURE__ */ jsx4(
|
|
601
749
|
"input",
|
|
602
750
|
{
|
|
603
751
|
type: "number",
|
|
@@ -613,12 +761,12 @@ function VectorToolbar({
|
|
|
613
761
|
}
|
|
614
762
|
)
|
|
615
763
|
] }),
|
|
616
|
-
state.tool === "paintBucket" && /* @__PURE__ */
|
|
764
|
+
state.tool === "paintBucket" && /* @__PURE__ */ jsx4(
|
|
617
765
|
"button",
|
|
618
766
|
{
|
|
619
767
|
type: "button",
|
|
620
768
|
"data-vector-fill-gap": "",
|
|
621
|
-
className:
|
|
769
|
+
className: buttonClass2,
|
|
622
770
|
"aria-pressed": editing.fillGap > 0,
|
|
623
771
|
title: "Close small gaps between path ends when filling",
|
|
624
772
|
"aria-label": "Close gaps",
|
|
@@ -627,22 +775,35 @@ function VectorToolbar({
|
|
|
627
775
|
}
|
|
628
776
|
)
|
|
629
777
|
] }),
|
|
630
|
-
showBlend && /* @__PURE__ */
|
|
631
|
-
showArrange && /* @__PURE__ */
|
|
632
|
-
/* @__PURE__ */
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
778
|
+
showBlend && /* @__PURE__ */ jsx4(VectorBlendControls, { controller }),
|
|
779
|
+
showArrange && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
780
|
+
/* @__PURE__ */ jsxs4("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
|
|
781
|
+
/* @__PURE__ */ jsx4(
|
|
782
|
+
"button",
|
|
783
|
+
{
|
|
784
|
+
type: "button",
|
|
785
|
+
"data-vector-live-boolean": "",
|
|
786
|
+
className: buttonClass2,
|
|
787
|
+
"aria-pressed": live,
|
|
788
|
+
title: "Live booleans: keep the shapes editable",
|
|
789
|
+
"aria-label": "Live booleans",
|
|
790
|
+
onClick: () => setLive(!live),
|
|
791
|
+
children: "Live"
|
|
792
|
+
}
|
|
793
|
+
),
|
|
794
|
+
act("\u222A", "Union", () => controller.booleanOp("union", { live }), booleanDisabled),
|
|
795
|
+
act("\u2212", "Subtract", () => controller.booleanOp("subtract", { live }), booleanDisabled),
|
|
796
|
+
act("\u2229", "Intersect", () => controller.booleanOp("intersect", { live }), booleanDisabled),
|
|
797
|
+
act("\u22BB", "Exclude", () => controller.booleanOp("exclude", { live }), booleanDisabled),
|
|
637
798
|
act("G", "Group", () => controller.group(), count < 1),
|
|
638
799
|
act("U", "Ungroup", () => controller.ungroup(), !groupSelected)
|
|
639
800
|
] }),
|
|
640
|
-
/* @__PURE__ */
|
|
801
|
+
/* @__PURE__ */ jsx4("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx4(
|
|
641
802
|
"button",
|
|
642
803
|
{
|
|
643
804
|
type: "button",
|
|
644
805
|
"data-vector-pathfinder": a.id,
|
|
645
|
-
className:
|
|
806
|
+
className: buttonClass2,
|
|
646
807
|
title: a.title,
|
|
647
808
|
"aria-label": a.title,
|
|
648
809
|
disabled: count < a.min,
|
|
@@ -651,7 +812,7 @@ function VectorToolbar({
|
|
|
651
812
|
},
|
|
652
813
|
a.id
|
|
653
814
|
)) }),
|
|
654
|
-
/* @__PURE__ */
|
|
815
|
+
/* @__PURE__ */ jsxs4("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
|
|
655
816
|
act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
|
|
656
817
|
act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
|
|
657
818
|
act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
|
|
@@ -669,19 +830,19 @@ function VectorBlendControls({ controller }) {
|
|
|
669
830
|
const blend = getVectorBlendControls(state);
|
|
670
831
|
const o = blend.options;
|
|
671
832
|
const selected = blend.ids.length > 0;
|
|
672
|
-
return /* @__PURE__ */
|
|
673
|
-
/* @__PURE__ */
|
|
674
|
-
/* @__PURE__ */
|
|
833
|
+
return /* @__PURE__ */ jsxs4("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
|
|
834
|
+
/* @__PURE__ */ jsx4("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" }),
|
|
835
|
+
/* @__PURE__ */ jsx4(
|
|
675
836
|
"select",
|
|
676
837
|
{
|
|
677
838
|
"aria-label": "Blend spacing",
|
|
678
839
|
className: "h-7 rounded border border-border bg-background px-1 text-xs",
|
|
679
840
|
value: o.spacing,
|
|
680
841
|
onChange: (e) => applyVectorBlendOptions(controller, { spacing: e.target.value }),
|
|
681
|
-
children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */
|
|
842
|
+
children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx4("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
|
|
682
843
|
}
|
|
683
844
|
),
|
|
684
|
-
o.spacing !== "smoothColor" && /* @__PURE__ */
|
|
845
|
+
o.spacing !== "smoothColor" && /* @__PURE__ */ jsx4(
|
|
685
846
|
"input",
|
|
686
847
|
{
|
|
687
848
|
type: "number",
|
|
@@ -697,12 +858,12 @@ function VectorBlendControls({ controller }) {
|
|
|
697
858
|
}
|
|
698
859
|
}
|
|
699
860
|
),
|
|
700
|
-
/* @__PURE__ */
|
|
861
|
+
/* @__PURE__ */ jsx4(
|
|
701
862
|
"button",
|
|
702
863
|
{
|
|
703
864
|
type: "button",
|
|
704
865
|
"data-vector-blend-action": "orient",
|
|
705
|
-
className:
|
|
866
|
+
className: buttonClass2,
|
|
706
867
|
"aria-pressed": o.orientToSpine,
|
|
707
868
|
title: "Orient steps to the spine",
|
|
708
869
|
"aria-label": "Orient to spine",
|
|
@@ -710,12 +871,12 @@ function VectorBlendControls({ controller }) {
|
|
|
710
871
|
children: "\u293F"
|
|
711
872
|
}
|
|
712
873
|
),
|
|
713
|
-
/* @__PURE__ */
|
|
874
|
+
/* @__PURE__ */ jsx4(
|
|
714
875
|
"button",
|
|
715
876
|
{
|
|
716
877
|
type: "button",
|
|
717
878
|
"data-vector-blend-action": "spine",
|
|
718
|
-
className:
|
|
879
|
+
className: buttonClass2,
|
|
719
880
|
title: "Replace Spine (select a blend and a path)",
|
|
720
881
|
"aria-label": "Replace Spine",
|
|
721
882
|
disabled: !blend.canReplaceSpine,
|
|
@@ -723,12 +884,12 @@ function VectorBlendControls({ controller }) {
|
|
|
723
884
|
children: "\u2307"
|
|
724
885
|
}
|
|
725
886
|
),
|
|
726
|
-
/* @__PURE__ */
|
|
727
|
-
/* @__PURE__ */
|
|
887
|
+
/* @__PURE__ */ jsx4("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass2, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
|
|
888
|
+
/* @__PURE__ */ jsx4("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass2, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
|
|
728
889
|
] });
|
|
729
890
|
}
|
|
730
|
-
var
|
|
731
|
-
var
|
|
891
|
+
var selectClass2 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
|
|
892
|
+
var numberClass2 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
|
|
732
893
|
function VectorStylePanel({ controller, className }) {
|
|
733
894
|
const state = useVectorEditorState(controller);
|
|
734
895
|
const style = getVectorSelectionStyle(state);
|
|
@@ -759,17 +920,17 @@ function VectorStylePanel({ controller, className }) {
|
|
|
759
920
|
if (style) controller.setWidthProfile(id);
|
|
760
921
|
else controller.setDefaults({ strokeWidthProfile: widthProfilePreset(id)?.length ? widthProfilePreset(id) : null });
|
|
761
922
|
};
|
|
762
|
-
const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */
|
|
923
|
+
const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx4("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
|
|
763
924
|
const arrowRow = (end, label) => {
|
|
764
925
|
const key = end === "start" ? "startArrow" : "endArrow";
|
|
765
926
|
const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
|
|
766
|
-
return /* @__PURE__ */
|
|
767
|
-
/* @__PURE__ */
|
|
768
|
-
/* @__PURE__ */
|
|
769
|
-
/* @__PURE__ */
|
|
770
|
-
VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */
|
|
927
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
|
|
928
|
+
/* @__PURE__ */ jsx4("span", { className: "w-16 text-xs text-foreground", children: label }),
|
|
929
|
+
/* @__PURE__ */ jsxs4("select", { "aria-label": `${label} arrowhead`, className: selectClass2, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
|
|
930
|
+
/* @__PURE__ */ jsx4("option", { value: "", children: "None" }),
|
|
931
|
+
VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx4("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
|
|
771
932
|
] }),
|
|
772
|
-
/* @__PURE__ */
|
|
933
|
+
/* @__PURE__ */ jsx4(
|
|
773
934
|
"input",
|
|
774
935
|
{
|
|
775
936
|
type: "number",
|
|
@@ -777,7 +938,7 @@ function VectorStylePanel({ controller, className }) {
|
|
|
777
938
|
step: 0.25,
|
|
778
939
|
"aria-label": `${label} arrowhead scale`,
|
|
779
940
|
title: "Scale (\xD7 stroke width)",
|
|
780
|
-
className:
|
|
941
|
+
className: numberClass2,
|
|
781
942
|
value: source[scaleKey],
|
|
782
943
|
onChange: (e) => {
|
|
783
944
|
const n = parseFloat(e.target.value);
|
|
@@ -795,12 +956,12 @@ function VectorStylePanel({ controller, className }) {
|
|
|
795
956
|
const paint = source[key];
|
|
796
957
|
const hex = vectorPaintToHex(paint);
|
|
797
958
|
const on = paint !== null && paint !== "none";
|
|
798
|
-
return /* @__PURE__ */
|
|
799
|
-
/* @__PURE__ */
|
|
800
|
-
/* @__PURE__ */
|
|
959
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
|
|
960
|
+
/* @__PURE__ */ jsxs4("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
|
|
961
|
+
/* @__PURE__ */ jsx4("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
|
|
801
962
|
label
|
|
802
963
|
] }),
|
|
803
|
-
/* @__PURE__ */
|
|
964
|
+
/* @__PURE__ */ jsx4(
|
|
804
965
|
"input",
|
|
805
966
|
{
|
|
806
967
|
type: "color",
|
|
@@ -811,17 +972,17 @@ function VectorStylePanel({ controller, className }) {
|
|
|
811
972
|
onChange: (e) => apply({ [key]: e.target.value })
|
|
812
973
|
}
|
|
813
974
|
),
|
|
814
|
-
on && !hex && /* @__PURE__ */
|
|
815
|
-
style?.mixed[key] && /* @__PURE__ */
|
|
975
|
+
on && !hex && /* @__PURE__ */ jsx4("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
|
|
976
|
+
style?.mixed[key] && /* @__PURE__ */ jsx4("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
|
|
816
977
|
] });
|
|
817
978
|
};
|
|
818
|
-
return /* @__PURE__ */
|
|
819
|
-
/* @__PURE__ */
|
|
979
|
+
return /* @__PURE__ */ jsxs4("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
|
|
980
|
+
/* @__PURE__ */ jsx4("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
|
|
820
981
|
paintRow("fill", "Fill"),
|
|
821
982
|
paintRow("stroke", "Stroke"),
|
|
822
|
-
/* @__PURE__ */
|
|
823
|
-
/* @__PURE__ */
|
|
824
|
-
/* @__PURE__ */
|
|
983
|
+
/* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
|
|
984
|
+
/* @__PURE__ */ jsx4("span", { className: "w-16", children: "Width" }),
|
|
985
|
+
/* @__PURE__ */ jsx4(
|
|
825
986
|
"input",
|
|
826
987
|
{
|
|
827
988
|
type: "number",
|
|
@@ -837,33 +998,33 @@ function VectorStylePanel({ controller, className }) {
|
|
|
837
998
|
}
|
|
838
999
|
)
|
|
839
1000
|
] }),
|
|
840
|
-
/* @__PURE__ */
|
|
841
|
-
/* @__PURE__ */
|
|
842
|
-
/* @__PURE__ */
|
|
1001
|
+
/* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
|
|
1002
|
+
/* @__PURE__ */ jsx4("span", { className: "w-16", children: "Align" }),
|
|
1003
|
+
/* @__PURE__ */ jsx4("select", { "aria-label": "Stroke alignment", className: selectClass2, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(VECTOR_STROKE_ALIGN_LABELS).map((a) => /* @__PURE__ */ jsx4("option", { value: a, children: VECTOR_STROKE_ALIGN_LABELS[a] }, a)) }),
|
|
843
1004
|
mixed("strokeAlign")
|
|
844
1005
|
] }),
|
|
845
|
-
/* @__PURE__ */
|
|
846
|
-
/* @__PURE__ */
|
|
847
|
-
/* @__PURE__ */
|
|
1006
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
|
|
1007
|
+
/* @__PURE__ */ jsx4("span", { className: "w-16", children: "Profile" }),
|
|
1008
|
+
/* @__PURE__ */ jsxs4(
|
|
848
1009
|
"select",
|
|
849
1010
|
{
|
|
850
1011
|
"aria-label": "Width profile",
|
|
851
|
-
className:
|
|
1012
|
+
className: selectClass2,
|
|
852
1013
|
value: source.widthProfile,
|
|
853
1014
|
onChange: (e) => {
|
|
854
1015
|
if (e.target.value !== "custom") setProfile(e.target.value);
|
|
855
1016
|
},
|
|
856
1017
|
children: [
|
|
857
|
-
VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */
|
|
858
|
-
source.widthProfile === "custom" && /* @__PURE__ */
|
|
1018
|
+
VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx4("option", { value: p.id, children: p.label }, p.id)),
|
|
1019
|
+
source.widthProfile === "custom" && /* @__PURE__ */ jsx4("option", { value: "custom", disabled: true, children: "Custom" })
|
|
859
1020
|
]
|
|
860
1021
|
}
|
|
861
1022
|
),
|
|
862
|
-
/* @__PURE__ */
|
|
1023
|
+
/* @__PURE__ */ jsx4(
|
|
863
1024
|
"button",
|
|
864
1025
|
{
|
|
865
1026
|
type: "button",
|
|
866
|
-
className:
|
|
1027
|
+
className: buttonClass2,
|
|
867
1028
|
title: "Reset to uniform width",
|
|
868
1029
|
"aria-label": "Reset to uniform width",
|
|
869
1030
|
disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
|
|
@@ -873,27 +1034,27 @@ function VectorStylePanel({ controller, className }) {
|
|
|
873
1034
|
),
|
|
874
1035
|
mixed("widthProfile")
|
|
875
1036
|
] }),
|
|
876
|
-
source.hasOpenPath && /* @__PURE__ */
|
|
1037
|
+
source.hasOpenPath && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
877
1038
|
arrowRow("start", "Start"),
|
|
878
1039
|
arrowRow("end", "End"),
|
|
879
|
-
(source.startArrow || source.endArrow) && /* @__PURE__ */
|
|
880
|
-
/* @__PURE__ */
|
|
881
|
-
/* @__PURE__ */
|
|
882
|
-
/* @__PURE__ */
|
|
883
|
-
/* @__PURE__ */
|
|
1040
|
+
(source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
|
|
1041
|
+
/* @__PURE__ */ jsx4("span", { className: "w-16", children: "Heads" }),
|
|
1042
|
+
/* @__PURE__ */ jsxs4("select", { "aria-label": "Arrowhead alignment", className: selectClass2, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
|
|
1043
|
+
/* @__PURE__ */ jsx4("option", { value: "extend", children: "Tip past end" }),
|
|
1044
|
+
/* @__PURE__ */ jsx4("option", { value: "tip", children: "Tip at end" })
|
|
884
1045
|
] })
|
|
885
1046
|
] })
|
|
886
1047
|
] }),
|
|
887
|
-
source.corners > 0 && /* @__PURE__ */
|
|
888
|
-
/* @__PURE__ */
|
|
889
|
-
/* @__PURE__ */
|
|
1048
|
+
source.corners > 0 && /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
|
|
1049
|
+
/* @__PURE__ */ jsx4("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
|
|
1050
|
+
/* @__PURE__ */ jsx4(
|
|
890
1051
|
"input",
|
|
891
1052
|
{
|
|
892
1053
|
type: "number",
|
|
893
1054
|
min: 0,
|
|
894
1055
|
step: 1,
|
|
895
1056
|
"aria-label": "Corner radius",
|
|
896
|
-
className:
|
|
1057
|
+
className: numberClass2,
|
|
897
1058
|
value: Math.round(source.cornerRadius * 100) / 100,
|
|
898
1059
|
onChange: (e) => {
|
|
899
1060
|
const n = parseFloat(e.target.value);
|
|
@@ -901,12 +1062,12 @@ function VectorStylePanel({ controller, className }) {
|
|
|
901
1062
|
}
|
|
902
1063
|
}
|
|
903
1064
|
),
|
|
904
|
-
/* @__PURE__ */
|
|
1065
|
+
/* @__PURE__ */ jsx4("select", { "aria-label": "Corner kind", className: selectClass2, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ jsx4("option", { value: k, children: VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
|
|
905
1066
|
mixed("cornerRadius")
|
|
906
1067
|
] }),
|
|
907
|
-
style && /* @__PURE__ */
|
|
908
|
-
/* @__PURE__ */
|
|
909
|
-
/* @__PURE__ */
|
|
1068
|
+
style && /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
|
|
1069
|
+
/* @__PURE__ */ jsx4("span", { className: "w-16", children: "Opacity" }),
|
|
1070
|
+
/* @__PURE__ */ jsx4(
|
|
910
1071
|
"input",
|
|
911
1072
|
{
|
|
912
1073
|
type: "range",
|
|
@@ -919,24 +1080,26 @@ function VectorStylePanel({ controller, className }) {
|
|
|
919
1080
|
onChange: (e) => apply({ opacity: parseFloat(e.target.value) })
|
|
920
1081
|
}
|
|
921
1082
|
)
|
|
922
|
-
] })
|
|
1083
|
+
] }),
|
|
1084
|
+
style && /* @__PURE__ */ jsx4(VectorEffectsPanel, { controller })
|
|
923
1085
|
] });
|
|
924
1086
|
}
|
|
925
1087
|
|
|
926
1088
|
// src/vector/VectorEditor.tsx
|
|
927
|
-
import { jsx as
|
|
1089
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
928
1090
|
function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }) {
|
|
929
|
-
return /* @__PURE__ */
|
|
930
|
-
/* @__PURE__ */
|
|
931
|
-
/* @__PURE__ */
|
|
932
|
-
/* @__PURE__ */
|
|
933
|
-
showStylePanel && /* @__PURE__ */
|
|
1091
|
+
return /* @__PURE__ */ jsxs5("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
|
|
1092
|
+
/* @__PURE__ */ jsx5(VectorToolbar, { controller, tools }),
|
|
1093
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
|
|
1094
|
+
/* @__PURE__ */ jsx5("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx5(VectorCanvas, { controller, ...canvasProps }) }),
|
|
1095
|
+
showStylePanel && /* @__PURE__ */ jsx5(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
|
|
934
1096
|
] })
|
|
935
1097
|
] });
|
|
936
1098
|
}
|
|
937
1099
|
export {
|
|
938
1100
|
VectorCanvas,
|
|
939
1101
|
VectorEditor,
|
|
1102
|
+
VectorEffectsPanel,
|
|
940
1103
|
VectorGradientDefs,
|
|
941
1104
|
VectorSceneNodes,
|
|
942
1105
|
VectorStylePanel,
|