@pixerate/editor-react 0.32.0 → 0.34.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 +469 -113
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +39 -6
- package/dist/vector/index.d.ts +39 -6
- package/dist/vector/index.js +465 -101
- 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(
|
|
@@ -327,13 +365,28 @@ function VectorCanvas({
|
|
|
327
365
|
/* @__PURE__ */ jsxs2("g", { "data-vector-overlay": "", pointerEvents: "none", fill: "none", children: [
|
|
328
366
|
overlay.outlines.map((o) => /* @__PURE__ */ jsx2("path", { d: o.d, stroke: accentColor, strokeWidth: o.selected ? 1 : 1.5, opacity: o.selected ? 1 : 0.7 }, o.id)),
|
|
329
367
|
overlay.penPreview && /* @__PURE__ */ jsx2("path", { d: overlay.penPreview, stroke: accentColor, strokeDasharray: "4 3" }),
|
|
330
|
-
box && /* @__PURE__ */ jsxs2("g", { "data-vector-selection": "", transform: box.transform || void 0, children: [
|
|
368
|
+
box && /* @__PURE__ */ jsxs2("g", { "data-vector-selection": "", "data-free-distort": box.freeDistort || void 0, transform: box.transform || void 0, children: [
|
|
331
369
|
/* @__PURE__ */ jsx2("rect", { x: box.x, y: box.y, width: box.width, height: box.height, stroke: accentColor }),
|
|
332
370
|
/* @__PURE__ */ jsx2("line", { x1: box.x + box.width / 2, y1: box.y, x2: box.x + box.width / 2, y2: box.y - 24, stroke: accentColor }),
|
|
333
371
|
box.handles.map(
|
|
334
372
|
(h) => h.handle === "rotate" ? /* @__PURE__ */ jsx2("circle", { "data-handle": h.handle, cx: h.x, cy: h.y, r: hs / 2 + 1, fill: THEME2.handleFill, stroke: accentColor }, h.handle) : /* @__PURE__ */ jsx2("rect", { "data-handle": h.handle, x: h.x - hs / 2, y: h.y - hs / 2, width: hs, height: hs, fill: THEME2.handleFill, stroke: accentColor }, h.handle)
|
|
335
373
|
)
|
|
336
374
|
] }),
|
|
375
|
+
overlay.distort && /* @__PURE__ */ jsxs2("g", { "data-vector-distort": overlay.distort.mode, children: [
|
|
376
|
+
/* @__PURE__ */ jsx2("path", { d: overlay.distort.d, stroke: accentColor }),
|
|
377
|
+
overlay.distort.corners.map((c, i) => /* @__PURE__ */ jsx2("rect", { x: c.x - hs / 2, y: c.y - hs / 2, width: hs, height: hs, fill: THEME2.handleFill, stroke: accentColor }, i))
|
|
378
|
+
] }),
|
|
379
|
+
overlay.envelope && /* @__PURE__ */ jsxs2("g", { "data-vector-envelope-mesh": overlay.envelope.nodeId, children: [
|
|
380
|
+
/* @__PURE__ */ jsx2("path", { "data-vector-envelope-grid": "", d: overlay.envelope.grid, stroke: accentColor, strokeOpacity: 0.8 }),
|
|
381
|
+
overlay.envelope.handles.map((h) => /* @__PURE__ */ jsxs2("g", { "data-vector-envelope-handle": h.index, children: [
|
|
382
|
+
/* @__PURE__ */ jsx2("line", { x1: h.anchorX, y1: h.anchorY, x2: h.x, y2: h.y, stroke: accentColor, strokeOpacity: 0.6 }),
|
|
383
|
+
/* @__PURE__ */ jsx2("circle", { cx: h.x, cy: h.y, r: THEME2.handleRadius + (h.hovered || h.active ? 1 : 0), fill: h.active ? accentColor : THEME2.handleFill, stroke: accentColor })
|
|
384
|
+
] }, h.index)),
|
|
385
|
+
overlay.envelope.anchors.map((a) => {
|
|
386
|
+
const size = as + (a.hovered || a.active ? 2 : 0);
|
|
387
|
+
return /* @__PURE__ */ jsx2("rect", { "data-vector-envelope-anchor": a.index, x: a.x - size / 2, y: a.y - size / 2, width: size, height: size, fill: a.active ? accentColor : THEME2.handleFill, stroke: accentColor }, a.index);
|
|
388
|
+
})
|
|
389
|
+
] }),
|
|
337
390
|
overlay.handles.map((h) => /* @__PURE__ */ jsxs2("g", { "data-vector-handle": h.ref.handle, children: [
|
|
338
391
|
/* @__PURE__ */ jsx2("line", { x1: h.anchorX, y1: h.anchorY, x2: h.x, y2: h.y, stroke: accentColor }),
|
|
339
392
|
/* @__PURE__ */ jsx2("circle", { cx: h.x, cy: h.y, r: THEME2.handleRadius + (h.hovered ? 1 : 0), fill: THEME2.handleFill, stroke: accentColor })
|
|
@@ -369,6 +422,7 @@ function VectorCanvas({
|
|
|
369
422
|
},
|
|
370
423
|
`blend-anchor:${i}`
|
|
371
424
|
)),
|
|
425
|
+
overlay.gradient && /* @__PURE__ */ jsx2(VectorGradientAnnotator, { annotator: overlay.gradient, accentColor }),
|
|
372
426
|
overlay.widthSpot && /* @__PURE__ */ jsxs2("g", { "data-vector-width-spot": "", children: [
|
|
373
427
|
/* @__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
428
|
/* @__PURE__ */ jsx2("path", { d: vectorDiamondPath(overlay.widthSpot.x, overlay.widthSpot.y), fill: "none", stroke: accentColor })
|
|
@@ -474,7 +528,7 @@ function VectorCanvas({
|
|
|
474
528
|
}
|
|
475
529
|
|
|
476
530
|
// src/vector/VectorToolbar.tsx
|
|
477
|
-
import
|
|
531
|
+
import React4 from "react";
|
|
478
532
|
import {
|
|
479
533
|
VECTOR_TOOLS,
|
|
480
534
|
VECTOR_TOOL_GLYPHS,
|
|
@@ -496,7 +550,7 @@ import {
|
|
|
496
550
|
getVectorSelectionStyle,
|
|
497
551
|
widthProfilePreset,
|
|
498
552
|
widthProfilePresetId,
|
|
499
|
-
vectorPaintToHex,
|
|
553
|
+
vectorPaintToHex as vectorPaintToHex2,
|
|
500
554
|
vectorToolLabel
|
|
501
555
|
} from "@pixerate/editor/vector";
|
|
502
556
|
|
|
@@ -643,11 +697,302 @@ function VectorEffectsPanel({ controller, className }) {
|
|
|
643
697
|
] });
|
|
644
698
|
}
|
|
645
699
|
|
|
700
|
+
// src/vector/VectorEnvelopePanel.tsx
|
|
701
|
+
import React3 from "react";
|
|
702
|
+
import { VECTOR_ENVELOPE_STYLE_OPTIONS, getVectorEnvelopePanel } from "@pixerate/editor/vector";
|
|
703
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
704
|
+
var buttonClass2 = "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";
|
|
705
|
+
var numberClass2 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
|
|
706
|
+
var selectClass2 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
|
|
707
|
+
var labelClass2 = "w-full truncate text-[11px] text-muted-foreground";
|
|
708
|
+
var SLIDERS = [
|
|
709
|
+
{ key: "bend", label: "Bend %" },
|
|
710
|
+
{ key: "horizontal", label: "Horizontal distortion %" },
|
|
711
|
+
{ key: "vertical", label: "Vertical distortion %" }
|
|
712
|
+
];
|
|
713
|
+
function VectorEnvelopePanel({ controller, className }) {
|
|
714
|
+
const state = useVectorEditorState(controller);
|
|
715
|
+
const panel = getVectorEnvelopePanel(state);
|
|
716
|
+
const [size, setSize] = React3.useState({ rows: 2, columns: 2 });
|
|
717
|
+
if (!panel) return null;
|
|
718
|
+
const env = panel.envelope;
|
|
719
|
+
const ids = panel.nodeId ? [panel.nodeId] : void 0;
|
|
720
|
+
const end = () => controller.endMergedEdit();
|
|
721
|
+
const set = (patch, merge = false) => controller.envelope(patch, { ids, merge });
|
|
722
|
+
const rows = env?.params.rows ?? size.rows;
|
|
723
|
+
const columns = env?.params.columns ?? size.columns;
|
|
724
|
+
const setCount = (key, raw) => {
|
|
725
|
+
const n = Math.round(parseFloat(raw));
|
|
726
|
+
if (!Number.isFinite(n) || n < 1 || n > 16) return;
|
|
727
|
+
if (env) set({ [key]: n });
|
|
728
|
+
else setSize({ ...size, [key]: n });
|
|
729
|
+
};
|
|
730
|
+
return /* @__PURE__ */ jsxs4("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-envelope": "", "aria-label": "Envelope", children: [
|
|
731
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
732
|
+
/* @__PURE__ */ jsx4("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Envelope" }),
|
|
733
|
+
/* @__PURE__ */ jsx4("button", { type: "button", "data-vector-envelope-action": "warp", className: buttonClass2, title: "Make with Warp", "aria-label": "Make with Warp", disabled: !panel.canMake || !!env, onClick: () => set({ style: "arc", bend: 50 }), children: "Warp" }),
|
|
734
|
+
/* @__PURE__ */ jsx4(
|
|
735
|
+
"button",
|
|
736
|
+
{
|
|
737
|
+
type: "button",
|
|
738
|
+
"data-vector-envelope-action": "mesh",
|
|
739
|
+
className: buttonClass2,
|
|
740
|
+
title: "Make with Mesh",
|
|
741
|
+
"aria-label": "Make with Mesh",
|
|
742
|
+
disabled: !panel.canMake || !!env,
|
|
743
|
+
onClick: () => set({ style: "mesh", rows: size.rows, columns: size.columns }),
|
|
744
|
+
children: "Mesh"
|
|
745
|
+
}
|
|
746
|
+
),
|
|
747
|
+
/* @__PURE__ */ jsx4(
|
|
748
|
+
"button",
|
|
749
|
+
{
|
|
750
|
+
type: "button",
|
|
751
|
+
"data-vector-envelope-action": "top",
|
|
752
|
+
className: buttonClass2,
|
|
753
|
+
title: "Make with Top Object",
|
|
754
|
+
"aria-label": "Make with Top Object",
|
|
755
|
+
disabled: !panel.canMakeWithTopObject,
|
|
756
|
+
onClick: () => controller.makeEnvelopeWithTopObject({ rows: size.rows, columns: size.columns }),
|
|
757
|
+
children: "Top"
|
|
758
|
+
}
|
|
759
|
+
),
|
|
760
|
+
/* @__PURE__ */ jsx4("button", { type: "button", "data-vector-envelope-action": "release", className: buttonClass2, title: "Release Envelope", "aria-label": "Release Envelope", disabled: !panel.canRelease, onClick: () => controller.releaseEnvelope(), children: "Release" }),
|
|
761
|
+
/* @__PURE__ */ jsx4("button", { type: "button", "data-vector-envelope-action": "expand", className: buttonClass2, title: "Expand Envelope", "aria-label": "Expand Envelope", disabled: !panel.canRelease, onClick: () => controller.expandAppearance(), children: "Expand" })
|
|
762
|
+
] }),
|
|
763
|
+
env && /* @__PURE__ */ jsxs4("label", { className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-foreground", "data-vector-envelope-param": "style", children: [
|
|
764
|
+
/* @__PURE__ */ jsx4("span", { className: labelClass2, children: "Warp" }),
|
|
765
|
+
/* @__PURE__ */ jsx4("select", { "aria-label": "Envelope warp", className: selectClass2, value: env.params.style, onChange: (e) => set({ style: e.target.value }), children: VECTOR_ENVELOPE_STYLE_OPTIONS.map((o) => /* @__PURE__ */ jsx4("option", { value: o.value, children: o.label }, o.value)) })
|
|
766
|
+
] }),
|
|
767
|
+
env && env.params.style !== "mesh" && SLIDERS.map((s) => {
|
|
768
|
+
const value = env.params[s.key];
|
|
769
|
+
const onChange = (ev) => {
|
|
770
|
+
const n = parseFloat(ev.target.value);
|
|
771
|
+
if (Number.isFinite(n)) set({ [s.key]: n }, true);
|
|
772
|
+
};
|
|
773
|
+
return /* @__PURE__ */ jsxs4("label", { className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-foreground", "data-vector-envelope-param": s.key, children: [
|
|
774
|
+
/* @__PURE__ */ jsx4("span", { className: labelClass2, children: s.label }),
|
|
775
|
+
/* @__PURE__ */ jsx4(
|
|
776
|
+
"input",
|
|
777
|
+
{
|
|
778
|
+
type: "range",
|
|
779
|
+
min: -100,
|
|
780
|
+
max: 100,
|
|
781
|
+
step: 1,
|
|
782
|
+
"aria-label": `Envelope ${s.label} slider`,
|
|
783
|
+
className: "h-4 min-w-0 flex-1 accent-primary",
|
|
784
|
+
value,
|
|
785
|
+
onChange,
|
|
786
|
+
onPointerUp: end,
|
|
787
|
+
onKeyUp: end,
|
|
788
|
+
onBlur: end
|
|
789
|
+
}
|
|
790
|
+
),
|
|
791
|
+
/* @__PURE__ */ jsx4("input", { type: "number", min: -100, max: 100, step: 1, "aria-label": `Envelope ${s.label}`, className: numberClass2, value, onChange, onBlur: end })
|
|
792
|
+
] }, s.key);
|
|
793
|
+
}),
|
|
794
|
+
env && env.params.style !== "mesh" && /* @__PURE__ */ jsxs4("label", { className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-foreground", "data-vector-envelope-param": "orientation", children: [
|
|
795
|
+
/* @__PURE__ */ jsx4("span", { className: labelClass2, children: "Orientation" }),
|
|
796
|
+
/* @__PURE__ */ jsxs4("select", { "aria-label": "Envelope orientation", className: selectClass2, value: env.params.orientation, onChange: (e) => set({ orientation: e.target.value }), children: [
|
|
797
|
+
/* @__PURE__ */ jsx4("option", { value: "horizontal", children: "Horizontal" }),
|
|
798
|
+
/* @__PURE__ */ jsx4("option", { value: "vertical", children: "Vertical" })
|
|
799
|
+
] })
|
|
800
|
+
] }),
|
|
801
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-envelope-param": "mesh", children: [
|
|
802
|
+
/* @__PURE__ */ jsx4("span", { className: "min-w-0 flex-1 truncate text-[11px] text-muted-foreground", children: "Mesh rows \xD7 columns" }),
|
|
803
|
+
/* @__PURE__ */ jsx4("input", { type: "number", min: 1, max: 16, step: 1, "aria-label": "Envelope mesh rows", className: numberClass2, value: rows, onChange: (e) => setCount("rows", e.target.value) }),
|
|
804
|
+
/* @__PURE__ */ jsx4("input", { type: "number", min: 1, max: 16, step: 1, "aria-label": "Envelope mesh columns", className: numberClass2, value: columns, onChange: (e) => setCount("columns", e.target.value) })
|
|
805
|
+
] }),
|
|
806
|
+
panel.count > 1 && !panel.canMakeWithTopObject && /* @__PURE__ */ jsx4("p", { className: "text-[11px] text-muted-foreground", children: "Select one object to edit its envelope." }),
|
|
807
|
+
env && /* @__PURE__ */ jsx4("p", { className: "text-[11px] text-muted-foreground", children: "Edit the mesh with the Envelope tool (U)." })
|
|
808
|
+
] });
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// src/vector/VectorGradientEditor.tsx
|
|
812
|
+
import {
|
|
813
|
+
VECTOR_GRADIENT_TYPES,
|
|
814
|
+
VECTOR_GRADIENT_SPREADS,
|
|
815
|
+
VECTOR_GRADIENT_INTERPOLATIONS,
|
|
816
|
+
VECTOR_STROKE_GRADIENT_MODES,
|
|
817
|
+
VECTOR_GRADIENT_TYPE_LABELS,
|
|
818
|
+
VECTOR_GRADIENT_SPREAD_LABELS,
|
|
819
|
+
VECTOR_GRADIENT_INTERPOLATION_LABELS,
|
|
820
|
+
VECTOR_STROKE_GRADIENT_MODE_LABELS,
|
|
821
|
+
getVectorGradientEditor,
|
|
822
|
+
vectorPaintToHex
|
|
823
|
+
} from "@pixerate/editor/vector";
|
|
824
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
825
|
+
var selectClass3 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
|
|
826
|
+
var numberClass3 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
|
|
827
|
+
var buttonClass3 = "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";
|
|
828
|
+
var percent = (v) => Math.round(v * 1e3) / 10;
|
|
829
|
+
function VectorGradientEditor({ controller, className }) {
|
|
830
|
+
const state = useVectorEditorState(controller);
|
|
831
|
+
const ed = getVectorGradientEditor(state);
|
|
832
|
+
if (!ed) return null;
|
|
833
|
+
const g = ed.gradient;
|
|
834
|
+
const sel = ed.selected;
|
|
835
|
+
const end = () => controller.endMergedEdit();
|
|
836
|
+
const setType = (value) => {
|
|
837
|
+
if (value === "solid") {
|
|
838
|
+
const first = ed.stops[0];
|
|
839
|
+
controller.setStyle({ [ed.target]: vectorPaintToHex(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
|
|
840
|
+
} else controller.setGradient({ type: value });
|
|
841
|
+
};
|
|
842
|
+
return /* @__PURE__ */ jsxs5("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
|
|
843
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1.5", children: [
|
|
844
|
+
/* @__PURE__ */ jsx5("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
|
|
845
|
+
["fill", "stroke"].map((t) => /* @__PURE__ */ jsx5("button", { type: "button", className: buttonClass3, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
|
|
846
|
+
ed.mixed && /* @__PURE__ */ jsx5("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
|
|
847
|
+
] }),
|
|
848
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1.5", children: [
|
|
849
|
+
/* @__PURE__ */ jsx5("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
|
|
850
|
+
/* @__PURE__ */ jsxs5("select", { "aria-label": "Gradient type", className: selectClass3, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
|
|
851
|
+
/* @__PURE__ */ jsx5("option", { value: "solid", children: "Solid" }),
|
|
852
|
+
VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ jsx5("option", { value: t, children: VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
|
|
853
|
+
] }),
|
|
854
|
+
/* @__PURE__ */ jsx5("button", { type: "button", className: buttonClass3, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
|
|
855
|
+
] }),
|
|
856
|
+
g && /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
857
|
+
/* @__PURE__ */ jsxs5("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
|
|
858
|
+
/* @__PURE__ */ jsx5(
|
|
859
|
+
"div",
|
|
860
|
+
{
|
|
861
|
+
role: "button",
|
|
862
|
+
tabIndex: -1,
|
|
863
|
+
"aria-label": "Add gradient stop",
|
|
864
|
+
className: "absolute inset-x-1.5 top-0 h-4 cursor-copy rounded border border-border",
|
|
865
|
+
style: { backgroundImage: `${ed.preview}, repeating-conic-gradient(#ddd 0% 25%, #fff 0% 50%)`, backgroundSize: "auto, 8px 8px" },
|
|
866
|
+
onClick: (e) => {
|
|
867
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
868
|
+
controller.addGradientStop(r.width > 0 ? Math.min(1, Math.max(0, (e.clientX - r.left) / r.width)) : 0.5);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
),
|
|
872
|
+
ed.stops.map((s) => /* @__PURE__ */ jsx5(
|
|
873
|
+
"button",
|
|
874
|
+
{
|
|
875
|
+
type: "button",
|
|
876
|
+
"data-vector-gradient-stop": s.index,
|
|
877
|
+
"aria-label": `Gradient stop ${s.index + 1} at ${percent(s.offset)}%`,
|
|
878
|
+
"aria-pressed": s.selected,
|
|
879
|
+
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",
|
|
880
|
+
style: { left: `calc(6px + (100% - 12px) * ${s.offset})`, background: s.color, opacity: 0.4 + 0.6 * (s.opacity ?? 1) },
|
|
881
|
+
onClick: () => controller.selectGradientStop(s.selected ? null : s.index)
|
|
882
|
+
},
|
|
883
|
+
s.index
|
|
884
|
+
))
|
|
885
|
+
] }),
|
|
886
|
+
sel && /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
|
|
887
|
+
/* @__PURE__ */ jsx5(
|
|
888
|
+
"input",
|
|
889
|
+
{
|
|
890
|
+
type: "color",
|
|
891
|
+
"aria-label": "Stop colour",
|
|
892
|
+
className: "h-7 w-9 cursor-pointer rounded border border-border bg-background",
|
|
893
|
+
value: vectorPaintToHex(sel.color) ?? "#000000",
|
|
894
|
+
onChange: (e) => controller.setGradientStop(sel.index, { color: e.target.value }, { merge: `gradient-stop-color-${sel.index}` }),
|
|
895
|
+
onBlur: end
|
|
896
|
+
}
|
|
897
|
+
),
|
|
898
|
+
/* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
|
|
899
|
+
"Opacity",
|
|
900
|
+
/* @__PURE__ */ jsx5(
|
|
901
|
+
"input",
|
|
902
|
+
{
|
|
903
|
+
type: "number",
|
|
904
|
+
min: 0,
|
|
905
|
+
max: 100,
|
|
906
|
+
step: 5,
|
|
907
|
+
"aria-label": "Stop opacity",
|
|
908
|
+
className: numberClass3,
|
|
909
|
+
value: percent(sel.opacity ?? 1),
|
|
910
|
+
onChange: (e) => {
|
|
911
|
+
const n = parseFloat(e.target.value);
|
|
912
|
+
if (Number.isFinite(n)) controller.setGradientStop(sel.index, { opacity: n / 100 });
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
)
|
|
916
|
+
] }),
|
|
917
|
+
/* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
|
|
918
|
+
"At",
|
|
919
|
+
/* @__PURE__ */ jsx5(
|
|
920
|
+
"input",
|
|
921
|
+
{
|
|
922
|
+
type: "number",
|
|
923
|
+
min: 0,
|
|
924
|
+
max: 100,
|
|
925
|
+
step: 1,
|
|
926
|
+
"aria-label": "Stop position",
|
|
927
|
+
className: numberClass3,
|
|
928
|
+
value: percent(sel.offset),
|
|
929
|
+
onChange: (e) => {
|
|
930
|
+
const n = parseFloat(e.target.value);
|
|
931
|
+
if (Number.isFinite(n)) controller.setGradientStop(sel.index, { offset: n / 100 });
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
)
|
|
935
|
+
] }),
|
|
936
|
+
!sel.last && /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
|
|
937
|
+
"Midpoint",
|
|
938
|
+
/* @__PURE__ */ jsx5(
|
|
939
|
+
"input",
|
|
940
|
+
{
|
|
941
|
+
type: "number",
|
|
942
|
+
min: 5,
|
|
943
|
+
max: 95,
|
|
944
|
+
step: 1,
|
|
945
|
+
"aria-label": "Stop midpoint",
|
|
946
|
+
className: numberClass3,
|
|
947
|
+
value: percent(sel.midpoint ?? 0.5),
|
|
948
|
+
onChange: (e) => {
|
|
949
|
+
const n = parseFloat(e.target.value);
|
|
950
|
+
if (Number.isFinite(n)) controller.setGradientStop(sel.index, { midpoint: n / 100 });
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
)
|
|
954
|
+
] }),
|
|
955
|
+
/* @__PURE__ */ jsx5("button", { type: "button", className: buttonClass3, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
|
|
956
|
+
] }),
|
|
957
|
+
/* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
|
|
958
|
+
/* @__PURE__ */ jsx5("span", { className: "w-16", children: "Colours" }),
|
|
959
|
+
/* @__PURE__ */ jsx5("select", { "aria-label": "Gradient interpolation", className: selectClass3, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ jsx5("option", { value: i, children: VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
|
|
960
|
+
] }),
|
|
961
|
+
/* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
|
|
962
|
+
/* @__PURE__ */ jsx5("span", { className: "w-16", children: "Smooth" }),
|
|
963
|
+
/* @__PURE__ */ jsx5(
|
|
964
|
+
"input",
|
|
965
|
+
{
|
|
966
|
+
type: "range",
|
|
967
|
+
min: 0,
|
|
968
|
+
max: 1,
|
|
969
|
+
step: 0.05,
|
|
970
|
+
"aria-label": "Gradient smoothness",
|
|
971
|
+
className: "h-4 flex-1 accent-primary",
|
|
972
|
+
value: ed.smoothness,
|
|
973
|
+
onChange: (e) => controller.setGradient({ smoothness: parseFloat(e.target.value) }, { merge: "gradient-smoothness" }),
|
|
974
|
+
onPointerUp: end,
|
|
975
|
+
onBlur: end
|
|
976
|
+
}
|
|
977
|
+
)
|
|
978
|
+
] }),
|
|
979
|
+
/* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
|
|
980
|
+
/* @__PURE__ */ jsx5("span", { className: "w-16", children: "Spread" }),
|
|
981
|
+
/* @__PURE__ */ jsx5("select", { "aria-label": "Gradient spread", className: selectClass3, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ jsx5("option", { value: sp, children: VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
|
|
982
|
+
] }),
|
|
983
|
+
ed.target === "stroke" && /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
|
|
984
|
+
/* @__PURE__ */ jsx5("span", { className: "w-16", children: "On stroke" }),
|
|
985
|
+
/* @__PURE__ */ jsx5("select", { "aria-label": "Gradient on stroke", className: selectClass3, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ jsx5("option", { value: m, children: VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
|
|
986
|
+
] })
|
|
987
|
+
] })
|
|
988
|
+
] });
|
|
989
|
+
}
|
|
990
|
+
|
|
646
991
|
// src/vector/VectorToolbar.tsx
|
|
647
|
-
import { Fragment as
|
|
992
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
648
993
|
var VECTOR_SNAP_TITLE = "Snap to objects";
|
|
649
994
|
var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
|
|
650
|
-
var
|
|
995
|
+
var buttonClass4 = "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";
|
|
651
996
|
function VectorToolbar({
|
|
652
997
|
controller,
|
|
653
998
|
tools = VECTOR_TOOLS,
|
|
@@ -656,6 +1001,7 @@ function VectorToolbar({
|
|
|
656
1001
|
showSnap = true,
|
|
657
1002
|
showPathEdit = true,
|
|
658
1003
|
showBlend = true,
|
|
1004
|
+
showDistort = true,
|
|
659
1005
|
className
|
|
660
1006
|
}) {
|
|
661
1007
|
const state = useVectorEditorState(controller);
|
|
@@ -663,16 +1009,16 @@ function VectorToolbar({
|
|
|
663
1009
|
const points = state.tool === "direct" ? state.selectedAnchors.length : 0;
|
|
664
1010
|
const editing = state.editing;
|
|
665
1011
|
const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
|
|
666
|
-
const [live, setLive] =
|
|
1012
|
+
const [live, setLive] = React4.useState(false);
|
|
667
1013
|
const booleanDisabled = live ? count < 1 : count < 2;
|
|
668
|
-
const act = (label, title, run, disabled = false) => /* @__PURE__ */
|
|
669
|
-
return /* @__PURE__ */
|
|
670
|
-
/* @__PURE__ */
|
|
1014
|
+
const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx6("button", { type: "button", className: buttonClass4, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
|
|
1015
|
+
return /* @__PURE__ */ jsxs6("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
|
|
1016
|
+
/* @__PURE__ */ jsx6("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx6(
|
|
671
1017
|
"button",
|
|
672
1018
|
{
|
|
673
1019
|
type: "button",
|
|
674
1020
|
"data-vector-tool": tool,
|
|
675
|
-
className:
|
|
1021
|
+
className: buttonClass4,
|
|
676
1022
|
"aria-pressed": state.tool === tool,
|
|
677
1023
|
title: `${vectorToolLabel(tool)} (${VECTOR_TOOL_SHORTCUTS[tool]})`,
|
|
678
1024
|
"aria-label": vectorToolLabel(tool),
|
|
@@ -681,16 +1027,16 @@ function VectorToolbar({
|
|
|
681
1027
|
},
|
|
682
1028
|
tool
|
|
683
1029
|
)) }),
|
|
684
|
-
showHistory && /* @__PURE__ */
|
|
1030
|
+
showHistory && /* @__PURE__ */ jsxs6("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
|
|
685
1031
|
act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
|
|
686
1032
|
act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
|
|
687
1033
|
] }),
|
|
688
|
-
showSnap && /* @__PURE__ */
|
|
1034
|
+
showSnap && /* @__PURE__ */ jsx6("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: /* @__PURE__ */ jsx6(
|
|
689
1035
|
"button",
|
|
690
1036
|
{
|
|
691
1037
|
type: "button",
|
|
692
1038
|
"data-vector-snap": "",
|
|
693
|
-
className:
|
|
1039
|
+
className: buttonClass4,
|
|
694
1040
|
"aria-pressed": state.snap.objects,
|
|
695
1041
|
title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
|
|
696
1042
|
"aria-label": VECTOR_SNAP_TITLE,
|
|
@@ -698,13 +1044,13 @@ function VectorToolbar({
|
|
|
698
1044
|
children: "Snap"
|
|
699
1045
|
}
|
|
700
1046
|
) }),
|
|
701
|
-
showPathEdit && /* @__PURE__ */
|
|
702
|
-
VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */
|
|
1047
|
+
showPathEdit && /* @__PURE__ */ jsxs6("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
|
|
1048
|
+
VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx6(
|
|
703
1049
|
"button",
|
|
704
1050
|
{
|
|
705
1051
|
type: "button",
|
|
706
1052
|
"data-vector-path-action": a.id,
|
|
707
|
-
className:
|
|
1053
|
+
className: buttonClass4,
|
|
708
1054
|
title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
|
|
709
1055
|
"aria-label": a.title,
|
|
710
1056
|
disabled: !a.enabled(count, points),
|
|
@@ -713,12 +1059,12 @@ function VectorToolbar({
|
|
|
713
1059
|
},
|
|
714
1060
|
a.id
|
|
715
1061
|
)),
|
|
716
|
-
VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */
|
|
1062
|
+
VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx6(
|
|
717
1063
|
"button",
|
|
718
1064
|
{
|
|
719
1065
|
type: "button",
|
|
720
1066
|
"data-vector-edit": t.key,
|
|
721
|
-
className:
|
|
1067
|
+
className: buttonClass4,
|
|
722
1068
|
"aria-pressed": editing[t.key],
|
|
723
1069
|
title: t.title,
|
|
724
1070
|
"aria-label": t.title,
|
|
@@ -727,9 +1073,9 @@ function VectorToolbar({
|
|
|
727
1073
|
},
|
|
728
1074
|
t.key
|
|
729
1075
|
)),
|
|
730
|
-
state.tool === "pencil" && /* @__PURE__ */
|
|
1076
|
+
state.tool === "pencil" && /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
|
|
731
1077
|
"Smoothing",
|
|
732
|
-
/* @__PURE__ */
|
|
1078
|
+
/* @__PURE__ */ jsx6(
|
|
733
1079
|
"input",
|
|
734
1080
|
{
|
|
735
1081
|
type: "range",
|
|
@@ -743,9 +1089,9 @@ function VectorToolbar({
|
|
|
743
1089
|
}
|
|
744
1090
|
)
|
|
745
1091
|
] }),
|
|
746
|
-
state.tool === "eraser" && /* @__PURE__ */
|
|
1092
|
+
state.tool === "eraser" && /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
|
|
747
1093
|
"Size",
|
|
748
|
-
/* @__PURE__ */
|
|
1094
|
+
/* @__PURE__ */ jsx6(
|
|
749
1095
|
"input",
|
|
750
1096
|
{
|
|
751
1097
|
type: "number",
|
|
@@ -761,12 +1107,12 @@ function VectorToolbar({
|
|
|
761
1107
|
}
|
|
762
1108
|
)
|
|
763
1109
|
] }),
|
|
764
|
-
state.tool === "paintBucket" && /* @__PURE__ */
|
|
1110
|
+
state.tool === "paintBucket" && /* @__PURE__ */ jsx6(
|
|
765
1111
|
"button",
|
|
766
1112
|
{
|
|
767
1113
|
type: "button",
|
|
768
1114
|
"data-vector-fill-gap": "",
|
|
769
|
-
className:
|
|
1115
|
+
className: buttonClass4,
|
|
770
1116
|
"aria-pressed": editing.fillGap > 0,
|
|
771
1117
|
title: "Close small gaps between path ends when filling",
|
|
772
1118
|
"aria-label": "Close gaps",
|
|
@@ -775,15 +1121,28 @@ function VectorToolbar({
|
|
|
775
1121
|
}
|
|
776
1122
|
)
|
|
777
1123
|
] }),
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1124
|
+
showDistort && /* @__PURE__ */ jsx6("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx6(
|
|
1125
|
+
"button",
|
|
1126
|
+
{
|
|
1127
|
+
type: "button",
|
|
1128
|
+
"data-vector-free-distort": "",
|
|
1129
|
+
className: buttonClass4,
|
|
1130
|
+
"aria-pressed": state.freeDistort,
|
|
1131
|
+
title: `Free Distort: drag a corner of the selection box to distort in perspective (Shift+Alt+${modKeyLabel()}: symmetric perspective; ${modKeyLabel()}-drag a side to skew)`,
|
|
1132
|
+
"aria-label": "Free Distort",
|
|
1133
|
+
onClick: () => controller.setFreeDistort(!state.freeDistort),
|
|
1134
|
+
children: "Distort"
|
|
1135
|
+
}
|
|
1136
|
+
) }),
|
|
1137
|
+
showBlend && /* @__PURE__ */ jsx6(VectorBlendControls, { controller }),
|
|
1138
|
+
showArrange && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
1139
|
+
/* @__PURE__ */ jsxs6("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
|
|
1140
|
+
/* @__PURE__ */ jsx6(
|
|
782
1141
|
"button",
|
|
783
1142
|
{
|
|
784
1143
|
type: "button",
|
|
785
1144
|
"data-vector-live-boolean": "",
|
|
786
|
-
className:
|
|
1145
|
+
className: buttonClass4,
|
|
787
1146
|
"aria-pressed": live,
|
|
788
1147
|
title: "Live booleans: keep the shapes editable",
|
|
789
1148
|
"aria-label": "Live booleans",
|
|
@@ -798,12 +1157,12 @@ function VectorToolbar({
|
|
|
798
1157
|
act("G", "Group", () => controller.group(), count < 1),
|
|
799
1158
|
act("U", "Ungroup", () => controller.ungroup(), !groupSelected)
|
|
800
1159
|
] }),
|
|
801
|
-
/* @__PURE__ */
|
|
1160
|
+
/* @__PURE__ */ jsx6("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx6(
|
|
802
1161
|
"button",
|
|
803
1162
|
{
|
|
804
1163
|
type: "button",
|
|
805
1164
|
"data-vector-pathfinder": a.id,
|
|
806
|
-
className:
|
|
1165
|
+
className: buttonClass4,
|
|
807
1166
|
title: a.title,
|
|
808
1167
|
"aria-label": a.title,
|
|
809
1168
|
disabled: count < a.min,
|
|
@@ -812,7 +1171,7 @@ function VectorToolbar({
|
|
|
812
1171
|
},
|
|
813
1172
|
a.id
|
|
814
1173
|
)) }),
|
|
815
|
-
/* @__PURE__ */
|
|
1174
|
+
/* @__PURE__ */ jsxs6("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
|
|
816
1175
|
act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
|
|
817
1176
|
act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
|
|
818
1177
|
act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
|
|
@@ -830,19 +1189,19 @@ function VectorBlendControls({ controller }) {
|
|
|
830
1189
|
const blend = getVectorBlendControls(state);
|
|
831
1190
|
const o = blend.options;
|
|
832
1191
|
const selected = blend.ids.length > 0;
|
|
833
|
-
return /* @__PURE__ */
|
|
834
|
-
/* @__PURE__ */
|
|
835
|
-
/* @__PURE__ */
|
|
1192
|
+
return /* @__PURE__ */ jsxs6("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
|
|
1193
|
+
/* @__PURE__ */ jsx6("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass4, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
|
|
1194
|
+
/* @__PURE__ */ jsx6(
|
|
836
1195
|
"select",
|
|
837
1196
|
{
|
|
838
1197
|
"aria-label": "Blend spacing",
|
|
839
1198
|
className: "h-7 rounded border border-border bg-background px-1 text-xs",
|
|
840
1199
|
value: o.spacing,
|
|
841
1200
|
onChange: (e) => applyVectorBlendOptions(controller, { spacing: e.target.value }),
|
|
842
|
-
children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */
|
|
1201
|
+
children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx6("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
|
|
843
1202
|
}
|
|
844
1203
|
),
|
|
845
|
-
o.spacing !== "smoothColor" && /* @__PURE__ */
|
|
1204
|
+
o.spacing !== "smoothColor" && /* @__PURE__ */ jsx6(
|
|
846
1205
|
"input",
|
|
847
1206
|
{
|
|
848
1207
|
type: "number",
|
|
@@ -858,12 +1217,12 @@ function VectorBlendControls({ controller }) {
|
|
|
858
1217
|
}
|
|
859
1218
|
}
|
|
860
1219
|
),
|
|
861
|
-
/* @__PURE__ */
|
|
1220
|
+
/* @__PURE__ */ jsx6(
|
|
862
1221
|
"button",
|
|
863
1222
|
{
|
|
864
1223
|
type: "button",
|
|
865
1224
|
"data-vector-blend-action": "orient",
|
|
866
|
-
className:
|
|
1225
|
+
className: buttonClass4,
|
|
867
1226
|
"aria-pressed": o.orientToSpine,
|
|
868
1227
|
title: "Orient steps to the spine",
|
|
869
1228
|
"aria-label": "Orient to spine",
|
|
@@ -871,12 +1230,12 @@ function VectorBlendControls({ controller }) {
|
|
|
871
1230
|
children: "\u293F"
|
|
872
1231
|
}
|
|
873
1232
|
),
|
|
874
|
-
/* @__PURE__ */
|
|
1233
|
+
/* @__PURE__ */ jsx6(
|
|
875
1234
|
"button",
|
|
876
1235
|
{
|
|
877
1236
|
type: "button",
|
|
878
1237
|
"data-vector-blend-action": "spine",
|
|
879
|
-
className:
|
|
1238
|
+
className: buttonClass4,
|
|
880
1239
|
title: "Replace Spine (select a blend and a path)",
|
|
881
1240
|
"aria-label": "Replace Spine",
|
|
882
1241
|
disabled: !blend.canReplaceSpine,
|
|
@@ -884,12 +1243,12 @@ function VectorBlendControls({ controller }) {
|
|
|
884
1243
|
children: "\u2307"
|
|
885
1244
|
}
|
|
886
1245
|
),
|
|
887
|
-
/* @__PURE__ */
|
|
888
|
-
/* @__PURE__ */
|
|
1246
|
+
/* @__PURE__ */ jsx6("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass4, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
|
|
1247
|
+
/* @__PURE__ */ jsx6("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass4, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
|
|
889
1248
|
] });
|
|
890
1249
|
}
|
|
891
|
-
var
|
|
892
|
-
var
|
|
1250
|
+
var selectClass4 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
|
|
1251
|
+
var numberClass4 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
|
|
893
1252
|
function VectorStylePanel({ controller, className }) {
|
|
894
1253
|
const state = useVectorEditorState(controller);
|
|
895
1254
|
const style = getVectorSelectionStyle(state);
|
|
@@ -920,17 +1279,17 @@ function VectorStylePanel({ controller, className }) {
|
|
|
920
1279
|
if (style) controller.setWidthProfile(id);
|
|
921
1280
|
else controller.setDefaults({ strokeWidthProfile: widthProfilePreset(id)?.length ? widthProfilePreset(id) : null });
|
|
922
1281
|
};
|
|
923
|
-
const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */
|
|
1282
|
+
const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx6("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
|
|
924
1283
|
const arrowRow = (end, label) => {
|
|
925
1284
|
const key = end === "start" ? "startArrow" : "endArrow";
|
|
926
1285
|
const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
|
|
927
|
-
return /* @__PURE__ */
|
|
928
|
-
/* @__PURE__ */
|
|
929
|
-
/* @__PURE__ */
|
|
930
|
-
/* @__PURE__ */
|
|
931
|
-
VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */
|
|
1286
|
+
return /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
|
|
1287
|
+
/* @__PURE__ */ jsx6("span", { className: "w-16 text-xs text-foreground", children: label }),
|
|
1288
|
+
/* @__PURE__ */ jsxs6("select", { "aria-label": `${label} arrowhead`, className: selectClass4, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
|
|
1289
|
+
/* @__PURE__ */ jsx6("option", { value: "", children: "None" }),
|
|
1290
|
+
VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx6("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
|
|
932
1291
|
] }),
|
|
933
|
-
/* @__PURE__ */
|
|
1292
|
+
/* @__PURE__ */ jsx6(
|
|
934
1293
|
"input",
|
|
935
1294
|
{
|
|
936
1295
|
type: "number",
|
|
@@ -938,7 +1297,7 @@ function VectorStylePanel({ controller, className }) {
|
|
|
938
1297
|
step: 0.25,
|
|
939
1298
|
"aria-label": `${label} arrowhead scale`,
|
|
940
1299
|
title: "Scale (\xD7 stroke width)",
|
|
941
|
-
className:
|
|
1300
|
+
className: numberClass4,
|
|
942
1301
|
value: source[scaleKey],
|
|
943
1302
|
onChange: (e) => {
|
|
944
1303
|
const n = parseFloat(e.target.value);
|
|
@@ -954,14 +1313,14 @@ function VectorStylePanel({ controller, className }) {
|
|
|
954
1313
|
};
|
|
955
1314
|
const paintRow = (key, label) => {
|
|
956
1315
|
const paint = source[key];
|
|
957
|
-
const hex =
|
|
1316
|
+
const hex = vectorPaintToHex2(paint);
|
|
958
1317
|
const on = paint !== null && paint !== "none";
|
|
959
|
-
return /* @__PURE__ */
|
|
960
|
-
/* @__PURE__ */
|
|
961
|
-
/* @__PURE__ */
|
|
1318
|
+
return /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
|
|
1319
|
+
/* @__PURE__ */ jsxs6("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
|
|
1320
|
+
/* @__PURE__ */ jsx6("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
|
|
962
1321
|
label
|
|
963
1322
|
] }),
|
|
964
|
-
/* @__PURE__ */
|
|
1323
|
+
/* @__PURE__ */ jsx6(
|
|
965
1324
|
"input",
|
|
966
1325
|
{
|
|
967
1326
|
type: "color",
|
|
@@ -972,17 +1331,18 @@ function VectorStylePanel({ controller, className }) {
|
|
|
972
1331
|
onChange: (e) => apply({ [key]: e.target.value })
|
|
973
1332
|
}
|
|
974
1333
|
),
|
|
975
|
-
on && !hex && /* @__PURE__ */
|
|
976
|
-
style?.mixed[key] && /* @__PURE__ */
|
|
1334
|
+
on && !hex && /* @__PURE__ */ jsx6("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
|
|
1335
|
+
style?.mixed[key] && /* @__PURE__ */ jsx6("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
|
|
977
1336
|
] });
|
|
978
1337
|
};
|
|
979
|
-
return /* @__PURE__ */
|
|
980
|
-
/* @__PURE__ */
|
|
1338
|
+
return /* @__PURE__ */ jsxs6("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
|
|
1339
|
+
/* @__PURE__ */ jsx6("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
|
|
981
1340
|
paintRow("fill", "Fill"),
|
|
982
1341
|
paintRow("stroke", "Stroke"),
|
|
983
|
-
/* @__PURE__ */
|
|
984
|
-
|
|
985
|
-
/* @__PURE__ */
|
|
1342
|
+
style && /* @__PURE__ */ jsx6(VectorGradientEditor, { controller }),
|
|
1343
|
+
/* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
|
|
1344
|
+
/* @__PURE__ */ jsx6("span", { className: "w-16", children: "Width" }),
|
|
1345
|
+
/* @__PURE__ */ jsx6(
|
|
986
1346
|
"input",
|
|
987
1347
|
{
|
|
988
1348
|
type: "number",
|
|
@@ -998,33 +1358,33 @@ function VectorStylePanel({ controller, className }) {
|
|
|
998
1358
|
}
|
|
999
1359
|
)
|
|
1000
1360
|
] }),
|
|
1001
|
-
/* @__PURE__ */
|
|
1002
|
-
/* @__PURE__ */
|
|
1003
|
-
/* @__PURE__ */
|
|
1361
|
+
/* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
|
|
1362
|
+
/* @__PURE__ */ jsx6("span", { className: "w-16", children: "Align" }),
|
|
1363
|
+
/* @__PURE__ */ jsx6("select", { "aria-label": "Stroke alignment", className: selectClass4, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(VECTOR_STROKE_ALIGN_LABELS).map((a) => /* @__PURE__ */ jsx6("option", { value: a, children: VECTOR_STROKE_ALIGN_LABELS[a] }, a)) }),
|
|
1004
1364
|
mixed("strokeAlign")
|
|
1005
1365
|
] }),
|
|
1006
|
-
/* @__PURE__ */
|
|
1007
|
-
/* @__PURE__ */
|
|
1008
|
-
/* @__PURE__ */
|
|
1366
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
|
|
1367
|
+
/* @__PURE__ */ jsx6("span", { className: "w-16", children: "Profile" }),
|
|
1368
|
+
/* @__PURE__ */ jsxs6(
|
|
1009
1369
|
"select",
|
|
1010
1370
|
{
|
|
1011
1371
|
"aria-label": "Width profile",
|
|
1012
|
-
className:
|
|
1372
|
+
className: selectClass4,
|
|
1013
1373
|
value: source.widthProfile,
|
|
1014
1374
|
onChange: (e) => {
|
|
1015
1375
|
if (e.target.value !== "custom") setProfile(e.target.value);
|
|
1016
1376
|
},
|
|
1017
1377
|
children: [
|
|
1018
|
-
VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */
|
|
1019
|
-
source.widthProfile === "custom" && /* @__PURE__ */
|
|
1378
|
+
VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx6("option", { value: p.id, children: p.label }, p.id)),
|
|
1379
|
+
source.widthProfile === "custom" && /* @__PURE__ */ jsx6("option", { value: "custom", disabled: true, children: "Custom" })
|
|
1020
1380
|
]
|
|
1021
1381
|
}
|
|
1022
1382
|
),
|
|
1023
|
-
/* @__PURE__ */
|
|
1383
|
+
/* @__PURE__ */ jsx6(
|
|
1024
1384
|
"button",
|
|
1025
1385
|
{
|
|
1026
1386
|
type: "button",
|
|
1027
|
-
className:
|
|
1387
|
+
className: buttonClass4,
|
|
1028
1388
|
title: "Reset to uniform width",
|
|
1029
1389
|
"aria-label": "Reset to uniform width",
|
|
1030
1390
|
disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
|
|
@@ -1034,27 +1394,27 @@ function VectorStylePanel({ controller, className }) {
|
|
|
1034
1394
|
),
|
|
1035
1395
|
mixed("widthProfile")
|
|
1036
1396
|
] }),
|
|
1037
|
-
source.hasOpenPath && /* @__PURE__ */
|
|
1397
|
+
source.hasOpenPath && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
1038
1398
|
arrowRow("start", "Start"),
|
|
1039
1399
|
arrowRow("end", "End"),
|
|
1040
|
-
(source.startArrow || source.endArrow) && /* @__PURE__ */
|
|
1041
|
-
/* @__PURE__ */
|
|
1042
|
-
/* @__PURE__ */
|
|
1043
|
-
/* @__PURE__ */
|
|
1044
|
-
/* @__PURE__ */
|
|
1400
|
+
(source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
|
|
1401
|
+
/* @__PURE__ */ jsx6("span", { className: "w-16", children: "Heads" }),
|
|
1402
|
+
/* @__PURE__ */ jsxs6("select", { "aria-label": "Arrowhead alignment", className: selectClass4, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
|
|
1403
|
+
/* @__PURE__ */ jsx6("option", { value: "extend", children: "Tip past end" }),
|
|
1404
|
+
/* @__PURE__ */ jsx6("option", { value: "tip", children: "Tip at end" })
|
|
1045
1405
|
] })
|
|
1046
1406
|
] })
|
|
1047
1407
|
] }),
|
|
1048
|
-
source.corners > 0 && /* @__PURE__ */
|
|
1049
|
-
/* @__PURE__ */
|
|
1050
|
-
/* @__PURE__ */
|
|
1408
|
+
source.corners > 0 && /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
|
|
1409
|
+
/* @__PURE__ */ jsx6("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
|
|
1410
|
+
/* @__PURE__ */ jsx6(
|
|
1051
1411
|
"input",
|
|
1052
1412
|
{
|
|
1053
1413
|
type: "number",
|
|
1054
1414
|
min: 0,
|
|
1055
1415
|
step: 1,
|
|
1056
1416
|
"aria-label": "Corner radius",
|
|
1057
|
-
className:
|
|
1417
|
+
className: numberClass4,
|
|
1058
1418
|
value: Math.round(source.cornerRadius * 100) / 100,
|
|
1059
1419
|
onChange: (e) => {
|
|
1060
1420
|
const n = parseFloat(e.target.value);
|
|
@@ -1062,12 +1422,12 @@ function VectorStylePanel({ controller, className }) {
|
|
|
1062
1422
|
}
|
|
1063
1423
|
}
|
|
1064
1424
|
),
|
|
1065
|
-
/* @__PURE__ */
|
|
1425
|
+
/* @__PURE__ */ jsx6("select", { "aria-label": "Corner kind", className: selectClass4, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ jsx6("option", { value: k, children: VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
|
|
1066
1426
|
mixed("cornerRadius")
|
|
1067
1427
|
] }),
|
|
1068
|
-
style && /* @__PURE__ */
|
|
1069
|
-
/* @__PURE__ */
|
|
1070
|
-
/* @__PURE__ */
|
|
1428
|
+
style && /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
|
|
1429
|
+
/* @__PURE__ */ jsx6("span", { className: "w-16", children: "Opacity" }),
|
|
1430
|
+
/* @__PURE__ */ jsx6(
|
|
1071
1431
|
"input",
|
|
1072
1432
|
{
|
|
1073
1433
|
type: "range",
|
|
@@ -1081,18 +1441,19 @@ function VectorStylePanel({ controller, className }) {
|
|
|
1081
1441
|
}
|
|
1082
1442
|
)
|
|
1083
1443
|
] }),
|
|
1084
|
-
style && /* @__PURE__ */
|
|
1444
|
+
style && /* @__PURE__ */ jsx6(VectorEffectsPanel, { controller }),
|
|
1445
|
+
style && /* @__PURE__ */ jsx6(VectorEnvelopePanel, { controller })
|
|
1085
1446
|
] });
|
|
1086
1447
|
}
|
|
1087
1448
|
|
|
1088
1449
|
// src/vector/VectorEditor.tsx
|
|
1089
|
-
import { jsx as
|
|
1450
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1090
1451
|
function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }) {
|
|
1091
|
-
return /* @__PURE__ */
|
|
1092
|
-
/* @__PURE__ */
|
|
1093
|
-
/* @__PURE__ */
|
|
1094
|
-
/* @__PURE__ */
|
|
1095
|
-
showStylePanel && /* @__PURE__ */
|
|
1452
|
+
return /* @__PURE__ */ jsxs7("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
|
|
1453
|
+
/* @__PURE__ */ jsx7(VectorToolbar, { controller, tools }),
|
|
1454
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
|
|
1455
|
+
/* @__PURE__ */ jsx7("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx7(VectorCanvas, { controller, ...canvasProps }) }),
|
|
1456
|
+
showStylePanel && /* @__PURE__ */ jsx7(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
|
|
1096
1457
|
] })
|
|
1097
1458
|
] });
|
|
1098
1459
|
}
|
|
@@ -1100,7 +1461,10 @@ export {
|
|
|
1100
1461
|
VectorCanvas,
|
|
1101
1462
|
VectorEditor,
|
|
1102
1463
|
VectorEffectsPanel,
|
|
1464
|
+
VectorEnvelopePanel,
|
|
1465
|
+
VectorGradientAnnotator,
|
|
1103
1466
|
VectorGradientDefs,
|
|
1467
|
+
VectorGradientEditor,
|
|
1104
1468
|
VectorSceneNodes,
|
|
1105
1469
|
VectorStylePanel,
|
|
1106
1470
|
VectorToolbar,
|