@pixerate/editor-react 0.31.0 → 0.33.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 +494 -110
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +36 -5
- package/dist/vector/index.d.ts +36 -5
- package/dist/vector/index.js +485 -100
- package/dist/vector/index.js.map +1 -1
- package/package.json +3 -3
package/dist/vector/index.js
CHANGED
|
@@ -140,12 +140,50 @@ function VectorWidthPointEditor({ controller, edit, accentColor = THEME.accent,
|
|
|
140
140
|
|
|
141
141
|
// src/vector/VectorCanvas.tsx
|
|
142
142
|
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
143
|
+
function GradientElement({ def }) {
|
|
144
|
+
const stops = def.stops.map((s, i) => /* @__PURE__ */ jsx2("stop", { offset: s.offset, stopColor: s.stopColor, stopOpacity: s.stopOpacity }, i));
|
|
145
|
+
return def.type === "radial" ? /* @__PURE__ */ jsx2("radialGradient", { id: def.id, ...def.attrs, children: stops }) : /* @__PURE__ */ jsx2("linearGradient", { id: def.id, ...def.attrs, children: stops });
|
|
146
|
+
}
|
|
143
147
|
function VectorGradientDefs({ defs }) {
|
|
144
148
|
if (!defs.length) return null;
|
|
145
|
-
return /* @__PURE__ */ jsx2("defs", { children: defs.map(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
return /* @__PURE__ */ jsx2("defs", { children: defs.map(
|
|
150
|
+
(def) => def.type === "pattern" ? /* @__PURE__ */ jsxs2("pattern", { id: def.id, ...def.attrs, children: [
|
|
151
|
+
def.gradients.map((g) => /* @__PURE__ */ jsx2(GradientElement, { def: g }, g.id)),
|
|
152
|
+
def.tiles.map((t, i) => /* @__PURE__ */ jsx2("path", { d: t.d, fill: t.fill }, i))
|
|
153
|
+
] }, def.id) : /* @__PURE__ */ jsx2(GradientElement, { def }, def.id)
|
|
154
|
+
) });
|
|
155
|
+
}
|
|
156
|
+
function VectorGradientAnnotator({ annotator: g, accentColor = THEME2.accent }) {
|
|
157
|
+
const r = THEME2.handleRadius + 1.5;
|
|
158
|
+
const ring = (p, part, size = r) => /* @__PURE__ */ jsx2("circle", { "data-vector-gradient-handle": part, cx: p.x, cy: p.y, r: size + (p.hovered ? 1 : 0), fill: THEME2.handleFill, stroke: accentColor, strokeWidth: p.hovered ? 2 : 1 });
|
|
159
|
+
return /* @__PURE__ */ jsxs2("g", { "data-vector-gradient-annotator": g.type, children: [
|
|
160
|
+
g.extent && /* @__PURE__ */ jsx2("path", { "data-vector-gradient-extent": "", d: g.extent, fill: "none", stroke: accentColor, strokeDasharray: "4 3" }),
|
|
161
|
+
/* @__PURE__ */ jsx2("line", { x1: g.start.x, y1: g.start.y, x2: g.end.x, y2: g.end.y, stroke: "#ffffff", strokeWidth: 3 }),
|
|
162
|
+
/* @__PURE__ */ jsx2("line", { "data-vector-gradient-bar": "", x1: g.start.x, y1: g.start.y, x2: g.end.x, y2: g.end.y, stroke: accentColor }),
|
|
163
|
+
g.aspect && /* @__PURE__ */ jsx2("line", { x1: g.start.x, y1: g.start.y, x2: g.aspect.x, y2: g.aspect.y, stroke: accentColor, strokeDasharray: "2 2" }),
|
|
164
|
+
ring(g.start, "start"),
|
|
165
|
+
/* @__PURE__ */ jsx2(
|
|
166
|
+
"rect",
|
|
167
|
+
{
|
|
168
|
+
"data-vector-gradient-handle": g.end.handle,
|
|
169
|
+
x: g.end.x - r,
|
|
170
|
+
y: g.end.y - r,
|
|
171
|
+
width: 2 * r,
|
|
172
|
+
height: 2 * r,
|
|
173
|
+
fill: THEME2.handleFill,
|
|
174
|
+
stroke: accentColor,
|
|
175
|
+
strokeWidth: g.end.hovered ? 2 : 1
|
|
176
|
+
}
|
|
177
|
+
),
|
|
178
|
+
g.aspect && ring(g.aspect, "aspect", THEME2.handleRadius),
|
|
179
|
+
g.focal && /* @__PURE__ */ jsx2("g", { "data-vector-gradient-handle": "focal", children: /* @__PURE__ */ jsx2("circle", { cx: g.focal.x, cy: g.focal.y, r: THEME2.handleRadius - 1 + (g.focal.hovered ? 1 : 0), fill: accentColor, stroke: THEME2.handleFill }) }),
|
|
180
|
+
g.midpoints.map((m) => /* @__PURE__ */ jsx2("path", { "data-vector-gradient-midpoint": m.index, d: vectorDiamondPath(m.x, m.y, 4 + (m.hovered ? 1 : 0)), fill: THEME2.handleFill, stroke: accentColor }, m.index)),
|
|
181
|
+
g.stops.map((s) => /* @__PURE__ */ jsxs2("g", { "data-vector-gradient-stop": s.index, "data-selected": s.selected || void 0, opacity: g.removing && s.selected ? 0.35 : 1, children: [
|
|
182
|
+
/* @__PURE__ */ jsx2("line", { x1: s.barX, y1: s.barY, x2: s.x, y2: s.y, stroke: accentColor }),
|
|
183
|
+
/* @__PURE__ */ jsx2("rect", { x: s.x - 5, y: s.y - 5, width: 10, height: 10, rx: 2, fill: s.color, fillOpacity: s.opacity, stroke: s.selected ? accentColor : "#ffffff", strokeWidth: s.selected || s.hovered ? 2 : 1 }),
|
|
184
|
+
/* @__PURE__ */ jsx2("rect", { x: s.x - 6.5, y: s.y - 6.5, width: 13, height: 13, rx: 3, fill: "none", stroke: s.selected ? "#ffffff" : accentColor })
|
|
185
|
+
] }, s.index))
|
|
186
|
+
] });
|
|
149
187
|
}
|
|
150
188
|
function VectorSceneNodes({ nodes }) {
|
|
151
189
|
return /* @__PURE__ */ jsx2(Fragment, { children: nodes.map(
|
|
@@ -369,6 +407,7 @@ function VectorCanvas({
|
|
|
369
407
|
},
|
|
370
408
|
`blend-anchor:${i}`
|
|
371
409
|
)),
|
|
410
|
+
overlay.gradient && /* @__PURE__ */ jsx2(VectorGradientAnnotator, { annotator: overlay.gradient, accentColor }),
|
|
372
411
|
overlay.widthSpot && /* @__PURE__ */ jsxs2("g", { "data-vector-width-spot": "", children: [
|
|
373
412
|
/* @__PURE__ */ jsx2("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" }),
|
|
374
413
|
/* @__PURE__ */ jsx2("path", { d: vectorDiamondPath(overlay.widthSpot.x, overlay.widthSpot.y), fill: "none", stroke: accentColor })
|
|
@@ -474,6 +513,7 @@ function VectorCanvas({
|
|
|
474
513
|
}
|
|
475
514
|
|
|
476
515
|
// src/vector/VectorToolbar.tsx
|
|
516
|
+
import React3 from "react";
|
|
477
517
|
import {
|
|
478
518
|
VECTOR_TOOLS,
|
|
479
519
|
VECTOR_TOOL_GLYPHS,
|
|
@@ -495,13 +535,338 @@ import {
|
|
|
495
535
|
getVectorSelectionStyle,
|
|
496
536
|
widthProfilePreset,
|
|
497
537
|
widthProfilePresetId,
|
|
498
|
-
vectorPaintToHex,
|
|
538
|
+
vectorPaintToHex as vectorPaintToHex2,
|
|
499
539
|
vectorToolLabel
|
|
500
540
|
} from "@pixerate/editor/vector";
|
|
501
|
-
|
|
541
|
+
|
|
542
|
+
// src/vector/VectorEffectsPanel.tsx
|
|
543
|
+
import {
|
|
544
|
+
VECTOR_EFFECT_MENU,
|
|
545
|
+
getVectorEffectsPanel
|
|
546
|
+
} from "@pixerate/editor/vector";
|
|
547
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
548
|
+
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";
|
|
549
|
+
var numberClass = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
|
|
550
|
+
var selectClass = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
|
|
551
|
+
var labelClass = "w-full truncate text-[11px] text-muted-foreground";
|
|
552
|
+
function VectorEffectsPanel({ controller, className }) {
|
|
553
|
+
const state = useVectorEditorState(controller);
|
|
554
|
+
const panel = getVectorEffectsPanel(state);
|
|
555
|
+
if (!panel) return null;
|
|
556
|
+
const nodeId = panel.nodeId;
|
|
557
|
+
const end = () => controller.endMergedEdit();
|
|
558
|
+
const set = (e, key, value, merge) => {
|
|
559
|
+
if (nodeId) controller.updateEffect(nodeId, e.id, { params: { [key]: value } }, { merge });
|
|
560
|
+
};
|
|
561
|
+
const number = (e, spec) => {
|
|
562
|
+
const value = Number(e.params[spec.key] ?? 0);
|
|
563
|
+
const label = `${e.label} ${spec.label}`;
|
|
564
|
+
const onChange = (ev) => {
|
|
565
|
+
const n = parseFloat(ev.target.value);
|
|
566
|
+
if (Number.isFinite(n)) set(e, spec.key, n, true);
|
|
567
|
+
};
|
|
568
|
+
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: [
|
|
569
|
+
/* @__PURE__ */ jsx3("span", { className: labelClass, children: spec.label }),
|
|
570
|
+
spec.slider && /* @__PURE__ */ jsx3(
|
|
571
|
+
"input",
|
|
572
|
+
{
|
|
573
|
+
type: "range",
|
|
574
|
+
min: spec.min,
|
|
575
|
+
max: spec.max,
|
|
576
|
+
step: spec.step,
|
|
577
|
+
"aria-label": `${label} slider`,
|
|
578
|
+
className: "h-4 min-w-0 flex-1 accent-primary",
|
|
579
|
+
value,
|
|
580
|
+
onChange,
|
|
581
|
+
onPointerUp: end,
|
|
582
|
+
onKeyUp: end,
|
|
583
|
+
onBlur: end
|
|
584
|
+
}
|
|
585
|
+
),
|
|
586
|
+
/* @__PURE__ */ jsx3(
|
|
587
|
+
"input",
|
|
588
|
+
{
|
|
589
|
+
type: "number",
|
|
590
|
+
min: spec.min,
|
|
591
|
+
max: spec.max,
|
|
592
|
+
step: spec.step,
|
|
593
|
+
"aria-label": label,
|
|
594
|
+
className: numberClass,
|
|
595
|
+
value: Math.round(value * 1e3) / 1e3,
|
|
596
|
+
onChange,
|
|
597
|
+
onBlur: end
|
|
598
|
+
}
|
|
599
|
+
)
|
|
600
|
+
] }, spec.key);
|
|
601
|
+
};
|
|
602
|
+
const vec2 = (e, spec) => {
|
|
603
|
+
const v = e.params[spec.key] ?? { x: 0, y: 0 };
|
|
604
|
+
const field = (axis) => /* @__PURE__ */ jsx3(
|
|
605
|
+
"input",
|
|
606
|
+
{
|
|
607
|
+
type: "number",
|
|
608
|
+
step: spec.kind === "vec2" ? spec.step : 1,
|
|
609
|
+
"aria-label": `${e.label} ${spec.label} ${axis}`,
|
|
610
|
+
className: numberClass,
|
|
611
|
+
value: Math.round(v[axis] * 1e3) / 1e3,
|
|
612
|
+
onChange: (ev) => {
|
|
613
|
+
const n = parseFloat(ev.target.value);
|
|
614
|
+
if (Number.isFinite(n)) set(e, spec.key, { ...v, [axis]: n }, true);
|
|
615
|
+
},
|
|
616
|
+
onBlur: end
|
|
617
|
+
}
|
|
618
|
+
);
|
|
619
|
+
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: [
|
|
620
|
+
/* @__PURE__ */ jsx3("span", { className: labelClass, children: spec.label }),
|
|
621
|
+
field("x"),
|
|
622
|
+
field("y")
|
|
623
|
+
] }, spec.key);
|
|
624
|
+
};
|
|
625
|
+
const param = (e, spec) => {
|
|
626
|
+
if (spec.kind === "number") return number(e, spec);
|
|
627
|
+
if (spec.kind === "vec2") return vec2(e, spec);
|
|
628
|
+
if (spec.kind === "boolean")
|
|
629
|
+
return /* @__PURE__ */ jsxs3("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-effect-param": spec.key, children: [
|
|
630
|
+
/* @__PURE__ */ jsx3("span", { className: "min-w-0 flex-1 truncate", children: spec.label }),
|
|
631
|
+
/* @__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) })
|
|
632
|
+
] }, spec.key);
|
|
633
|
+
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: [
|
|
634
|
+
/* @__PURE__ */ jsx3("span", { className: labelClass, children: spec.label }),
|
|
635
|
+
/* @__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)) })
|
|
636
|
+
] }, spec.key);
|
|
637
|
+
};
|
|
638
|
+
return /* @__PURE__ */ jsxs3("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-effects": "", "aria-label": "Effects", children: [
|
|
639
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
640
|
+
/* @__PURE__ */ jsx3("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Effects" }),
|
|
641
|
+
/* @__PURE__ */ jsxs3(
|
|
642
|
+
"select",
|
|
643
|
+
{
|
|
644
|
+
"aria-label": "Add effect",
|
|
645
|
+
className: selectClass,
|
|
646
|
+
value: "",
|
|
647
|
+
onChange: (ev) => {
|
|
648
|
+
if (ev.target.value) controller.addEffect(ev.target.value);
|
|
649
|
+
},
|
|
650
|
+
children: [
|
|
651
|
+
/* @__PURE__ */ jsx3("option", { value: "", children: "Add effect\u2026" }),
|
|
652
|
+
VECTOR_EFFECT_MENU.map((m) => /* @__PURE__ */ jsx3("option", { value: m.type, children: m.label }, m.type))
|
|
653
|
+
]
|
|
654
|
+
}
|
|
655
|
+
),
|
|
656
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, title: "Expand Appearance", "aria-label": "Expand Appearance", disabled: !panel.expandable, onClick: () => controller.expandAppearance(), children: "Expand" })
|
|
657
|
+
] }),
|
|
658
|
+
panel.count > 1 && /* @__PURE__ */ jsx3("p", { className: "text-[11px] text-muted-foreground", children: "Select one object to edit its effects." }),
|
|
659
|
+
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: [
|
|
660
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1", children: [
|
|
661
|
+
/* @__PURE__ */ jsxs3("label", { className: "flex min-w-0 flex-1 items-center gap-1.5 text-xs font-medium text-foreground", children: [
|
|
662
|
+
/* @__PURE__ */ jsx3("input", { type: "checkbox", "aria-label": `${e.label} on`, checked: e.enabled, onChange: (ev) => controller.updateEffect(nodeId, e.id, { enabled: ev.target.checked }) }),
|
|
663
|
+
/* @__PURE__ */ jsx3("span", { className: "truncate", children: e.label })
|
|
664
|
+
] }),
|
|
665
|
+
/* @__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" }),
|
|
666
|
+
/* @__PURE__ */ jsx3(
|
|
667
|
+
"button",
|
|
668
|
+
{
|
|
669
|
+
type: "button",
|
|
670
|
+
className: buttonClass,
|
|
671
|
+
title: "Move down",
|
|
672
|
+
"aria-label": `Move ${e.label} down`,
|
|
673
|
+
disabled: i === panel.effects.length - 1,
|
|
674
|
+
onClick: () => controller.moveEffect(nodeId, e.id, i + 1),
|
|
675
|
+
children: "\u2193"
|
|
676
|
+
}
|
|
677
|
+
),
|
|
678
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: buttonClass, title: "Remove", "aria-label": `Remove ${e.label}`, onClick: () => controller.removeEffect(nodeId, e.id), children: "\xD7" })
|
|
679
|
+
] }),
|
|
680
|
+
e.specs.map((spec) => param(e, spec))
|
|
681
|
+
] }, e.id))
|
|
682
|
+
] });
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// src/vector/VectorGradientEditor.tsx
|
|
686
|
+
import {
|
|
687
|
+
VECTOR_GRADIENT_TYPES,
|
|
688
|
+
VECTOR_GRADIENT_SPREADS,
|
|
689
|
+
VECTOR_GRADIENT_INTERPOLATIONS,
|
|
690
|
+
VECTOR_STROKE_GRADIENT_MODES,
|
|
691
|
+
VECTOR_GRADIENT_TYPE_LABELS,
|
|
692
|
+
VECTOR_GRADIENT_SPREAD_LABELS,
|
|
693
|
+
VECTOR_GRADIENT_INTERPOLATION_LABELS,
|
|
694
|
+
VECTOR_STROKE_GRADIENT_MODE_LABELS,
|
|
695
|
+
getVectorGradientEditor,
|
|
696
|
+
vectorPaintToHex
|
|
697
|
+
} from "@pixerate/editor/vector";
|
|
698
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
699
|
+
var selectClass2 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
|
|
700
|
+
var numberClass2 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
|
|
701
|
+
var buttonClass2 = "inline-flex h-7 min-w-7 items-center justify-center rounded border border-border bg-background px-1.5 text-xs text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
|
|
702
|
+
var percent = (v) => Math.round(v * 1e3) / 10;
|
|
703
|
+
function VectorGradientEditor({ controller, className }) {
|
|
704
|
+
const state = useVectorEditorState(controller);
|
|
705
|
+
const ed = getVectorGradientEditor(state);
|
|
706
|
+
if (!ed) return null;
|
|
707
|
+
const g = ed.gradient;
|
|
708
|
+
const sel = ed.selected;
|
|
709
|
+
const end = () => controller.endMergedEdit();
|
|
710
|
+
const setType = (value) => {
|
|
711
|
+
if (value === "solid") {
|
|
712
|
+
const first = ed.stops[0];
|
|
713
|
+
controller.setStyle({ [ed.target]: vectorPaintToHex(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
|
|
714
|
+
} else controller.setGradient({ type: value });
|
|
715
|
+
};
|
|
716
|
+
return /* @__PURE__ */ jsxs4("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
|
|
717
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1.5", children: [
|
|
718
|
+
/* @__PURE__ */ jsx4("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
|
|
719
|
+
["fill", "stroke"].map((t) => /* @__PURE__ */ jsx4("button", { type: "button", className: buttonClass2, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
|
|
720
|
+
ed.mixed && /* @__PURE__ */ jsx4("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
|
|
721
|
+
] }),
|
|
722
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1.5", children: [
|
|
723
|
+
/* @__PURE__ */ jsx4("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
|
|
724
|
+
/* @__PURE__ */ jsxs4("select", { "aria-label": "Gradient type", className: selectClass2, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
|
|
725
|
+
/* @__PURE__ */ jsx4("option", { value: "solid", children: "Solid" }),
|
|
726
|
+
VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ jsx4("option", { value: t, children: VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
|
|
727
|
+
] }),
|
|
728
|
+
/* @__PURE__ */ jsx4("button", { type: "button", className: buttonClass2, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
|
|
729
|
+
] }),
|
|
730
|
+
g && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
731
|
+
/* @__PURE__ */ jsxs4("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
|
|
732
|
+
/* @__PURE__ */ jsx4(
|
|
733
|
+
"div",
|
|
734
|
+
{
|
|
735
|
+
role: "button",
|
|
736
|
+
tabIndex: -1,
|
|
737
|
+
"aria-label": "Add gradient stop",
|
|
738
|
+
className: "absolute inset-x-1.5 top-0 h-4 cursor-copy rounded border border-border",
|
|
739
|
+
style: { backgroundImage: `${ed.preview}, repeating-conic-gradient(#ddd 0% 25%, #fff 0% 50%)`, backgroundSize: "auto, 8px 8px" },
|
|
740
|
+
onClick: (e) => {
|
|
741
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
742
|
+
controller.addGradientStop(r.width > 0 ? Math.min(1, Math.max(0, (e.clientX - r.left) / r.width)) : 0.5);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
),
|
|
746
|
+
ed.stops.map((s) => /* @__PURE__ */ jsx4(
|
|
747
|
+
"button",
|
|
748
|
+
{
|
|
749
|
+
type: "button",
|
|
750
|
+
"data-vector-gradient-stop": s.index,
|
|
751
|
+
"aria-label": `Gradient stop ${s.index + 1} at ${percent(s.offset)}%`,
|
|
752
|
+
"aria-pressed": s.selected,
|
|
753
|
+
className: "absolute top-4 h-3.5 w-3 -translate-x-1/2 rounded-sm border border-border aria-pressed:ring-2 aria-pressed:ring-primary",
|
|
754
|
+
style: { left: `calc(6px + (100% - 12px) * ${s.offset})`, background: s.color, opacity: 0.4 + 0.6 * (s.opacity ?? 1) },
|
|
755
|
+
onClick: () => controller.selectGradientStop(s.selected ? null : s.index)
|
|
756
|
+
},
|
|
757
|
+
s.index
|
|
758
|
+
))
|
|
759
|
+
] }),
|
|
760
|
+
sel && /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
|
|
761
|
+
/* @__PURE__ */ jsx4(
|
|
762
|
+
"input",
|
|
763
|
+
{
|
|
764
|
+
type: "color",
|
|
765
|
+
"aria-label": "Stop colour",
|
|
766
|
+
className: "h-7 w-9 cursor-pointer rounded border border-border bg-background",
|
|
767
|
+
value: vectorPaintToHex(sel.color) ?? "#000000",
|
|
768
|
+
onChange: (e) => controller.setGradientStop(sel.index, { color: e.target.value }, { merge: `gradient-stop-color-${sel.index}` }),
|
|
769
|
+
onBlur: end
|
|
770
|
+
}
|
|
771
|
+
),
|
|
772
|
+
/* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
|
|
773
|
+
"Opacity",
|
|
774
|
+
/* @__PURE__ */ jsx4(
|
|
775
|
+
"input",
|
|
776
|
+
{
|
|
777
|
+
type: "number",
|
|
778
|
+
min: 0,
|
|
779
|
+
max: 100,
|
|
780
|
+
step: 5,
|
|
781
|
+
"aria-label": "Stop opacity",
|
|
782
|
+
className: numberClass2,
|
|
783
|
+
value: percent(sel.opacity ?? 1),
|
|
784
|
+
onChange: (e) => {
|
|
785
|
+
const n = parseFloat(e.target.value);
|
|
786
|
+
if (Number.isFinite(n)) controller.setGradientStop(sel.index, { opacity: n / 100 });
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
)
|
|
790
|
+
] }),
|
|
791
|
+
/* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
|
|
792
|
+
"At",
|
|
793
|
+
/* @__PURE__ */ jsx4(
|
|
794
|
+
"input",
|
|
795
|
+
{
|
|
796
|
+
type: "number",
|
|
797
|
+
min: 0,
|
|
798
|
+
max: 100,
|
|
799
|
+
step: 1,
|
|
800
|
+
"aria-label": "Stop position",
|
|
801
|
+
className: numberClass2,
|
|
802
|
+
value: percent(sel.offset),
|
|
803
|
+
onChange: (e) => {
|
|
804
|
+
const n = parseFloat(e.target.value);
|
|
805
|
+
if (Number.isFinite(n)) controller.setGradientStop(sel.index, { offset: n / 100 });
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
)
|
|
809
|
+
] }),
|
|
810
|
+
!sel.last && /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
|
|
811
|
+
"Midpoint",
|
|
812
|
+
/* @__PURE__ */ jsx4(
|
|
813
|
+
"input",
|
|
814
|
+
{
|
|
815
|
+
type: "number",
|
|
816
|
+
min: 5,
|
|
817
|
+
max: 95,
|
|
818
|
+
step: 1,
|
|
819
|
+
"aria-label": "Stop midpoint",
|
|
820
|
+
className: numberClass2,
|
|
821
|
+
value: percent(sel.midpoint ?? 0.5),
|
|
822
|
+
onChange: (e) => {
|
|
823
|
+
const n = parseFloat(e.target.value);
|
|
824
|
+
if (Number.isFinite(n)) controller.setGradientStop(sel.index, { midpoint: n / 100 });
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
)
|
|
828
|
+
] }),
|
|
829
|
+
/* @__PURE__ */ jsx4("button", { type: "button", className: buttonClass2, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
|
|
830
|
+
] }),
|
|
831
|
+
/* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
|
|
832
|
+
/* @__PURE__ */ jsx4("span", { className: "w-16", children: "Colours" }),
|
|
833
|
+
/* @__PURE__ */ jsx4("select", { "aria-label": "Gradient interpolation", className: selectClass2, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ jsx4("option", { value: i, children: VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
|
|
834
|
+
] }),
|
|
835
|
+
/* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
|
|
836
|
+
/* @__PURE__ */ jsx4("span", { className: "w-16", children: "Smooth" }),
|
|
837
|
+
/* @__PURE__ */ jsx4(
|
|
838
|
+
"input",
|
|
839
|
+
{
|
|
840
|
+
type: "range",
|
|
841
|
+
min: 0,
|
|
842
|
+
max: 1,
|
|
843
|
+
step: 0.05,
|
|
844
|
+
"aria-label": "Gradient smoothness",
|
|
845
|
+
className: "h-4 flex-1 accent-primary",
|
|
846
|
+
value: ed.smoothness,
|
|
847
|
+
onChange: (e) => controller.setGradient({ smoothness: parseFloat(e.target.value) }, { merge: "gradient-smoothness" }),
|
|
848
|
+
onPointerUp: end,
|
|
849
|
+
onBlur: end
|
|
850
|
+
}
|
|
851
|
+
)
|
|
852
|
+
] }),
|
|
853
|
+
/* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
|
|
854
|
+
/* @__PURE__ */ jsx4("span", { className: "w-16", children: "Spread" }),
|
|
855
|
+
/* @__PURE__ */ jsx4("select", { "aria-label": "Gradient spread", className: selectClass2, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ jsx4("option", { value: sp, children: VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
|
|
856
|
+
] }),
|
|
857
|
+
ed.target === "stroke" && /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
|
|
858
|
+
/* @__PURE__ */ jsx4("span", { className: "w-16", children: "On stroke" }),
|
|
859
|
+
/* @__PURE__ */ jsx4("select", { "aria-label": "Gradient on stroke", className: selectClass2, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ jsx4("option", { value: m, children: VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
|
|
860
|
+
] })
|
|
861
|
+
] })
|
|
862
|
+
] });
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// src/vector/VectorToolbar.tsx
|
|
866
|
+
import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
502
867
|
var VECTOR_SNAP_TITLE = "Snap to objects";
|
|
503
868
|
var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
|
|
504
|
-
var
|
|
869
|
+
var buttonClass3 = "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
870
|
function VectorToolbar({
|
|
506
871
|
controller,
|
|
507
872
|
tools = VECTOR_TOOLS,
|
|
@@ -517,14 +882,16 @@ function VectorToolbar({
|
|
|
517
882
|
const points = state.tool === "direct" ? state.selectedAnchors.length : 0;
|
|
518
883
|
const editing = state.editing;
|
|
519
884
|
const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
885
|
+
const [live, setLive] = React3.useState(false);
|
|
886
|
+
const booleanDisabled = live ? count < 1 : count < 2;
|
|
887
|
+
const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx5("button", { type: "button", className: buttonClass3, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
|
|
888
|
+
return /* @__PURE__ */ jsxs5("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
|
|
889
|
+
/* @__PURE__ */ jsx5("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx5(
|
|
523
890
|
"button",
|
|
524
891
|
{
|
|
525
892
|
type: "button",
|
|
526
893
|
"data-vector-tool": tool,
|
|
527
|
-
className:
|
|
894
|
+
className: buttonClass3,
|
|
528
895
|
"aria-pressed": state.tool === tool,
|
|
529
896
|
title: `${vectorToolLabel(tool)} (${VECTOR_TOOL_SHORTCUTS[tool]})`,
|
|
530
897
|
"aria-label": vectorToolLabel(tool),
|
|
@@ -533,16 +900,16 @@ function VectorToolbar({
|
|
|
533
900
|
},
|
|
534
901
|
tool
|
|
535
902
|
)) }),
|
|
536
|
-
showHistory && /* @__PURE__ */
|
|
903
|
+
showHistory && /* @__PURE__ */ jsxs5("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
|
|
537
904
|
act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
|
|
538
905
|
act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
|
|
539
906
|
] }),
|
|
540
|
-
showSnap && /* @__PURE__ */
|
|
907
|
+
showSnap && /* @__PURE__ */ jsx5("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: /* @__PURE__ */ jsx5(
|
|
541
908
|
"button",
|
|
542
909
|
{
|
|
543
910
|
type: "button",
|
|
544
911
|
"data-vector-snap": "",
|
|
545
|
-
className:
|
|
912
|
+
className: buttonClass3,
|
|
546
913
|
"aria-pressed": state.snap.objects,
|
|
547
914
|
title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
|
|
548
915
|
"aria-label": VECTOR_SNAP_TITLE,
|
|
@@ -550,13 +917,13 @@ function VectorToolbar({
|
|
|
550
917
|
children: "Snap"
|
|
551
918
|
}
|
|
552
919
|
) }),
|
|
553
|
-
showPathEdit && /* @__PURE__ */
|
|
554
|
-
VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */
|
|
920
|
+
showPathEdit && /* @__PURE__ */ jsxs5("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
|
|
921
|
+
VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx5(
|
|
555
922
|
"button",
|
|
556
923
|
{
|
|
557
924
|
type: "button",
|
|
558
925
|
"data-vector-path-action": a.id,
|
|
559
|
-
className:
|
|
926
|
+
className: buttonClass3,
|
|
560
927
|
title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
|
|
561
928
|
"aria-label": a.title,
|
|
562
929
|
disabled: !a.enabled(count, points),
|
|
@@ -565,12 +932,12 @@ function VectorToolbar({
|
|
|
565
932
|
},
|
|
566
933
|
a.id
|
|
567
934
|
)),
|
|
568
|
-
VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */
|
|
935
|
+
VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx5(
|
|
569
936
|
"button",
|
|
570
937
|
{
|
|
571
938
|
type: "button",
|
|
572
939
|
"data-vector-edit": t.key,
|
|
573
|
-
className:
|
|
940
|
+
className: buttonClass3,
|
|
574
941
|
"aria-pressed": editing[t.key],
|
|
575
942
|
title: t.title,
|
|
576
943
|
"aria-label": t.title,
|
|
@@ -579,9 +946,9 @@ function VectorToolbar({
|
|
|
579
946
|
},
|
|
580
947
|
t.key
|
|
581
948
|
)),
|
|
582
|
-
state.tool === "pencil" && /* @__PURE__ */
|
|
949
|
+
state.tool === "pencil" && /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
|
|
583
950
|
"Smoothing",
|
|
584
|
-
/* @__PURE__ */
|
|
951
|
+
/* @__PURE__ */ jsx5(
|
|
585
952
|
"input",
|
|
586
953
|
{
|
|
587
954
|
type: "range",
|
|
@@ -595,9 +962,9 @@ function VectorToolbar({
|
|
|
595
962
|
}
|
|
596
963
|
)
|
|
597
964
|
] }),
|
|
598
|
-
state.tool === "eraser" && /* @__PURE__ */
|
|
965
|
+
state.tool === "eraser" && /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
|
|
599
966
|
"Size",
|
|
600
|
-
/* @__PURE__ */
|
|
967
|
+
/* @__PURE__ */ jsx5(
|
|
601
968
|
"input",
|
|
602
969
|
{
|
|
603
970
|
type: "number",
|
|
@@ -613,12 +980,12 @@ function VectorToolbar({
|
|
|
613
980
|
}
|
|
614
981
|
)
|
|
615
982
|
] }),
|
|
616
|
-
state.tool === "paintBucket" && /* @__PURE__ */
|
|
983
|
+
state.tool === "paintBucket" && /* @__PURE__ */ jsx5(
|
|
617
984
|
"button",
|
|
618
985
|
{
|
|
619
986
|
type: "button",
|
|
620
987
|
"data-vector-fill-gap": "",
|
|
621
|
-
className:
|
|
988
|
+
className: buttonClass3,
|
|
622
989
|
"aria-pressed": editing.fillGap > 0,
|
|
623
990
|
title: "Close small gaps between path ends when filling",
|
|
624
991
|
"aria-label": "Close gaps",
|
|
@@ -627,22 +994,35 @@ function VectorToolbar({
|
|
|
627
994
|
}
|
|
628
995
|
)
|
|
629
996
|
] }),
|
|
630
|
-
showBlend && /* @__PURE__ */
|
|
631
|
-
showArrange && /* @__PURE__ */
|
|
632
|
-
/* @__PURE__ */
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
997
|
+
showBlend && /* @__PURE__ */ jsx5(VectorBlendControls, { controller }),
|
|
998
|
+
showArrange && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
999
|
+
/* @__PURE__ */ jsxs5("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
|
|
1000
|
+
/* @__PURE__ */ jsx5(
|
|
1001
|
+
"button",
|
|
1002
|
+
{
|
|
1003
|
+
type: "button",
|
|
1004
|
+
"data-vector-live-boolean": "",
|
|
1005
|
+
className: buttonClass3,
|
|
1006
|
+
"aria-pressed": live,
|
|
1007
|
+
title: "Live booleans: keep the shapes editable",
|
|
1008
|
+
"aria-label": "Live booleans",
|
|
1009
|
+
onClick: () => setLive(!live),
|
|
1010
|
+
children: "Live"
|
|
1011
|
+
}
|
|
1012
|
+
),
|
|
1013
|
+
act("\u222A", "Union", () => controller.booleanOp("union", { live }), booleanDisabled),
|
|
1014
|
+
act("\u2212", "Subtract", () => controller.booleanOp("subtract", { live }), booleanDisabled),
|
|
1015
|
+
act("\u2229", "Intersect", () => controller.booleanOp("intersect", { live }), booleanDisabled),
|
|
1016
|
+
act("\u22BB", "Exclude", () => controller.booleanOp("exclude", { live }), booleanDisabled),
|
|
637
1017
|
act("G", "Group", () => controller.group(), count < 1),
|
|
638
1018
|
act("U", "Ungroup", () => controller.ungroup(), !groupSelected)
|
|
639
1019
|
] }),
|
|
640
|
-
/* @__PURE__ */
|
|
1020
|
+
/* @__PURE__ */ jsx5("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx5(
|
|
641
1021
|
"button",
|
|
642
1022
|
{
|
|
643
1023
|
type: "button",
|
|
644
1024
|
"data-vector-pathfinder": a.id,
|
|
645
|
-
className:
|
|
1025
|
+
className: buttonClass3,
|
|
646
1026
|
title: a.title,
|
|
647
1027
|
"aria-label": a.title,
|
|
648
1028
|
disabled: count < a.min,
|
|
@@ -651,7 +1031,7 @@ function VectorToolbar({
|
|
|
651
1031
|
},
|
|
652
1032
|
a.id
|
|
653
1033
|
)) }),
|
|
654
|
-
/* @__PURE__ */
|
|
1034
|
+
/* @__PURE__ */ jsxs5("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
|
|
655
1035
|
act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
|
|
656
1036
|
act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
|
|
657
1037
|
act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
|
|
@@ -669,19 +1049,19 @@ function VectorBlendControls({ controller }) {
|
|
|
669
1049
|
const blend = getVectorBlendControls(state);
|
|
670
1050
|
const o = blend.options;
|
|
671
1051
|
const selected = blend.ids.length > 0;
|
|
672
|
-
return /* @__PURE__ */
|
|
673
|
-
/* @__PURE__ */
|
|
674
|
-
/* @__PURE__ */
|
|
1052
|
+
return /* @__PURE__ */ jsxs5("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
|
|
1053
|
+
/* @__PURE__ */ jsx5("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass3, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
|
|
1054
|
+
/* @__PURE__ */ jsx5(
|
|
675
1055
|
"select",
|
|
676
1056
|
{
|
|
677
1057
|
"aria-label": "Blend spacing",
|
|
678
1058
|
className: "h-7 rounded border border-border bg-background px-1 text-xs",
|
|
679
1059
|
value: o.spacing,
|
|
680
1060
|
onChange: (e) => applyVectorBlendOptions(controller, { spacing: e.target.value }),
|
|
681
|
-
children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */
|
|
1061
|
+
children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx5("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
|
|
682
1062
|
}
|
|
683
1063
|
),
|
|
684
|
-
o.spacing !== "smoothColor" && /* @__PURE__ */
|
|
1064
|
+
o.spacing !== "smoothColor" && /* @__PURE__ */ jsx5(
|
|
685
1065
|
"input",
|
|
686
1066
|
{
|
|
687
1067
|
type: "number",
|
|
@@ -697,12 +1077,12 @@ function VectorBlendControls({ controller }) {
|
|
|
697
1077
|
}
|
|
698
1078
|
}
|
|
699
1079
|
),
|
|
700
|
-
/* @__PURE__ */
|
|
1080
|
+
/* @__PURE__ */ jsx5(
|
|
701
1081
|
"button",
|
|
702
1082
|
{
|
|
703
1083
|
type: "button",
|
|
704
1084
|
"data-vector-blend-action": "orient",
|
|
705
|
-
className:
|
|
1085
|
+
className: buttonClass3,
|
|
706
1086
|
"aria-pressed": o.orientToSpine,
|
|
707
1087
|
title: "Orient steps to the spine",
|
|
708
1088
|
"aria-label": "Orient to spine",
|
|
@@ -710,12 +1090,12 @@ function VectorBlendControls({ controller }) {
|
|
|
710
1090
|
children: "\u293F"
|
|
711
1091
|
}
|
|
712
1092
|
),
|
|
713
|
-
/* @__PURE__ */
|
|
1093
|
+
/* @__PURE__ */ jsx5(
|
|
714
1094
|
"button",
|
|
715
1095
|
{
|
|
716
1096
|
type: "button",
|
|
717
1097
|
"data-vector-blend-action": "spine",
|
|
718
|
-
className:
|
|
1098
|
+
className: buttonClass3,
|
|
719
1099
|
title: "Replace Spine (select a blend and a path)",
|
|
720
1100
|
"aria-label": "Replace Spine",
|
|
721
1101
|
disabled: !blend.canReplaceSpine,
|
|
@@ -723,12 +1103,12 @@ function VectorBlendControls({ controller }) {
|
|
|
723
1103
|
children: "\u2307"
|
|
724
1104
|
}
|
|
725
1105
|
),
|
|
726
|
-
/* @__PURE__ */
|
|
727
|
-
/* @__PURE__ */
|
|
1106
|
+
/* @__PURE__ */ jsx5("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass3, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
|
|
1107
|
+
/* @__PURE__ */ jsx5("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass3, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
|
|
728
1108
|
] });
|
|
729
1109
|
}
|
|
730
|
-
var
|
|
731
|
-
var
|
|
1110
|
+
var selectClass3 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
|
|
1111
|
+
var numberClass3 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
|
|
732
1112
|
function VectorStylePanel({ controller, className }) {
|
|
733
1113
|
const state = useVectorEditorState(controller);
|
|
734
1114
|
const style = getVectorSelectionStyle(state);
|
|
@@ -759,17 +1139,17 @@ function VectorStylePanel({ controller, className }) {
|
|
|
759
1139
|
if (style) controller.setWidthProfile(id);
|
|
760
1140
|
else controller.setDefaults({ strokeWidthProfile: widthProfilePreset(id)?.length ? widthProfilePreset(id) : null });
|
|
761
1141
|
};
|
|
762
|
-
const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */
|
|
1142
|
+
const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx5("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
|
|
763
1143
|
const arrowRow = (end, label) => {
|
|
764
1144
|
const key = end === "start" ? "startArrow" : "endArrow";
|
|
765
1145
|
const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
|
|
766
|
-
return /* @__PURE__ */
|
|
767
|
-
/* @__PURE__ */
|
|
768
|
-
/* @__PURE__ */
|
|
769
|
-
/* @__PURE__ */
|
|
770
|
-
VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */
|
|
1146
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
|
|
1147
|
+
/* @__PURE__ */ jsx5("span", { className: "w-16 text-xs text-foreground", children: label }),
|
|
1148
|
+
/* @__PURE__ */ jsxs5("select", { "aria-label": `${label} arrowhead`, className: selectClass3, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
|
|
1149
|
+
/* @__PURE__ */ jsx5("option", { value: "", children: "None" }),
|
|
1150
|
+
VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx5("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
|
|
771
1151
|
] }),
|
|
772
|
-
/* @__PURE__ */
|
|
1152
|
+
/* @__PURE__ */ jsx5(
|
|
773
1153
|
"input",
|
|
774
1154
|
{
|
|
775
1155
|
type: "number",
|
|
@@ -777,7 +1157,7 @@ function VectorStylePanel({ controller, className }) {
|
|
|
777
1157
|
step: 0.25,
|
|
778
1158
|
"aria-label": `${label} arrowhead scale`,
|
|
779
1159
|
title: "Scale (\xD7 stroke width)",
|
|
780
|
-
className:
|
|
1160
|
+
className: numberClass3,
|
|
781
1161
|
value: source[scaleKey],
|
|
782
1162
|
onChange: (e) => {
|
|
783
1163
|
const n = parseFloat(e.target.value);
|
|
@@ -793,14 +1173,14 @@ function VectorStylePanel({ controller, className }) {
|
|
|
793
1173
|
};
|
|
794
1174
|
const paintRow = (key, label) => {
|
|
795
1175
|
const paint = source[key];
|
|
796
|
-
const hex =
|
|
1176
|
+
const hex = vectorPaintToHex2(paint);
|
|
797
1177
|
const on = paint !== null && paint !== "none";
|
|
798
|
-
return /* @__PURE__ */
|
|
799
|
-
/* @__PURE__ */
|
|
800
|
-
/* @__PURE__ */
|
|
1178
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
|
|
1179
|
+
/* @__PURE__ */ jsxs5("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
|
|
1180
|
+
/* @__PURE__ */ jsx5("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
|
|
801
1181
|
label
|
|
802
1182
|
] }),
|
|
803
|
-
/* @__PURE__ */
|
|
1183
|
+
/* @__PURE__ */ jsx5(
|
|
804
1184
|
"input",
|
|
805
1185
|
{
|
|
806
1186
|
type: "color",
|
|
@@ -811,17 +1191,18 @@ function VectorStylePanel({ controller, className }) {
|
|
|
811
1191
|
onChange: (e) => apply({ [key]: e.target.value })
|
|
812
1192
|
}
|
|
813
1193
|
),
|
|
814
|
-
on && !hex && /* @__PURE__ */
|
|
815
|
-
style?.mixed[key] && /* @__PURE__ */
|
|
1194
|
+
on && !hex && /* @__PURE__ */ jsx5("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
|
|
1195
|
+
style?.mixed[key] && /* @__PURE__ */ jsx5("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
|
|
816
1196
|
] });
|
|
817
1197
|
};
|
|
818
|
-
return /* @__PURE__ */
|
|
819
|
-
/* @__PURE__ */
|
|
1198
|
+
return /* @__PURE__ */ jsxs5("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
|
|
1199
|
+
/* @__PURE__ */ jsx5("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
|
|
820
1200
|
paintRow("fill", "Fill"),
|
|
821
1201
|
paintRow("stroke", "Stroke"),
|
|
822
|
-
/* @__PURE__ */
|
|
823
|
-
|
|
824
|
-
/* @__PURE__ */
|
|
1202
|
+
style && /* @__PURE__ */ jsx5(VectorGradientEditor, { controller }),
|
|
1203
|
+
/* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
|
|
1204
|
+
/* @__PURE__ */ jsx5("span", { className: "w-16", children: "Width" }),
|
|
1205
|
+
/* @__PURE__ */ jsx5(
|
|
825
1206
|
"input",
|
|
826
1207
|
{
|
|
827
1208
|
type: "number",
|
|
@@ -837,33 +1218,33 @@ function VectorStylePanel({ controller, className }) {
|
|
|
837
1218
|
}
|
|
838
1219
|
)
|
|
839
1220
|
] }),
|
|
840
|
-
/* @__PURE__ */
|
|
841
|
-
/* @__PURE__ */
|
|
842
|
-
/* @__PURE__ */
|
|
1221
|
+
/* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
|
|
1222
|
+
/* @__PURE__ */ jsx5("span", { className: "w-16", children: "Align" }),
|
|
1223
|
+
/* @__PURE__ */ jsx5("select", { "aria-label": "Stroke alignment", className: selectClass3, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(VECTOR_STROKE_ALIGN_LABELS).map((a) => /* @__PURE__ */ jsx5("option", { value: a, children: VECTOR_STROKE_ALIGN_LABELS[a] }, a)) }),
|
|
843
1224
|
mixed("strokeAlign")
|
|
844
1225
|
] }),
|
|
845
|
-
/* @__PURE__ */
|
|
846
|
-
/* @__PURE__ */
|
|
847
|
-
/* @__PURE__ */
|
|
1226
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
|
|
1227
|
+
/* @__PURE__ */ jsx5("span", { className: "w-16", children: "Profile" }),
|
|
1228
|
+
/* @__PURE__ */ jsxs5(
|
|
848
1229
|
"select",
|
|
849
1230
|
{
|
|
850
1231
|
"aria-label": "Width profile",
|
|
851
|
-
className:
|
|
1232
|
+
className: selectClass3,
|
|
852
1233
|
value: source.widthProfile,
|
|
853
1234
|
onChange: (e) => {
|
|
854
1235
|
if (e.target.value !== "custom") setProfile(e.target.value);
|
|
855
1236
|
},
|
|
856
1237
|
children: [
|
|
857
|
-
VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */
|
|
858
|
-
source.widthProfile === "custom" && /* @__PURE__ */
|
|
1238
|
+
VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx5("option", { value: p.id, children: p.label }, p.id)),
|
|
1239
|
+
source.widthProfile === "custom" && /* @__PURE__ */ jsx5("option", { value: "custom", disabled: true, children: "Custom" })
|
|
859
1240
|
]
|
|
860
1241
|
}
|
|
861
1242
|
),
|
|
862
|
-
/* @__PURE__ */
|
|
1243
|
+
/* @__PURE__ */ jsx5(
|
|
863
1244
|
"button",
|
|
864
1245
|
{
|
|
865
1246
|
type: "button",
|
|
866
|
-
className:
|
|
1247
|
+
className: buttonClass3,
|
|
867
1248
|
title: "Reset to uniform width",
|
|
868
1249
|
"aria-label": "Reset to uniform width",
|
|
869
1250
|
disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
|
|
@@ -873,27 +1254,27 @@ function VectorStylePanel({ controller, className }) {
|
|
|
873
1254
|
),
|
|
874
1255
|
mixed("widthProfile")
|
|
875
1256
|
] }),
|
|
876
|
-
source.hasOpenPath && /* @__PURE__ */
|
|
1257
|
+
source.hasOpenPath && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
877
1258
|
arrowRow("start", "Start"),
|
|
878
1259
|
arrowRow("end", "End"),
|
|
879
|
-
(source.startArrow || source.endArrow) && /* @__PURE__ */
|
|
880
|
-
/* @__PURE__ */
|
|
881
|
-
/* @__PURE__ */
|
|
882
|
-
/* @__PURE__ */
|
|
883
|
-
/* @__PURE__ */
|
|
1260
|
+
(source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
|
|
1261
|
+
/* @__PURE__ */ jsx5("span", { className: "w-16", children: "Heads" }),
|
|
1262
|
+
/* @__PURE__ */ jsxs5("select", { "aria-label": "Arrowhead alignment", className: selectClass3, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
|
|
1263
|
+
/* @__PURE__ */ jsx5("option", { value: "extend", children: "Tip past end" }),
|
|
1264
|
+
/* @__PURE__ */ jsx5("option", { value: "tip", children: "Tip at end" })
|
|
884
1265
|
] })
|
|
885
1266
|
] })
|
|
886
1267
|
] }),
|
|
887
|
-
source.corners > 0 && /* @__PURE__ */
|
|
888
|
-
/* @__PURE__ */
|
|
889
|
-
/* @__PURE__ */
|
|
1268
|
+
source.corners > 0 && /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
|
|
1269
|
+
/* @__PURE__ */ jsx5("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
|
|
1270
|
+
/* @__PURE__ */ jsx5(
|
|
890
1271
|
"input",
|
|
891
1272
|
{
|
|
892
1273
|
type: "number",
|
|
893
1274
|
min: 0,
|
|
894
1275
|
step: 1,
|
|
895
1276
|
"aria-label": "Corner radius",
|
|
896
|
-
className:
|
|
1277
|
+
className: numberClass3,
|
|
897
1278
|
value: Math.round(source.cornerRadius * 100) / 100,
|
|
898
1279
|
onChange: (e) => {
|
|
899
1280
|
const n = parseFloat(e.target.value);
|
|
@@ -901,12 +1282,12 @@ function VectorStylePanel({ controller, className }) {
|
|
|
901
1282
|
}
|
|
902
1283
|
}
|
|
903
1284
|
),
|
|
904
|
-
/* @__PURE__ */
|
|
1285
|
+
/* @__PURE__ */ jsx5("select", { "aria-label": "Corner kind", className: selectClass3, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ jsx5("option", { value: k, children: VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
|
|
905
1286
|
mixed("cornerRadius")
|
|
906
1287
|
] }),
|
|
907
|
-
style && /* @__PURE__ */
|
|
908
|
-
/* @__PURE__ */
|
|
909
|
-
/* @__PURE__ */
|
|
1288
|
+
style && /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
|
|
1289
|
+
/* @__PURE__ */ jsx5("span", { className: "w-16", children: "Opacity" }),
|
|
1290
|
+
/* @__PURE__ */ jsx5(
|
|
910
1291
|
"input",
|
|
911
1292
|
{
|
|
912
1293
|
type: "range",
|
|
@@ -919,25 +1300,29 @@ function VectorStylePanel({ controller, className }) {
|
|
|
919
1300
|
onChange: (e) => apply({ opacity: parseFloat(e.target.value) })
|
|
920
1301
|
}
|
|
921
1302
|
)
|
|
922
|
-
] })
|
|
1303
|
+
] }),
|
|
1304
|
+
style && /* @__PURE__ */ jsx5(VectorEffectsPanel, { controller })
|
|
923
1305
|
] });
|
|
924
1306
|
}
|
|
925
1307
|
|
|
926
1308
|
// src/vector/VectorEditor.tsx
|
|
927
|
-
import { jsx as
|
|
1309
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
928
1310
|
function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }) {
|
|
929
|
-
return /* @__PURE__ */
|
|
930
|
-
/* @__PURE__ */
|
|
931
|
-
/* @__PURE__ */
|
|
932
|
-
/* @__PURE__ */
|
|
933
|
-
showStylePanel && /* @__PURE__ */
|
|
1311
|
+
return /* @__PURE__ */ jsxs6("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
|
|
1312
|
+
/* @__PURE__ */ jsx6(VectorToolbar, { controller, tools }),
|
|
1313
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
|
|
1314
|
+
/* @__PURE__ */ jsx6("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx6(VectorCanvas, { controller, ...canvasProps }) }),
|
|
1315
|
+
showStylePanel && /* @__PURE__ */ jsx6(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
|
|
934
1316
|
] })
|
|
935
1317
|
] });
|
|
936
1318
|
}
|
|
937
1319
|
export {
|
|
938
1320
|
VectorCanvas,
|
|
939
1321
|
VectorEditor,
|
|
1322
|
+
VectorEffectsPanel,
|
|
1323
|
+
VectorGradientAnnotator,
|
|
940
1324
|
VectorGradientDefs,
|
|
1325
|
+
VectorGradientEditor,
|
|
941
1326
|
VectorSceneNodes,
|
|
942
1327
|
VectorStylePanel,
|
|
943
1328
|
VectorToolbar,
|