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