@pixerate/editor-react 0.22.0 → 0.23.1
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/README.md +1 -0
- package/dist/vector/index.cjs +411 -0
- package/dist/vector/index.cjs.map +1 -0
- package/dist/vector/index.d.cts +95 -0
- package/dist/vector/index.d.ts +95 -0
- package/dist/vector/index.js +392 -0
- package/dist/vector/index.js.map +1 -0
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -100,6 +100,7 @@ useAgentToolProvider(registry, provider);
|
|
|
100
100
|
| `@pixerate/editor-react/commands` | `CommandPalette`, `useCommandPalette` (`hotkey: "Mod+K"`), `useCommandShortcuts`, `useRegisterCommands` over a `CommandRegistry` from `@pixerate/editor/commands` (also re-exported from the root entry) |
|
|
101
101
|
| `@pixerate/editor-react/persistence` | `useAutosave` (crash-recovery snapshots bound to a history / dirty flag, flushed on `pagehide`, with a recovery check on mount) and `<RecoveryPrompt>` (also re-exported from the root entry) |
|
|
102
102
|
| `@pixerate/editor-react/generation` | `GenerationSettings` (controls generated from a model's capabilities, reference slots by role, plan adjustments), `useGenerationPlan`, `JobProgress`, `useJobProgress` over `@pixerate/editor/generation` (also re-exported from the root entry) |
|
|
103
|
+
| `@pixerate/editor-react/vector` | `VectorEditor` (toolbar, canvas and style panel), `VectorCanvas` (SVG canvas forwarding pointer, wheel, keyboard and clipboard events), `VectorToolbar`, `VectorStylePanel`, `VectorSceneNodes`, `VectorGradientDefs`, `useVectorEditor`, `useVectorEditorController`, `useVectorEditorState` and `useVectorCommands` over `@pixerate/editor/vector` (see [the vector editor guide](../../docs/vector-editor.md)) |
|
|
103
104
|
| `@pixerate/editor-react/parameters` | `ParametersPanel` (a whole schema with groups, presets, undo / reset / randomize and keyframe diamonds), `useParameters`, `useParametersController`, `useParametersState`, `useParameterCommands`, and the field controls `ParameterFieldControl`, `ParameterSlider`, `ParameterToggle`, `ParameterSelect`, `ParameterColor`, `ParameterVector`, `ParameterImage`, `ParameterText`, `KeyframeDiamond` over `@pixerate/editor/parameters` (see [the parameters guide](../../docs/parameters.md)) |
|
|
104
105
|
| `@pixerate/editor-react/canvas` | `useCanvasGraph`, `useCanvasLayout`, `useCanvasClipboard`, `useCanvasDocking`, `useCanvasShortcuts`, `useCanvasExplosion`, `useStreamingCanvas`, `AgentPresenceLayer`, plus the pure layout helpers from `@pixerate/editor/canvas` |
|
|
105
106
|
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/vector/index.ts
|
|
21
|
+
var vector_exports = {};
|
|
22
|
+
__export(vector_exports, {
|
|
23
|
+
VectorCanvas: () => VectorCanvas,
|
|
24
|
+
VectorEditor: () => VectorEditor,
|
|
25
|
+
VectorGradientDefs: () => VectorGradientDefs,
|
|
26
|
+
VectorSceneNodes: () => VectorSceneNodes,
|
|
27
|
+
VectorStylePanel: () => VectorStylePanel,
|
|
28
|
+
VectorToolbar: () => VectorToolbar,
|
|
29
|
+
useVectorCommands: () => useVectorCommands,
|
|
30
|
+
useVectorEditor: () => useVectorEditor,
|
|
31
|
+
useVectorEditorController: () => useVectorEditorController,
|
|
32
|
+
useVectorEditorState: () => useVectorEditorState
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(vector_exports);
|
|
35
|
+
|
|
36
|
+
// src/vector/useVectorEditor.ts
|
|
37
|
+
var import_react = require("react");
|
|
38
|
+
var import_vector = require("@pixerate/editor/vector");
|
|
39
|
+
function useVectorEditorController(options = {}) {
|
|
40
|
+
const ref = (0, import_react.useRef)(null);
|
|
41
|
+
if (!ref.current) ref.current = new import_vector.VectorEditorController(options);
|
|
42
|
+
const controller = ref.current;
|
|
43
|
+
(0, import_react.useEffect)(() => () => controller.destroy(), [controller]);
|
|
44
|
+
return controller;
|
|
45
|
+
}
|
|
46
|
+
function useVectorEditorState(controller) {
|
|
47
|
+
return (0, import_react.useSyncExternalStore)(controller.subscribe, controller.getState, controller.getState);
|
|
48
|
+
}
|
|
49
|
+
function useVectorEditor(controllerOrOptions = {}) {
|
|
50
|
+
const own = (0, import_react.useRef)(null);
|
|
51
|
+
let controller;
|
|
52
|
+
if (controllerOrOptions instanceof import_vector.VectorEditorController) controller = controllerOrOptions;
|
|
53
|
+
else {
|
|
54
|
+
if (!own.current) own.current = new import_vector.VectorEditorController(controllerOrOptions);
|
|
55
|
+
controller = own.current;
|
|
56
|
+
}
|
|
57
|
+
const state = useVectorEditorState(controller);
|
|
58
|
+
const actions = (0, import_react.useMemo)(
|
|
59
|
+
() => ({
|
|
60
|
+
setTool: (tool) => controller.setTool(tool),
|
|
61
|
+
undo: () => void controller.undo(),
|
|
62
|
+
redo: () => void controller.redo(),
|
|
63
|
+
deleteSelection: () => void controller.deleteSelection(),
|
|
64
|
+
toSvg: () => controller.toSvg()
|
|
65
|
+
}),
|
|
66
|
+
[controller]
|
|
67
|
+
);
|
|
68
|
+
return { controller, state, ...actions };
|
|
69
|
+
}
|
|
70
|
+
function useVectorCommands(registry, controller, options = {}) {
|
|
71
|
+
const { prefix, group, scope } = options;
|
|
72
|
+
(0, import_react.useEffect)(() => {
|
|
73
|
+
if (!registry) return;
|
|
74
|
+
return registry.register((0, import_vector.createVectorCommands)(controller, { prefix, group, scope }));
|
|
75
|
+
}, [registry, controller, prefix, group, scope]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/vector/VectorCanvas.tsx
|
|
79
|
+
var import_react2 = require("react");
|
|
80
|
+
var import_vector2 = require("@pixerate/editor/vector");
|
|
81
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
82
|
+
function VectorGradientDefs({ defs }) {
|
|
83
|
+
if (!defs.length) return null;
|
|
84
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("defs", { children: defs.map((def) => {
|
|
85
|
+
const stops = def.stops.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", { offset: s.offset, stopColor: s.stopColor, stopOpacity: s.stopOpacity }, i));
|
|
86
|
+
return def.type === "radial" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("radialGradient", { id: def.id, ...def.attrs, children: stops }, def.id) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("linearGradient", { id: def.id, ...def.attrs, children: stops }, def.id);
|
|
87
|
+
}) });
|
|
88
|
+
}
|
|
89
|
+
function VectorSceneNodes({ nodes }) {
|
|
90
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: nodes.map(
|
|
91
|
+
(node) => node.kind === "group" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("g", { "data-vector-id": node.id, transform: node.transform, opacity: node.opacity < 1 ? node.opacity : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VectorSceneNodes, { nodes: node.children }) }, node.id) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
92
|
+
"path",
|
|
93
|
+
{
|
|
94
|
+
"data-vector-id": node.id,
|
|
95
|
+
transform: node.transform,
|
|
96
|
+
d: node.d,
|
|
97
|
+
fill: node.fill,
|
|
98
|
+
fillRule: node.fillRule,
|
|
99
|
+
stroke: node.stroke,
|
|
100
|
+
strokeWidth: node.stroke === "none" ? void 0 : node.strokeWidth,
|
|
101
|
+
strokeLinecap: node.strokeLinecap,
|
|
102
|
+
strokeLinejoin: node.strokeLinejoin,
|
|
103
|
+
strokeDasharray: node.strokeDasharray,
|
|
104
|
+
opacity: node.opacity < 1 ? node.opacity : void 0
|
|
105
|
+
},
|
|
106
|
+
node.id
|
|
107
|
+
)
|
|
108
|
+
) });
|
|
109
|
+
}
|
|
110
|
+
function VectorCanvas({
|
|
111
|
+
controller,
|
|
112
|
+
fitOnMount = true,
|
|
113
|
+
checkerboard = true,
|
|
114
|
+
accentColor = import_vector2.VECTOR_OVERLAY_THEME.accent,
|
|
115
|
+
className,
|
|
116
|
+
style,
|
|
117
|
+
"aria-label": ariaLabel = "Vector canvas"
|
|
118
|
+
}) {
|
|
119
|
+
const state = useVectorEditorState(controller);
|
|
120
|
+
const svgRef = (0, import_react2.useRef)(null);
|
|
121
|
+
const fitted = (0, import_react2.useRef)(!fitOnMount);
|
|
122
|
+
const uid = (0, import_react2.useId)().replace(/:/g, "");
|
|
123
|
+
const scene = (0, import_vector2.getVectorSceneRenderData)(state, { idPrefix: `ve${uid}-` });
|
|
124
|
+
const overlay = (0, import_vector2.getVectorOverlayRenderData)(state);
|
|
125
|
+
(0, import_react2.useEffect)(() => {
|
|
126
|
+
const el = svgRef.current;
|
|
127
|
+
if (!el || fitted.current) return;
|
|
128
|
+
const fit = () => {
|
|
129
|
+
const rect = el.getBoundingClientRect();
|
|
130
|
+
if (fitted.current || rect.width <= 0 || rect.height <= 0) return;
|
|
131
|
+
fitted.current = true;
|
|
132
|
+
controller.zoomToFit({ width: rect.width, height: rect.height });
|
|
133
|
+
};
|
|
134
|
+
fit();
|
|
135
|
+
if (fitted.current || typeof ResizeObserver === "undefined") return;
|
|
136
|
+
const observer = new ResizeObserver(fit);
|
|
137
|
+
observer.observe(el);
|
|
138
|
+
return () => observer.disconnect();
|
|
139
|
+
}, [controller]);
|
|
140
|
+
(0, import_react2.useEffect)(() => {
|
|
141
|
+
const el = svgRef.current;
|
|
142
|
+
if (!el) return;
|
|
143
|
+
const onWheel = (e) => {
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
controller.wheel(local(e), { x: e.deltaX, y: e.deltaY }, (0, import_vector2.toVectorPointerInput)(e));
|
|
146
|
+
};
|
|
147
|
+
el.addEventListener("wheel", onWheel, { passive: false });
|
|
148
|
+
return () => el.removeEventListener("wheel", onWheel);
|
|
149
|
+
}, [controller]);
|
|
150
|
+
const local = (e) => {
|
|
151
|
+
const rect = svgRef.current?.getBoundingClientRect();
|
|
152
|
+
return { x: e.clientX - (rect?.left ?? 0), y: e.clientY - (rect?.top ?? 0) };
|
|
153
|
+
};
|
|
154
|
+
const clipboard = (kind) => (event) => {
|
|
155
|
+
const native = event && "nativeEvent" in event ? event.nativeEvent : event;
|
|
156
|
+
const data = native?.clipboardData;
|
|
157
|
+
if (kind === "paste") {
|
|
158
|
+
const text2 = data?.getData("text/plain") ?? "";
|
|
159
|
+
const pasted = text2 ? controller.paste(text2) : controller.paste();
|
|
160
|
+
if (pasted.length) event.preventDefault?.();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const text = kind === "cut" ? controller.cut() : controller.copy();
|
|
164
|
+
if (text && data) {
|
|
165
|
+
data.setData("text/plain", text);
|
|
166
|
+
event.preventDefault?.();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
const hs = import_vector2.VECTOR_OVERLAY_THEME.transformHandleSize;
|
|
170
|
+
const as = import_vector2.VECTOR_OVERLAY_THEME.anchorSize;
|
|
171
|
+
const box = overlay.selectionBox;
|
|
172
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
173
|
+
"svg",
|
|
174
|
+
{
|
|
175
|
+
ref: svgRef,
|
|
176
|
+
role: "application",
|
|
177
|
+
"aria-label": ariaLabel,
|
|
178
|
+
tabIndex: 0,
|
|
179
|
+
"data-tool": state.tool,
|
|
180
|
+
className,
|
|
181
|
+
style: { display: "block", width: "100%", height: "100%", outline: "none", touchAction: "none", userSelect: "none", cursor: state.cursor, ...style },
|
|
182
|
+
onPointerDown: (e) => {
|
|
183
|
+
svgRef.current?.focus({ preventScroll: true });
|
|
184
|
+
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
185
|
+
controller.pointerDown(local(e), (0, import_vector2.toVectorPointerInput)(e));
|
|
186
|
+
},
|
|
187
|
+
onPointerMove: (e) => controller.pointerMove(local(e), (0, import_vector2.toVectorPointerInput)(e)),
|
|
188
|
+
onPointerUp: (e) => {
|
|
189
|
+
e.currentTarget.releasePointerCapture?.(e.pointerId);
|
|
190
|
+
controller.pointerUp(local(e), (0, import_vector2.toVectorPointerInput)(e));
|
|
191
|
+
},
|
|
192
|
+
onPointerCancel: () => controller.cancelDrag(),
|
|
193
|
+
onPointerLeave: () => controller.pointerLeave(),
|
|
194
|
+
onDoubleClick: (e) => controller.doubleClick(local(e)),
|
|
195
|
+
onKeyDown: (e) => {
|
|
196
|
+
if ((0, import_vector2.isVectorTextTarget)(e.target)) return;
|
|
197
|
+
if (controller.keyDown((0, import_vector2.toVectorKeyInput)(e))) e.preventDefault();
|
|
198
|
+
},
|
|
199
|
+
onKeyUp: (e) => controller.keyUp((0, import_vector2.toVectorKeyInput)(e)),
|
|
200
|
+
onCopy: clipboard("copy"),
|
|
201
|
+
onCut: clipboard("cut"),
|
|
202
|
+
onPaste: clipboard("paste"),
|
|
203
|
+
children: [
|
|
204
|
+
checkerboard && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("pattern", { id: `ve${uid}-checker`, width: 16, height: 16, patternUnits: "userSpaceOnUse", children: [
|
|
205
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { width: 16, height: 16, fill: "#ffffff" }),
|
|
206
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { width: 8, height: 8, fill: "#e5e7eb" }),
|
|
207
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: 8, y: 8, width: 8, height: 8, fill: "#e5e7eb" })
|
|
208
|
+
] }) }),
|
|
209
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(VectorGradientDefs, { defs: scene.defs }),
|
|
210
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
211
|
+
"rect",
|
|
212
|
+
{
|
|
213
|
+
"data-vector-artboard": "",
|
|
214
|
+
x: overlay.artboard.x,
|
|
215
|
+
y: overlay.artboard.y,
|
|
216
|
+
width: overlay.artboard.width,
|
|
217
|
+
height: overlay.artboard.height,
|
|
218
|
+
fill: scene.background ?? (checkerboard ? `url(#ve${uid}-checker)` : "#ffffff"),
|
|
219
|
+
stroke: "rgba(0,0,0,0.12)"
|
|
220
|
+
}
|
|
221
|
+
),
|
|
222
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("g", { "data-vector-scene": "", transform: scene.transform, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VectorSceneNodes, { nodes: scene.nodes }) }),
|
|
223
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { "data-vector-overlay": "", pointerEvents: "none", fill: "none", children: [
|
|
224
|
+
overlay.outlines.map((o) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: o.d, stroke: accentColor, strokeWidth: o.selected ? 1 : 1.5, opacity: o.selected ? 1 : 0.7 }, o.id)),
|
|
225
|
+
overlay.penPreview && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: overlay.penPreview, stroke: accentColor, strokeDasharray: "4 3" }),
|
|
226
|
+
box && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { "data-vector-selection": "", transform: box.transform || void 0, children: [
|
|
227
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: box.x, y: box.y, width: box.width, height: box.height, stroke: accentColor }),
|
|
228
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: box.x + box.width / 2, y1: box.y, x2: box.x + box.width / 2, y2: box.y - 24, stroke: accentColor }),
|
|
229
|
+
box.handles.map(
|
|
230
|
+
(h) => h.handle === "rotate" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { "data-handle": h.handle, cx: h.x, cy: h.y, r: hs / 2 + 1, fill: import_vector2.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }, h.handle) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { "data-handle": h.handle, x: h.x - hs / 2, y: h.y - hs / 2, width: hs, height: hs, fill: import_vector2.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }, h.handle)
|
|
231
|
+
)
|
|
232
|
+
] }),
|
|
233
|
+
overlay.handles.map((h) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { "data-vector-handle": h.ref.handle, children: [
|
|
234
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: h.anchorX, y1: h.anchorY, x2: h.x, y2: h.y, stroke: accentColor }),
|
|
235
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: h.x, cy: h.y, r: import_vector2.VECTOR_OVERLAY_THEME.handleRadius + (h.hovered ? 1 : 0), fill: import_vector2.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor })
|
|
236
|
+
] }, `${h.ref.nodeId}:${h.ref.subpath}:${h.ref.index}:${h.ref.handle}`)),
|
|
237
|
+
overlay.anchors.map((a) => {
|
|
238
|
+
const size = as + (a.hovered ? 2 : 0);
|
|
239
|
+
const fill = a.selected ? accentColor : import_vector2.VECTOR_OVERLAY_THEME.handleFill;
|
|
240
|
+
const key = `${a.ref.nodeId}:${a.ref.subpath}:${a.ref.index}`;
|
|
241
|
+
return a.type === "corner" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { "data-vector-anchor": a.ref.index, x: a.x - size / 2, y: a.y - size / 2, width: size, height: size, fill, stroke: accentColor }, key) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { "data-vector-anchor": a.ref.index, cx: a.x, cy: a.y, r: size / 2, fill, stroke: accentColor }, key);
|
|
242
|
+
}),
|
|
243
|
+
state.tool === "pen" && overlay.anchors.some((a) => a.closes && a.hovered) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: overlay.anchors.find((a) => a.closes).x, cy: overlay.anchors.find((a) => a.closes).y, r: as, stroke: accentColor }),
|
|
244
|
+
overlay.marquee && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
245
|
+
"rect",
|
|
246
|
+
{
|
|
247
|
+
"data-vector-marquee": "",
|
|
248
|
+
x: overlay.marquee.x,
|
|
249
|
+
y: overlay.marquee.y,
|
|
250
|
+
width: overlay.marquee.width,
|
|
251
|
+
height: overlay.marquee.height,
|
|
252
|
+
fill: import_vector2.VECTOR_OVERLAY_THEME.marqueeFill,
|
|
253
|
+
stroke: accentColor,
|
|
254
|
+
strokeDasharray: "3 2"
|
|
255
|
+
}
|
|
256
|
+
),
|
|
257
|
+
overlay.status && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { "data-vector-status": "", x: 8, y: "100%", dy: -8, fontSize: 11, fill: accentColor, style: { fontFamily: "system-ui, sans-serif" }, children: overlay.status })
|
|
258
|
+
] })
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/vector/VectorToolbar.tsx
|
|
265
|
+
var import_vector3 = require("@pixerate/editor/vector");
|
|
266
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
267
|
+
var buttonClass = "inline-flex h-8 min-w-8 items-center justify-center rounded-md border border-border bg-background px-2 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
|
|
268
|
+
function VectorToolbar({ controller, tools = import_vector3.VECTOR_TOOLS, showHistory = true, showArrange = true, className }) {
|
|
269
|
+
const state = useVectorEditorState(controller);
|
|
270
|
+
const count = state.selection.length;
|
|
271
|
+
const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
|
|
272
|
+
const act = (label, title, run, disabled = false) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: buttonClass, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
|
|
273
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
|
|
274
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
275
|
+
"button",
|
|
276
|
+
{
|
|
277
|
+
type: "button",
|
|
278
|
+
"data-vector-tool": tool,
|
|
279
|
+
className: buttonClass,
|
|
280
|
+
"aria-pressed": state.tool === tool,
|
|
281
|
+
title: `${(0, import_vector3.vectorToolLabel)(tool)} (${import_vector3.VECTOR_TOOL_SHORTCUTS[tool]})`,
|
|
282
|
+
"aria-label": (0, import_vector3.vectorToolLabel)(tool),
|
|
283
|
+
onClick: () => controller.setTool(tool),
|
|
284
|
+
children: import_vector3.VECTOR_TOOL_GLYPHS[tool]
|
|
285
|
+
},
|
|
286
|
+
tool
|
|
287
|
+
)) }),
|
|
288
|
+
showHistory && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
|
|
289
|
+
act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
|
|
290
|
+
act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
|
|
291
|
+
] }),
|
|
292
|
+
showArrange && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
293
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
|
|
294
|
+
act("\u222A", "Union", () => controller.booleanOp("union"), count < 2),
|
|
295
|
+
act("\u2212", "Subtract", () => controller.booleanOp("subtract"), count < 2),
|
|
296
|
+
act("\u2229", "Intersect", () => controller.booleanOp("intersect"), count < 2),
|
|
297
|
+
act("\u22BB", "Exclude", () => controller.booleanOp("exclude"), count < 2),
|
|
298
|
+
act("G", "Group", () => controller.group(), count < 1),
|
|
299
|
+
act("U", "Ungroup", () => controller.ungroup(), !groupSelected)
|
|
300
|
+
] }),
|
|
301
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
|
|
302
|
+
act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
|
|
303
|
+
act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
|
|
304
|
+
act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
|
|
305
|
+
act("\u2194", "Align Center", () => controller.align("center"), count < 1),
|
|
306
|
+
act("\u21E5", "Align Right", () => controller.align("right"), count < 1),
|
|
307
|
+
act("\u2912", "Align Top", () => controller.align("top"), count < 1),
|
|
308
|
+
act("\u2195", "Align Middle", () => controller.align("middle"), count < 1),
|
|
309
|
+
act("\u2913", "Align Bottom", () => controller.align("bottom"), count < 1)
|
|
310
|
+
] })
|
|
311
|
+
] })
|
|
312
|
+
] });
|
|
313
|
+
}
|
|
314
|
+
function VectorStylePanel({ controller, className }) {
|
|
315
|
+
const state = useVectorEditorState(controller);
|
|
316
|
+
const style = (0, import_vector3.getVectorSelectionStyle)(state);
|
|
317
|
+
const source = style ?? { fill: state.defaults.fill ?? null, stroke: state.defaults.stroke ?? null, strokeWidth: state.defaults.strokeWidth ?? 1, opacity: 1 };
|
|
318
|
+
const apply = (patch) => {
|
|
319
|
+
if (style) controller.setStyle(patch);
|
|
320
|
+
else controller.setDefaults(patch);
|
|
321
|
+
};
|
|
322
|
+
const paintRow = (key, label) => {
|
|
323
|
+
const paint = source[key];
|
|
324
|
+
const hex = (0, import_vector3.vectorPaintToHex)(paint);
|
|
325
|
+
const on = paint !== null && paint !== "none";
|
|
326
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
|
|
327
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
|
|
328
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
|
|
329
|
+
label
|
|
330
|
+
] }),
|
|
331
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
332
|
+
"input",
|
|
333
|
+
{
|
|
334
|
+
type: "color",
|
|
335
|
+
"aria-label": `${label} colour`,
|
|
336
|
+
className: "h-7 w-10 cursor-pointer rounded border border-border bg-background disabled:opacity-40",
|
|
337
|
+
disabled: !on,
|
|
338
|
+
value: hex ?? "#000000",
|
|
339
|
+
onChange: (e) => apply({ [key]: e.target.value })
|
|
340
|
+
}
|
|
341
|
+
),
|
|
342
|
+
on && !hex && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
|
|
343
|
+
style?.mixed[key] && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
|
|
344
|
+
] });
|
|
345
|
+
};
|
|
346
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
|
|
347
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
|
|
348
|
+
paintRow("fill", "Fill"),
|
|
349
|
+
paintRow("stroke", "Stroke"),
|
|
350
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
|
|
351
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-16", children: "Width" }),
|
|
352
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
353
|
+
"input",
|
|
354
|
+
{
|
|
355
|
+
type: "number",
|
|
356
|
+
min: 0,
|
|
357
|
+
step: 0.5,
|
|
358
|
+
"aria-label": "Stroke width",
|
|
359
|
+
className: "h-7 w-20 rounded border border-border bg-background px-1.5 text-xs",
|
|
360
|
+
value: source.strokeWidth,
|
|
361
|
+
onChange: (e) => {
|
|
362
|
+
const n = parseFloat(e.target.value);
|
|
363
|
+
if (Number.isFinite(n) && n >= 0) apply({ strokeWidth: n });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
)
|
|
367
|
+
] }),
|
|
368
|
+
style && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
|
|
369
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-16", children: "Opacity" }),
|
|
370
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
371
|
+
"input",
|
|
372
|
+
{
|
|
373
|
+
type: "range",
|
|
374
|
+
min: 0,
|
|
375
|
+
max: 1,
|
|
376
|
+
step: 0.05,
|
|
377
|
+
"aria-label": "Opacity",
|
|
378
|
+
className: "h-4 flex-1 accent-primary",
|
|
379
|
+
value: source.opacity,
|
|
380
|
+
onChange: (e) => apply({ opacity: parseFloat(e.target.value) })
|
|
381
|
+
}
|
|
382
|
+
)
|
|
383
|
+
] })
|
|
384
|
+
] });
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/vector/VectorEditor.tsx
|
|
388
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
389
|
+
function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }) {
|
|
390
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
|
|
391
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(VectorToolbar, { controller, tools }),
|
|
392
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
|
|
393
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(VectorCanvas, { controller, ...canvasProps }) }),
|
|
394
|
+
showStylePanel && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
|
|
395
|
+
] })
|
|
396
|
+
] });
|
|
397
|
+
}
|
|
398
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
399
|
+
0 && (module.exports = {
|
|
400
|
+
VectorCanvas,
|
|
401
|
+
VectorEditor,
|
|
402
|
+
VectorGradientDefs,
|
|
403
|
+
VectorSceneNodes,
|
|
404
|
+
VectorStylePanel,
|
|
405
|
+
VectorToolbar,
|
|
406
|
+
useVectorCommands,
|
|
407
|
+
useVectorEditor,
|
|
408
|
+
useVectorEditorController,
|
|
409
|
+
useVectorEditorState
|
|
410
|
+
});
|
|
411
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/vector/index.ts","../../src/vector/useVectorEditor.ts","../../src/vector/VectorCanvas.tsx","../../src/vector/VectorToolbar.tsx","../../src/vector/VectorEditor.tsx"],"sourcesContent":["export * from './useVectorEditor';\nexport * from './VectorCanvas';\nexport * from './VectorToolbar';\nexport * from './VectorEditor';\n","import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport {\n VectorEditorController,\n createVectorCommands,\n type VectorCommandOptions,\n type VectorEditorOptions,\n type VectorEditorState,\n type VectorTool,\n} from '@pixerate/editor/vector';\n\n/** A `VectorEditorController` created once for the component's lifetime (destroyed on unmount). */\nexport function useVectorEditorController(options: VectorEditorOptions = {}): VectorEditorController {\n const ref = useRef<VectorEditorController | null>(null);\n if (!ref.current) ref.current = new VectorEditorController(options);\n const controller = ref.current;\n useEffect(() => () => controller.destroy(), [controller]);\n return controller;\n}\n\n/** The controller's state (re-renders on every change). */\nexport function useVectorEditorState(controller: VectorEditorController): VectorEditorState {\n return useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);\n}\n\nexport interface UseVectorEditorResult {\n controller: VectorEditorController;\n state: VectorEditorState;\n setTool: (tool: VectorTool) => void;\n undo: () => void;\n redo: () => void;\n deleteSelection: () => void;\n toSvg: () => string;\n}\n\n/**\n * Subscribes to a vector editor controller and exposes its state with bound actions. Pass\n * options instead of a controller to create one for the component's lifetime.\n */\nexport function useVectorEditor(controllerOrOptions: VectorEditorController | VectorEditorOptions = {}): UseVectorEditorResult {\n const own = useRef<VectorEditorController | null>(null);\n let controller: VectorEditorController;\n if (controllerOrOptions instanceof VectorEditorController) controller = controllerOrOptions;\n else {\n if (!own.current) own.current = new VectorEditorController(controllerOrOptions);\n controller = own.current;\n }\n const state = useVectorEditorState(controller);\n const actions = useMemo(\n () => ({\n setTool: (tool: VectorTool) => controller.setTool(tool),\n undo: () => void controller.undo(),\n redo: () => void controller.redo(),\n deleteSelection: () => void controller.deleteSelection(),\n toSvg: () => controller.toSvg(),\n }),\n [controller],\n );\n return { controller, state, ...actions };\n}\n\n/** Anything with `register(specs) → unregister` (a `CommandRegistry`). */\nexport interface VectorCommandTarget {\n register(specs: any): () => void;\n}\n\n/**\n * Registers the controller's palette / agent commands (`vector.addShape`, `vector.boolean`, …)\n * in `registry` while mounted, so `createCommandToolProvider` exposes them to agents.\n */\nexport function useVectorCommands(\n registry: VectorCommandTarget | null | undefined,\n controller: VectorEditorController,\n options: VectorCommandOptions = {},\n): void {\n const { prefix, group, scope } = options;\n useEffect(() => {\n if (!registry) return;\n return registry.register(createVectorCommands(controller, { prefix, group, scope }));\n }, [registry, controller, prefix, group, scope]);\n}\n","import React, { useEffect, useId, useRef } from 'react';\nimport {\n VECTOR_OVERLAY_THEME as THEME,\n getVectorOverlayRenderData,\n getVectorSceneRenderData,\n isVectorTextTarget,\n toVectorKeyInput,\n toVectorPointerInput,\n type VectorEditorController,\n type VectorRenderNode,\n type SvgGradientDef,\n} from '@pixerate/editor/vector';\nimport { useVectorEditorState } from './useVectorEditor';\n\nexport interface VectorCanvasProps {\n controller: VectorEditorController;\n /** Fit the artboard into the canvas when it first gets a size (default `true`). */\n fitOnMount?: boolean;\n /** Draw a checkerboard behind a transparent artboard (default `true`). */\n checkerboard?: boolean;\n /** Overlay accent colour (default sky blue). */\n accentColor?: string;\n className?: string;\n style?: React.CSSProperties;\n /** Accessible label (default \"Vector canvas\"). */\n 'aria-label'?: string;\n}\n\n/** Renders `<linearGradient>` / `<radialGradient>` descriptors. */\nexport function VectorGradientDefs({ defs }: { defs: readonly SvgGradientDef[] }) {\n if (!defs.length) return null;\n return (\n <defs>\n {defs.map((def) => {\n const stops = def.stops.map((s, i) => <stop key={i} offset={s.offset} stopColor={s.stopColor} stopOpacity={s.stopOpacity} />);\n return def.type === 'radial' ? (\n <radialGradient key={def.id} id={def.id} {...(def.attrs as any)}>\n {stops}\n </radialGradient>\n ) : (\n <linearGradient key={def.id} id={def.id} {...(def.attrs as any)}>\n {stops}\n </linearGradient>\n );\n })}\n </defs>\n );\n}\n\n/** Renders scene nodes (paths and groups) from `getVectorSceneRenderData`. */\nexport function VectorSceneNodes({ nodes }: { nodes: readonly VectorRenderNode[] }) {\n return (\n <>\n {nodes.map((node) =>\n node.kind === 'group' ? (\n <g key={node.id} data-vector-id={node.id} transform={node.transform} opacity={node.opacity < 1 ? node.opacity : undefined}>\n <VectorSceneNodes nodes={node.children} />\n </g>\n ) : (\n <path\n key={node.id}\n data-vector-id={node.id}\n transform={node.transform}\n d={node.d}\n fill={node.fill}\n fillRule={node.fillRule}\n stroke={node.stroke}\n strokeWidth={node.stroke === 'none' ? undefined : node.strokeWidth}\n strokeLinecap={node.strokeLinecap as any}\n strokeLinejoin={node.strokeLinejoin as any}\n strokeDasharray={node.strokeDasharray}\n opacity={node.opacity < 1 ? node.opacity : undefined}\n />\n ),\n )}\n </>\n );\n}\n\n/**\n * An SVG canvas for a `VectorEditorController`: draws the document and the editing overlay\n * and forwards pointer, wheel, keyboard and clipboard events. Focus it to use shortcuts.\n */\nexport function VectorCanvas({\n controller,\n fitOnMount = true,\n checkerboard = true,\n accentColor = THEME.accent,\n className,\n style,\n 'aria-label': ariaLabel = 'Vector canvas',\n}: VectorCanvasProps) {\n const state = useVectorEditorState(controller);\n const svgRef = useRef<SVGSVGElement | null>(null);\n const fitted = useRef(!fitOnMount);\n const uid = useId().replace(/:/g, '');\n const scene = getVectorSceneRenderData(state, { idPrefix: `ve${uid}-` });\n const overlay = getVectorOverlayRenderData(state);\n\n useEffect(() => {\n const el = svgRef.current;\n if (!el || fitted.current) return;\n const fit = () => {\n const rect = el.getBoundingClientRect();\n if (fitted.current || rect.width <= 0 || rect.height <= 0) return;\n fitted.current = true;\n controller.zoomToFit({ width: rect.width, height: rect.height });\n };\n fit();\n if (fitted.current || typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(fit);\n observer.observe(el);\n return () => observer.disconnect();\n }, [controller]);\n\n // Wheel needs a non-passive listener to stop page scroll / browser zoom.\n useEffect(() => {\n const el = svgRef.current;\n if (!el) return;\n const onWheel = (e: WheelEvent) => {\n e.preventDefault();\n controller.wheel(local(e), { x: e.deltaX, y: e.deltaY }, toVectorPointerInput(e));\n };\n el.addEventListener('wheel', onWheel, { passive: false });\n return () => el.removeEventListener('wheel', onWheel);\n }, [controller]);\n\n const local = (e: { clientX: number; clientY: number }) => {\n const rect = svgRef.current?.getBoundingClientRect();\n return { x: e.clientX - (rect?.left ?? 0), y: e.clientY - (rect?.top ?? 0) };\n };\n\n const clipboard = (kind: 'copy' | 'cut' | 'paste') => (event: React.ClipboardEvent) => {\n const native = (event && 'nativeEvent' in event ? event.nativeEvent : event) as ClipboardEvent;\n const data = native?.clipboardData;\n if (kind === 'paste') {\n const text = data?.getData('text/plain') ?? '';\n const pasted = text ? controller.paste(text) : controller.paste();\n if (pasted.length) event.preventDefault?.();\n return;\n }\n const text = kind === 'cut' ? controller.cut() : controller.copy();\n if (text && data) {\n data.setData('text/plain', text);\n event.preventDefault?.();\n }\n };\n\n const hs = THEME.transformHandleSize;\n const as = THEME.anchorSize;\n const box = overlay.selectionBox;\n\n return (\n <svg\n ref={svgRef}\n role=\"application\"\n aria-label={ariaLabel}\n tabIndex={0}\n data-tool={state.tool}\n className={className}\n style={{ display: 'block', width: '100%', height: '100%', outline: 'none', touchAction: 'none', userSelect: 'none', cursor: state.cursor, ...style }}\n onPointerDown={(e) => {\n svgRef.current?.focus({ preventScroll: true });\n (e.currentTarget as Element).setPointerCapture?.(e.pointerId);\n controller.pointerDown(local(e), toVectorPointerInput(e));\n }}\n onPointerMove={(e) => controller.pointerMove(local(e), toVectorPointerInput(e))}\n onPointerUp={(e) => {\n (e.currentTarget as Element).releasePointerCapture?.(e.pointerId);\n controller.pointerUp(local(e), toVectorPointerInput(e));\n }}\n onPointerCancel={() => controller.cancelDrag()}\n onPointerLeave={() => controller.pointerLeave()}\n onDoubleClick={(e) => controller.doubleClick(local(e))}\n onKeyDown={(e) => {\n if (isVectorTextTarget(e.target)) return;\n if (controller.keyDown(toVectorKeyInput(e))) e.preventDefault();\n }}\n onKeyUp={(e) => controller.keyUp(toVectorKeyInput(e))}\n onCopy={clipboard('copy')}\n onCut={clipboard('cut')}\n onPaste={clipboard('paste')}\n >\n {checkerboard && (\n <defs>\n <pattern id={`ve${uid}-checker`} width={16} height={16} patternUnits=\"userSpaceOnUse\">\n <rect width={16} height={16} fill=\"#ffffff\" />\n <rect width={8} height={8} fill=\"#e5e7eb\" />\n <rect x={8} y={8} width={8} height={8} fill=\"#e5e7eb\" />\n </pattern>\n </defs>\n )}\n <VectorGradientDefs defs={scene.defs} />\n <rect\n data-vector-artboard=\"\"\n x={overlay.artboard.x}\n y={overlay.artboard.y}\n width={overlay.artboard.width}\n height={overlay.artboard.height}\n fill={scene.background ?? (checkerboard ? `url(#ve${uid}-checker)` : '#ffffff')}\n stroke=\"rgba(0,0,0,0.12)\"\n />\n <g data-vector-scene=\"\" transform={scene.transform}>\n <VectorSceneNodes nodes={scene.nodes} />\n </g>\n <g data-vector-overlay=\"\" pointerEvents=\"none\" fill=\"none\">\n {overlay.outlines.map((o) => (\n <path key={o.id} d={o.d} stroke={accentColor} strokeWidth={o.selected ? 1 : 1.5} opacity={o.selected ? 1 : 0.7} />\n ))}\n {overlay.penPreview && <path d={overlay.penPreview} stroke={accentColor} strokeDasharray=\"4 3\" />}\n {box && (\n <g data-vector-selection=\"\" transform={box.transform || undefined}>\n <rect x={box.x} y={box.y} width={box.width} height={box.height} stroke={accentColor} />\n <line x1={box.x + box.width / 2} y1={box.y} x2={box.x + box.width / 2} y2={box.y - 24} stroke={accentColor} />\n {box.handles.map((h) =>\n h.handle === 'rotate' ? (\n <circle key={h.handle} data-handle={h.handle} cx={h.x} cy={h.y} r={hs / 2 + 1} fill={THEME.handleFill} stroke={accentColor} />\n ) : (\n <rect key={h.handle} data-handle={h.handle} x={h.x - hs / 2} y={h.y - hs / 2} width={hs} height={hs} fill={THEME.handleFill} stroke={accentColor} />\n ),\n )}\n </g>\n )}\n {overlay.handles.map((h) => (\n <g key={`${h.ref.nodeId}:${h.ref.subpath}:${h.ref.index}:${h.ref.handle}`} data-vector-handle={h.ref.handle}>\n <line x1={h.anchorX} y1={h.anchorY} x2={h.x} y2={h.y} stroke={accentColor} />\n <circle cx={h.x} cy={h.y} r={THEME.handleRadius + (h.hovered ? 1 : 0)} fill={THEME.handleFill} stroke={accentColor} />\n </g>\n ))}\n {overlay.anchors.map((a) => {\n const size = as + (a.hovered ? 2 : 0);\n const fill = a.selected ? accentColor : THEME.handleFill;\n const key = `${a.ref.nodeId}:${a.ref.subpath}:${a.ref.index}`;\n return a.type === 'corner' ? (\n <rect key={key} data-vector-anchor={a.ref.index} x={a.x - size / 2} y={a.y - size / 2} width={size} height={size} fill={fill} stroke={accentColor} />\n ) : (\n <circle key={key} data-vector-anchor={a.ref.index} cx={a.x} cy={a.y} r={size / 2} fill={fill} stroke={accentColor} />\n );\n })}\n {state.tool === 'pen' && overlay.anchors.some((a) => a.closes && a.hovered) && (\n <circle cx={overlay.anchors.find((a) => a.closes)!.x} cy={overlay.anchors.find((a) => a.closes)!.y} r={as} stroke={accentColor} />\n )}\n {overlay.marquee && (\n <rect\n data-vector-marquee=\"\"\n x={overlay.marquee.x}\n y={overlay.marquee.y}\n width={overlay.marquee.width}\n height={overlay.marquee.height}\n fill={THEME.marqueeFill}\n stroke={accentColor}\n strokeDasharray=\"3 2\"\n />\n )}\n {overlay.status && (\n <text data-vector-status=\"\" x={8} y=\"100%\" dy={-8} fontSize={11} fill={accentColor} style={{ fontFamily: 'system-ui, sans-serif' }}>\n {overlay.status}\n </text>\n )}\n </g>\n </svg>\n );\n}\n","import React from 'react';\nimport {\n VECTOR_TOOLS,\n VECTOR_TOOL_GLYPHS,\n VECTOR_TOOL_SHORTCUTS,\n getVectorSelectionStyle,\n vectorPaintToHex,\n vectorToolLabel,\n type VectorEditorController,\n type VectorTool,\n} from '@pixerate/editor/vector';\nimport { useVectorEditorState } from './useVectorEditor';\n\nconst buttonClass =\n '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';\n\nexport interface VectorToolbarProps {\n controller: VectorEditorController;\n /** Tools to show (default: all). */\n tools?: readonly VectorTool[];\n /** Undo / redo buttons (default `true`). */\n showHistory?: boolean;\n /** Boolean, group, arrange and align buttons (default `true`). */\n showArrange?: boolean;\n className?: string;\n}\n\n/** Tool picker plus undo, boolean operations, grouping, stacking and alignment. */\nexport function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, showArrange = true, className }: VectorToolbarProps) {\n const state = useVectorEditorState(controller);\n const count = state.selection.length;\n const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === 'group');\n const act = (label: string, title: string, run: () => unknown, disabled = false) => (\n <button key={label} type=\"button\" className={buttonClass} title={title} aria-label={title} disabled={disabled} onClick={() => void run()}>\n {label}\n </button>\n );\n return (\n <div role=\"toolbar\" aria-label=\"Vector tools\" className={`flex flex-wrap items-center gap-1 ${className ?? ''}`}>\n <div className=\"flex items-center gap-1\" role=\"group\" aria-label=\"Tools\">\n {tools.map((tool) => (\n <button\n key={tool}\n type=\"button\"\n data-vector-tool={tool}\n className={buttonClass}\n aria-pressed={state.tool === tool}\n title={`${vectorToolLabel(tool)} (${VECTOR_TOOL_SHORTCUTS[tool]})`}\n aria-label={vectorToolLabel(tool)}\n onClick={() => controller.setTool(tool)}\n >\n {VECTOR_TOOL_GLYPHS[tool]}\n </button>\n ))}\n </div>\n {showHistory && (\n <div className=\"ml-1 flex items-center gap-1\" role=\"group\" aria-label=\"History\">\n {act('↶', state.canUndo ? `Undo ${state.undoLabel}` : 'Undo', () => controller.undo(), !state.canUndo)}\n {act('↷', state.canRedo ? `Redo ${state.redoLabel}` : 'Redo', () => controller.redo(), !state.canRedo)}\n </div>\n )}\n {showArrange && (\n <>\n <div className=\"ml-1 flex items-center gap-1\" role=\"group\" aria-label=\"Combine\">\n {act('∪', 'Union', () => controller.booleanOp('union'), count < 2)}\n {act('−', 'Subtract', () => controller.booleanOp('subtract'), count < 2)}\n {act('∩', 'Intersect', () => controller.booleanOp('intersect'), count < 2)}\n {act('⊻', 'Exclude', () => controller.booleanOp('exclude'), count < 2)}\n {act('G', 'Group', () => controller.group(), count < 1)}\n {act('U', 'Ungroup', () => controller.ungroup(), !groupSelected)}\n </div>\n <div className=\"ml-1 flex items-center gap-1\" role=\"group\" aria-label=\"Arrange\">\n {act('⇈', 'Bring to Front', () => controller.arrange('front'), count < 1)}\n {act('⇊', 'Send to Back', () => controller.arrange('back'), count < 1)}\n {act('⇤', 'Align Left', () => controller.align('left'), count < 1)}\n {act('↔', 'Align Center', () => controller.align('center'), count < 1)}\n {act('⇥', 'Align Right', () => controller.align('right'), count < 1)}\n {act('⤒', 'Align Top', () => controller.align('top'), count < 1)}\n {act('↕', 'Align Middle', () => controller.align('middle'), count < 1)}\n {act('⤓', 'Align Bottom', () => controller.align('bottom'), count < 1)}\n </div>\n </>\n )}\n </div>\n );\n}\n\nexport interface VectorStylePanelProps {\n controller: VectorEditorController;\n className?: string;\n}\n\n/**\n * Fill, stroke, stroke width and opacity of the selection; with nothing selected it edits\n * the defaults for new shapes.\n */\nexport function VectorStylePanel({ controller, className }: VectorStylePanelProps) {\n const state = useVectorEditorState(controller);\n const style = getVectorSelectionStyle(state);\n const source = style ?? { fill: state.defaults.fill ?? null, stroke: state.defaults.stroke ?? null, strokeWidth: state.defaults.strokeWidth ?? 1, opacity: 1 };\n const apply = (patch: Record<string, unknown>) => {\n if (style) controller.setStyle(patch);\n else controller.setDefaults(patch);\n };\n const paintRow = (key: 'fill' | 'stroke', label: string) => {\n const paint = source[key];\n const hex = vectorPaintToHex(paint);\n const on = paint !== null && paint !== 'none';\n return (\n <div className=\"flex items-center gap-2\" data-vector-style={key}>\n <label className=\"flex w-16 items-center gap-1.5 text-xs text-foreground\">\n <input type=\"checkbox\" checked={on} aria-label={`${label} on`} onChange={(e) => apply({ [key]: e.target.checked ? (hex ?? '#000000') : null })} />\n {label}\n </label>\n <input\n type=\"color\"\n aria-label={`${label} colour`}\n className=\"h-7 w-10 cursor-pointer rounded border border-border bg-background disabled:opacity-40\"\n disabled={!on}\n value={hex ?? '#000000'}\n onChange={(e) => apply({ [key]: e.target.value })}\n />\n {on && !hex && <span className=\"truncate text-[11px] text-muted-foreground\">{typeof paint === 'string' ? paint : 'Gradient'}</span>}\n {style?.mixed[key] && <span className=\"text-[11px] text-muted-foreground\">Mixed</span>}\n </div>\n );\n };\n return (\n <div className={`space-y-2 ${className ?? ''}`} aria-label={style ? 'Selection style' : 'New shape style'}>\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-muted-foreground\">{style ? 'Selection' : 'New shapes'}</p>\n {paintRow('fill', 'Fill')}\n {paintRow('stroke', 'Stroke')}\n <label className=\"flex items-center gap-2 text-xs text-foreground\">\n <span className=\"w-16\">Width</span>\n <input\n type=\"number\"\n min={0}\n step={0.5}\n aria-label=\"Stroke width\"\n className=\"h-7 w-20 rounded border border-border bg-background px-1.5 text-xs\"\n value={source.strokeWidth}\n onChange={(e) => {\n const n = parseFloat(e.target.value);\n if (Number.isFinite(n) && n >= 0) apply({ strokeWidth: n });\n }}\n />\n </label>\n {style && (\n <label className=\"flex items-center gap-2 text-xs text-foreground\">\n <span className=\"w-16\">Opacity</span>\n <input\n type=\"range\"\n min={0}\n max={1}\n step={0.05}\n aria-label=\"Opacity\"\n className=\"h-4 flex-1 accent-primary\"\n value={source.opacity}\n onChange={(e) => apply({ opacity: parseFloat(e.target.value) })}\n />\n </label>\n )}\n </div>\n );\n}\n","import React from 'react';\nimport type { VectorEditorController, VectorTool } from '@pixerate/editor/vector';\nimport { VectorCanvas, type VectorCanvasProps } from './VectorCanvas';\nimport { VectorStylePanel, VectorToolbar } from './VectorToolbar';\n\nexport interface VectorEditorProps {\n controller: VectorEditorController;\n /** Tools in the toolbar (default: all). */\n tools?: readonly VectorTool[];\n /** The style sidebar (default `true`). */\n showStylePanel?: boolean;\n /** Canvas height (default 520px). */\n height?: number | string;\n canvasProps?: Omit<VectorCanvasProps, 'controller'>;\n className?: string;\n}\n\n/** Toolbar, canvas and style panel for a `VectorEditorController`. */\nexport function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }: VectorEditorProps) {\n return (\n <div className={`flex flex-col gap-2 ${className ?? ''}`} data-vector-editor=\"\">\n <VectorToolbar controller={controller} tools={tools} />\n <div className=\"flex min-h-0 flex-col gap-2 md:flex-row\">\n <div className=\"relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40\" style={{ height }}>\n <VectorCanvas controller={controller} {...canvasProps} />\n </div>\n {showStylePanel && <VectorStylePanel controller={controller} className=\"w-full shrink-0 rounded-md border border-border p-3 md:w-56\" />}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["import_react","import_vector","THEME","text","import_vector","import_jsx_runtime","import_jsx_runtime"]}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { VectorEditorController, VectorEditorState, VectorTool, VectorCommandOptions, VectorEditorOptions, SvgGradientDef, VectorRenderNode } from '@pixerate/editor/vector';
|
|
2
|
+
import React__default from 'react';
|
|
3
|
+
|
|
4
|
+
/** A `VectorEditorController` created once for the component's lifetime (destroyed on unmount). */
|
|
5
|
+
declare function useVectorEditorController(options?: VectorEditorOptions): VectorEditorController;
|
|
6
|
+
/** The controller's state (re-renders on every change). */
|
|
7
|
+
declare function useVectorEditorState(controller: VectorEditorController): VectorEditorState;
|
|
8
|
+
interface UseVectorEditorResult {
|
|
9
|
+
controller: VectorEditorController;
|
|
10
|
+
state: VectorEditorState;
|
|
11
|
+
setTool: (tool: VectorTool) => void;
|
|
12
|
+
undo: () => void;
|
|
13
|
+
redo: () => void;
|
|
14
|
+
deleteSelection: () => void;
|
|
15
|
+
toSvg: () => string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Subscribes to a vector editor controller and exposes its state with bound actions. Pass
|
|
19
|
+
* options instead of a controller to create one for the component's lifetime.
|
|
20
|
+
*/
|
|
21
|
+
declare function useVectorEditor(controllerOrOptions?: VectorEditorController | VectorEditorOptions): UseVectorEditorResult;
|
|
22
|
+
/** Anything with `register(specs) → unregister` (a `CommandRegistry`). */
|
|
23
|
+
interface VectorCommandTarget {
|
|
24
|
+
register(specs: any): () => void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Registers the controller's palette / agent commands (`vector.addShape`, `vector.boolean`, …)
|
|
28
|
+
* in `registry` while mounted, so `createCommandToolProvider` exposes them to agents.
|
|
29
|
+
*/
|
|
30
|
+
declare function useVectorCommands(registry: VectorCommandTarget | null | undefined, controller: VectorEditorController, options?: VectorCommandOptions): void;
|
|
31
|
+
|
|
32
|
+
interface VectorCanvasProps {
|
|
33
|
+
controller: VectorEditorController;
|
|
34
|
+
/** Fit the artboard into the canvas when it first gets a size (default `true`). */
|
|
35
|
+
fitOnMount?: boolean;
|
|
36
|
+
/** Draw a checkerboard behind a transparent artboard (default `true`). */
|
|
37
|
+
checkerboard?: boolean;
|
|
38
|
+
/** Overlay accent colour (default sky blue). */
|
|
39
|
+
accentColor?: string;
|
|
40
|
+
className?: string;
|
|
41
|
+
style?: React__default.CSSProperties;
|
|
42
|
+
/** Accessible label (default "Vector canvas"). */
|
|
43
|
+
'aria-label'?: string;
|
|
44
|
+
}
|
|
45
|
+
/** Renders `<linearGradient>` / `<radialGradient>` descriptors. */
|
|
46
|
+
declare function VectorGradientDefs({ defs }: {
|
|
47
|
+
defs: readonly SvgGradientDef[];
|
|
48
|
+
}): React__default.JSX.Element | null;
|
|
49
|
+
/** Renders scene nodes (paths and groups) from `getVectorSceneRenderData`. */
|
|
50
|
+
declare function VectorSceneNodes({ nodes }: {
|
|
51
|
+
nodes: readonly VectorRenderNode[];
|
|
52
|
+
}): React__default.JSX.Element;
|
|
53
|
+
/**
|
|
54
|
+
* An SVG canvas for a `VectorEditorController`: draws the document and the editing overlay
|
|
55
|
+
* and forwards pointer, wheel, keyboard and clipboard events. Focus it to use shortcuts.
|
|
56
|
+
*/
|
|
57
|
+
declare function VectorCanvas({ controller, fitOnMount, checkerboard, accentColor, className, style, 'aria-label': ariaLabel, }: VectorCanvasProps): React__default.JSX.Element;
|
|
58
|
+
|
|
59
|
+
interface VectorToolbarProps {
|
|
60
|
+
controller: VectorEditorController;
|
|
61
|
+
/** Tools to show (default: all). */
|
|
62
|
+
tools?: readonly VectorTool[];
|
|
63
|
+
/** Undo / redo buttons (default `true`). */
|
|
64
|
+
showHistory?: boolean;
|
|
65
|
+
/** Boolean, group, arrange and align buttons (default `true`). */
|
|
66
|
+
showArrange?: boolean;
|
|
67
|
+
className?: string;
|
|
68
|
+
}
|
|
69
|
+
/** Tool picker plus undo, boolean operations, grouping, stacking and alignment. */
|
|
70
|
+
declare function VectorToolbar({ controller, tools, showHistory, showArrange, className }: VectorToolbarProps): React__default.JSX.Element;
|
|
71
|
+
interface VectorStylePanelProps {
|
|
72
|
+
controller: VectorEditorController;
|
|
73
|
+
className?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Fill, stroke, stroke width and opacity of the selection; with nothing selected it edits
|
|
77
|
+
* the defaults for new shapes.
|
|
78
|
+
*/
|
|
79
|
+
declare function VectorStylePanel({ controller, className }: VectorStylePanelProps): React__default.JSX.Element;
|
|
80
|
+
|
|
81
|
+
interface VectorEditorProps {
|
|
82
|
+
controller: VectorEditorController;
|
|
83
|
+
/** Tools in the toolbar (default: all). */
|
|
84
|
+
tools?: readonly VectorTool[];
|
|
85
|
+
/** The style sidebar (default `true`). */
|
|
86
|
+
showStylePanel?: boolean;
|
|
87
|
+
/** Canvas height (default 520px). */
|
|
88
|
+
height?: number | string;
|
|
89
|
+
canvasProps?: Omit<VectorCanvasProps, 'controller'>;
|
|
90
|
+
className?: string;
|
|
91
|
+
}
|
|
92
|
+
/** Toolbar, canvas and style panel for a `VectorEditorController`. */
|
|
93
|
+
declare function VectorEditor({ controller, tools, showStylePanel, height, canvasProps, className }: VectorEditorProps): React__default.JSX.Element;
|
|
94
|
+
|
|
95
|
+
export { type UseVectorEditorResult, VectorCanvas, type VectorCanvasProps, type VectorCommandTarget, VectorEditor, type VectorEditorProps, VectorGradientDefs, VectorSceneNodes, VectorStylePanel, type VectorStylePanelProps, VectorToolbar, type VectorToolbarProps, useVectorCommands, useVectorEditor, useVectorEditorController, useVectorEditorState };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { VectorEditorController, VectorEditorState, VectorTool, VectorCommandOptions, VectorEditorOptions, SvgGradientDef, VectorRenderNode } from '@pixerate/editor/vector';
|
|
2
|
+
import React__default from 'react';
|
|
3
|
+
|
|
4
|
+
/** A `VectorEditorController` created once for the component's lifetime (destroyed on unmount). */
|
|
5
|
+
declare function useVectorEditorController(options?: VectorEditorOptions): VectorEditorController;
|
|
6
|
+
/** The controller's state (re-renders on every change). */
|
|
7
|
+
declare function useVectorEditorState(controller: VectorEditorController): VectorEditorState;
|
|
8
|
+
interface UseVectorEditorResult {
|
|
9
|
+
controller: VectorEditorController;
|
|
10
|
+
state: VectorEditorState;
|
|
11
|
+
setTool: (tool: VectorTool) => void;
|
|
12
|
+
undo: () => void;
|
|
13
|
+
redo: () => void;
|
|
14
|
+
deleteSelection: () => void;
|
|
15
|
+
toSvg: () => string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Subscribes to a vector editor controller and exposes its state with bound actions. Pass
|
|
19
|
+
* options instead of a controller to create one for the component's lifetime.
|
|
20
|
+
*/
|
|
21
|
+
declare function useVectorEditor(controllerOrOptions?: VectorEditorController | VectorEditorOptions): UseVectorEditorResult;
|
|
22
|
+
/** Anything with `register(specs) → unregister` (a `CommandRegistry`). */
|
|
23
|
+
interface VectorCommandTarget {
|
|
24
|
+
register(specs: any): () => void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Registers the controller's palette / agent commands (`vector.addShape`, `vector.boolean`, …)
|
|
28
|
+
* in `registry` while mounted, so `createCommandToolProvider` exposes them to agents.
|
|
29
|
+
*/
|
|
30
|
+
declare function useVectorCommands(registry: VectorCommandTarget | null | undefined, controller: VectorEditorController, options?: VectorCommandOptions): void;
|
|
31
|
+
|
|
32
|
+
interface VectorCanvasProps {
|
|
33
|
+
controller: VectorEditorController;
|
|
34
|
+
/** Fit the artboard into the canvas when it first gets a size (default `true`). */
|
|
35
|
+
fitOnMount?: boolean;
|
|
36
|
+
/** Draw a checkerboard behind a transparent artboard (default `true`). */
|
|
37
|
+
checkerboard?: boolean;
|
|
38
|
+
/** Overlay accent colour (default sky blue). */
|
|
39
|
+
accentColor?: string;
|
|
40
|
+
className?: string;
|
|
41
|
+
style?: React__default.CSSProperties;
|
|
42
|
+
/** Accessible label (default "Vector canvas"). */
|
|
43
|
+
'aria-label'?: string;
|
|
44
|
+
}
|
|
45
|
+
/** Renders `<linearGradient>` / `<radialGradient>` descriptors. */
|
|
46
|
+
declare function VectorGradientDefs({ defs }: {
|
|
47
|
+
defs: readonly SvgGradientDef[];
|
|
48
|
+
}): React__default.JSX.Element | null;
|
|
49
|
+
/** Renders scene nodes (paths and groups) from `getVectorSceneRenderData`. */
|
|
50
|
+
declare function VectorSceneNodes({ nodes }: {
|
|
51
|
+
nodes: readonly VectorRenderNode[];
|
|
52
|
+
}): React__default.JSX.Element;
|
|
53
|
+
/**
|
|
54
|
+
* An SVG canvas for a `VectorEditorController`: draws the document and the editing overlay
|
|
55
|
+
* and forwards pointer, wheel, keyboard and clipboard events. Focus it to use shortcuts.
|
|
56
|
+
*/
|
|
57
|
+
declare function VectorCanvas({ controller, fitOnMount, checkerboard, accentColor, className, style, 'aria-label': ariaLabel, }: VectorCanvasProps): React__default.JSX.Element;
|
|
58
|
+
|
|
59
|
+
interface VectorToolbarProps {
|
|
60
|
+
controller: VectorEditorController;
|
|
61
|
+
/** Tools to show (default: all). */
|
|
62
|
+
tools?: readonly VectorTool[];
|
|
63
|
+
/** Undo / redo buttons (default `true`). */
|
|
64
|
+
showHistory?: boolean;
|
|
65
|
+
/** Boolean, group, arrange and align buttons (default `true`). */
|
|
66
|
+
showArrange?: boolean;
|
|
67
|
+
className?: string;
|
|
68
|
+
}
|
|
69
|
+
/** Tool picker plus undo, boolean operations, grouping, stacking and alignment. */
|
|
70
|
+
declare function VectorToolbar({ controller, tools, showHistory, showArrange, className }: VectorToolbarProps): React__default.JSX.Element;
|
|
71
|
+
interface VectorStylePanelProps {
|
|
72
|
+
controller: VectorEditorController;
|
|
73
|
+
className?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Fill, stroke, stroke width and opacity of the selection; with nothing selected it edits
|
|
77
|
+
* the defaults for new shapes.
|
|
78
|
+
*/
|
|
79
|
+
declare function VectorStylePanel({ controller, className }: VectorStylePanelProps): React__default.JSX.Element;
|
|
80
|
+
|
|
81
|
+
interface VectorEditorProps {
|
|
82
|
+
controller: VectorEditorController;
|
|
83
|
+
/** Tools in the toolbar (default: all). */
|
|
84
|
+
tools?: readonly VectorTool[];
|
|
85
|
+
/** The style sidebar (default `true`). */
|
|
86
|
+
showStylePanel?: boolean;
|
|
87
|
+
/** Canvas height (default 520px). */
|
|
88
|
+
height?: number | string;
|
|
89
|
+
canvasProps?: Omit<VectorCanvasProps, 'controller'>;
|
|
90
|
+
className?: string;
|
|
91
|
+
}
|
|
92
|
+
/** Toolbar, canvas and style panel for a `VectorEditorController`. */
|
|
93
|
+
declare function VectorEditor({ controller, tools, showStylePanel, height, canvasProps, className }: VectorEditorProps): React__default.JSX.Element;
|
|
94
|
+
|
|
95
|
+
export { type UseVectorEditorResult, VectorCanvas, type VectorCanvasProps, type VectorCommandTarget, VectorEditor, type VectorEditorProps, VectorGradientDefs, VectorSceneNodes, VectorStylePanel, type VectorStylePanelProps, VectorToolbar, type VectorToolbarProps, useVectorCommands, useVectorEditor, useVectorEditorController, useVectorEditorState };
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
// src/vector/useVectorEditor.ts
|
|
2
|
+
import { useEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
3
|
+
import {
|
|
4
|
+
VectorEditorController,
|
|
5
|
+
createVectorCommands
|
|
6
|
+
} from "@pixerate/editor/vector";
|
|
7
|
+
function useVectorEditorController(options = {}) {
|
|
8
|
+
const ref = useRef(null);
|
|
9
|
+
if (!ref.current) ref.current = new VectorEditorController(options);
|
|
10
|
+
const controller = ref.current;
|
|
11
|
+
useEffect(() => () => controller.destroy(), [controller]);
|
|
12
|
+
return controller;
|
|
13
|
+
}
|
|
14
|
+
function useVectorEditorState(controller) {
|
|
15
|
+
return useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);
|
|
16
|
+
}
|
|
17
|
+
function useVectorEditor(controllerOrOptions = {}) {
|
|
18
|
+
const own = useRef(null);
|
|
19
|
+
let controller;
|
|
20
|
+
if (controllerOrOptions instanceof VectorEditorController) controller = controllerOrOptions;
|
|
21
|
+
else {
|
|
22
|
+
if (!own.current) own.current = new VectorEditorController(controllerOrOptions);
|
|
23
|
+
controller = own.current;
|
|
24
|
+
}
|
|
25
|
+
const state = useVectorEditorState(controller);
|
|
26
|
+
const actions = useMemo(
|
|
27
|
+
() => ({
|
|
28
|
+
setTool: (tool) => controller.setTool(tool),
|
|
29
|
+
undo: () => void controller.undo(),
|
|
30
|
+
redo: () => void controller.redo(),
|
|
31
|
+
deleteSelection: () => void controller.deleteSelection(),
|
|
32
|
+
toSvg: () => controller.toSvg()
|
|
33
|
+
}),
|
|
34
|
+
[controller]
|
|
35
|
+
);
|
|
36
|
+
return { controller, state, ...actions };
|
|
37
|
+
}
|
|
38
|
+
function useVectorCommands(registry, controller, options = {}) {
|
|
39
|
+
const { prefix, group, scope } = options;
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!registry) return;
|
|
42
|
+
return registry.register(createVectorCommands(controller, { prefix, group, scope }));
|
|
43
|
+
}, [registry, controller, prefix, group, scope]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/vector/VectorCanvas.tsx
|
|
47
|
+
import { useEffect as useEffect2, useId, useRef as useRef2 } from "react";
|
|
48
|
+
import {
|
|
49
|
+
VECTOR_OVERLAY_THEME as THEME,
|
|
50
|
+
getVectorOverlayRenderData,
|
|
51
|
+
getVectorSceneRenderData,
|
|
52
|
+
isVectorTextTarget,
|
|
53
|
+
toVectorKeyInput,
|
|
54
|
+
toVectorPointerInput
|
|
55
|
+
} from "@pixerate/editor/vector";
|
|
56
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
57
|
+
function VectorGradientDefs({ defs }) {
|
|
58
|
+
if (!defs.length) return null;
|
|
59
|
+
return /* @__PURE__ */ jsx("defs", { children: defs.map((def) => {
|
|
60
|
+
const stops = def.stops.map((s, i) => /* @__PURE__ */ jsx("stop", { offset: s.offset, stopColor: s.stopColor, stopOpacity: s.stopOpacity }, i));
|
|
61
|
+
return def.type === "radial" ? /* @__PURE__ */ jsx("radialGradient", { id: def.id, ...def.attrs, children: stops }, def.id) : /* @__PURE__ */ jsx("linearGradient", { id: def.id, ...def.attrs, children: stops }, def.id);
|
|
62
|
+
}) });
|
|
63
|
+
}
|
|
64
|
+
function VectorSceneNodes({ nodes }) {
|
|
65
|
+
return /* @__PURE__ */ jsx(Fragment, { children: nodes.map(
|
|
66
|
+
(node) => node.kind === "group" ? /* @__PURE__ */ jsx("g", { "data-vector-id": node.id, transform: node.transform, opacity: node.opacity < 1 ? node.opacity : void 0, children: /* @__PURE__ */ jsx(VectorSceneNodes, { nodes: node.children }) }, node.id) : /* @__PURE__ */ jsx(
|
|
67
|
+
"path",
|
|
68
|
+
{
|
|
69
|
+
"data-vector-id": node.id,
|
|
70
|
+
transform: node.transform,
|
|
71
|
+
d: node.d,
|
|
72
|
+
fill: node.fill,
|
|
73
|
+
fillRule: node.fillRule,
|
|
74
|
+
stroke: node.stroke,
|
|
75
|
+
strokeWidth: node.stroke === "none" ? void 0 : node.strokeWidth,
|
|
76
|
+
strokeLinecap: node.strokeLinecap,
|
|
77
|
+
strokeLinejoin: node.strokeLinejoin,
|
|
78
|
+
strokeDasharray: node.strokeDasharray,
|
|
79
|
+
opacity: node.opacity < 1 ? node.opacity : void 0
|
|
80
|
+
},
|
|
81
|
+
node.id
|
|
82
|
+
)
|
|
83
|
+
) });
|
|
84
|
+
}
|
|
85
|
+
function VectorCanvas({
|
|
86
|
+
controller,
|
|
87
|
+
fitOnMount = true,
|
|
88
|
+
checkerboard = true,
|
|
89
|
+
accentColor = THEME.accent,
|
|
90
|
+
className,
|
|
91
|
+
style,
|
|
92
|
+
"aria-label": ariaLabel = "Vector canvas"
|
|
93
|
+
}) {
|
|
94
|
+
const state = useVectorEditorState(controller);
|
|
95
|
+
const svgRef = useRef2(null);
|
|
96
|
+
const fitted = useRef2(!fitOnMount);
|
|
97
|
+
const uid = useId().replace(/:/g, "");
|
|
98
|
+
const scene = getVectorSceneRenderData(state, { idPrefix: `ve${uid}-` });
|
|
99
|
+
const overlay = getVectorOverlayRenderData(state);
|
|
100
|
+
useEffect2(() => {
|
|
101
|
+
const el = svgRef.current;
|
|
102
|
+
if (!el || fitted.current) return;
|
|
103
|
+
const fit = () => {
|
|
104
|
+
const rect = el.getBoundingClientRect();
|
|
105
|
+
if (fitted.current || rect.width <= 0 || rect.height <= 0) return;
|
|
106
|
+
fitted.current = true;
|
|
107
|
+
controller.zoomToFit({ width: rect.width, height: rect.height });
|
|
108
|
+
};
|
|
109
|
+
fit();
|
|
110
|
+
if (fitted.current || typeof ResizeObserver === "undefined") return;
|
|
111
|
+
const observer = new ResizeObserver(fit);
|
|
112
|
+
observer.observe(el);
|
|
113
|
+
return () => observer.disconnect();
|
|
114
|
+
}, [controller]);
|
|
115
|
+
useEffect2(() => {
|
|
116
|
+
const el = svgRef.current;
|
|
117
|
+
if (!el) return;
|
|
118
|
+
const onWheel = (e) => {
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
controller.wheel(local(e), { x: e.deltaX, y: e.deltaY }, toVectorPointerInput(e));
|
|
121
|
+
};
|
|
122
|
+
el.addEventListener("wheel", onWheel, { passive: false });
|
|
123
|
+
return () => el.removeEventListener("wheel", onWheel);
|
|
124
|
+
}, [controller]);
|
|
125
|
+
const local = (e) => {
|
|
126
|
+
const rect = svgRef.current?.getBoundingClientRect();
|
|
127
|
+
return { x: e.clientX - (rect?.left ?? 0), y: e.clientY - (rect?.top ?? 0) };
|
|
128
|
+
};
|
|
129
|
+
const clipboard = (kind) => (event) => {
|
|
130
|
+
const native = event && "nativeEvent" in event ? event.nativeEvent : event;
|
|
131
|
+
const data = native?.clipboardData;
|
|
132
|
+
if (kind === "paste") {
|
|
133
|
+
const text2 = data?.getData("text/plain") ?? "";
|
|
134
|
+
const pasted = text2 ? controller.paste(text2) : controller.paste();
|
|
135
|
+
if (pasted.length) event.preventDefault?.();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const text = kind === "cut" ? controller.cut() : controller.copy();
|
|
139
|
+
if (text && data) {
|
|
140
|
+
data.setData("text/plain", text);
|
|
141
|
+
event.preventDefault?.();
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const hs = THEME.transformHandleSize;
|
|
145
|
+
const as = THEME.anchorSize;
|
|
146
|
+
const box = overlay.selectionBox;
|
|
147
|
+
return /* @__PURE__ */ jsxs(
|
|
148
|
+
"svg",
|
|
149
|
+
{
|
|
150
|
+
ref: svgRef,
|
|
151
|
+
role: "application",
|
|
152
|
+
"aria-label": ariaLabel,
|
|
153
|
+
tabIndex: 0,
|
|
154
|
+
"data-tool": state.tool,
|
|
155
|
+
className,
|
|
156
|
+
style: { display: "block", width: "100%", height: "100%", outline: "none", touchAction: "none", userSelect: "none", cursor: state.cursor, ...style },
|
|
157
|
+
onPointerDown: (e) => {
|
|
158
|
+
svgRef.current?.focus({ preventScroll: true });
|
|
159
|
+
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
160
|
+
controller.pointerDown(local(e), toVectorPointerInput(e));
|
|
161
|
+
},
|
|
162
|
+
onPointerMove: (e) => controller.pointerMove(local(e), toVectorPointerInput(e)),
|
|
163
|
+
onPointerUp: (e) => {
|
|
164
|
+
e.currentTarget.releasePointerCapture?.(e.pointerId);
|
|
165
|
+
controller.pointerUp(local(e), toVectorPointerInput(e));
|
|
166
|
+
},
|
|
167
|
+
onPointerCancel: () => controller.cancelDrag(),
|
|
168
|
+
onPointerLeave: () => controller.pointerLeave(),
|
|
169
|
+
onDoubleClick: (e) => controller.doubleClick(local(e)),
|
|
170
|
+
onKeyDown: (e) => {
|
|
171
|
+
if (isVectorTextTarget(e.target)) return;
|
|
172
|
+
if (controller.keyDown(toVectorKeyInput(e))) e.preventDefault();
|
|
173
|
+
},
|
|
174
|
+
onKeyUp: (e) => controller.keyUp(toVectorKeyInput(e)),
|
|
175
|
+
onCopy: clipboard("copy"),
|
|
176
|
+
onCut: clipboard("cut"),
|
|
177
|
+
onPaste: clipboard("paste"),
|
|
178
|
+
children: [
|
|
179
|
+
checkerboard && /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("pattern", { id: `ve${uid}-checker`, width: 16, height: 16, patternUnits: "userSpaceOnUse", children: [
|
|
180
|
+
/* @__PURE__ */ jsx("rect", { width: 16, height: 16, fill: "#ffffff" }),
|
|
181
|
+
/* @__PURE__ */ jsx("rect", { width: 8, height: 8, fill: "#e5e7eb" }),
|
|
182
|
+
/* @__PURE__ */ jsx("rect", { x: 8, y: 8, width: 8, height: 8, fill: "#e5e7eb" })
|
|
183
|
+
] }) }),
|
|
184
|
+
/* @__PURE__ */ jsx(VectorGradientDefs, { defs: scene.defs }),
|
|
185
|
+
/* @__PURE__ */ jsx(
|
|
186
|
+
"rect",
|
|
187
|
+
{
|
|
188
|
+
"data-vector-artboard": "",
|
|
189
|
+
x: overlay.artboard.x,
|
|
190
|
+
y: overlay.artboard.y,
|
|
191
|
+
width: overlay.artboard.width,
|
|
192
|
+
height: overlay.artboard.height,
|
|
193
|
+
fill: scene.background ?? (checkerboard ? `url(#ve${uid}-checker)` : "#ffffff"),
|
|
194
|
+
stroke: "rgba(0,0,0,0.12)"
|
|
195
|
+
}
|
|
196
|
+
),
|
|
197
|
+
/* @__PURE__ */ jsx("g", { "data-vector-scene": "", transform: scene.transform, children: /* @__PURE__ */ jsx(VectorSceneNodes, { nodes: scene.nodes }) }),
|
|
198
|
+
/* @__PURE__ */ jsxs("g", { "data-vector-overlay": "", pointerEvents: "none", fill: "none", children: [
|
|
199
|
+
overlay.outlines.map((o) => /* @__PURE__ */ jsx("path", { d: o.d, stroke: accentColor, strokeWidth: o.selected ? 1 : 1.5, opacity: o.selected ? 1 : 0.7 }, o.id)),
|
|
200
|
+
overlay.penPreview && /* @__PURE__ */ jsx("path", { d: overlay.penPreview, stroke: accentColor, strokeDasharray: "4 3" }),
|
|
201
|
+
box && /* @__PURE__ */ jsxs("g", { "data-vector-selection": "", transform: box.transform || void 0, children: [
|
|
202
|
+
/* @__PURE__ */ jsx("rect", { x: box.x, y: box.y, width: box.width, height: box.height, stroke: accentColor }),
|
|
203
|
+
/* @__PURE__ */ jsx("line", { x1: box.x + box.width / 2, y1: box.y, x2: box.x + box.width / 2, y2: box.y - 24, stroke: accentColor }),
|
|
204
|
+
box.handles.map(
|
|
205
|
+
(h) => h.handle === "rotate" ? /* @__PURE__ */ jsx("circle", { "data-handle": h.handle, cx: h.x, cy: h.y, r: hs / 2 + 1, fill: THEME.handleFill, stroke: accentColor }, h.handle) : /* @__PURE__ */ jsx("rect", { "data-handle": h.handle, x: h.x - hs / 2, y: h.y - hs / 2, width: hs, height: hs, fill: THEME.handleFill, stroke: accentColor }, h.handle)
|
|
206
|
+
)
|
|
207
|
+
] }),
|
|
208
|
+
overlay.handles.map((h) => /* @__PURE__ */ jsxs("g", { "data-vector-handle": h.ref.handle, children: [
|
|
209
|
+
/* @__PURE__ */ jsx("line", { x1: h.anchorX, y1: h.anchorY, x2: h.x, y2: h.y, stroke: accentColor }),
|
|
210
|
+
/* @__PURE__ */ jsx("circle", { cx: h.x, cy: h.y, r: THEME.handleRadius + (h.hovered ? 1 : 0), fill: THEME.handleFill, stroke: accentColor })
|
|
211
|
+
] }, `${h.ref.nodeId}:${h.ref.subpath}:${h.ref.index}:${h.ref.handle}`)),
|
|
212
|
+
overlay.anchors.map((a) => {
|
|
213
|
+
const size = as + (a.hovered ? 2 : 0);
|
|
214
|
+
const fill = a.selected ? accentColor : THEME.handleFill;
|
|
215
|
+
const key = `${a.ref.nodeId}:${a.ref.subpath}:${a.ref.index}`;
|
|
216
|
+
return a.type === "corner" ? /* @__PURE__ */ jsx("rect", { "data-vector-anchor": a.ref.index, x: a.x - size / 2, y: a.y - size / 2, width: size, height: size, fill, stroke: accentColor }, key) : /* @__PURE__ */ jsx("circle", { "data-vector-anchor": a.ref.index, cx: a.x, cy: a.y, r: size / 2, fill, stroke: accentColor }, key);
|
|
217
|
+
}),
|
|
218
|
+
state.tool === "pen" && overlay.anchors.some((a) => a.closes && a.hovered) && /* @__PURE__ */ jsx("circle", { cx: overlay.anchors.find((a) => a.closes).x, cy: overlay.anchors.find((a) => a.closes).y, r: as, stroke: accentColor }),
|
|
219
|
+
overlay.marquee && /* @__PURE__ */ jsx(
|
|
220
|
+
"rect",
|
|
221
|
+
{
|
|
222
|
+
"data-vector-marquee": "",
|
|
223
|
+
x: overlay.marquee.x,
|
|
224
|
+
y: overlay.marquee.y,
|
|
225
|
+
width: overlay.marquee.width,
|
|
226
|
+
height: overlay.marquee.height,
|
|
227
|
+
fill: THEME.marqueeFill,
|
|
228
|
+
stroke: accentColor,
|
|
229
|
+
strokeDasharray: "3 2"
|
|
230
|
+
}
|
|
231
|
+
),
|
|
232
|
+
overlay.status && /* @__PURE__ */ jsx("text", { "data-vector-status": "", x: 8, y: "100%", dy: -8, fontSize: 11, fill: accentColor, style: { fontFamily: "system-ui, sans-serif" }, children: overlay.status })
|
|
233
|
+
] })
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/vector/VectorToolbar.tsx
|
|
240
|
+
import {
|
|
241
|
+
VECTOR_TOOLS,
|
|
242
|
+
VECTOR_TOOL_GLYPHS,
|
|
243
|
+
VECTOR_TOOL_SHORTCUTS,
|
|
244
|
+
getVectorSelectionStyle,
|
|
245
|
+
vectorPaintToHex,
|
|
246
|
+
vectorToolLabel
|
|
247
|
+
} from "@pixerate/editor/vector";
|
|
248
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
249
|
+
var buttonClass = "inline-flex h-8 min-w-8 items-center justify-center rounded-md border border-border bg-background px-2 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
|
|
250
|
+
function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, showArrange = true, className }) {
|
|
251
|
+
const state = useVectorEditorState(controller);
|
|
252
|
+
const count = state.selection.length;
|
|
253
|
+
const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
|
|
254
|
+
const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx2("button", { type: "button", className: buttonClass, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
|
|
255
|
+
return /* @__PURE__ */ jsxs2("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
|
|
256
|
+
/* @__PURE__ */ jsx2("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx2(
|
|
257
|
+
"button",
|
|
258
|
+
{
|
|
259
|
+
type: "button",
|
|
260
|
+
"data-vector-tool": tool,
|
|
261
|
+
className: buttonClass,
|
|
262
|
+
"aria-pressed": state.tool === tool,
|
|
263
|
+
title: `${vectorToolLabel(tool)} (${VECTOR_TOOL_SHORTCUTS[tool]})`,
|
|
264
|
+
"aria-label": vectorToolLabel(tool),
|
|
265
|
+
onClick: () => controller.setTool(tool),
|
|
266
|
+
children: VECTOR_TOOL_GLYPHS[tool]
|
|
267
|
+
},
|
|
268
|
+
tool
|
|
269
|
+
)) }),
|
|
270
|
+
showHistory && /* @__PURE__ */ jsxs2("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
|
|
271
|
+
act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
|
|
272
|
+
act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
|
|
273
|
+
] }),
|
|
274
|
+
showArrange && /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
275
|
+
/* @__PURE__ */ jsxs2("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
|
|
276
|
+
act("\u222A", "Union", () => controller.booleanOp("union"), count < 2),
|
|
277
|
+
act("\u2212", "Subtract", () => controller.booleanOp("subtract"), count < 2),
|
|
278
|
+
act("\u2229", "Intersect", () => controller.booleanOp("intersect"), count < 2),
|
|
279
|
+
act("\u22BB", "Exclude", () => controller.booleanOp("exclude"), count < 2),
|
|
280
|
+
act("G", "Group", () => controller.group(), count < 1),
|
|
281
|
+
act("U", "Ungroup", () => controller.ungroup(), !groupSelected)
|
|
282
|
+
] }),
|
|
283
|
+
/* @__PURE__ */ jsxs2("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
|
|
284
|
+
act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
|
|
285
|
+
act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
|
|
286
|
+
act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
|
|
287
|
+
act("\u2194", "Align Center", () => controller.align("center"), count < 1),
|
|
288
|
+
act("\u21E5", "Align Right", () => controller.align("right"), count < 1),
|
|
289
|
+
act("\u2912", "Align Top", () => controller.align("top"), count < 1),
|
|
290
|
+
act("\u2195", "Align Middle", () => controller.align("middle"), count < 1),
|
|
291
|
+
act("\u2913", "Align Bottom", () => controller.align("bottom"), count < 1)
|
|
292
|
+
] })
|
|
293
|
+
] })
|
|
294
|
+
] });
|
|
295
|
+
}
|
|
296
|
+
function VectorStylePanel({ controller, className }) {
|
|
297
|
+
const state = useVectorEditorState(controller);
|
|
298
|
+
const style = getVectorSelectionStyle(state);
|
|
299
|
+
const source = style ?? { fill: state.defaults.fill ?? null, stroke: state.defaults.stroke ?? null, strokeWidth: state.defaults.strokeWidth ?? 1, opacity: 1 };
|
|
300
|
+
const apply = (patch) => {
|
|
301
|
+
if (style) controller.setStyle(patch);
|
|
302
|
+
else controller.setDefaults(patch);
|
|
303
|
+
};
|
|
304
|
+
const paintRow = (key, label) => {
|
|
305
|
+
const paint = source[key];
|
|
306
|
+
const hex = vectorPaintToHex(paint);
|
|
307
|
+
const on = paint !== null && paint !== "none";
|
|
308
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
|
|
309
|
+
/* @__PURE__ */ jsxs2("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
|
|
310
|
+
/* @__PURE__ */ jsx2("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
|
|
311
|
+
label
|
|
312
|
+
] }),
|
|
313
|
+
/* @__PURE__ */ jsx2(
|
|
314
|
+
"input",
|
|
315
|
+
{
|
|
316
|
+
type: "color",
|
|
317
|
+
"aria-label": `${label} colour`,
|
|
318
|
+
className: "h-7 w-10 cursor-pointer rounded border border-border bg-background disabled:opacity-40",
|
|
319
|
+
disabled: !on,
|
|
320
|
+
value: hex ?? "#000000",
|
|
321
|
+
onChange: (e) => apply({ [key]: e.target.value })
|
|
322
|
+
}
|
|
323
|
+
),
|
|
324
|
+
on && !hex && /* @__PURE__ */ jsx2("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
|
|
325
|
+
style?.mixed[key] && /* @__PURE__ */ jsx2("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
|
|
326
|
+
] });
|
|
327
|
+
};
|
|
328
|
+
return /* @__PURE__ */ jsxs2("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
|
|
329
|
+
/* @__PURE__ */ jsx2("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
|
|
330
|
+
paintRow("fill", "Fill"),
|
|
331
|
+
paintRow("stroke", "Stroke"),
|
|
332
|
+
/* @__PURE__ */ jsxs2("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
|
|
333
|
+
/* @__PURE__ */ jsx2("span", { className: "w-16", children: "Width" }),
|
|
334
|
+
/* @__PURE__ */ jsx2(
|
|
335
|
+
"input",
|
|
336
|
+
{
|
|
337
|
+
type: "number",
|
|
338
|
+
min: 0,
|
|
339
|
+
step: 0.5,
|
|
340
|
+
"aria-label": "Stroke width",
|
|
341
|
+
className: "h-7 w-20 rounded border border-border bg-background px-1.5 text-xs",
|
|
342
|
+
value: source.strokeWidth,
|
|
343
|
+
onChange: (e) => {
|
|
344
|
+
const n = parseFloat(e.target.value);
|
|
345
|
+
if (Number.isFinite(n) && n >= 0) apply({ strokeWidth: n });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
)
|
|
349
|
+
] }),
|
|
350
|
+
style && /* @__PURE__ */ jsxs2("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
|
|
351
|
+
/* @__PURE__ */ jsx2("span", { className: "w-16", children: "Opacity" }),
|
|
352
|
+
/* @__PURE__ */ jsx2(
|
|
353
|
+
"input",
|
|
354
|
+
{
|
|
355
|
+
type: "range",
|
|
356
|
+
min: 0,
|
|
357
|
+
max: 1,
|
|
358
|
+
step: 0.05,
|
|
359
|
+
"aria-label": "Opacity",
|
|
360
|
+
className: "h-4 flex-1 accent-primary",
|
|
361
|
+
value: source.opacity,
|
|
362
|
+
onChange: (e) => apply({ opacity: parseFloat(e.target.value) })
|
|
363
|
+
}
|
|
364
|
+
)
|
|
365
|
+
] })
|
|
366
|
+
] });
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// src/vector/VectorEditor.tsx
|
|
370
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
371
|
+
function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }) {
|
|
372
|
+
return /* @__PURE__ */ jsxs3("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
|
|
373
|
+
/* @__PURE__ */ jsx3(VectorToolbar, { controller, tools }),
|
|
374
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
|
|
375
|
+
/* @__PURE__ */ jsx3("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx3(VectorCanvas, { controller, ...canvasProps }) }),
|
|
376
|
+
showStylePanel && /* @__PURE__ */ jsx3(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
|
|
377
|
+
] })
|
|
378
|
+
] });
|
|
379
|
+
}
|
|
380
|
+
export {
|
|
381
|
+
VectorCanvas,
|
|
382
|
+
VectorEditor,
|
|
383
|
+
VectorGradientDefs,
|
|
384
|
+
VectorSceneNodes,
|
|
385
|
+
VectorStylePanel,
|
|
386
|
+
VectorToolbar,
|
|
387
|
+
useVectorCommands,
|
|
388
|
+
useVectorEditor,
|
|
389
|
+
useVectorEditorController,
|
|
390
|
+
useVectorEditorState
|
|
391
|
+
};
|
|
392
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/vector/useVectorEditor.ts","../../src/vector/VectorCanvas.tsx","../../src/vector/VectorToolbar.tsx","../../src/vector/VectorEditor.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport {\n VectorEditorController,\n createVectorCommands,\n type VectorCommandOptions,\n type VectorEditorOptions,\n type VectorEditorState,\n type VectorTool,\n} from '@pixerate/editor/vector';\n\n/** A `VectorEditorController` created once for the component's lifetime (destroyed on unmount). */\nexport function useVectorEditorController(options: VectorEditorOptions = {}): VectorEditorController {\n const ref = useRef<VectorEditorController | null>(null);\n if (!ref.current) ref.current = new VectorEditorController(options);\n const controller = ref.current;\n useEffect(() => () => controller.destroy(), [controller]);\n return controller;\n}\n\n/** The controller's state (re-renders on every change). */\nexport function useVectorEditorState(controller: VectorEditorController): VectorEditorState {\n return useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);\n}\n\nexport interface UseVectorEditorResult {\n controller: VectorEditorController;\n state: VectorEditorState;\n setTool: (tool: VectorTool) => void;\n undo: () => void;\n redo: () => void;\n deleteSelection: () => void;\n toSvg: () => string;\n}\n\n/**\n * Subscribes to a vector editor controller and exposes its state with bound actions. Pass\n * options instead of a controller to create one for the component's lifetime.\n */\nexport function useVectorEditor(controllerOrOptions: VectorEditorController | VectorEditorOptions = {}): UseVectorEditorResult {\n const own = useRef<VectorEditorController | null>(null);\n let controller: VectorEditorController;\n if (controllerOrOptions instanceof VectorEditorController) controller = controllerOrOptions;\n else {\n if (!own.current) own.current = new VectorEditorController(controllerOrOptions);\n controller = own.current;\n }\n const state = useVectorEditorState(controller);\n const actions = useMemo(\n () => ({\n setTool: (tool: VectorTool) => controller.setTool(tool),\n undo: () => void controller.undo(),\n redo: () => void controller.redo(),\n deleteSelection: () => void controller.deleteSelection(),\n toSvg: () => controller.toSvg(),\n }),\n [controller],\n );\n return { controller, state, ...actions };\n}\n\n/** Anything with `register(specs) → unregister` (a `CommandRegistry`). */\nexport interface VectorCommandTarget {\n register(specs: any): () => void;\n}\n\n/**\n * Registers the controller's palette / agent commands (`vector.addShape`, `vector.boolean`, …)\n * in `registry` while mounted, so `createCommandToolProvider` exposes them to agents.\n */\nexport function useVectorCommands(\n registry: VectorCommandTarget | null | undefined,\n controller: VectorEditorController,\n options: VectorCommandOptions = {},\n): void {\n const { prefix, group, scope } = options;\n useEffect(() => {\n if (!registry) return;\n return registry.register(createVectorCommands(controller, { prefix, group, scope }));\n }, [registry, controller, prefix, group, scope]);\n}\n","import React, { useEffect, useId, useRef } from 'react';\nimport {\n VECTOR_OVERLAY_THEME as THEME,\n getVectorOverlayRenderData,\n getVectorSceneRenderData,\n isVectorTextTarget,\n toVectorKeyInput,\n toVectorPointerInput,\n type VectorEditorController,\n type VectorRenderNode,\n type SvgGradientDef,\n} from '@pixerate/editor/vector';\nimport { useVectorEditorState } from './useVectorEditor';\n\nexport interface VectorCanvasProps {\n controller: VectorEditorController;\n /** Fit the artboard into the canvas when it first gets a size (default `true`). */\n fitOnMount?: boolean;\n /** Draw a checkerboard behind a transparent artboard (default `true`). */\n checkerboard?: boolean;\n /** Overlay accent colour (default sky blue). */\n accentColor?: string;\n className?: string;\n style?: React.CSSProperties;\n /** Accessible label (default \"Vector canvas\"). */\n 'aria-label'?: string;\n}\n\n/** Renders `<linearGradient>` / `<radialGradient>` descriptors. */\nexport function VectorGradientDefs({ defs }: { defs: readonly SvgGradientDef[] }) {\n if (!defs.length) return null;\n return (\n <defs>\n {defs.map((def) => {\n const stops = def.stops.map((s, i) => <stop key={i} offset={s.offset} stopColor={s.stopColor} stopOpacity={s.stopOpacity} />);\n return def.type === 'radial' ? (\n <radialGradient key={def.id} id={def.id} {...(def.attrs as any)}>\n {stops}\n </radialGradient>\n ) : (\n <linearGradient key={def.id} id={def.id} {...(def.attrs as any)}>\n {stops}\n </linearGradient>\n );\n })}\n </defs>\n );\n}\n\n/** Renders scene nodes (paths and groups) from `getVectorSceneRenderData`. */\nexport function VectorSceneNodes({ nodes }: { nodes: readonly VectorRenderNode[] }) {\n return (\n <>\n {nodes.map((node) =>\n node.kind === 'group' ? (\n <g key={node.id} data-vector-id={node.id} transform={node.transform} opacity={node.opacity < 1 ? node.opacity : undefined}>\n <VectorSceneNodes nodes={node.children} />\n </g>\n ) : (\n <path\n key={node.id}\n data-vector-id={node.id}\n transform={node.transform}\n d={node.d}\n fill={node.fill}\n fillRule={node.fillRule}\n stroke={node.stroke}\n strokeWidth={node.stroke === 'none' ? undefined : node.strokeWidth}\n strokeLinecap={node.strokeLinecap as any}\n strokeLinejoin={node.strokeLinejoin as any}\n strokeDasharray={node.strokeDasharray}\n opacity={node.opacity < 1 ? node.opacity : undefined}\n />\n ),\n )}\n </>\n );\n}\n\n/**\n * An SVG canvas for a `VectorEditorController`: draws the document and the editing overlay\n * and forwards pointer, wheel, keyboard and clipboard events. Focus it to use shortcuts.\n */\nexport function VectorCanvas({\n controller,\n fitOnMount = true,\n checkerboard = true,\n accentColor = THEME.accent,\n className,\n style,\n 'aria-label': ariaLabel = 'Vector canvas',\n}: VectorCanvasProps) {\n const state = useVectorEditorState(controller);\n const svgRef = useRef<SVGSVGElement | null>(null);\n const fitted = useRef(!fitOnMount);\n const uid = useId().replace(/:/g, '');\n const scene = getVectorSceneRenderData(state, { idPrefix: `ve${uid}-` });\n const overlay = getVectorOverlayRenderData(state);\n\n useEffect(() => {\n const el = svgRef.current;\n if (!el || fitted.current) return;\n const fit = () => {\n const rect = el.getBoundingClientRect();\n if (fitted.current || rect.width <= 0 || rect.height <= 0) return;\n fitted.current = true;\n controller.zoomToFit({ width: rect.width, height: rect.height });\n };\n fit();\n if (fitted.current || typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(fit);\n observer.observe(el);\n return () => observer.disconnect();\n }, [controller]);\n\n // Wheel needs a non-passive listener to stop page scroll / browser zoom.\n useEffect(() => {\n const el = svgRef.current;\n if (!el) return;\n const onWheel = (e: WheelEvent) => {\n e.preventDefault();\n controller.wheel(local(e), { x: e.deltaX, y: e.deltaY }, toVectorPointerInput(e));\n };\n el.addEventListener('wheel', onWheel, { passive: false });\n return () => el.removeEventListener('wheel', onWheel);\n }, [controller]);\n\n const local = (e: { clientX: number; clientY: number }) => {\n const rect = svgRef.current?.getBoundingClientRect();\n return { x: e.clientX - (rect?.left ?? 0), y: e.clientY - (rect?.top ?? 0) };\n };\n\n const clipboard = (kind: 'copy' | 'cut' | 'paste') => (event: React.ClipboardEvent) => {\n const native = (event && 'nativeEvent' in event ? event.nativeEvent : event) as ClipboardEvent;\n const data = native?.clipboardData;\n if (kind === 'paste') {\n const text = data?.getData('text/plain') ?? '';\n const pasted = text ? controller.paste(text) : controller.paste();\n if (pasted.length) event.preventDefault?.();\n return;\n }\n const text = kind === 'cut' ? controller.cut() : controller.copy();\n if (text && data) {\n data.setData('text/plain', text);\n event.preventDefault?.();\n }\n };\n\n const hs = THEME.transformHandleSize;\n const as = THEME.anchorSize;\n const box = overlay.selectionBox;\n\n return (\n <svg\n ref={svgRef}\n role=\"application\"\n aria-label={ariaLabel}\n tabIndex={0}\n data-tool={state.tool}\n className={className}\n style={{ display: 'block', width: '100%', height: '100%', outline: 'none', touchAction: 'none', userSelect: 'none', cursor: state.cursor, ...style }}\n onPointerDown={(e) => {\n svgRef.current?.focus({ preventScroll: true });\n (e.currentTarget as Element).setPointerCapture?.(e.pointerId);\n controller.pointerDown(local(e), toVectorPointerInput(e));\n }}\n onPointerMove={(e) => controller.pointerMove(local(e), toVectorPointerInput(e))}\n onPointerUp={(e) => {\n (e.currentTarget as Element).releasePointerCapture?.(e.pointerId);\n controller.pointerUp(local(e), toVectorPointerInput(e));\n }}\n onPointerCancel={() => controller.cancelDrag()}\n onPointerLeave={() => controller.pointerLeave()}\n onDoubleClick={(e) => controller.doubleClick(local(e))}\n onKeyDown={(e) => {\n if (isVectorTextTarget(e.target)) return;\n if (controller.keyDown(toVectorKeyInput(e))) e.preventDefault();\n }}\n onKeyUp={(e) => controller.keyUp(toVectorKeyInput(e))}\n onCopy={clipboard('copy')}\n onCut={clipboard('cut')}\n onPaste={clipboard('paste')}\n >\n {checkerboard && (\n <defs>\n <pattern id={`ve${uid}-checker`} width={16} height={16} patternUnits=\"userSpaceOnUse\">\n <rect width={16} height={16} fill=\"#ffffff\" />\n <rect width={8} height={8} fill=\"#e5e7eb\" />\n <rect x={8} y={8} width={8} height={8} fill=\"#e5e7eb\" />\n </pattern>\n </defs>\n )}\n <VectorGradientDefs defs={scene.defs} />\n <rect\n data-vector-artboard=\"\"\n x={overlay.artboard.x}\n y={overlay.artboard.y}\n width={overlay.artboard.width}\n height={overlay.artboard.height}\n fill={scene.background ?? (checkerboard ? `url(#ve${uid}-checker)` : '#ffffff')}\n stroke=\"rgba(0,0,0,0.12)\"\n />\n <g data-vector-scene=\"\" transform={scene.transform}>\n <VectorSceneNodes nodes={scene.nodes} />\n </g>\n <g data-vector-overlay=\"\" pointerEvents=\"none\" fill=\"none\">\n {overlay.outlines.map((o) => (\n <path key={o.id} d={o.d} stroke={accentColor} strokeWidth={o.selected ? 1 : 1.5} opacity={o.selected ? 1 : 0.7} />\n ))}\n {overlay.penPreview && <path d={overlay.penPreview} stroke={accentColor} strokeDasharray=\"4 3\" />}\n {box && (\n <g data-vector-selection=\"\" transform={box.transform || undefined}>\n <rect x={box.x} y={box.y} width={box.width} height={box.height} stroke={accentColor} />\n <line x1={box.x + box.width / 2} y1={box.y} x2={box.x + box.width / 2} y2={box.y - 24} stroke={accentColor} />\n {box.handles.map((h) =>\n h.handle === 'rotate' ? (\n <circle key={h.handle} data-handle={h.handle} cx={h.x} cy={h.y} r={hs / 2 + 1} fill={THEME.handleFill} stroke={accentColor} />\n ) : (\n <rect key={h.handle} data-handle={h.handle} x={h.x - hs / 2} y={h.y - hs / 2} width={hs} height={hs} fill={THEME.handleFill} stroke={accentColor} />\n ),\n )}\n </g>\n )}\n {overlay.handles.map((h) => (\n <g key={`${h.ref.nodeId}:${h.ref.subpath}:${h.ref.index}:${h.ref.handle}`} data-vector-handle={h.ref.handle}>\n <line x1={h.anchorX} y1={h.anchorY} x2={h.x} y2={h.y} stroke={accentColor} />\n <circle cx={h.x} cy={h.y} r={THEME.handleRadius + (h.hovered ? 1 : 0)} fill={THEME.handleFill} stroke={accentColor} />\n </g>\n ))}\n {overlay.anchors.map((a) => {\n const size = as + (a.hovered ? 2 : 0);\n const fill = a.selected ? accentColor : THEME.handleFill;\n const key = `${a.ref.nodeId}:${a.ref.subpath}:${a.ref.index}`;\n return a.type === 'corner' ? (\n <rect key={key} data-vector-anchor={a.ref.index} x={a.x - size / 2} y={a.y - size / 2} width={size} height={size} fill={fill} stroke={accentColor} />\n ) : (\n <circle key={key} data-vector-anchor={a.ref.index} cx={a.x} cy={a.y} r={size / 2} fill={fill} stroke={accentColor} />\n );\n })}\n {state.tool === 'pen' && overlay.anchors.some((a) => a.closes && a.hovered) && (\n <circle cx={overlay.anchors.find((a) => a.closes)!.x} cy={overlay.anchors.find((a) => a.closes)!.y} r={as} stroke={accentColor} />\n )}\n {overlay.marquee && (\n <rect\n data-vector-marquee=\"\"\n x={overlay.marquee.x}\n y={overlay.marquee.y}\n width={overlay.marquee.width}\n height={overlay.marquee.height}\n fill={THEME.marqueeFill}\n stroke={accentColor}\n strokeDasharray=\"3 2\"\n />\n )}\n {overlay.status && (\n <text data-vector-status=\"\" x={8} y=\"100%\" dy={-8} fontSize={11} fill={accentColor} style={{ fontFamily: 'system-ui, sans-serif' }}>\n {overlay.status}\n </text>\n )}\n </g>\n </svg>\n );\n}\n","import React from 'react';\nimport {\n VECTOR_TOOLS,\n VECTOR_TOOL_GLYPHS,\n VECTOR_TOOL_SHORTCUTS,\n getVectorSelectionStyle,\n vectorPaintToHex,\n vectorToolLabel,\n type VectorEditorController,\n type VectorTool,\n} from '@pixerate/editor/vector';\nimport { useVectorEditorState } from './useVectorEditor';\n\nconst buttonClass =\n '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';\n\nexport interface VectorToolbarProps {\n controller: VectorEditorController;\n /** Tools to show (default: all). */\n tools?: readonly VectorTool[];\n /** Undo / redo buttons (default `true`). */\n showHistory?: boolean;\n /** Boolean, group, arrange and align buttons (default `true`). */\n showArrange?: boolean;\n className?: string;\n}\n\n/** Tool picker plus undo, boolean operations, grouping, stacking and alignment. */\nexport function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, showArrange = true, className }: VectorToolbarProps) {\n const state = useVectorEditorState(controller);\n const count = state.selection.length;\n const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === 'group');\n const act = (label: string, title: string, run: () => unknown, disabled = false) => (\n <button key={label} type=\"button\" className={buttonClass} title={title} aria-label={title} disabled={disabled} onClick={() => void run()}>\n {label}\n </button>\n );\n return (\n <div role=\"toolbar\" aria-label=\"Vector tools\" className={`flex flex-wrap items-center gap-1 ${className ?? ''}`}>\n <div className=\"flex items-center gap-1\" role=\"group\" aria-label=\"Tools\">\n {tools.map((tool) => (\n <button\n key={tool}\n type=\"button\"\n data-vector-tool={tool}\n className={buttonClass}\n aria-pressed={state.tool === tool}\n title={`${vectorToolLabel(tool)} (${VECTOR_TOOL_SHORTCUTS[tool]})`}\n aria-label={vectorToolLabel(tool)}\n onClick={() => controller.setTool(tool)}\n >\n {VECTOR_TOOL_GLYPHS[tool]}\n </button>\n ))}\n </div>\n {showHistory && (\n <div className=\"ml-1 flex items-center gap-1\" role=\"group\" aria-label=\"History\">\n {act('↶', state.canUndo ? `Undo ${state.undoLabel}` : 'Undo', () => controller.undo(), !state.canUndo)}\n {act('↷', state.canRedo ? `Redo ${state.redoLabel}` : 'Redo', () => controller.redo(), !state.canRedo)}\n </div>\n )}\n {showArrange && (\n <>\n <div className=\"ml-1 flex items-center gap-1\" role=\"group\" aria-label=\"Combine\">\n {act('∪', 'Union', () => controller.booleanOp('union'), count < 2)}\n {act('−', 'Subtract', () => controller.booleanOp('subtract'), count < 2)}\n {act('∩', 'Intersect', () => controller.booleanOp('intersect'), count < 2)}\n {act('⊻', 'Exclude', () => controller.booleanOp('exclude'), count < 2)}\n {act('G', 'Group', () => controller.group(), count < 1)}\n {act('U', 'Ungroup', () => controller.ungroup(), !groupSelected)}\n </div>\n <div className=\"ml-1 flex items-center gap-1\" role=\"group\" aria-label=\"Arrange\">\n {act('⇈', 'Bring to Front', () => controller.arrange('front'), count < 1)}\n {act('⇊', 'Send to Back', () => controller.arrange('back'), count < 1)}\n {act('⇤', 'Align Left', () => controller.align('left'), count < 1)}\n {act('↔', 'Align Center', () => controller.align('center'), count < 1)}\n {act('⇥', 'Align Right', () => controller.align('right'), count < 1)}\n {act('⤒', 'Align Top', () => controller.align('top'), count < 1)}\n {act('↕', 'Align Middle', () => controller.align('middle'), count < 1)}\n {act('⤓', 'Align Bottom', () => controller.align('bottom'), count < 1)}\n </div>\n </>\n )}\n </div>\n );\n}\n\nexport interface VectorStylePanelProps {\n controller: VectorEditorController;\n className?: string;\n}\n\n/**\n * Fill, stroke, stroke width and opacity of the selection; with nothing selected it edits\n * the defaults for new shapes.\n */\nexport function VectorStylePanel({ controller, className }: VectorStylePanelProps) {\n const state = useVectorEditorState(controller);\n const style = getVectorSelectionStyle(state);\n const source = style ?? { fill: state.defaults.fill ?? null, stroke: state.defaults.stroke ?? null, strokeWidth: state.defaults.strokeWidth ?? 1, opacity: 1 };\n const apply = (patch: Record<string, unknown>) => {\n if (style) controller.setStyle(patch);\n else controller.setDefaults(patch);\n };\n const paintRow = (key: 'fill' | 'stroke', label: string) => {\n const paint = source[key];\n const hex = vectorPaintToHex(paint);\n const on = paint !== null && paint !== 'none';\n return (\n <div className=\"flex items-center gap-2\" data-vector-style={key}>\n <label className=\"flex w-16 items-center gap-1.5 text-xs text-foreground\">\n <input type=\"checkbox\" checked={on} aria-label={`${label} on`} onChange={(e) => apply({ [key]: e.target.checked ? (hex ?? '#000000') : null })} />\n {label}\n </label>\n <input\n type=\"color\"\n aria-label={`${label} colour`}\n className=\"h-7 w-10 cursor-pointer rounded border border-border bg-background disabled:opacity-40\"\n disabled={!on}\n value={hex ?? '#000000'}\n onChange={(e) => apply({ [key]: e.target.value })}\n />\n {on && !hex && <span className=\"truncate text-[11px] text-muted-foreground\">{typeof paint === 'string' ? paint : 'Gradient'}</span>}\n {style?.mixed[key] && <span className=\"text-[11px] text-muted-foreground\">Mixed</span>}\n </div>\n );\n };\n return (\n <div className={`space-y-2 ${className ?? ''}`} aria-label={style ? 'Selection style' : 'New shape style'}>\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-muted-foreground\">{style ? 'Selection' : 'New shapes'}</p>\n {paintRow('fill', 'Fill')}\n {paintRow('stroke', 'Stroke')}\n <label className=\"flex items-center gap-2 text-xs text-foreground\">\n <span className=\"w-16\">Width</span>\n <input\n type=\"number\"\n min={0}\n step={0.5}\n aria-label=\"Stroke width\"\n className=\"h-7 w-20 rounded border border-border bg-background px-1.5 text-xs\"\n value={source.strokeWidth}\n onChange={(e) => {\n const n = parseFloat(e.target.value);\n if (Number.isFinite(n) && n >= 0) apply({ strokeWidth: n });\n }}\n />\n </label>\n {style && (\n <label className=\"flex items-center gap-2 text-xs text-foreground\">\n <span className=\"w-16\">Opacity</span>\n <input\n type=\"range\"\n min={0}\n max={1}\n step={0.05}\n aria-label=\"Opacity\"\n className=\"h-4 flex-1 accent-primary\"\n value={source.opacity}\n onChange={(e) => apply({ opacity: parseFloat(e.target.value) })}\n />\n </label>\n )}\n </div>\n );\n}\n","import React from 'react';\nimport type { VectorEditorController, VectorTool } from '@pixerate/editor/vector';\nimport { VectorCanvas, type VectorCanvasProps } from './VectorCanvas';\nimport { VectorStylePanel, VectorToolbar } from './VectorToolbar';\n\nexport interface VectorEditorProps {\n controller: VectorEditorController;\n /** Tools in the toolbar (default: all). */\n tools?: readonly VectorTool[];\n /** The style sidebar (default `true`). */\n showStylePanel?: boolean;\n /** Canvas height (default 520px). */\n height?: number | string;\n canvasProps?: Omit<VectorCanvasProps, 'controller'>;\n className?: string;\n}\n\n/** Toolbar, canvas and style panel for a `VectorEditorController`. */\nexport function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }: VectorEditorProps) {\n return (\n <div className={`flex flex-col gap-2 ${className ?? ''}`} data-vector-editor=\"\">\n <VectorToolbar controller={controller} tools={tools} />\n <div className=\"flex min-h-0 flex-col gap-2 md:flex-row\">\n <div className=\"relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40\" style={{ height }}>\n <VectorCanvas controller={controller} {...canvasProps} />\n </div>\n {showStylePanel && <VectorStylePanel controller={controller} className=\"w-full shrink-0 rounded-md border border-border p-3 md:w-56\" />}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["useEffect","useRef","useRef","useEffect","text","Fragment","jsx","jsxs","jsx","jsxs"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixerate/editor-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.1",
|
|
4
4
|
"description": "React bindings and prompt/rich-text components for @pixerate/editor",
|
|
5
5
|
"author": "Pixerate",
|
|
6
6
|
"license": "MIT",
|
|
@@ -58,6 +58,11 @@
|
|
|
58
58
|
"types": "./dist/parameters/index.d.ts",
|
|
59
59
|
"import": "./dist/parameters/index.js",
|
|
60
60
|
"require": "./dist/parameters/index.cjs"
|
|
61
|
+
},
|
|
62
|
+
"./vector": {
|
|
63
|
+
"types": "./dist/vector/index.d.ts",
|
|
64
|
+
"import": "./dist/vector/index.js",
|
|
65
|
+
"require": "./dist/vector/index.cjs"
|
|
61
66
|
}
|
|
62
67
|
},
|
|
63
68
|
"typesVersions": {
|
|
@@ -85,6 +90,9 @@
|
|
|
85
90
|
],
|
|
86
91
|
"parameters": [
|
|
87
92
|
"./dist/parameters/index.d.ts"
|
|
93
|
+
],
|
|
94
|
+
"vector": [
|
|
95
|
+
"./dist/vector/index.d.ts"
|
|
88
96
|
]
|
|
89
97
|
}
|
|
90
98
|
},
|
|
@@ -99,7 +107,7 @@
|
|
|
99
107
|
},
|
|
100
108
|
"peerDependencies": {
|
|
101
109
|
"@dagrejs/dagre": "^1.1.8",
|
|
102
|
-
"@pixerate/editor": ">=0.
|
|
110
|
+
"@pixerate/editor": ">=0.33.0 <1.0.0-0",
|
|
103
111
|
"@tiptap/core": "^2.11.5",
|
|
104
112
|
"@tiptap/pm": "^2.11.5",
|
|
105
113
|
"@tiptap/react": "^2.11.5",
|
|
@@ -126,7 +134,7 @@
|
|
|
126
134
|
"tsup": "^8.4.0",
|
|
127
135
|
"typescript": "^5.7.3",
|
|
128
136
|
"vitest": "^4.1.11",
|
|
129
|
-
"@pixerate/editor": "0.
|
|
137
|
+
"@pixerate/editor": "0.33.0"
|
|
130
138
|
},
|
|
131
139
|
"scripts": {
|
|
132
140
|
"build": "tsup",
|