@deckedout/visual-editor 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,3992 @@
1
+ "use client"
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+
23
+ // src/utils/editorUtils.ts
24
+ var editorUtils_exports = {};
25
+ __export(editorUtils_exports, {
26
+ bringToFront: () => bringToFront,
27
+ checkOverlap: () => checkOverlap,
28
+ clamp: () => clamp,
29
+ cloneElement: () => cloneElement,
30
+ constrainToCanvas: () => constrainToCanvas,
31
+ createElement: () => createElement,
32
+ degToRad: () => degToRad,
33
+ distance: () => distance,
34
+ duplicateElement: () => duplicateElement,
35
+ exportToJSON: () => exportToJSON,
36
+ generateElementId: () => generateElementId,
37
+ getElementCenter: () => getElementCenter,
38
+ getMaxZIndex: () => getMaxZIndex,
39
+ getRotatedBoundingBox: () => getRotatedBoundingBox,
40
+ importFromJSON: () => importFromJSON,
41
+ isValidCanvasExport: () => isValidCanvasExport,
42
+ isValidElement: () => isValidElement,
43
+ pointInRect: () => pointInRect,
44
+ radToDeg: () => radToDeg,
45
+ sendToBack: () => sendToBack,
46
+ snapPositionToGrid: () => snapPositionToGrid,
47
+ snapToGrid: () => snapToGrid,
48
+ sortByZIndex: () => sortByZIndex
49
+ });
50
+ import { v4 as uuidv4 } from "uuid";
51
+ var generateElementId, createElement, cloneElement, duplicateElement, sortByZIndex, getMaxZIndex, bringToFront, sendToBack, checkOverlap, pointInRect, snapToGrid, snapPositionToGrid, clamp, constrainToCanvas, getRotatedBoundingBox, exportToJSON, importFromJSON, getElementCenter, distance, degToRad, radToDeg, isValidElement, isValidCanvasExport;
52
+ var init_editorUtils = __esm({
53
+ "src/utils/editorUtils.ts"() {
54
+ "use strict";
55
+ generateElementId = () => {
56
+ return `element-${uuidv4()}`;
57
+ };
58
+ createElement = (type, props, options) => {
59
+ return {
60
+ id: generateElementId(),
61
+ type,
62
+ position: options?.position || { x: 0, y: 0 },
63
+ size: options?.size || { width: 100, height: 100 },
64
+ rotation: options?.rotation || 0,
65
+ opacity: options?.opacity ?? 1,
66
+ zIndex: options?.zIndex || 0,
67
+ visible: options?.visible ?? true,
68
+ locked: options?.locked ?? false,
69
+ displayName: options?.displayName,
70
+ props
71
+ };
72
+ };
73
+ cloneElement = (element) => {
74
+ return {
75
+ ...element,
76
+ id: generateElementId(),
77
+ // New ID for the clone
78
+ props: { ...element.props },
79
+ position: { ...element.position },
80
+ size: { ...element.size }
81
+ };
82
+ };
83
+ duplicateElement = (element, offset = { x: 20, y: 20 }) => {
84
+ const cloned = cloneElement(element);
85
+ return {
86
+ ...cloned,
87
+ position: {
88
+ x: element.position.x + offset.x,
89
+ y: element.position.y + offset.y
90
+ },
91
+ zIndex: element.zIndex + 1
92
+ // Place on top
93
+ };
94
+ };
95
+ sortByZIndex = (elements) => {
96
+ return [...elements].sort((a, b) => a.zIndex - b.zIndex);
97
+ };
98
+ getMaxZIndex = (elements) => {
99
+ if (elements.length === 0) return 0;
100
+ return Math.max(...elements.map((el) => el.zIndex));
101
+ };
102
+ bringToFront = (elements, elementId) => {
103
+ const maxZ = getMaxZIndex(elements);
104
+ return elements.map((el) => el.id === elementId ? { ...el, zIndex: maxZ + 1 } : el);
105
+ };
106
+ sendToBack = (elements, elementId) => {
107
+ const minZ = Math.min(...elements.map((el) => el.zIndex));
108
+ return elements.map((el) => el.id === elementId ? { ...el, zIndex: minZ - 1 } : el);
109
+ };
110
+ checkOverlap = (rect1, rect2) => {
111
+ return !(rect1.x + rect1.width < rect2.x || rect2.x + rect2.width < rect1.x || rect1.y + rect1.height < rect2.y || rect2.y + rect2.height < rect1.y);
112
+ };
113
+ pointInRect = (point, rect) => {
114
+ return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
115
+ };
116
+ snapToGrid = (value, gridSize) => {
117
+ return Math.round(value / gridSize) * gridSize;
118
+ };
119
+ snapPositionToGrid = (position, gridSize) => {
120
+ return {
121
+ x: snapToGrid(position.x, gridSize),
122
+ y: snapToGrid(position.y, gridSize)
123
+ };
124
+ };
125
+ clamp = (value, min, max) => {
126
+ return Math.min(Math.max(value, min), max);
127
+ };
128
+ constrainToCanvas = (position, size, canvasSize) => {
129
+ return {
130
+ x: clamp(position.x, 0, canvasSize.width - size.width),
131
+ y: clamp(position.y, 0, canvasSize.height - size.height)
132
+ };
133
+ };
134
+ getRotatedBoundingBox = (x, y, width, height, rotation) => {
135
+ const rad = rotation * Math.PI / 180;
136
+ const cos = Math.abs(Math.cos(rad));
137
+ const sin = Math.abs(Math.sin(rad));
138
+ const newWidth = width * cos + height * sin;
139
+ const newHeight = width * sin + height * cos;
140
+ return {
141
+ x: x - (newWidth - width) / 2,
142
+ y: y - (newHeight - height) / 2,
143
+ width: newWidth,
144
+ height: newHeight
145
+ };
146
+ };
147
+ exportToJSON = (data) => {
148
+ return JSON.stringify(data, null, 2);
149
+ };
150
+ importFromJSON = (json) => {
151
+ try {
152
+ const data = JSON.parse(json);
153
+ if (!data.width || !data.height || !Array.isArray(data.elements)) {
154
+ throw new Error("Invalid canvas data structure");
155
+ }
156
+ const normalizedElements = data.elements.map((element) => ({
157
+ ...element,
158
+ visible: element.visible ?? true,
159
+ locked: element.locked ?? false
160
+ }));
161
+ return {
162
+ ...data,
163
+ elements: normalizedElements
164
+ };
165
+ } catch (error) {
166
+ throw new Error(`Failed to parse canvas data: ${error.message}`);
167
+ }
168
+ };
169
+ getElementCenter = (element) => {
170
+ return {
171
+ x: element.position.x + element.size.width / 2,
172
+ y: element.position.y + element.size.height / 2
173
+ };
174
+ };
175
+ distance = (p1, p2) => {
176
+ const dx = p2.x - p1.x;
177
+ const dy = p2.y - p1.y;
178
+ return Math.sqrt(dx * dx + dy * dy);
179
+ };
180
+ degToRad = (degrees) => {
181
+ return degrees * Math.PI / 180;
182
+ };
183
+ radToDeg = (radians) => {
184
+ return radians * 180 / Math.PI;
185
+ };
186
+ isValidElement = (element) => {
187
+ return element && typeof element.id === "string" && typeof element.type === "string" && element.position && typeof element.position.x === "number" && typeof element.position.y === "number" && element.size && typeof element.size.width === "number" && typeof element.size.height === "number" && typeof element.rotation === "number" && typeof element.zIndex === "number" && element.props !== void 0;
188
+ };
189
+ isValidCanvasExport = (data) => {
190
+ return data && typeof data.width === "number" && typeof data.height === "number" && Array.isArray(data.elements) && data.elements.every(isValidElement);
191
+ };
192
+ }
193
+ });
194
+
195
+ // src/core/VisualEditor.tsx
196
+ import { useEffect, useCallback as useCallback2, useRef } from "react";
197
+ import { Stage, Layer } from "react-konva";
198
+
199
+ // src/core/useEditorState.ts
200
+ import { useReducer, useCallback, useMemo } from "react";
201
+ var createInitialState = (mode) => ({
202
+ elements: [],
203
+ selectedElementId: null,
204
+ canvasSize: mode?.defaultCanvasSize || { width: 800, height: 600 },
205
+ zoom: 1,
206
+ pan: { x: 0, y: 0 },
207
+ mode,
208
+ history: {
209
+ past: [],
210
+ future: []
211
+ }
212
+ });
213
+ var editorReducer = (state, action) => {
214
+ switch (action.type) {
215
+ case "ADD_ELEMENT": {
216
+ const normalizedElement = {
217
+ ...action.element,
218
+ visible: action.element.visible ?? true,
219
+ locked: action.element.locked ?? false
220
+ };
221
+ const newElements = [...state.elements, normalizedElement];
222
+ return {
223
+ ...state,
224
+ elements: newElements,
225
+ selectedElementId: normalizedElement.id,
226
+ history: {
227
+ past: [...state.history.past, state.elements],
228
+ future: []
229
+ }
230
+ };
231
+ }
232
+ case "UPDATE_ELEMENT": {
233
+ const newElements = state.elements.map(
234
+ (el) => el.id === action.id ? { ...el, ...action.updates } : el
235
+ );
236
+ return {
237
+ ...state,
238
+ elements: newElements,
239
+ history: {
240
+ past: [...state.history.past, state.elements],
241
+ future: []
242
+ }
243
+ };
244
+ }
245
+ case "REMOVE_ELEMENT": {
246
+ const newElements = state.elements.filter((el) => el.id !== action.id);
247
+ return {
248
+ ...state,
249
+ elements: newElements,
250
+ selectedElementId: state.selectedElementId === action.id ? null : state.selectedElementId,
251
+ history: {
252
+ past: [...state.history.past, state.elements],
253
+ future: []
254
+ }
255
+ };
256
+ }
257
+ case "SELECT_ELEMENT":
258
+ return {
259
+ ...state,
260
+ selectedElementId: action.id
261
+ };
262
+ case "SET_ELEMENTS":
263
+ return {
264
+ ...state,
265
+ elements: action.elements,
266
+ history: {
267
+ past: [...state.history.past, state.elements],
268
+ future: []
269
+ }
270
+ };
271
+ case "LOAD_ELEMENTS":
272
+ return {
273
+ ...state,
274
+ elements: action.elements.map((el) => ({
275
+ ...el,
276
+ visible: el.visible ?? true,
277
+ locked: el.locked ?? false
278
+ }))
279
+ };
280
+ case "REORDER_ELEMENT": {
281
+ const currentIndex = state.elements.findIndex((el) => el.id === action.elementId);
282
+ if (currentIndex === -1 || currentIndex === action.newIndex) return state;
283
+ const newElements = [...state.elements];
284
+ const [element] = newElements.splice(currentIndex, 1);
285
+ newElements.splice(action.newIndex, 0, element);
286
+ newElements.forEach((el, index) => {
287
+ el.zIndex = index;
288
+ });
289
+ return {
290
+ ...state,
291
+ elements: newElements,
292
+ history: {
293
+ past: [...state.history.past, state.elements],
294
+ future: []
295
+ }
296
+ };
297
+ }
298
+ case "SET_CANVAS_SIZE":
299
+ return {
300
+ ...state,
301
+ canvasSize: {
302
+ width: action.width,
303
+ height: action.height
304
+ }
305
+ };
306
+ case "SET_ZOOM":
307
+ return {
308
+ ...state,
309
+ zoom: action.zoom
310
+ };
311
+ case "SET_PAN":
312
+ return {
313
+ ...state,
314
+ pan: {
315
+ x: action.x,
316
+ y: action.y
317
+ }
318
+ };
319
+ case "SET_MODE":
320
+ return {
321
+ ...state,
322
+ mode: action.mode,
323
+ canvasSize: action.mode.defaultCanvasSize
324
+ };
325
+ case "CLEAR":
326
+ return {
327
+ ...state,
328
+ elements: [],
329
+ selectedElementId: null,
330
+ history: {
331
+ past: [...state.history.past, state.elements],
332
+ future: []
333
+ }
334
+ };
335
+ case "UNDO": {
336
+ if (state.history.past.length === 0) return state;
337
+ const previous = state.history.past[state.history.past.length - 1];
338
+ const newPast = state.history.past.slice(0, -1);
339
+ return {
340
+ ...state,
341
+ elements: previous,
342
+ history: {
343
+ past: newPast,
344
+ future: [state.elements, ...state.history.future]
345
+ }
346
+ };
347
+ }
348
+ case "REDO": {
349
+ if (state.history.future.length === 0) return state;
350
+ const next = state.history.future[0];
351
+ const newFuture = state.history.future.slice(1);
352
+ return {
353
+ ...state,
354
+ elements: next,
355
+ history: {
356
+ past: [...state.history.past, state.elements],
357
+ future: newFuture
358
+ }
359
+ };
360
+ }
361
+ default:
362
+ return state;
363
+ }
364
+ };
365
+ var useEditorState = (initialMode = null) => {
366
+ const [state, dispatch] = useReducer(editorReducer, createInitialState(initialMode));
367
+ const addElement = useCallback((element) => {
368
+ dispatch({ type: "ADD_ELEMENT", element });
369
+ }, []);
370
+ const updateElement = useCallback((id, updates) => {
371
+ dispatch({ type: "UPDATE_ELEMENT", id, updates });
372
+ }, []);
373
+ const removeElement = useCallback((id) => {
374
+ dispatch({ type: "REMOVE_ELEMENT", id });
375
+ }, []);
376
+ const selectElement = useCallback((id) => {
377
+ dispatch({ type: "SELECT_ELEMENT", id });
378
+ }, []);
379
+ const getSelectedElement = useCallback(() => {
380
+ if (!state.selectedElementId) return null;
381
+ return state.elements.find((el) => el.id === state.selectedElementId) || null;
382
+ }, [state.selectedElementId, state.elements]);
383
+ const getAllElements = useCallback(() => {
384
+ return state.elements;
385
+ }, [state.elements]);
386
+ const moveElement = useCallback(
387
+ (id, deltaX, deltaY) => {
388
+ const element = state.elements.find((el) => el.id === id);
389
+ if (!element) return;
390
+ updateElement(id, {
391
+ position: {
392
+ x: element.position.x + deltaX,
393
+ y: element.position.y + deltaY
394
+ }
395
+ });
396
+ },
397
+ [state.elements, updateElement]
398
+ );
399
+ const rotateElement = useCallback(
400
+ (id, angle) => {
401
+ updateElement(id, { rotation: angle });
402
+ },
403
+ [updateElement]
404
+ );
405
+ const resizeElement = useCallback(
406
+ (id, width, height) => {
407
+ updateElement(id, {
408
+ size: { width, height }
409
+ });
410
+ },
411
+ [updateElement]
412
+ );
413
+ const updateZIndex = useCallback(
414
+ (id, zIndex) => {
415
+ updateElement(id, { zIndex });
416
+ },
417
+ [updateElement]
418
+ );
419
+ const reorderElement = useCallback((id, newIndex) => {
420
+ dispatch({ type: "REORDER_ELEMENT", elementId: id, newIndex });
421
+ }, []);
422
+ const exportJSON = useCallback(() => {
423
+ return {
424
+ width: state.canvasSize.width,
425
+ height: state.canvasSize.height,
426
+ elements: state.elements,
427
+ metadata: {
428
+ version: "1.0.0",
429
+ mode: state.mode?.name,
430
+ created: (/* @__PURE__ */ new Date()).toISOString()
431
+ }
432
+ };
433
+ }, [state.canvasSize, state.elements, state.mode]);
434
+ const importJSON = useCallback((data) => {
435
+ dispatch({
436
+ type: "SET_CANVAS_SIZE",
437
+ width: data.width,
438
+ height: data.height
439
+ });
440
+ dispatch({ type: "SET_ELEMENTS", elements: data.elements });
441
+ }, []);
442
+ const clear = useCallback(() => {
443
+ dispatch({ type: "CLEAR" });
444
+ }, []);
445
+ const setCanvasSize = useCallback((width, height) => {
446
+ dispatch({ type: "SET_CANVAS_SIZE", width, height });
447
+ }, []);
448
+ const setMode = useCallback((mode) => {
449
+ dispatch({ type: "SET_MODE", mode });
450
+ }, []);
451
+ const undo = useCallback(() => {
452
+ dispatch({ type: "UNDO" });
453
+ }, []);
454
+ const redo = useCallback(() => {
455
+ dispatch({ type: "REDO" });
456
+ }, []);
457
+ const copyElement = useCallback(
458
+ (id = null) => {
459
+ const elementToCopy = id ? state.elements.find((el) => el.id === id) : getSelectedElement();
460
+ if (!elementToCopy) return null;
461
+ return {
462
+ ...elementToCopy,
463
+ props: { ...elementToCopy.props },
464
+ position: { ...elementToCopy.position },
465
+ size: { ...elementToCopy.size }
466
+ };
467
+ },
468
+ [state.elements, getSelectedElement]
469
+ );
470
+ const duplicateElement2 = useCallback(
471
+ (id = null, offset = { x: 20, y: 20 }) => {
472
+ const elementToDuplicate = id ? state.elements.find((el) => el.id === id) : getSelectedElement();
473
+ if (!elementToDuplicate) return;
474
+ const { duplicateElement: duplicateUtil } = (init_editorUtils(), __toCommonJS(editorUtils_exports));
475
+ const duplicated = duplicateUtil(elementToDuplicate, offset);
476
+ addElement(duplicated);
477
+ },
478
+ [state.elements, getSelectedElement, addElement]
479
+ );
480
+ const pasteElement = useCallback(
481
+ (copiedElement, offset = { x: 20, y: 20 }) => {
482
+ if (!copiedElement) return;
483
+ const { generateElementId: generateElementId2 } = (init_editorUtils(), __toCommonJS(editorUtils_exports));
484
+ const pasted = {
485
+ ...copiedElement,
486
+ id: generateElementId2(),
487
+ props: { ...copiedElement.props },
488
+ position: {
489
+ x: copiedElement.position.x + offset.x,
490
+ y: copiedElement.position.y + offset.y
491
+ },
492
+ size: { ...copiedElement.size },
493
+ zIndex: Math.max(...state.elements.map((el) => el.zIndex), 0) + 1
494
+ };
495
+ addElement(pasted);
496
+ },
497
+ [state.elements, addElement]
498
+ );
499
+ const clearHistory = useCallback(() => {
500
+ state.history.past = [];
501
+ state.history.future = [];
502
+ }, []);
503
+ const loadElements = useCallback((elements) => {
504
+ dispatch({ type: "LOAD_ELEMENTS", elements });
505
+ }, []);
506
+ const api = useMemo(
507
+ () => ({
508
+ addElement,
509
+ updateElement,
510
+ removeElement,
511
+ selectElement,
512
+ getSelectedElement,
513
+ getAllElements,
514
+ moveElement,
515
+ rotateElement,
516
+ resizeElement,
517
+ updateZIndex,
518
+ reorderElement,
519
+ exportJSON,
520
+ importJSON,
521
+ clear,
522
+ copyElement,
523
+ duplicateElement: duplicateElement2,
524
+ pasteElement,
525
+ clearHistory,
526
+ loadElements
527
+ }),
528
+ [
529
+ addElement,
530
+ updateElement,
531
+ removeElement,
532
+ selectElement,
533
+ getSelectedElement,
534
+ getAllElements,
535
+ moveElement,
536
+ rotateElement,
537
+ resizeElement,
538
+ updateZIndex,
539
+ reorderElement,
540
+ exportJSON,
541
+ importJSON,
542
+ clear,
543
+ copyElement,
544
+ duplicateElement2,
545
+ pasteElement,
546
+ clearHistory,
547
+ loadElements
548
+ ]
549
+ );
550
+ return {
551
+ state,
552
+ api,
553
+ // Additional helpers
554
+ setCanvasSize,
555
+ setMode,
556
+ undo,
557
+ redo,
558
+ canUndo: state.history.past.length > 0,
559
+ canRedo: state.history.future.length > 0
560
+ };
561
+ };
562
+
563
+ // src/core/ElementRegistry.ts
564
+ import { useMemo as useMemo2 } from "react";
565
+ var ElementRegistry = class {
566
+ constructor() {
567
+ this.renderers = /* @__PURE__ */ new Map();
568
+ }
569
+ /**
570
+ * Register a new element renderer
571
+ */
572
+ register(renderer) {
573
+ if (this.renderers.has(renderer.type)) {
574
+ console.warn(
575
+ `Element renderer with type "${renderer.type}" is already registered. Skipping.`
576
+ );
577
+ return;
578
+ }
579
+ this.renderers.set(renderer.type, renderer);
580
+ }
581
+ /**
582
+ * Register multiple element renderers at once
583
+ */
584
+ registerMany(renderers) {
585
+ renderers.forEach((renderer) => this.register(renderer));
586
+ }
587
+ /**
588
+ * Get a renderer by type
589
+ */
590
+ get(type) {
591
+ return this.renderers.get(type);
592
+ }
593
+ /**
594
+ * Check if a renderer exists for a type
595
+ */
596
+ has(type) {
597
+ return this.renderers.has(type);
598
+ }
599
+ /**
600
+ * Get all registered renderers
601
+ */
602
+ getAll() {
603
+ return Array.from(this.renderers.values());
604
+ }
605
+ /**
606
+ * Get the internal renderers map
607
+ */
608
+ getMap() {
609
+ return this.renderers;
610
+ }
611
+ /**
612
+ * Get all registered types
613
+ */
614
+ getAllTypes() {
615
+ return Array.from(this.renderers.keys());
616
+ }
617
+ /**
618
+ * Unregister a renderer
619
+ */
620
+ unregister(type) {
621
+ return this.renderers.delete(type);
622
+ }
623
+ /**
624
+ * Clear all registered renderers
625
+ */
626
+ clear() {
627
+ this.renderers.clear();
628
+ }
629
+ /**
630
+ * Get the number of registered renderers
631
+ */
632
+ get size() {
633
+ return this.renderers.size;
634
+ }
635
+ };
636
+ var globalElementRegistry = new ElementRegistry();
637
+ var useElementRegistry = (initialRenderers) => {
638
+ return useMemo2(() => {
639
+ const registry = new ElementRegistry();
640
+ if (initialRenderers) {
641
+ registry.registerMany(initialRenderers);
642
+ }
643
+ return registry;
644
+ }, [initialRenderers]);
645
+ };
646
+
647
+ // src/elements/TextElement.tsx
648
+ import React from "react";
649
+ import { Text } from "react-konva";
650
+ import { Type } from "lucide-react";
651
+
652
+ // src/utils/snapping.ts
653
+ function getRotatedBounds(x, y, width, height, rotation) {
654
+ if (Math.abs(rotation) < 0.1 || Math.abs(rotation - 360) < 0.1) {
655
+ return {
656
+ left: x,
657
+ right: x + width,
658
+ top: y,
659
+ bottom: y + height,
660
+ centerX: x + width / 2,
661
+ centerY: y + height / 2
662
+ };
663
+ }
664
+ const rad = rotation * Math.PI / 180;
665
+ const cos = Math.cos(rad);
666
+ const sin = Math.sin(rad);
667
+ const corners = [
668
+ { x: 0, y: 0 },
669
+ // top-left (rotation origin)
670
+ { x: width, y: 0 },
671
+ // top-right
672
+ { x: width, y: height },
673
+ // bottom-right
674
+ { x: 0, y: height }
675
+ // bottom-left
676
+ ];
677
+ let minX = Infinity;
678
+ let maxX = -Infinity;
679
+ let minY = Infinity;
680
+ let maxY = -Infinity;
681
+ for (const corner of corners) {
682
+ const rotatedX = x + corner.x * cos - corner.y * sin;
683
+ const rotatedY = y + corner.x * sin + corner.y * cos;
684
+ minX = Math.min(minX, rotatedX);
685
+ maxX = Math.max(maxX, rotatedX);
686
+ minY = Math.min(minY, rotatedY);
687
+ maxY = Math.max(maxY, rotatedY);
688
+ }
689
+ const centerX = x + width / 2 * cos - height / 2 * sin;
690
+ const centerY = y + width / 2 * sin + height / 2 * cos;
691
+ return {
692
+ left: minX,
693
+ right: maxX,
694
+ top: minY,
695
+ bottom: maxY,
696
+ centerX,
697
+ centerY
698
+ };
699
+ }
700
+ function getSnappingPosition(draggedElement, x, y, allElements, options = {}) {
701
+ const { threshold = 5, snapToElements = true, snapToCanvas = true, canvasSize } = options;
702
+ let snappedX = x;
703
+ let snappedY = y;
704
+ const verticalGuides = [];
705
+ const horizontalGuides = [];
706
+ const draggedBounds = getRotatedBounds(
707
+ x,
708
+ y,
709
+ draggedElement.size.width,
710
+ draggedElement.size.height,
711
+ draggedElement.rotation
712
+ );
713
+ const draggedLeft = draggedBounds.left;
714
+ const draggedRight = draggedBounds.right;
715
+ const draggedTop = draggedBounds.top;
716
+ const draggedBottom = draggedBounds.bottom;
717
+ const draggedCenterX = draggedBounds.centerX;
718
+ const draggedCenterY = draggedBounds.centerY;
719
+ const isRotated = Math.abs(draggedElement.rotation) > 0.1 && Math.abs(draggedElement.rotation - 360) > 0.1;
720
+ let closestVerticalSnap = threshold;
721
+ let closestHorizontalSnap = threshold;
722
+ let verticalOffset = 0;
723
+ let horizontalOffset = 0;
724
+ if (snapToCanvas && canvasSize) {
725
+ if (Math.abs(draggedLeft) <= closestVerticalSnap) {
726
+ verticalOffset = -draggedLeft;
727
+ closestVerticalSnap = Math.abs(draggedLeft);
728
+ verticalGuides.push({ position: 0, orientation: "vertical", type: "canvas" });
729
+ }
730
+ if (Math.abs(draggedRight - canvasSize.width) <= closestVerticalSnap) {
731
+ verticalOffset = canvasSize.width - draggedRight;
732
+ closestVerticalSnap = Math.abs(draggedRight - canvasSize.width);
733
+ verticalGuides.push({
734
+ position: canvasSize.width,
735
+ orientation: "vertical",
736
+ type: "canvas"
737
+ });
738
+ }
739
+ if (Math.abs(draggedTop) <= closestHorizontalSnap) {
740
+ horizontalOffset = -draggedTop;
741
+ closestHorizontalSnap = Math.abs(draggedTop);
742
+ horizontalGuides.push({ position: 0, orientation: "horizontal", type: "canvas" });
743
+ }
744
+ if (Math.abs(draggedBottom - canvasSize.height) <= closestHorizontalSnap) {
745
+ horizontalOffset = canvasSize.height - draggedBottom;
746
+ closestHorizontalSnap = Math.abs(draggedBottom - canvasSize.height);
747
+ horizontalGuides.push({
748
+ position: canvasSize.height,
749
+ orientation: "horizontal",
750
+ type: "canvas"
751
+ });
752
+ }
753
+ const canvasCenterX = canvasSize.width / 2;
754
+ if (Math.abs(draggedCenterX - canvasCenterX) <= closestVerticalSnap) {
755
+ verticalOffset = canvasCenterX - draggedCenterX;
756
+ closestVerticalSnap = Math.abs(draggedCenterX - canvasCenterX);
757
+ verticalGuides.length = 0;
758
+ verticalGuides.push({
759
+ position: canvasCenterX,
760
+ orientation: "vertical",
761
+ type: "center"
762
+ });
763
+ }
764
+ const canvasCenterY = canvasSize.height / 2;
765
+ if (Math.abs(draggedCenterY - canvasCenterY) <= closestHorizontalSnap) {
766
+ horizontalOffset = canvasCenterY - draggedCenterY;
767
+ closestHorizontalSnap = Math.abs(draggedCenterY - canvasCenterY);
768
+ horizontalGuides.length = 0;
769
+ horizontalGuides.push({
770
+ position: canvasCenterY,
771
+ orientation: "horizontal",
772
+ type: "center"
773
+ });
774
+ }
775
+ }
776
+ if (snapToElements) {
777
+ for (const element of allElements) {
778
+ if (element.id === draggedElement.id) continue;
779
+ if (element.visible === false) continue;
780
+ const elementBounds = getRotatedBounds(
781
+ element.position.x,
782
+ element.position.y,
783
+ element.size.width,
784
+ element.size.height,
785
+ element.rotation
786
+ );
787
+ const elementLeft = elementBounds.left;
788
+ const elementRight = elementBounds.right;
789
+ const elementTop = elementBounds.top;
790
+ const elementBottom = elementBounds.bottom;
791
+ const elementCenterX = elementBounds.centerX;
792
+ const elementCenterY = elementBounds.centerY;
793
+ const isElementRotated = Math.abs(element.rotation) > 0.1 && Math.abs(element.rotation - 360) > 0.1;
794
+ const shouldSkipEdges = false;
795
+ if (!shouldSkipEdges) {
796
+ if (Math.abs(draggedLeft - elementLeft) <= closestVerticalSnap) {
797
+ verticalOffset = elementLeft - draggedLeft;
798
+ closestVerticalSnap = Math.abs(draggedLeft - elementLeft);
799
+ verticalGuides.length = 0;
800
+ verticalGuides.push({ position: elementLeft, orientation: "vertical", type: "edge" });
801
+ }
802
+ if (Math.abs(draggedRight - elementRight) <= closestVerticalSnap) {
803
+ verticalOffset = elementRight - draggedRight;
804
+ closestVerticalSnap = Math.abs(draggedRight - elementRight);
805
+ verticalGuides.length = 0;
806
+ verticalGuides.push({
807
+ position: elementRight,
808
+ orientation: "vertical",
809
+ type: "edge"
810
+ });
811
+ }
812
+ if (Math.abs(draggedLeft - elementRight) <= closestVerticalSnap) {
813
+ verticalOffset = elementRight - draggedLeft;
814
+ closestVerticalSnap = Math.abs(draggedLeft - elementRight);
815
+ verticalGuides.length = 0;
816
+ verticalGuides.push({
817
+ position: elementRight,
818
+ orientation: "vertical",
819
+ type: "edge"
820
+ });
821
+ }
822
+ if (Math.abs(draggedRight - elementLeft) <= closestVerticalSnap) {
823
+ verticalOffset = elementLeft - draggedRight;
824
+ closestVerticalSnap = Math.abs(draggedRight - elementLeft);
825
+ verticalGuides.length = 0;
826
+ verticalGuides.push({ position: elementLeft, orientation: "vertical", type: "edge" });
827
+ }
828
+ }
829
+ if (Math.abs(draggedCenterX - elementCenterX) <= closestVerticalSnap) {
830
+ verticalOffset = elementCenterX - draggedCenterX;
831
+ closestVerticalSnap = Math.abs(draggedCenterX - elementCenterX);
832
+ verticalGuides.length = 0;
833
+ verticalGuides.push({
834
+ position: elementCenterX,
835
+ orientation: "vertical",
836
+ type: "center"
837
+ });
838
+ }
839
+ if (!shouldSkipEdges) {
840
+ if (Math.abs(draggedTop - elementTop) <= closestHorizontalSnap) {
841
+ horizontalOffset = elementTop - draggedTop;
842
+ closestHorizontalSnap = Math.abs(draggedTop - elementTop);
843
+ horizontalGuides.length = 0;
844
+ horizontalGuides.push({
845
+ position: elementTop,
846
+ orientation: "horizontal",
847
+ type: "edge"
848
+ });
849
+ }
850
+ if (Math.abs(draggedBottom - elementBottom) <= closestHorizontalSnap) {
851
+ horizontalOffset = elementBottom - draggedBottom;
852
+ closestHorizontalSnap = Math.abs(draggedBottom - elementBottom);
853
+ horizontalGuides.length = 0;
854
+ horizontalGuides.push({
855
+ position: elementBottom,
856
+ orientation: "horizontal",
857
+ type: "edge"
858
+ });
859
+ }
860
+ if (Math.abs(draggedTop - elementBottom) <= closestHorizontalSnap) {
861
+ horizontalOffset = elementBottom - draggedTop;
862
+ closestHorizontalSnap = Math.abs(draggedTop - elementBottom);
863
+ horizontalGuides.length = 0;
864
+ horizontalGuides.push({
865
+ position: elementBottom,
866
+ orientation: "horizontal",
867
+ type: "edge"
868
+ });
869
+ }
870
+ if (Math.abs(draggedBottom - elementTop) <= closestHorizontalSnap) {
871
+ horizontalOffset = elementTop - draggedBottom;
872
+ closestHorizontalSnap = Math.abs(draggedBottom - elementTop);
873
+ horizontalGuides.length = 0;
874
+ horizontalGuides.push({
875
+ position: elementTop,
876
+ orientation: "horizontal",
877
+ type: "edge"
878
+ });
879
+ }
880
+ }
881
+ if (Math.abs(draggedCenterY - elementCenterY) <= closestHorizontalSnap) {
882
+ horizontalOffset = elementCenterY - draggedCenterY;
883
+ closestHorizontalSnap = Math.abs(draggedCenterY - elementCenterY);
884
+ horizontalGuides.length = 0;
885
+ horizontalGuides.push({
886
+ position: elementCenterY,
887
+ orientation: "horizontal",
888
+ type: "center"
889
+ });
890
+ }
891
+ }
892
+ }
893
+ snappedX = x + verticalOffset;
894
+ snappedY = y + horizontalOffset;
895
+ return {
896
+ x: snappedX,
897
+ y: snappedY,
898
+ verticalGuides,
899
+ horizontalGuides
900
+ };
901
+ }
902
+
903
+ // src/elements/TextElement.tsx
904
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
905
+ var TextElementRenderer = ({
906
+ element,
907
+ isSelected,
908
+ onSelect,
909
+ onTransform,
910
+ allElements = [],
911
+ canvasSize,
912
+ onSnapGuides,
913
+ onClearSnapGuides,
914
+ elementId
915
+ }) => {
916
+ const shapeRef = React.useRef(null);
917
+ const isVisible = element.visible !== false;
918
+ const isLocked = element.locked === true;
919
+ if (!isVisible) {
920
+ return null;
921
+ }
922
+ const handleClick = (e) => {
923
+ if (isLocked) return;
924
+ e.evt.button !== 0 ? void 0 : onSelect();
925
+ };
926
+ const handleDragMove = (e) => {
927
+ if (!canvasSize || !onSnapGuides || !isSelected || e.evt.button !== 0) return;
928
+ const node = e.target;
929
+ const snapResult = getSnappingPosition(element, node.x(), node.y(), allElements, {
930
+ threshold: 5,
931
+ snapToElements: true,
932
+ snapToCanvas: true,
933
+ canvasSize
934
+ });
935
+ node.x(snapResult.x);
936
+ node.y(snapResult.y);
937
+ onSnapGuides({
938
+ vertical: snapResult.verticalGuides,
939
+ horizontal: snapResult.horizontalGuides
940
+ });
941
+ };
942
+ const handleDragEnd = (e) => {
943
+ if (onClearSnapGuides) {
944
+ onClearSnapGuides();
945
+ }
946
+ onTransform({
947
+ position: {
948
+ x: e.target.x(),
949
+ y: e.target.y()
950
+ }
951
+ });
952
+ };
953
+ const handleTransform = () => {
954
+ const node = shapeRef.current;
955
+ if (!node) return;
956
+ const scaleX = node.scaleX();
957
+ const scaleY = node.scaleY();
958
+ const newWidth = Math.max(20, node.width() * scaleX);
959
+ const newHeight = Math.max(20, node.height() * scaleY);
960
+ node.scaleX(1);
961
+ node.scaleY(1);
962
+ node.width(newWidth);
963
+ node.height(newHeight);
964
+ };
965
+ const getTextDecoration = () => {
966
+ const decorations = [];
967
+ if (element.props.underline) decorations.push("underline");
968
+ if (element.props.strikethrough) decorations.push("line-through");
969
+ if (element.props.overline) decorations.push("overline");
970
+ return decorations.join(" ") || "";
971
+ };
972
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
973
+ Text,
974
+ {
975
+ ref: shapeRef,
976
+ id: elementId || element.id,
977
+ x: element.position.x,
978
+ y: element.position.y,
979
+ width: element.size.width,
980
+ height: element.size.height,
981
+ text: element.props.content,
982
+ fontSize: element.props.fontSize,
983
+ fontFamily: element.props.fontFamily || "Arial",
984
+ opacity: element.opacity,
985
+ fill: element.props.color,
986
+ align: element.props.align || "left",
987
+ fontStyle: `${element.props.bold ? "bold" : ""} ${element.props.italic ? "italic" : ""}`.trim() || "normal",
988
+ textDecoration: getTextDecoration(),
989
+ rotation: element.rotation,
990
+ draggable: !isLocked && isSelected,
991
+ listening: !isLocked,
992
+ onClick: handleClick,
993
+ onTap: isLocked ? void 0 : onSelect,
994
+ onDragMove: handleDragMove,
995
+ onDragEnd: handleDragEnd,
996
+ onTransform: handleTransform,
997
+ ellipsis: element.props.textOverflow === "ellipsis",
998
+ verticalAlign: element.props.verticalAlign || "top",
999
+ stroke: element.props.strokeColor || "#000000",
1000
+ strokeWidth: element.props.strokeWidth || 0,
1001
+ strokeEnabled: true,
1002
+ fillAfterStrokeEnabled: true
1003
+ }
1004
+ ) });
1005
+ };
1006
+ var textElementRenderer = {
1007
+ type: "text",
1008
+ displayName: "Text",
1009
+ render: (element) => /* @__PURE__ */ jsxs("div", { children: [
1010
+ "Text: ",
1011
+ element.props.content
1012
+ ] }),
1013
+ // Placeholder for non-Konva contexts
1014
+ renderComponent: TextElementRenderer,
1015
+ // Konva rendering component
1016
+ icon: /* @__PURE__ */ jsx(Type, { className: "w-4 h-4" }),
1017
+ defaultProps: {
1018
+ content: "Text",
1019
+ fontSize: 16,
1020
+ fontFamily: "Arial",
1021
+ color: "#000000",
1022
+ strokeColor: "#000000",
1023
+ strokeWidth: 0,
1024
+ align: "left",
1025
+ verticalAlign: "top",
1026
+ bold: false,
1027
+ italic: false,
1028
+ underline: false,
1029
+ overline: false,
1030
+ strikethrough: false,
1031
+ wordWrap: "break-word"
1032
+ },
1033
+ defaultSize: {
1034
+ width: 200,
1035
+ height: 50
1036
+ },
1037
+ inspectorSchema: [
1038
+ {
1039
+ name: "content",
1040
+ type: "string",
1041
+ label: "Text Content",
1042
+ defaultValue: "Text"
1043
+ },
1044
+ {
1045
+ name: "fontSize",
1046
+ type: "number",
1047
+ label: "Font Size",
1048
+ min: 0,
1049
+ max: 1024,
1050
+ step: 1,
1051
+ defaultValue: 16
1052
+ },
1053
+ {
1054
+ name: "fontFamily",
1055
+ type: "select",
1056
+ label: "Font Family",
1057
+ options: [
1058
+ { value: "Arial", label: "Arial" },
1059
+ { value: "Times New Roman", label: "Times New Roman" },
1060
+ { value: "Courier New", label: "Courier New" },
1061
+ { value: "Georgia", label: "Georgia" },
1062
+ { value: "Verdana", label: "Verdana" },
1063
+ { value: "Outfit", label: "Outfit" }
1064
+ ],
1065
+ defaultValue: "Outfit"
1066
+ },
1067
+ {
1068
+ name: "color",
1069
+ type: "color",
1070
+ label: "Text Color",
1071
+ defaultValue: "#000000"
1072
+ },
1073
+ {
1074
+ name: "strokeColor",
1075
+ type: "color",
1076
+ label: "Stroke Color",
1077
+ defaultValue: "#000000"
1078
+ },
1079
+ {
1080
+ name: "strokeWidth",
1081
+ type: "number",
1082
+ label: "Stroke Width",
1083
+ min: 0,
1084
+ max: 50,
1085
+ step: 1,
1086
+ defaultValue: 0
1087
+ },
1088
+ {
1089
+ name: "align",
1090
+ type: "select",
1091
+ label: "Horizontal Alignment",
1092
+ options: [
1093
+ { value: "left", label: "Left" },
1094
+ { value: "center", label: "Center" },
1095
+ { value: "right", label: "Right" }
1096
+ ],
1097
+ defaultValue: "left"
1098
+ },
1099
+ {
1100
+ name: "verticalAlign",
1101
+ type: "select",
1102
+ label: "Vertical Alignment",
1103
+ options: [
1104
+ { value: "top", label: "Top" },
1105
+ { value: "middle", label: "Middle" },
1106
+ { value: "bottom", label: "Bottom" }
1107
+ ],
1108
+ defaultValue: "top"
1109
+ },
1110
+ {
1111
+ name: "bold",
1112
+ type: "boolean",
1113
+ label: "Bold",
1114
+ defaultValue: false
1115
+ },
1116
+ {
1117
+ name: "italic",
1118
+ type: "boolean",
1119
+ label: "Italic",
1120
+ defaultValue: false
1121
+ },
1122
+ {
1123
+ name: "underline",
1124
+ type: "boolean",
1125
+ label: "Underline",
1126
+ defaultValue: false
1127
+ },
1128
+ {
1129
+ name: "strikethrough",
1130
+ type: "boolean",
1131
+ label: "Strikethrough",
1132
+ defaultValue: false
1133
+ }
1134
+ ]
1135
+ };
1136
+
1137
+ // src/elements/ImageElement.tsx
1138
+ import React2 from "react";
1139
+ import { Image as KonvaImage, Rect, Text as Text2, Group } from "react-konva";
1140
+ import useImage from "use-image";
1141
+ import { Image } from "lucide-react";
1142
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1143
+ var ImageElementRenderer = ({
1144
+ element,
1145
+ isSelected,
1146
+ onSelect,
1147
+ onTransform,
1148
+ allElements = [],
1149
+ canvasSize,
1150
+ onSnapGuides,
1151
+ onClearSnapGuides,
1152
+ imageUrls,
1153
+ elementId,
1154
+ onNodeUpdate
1155
+ }) => {
1156
+ const imageSrc = React2.useMemo(() => {
1157
+ const src = element.props.src;
1158
+ if (src && (src.startsWith("http") || src.startsWith("blob:") || src.startsWith("data:"))) {
1159
+ return src;
1160
+ }
1161
+ if (src && imageUrls) {
1162
+ const resolvedUrl = imageUrls.get(src);
1163
+ if (resolvedUrl) {
1164
+ return resolvedUrl;
1165
+ }
1166
+ }
1167
+ return src;
1168
+ }, [element.props.src, imageUrls]);
1169
+ const [image] = useImage(imageSrc);
1170
+ const shapeRef = React2.useRef(null);
1171
+ const transformerRef = React2.useRef(null);
1172
+ const isVisible = element.visible !== false;
1173
+ const isLocked = element.locked === true;
1174
+ React2.useEffect(() => {
1175
+ if (isSelected && transformerRef.current && shapeRef.current) {
1176
+ transformerRef.current.nodes([shapeRef.current]);
1177
+ transformerRef.current.getLayer().batchDraw();
1178
+ }
1179
+ }, [isSelected]);
1180
+ React2.useEffect(() => {
1181
+ if (shapeRef.current) {
1182
+ const layer = shapeRef.current.getLayer();
1183
+ if (layer) {
1184
+ layer.batchDraw();
1185
+ }
1186
+ if (onNodeUpdate) {
1187
+ onNodeUpdate();
1188
+ }
1189
+ }
1190
+ }, [image, onNodeUpdate]);
1191
+ if (!isVisible) {
1192
+ return null;
1193
+ }
1194
+ const handleClick = (e) => {
1195
+ if (isLocked) return;
1196
+ e.evt.button !== 0 ? void 0 : onSelect();
1197
+ };
1198
+ const handleDragMove = (e) => {
1199
+ if (!canvasSize || !onSnapGuides || e.evt.button !== 0) return;
1200
+ const node = e.target;
1201
+ const snapResult = getSnappingPosition(element, node.x(), node.y(), allElements, {
1202
+ threshold: 5,
1203
+ snapToElements: true,
1204
+ snapToCanvas: true,
1205
+ canvasSize
1206
+ });
1207
+ node.x(snapResult.x);
1208
+ node.y(snapResult.y);
1209
+ onSnapGuides({
1210
+ vertical: snapResult.verticalGuides,
1211
+ horizontal: snapResult.horizontalGuides
1212
+ });
1213
+ };
1214
+ const handleDragEnd = (e) => {
1215
+ if (onClearSnapGuides) {
1216
+ onClearSnapGuides();
1217
+ }
1218
+ onTransform({
1219
+ position: {
1220
+ x: e.target.x(),
1221
+ y: e.target.y()
1222
+ }
1223
+ });
1224
+ };
1225
+ return /* @__PURE__ */ jsx2(Fragment2, { children: image ? /* @__PURE__ */ jsx2(
1226
+ KonvaImage,
1227
+ {
1228
+ ref: shapeRef,
1229
+ id: elementId || element.id,
1230
+ x: element.position.x,
1231
+ y: element.position.y,
1232
+ width: element.size.width,
1233
+ height: element.size.height,
1234
+ image,
1235
+ opacity: element.opacity,
1236
+ rotation: element.rotation,
1237
+ draggable: !isLocked && isSelected,
1238
+ listening: !isLocked,
1239
+ onClick: handleClick,
1240
+ onTap: isLocked ? void 0 : onSelect,
1241
+ onDragMove: handleDragMove,
1242
+ onDragEnd: handleDragEnd
1243
+ }
1244
+ ) : /* @__PURE__ */ jsxs2(
1245
+ Group,
1246
+ {
1247
+ ref: shapeRef,
1248
+ id: elementId || element.id,
1249
+ x: element.position.x,
1250
+ y: element.position.y,
1251
+ width: element.size.width,
1252
+ height: element.size.height,
1253
+ rotation: element.rotation,
1254
+ draggable: !isLocked && isSelected,
1255
+ listening: !isLocked,
1256
+ onClick: isLocked ? void 0 : handleClick,
1257
+ onTap: isLocked ? void 0 : onSelect,
1258
+ onDragMove: handleDragMove,
1259
+ onDragEnd: handleDragEnd,
1260
+ children: [
1261
+ /* @__PURE__ */ jsx2(
1262
+ Rect,
1263
+ {
1264
+ width: element.size.width,
1265
+ height: element.size.height,
1266
+ fill: "#f0f0f0",
1267
+ stroke: "#999999",
1268
+ strokeWidth: 2,
1269
+ dash: [10, 5],
1270
+ opacity: element.opacity
1271
+ }
1272
+ ),
1273
+ /* @__PURE__ */ jsx2(
1274
+ Text2,
1275
+ {
1276
+ width: element.size.width,
1277
+ height: element.size.height,
1278
+ text: "No Image",
1279
+ fontSize: 16,
1280
+ fill: "#666666",
1281
+ align: "center",
1282
+ verticalAlign: "middle"
1283
+ }
1284
+ )
1285
+ ]
1286
+ }
1287
+ ) });
1288
+ };
1289
+ var imageElementRenderer = {
1290
+ type: "image",
1291
+ displayName: "Image",
1292
+ render: (element) => /* @__PURE__ */ jsxs2("div", { children: [
1293
+ "Image: ",
1294
+ element.props.src
1295
+ ] }),
1296
+ // Placeholder for non-Konva contexts
1297
+ renderComponent: ImageElementRenderer,
1298
+ // Konva rendering component
1299
+ icon: /* @__PURE__ */ jsx2(Image, { className: "w-4 h-4" }),
1300
+ defaultProps: {
1301
+ src: "",
1302
+ fit: "fill"
1303
+ },
1304
+ defaultSize: {
1305
+ width: 200,
1306
+ height: 200
1307
+ },
1308
+ inspectorSchema: [
1309
+ {
1310
+ name: "src",
1311
+ type: "image",
1312
+ label: "Image Source",
1313
+ description: "URL or path to the image",
1314
+ defaultValue: ""
1315
+ },
1316
+ {
1317
+ name: "fit",
1318
+ type: "select",
1319
+ label: "Fit Mode",
1320
+ options: [{ value: "fill", label: "Fill" }],
1321
+ defaultValue: "contain"
1322
+ }
1323
+ ]
1324
+ };
1325
+
1326
+ // src/elements/index.ts
1327
+ var defaultElements = [textElementRenderer, imageElementRenderer];
1328
+
1329
+ // src/core/VisualEditor.tsx
1330
+ import { jsx as jsx3 } from "react/jsx-runtime";
1331
+ var VisualEditor = ({
1332
+ mode,
1333
+ initialData,
1334
+ width: propWidth,
1335
+ height: propHeight,
1336
+ readonly = false,
1337
+ onChange,
1338
+ onSelectionChange,
1339
+ onExport,
1340
+ customElements = [],
1341
+ showToolbar = true,
1342
+ showInspector = true,
1343
+ className = "",
1344
+ style = {}
1345
+ }) => {
1346
+ const containerRef = useRef(null);
1347
+ const stageRef = useRef(null);
1348
+ const {
1349
+ state,
1350
+ api,
1351
+ setCanvasSize,
1352
+ setMode: setEditorMode,
1353
+ undo,
1354
+ redo,
1355
+ canUndo,
1356
+ canRedo
1357
+ } = useEditorState(mode || null);
1358
+ const registry = useElementRegistry([
1359
+ ...defaultElements,
1360
+ ...mode?.registeredElements || [],
1361
+ ...customElements
1362
+ ]);
1363
+ const canvasWidth = propWidth || state.canvasSize.width || mode?.defaultCanvasSize.width || 800;
1364
+ const canvasHeight = propHeight || state.canvasSize.height || mode?.defaultCanvasSize.height || 600;
1365
+ useEffect(() => {
1366
+ if (propWidth && propHeight) {
1367
+ setCanvasSize(propWidth, propHeight);
1368
+ }
1369
+ }, [propWidth, propHeight, setCanvasSize]);
1370
+ useEffect(() => {
1371
+ if (initialData) {
1372
+ api.importJSON(initialData);
1373
+ }
1374
+ }, [initialData, api]);
1375
+ useEffect(() => {
1376
+ if (mode) {
1377
+ setEditorMode(mode);
1378
+ if (mode.registeredElements) {
1379
+ mode.registeredElements.forEach((el) => registry.register(el));
1380
+ }
1381
+ if (mode.onModeActivate) {
1382
+ mode.onModeActivate(api);
1383
+ }
1384
+ }
1385
+ }, [mode, setEditorMode, registry, api]);
1386
+ useEffect(() => {
1387
+ if (onChange) {
1388
+ const data = api.exportJSON();
1389
+ onChange(data);
1390
+ }
1391
+ }, [state.elements, onChange, api]);
1392
+ useEffect(() => {
1393
+ if (onSelectionChange) {
1394
+ const selected = api.getSelectedElement();
1395
+ onSelectionChange(selected);
1396
+ }
1397
+ }, [state.selectedElementId, onSelectionChange, api]);
1398
+ useEffect(() => {
1399
+ const handleKeyDown = (e) => {
1400
+ if ((e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey) {
1401
+ e.preventDefault();
1402
+ undo();
1403
+ }
1404
+ if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "z" || (e.ctrlKey || e.metaKey) && e.key === "y") {
1405
+ e.preventDefault();
1406
+ redo();
1407
+ }
1408
+ if ((e.key === "Delete" || e.key === "Backspace") && !readonly) {
1409
+ e.preventDefault();
1410
+ const selected = api.getSelectedElement();
1411
+ if (selected) {
1412
+ api.removeElement(selected.id);
1413
+ }
1414
+ }
1415
+ if (e.key === "Escape") {
1416
+ e.preventDefault();
1417
+ api.selectElement(null);
1418
+ }
1419
+ };
1420
+ window.addEventListener("keydown", handleKeyDown);
1421
+ return () => window.removeEventListener("keydown", handleKeyDown);
1422
+ }, [undo, redo, readonly, api]);
1423
+ const renderElement = useCallback2(
1424
+ (element) => {
1425
+ const renderer = registry.get(element.type);
1426
+ if (!renderer) {
1427
+ console.warn(`No renderer found for element type: ${element.type}`);
1428
+ return null;
1429
+ }
1430
+ const RendererComponent = renderer.renderComponent;
1431
+ if (!RendererComponent) {
1432
+ console.warn(`No renderComponent found for element type: ${element.type}`);
1433
+ return null;
1434
+ }
1435
+ const isSelected = state.selectedElementId === element.id;
1436
+ return /* @__PURE__ */ jsx3(
1437
+ RendererComponent,
1438
+ {
1439
+ element,
1440
+ isSelected,
1441
+ onSelect: () => !readonly && api.selectElement(element.id),
1442
+ onTransform: (updates) => !readonly && api.updateElement(element.id, updates)
1443
+ },
1444
+ element.id
1445
+ );
1446
+ },
1447
+ [registry, state.selectedElementId, readonly, api]
1448
+ );
1449
+ const handleStageClick = useCallback2(
1450
+ (e) => {
1451
+ if (e.target === e.target.getStage()) {
1452
+ api.selectElement(null);
1453
+ }
1454
+ },
1455
+ [api]
1456
+ );
1457
+ const sortedElements = [...state.elements].sort((a, b) => a.zIndex - b.zIndex);
1458
+ return /* @__PURE__ */ jsx3(
1459
+ "div",
1460
+ {
1461
+ ref: containerRef,
1462
+ className: `visual-editor ${className}`,
1463
+ style: {
1464
+ width: "100%",
1465
+ height: "100%",
1466
+ display: "flex",
1467
+ flexDirection: "column",
1468
+ overflow: "hidden",
1469
+ backgroundColor: mode?.backgroundColor || "#f0f0f0",
1470
+ ...style
1471
+ },
1472
+ children: /* @__PURE__ */ jsx3(
1473
+ "div",
1474
+ {
1475
+ style: {
1476
+ flex: 1,
1477
+ display: "flex",
1478
+ justifyContent: "center",
1479
+ alignItems: "center",
1480
+ overflow: "auto"
1481
+ },
1482
+ children: /* @__PURE__ */ jsx3(
1483
+ Stage,
1484
+ {
1485
+ ref: stageRef,
1486
+ width: canvasWidth,
1487
+ height: canvasHeight,
1488
+ onClick: handleStageClick,
1489
+ onTap: handleStageClick,
1490
+ style: {
1491
+ backgroundColor: "#ffffff",
1492
+ boxShadow: "0 2px 8px rgba(0,0,0,0.1)"
1493
+ },
1494
+ children: /* @__PURE__ */ jsx3(Layer, { children: sortedElements.map(renderElement) })
1495
+ }
1496
+ )
1497
+ }
1498
+ )
1499
+ }
1500
+ );
1501
+ };
1502
+
1503
+ // src/components/Inspector.tsx
1504
+ import React11 from "react";
1505
+
1506
+ // src/ui/input.tsx
1507
+ import * as React4 from "react";
1508
+
1509
+ // src/lib/utils.ts
1510
+ import { clsx } from "clsx";
1511
+ import { twMerge } from "tailwind-merge";
1512
+ function cn(...inputs) {
1513
+ return twMerge(clsx(inputs));
1514
+ }
1515
+
1516
+ // src/ui/input.tsx
1517
+ import { jsx as jsx4 } from "react/jsx-runtime";
1518
+ var Input = React4.forwardRef(
1519
+ ({ className, type, ...props }, ref) => {
1520
+ return /* @__PURE__ */ jsx4(
1521
+ "input",
1522
+ {
1523
+ type,
1524
+ className: cn(
1525
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
1526
+ className
1527
+ ),
1528
+ ref,
1529
+ ...props
1530
+ }
1531
+ );
1532
+ }
1533
+ );
1534
+ Input.displayName = "Input";
1535
+
1536
+ // src/ui/label.tsx
1537
+ import * as React5 from "react";
1538
+ import * as LabelPrimitive from "@radix-ui/react-label";
1539
+ import { cva } from "class-variance-authority";
1540
+ import { jsx as jsx5 } from "react/jsx-runtime";
1541
+ var labelVariants = cva(
1542
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
1543
+ );
1544
+ var Label = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx5(
1545
+ LabelPrimitive.Root,
1546
+ {
1547
+ ref,
1548
+ className: cn(labelVariants(), className),
1549
+ ...props
1550
+ }
1551
+ ));
1552
+ Label.displayName = LabelPrimitive.Root.displayName;
1553
+
1554
+ // src/ui/slider.tsx
1555
+ import * as React6 from "react";
1556
+ import * as SliderPrimitive from "@radix-ui/react-slider";
1557
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
1558
+ var Slider = React6.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxs3(
1559
+ SliderPrimitive.Root,
1560
+ {
1561
+ ref,
1562
+ className: cn(
1563
+ "relative flex w-full touch-none select-none items-center",
1564
+ className
1565
+ ),
1566
+ ...props,
1567
+ children: [
1568
+ /* @__PURE__ */ jsx6(SliderPrimitive.Track, { className: "relative h-2 w-full grow overflow-hidden rounded-full bg-secondary", children: /* @__PURE__ */ jsx6(SliderPrimitive.Range, { className: "absolute h-full bg-primary" }) }),
1569
+ /* @__PURE__ */ jsx6(SliderPrimitive.Thumb, { className: "block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" })
1570
+ ]
1571
+ }
1572
+ ));
1573
+ Slider.displayName = SliderPrimitive.Root.displayName;
1574
+
1575
+ // src/ui/separator.tsx
1576
+ import * as React7 from "react";
1577
+ import * as SeparatorPrimitive from "@radix-ui/react-separator";
1578
+ import { jsx as jsx7 } from "react/jsx-runtime";
1579
+ var Separator = React7.forwardRef(
1580
+ ({ className, orientation = "horizontal", decorative = true, ...props }, ref) => /* @__PURE__ */ jsx7(
1581
+ SeparatorPrimitive.Root,
1582
+ {
1583
+ ref,
1584
+ decorative,
1585
+ orientation,
1586
+ className: cn(
1587
+ "shrink-0 bg-border",
1588
+ orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
1589
+ className
1590
+ ),
1591
+ ...props
1592
+ }
1593
+ )
1594
+ );
1595
+ Separator.displayName = SeparatorPrimitive.Root.displayName;
1596
+
1597
+ // src/ui/select.tsx
1598
+ import * as React8 from "react";
1599
+ import * as SelectPrimitive from "@radix-ui/react-select";
1600
+ import { Check, ChevronDown, ChevronUp } from "lucide-react";
1601
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
1602
+ var Select = SelectPrimitive.Root;
1603
+ var SelectValue = SelectPrimitive.Value;
1604
+ var SelectTrigger = React8.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs4(
1605
+ SelectPrimitive.Trigger,
1606
+ {
1607
+ ref,
1608
+ className: cn(
1609
+ "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
1610
+ className
1611
+ ),
1612
+ ...props,
1613
+ children: [
1614
+ children,
1615
+ /* @__PURE__ */ jsx8(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx8(ChevronDown, { className: "h-4 w-4 opacity-50" }) })
1616
+ ]
1617
+ }
1618
+ ));
1619
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
1620
+ var SelectScrollUpButton = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx8(
1621
+ SelectPrimitive.ScrollUpButton,
1622
+ {
1623
+ ref,
1624
+ className: cn(
1625
+ "flex cursor-default items-center justify-center py-1",
1626
+ className
1627
+ ),
1628
+ ...props,
1629
+ children: /* @__PURE__ */ jsx8(ChevronUp, { className: "h-4 w-4" })
1630
+ }
1631
+ ));
1632
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
1633
+ var SelectScrollDownButton = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx8(
1634
+ SelectPrimitive.ScrollDownButton,
1635
+ {
1636
+ ref,
1637
+ className: cn(
1638
+ "flex cursor-default items-center justify-center py-1",
1639
+ className
1640
+ ),
1641
+ ...props,
1642
+ children: /* @__PURE__ */ jsx8(ChevronDown, { className: "h-4 w-4" })
1643
+ }
1644
+ ));
1645
+ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
1646
+ var SelectContent = React8.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsx8(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs4(
1647
+ SelectPrimitive.Content,
1648
+ {
1649
+ ref,
1650
+ className: cn(
1651
+ "relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
1652
+ position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
1653
+ className
1654
+ ),
1655
+ position,
1656
+ ...props,
1657
+ children: [
1658
+ /* @__PURE__ */ jsx8(SelectScrollUpButton, {}),
1659
+ /* @__PURE__ */ jsx8(
1660
+ SelectPrimitive.Viewport,
1661
+ {
1662
+ className: cn(
1663
+ "p-1",
1664
+ position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
1665
+ ),
1666
+ children
1667
+ }
1668
+ ),
1669
+ /* @__PURE__ */ jsx8(SelectScrollDownButton, {})
1670
+ ]
1671
+ }
1672
+ ) }));
1673
+ SelectContent.displayName = SelectPrimitive.Content.displayName;
1674
+ var SelectLabel = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx8(
1675
+ SelectPrimitive.Label,
1676
+ {
1677
+ ref,
1678
+ className: cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className),
1679
+ ...props
1680
+ }
1681
+ ));
1682
+ SelectLabel.displayName = SelectPrimitive.Label.displayName;
1683
+ var SelectItem = React8.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs4(
1684
+ SelectPrimitive.Item,
1685
+ {
1686
+ ref,
1687
+ className: cn(
1688
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
1689
+ className
1690
+ ),
1691
+ ...props,
1692
+ children: [
1693
+ /* @__PURE__ */ jsx8("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx8(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx8(Check, { className: "h-4 w-4" }) }) }),
1694
+ /* @__PURE__ */ jsx8(SelectPrimitive.ItemText, { children })
1695
+ ]
1696
+ }
1697
+ ));
1698
+ SelectItem.displayName = SelectPrimitive.Item.displayName;
1699
+ var SelectSeparator = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx8(
1700
+ SelectPrimitive.Separator,
1701
+ {
1702
+ ref,
1703
+ className: cn("-mx-1 my-1 h-px bg-muted", className),
1704
+ ...props
1705
+ }
1706
+ ));
1707
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
1708
+
1709
+ // src/ui/textarea.tsx
1710
+ import * as React9 from "react";
1711
+ import { jsx as jsx9 } from "react/jsx-runtime";
1712
+ var Textarea = React9.forwardRef(({ className, ...props }, ref) => {
1713
+ return /* @__PURE__ */ jsx9(
1714
+ "textarea",
1715
+ {
1716
+ className: cn(
1717
+ "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
1718
+ className
1719
+ ),
1720
+ ref,
1721
+ ...props
1722
+ }
1723
+ );
1724
+ });
1725
+ Textarea.displayName = "Textarea";
1726
+
1727
+ // src/ui/checkbox.tsx
1728
+ import * as React10 from "react";
1729
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
1730
+ import { Check as Check2 } from "lucide-react";
1731
+ import { jsx as jsx10 } from "react/jsx-runtime";
1732
+ var Checkbox = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx10(
1733
+ CheckboxPrimitive.Root,
1734
+ {
1735
+ ref,
1736
+ className: cn(
1737
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
1738
+ className
1739
+ ),
1740
+ ...props,
1741
+ children: /* @__PURE__ */ jsx10(
1742
+ CheckboxPrimitive.Indicator,
1743
+ {
1744
+ className: cn("flex items-center justify-center text-current"),
1745
+ children: /* @__PURE__ */ jsx10(Check2, { className: "h-4 w-4" })
1746
+ }
1747
+ )
1748
+ }
1749
+ ));
1750
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName;
1751
+
1752
+ // src/components/Inspector.tsx
1753
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
1754
+ var Inspector = ({
1755
+ selectedElement,
1756
+ elementRenderer,
1757
+ api,
1758
+ mode,
1759
+ canvasSize,
1760
+ setCanvasSize,
1761
+ style,
1762
+ className
1763
+ }) => {
1764
+ const [editingValues, setEditingValues] = React11.useState({});
1765
+ const debounceTimers = React11.useRef({});
1766
+ React11.useEffect(() => {
1767
+ return () => {
1768
+ Object.values(debounceTimers.current).forEach((timer) => clearTimeout(timer));
1769
+ };
1770
+ }, []);
1771
+ if (!selectedElement || !elementRenderer) {
1772
+ if (mode?.inspectorPlaceholder) {
1773
+ const PlaceholderComponent = mode.inspectorPlaceholder;
1774
+ return /* @__PURE__ */ jsxs5("div", { className: `w-full bg-card h-full ${className}`, style, children: [
1775
+ /* @__PURE__ */ jsx11("div", { className: "flex h-10 sticky top-0 items-center justify-between px-3 py-2 border-b bg-popover", children: /* @__PURE__ */ jsx11("h3", { className: "text-sm font-semibold", children: "Inspector" }) }),
1776
+ /* @__PURE__ */ jsx11(
1777
+ PlaceholderComponent,
1778
+ {
1779
+ api,
1780
+ canvasSize,
1781
+ setCanvasSize,
1782
+ ...mode.context || {}
1783
+ }
1784
+ )
1785
+ ] });
1786
+ }
1787
+ return /* @__PURE__ */ jsxs5("div", { className: `w-full bg-card h-full ${className}`, style, children: [
1788
+ /* @__PURE__ */ jsx11("div", { className: "flex h-10 items-center justify-between px-3 py-2 border-b bg-popover", children: /* @__PURE__ */ jsx11("h3", { className: "text-sm font-semibold", children: "Inspector" }) }),
1789
+ /* @__PURE__ */ jsx11("p", { className: "p-4 text-sm text-muted-foreground", children: "No element selected. Click on an element to edit its properties." })
1790
+ ] });
1791
+ }
1792
+ const schema = elementRenderer.inspectorSchema || [];
1793
+ const handlePropChange = (propName, value) => {
1794
+ api.updateElement(selectedElement.id, {
1795
+ props: {
1796
+ ...selectedElement.props,
1797
+ [propName]: value
1798
+ }
1799
+ });
1800
+ };
1801
+ const handleColorChange = (propName, value) => {
1802
+ if (debounceTimers.current[propName]) {
1803
+ clearTimeout(debounceTimers.current[propName]);
1804
+ }
1805
+ setEditingValues((prev) => ({ ...prev, [propName]: value }));
1806
+ debounceTimers.current[propName] = setTimeout(() => {
1807
+ handlePropChange(propName, value);
1808
+ setEditingValues((prev) => {
1809
+ const newState = { ...prev };
1810
+ delete newState[propName];
1811
+ return newState;
1812
+ });
1813
+ delete debounceTimers.current[propName];
1814
+ }, 300);
1815
+ };
1816
+ const handleTransformChange = (field, value) => {
1817
+ const roundedValue = Math.round(value * 100) / 100;
1818
+ if (field === "x" || field === "y") {
1819
+ api.updateElement(selectedElement.id, {
1820
+ position: {
1821
+ ...selectedElement.position,
1822
+ [field]: roundedValue
1823
+ }
1824
+ });
1825
+ } else if (field === "width" || field === "height") {
1826
+ api.updateElement(selectedElement.id, {
1827
+ size: {
1828
+ ...selectedElement.size,
1829
+ [field]: roundedValue
1830
+ }
1831
+ });
1832
+ } else if (field === "rotation") {
1833
+ const width = selectedElement.size.width;
1834
+ const height = selectedElement.size.height;
1835
+ const oldRotRad = selectedElement.rotation * Math.PI / 180;
1836
+ const centerOffsetX = width / 2;
1837
+ const centerOffsetY = height / 2;
1838
+ const rotatedCenterX = selectedElement.position.x + centerOffsetX * Math.cos(oldRotRad) - centerOffsetY * Math.sin(oldRotRad);
1839
+ const rotatedCenterY = selectedElement.position.y + centerOffsetX * Math.sin(oldRotRad) + centerOffsetY * Math.cos(oldRotRad);
1840
+ const newRotRad = roundedValue * Math.PI / 180;
1841
+ const newX = rotatedCenterX - (centerOffsetX * Math.cos(newRotRad) - centerOffsetY * Math.sin(newRotRad));
1842
+ const newY = rotatedCenterY - (centerOffsetX * Math.sin(newRotRad) + centerOffsetY * Math.cos(newRotRad));
1843
+ api.updateElement(selectedElement.id, {
1844
+ position: {
1845
+ x: Math.round(newX * 100) / 100,
1846
+ y: Math.round(newY * 100) / 100
1847
+ },
1848
+ rotation: roundedValue
1849
+ });
1850
+ } else if (field === "opacity") {
1851
+ const clampedValue = Math.max(0, Math.min(1, roundedValue));
1852
+ api.updateElement(selectedElement.id, {
1853
+ opacity: clampedValue
1854
+ });
1855
+ }
1856
+ };
1857
+ const getDisplayValue = (field) => {
1858
+ if (editingValues[field] !== void 0) {
1859
+ return editingValues[field];
1860
+ }
1861
+ let value;
1862
+ if (field === "x") value = selectedElement.position.x;
1863
+ else if (field === "y") value = selectedElement.position.y;
1864
+ else if (field === "width") value = selectedElement.size.width;
1865
+ else if (field === "height") value = selectedElement.size.height;
1866
+ else if (field === "rotation") value = selectedElement.rotation;
1867
+ else if (field === "opacity") value = selectedElement.opacity;
1868
+ else return "0";
1869
+ return value.toFixed(2);
1870
+ };
1871
+ const handleInputChange = (field, value) => {
1872
+ setEditingValues((prev) => ({ ...prev, [field]: value }));
1873
+ };
1874
+ const handleInputBlur = (field) => {
1875
+ const value = editingValues[field];
1876
+ if (value !== void 0) {
1877
+ const numValue = parseFloat(value);
1878
+ if (!isNaN(numValue)) {
1879
+ handleTransformChange(field, numValue);
1880
+ }
1881
+ setEditingValues((prev) => {
1882
+ const newState = { ...prev };
1883
+ delete newState[field];
1884
+ return newState;
1885
+ });
1886
+ }
1887
+ };
1888
+ return /* @__PURE__ */ jsxs5("div", { className: `h-full bg-card overflow-y-auto ${className}`, style, children: [
1889
+ /* @__PURE__ */ jsx11("div", { className: "flex h-10 sticky top-0 items-center justify-between px-3 py-2 border-b bg-popover", children: /* @__PURE__ */ jsx11("h3", { className: "text-sm font-semibold", children: "Inspector" }) }),
1890
+ /* @__PURE__ */ jsxs5("div", { className: "p-4", children: [
1891
+ /* @__PURE__ */ jsx11("h3", { className: "text-lg font-semibold mb-1", children: elementRenderer.displayName || selectedElement.type }),
1892
+ /* @__PURE__ */ jsxs5("p", { className: "text-xs text-muted-foreground mb-4", children: [
1893
+ "ID: ",
1894
+ selectedElement.id
1895
+ ] }),
1896
+ /* @__PURE__ */ jsx11(Separator, { className: "my-4" }),
1897
+ /* @__PURE__ */ jsxs5("div", { className: "mb-4", children: [
1898
+ /* @__PURE__ */ jsx11(Label, { htmlFor: "display-name", className: "text-xs", children: "Display Name" }),
1899
+ /* @__PURE__ */ jsx11(
1900
+ Input,
1901
+ {
1902
+ id: "display-name",
1903
+ type: "text",
1904
+ value: selectedElement.displayName || "",
1905
+ onChange: (e) => {
1906
+ api.updateElement(selectedElement.id, {
1907
+ displayName: e.target.value
1908
+ });
1909
+ },
1910
+ placeholder: elementRenderer.displayName || selectedElement.type,
1911
+ className: "h-8 mt-1"
1912
+ }
1913
+ )
1914
+ ] }),
1915
+ /* @__PURE__ */ jsx11(Separator, { className: "my-4" }),
1916
+ /* @__PURE__ */ jsx11("h4", { className: "text-sm font-medium mb-3", children: "Transform" }),
1917
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-3", children: [
1918
+ /* @__PURE__ */ jsxs5("div", { className: "flex gap-2", children: [
1919
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-2 items-start", children: [
1920
+ /* @__PURE__ */ jsx11(Label, { htmlFor: "x-position", className: "text-xs", children: "X" }),
1921
+ /* @__PURE__ */ jsx11(
1922
+ Input,
1923
+ {
1924
+ id: "x-position",
1925
+ type: "number",
1926
+ step: 0.5,
1927
+ value: getDisplayValue("x"),
1928
+ onChange: (e) => handleInputChange("x", e.target.value),
1929
+ onBlur: () => handleInputBlur("x"),
1930
+ className: "h-8"
1931
+ }
1932
+ )
1933
+ ] }),
1934
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-2 items-start", children: [
1935
+ /* @__PURE__ */ jsx11(Label, { htmlFor: "y-position", className: "text-xs", children: "Y" }),
1936
+ /* @__PURE__ */ jsx11(
1937
+ Input,
1938
+ {
1939
+ id: "y-position",
1940
+ type: "number",
1941
+ step: 0.5,
1942
+ value: getDisplayValue("y"),
1943
+ onChange: (e) => handleInputChange("y", e.target.value),
1944
+ onBlur: () => handleInputBlur("y"),
1945
+ className: "h-8"
1946
+ }
1947
+ )
1948
+ ] })
1949
+ ] }),
1950
+ /* @__PURE__ */ jsxs5("div", { className: "flex gap-2", children: [
1951
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-2 items-start", children: [
1952
+ /* @__PURE__ */ jsx11(Label, { htmlFor: "width", className: "text-xs", children: "Width" }),
1953
+ /* @__PURE__ */ jsx11(
1954
+ Input,
1955
+ {
1956
+ id: "width",
1957
+ type: "number",
1958
+ step: 0.5,
1959
+ value: getDisplayValue("width"),
1960
+ onChange: (e) => handleInputChange("width", e.target.value),
1961
+ onBlur: () => handleInputBlur("width"),
1962
+ className: "h-8"
1963
+ }
1964
+ )
1965
+ ] }),
1966
+ /* @__PURE__ */ jsx11("div", { className: "flex h-8 gap-2", children: /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-2 items-start", children: [
1967
+ /* @__PURE__ */ jsx11(Label, { htmlFor: "height", className: "text-xs", children: "Height" }),
1968
+ /* @__PURE__ */ jsx11(
1969
+ Input,
1970
+ {
1971
+ id: "height",
1972
+ type: "number",
1973
+ step: 0.5,
1974
+ value: getDisplayValue("height"),
1975
+ onChange: (e) => handleInputChange("height", e.target.value),
1976
+ onBlur: () => handleInputBlur("height"),
1977
+ className: "h-8"
1978
+ }
1979
+ )
1980
+ ] }) })
1981
+ ] }),
1982
+ /* @__PURE__ */ jsxs5("div", { className: "flex gap-2", children: [
1983
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-2 items-start flex-1", children: [
1984
+ /* @__PURE__ */ jsx11(Label, { htmlFor: "rotation", className: "text-xs", children: "Rotation" }),
1985
+ /* @__PURE__ */ jsx11(
1986
+ Input,
1987
+ {
1988
+ id: "rotation",
1989
+ type: "number",
1990
+ step: 1,
1991
+ value: getDisplayValue("rotation"),
1992
+ onChange: (e) => handleInputChange("rotation", e.target.value),
1993
+ onBlur: () => handleInputBlur("rotation"),
1994
+ className: "h-8 flex-1"
1995
+ }
1996
+ )
1997
+ ] }),
1998
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-2 items-start flex-1", children: [
1999
+ /* @__PURE__ */ jsx11(Label, { htmlFor: "opacity", className: "text-xs", children: "Opacity" }),
2000
+ /* @__PURE__ */ jsx11(
2001
+ Input,
2002
+ {
2003
+ id: "opacity",
2004
+ type: "number",
2005
+ step: 0.01,
2006
+ min: 0,
2007
+ max: 1,
2008
+ value: getDisplayValue("opacity"),
2009
+ onChange: (e) => handleInputChange("opacity", e.target.value),
2010
+ onBlur: () => handleInputBlur("opacity"),
2011
+ className: "h-8 flex-1"
2012
+ }
2013
+ )
2014
+ ] })
2015
+ ] }),
2016
+ /* @__PURE__ */ jsx11(Separator, { className: "my-4" }),
2017
+ schema.length > 0 && /* @__PURE__ */ jsxs5(Fragment3, { children: [
2018
+ /* @__PURE__ */ jsx11("h4", { className: "text-sm font-medium mb-3", children: "Properties" }),
2019
+ /* @__PURE__ */ jsx11("div", { className: "space-y-3", children: schema.map(
2020
+ (field) => renderField(
2021
+ field,
2022
+ selectedElement.props,
2023
+ editingValues,
2024
+ handlePropChange,
2025
+ handleColorChange,
2026
+ mode
2027
+ )
2028
+ ) })
2029
+ ] })
2030
+ ] })
2031
+ ] })
2032
+ ] });
2033
+ };
2034
+ function renderField(field, props, editingValues, onChange, onColorChange, mode) {
2035
+ const value = editingValues[field.name] ?? props[field.name] ?? field.defaultValue;
2036
+ switch (field.type) {
2037
+ case "custom":
2038
+ if (!field.customRenderer) {
2039
+ console.error(`Custom field "${field.name}" has no customRenderer defined`);
2040
+ return null;
2041
+ }
2042
+ const CustomComponent = field.customRenderer;
2043
+ return /* @__PURE__ */ jsx11("div", { children: /* @__PURE__ */ jsx11(
2044
+ CustomComponent,
2045
+ {
2046
+ value,
2047
+ onChange: (newValue) => onChange(field.name, newValue),
2048
+ field,
2049
+ elementProps: props,
2050
+ mode
2051
+ }
2052
+ ) }, field.name);
2053
+ case "string":
2054
+ const isMultiline = field.name === "content" || field.name === "text";
2055
+ return /* @__PURE__ */ jsxs5("div", { children: [
2056
+ /* @__PURE__ */ jsx11(Label, { htmlFor: field.name, className: "text-xs", children: field.label }),
2057
+ isMultiline ? /* @__PURE__ */ jsx11(
2058
+ Textarea,
2059
+ {
2060
+ id: field.name,
2061
+ value: value || "",
2062
+ onChange: (e) => onChange(field.name, e.target.value),
2063
+ className: "mt-1",
2064
+ rows: 3
2065
+ }
2066
+ ) : /* @__PURE__ */ jsx11(
2067
+ Input,
2068
+ {
2069
+ id: field.name,
2070
+ value: value || "",
2071
+ onChange: (e) => onChange(field.name, e.target.value),
2072
+ className: "h-8 mt-1"
2073
+ }
2074
+ ),
2075
+ field.description && /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground mt-1", children: field.description })
2076
+ ] }, field.name);
2077
+ case "number":
2078
+ return /* @__PURE__ */ jsxs5("div", { children: [
2079
+ /* @__PURE__ */ jsx11(Label, { htmlFor: field.name, className: "text-xs", children: field.label }),
2080
+ /* @__PURE__ */ jsx11(
2081
+ Input,
2082
+ {
2083
+ id: field.name,
2084
+ type: "number",
2085
+ value: value ?? 0,
2086
+ onChange: (e) => onChange(field.name, parseFloat(Number(e.target.value).toFixed(2))),
2087
+ min: field.min,
2088
+ max: field.max,
2089
+ step: field.step || 1,
2090
+ className: "h-8 mt-1"
2091
+ }
2092
+ ),
2093
+ field.description && /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground mt-1", children: field.description })
2094
+ ] }, field.name);
2095
+ case "color":
2096
+ return /* @__PURE__ */ jsxs5("div", { children: [
2097
+ /* @__PURE__ */ jsx11(Label, { htmlFor: field.name, className: "text-xs", children: field.label }),
2098
+ /* @__PURE__ */ jsx11(
2099
+ Input,
2100
+ {
2101
+ id: field.name,
2102
+ type: "color",
2103
+ value: value || "#000000",
2104
+ onChange: (e) => onColorChange(field.name, e.target.value),
2105
+ className: "h-10 mt-1"
2106
+ }
2107
+ ),
2108
+ field.description && /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground mt-1", children: field.description })
2109
+ ] }, field.name);
2110
+ case "select":
2111
+ return /* @__PURE__ */ jsxs5("div", { children: [
2112
+ /* @__PURE__ */ jsx11(Label, { htmlFor: field.name, className: "text-xs", children: field.label }),
2113
+ /* @__PURE__ */ jsxs5(
2114
+ Select,
2115
+ {
2116
+ value: value || field.defaultValue || "",
2117
+ onValueChange: (newValue) => onChange(field.name, newValue),
2118
+ children: [
2119
+ /* @__PURE__ */ jsx11(SelectTrigger, { className: "h-8 mt-1", children: /* @__PURE__ */ jsx11(SelectValue, { placeholder: field.label }) }),
2120
+ /* @__PURE__ */ jsx11(SelectContent, { children: field.options?.map((option) => /* @__PURE__ */ jsx11(SelectItem, { value: String(option.value), children: option.label }, option.value)) })
2121
+ ]
2122
+ }
2123
+ ),
2124
+ field.description && /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground mt-1", children: field.description })
2125
+ ] }, field.name);
2126
+ case "boolean":
2127
+ return /* @__PURE__ */ jsxs5("div", { className: "flex items-start space-x-2", children: [
2128
+ /* @__PURE__ */ jsx11(
2129
+ Checkbox,
2130
+ {
2131
+ id: field.name,
2132
+ checked: Boolean(value),
2133
+ onCheckedChange: (checked) => onChange(field.name, checked),
2134
+ className: "mt-1"
2135
+ }
2136
+ ),
2137
+ /* @__PURE__ */ jsxs5("div", { children: [
2138
+ /* @__PURE__ */ jsx11(Label, { htmlFor: field.name, className: "text-xs font-medium cursor-pointer", children: field.label }),
2139
+ field.description && /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: field.description })
2140
+ ] })
2141
+ ] }, field.name);
2142
+ case "slider":
2143
+ return /* @__PURE__ */ jsxs5("div", { children: [
2144
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between mb-2", children: [
2145
+ /* @__PURE__ */ jsx11(Label, { className: "text-xs", children: field.label }),
2146
+ /* @__PURE__ */ jsx11("span", { className: "text-xs text-muted-foreground", children: value })
2147
+ ] }),
2148
+ /* @__PURE__ */ jsx11(
2149
+ Slider,
2150
+ {
2151
+ value: [Number(value ?? field.defaultValue ?? 0)],
2152
+ onValueChange: ([newValue]) => onChange(field.name, newValue),
2153
+ min: field.min || 0,
2154
+ max: field.max || 100,
2155
+ step: field.step || 1,
2156
+ className: "mt-1"
2157
+ }
2158
+ ),
2159
+ field.description && /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground mt-1", children: field.description })
2160
+ ] }, field.name);
2161
+ case "image":
2162
+ return /* @__PURE__ */ jsxs5("div", { children: [
2163
+ /* @__PURE__ */ jsx11(Label, { htmlFor: field.name, className: "text-xs", children: field.label }),
2164
+ /* @__PURE__ */ jsx11(
2165
+ Input,
2166
+ {
2167
+ id: field.name,
2168
+ value: value || "",
2169
+ onChange: (e) => onChange(field.name, e.target.value),
2170
+ className: "h-8 mt-1",
2171
+ placeholder: "Image URL or path"
2172
+ }
2173
+ ),
2174
+ field.description && /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground mt-1", children: field.description })
2175
+ ] }, field.name);
2176
+ default:
2177
+ return null;
2178
+ }
2179
+ }
2180
+
2181
+ // src/components/LayersPanel.tsx
2182
+ import React16 from "react";
2183
+
2184
+ // src/ui/badge.tsx
2185
+ import { cva as cva2 } from "class-variance-authority";
2186
+ import { jsx as jsx12 } from "react/jsx-runtime";
2187
+ var badgeVariants = cva2(
2188
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
2189
+ {
2190
+ variants: {
2191
+ variant: {
2192
+ default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
2193
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
2194
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
2195
+ outline: "text-foreground"
2196
+ }
2197
+ },
2198
+ defaultVariants: {
2199
+ variant: "default"
2200
+ }
2201
+ }
2202
+ );
2203
+ function Badge({ className, variant, ...props }) {
2204
+ return /* @__PURE__ */ jsx12("div", { className: cn(badgeVariants({ variant }), className), ...props });
2205
+ }
2206
+
2207
+ // src/ui/scroll-area.tsx
2208
+ import * as React12 from "react";
2209
+ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
2210
+ import { jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
2211
+ var ScrollArea = React12.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs6(
2212
+ ScrollAreaPrimitive.Root,
2213
+ {
2214
+ ref,
2215
+ className: cn("relative overflow-hidden", className),
2216
+ ...props,
2217
+ children: [
2218
+ /* @__PURE__ */ jsx13(ScrollAreaPrimitive.Viewport, { className: "h-full w-full rounded-[inherit]", children }),
2219
+ /* @__PURE__ */ jsx13(ScrollBar, {}),
2220
+ /* @__PURE__ */ jsx13(ScrollAreaPrimitive.Corner, {})
2221
+ ]
2222
+ }
2223
+ ));
2224
+ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
2225
+ var ScrollBar = React12.forwardRef(({ className, orientation = "vertical", ...props }, ref) => /* @__PURE__ */ jsx13(
2226
+ ScrollAreaPrimitive.ScrollAreaScrollbar,
2227
+ {
2228
+ ref,
2229
+ orientation,
2230
+ className: cn(
2231
+ "flex touch-none select-none transition-colors",
2232
+ orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]",
2233
+ orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-[1px]",
2234
+ className
2235
+ ),
2236
+ ...props,
2237
+ children: /* @__PURE__ */ jsx13(ScrollAreaPrimitive.ScrollAreaThumb, { className: "relative flex-1 rounded-full bg-border" })
2238
+ }
2239
+ ));
2240
+ ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
2241
+
2242
+ // src/ui/general/TooltipButton.tsx
2243
+ import * as React15 from "react";
2244
+
2245
+ // src/ui/button.tsx
2246
+ import * as React13 from "react";
2247
+ import { Slot } from "@radix-ui/react-slot";
2248
+ import { cva as cva3 } from "class-variance-authority";
2249
+ import { jsx as jsx14 } from "react/jsx-runtime";
2250
+ var buttonVariants = cva3(
2251
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
2252
+ {
2253
+ variants: {
2254
+ variant: {
2255
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
2256
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
2257
+ outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
2258
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
2259
+ ghost: "hover:bg-accent hover:text-accent-foreground",
2260
+ link: "text-primary underline-offset-4 hover:underline"
2261
+ },
2262
+ size: {
2263
+ default: "h-10 px-4 py-2",
2264
+ sm: "h-9 rounded-md px-3",
2265
+ lg: "h-11 rounded-md px-8",
2266
+ icon: "h-10 w-10"
2267
+ }
2268
+ },
2269
+ defaultVariants: {
2270
+ variant: "default",
2271
+ size: "default"
2272
+ }
2273
+ }
2274
+ );
2275
+ var Button = React13.forwardRef(
2276
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
2277
+ const Comp = asChild ? Slot : "button";
2278
+ return /* @__PURE__ */ jsx14(
2279
+ Comp,
2280
+ {
2281
+ className: cn(buttonVariants({ variant, size, className })),
2282
+ ref,
2283
+ ...props
2284
+ }
2285
+ );
2286
+ }
2287
+ );
2288
+ Button.displayName = "Button";
2289
+
2290
+ // src/ui/tooltip.tsx
2291
+ import * as React14 from "react";
2292
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
2293
+ import { jsx as jsx15 } from "react/jsx-runtime";
2294
+ var TooltipProvider = TooltipPrimitive.Provider;
2295
+ var Tooltip = TooltipPrimitive.Root;
2296
+ var TooltipTrigger = TooltipPrimitive.Trigger;
2297
+ var TooltipContent = React14.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx15(
2298
+ TooltipPrimitive.Content,
2299
+ {
2300
+ ref,
2301
+ sideOffset,
2302
+ className: cn(
2303
+ "z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]",
2304
+ className
2305
+ ),
2306
+ ...props
2307
+ }
2308
+ ));
2309
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
2310
+
2311
+ // src/ui/general/TooltipWrapper.tsx
2312
+ import { jsx as jsx16, jsxs as jsxs7 } from "react/jsx-runtime";
2313
+ var TooltipWrapper = ({
2314
+ tooltip,
2315
+ shortcut,
2316
+ tooltipSide = "top",
2317
+ tooltipAlign = "center",
2318
+ tooltipDelay = 700,
2319
+ children,
2320
+ triggerClassName = "flex-1",
2321
+ asChild = true
2322
+ }) => {
2323
+ return /* @__PURE__ */ jsx16(TooltipProvider, { delayDuration: tooltipDelay, children: /* @__PURE__ */ jsxs7(Tooltip, { children: [
2324
+ /* @__PURE__ */ jsx16(TooltipTrigger, { className: triggerClassName, asChild, children }),
2325
+ /* @__PURE__ */ jsx16(TooltipContent, { side: tooltipSide, align: tooltipAlign, children: /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
2326
+ /* @__PURE__ */ jsx16("span", { children: tooltip }),
2327
+ shortcut && /* @__PURE__ */ jsx16("kbd", { className: "inline-flex items-center px-1.5 py-0.5 text-xs font-mono bg-muted text-gray rounded border", children: shortcut })
2328
+ ] }) })
2329
+ ] }) });
2330
+ };
2331
+ TooltipWrapper.displayName = "TooltipWrapper";
2332
+
2333
+ // src/ui/general/TooltipButton.tsx
2334
+ import { jsx as jsx17 } from "react/jsx-runtime";
2335
+ var TooltipButton = React15.forwardRef(
2336
+ ({
2337
+ tooltip,
2338
+ shortcut,
2339
+ tooltipSide = "top",
2340
+ tooltipAlign = "center",
2341
+ tooltipDelay = 700,
2342
+ children,
2343
+ className,
2344
+ ...props
2345
+ }, ref) => {
2346
+ return /* @__PURE__ */ jsx17(
2347
+ TooltipWrapper,
2348
+ {
2349
+ tooltip,
2350
+ shortcut,
2351
+ tooltipSide,
2352
+ tooltipAlign,
2353
+ tooltipDelay,
2354
+ asChild: true,
2355
+ triggerClassName: "",
2356
+ children: /* @__PURE__ */ jsx17(Button, { ref, className, ...props, children })
2357
+ }
2358
+ );
2359
+ }
2360
+ );
2361
+ TooltipButton.displayName = "TooltipButton";
2362
+
2363
+ // src/components/LayersPanel.tsx
2364
+ import {
2365
+ Eye,
2366
+ EyeOff,
2367
+ Lock,
2368
+ Unlock,
2369
+ Trash2,
2370
+ ChevronDown as ChevronDown2,
2371
+ Type as Type2,
2372
+ Image as ImageIcon,
2373
+ Layers,
2374
+ ChevronUp as ChevronUp2,
2375
+ Filter
2376
+ } from "lucide-react";
2377
+ import { jsx as jsx18, jsxs as jsxs8 } from "react/jsx-runtime";
2378
+ var getElementIcon = (type) => {
2379
+ switch (type) {
2380
+ case "text":
2381
+ return /* @__PURE__ */ jsx18(Type2, { className: "h-4 w-4" });
2382
+ case "image":
2383
+ return /* @__PURE__ */ jsx18(ImageIcon, { className: "h-4 w-4" });
2384
+ default:
2385
+ return /* @__PURE__ */ jsx18(Layers, { className: "h-4 w-4" });
2386
+ }
2387
+ };
2388
+ var getElementDisplayName = (element, renderer) => {
2389
+ if (element.displayName) return element.displayName;
2390
+ if (renderer?.displayName) return renderer.displayName;
2391
+ if (element.type === "text" && element.props.content) {
2392
+ const preview = element.props.content.substring(0, 20);
2393
+ return `${preview}${element.props.content.length > 20 ? "..." : ""}`;
2394
+ }
2395
+ return element.type.charAt(0).toUpperCase() + element.type.slice(1);
2396
+ };
2397
+ var LayersPanel = ({
2398
+ elements,
2399
+ selectedElementId,
2400
+ api,
2401
+ elementRenderers,
2402
+ style,
2403
+ className
2404
+ }) => {
2405
+ const [filter, setFilter] = React16.useState("all");
2406
+ const availableTypes = Array.from(elementRenderers.keys());
2407
+ const sortedElements = [...elements].sort((a, b) => b.zIndex - a.zIndex);
2408
+ const filteredElements = sortedElements.filter((el) => {
2409
+ if (filter === "all") return true;
2410
+ return el.type === filter;
2411
+ });
2412
+ const handleMoveUp = (element, e) => {
2413
+ e.stopPropagation();
2414
+ const currentIndex = elements.findIndex((el) => el.id === element.id);
2415
+ if (currentIndex < elements.length - 1) {
2416
+ api.reorderElement(element.id, currentIndex + 1);
2417
+ }
2418
+ };
2419
+ const handleMoveDown = (element, e) => {
2420
+ e.stopPropagation();
2421
+ const currentIndex = elements.findIndex((el) => el.id === element.id);
2422
+ if (currentIndex > 0) {
2423
+ api.reorderElement(element.id, currentIndex - 1);
2424
+ }
2425
+ };
2426
+ return /* @__PURE__ */ jsxs8("div", { className: cn("flex flex-col bg-card border-l", className), style, children: [
2427
+ /* @__PURE__ */ jsxs8("div", { className: "flex h-10 items-center justify-between px-3 py-2 border-b bg-popover", children: [
2428
+ /* @__PURE__ */ jsx18("h3", { className: "text-sm font-semibold", children: "Layers" }),
2429
+ /* @__PURE__ */ jsx18(Badge, { variant: "default", className: "text-xs", children: filteredElements.length })
2430
+ ] }),
2431
+ /* @__PURE__ */ jsxs8("div", { className: "flex gap-2 items-center p-3 border-b", children: [
2432
+ /* @__PURE__ */ jsx18(Filter, { className: "icon" }),
2433
+ /* @__PURE__ */ jsxs8(Select, { value: filter, onValueChange: (value) => setFilter(value), children: [
2434
+ /* @__PURE__ */ jsx18(SelectTrigger, { id: "layer-filter", className: "h-8", children: /* @__PURE__ */ jsx18(SelectValue, {}) }),
2435
+ /* @__PURE__ */ jsxs8(SelectContent, { children: [
2436
+ /* @__PURE__ */ jsx18(SelectItem, { value: "all", children: /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
2437
+ /* @__PURE__ */ jsx18(Layers, { className: "h-4 w-4" }),
2438
+ "All Elements"
2439
+ ] }) }),
2440
+ availableTypes.map((type) => /* @__PURE__ */ jsx18(SelectItem, { value: type, children: /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
2441
+ getElementIcon(type),
2442
+ type.charAt(0).toUpperCase() + type.slice(1),
2443
+ " Only"
2444
+ ] }) }, type))
2445
+ ] })
2446
+ ] })
2447
+ ] }),
2448
+ /* @__PURE__ */ jsx18(ScrollArea, { className: "flex-1", children: /* @__PURE__ */ jsx18("div", { className: "p-2 space-y-1", children: filteredElements.length === 0 ? /* @__PURE__ */ jsx18("div", { className: "text-center py-8 text-sm text-muted-foreground", children: filter === "all" ? "No elements yet" : `No ${filter} elements` }) : filteredElements.map((element) => {
2449
+ const renderer = elementRenderers.get(element.type);
2450
+ const isSelected = element.id === selectedElementId;
2451
+ const isVisible = element.visible !== false;
2452
+ const isLocked = element.locked === true;
2453
+ return /* @__PURE__ */ jsxs8(
2454
+ "div",
2455
+ {
2456
+ className: cn(
2457
+ "group flex items-center gap-2 px-2 py-2 rounded cursor-pointer transition-all",
2458
+ isSelected ? "bg-primary/15 border border-primary/30 shadow-sm" : "hover:bg-muted/70",
2459
+ isLocked && "opacity-60",
2460
+ !isVisible && "opacity-40"
2461
+ ),
2462
+ onClick: () => !isLocked && api.selectElement(element.id),
2463
+ children: [
2464
+ /* @__PURE__ */ jsx18("div", { className: "text-muted-foreground", children: getElementIcon(element.type) }),
2465
+ /* @__PURE__ */ jsxs8("div", { className: "flex-1 min-w-0", children: [
2466
+ /* @__PURE__ */ jsx18("div", { className: "text-xs font-medium truncate", children: getElementDisplayName(element, renderer) }),
2467
+ /* @__PURE__ */ jsx18("div", { className: "flex items-center gap-2 text-[10px] text-muted-foreground", children: /* @__PURE__ */ jsxs8("span", { children: [
2468
+ "z:",
2469
+ element.zIndex
2470
+ ] }) })
2471
+ ] }),
2472
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity", children: [
2473
+ /* @__PURE__ */ jsxs8("div", { className: "flex flex-col", children: [
2474
+ element.zIndex < elements.length - 1 && /* @__PURE__ */ jsx18(
2475
+ TooltipButton,
2476
+ {
2477
+ variant: "ghost",
2478
+ size: "icon",
2479
+ className: "h-3 w-6",
2480
+ onClick: (e) => handleMoveUp(element, e),
2481
+ tooltip: "Move Upward",
2482
+ tooltipDelay: 500,
2483
+ children: /* @__PURE__ */ jsx18(ChevronUp2, { className: "h-3 w-3" })
2484
+ }
2485
+ ),
2486
+ element.zIndex > 0 && /* @__PURE__ */ jsx18(
2487
+ TooltipButton,
2488
+ {
2489
+ variant: "ghost",
2490
+ size: "icon",
2491
+ className: "h-3 w-6",
2492
+ onClick: (e) => handleMoveDown(element, e),
2493
+ tooltip: "Move Downward",
2494
+ tooltipDelay: 500,
2495
+ children: /* @__PURE__ */ jsx18(ChevronDown2, { className: "h-3 w-3" })
2496
+ }
2497
+ )
2498
+ ] }),
2499
+ /* @__PURE__ */ jsx18(
2500
+ TooltipButton,
2501
+ {
2502
+ variant: "ghost",
2503
+ size: "icon",
2504
+ className: "h-6 w-6",
2505
+ onClick: (e) => {
2506
+ e.stopPropagation();
2507
+ api.updateElement(element.id, { visible: !isVisible });
2508
+ },
2509
+ tooltip: isVisible ? "Hide" : "Show",
2510
+ tooltipDelay: 500,
2511
+ children: isVisible ? /* @__PURE__ */ jsx18(Eye, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx18(EyeOff, { className: "h-3 w-3" })
2512
+ }
2513
+ ),
2514
+ /* @__PURE__ */ jsx18(
2515
+ TooltipButton,
2516
+ {
2517
+ variant: "ghost",
2518
+ size: "icon",
2519
+ className: "h-6 w-6",
2520
+ onClick: (e) => {
2521
+ e.stopPropagation();
2522
+ api.updateElement(element.id, { locked: !isLocked });
2523
+ },
2524
+ tooltip: isLocked ? "Unlock" : "Lock",
2525
+ tooltipDelay: 500,
2526
+ children: isLocked ? /* @__PURE__ */ jsx18(Lock, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx18(Unlock, { className: "h-3 w-3" })
2527
+ }
2528
+ ),
2529
+ /* @__PURE__ */ jsx18(
2530
+ TooltipButton,
2531
+ {
2532
+ variant: "ghost",
2533
+ size: "icon",
2534
+ className: "h-6 w-6 text-destructive hover:text-destructive",
2535
+ onClick: (e) => {
2536
+ e.stopPropagation();
2537
+ api.removeElement(element.id);
2538
+ },
2539
+ tooltip: "Delete",
2540
+ tooltipDelay: 500,
2541
+ children: /* @__PURE__ */ jsx18(Trash2, { className: "h-3 w-3" })
2542
+ }
2543
+ )
2544
+ ] })
2545
+ ]
2546
+ },
2547
+ element.id
2548
+ );
2549
+ }) }) }),
2550
+ /* @__PURE__ */ jsx18("div", { className: "border-t p-2", children: /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [
2551
+ /* @__PURE__ */ jsxs8("span", { children: [
2552
+ elements.filter((e) => e.visible !== false).length,
2553
+ " visible"
2554
+ ] }),
2555
+ /* @__PURE__ */ jsx18("span", { children: "\u2022" }),
2556
+ /* @__PURE__ */ jsxs8("span", { children: [
2557
+ elements.filter((e) => e.locked === true).length,
2558
+ " locked"
2559
+ ] })
2560
+ ] }) })
2561
+ ] });
2562
+ };
2563
+
2564
+ // src/components/VisualEditorWorkspace.tsx
2565
+ import React20, { useCallback as useCallback4, useImperativeHandle } from "react";
2566
+
2567
+ // src/components/Topbar.tsx
2568
+ import { Undo, Redo, Trash2 as Trash22, Download, Upload, Copy, Clipboard, CopyPlus, Image as Image2, X } from "lucide-react";
2569
+
2570
+ // src/ui/switch.tsx
2571
+ import * as React17 from "react";
2572
+ import * as SwitchPrimitives from "@radix-ui/react-switch";
2573
+ import { jsx as jsx19 } from "react/jsx-runtime";
2574
+ var Switch = React17.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx19(
2575
+ SwitchPrimitives.Root,
2576
+ {
2577
+ className: cn(
2578
+ "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-secondary data-[state=unchecked]:bg-input",
2579
+ className
2580
+ ),
2581
+ ...props,
2582
+ ref,
2583
+ children: /* @__PURE__ */ jsx19(
2584
+ SwitchPrimitives.Thumb,
2585
+ {
2586
+ className: cn(
2587
+ "pointer-events-none block h-5 w-5 rounded-full bg-primary shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0 data-[state=checked]:bg-background"
2588
+ )
2589
+ }
2590
+ )
2591
+ }
2592
+ ));
2593
+ Switch.displayName = SwitchPrimitives.Root.displayName;
2594
+
2595
+ // src/components/CustomActionRenderer.tsx
2596
+ import { Plus } from "lucide-react";
2597
+ import { jsx as jsx20, jsxs as jsxs9 } from "react/jsx-runtime";
2598
+ var CustomActionRenderer = ({
2599
+ action,
2600
+ api,
2601
+ layout = "horizontal"
2602
+ }) => {
2603
+ const isDisabled = (disabled) => {
2604
+ if (typeof disabled === "function") {
2605
+ return disabled(api);
2606
+ }
2607
+ return disabled || false;
2608
+ };
2609
+ const isVertical = layout === "vertical";
2610
+ switch (action.type) {
2611
+ case "button":
2612
+ return /* @__PURE__ */ jsx20(
2613
+ TooltipButton,
2614
+ {
2615
+ variant: isVertical ? "outline" : "ghost",
2616
+ size: isVertical ? "sm" : "icon",
2617
+ onClick: () => action.onClick(api),
2618
+ disabled: isDisabled(action.disabled),
2619
+ className: isVertical ? "icon-button" : "h-8 w-8",
2620
+ tooltip: action.label,
2621
+ shortcut: action.shortcut,
2622
+ tooltipDelay: 500,
2623
+ tooltipSide: isVertical ? "right" : void 0,
2624
+ children: action.icon || /* @__PURE__ */ jsx20(Plus, { className: "h-4 w-4" })
2625
+ },
2626
+ action.id
2627
+ );
2628
+ case "dropdown":
2629
+ return /* @__PURE__ */ jsxs9(
2630
+ "div",
2631
+ {
2632
+ className: isVertical ? "flex flex-col gap-1 px-1" : "flex items-center gap-2",
2633
+ children: [
2634
+ isVertical ? /* @__PURE__ */ jsx20(Label, { className: "text-xs", children: action.label }) : action.icon && /* @__PURE__ */ jsx20("span", { className: "text-muted-foreground", children: action.icon }),
2635
+ /* @__PURE__ */ jsxs9(
2636
+ Select,
2637
+ {
2638
+ value: action.value,
2639
+ onValueChange: (value) => action.onChange(value, api),
2640
+ disabled: isDisabled(action.disabled),
2641
+ children: [
2642
+ /* @__PURE__ */ jsx20(SelectTrigger, { className: isVertical ? "h-8 w-full" : "h-8 w-auto min-w-[120px]", children: /* @__PURE__ */ jsx20(SelectValue, { placeholder: action.placeholder || action.label }) }),
2643
+ /* @__PURE__ */ jsx20(SelectContent, { children: action.options.map((option) => /* @__PURE__ */ jsx20(SelectItem, { value: option.value, children: /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2", children: [
2644
+ option.icon && /* @__PURE__ */ jsx20("span", { children: option.icon }),
2645
+ /* @__PURE__ */ jsx20("span", { children: option.label })
2646
+ ] }) }, option.value)) })
2647
+ ]
2648
+ }
2649
+ )
2650
+ ]
2651
+ },
2652
+ action.id
2653
+ );
2654
+ case "input":
2655
+ return /* @__PURE__ */ jsxs9(
2656
+ "div",
2657
+ {
2658
+ className: isVertical ? "flex flex-col gap-1 px-1" : "flex items-center gap-2",
2659
+ children: [
2660
+ /* @__PURE__ */ jsxs9(Label, { className: "text-xs whitespace-nowrap", children: [
2661
+ action.label,
2662
+ isVertical ? "" : ":"
2663
+ ] }),
2664
+ /* @__PURE__ */ jsx20(
2665
+ Input,
2666
+ {
2667
+ type: action.inputType || "text",
2668
+ value: action.value,
2669
+ onChange: (e) => action.onChange(e.target.value, api),
2670
+ placeholder: action.placeholder,
2671
+ disabled: isDisabled(action.disabled),
2672
+ className: isVertical ? "h-8 w-full" : "h-8 w-32",
2673
+ min: action.min,
2674
+ max: action.max,
2675
+ step: action.step
2676
+ }
2677
+ )
2678
+ ]
2679
+ },
2680
+ action.id
2681
+ );
2682
+ case "color":
2683
+ return /* @__PURE__ */ jsxs9(
2684
+ "div",
2685
+ {
2686
+ className: isVertical ? "flex flex-col gap-1 px-1" : "flex items-center gap-2",
2687
+ children: [
2688
+ /* @__PURE__ */ jsxs9(Label, { className: "text-xs whitespace-nowrap", children: [
2689
+ action.label,
2690
+ isVertical ? "" : ":"
2691
+ ] }),
2692
+ /* @__PURE__ */ jsx20(
2693
+ Input,
2694
+ {
2695
+ type: "color",
2696
+ value: action.value,
2697
+ onChange: (e) => action.onChange(e.target.value, api),
2698
+ disabled: isDisabled(action.disabled),
2699
+ className: isVertical ? "h-8 w-full p-1 cursor-pointer" : "h-8 w-16 p-1 cursor-pointer"
2700
+ }
2701
+ )
2702
+ ]
2703
+ },
2704
+ action.id
2705
+ );
2706
+ case "toggle":
2707
+ return /* @__PURE__ */ jsxs9(
2708
+ "div",
2709
+ {
2710
+ className: isVertical ? "flex items-center justify-between gap-2 px-1" : "flex items-center gap-2",
2711
+ children: [
2712
+ action.icon && /* @__PURE__ */ jsx20("span", { className: "text-muted-foreground", children: action.icon }),
2713
+ /* @__PURE__ */ jsx20(Label, { className: "text-xs whitespace-nowrap", children: action.label }),
2714
+ /* @__PURE__ */ jsx20(
2715
+ Switch,
2716
+ {
2717
+ checked: action.value,
2718
+ onCheckedChange: (checked) => action.onChange(checked, api),
2719
+ disabled: isDisabled(action.disabled)
2720
+ }
2721
+ )
2722
+ ]
2723
+ },
2724
+ action.id
2725
+ );
2726
+ case "separator":
2727
+ return /* @__PURE__ */ jsx20(
2728
+ Separator,
2729
+ {
2730
+ orientation: isVertical ? "horizontal" : "vertical",
2731
+ className: isVertical ? void 0 : "h-6"
2732
+ },
2733
+ action.id
2734
+ );
2735
+ default:
2736
+ return null;
2737
+ }
2738
+ };
2739
+ var CustomActionRenderer_default = CustomActionRenderer;
2740
+
2741
+ // src/components/Topbar.tsx
2742
+ import { Fragment as Fragment4, jsx as jsx21, jsxs as jsxs10 } from "react/jsx-runtime";
2743
+ var Topbar = ({
2744
+ api,
2745
+ canvasSize,
2746
+ canUndo,
2747
+ canRedo,
2748
+ onUndo,
2749
+ onRedo,
2750
+ onExport,
2751
+ onImport,
2752
+ setCanvasSize,
2753
+ backgroundColor,
2754
+ setBackgroundColor,
2755
+ backgroundImage,
2756
+ setBackgroundImage,
2757
+ imageUrls,
2758
+ clipboardRef,
2759
+ enableSnapGuides = true,
2760
+ onSnapGuidesChange,
2761
+ config,
2762
+ style,
2763
+ className
2764
+ }) => {
2765
+ const selectedElement = api.getSelectedElement();
2766
+ const {
2767
+ showUndo = true,
2768
+ showRedo = true,
2769
+ showDelete = true,
2770
+ showCopy = true,
2771
+ showPaste = true,
2772
+ showDuplicate = true,
2773
+ showExport = true,
2774
+ showImport = true,
2775
+ showCanvasSize = true,
2776
+ showSnapGuides = true,
2777
+ actionsStart = [],
2778
+ actionsEnd = [],
2779
+ actionsStartClassName = "",
2780
+ actionsEndClassName = "",
2781
+ customActions = []
2782
+ // Backwards compatibility - defaults to end
2783
+ } = config || {};
2784
+ const defaultSnapGuidesAction = showSnapGuides && onSnapGuidesChange ? [
2785
+ {
2786
+ type: "toggle",
2787
+ id: "snap-guides-toggle",
2788
+ label: "Snap Guides",
2789
+ value: enableSnapGuides,
2790
+ onChange: (value) => {
2791
+ onSnapGuidesChange(value);
2792
+ }
2793
+ }
2794
+ ] : [];
2795
+ const endActions = [...actionsEnd, ...defaultSnapGuidesAction, ...customActions];
2796
+ const handleDelete = () => {
2797
+ if (selectedElement) {
2798
+ api.removeElement(selectedElement.id);
2799
+ }
2800
+ };
2801
+ const handleCopy = () => {
2802
+ const copied = api.copyElement();
2803
+ if (copied && clipboardRef) {
2804
+ clipboardRef.current = copied;
2805
+ }
2806
+ };
2807
+ const handlePaste = () => {
2808
+ if (clipboardRef?.current) {
2809
+ api.pasteElement(clipboardRef.current);
2810
+ }
2811
+ };
2812
+ const handleDuplicate = () => {
2813
+ api.duplicateElement();
2814
+ };
2815
+ const isDisabled = (disabled) => {
2816
+ if (typeof disabled === "function") {
2817
+ return disabled(api);
2818
+ }
2819
+ return disabled || false;
2820
+ };
2821
+ return /* @__PURE__ */ jsxs10(
2822
+ "div",
2823
+ {
2824
+ className: `flex h-12 items-center px-3 py-1 bg-popover border-b gap-2 ${className}`,
2825
+ style,
2826
+ children: [
2827
+ showCanvasSize && /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
2828
+ /* @__PURE__ */ jsx21("span", { className: "text-sm", children: "Canvas:" }),
2829
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1", children: [
2830
+ /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground whitespace-nowrap", children: "W:" }),
2831
+ /* @__PURE__ */ jsx21(
2832
+ Input,
2833
+ {
2834
+ placeholder: "Width",
2835
+ className: "h-8 w-20",
2836
+ type: "number",
2837
+ step: 1,
2838
+ value: canvasSize.width,
2839
+ onChange: (e) => setCanvasSize(Number(Number(e.target.value).toFixed(0)), canvasSize.height)
2840
+ }
2841
+ )
2842
+ ] }),
2843
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1", children: [
2844
+ /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground whitespace-nowrap", children: "H:" }),
2845
+ /* @__PURE__ */ jsx21(
2846
+ Input,
2847
+ {
2848
+ placeholder: "Height",
2849
+ className: "h-8 w-20",
2850
+ type: "number",
2851
+ step: 1,
2852
+ value: canvasSize.height,
2853
+ onChange: (e) => setCanvasSize(canvasSize.width, Number(Number(e.target.value).toFixed(0)))
2854
+ }
2855
+ )
2856
+ ] }),
2857
+ setBackgroundColor && backgroundColor && /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1", children: [
2858
+ /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground whitespace-nowrap", children: "BG:" }),
2859
+ /* @__PURE__ */ jsx21(
2860
+ Input,
2861
+ {
2862
+ placeholder: "#000000",
2863
+ className: "h-8 w-24",
2864
+ type: "color",
2865
+ value: backgroundColor,
2866
+ onChange: (e) => setBackgroundColor(e.target.value)
2867
+ }
2868
+ )
2869
+ ] }),
2870
+ setBackgroundImage && imageUrls && imageUrls.size > 0 && /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1", children: [
2871
+ /* @__PURE__ */ jsx21("span", { className: "text-xs text-muted-foreground whitespace-nowrap", children: "Texture:" }),
2872
+ backgroundImage ? /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1", children: [
2873
+ /* @__PURE__ */ jsxs10(Select, { value: backgroundImage, onValueChange: setBackgroundImage, children: [
2874
+ /* @__PURE__ */ jsx21(SelectTrigger, { className: "h-8 w-32", children: /* @__PURE__ */ jsx21(SelectValue, { placeholder: "None" }) }),
2875
+ /* @__PURE__ */ jsx21(SelectContent, { children: Array.from(imageUrls.keys()).map((imageName) => /* @__PURE__ */ jsx21(SelectItem, { value: imageName, children: imageName }, imageName)) })
2876
+ ] }),
2877
+ /* @__PURE__ */ jsx21(
2878
+ Button,
2879
+ {
2880
+ variant: "ghost",
2881
+ size: "icon",
2882
+ className: "h-8 w-8",
2883
+ onClick: () => setBackgroundImage(""),
2884
+ children: /* @__PURE__ */ jsx21(X, { className: "h-3 w-3" })
2885
+ }
2886
+ )
2887
+ ] }) : /* @__PURE__ */ jsxs10(Select, { value: "", onValueChange: setBackgroundImage, children: [
2888
+ /* @__PURE__ */ jsx21(SelectTrigger, { className: "h-8 w-32", children: /* @__PURE__ */ jsx21(SelectValue, { placeholder: "None", children: /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
2889
+ /* @__PURE__ */ jsx21(Image2, { className: "h-3 w-3" }),
2890
+ /* @__PURE__ */ jsx21("span", { children: "None" })
2891
+ ] }) }) }),
2892
+ /* @__PURE__ */ jsx21(SelectContent, { children: Array.from(imageUrls.keys()).map((imageName) => /* @__PURE__ */ jsx21(SelectItem, { value: imageName, children: imageName }, imageName)) })
2893
+ ] })
2894
+ ] })
2895
+ ] }),
2896
+ /* @__PURE__ */ jsxs10("div", { className: "flex w-full gap-2", children: [
2897
+ actionsStart.length > 0 && /* @__PURE__ */ jsxs10(Fragment4, { children: [
2898
+ /* @__PURE__ */ jsx21("div", { className: `flex items-center gap-1 ${actionsStartClassName}`, children: actionsStart.map((action) => /* @__PURE__ */ jsx21(
2899
+ CustomActionRenderer_default,
2900
+ {
2901
+ action,
2902
+ api,
2903
+ layout: "horizontal"
2904
+ },
2905
+ action.id
2906
+ )) }),
2907
+ (showUndo || showRedo || showDelete || showExport && onExport || showImport && onImport || endActions.length > 0) && /* @__PURE__ */ jsx21(Separator, { orientation: "vertical", className: "h-6" })
2908
+ ] }),
2909
+ (showUndo || showRedo) && /* @__PURE__ */ jsxs10(Fragment4, { children: [
2910
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1", children: [
2911
+ showUndo && /* @__PURE__ */ jsx21(
2912
+ TooltipButton,
2913
+ {
2914
+ variant: "ghost",
2915
+ size: "icon",
2916
+ onClick: onUndo,
2917
+ disabled: !canUndo,
2918
+ className: "h-8 w-8",
2919
+ tooltip: "Undo",
2920
+ shortcut: "Ctrl+Z",
2921
+ tooltipDelay: 500,
2922
+ children: /* @__PURE__ */ jsx21(Undo, { className: "h-4 w-4" })
2923
+ }
2924
+ ),
2925
+ showRedo && /* @__PURE__ */ jsx21(
2926
+ TooltipButton,
2927
+ {
2928
+ variant: "ghost",
2929
+ size: "icon",
2930
+ onClick: onRedo,
2931
+ disabled: !canRedo,
2932
+ className: "h-8 w-8",
2933
+ tooltip: "Redo",
2934
+ shortcut: "Ctrl+Y",
2935
+ tooltipDelay: 500,
2936
+ children: /* @__PURE__ */ jsx21(Redo, { className: "h-4 w-4" })
2937
+ }
2938
+ )
2939
+ ] }),
2940
+ (showCopy || showPaste || showDuplicate || showDelete || showExport && onExport || showImport && onImport || endActions.length > 0) && /* @__PURE__ */ jsx21(Separator, { orientation: "vertical", className: "h-6" })
2941
+ ] }),
2942
+ (showCopy || showPaste || showDuplicate || showDelete) && /* @__PURE__ */ jsxs10(Fragment4, { children: [
2943
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1", children: [
2944
+ showCopy && /* @__PURE__ */ jsx21(
2945
+ TooltipButton,
2946
+ {
2947
+ variant: "ghost",
2948
+ size: "icon",
2949
+ onClick: handleCopy,
2950
+ disabled: !selectedElement,
2951
+ className: "h-8 w-8",
2952
+ tooltip: "Copy",
2953
+ shortcut: "Ctrl+C",
2954
+ tooltipDelay: 500,
2955
+ children: /* @__PURE__ */ jsx21(Copy, { className: "h-4 w-4" })
2956
+ }
2957
+ ),
2958
+ showPaste && /* @__PURE__ */ jsx21(
2959
+ TooltipButton,
2960
+ {
2961
+ variant: "ghost",
2962
+ size: "icon",
2963
+ onClick: handlePaste,
2964
+ disabled: !clipboardRef?.current,
2965
+ className: "h-8 w-8",
2966
+ tooltip: "Paste",
2967
+ shortcut: "Ctrl+V",
2968
+ tooltipDelay: 500,
2969
+ children: /* @__PURE__ */ jsx21(Clipboard, { className: "h-4 w-4" })
2970
+ }
2971
+ ),
2972
+ showDuplicate && /* @__PURE__ */ jsx21(
2973
+ TooltipButton,
2974
+ {
2975
+ variant: "ghost",
2976
+ size: "icon",
2977
+ onClick: handleDuplicate,
2978
+ disabled: !selectedElement,
2979
+ className: "h-8 w-8",
2980
+ tooltip: "Duplicate",
2981
+ shortcut: "Ctrl+D",
2982
+ tooltipDelay: 500,
2983
+ children: /* @__PURE__ */ jsx21(CopyPlus, { className: "h-4 w-4" })
2984
+ }
2985
+ ),
2986
+ showDelete && /* @__PURE__ */ jsx21(
2987
+ TooltipButton,
2988
+ {
2989
+ variant: "ghost",
2990
+ size: "icon",
2991
+ onClick: handleDelete,
2992
+ disabled: !selectedElement,
2993
+ className: "h-8 w-8 text-destructive hover:text-destructive",
2994
+ tooltip: "Delete Selected",
2995
+ shortcut: "Delete",
2996
+ tooltipDelay: 500,
2997
+ children: /* @__PURE__ */ jsx21(Trash22, { className: "h-4 w-4" })
2998
+ }
2999
+ )
3000
+ ] }),
3001
+ (showExport && onExport || showImport && onImport || endActions.length > 0) && /* @__PURE__ */ jsx21(Separator, { orientation: "vertical", className: "h-6" })
3002
+ ] }),
3003
+ (showExport && onExport || showImport && onImport) && /* @__PURE__ */ jsxs10(Fragment4, { children: [
3004
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1", children: [
3005
+ showExport && onExport && /* @__PURE__ */ jsx21(
3006
+ TooltipButton,
3007
+ {
3008
+ variant: "ghost",
3009
+ size: "icon",
3010
+ onClick: onExport,
3011
+ className: "h-8 w-8",
3012
+ tooltip: "Export to JSON",
3013
+ tooltipDelay: 500,
3014
+ children: /* @__PURE__ */ jsx21(Download, { className: "h-4 w-4" })
3015
+ }
3016
+ ),
3017
+ showImport && onImport && /* @__PURE__ */ jsx21(
3018
+ TooltipButton,
3019
+ {
3020
+ variant: "ghost",
3021
+ size: "icon",
3022
+ onClick: onImport,
3023
+ className: "h-8 w-8",
3024
+ tooltip: "Import from JSON",
3025
+ tooltipDelay: 500,
3026
+ children: /* @__PURE__ */ jsx21(Upload, { className: "h-4 w-4" })
3027
+ }
3028
+ )
3029
+ ] }),
3030
+ endActions.length > 0 && /* @__PURE__ */ jsx21(Separator, { orientation: "vertical", className: "h-6" })
3031
+ ] }),
3032
+ endActions.length > 0 && /* @__PURE__ */ jsx21("div", { className: `flex items-center gap-1 ${actionsEndClassName}`, children: endActions.map((action) => /* @__PURE__ */ jsx21(CustomActionRenderer_default, { action, api, layout: "horizontal" }, action.id)) })
3033
+ ] })
3034
+ ]
3035
+ }
3036
+ );
3037
+ };
3038
+
3039
+ // src/components/Canvas.tsx
3040
+ import React19, { useCallback as useCallback3, useRef as useRef3, useState } from "react";
3041
+ import { Stage as Stage2, Layer as Layer2, Rect as Rect2, Image as KonvaImage2 } from "react-konva";
3042
+
3043
+ // src/components/SnapGuides.tsx
3044
+ import { Line } from "react-konva";
3045
+ import { Fragment as Fragment5, jsx as jsx22, jsxs as jsxs11 } from "react/jsx-runtime";
3046
+ var SnapGuides = ({
3047
+ verticalGuides,
3048
+ horizontalGuides,
3049
+ canvasSize
3050
+ }) => {
3051
+ return /* @__PURE__ */ jsxs11(Fragment5, { children: [
3052
+ verticalGuides.map((guide, index) => /* @__PURE__ */ jsx22(
3053
+ Line,
3054
+ {
3055
+ points: [guide.position, 0, guide.position, canvasSize.height],
3056
+ stroke: guide.type === "center" ? "#ff00ff" : "#00ff00",
3057
+ strokeWidth: 1,
3058
+ dash: guide.type === "center" ? [4, 4] : void 0,
3059
+ listening: false
3060
+ },
3061
+ `v-${index}`
3062
+ )),
3063
+ horizontalGuides.map((guide, index) => /* @__PURE__ */ jsx22(
3064
+ Line,
3065
+ {
3066
+ points: [0, guide.position, canvasSize.width, guide.position],
3067
+ stroke: guide.type === "center" ? "#ff00ff" : "#00ff00",
3068
+ strokeWidth: 1,
3069
+ dash: guide.type === "center" ? [4, 4] : void 0,
3070
+ listening: false
3071
+ },
3072
+ `h-${index}`
3073
+ ))
3074
+ ] });
3075
+ };
3076
+
3077
+ // src/components/CentralizedTransformer.tsx
3078
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
3079
+ import { Transformer as Transformer2 } from "react-konva";
3080
+ import { jsx as jsx23 } from "react/jsx-runtime";
3081
+ var CentralizedTransformer = ({
3082
+ selectedElementId,
3083
+ stageRef,
3084
+ isLocked = false,
3085
+ updateTrigger = 0,
3086
+ onTransformEnd,
3087
+ onTransform
3088
+ }) => {
3089
+ const transformerRef = useRef2(null);
3090
+ useEffect2(() => {
3091
+ const transformer = transformerRef.current;
3092
+ const stage = stageRef.current;
3093
+ if (!transformer || !stage || !selectedElementId || isLocked) {
3094
+ if (transformer) {
3095
+ transformer.nodes([]);
3096
+ transformer.getLayer()?.batchDraw();
3097
+ }
3098
+ return;
3099
+ }
3100
+ const findNodeById = (id) => {
3101
+ const layer = stage.getLayers()[0];
3102
+ if (!layer) return null;
3103
+ const findInChildren = (node) => {
3104
+ if (node.attrs.id === id || node.id() === id) {
3105
+ return node;
3106
+ }
3107
+ const children = node.getChildren?.();
3108
+ if (children) {
3109
+ for (const child of children) {
3110
+ const found = findInChildren(child);
3111
+ if (found) return found;
3112
+ }
3113
+ }
3114
+ return null;
3115
+ };
3116
+ return findInChildren(layer);
3117
+ };
3118
+ const selectedNode = findNodeById(selectedElementId);
3119
+ if (selectedNode && selectedNode.draggable()) {
3120
+ transformer.nodes([selectedNode]);
3121
+ transformer.getLayer()?.batchDraw();
3122
+ } else {
3123
+ transformer.nodes([]);
3124
+ transformer.getLayer()?.batchDraw();
3125
+ }
3126
+ }, [selectedElementId, stageRef, isLocked, updateTrigger]);
3127
+ const handleTransform = (e) => {
3128
+ if (!onTransform || !selectedElementId) return;
3129
+ const node = e.target;
3130
+ const scaleX = node.scaleX();
3131
+ const scaleY = node.scaleY();
3132
+ const updates = {
3133
+ position: {
3134
+ x: node.x(),
3135
+ y: node.y()
3136
+ },
3137
+ size: {
3138
+ width: Math.max(5, node.width() * scaleX),
3139
+ height: Math.max(5, node.height() * scaleY)
3140
+ },
3141
+ rotation: node.rotation()
3142
+ };
3143
+ onTransform(selectedElementId, updates);
3144
+ };
3145
+ const handleTransformEnd = (e) => {
3146
+ if (!onTransformEnd || !selectedElementId) return;
3147
+ const node = e.target;
3148
+ const scaleX = node.scaleX();
3149
+ const scaleY = node.scaleY();
3150
+ node.scaleX(1);
3151
+ node.scaleY(1);
3152
+ const updates = {
3153
+ position: {
3154
+ x: node.x(),
3155
+ y: node.y()
3156
+ },
3157
+ size: {
3158
+ width: Math.max(5, node.width() * scaleX),
3159
+ height: Math.max(5, node.height() * scaleY)
3160
+ },
3161
+ rotation: node.rotation()
3162
+ };
3163
+ onTransformEnd(selectedElementId, updates);
3164
+ };
3165
+ if (isLocked || !selectedElementId) {
3166
+ return null;
3167
+ }
3168
+ return /* @__PURE__ */ jsx23(
3169
+ Transformer2,
3170
+ {
3171
+ ref: transformerRef,
3172
+ rotateEnabled: true,
3173
+ rotationSnaps: [0, 90, 180, 270],
3174
+ keepRatio: false,
3175
+ anchorCornerRadius: 2,
3176
+ flipEnabled: false,
3177
+ enabledAnchors: [
3178
+ "top-left",
3179
+ "top-right",
3180
+ "bottom-left",
3181
+ "bottom-right",
3182
+ "middle-left",
3183
+ "middle-right",
3184
+ "top-center",
3185
+ "bottom-center"
3186
+ ],
3187
+ boundBoxFunc: (oldBox, newBox) => {
3188
+ if (newBox.width < 20 || newBox.height < 20) {
3189
+ return oldBox;
3190
+ }
3191
+ return newBox;
3192
+ },
3193
+ onTransform: handleTransform,
3194
+ onTransformEnd: handleTransformEnd
3195
+ }
3196
+ );
3197
+ };
3198
+
3199
+ // src/components/Canvas.tsx
3200
+ import { jsx as jsx24, jsxs as jsxs12 } from "react/jsx-runtime";
3201
+ var Canvas = ({
3202
+ canvasSize,
3203
+ elements,
3204
+ selectedElementId,
3205
+ registry,
3206
+ mode,
3207
+ readonly = false,
3208
+ enableSnapGuides = true,
3209
+ enablePanZoom = true,
3210
+ backgroundImageUrl,
3211
+ hideElements = false,
3212
+ onSelectElement,
3213
+ onTransformElement,
3214
+ style,
3215
+ className = ""
3216
+ }) => {
3217
+ const stageRef = useRef3(null);
3218
+ const containerRef = useRef3(null);
3219
+ const [snapGuides, setSnapGuides] = useState({ vertical: [], horizontal: [] });
3220
+ const [stageScale, setStageScale] = useState(1);
3221
+ const [stagePosition, setStagePosition] = useState({ x: 0, y: 0 });
3222
+ const isPanning = useRef3(false);
3223
+ const [containerSize, setContainerSize] = useState({ width: 800, height: 600 });
3224
+ const [backgroundImage, setBackgroundImage] = useState(null);
3225
+ const [transformerUpdateTrigger, setTransformerUpdateTrigger] = useState(0);
3226
+ React19.useEffect(() => {
3227
+ if (!backgroundImageUrl) {
3228
+ setBackgroundImage(null);
3229
+ return;
3230
+ }
3231
+ const img = new window.Image();
3232
+ img.crossOrigin = "anonymous";
3233
+ img.onload = () => {
3234
+ setBackgroundImage(img);
3235
+ };
3236
+ img.onerror = (err) => {
3237
+ console.error("Failed to load canvas background image:", err);
3238
+ setBackgroundImage(null);
3239
+ };
3240
+ img.src = backgroundImageUrl;
3241
+ return () => {
3242
+ img.onload = null;
3243
+ img.onerror = null;
3244
+ };
3245
+ }, [backgroundImageUrl]);
3246
+ React19.useEffect(() => {
3247
+ const updateSize = () => {
3248
+ if (containerRef.current) {
3249
+ const rect = containerRef.current.getBoundingClientRect();
3250
+ setContainerSize({
3251
+ width: rect.width,
3252
+ height: rect.height
3253
+ });
3254
+ }
3255
+ };
3256
+ updateSize();
3257
+ const resizeObserver = new ResizeObserver(updateSize);
3258
+ if (containerRef.current) {
3259
+ resizeObserver.observe(containerRef.current);
3260
+ }
3261
+ return () => {
3262
+ resizeObserver.disconnect();
3263
+ };
3264
+ }, []);
3265
+ const stageSize = React19.useMemo(() => {
3266
+ const width = Math.max(containerSize.width, canvasSize.width);
3267
+ const height = Math.max(containerSize.height, canvasSize.height);
3268
+ return { width, height };
3269
+ }, [containerSize, canvasSize]);
3270
+ const layerOffset = React19.useMemo(() => {
3271
+ return {
3272
+ x: Math.max(0, (stageSize.width - canvasSize.width) / 2),
3273
+ y: Math.max(0, (stageSize.height - canvasSize.height) / 2)
3274
+ };
3275
+ }, [stageSize, canvasSize]);
3276
+ const handleStageClick = useCallback3(
3277
+ (e) => {
3278
+ if (e.target === e.target.getStage()) {
3279
+ onSelectElement(null);
3280
+ }
3281
+ },
3282
+ [onSelectElement]
3283
+ );
3284
+ const onClearSnapGuides = useCallback3(() => {
3285
+ setSnapGuides({ vertical: [], horizontal: [] });
3286
+ }, []);
3287
+ const onTransformerUpdate = useCallback3(() => {
3288
+ setTransformerUpdateTrigger((prev) => prev + 1);
3289
+ }, []);
3290
+ const handleWheel = useCallback3(
3291
+ (e) => {
3292
+ if (!enablePanZoom) return;
3293
+ e.evt.preventDefault();
3294
+ const stage = stageRef.current;
3295
+ if (!stage) return;
3296
+ const oldScale = stage.scaleX();
3297
+ const pointer = stage.getPointerPosition();
3298
+ const mousePointTo = {
3299
+ x: (pointer.x - stage.x()) / oldScale,
3300
+ y: (pointer.y - stage.y()) / oldScale
3301
+ };
3302
+ const scaleBy = 1.05;
3303
+ const direction = e.evt.deltaY > 0 ? -1 : 1;
3304
+ let newScale = direction > 0 ? oldScale * scaleBy : oldScale / scaleBy;
3305
+ newScale = Math.max(0.1, Math.min(5, newScale));
3306
+ const newPos = {
3307
+ x: pointer.x - mousePointTo.x * newScale,
3308
+ y: pointer.y - mousePointTo.y * newScale
3309
+ };
3310
+ setStageScale(newScale);
3311
+ setStagePosition(newPos);
3312
+ },
3313
+ [enablePanZoom]
3314
+ );
3315
+ const handleMouseDown = useCallback3(
3316
+ (e) => {
3317
+ if (!enablePanZoom) return;
3318
+ if (e.evt.button === 1 || e.evt.button === 0 && e.evt.shiftKey) {
3319
+ e.evt.preventDefault();
3320
+ isPanning.current = true;
3321
+ }
3322
+ },
3323
+ [enablePanZoom]
3324
+ );
3325
+ const handleMouseMove = useCallback3(
3326
+ (e) => {
3327
+ if (!enablePanZoom || !isPanning.current) return;
3328
+ e.evt.preventDefault();
3329
+ const stage = stageRef.current;
3330
+ if (!stage) return;
3331
+ const newPos = {
3332
+ x: stagePosition.x + e.evt.movementX,
3333
+ y: stagePosition.y + e.evt.movementY
3334
+ };
3335
+ setStagePosition(newPos);
3336
+ },
3337
+ [enablePanZoom, stagePosition]
3338
+ );
3339
+ const handleMouseUp = useCallback3(() => {
3340
+ isPanning.current = false;
3341
+ }, []);
3342
+ React19.useEffect(() => {
3343
+ const stage = stageRef.current;
3344
+ if (stage) {
3345
+ stage.scale({ x: stageScale, y: stageScale });
3346
+ stage.position(stagePosition);
3347
+ stage.batchDraw();
3348
+ }
3349
+ }, [stageScale, stagePosition]);
3350
+ const renderElement = useCallback3(
3351
+ (element) => {
3352
+ const isSelected = selectedElementId === element.id;
3353
+ const renderer = registry.get(element.type);
3354
+ if (!renderer) {
3355
+ console.warn(`No renderer found for element type: ${element.type}`);
3356
+ return null;
3357
+ }
3358
+ const RendererComponent = renderer.renderComponent;
3359
+ if (!RendererComponent) {
3360
+ console.warn(`No renderComponent found for element type: ${element.type}`);
3361
+ return null;
3362
+ }
3363
+ const commonProps = {
3364
+ element,
3365
+ isSelected,
3366
+ onSelect: () => !readonly && onSelectElement(element.id),
3367
+ onTransform: (updates) => !readonly && onTransformElement(element.id, updates),
3368
+ // Snapping callbacks
3369
+ allElements: elements,
3370
+ canvasSize,
3371
+ onSnapGuides: enableSnapGuides ? setSnapGuides : void 0,
3372
+ onClearSnapGuides: enableSnapGuides ? onClearSnapGuides : void 0,
3373
+ // Pass element ID for centralized transformer lookup
3374
+ elementId: element.id,
3375
+ // Disable individual transformers (centralized transformer will handle it)
3376
+ disableTransformer: true,
3377
+ // Callback to notify when node structure changes (e.g., image loads)
3378
+ onNodeUpdate: onTransformerUpdate,
3379
+ // Pass mode context to renderers (e.g., cardData for TemplatedText)
3380
+ ...mode?.context || {}
3381
+ };
3382
+ return /* @__PURE__ */ jsx24(RendererComponent, { ...commonProps }, element.id);
3383
+ },
3384
+ [
3385
+ selectedElementId,
3386
+ registry,
3387
+ readonly,
3388
+ onSelectElement,
3389
+ onTransformElement,
3390
+ elements,
3391
+ canvasSize,
3392
+ enableSnapGuides,
3393
+ onClearSnapGuides,
3394
+ onTransformerUpdate,
3395
+ mode
3396
+ ]
3397
+ );
3398
+ const sortedElements = [...elements].sort((a, b) => a.zIndex - b.zIndex);
3399
+ return /* @__PURE__ */ jsx24(
3400
+ "div",
3401
+ {
3402
+ className: `flex flex-1 overflow-hidden items-center justify-center ${className}`,
3403
+ style,
3404
+ children: /* @__PURE__ */ jsxs12(
3405
+ "div",
3406
+ {
3407
+ ref: containerRef,
3408
+ className: "flex relative w-full h-full items-center justify-center overflow-hidden",
3409
+ children: [
3410
+ enablePanZoom && stageScale !== 1 && /* @__PURE__ */ jsxs12(
3411
+ "div",
3412
+ {
3413
+ style: {
3414
+ position: "absolute",
3415
+ top: 10,
3416
+ right: 10,
3417
+ zIndex: 10,
3418
+ background: "rgba(0, 0, 0, 0.7)",
3419
+ color: "white",
3420
+ padding: "4px 12px",
3421
+ borderRadius: "4px",
3422
+ fontSize: "12px",
3423
+ fontWeight: 500,
3424
+ pointerEvents: "none"
3425
+ },
3426
+ children: [
3427
+ Math.round(stageScale * 100),
3428
+ "%"
3429
+ ]
3430
+ }
3431
+ ),
3432
+ /* @__PURE__ */ jsx24(
3433
+ Stage2,
3434
+ {
3435
+ className: "w-full h-full",
3436
+ ref: stageRef,
3437
+ width: stageSize.width,
3438
+ height: stageSize.height,
3439
+ onClick: handleStageClick,
3440
+ onTap: handleStageClick,
3441
+ onWheel: handleWheel,
3442
+ onMouseDown: handleMouseDown,
3443
+ onMouseMove: handleMouseMove,
3444
+ onMouseUp: handleMouseUp,
3445
+ style: {
3446
+ boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
3447
+ cursor: isPanning.current ? "grabbing" : "default"
3448
+ },
3449
+ children: /* @__PURE__ */ jsxs12(Layer2, { x: layerOffset.x, y: layerOffset.y, listening: true, children: [
3450
+ backgroundImage ? /* @__PURE__ */ jsx24(
3451
+ KonvaImage2,
3452
+ {
3453
+ x: 0,
3454
+ y: 0,
3455
+ width: canvasSize.width,
3456
+ height: canvasSize.height,
3457
+ image: backgroundImage,
3458
+ listening: false
3459
+ }
3460
+ ) : /* @__PURE__ */ jsx24(
3461
+ Rect2,
3462
+ {
3463
+ x: 0,
3464
+ y: 0,
3465
+ width: canvasSize.width,
3466
+ height: canvasSize.height,
3467
+ fill: mode?.backgroundColor || "#ffffff",
3468
+ listening: false
3469
+ }
3470
+ ),
3471
+ !hideElements && sortedElements.map(renderElement),
3472
+ enableSnapGuides && /* @__PURE__ */ jsx24(
3473
+ SnapGuides,
3474
+ {
3475
+ verticalGuides: snapGuides.vertical,
3476
+ horizontalGuides: snapGuides.horizontal,
3477
+ canvasSize
3478
+ }
3479
+ ),
3480
+ /* @__PURE__ */ jsx24(
3481
+ CentralizedTransformer,
3482
+ {
3483
+ selectedElementId,
3484
+ stageRef,
3485
+ isLocked: readonly,
3486
+ updateTrigger: transformerUpdateTrigger,
3487
+ onTransform: (elementId, updates) => {
3488
+ if (!enableSnapGuides) return;
3489
+ const element = elements.find((el) => el.id === elementId);
3490
+ if (!element) return;
3491
+ const tempElement = {
3492
+ ...element,
3493
+ position: updates.position || element.position,
3494
+ size: updates.size || element.size,
3495
+ rotation: updates.rotation ?? element.rotation
3496
+ };
3497
+ const snapResult = getSnappingPosition(
3498
+ tempElement,
3499
+ tempElement.position.x,
3500
+ tempElement.position.y,
3501
+ elements.filter((el) => el.id !== elementId),
3502
+ {
3503
+ threshold: 5,
3504
+ snapToElements: true,
3505
+ snapToCanvas: true,
3506
+ canvasSize
3507
+ }
3508
+ );
3509
+ setSnapGuides({
3510
+ vertical: snapResult.verticalGuides,
3511
+ horizontal: snapResult.horizontalGuides
3512
+ });
3513
+ },
3514
+ onTransformEnd: (elementId, updates) => {
3515
+ onTransformElement(elementId, updates);
3516
+ setSnapGuides({ vertical: [], horizontal: [] });
3517
+ }
3518
+ }
3519
+ )
3520
+ ] })
3521
+ }
3522
+ )
3523
+ ]
3524
+ }
3525
+ )
3526
+ }
3527
+ );
3528
+ };
3529
+
3530
+ // src/components/VisualEditorWorkspace.tsx
3531
+ init_editorUtils();
3532
+
3533
+ // src/components/Toolbar.tsx
3534
+ init_editorUtils();
3535
+ import { Plus as Plus2 } from "lucide-react";
3536
+ import { Fragment as Fragment6, jsx as jsx25, jsxs as jsxs13 } from "react/jsx-runtime";
3537
+ var Toolbar = ({
3538
+ api,
3539
+ elementRenderers,
3540
+ canvasSize,
3541
+ config,
3542
+ style,
3543
+ className
3544
+ }) => {
3545
+ const selectedElement = api.getSelectedElement();
3546
+ const {
3547
+ showElementTools = true,
3548
+ hiddenElementTypes = [],
3549
+ toolsStart = [],
3550
+ toolsEnd = [],
3551
+ toolsStartClassName = "",
3552
+ toolsEndClassName = "",
3553
+ customTools = []
3554
+ // Backwards compatibility - defaults to end
3555
+ } = config || {};
3556
+ const endTools = [...toolsEnd, ...customTools];
3557
+ const visibleRenderers = showElementTools ? elementRenderers.filter((renderer) => !hiddenElementTypes.includes(renderer.type)) : [];
3558
+ const handleCreateElement = (renderer) => {
3559
+ const newElement = createElement(renderer.type, renderer.defaultProps, {
3560
+ position: { x: canvasSize.width / 2, y: canvasSize.height / 2 },
3561
+ // Center of canvas
3562
+ size: {
3563
+ width: renderer.defaultSize?.width || 100,
3564
+ height: renderer.defaultSize?.height || 100
3565
+ },
3566
+ zIndex: api.getAllElements().length
3567
+ // Top of stack
3568
+ });
3569
+ api.addElement(newElement);
3570
+ api.selectElement(newElement.id);
3571
+ };
3572
+ const isDisabled = (disabled) => {
3573
+ if (typeof disabled === "function") {
3574
+ return disabled(api);
3575
+ }
3576
+ return disabled || false;
3577
+ };
3578
+ return /* @__PURE__ */ jsxs13(
3579
+ "div",
3580
+ {
3581
+ className: `flex flex-col justify-start items-center bg-popover gap-2 p-2 ${className}`,
3582
+ style,
3583
+ children: [
3584
+ toolsStart.length > 0 && /* @__PURE__ */ jsxs13(Fragment6, { children: [
3585
+ /* @__PURE__ */ jsx25("div", { className: `flex flex-col gap-1 ${toolsStartClassName}`, children: toolsStart.map((tool) => /* @__PURE__ */ jsx25(CustomActionRenderer_default, { action: tool, api, layout: "vertical" }, tool.id)) }),
3586
+ (visibleRenderers.length > 0 || endTools.length > 0) && /* @__PURE__ */ jsx25(Separator, { orientation: "horizontal" })
3587
+ ] }),
3588
+ visibleRenderers.length > 0 && /* @__PURE__ */ jsxs13(Fragment6, { children: [
3589
+ /* @__PURE__ */ jsx25("div", { className: "flex flex-col gap-1", children: visibleRenderers.map((renderer) => /* @__PURE__ */ jsx25(
3590
+ TooltipButton,
3591
+ {
3592
+ variant: "outline",
3593
+ size: "sm",
3594
+ onClick: () => handleCreateElement(renderer),
3595
+ className: "icon-button",
3596
+ tooltip: `Add ${renderer.displayName}`,
3597
+ tooltipDelay: 500,
3598
+ tooltipSide: "right",
3599
+ children: renderer.icon || /* @__PURE__ */ jsx25(Plus2, { className: "h-4 w-4" })
3600
+ },
3601
+ renderer.type
3602
+ )) }),
3603
+ endTools.length > 0 && /* @__PURE__ */ jsx25(Separator, { orientation: "horizontal" })
3604
+ ] }),
3605
+ endTools.length > 0 && /* @__PURE__ */ jsx25("div", { className: `flex flex-col gap-1 ${toolsEndClassName}`, children: endTools.map((tool) => /* @__PURE__ */ jsx25(CustomActionRenderer_default, { action: tool, api, layout: "vertical" }, tool.id)) })
3606
+ ]
3607
+ }
3608
+ );
3609
+ };
3610
+
3611
+ // src/components/VisualEditorWorkspace.tsx
3612
+ import { jsx as jsx26, jsxs as jsxs14 } from "react/jsx-runtime";
3613
+ var VisualEditorWorkspace = ({
3614
+ mode,
3615
+ initialData,
3616
+ width,
3617
+ height,
3618
+ readonly = false,
3619
+ onChange,
3620
+ customElements = [],
3621
+ showTopbar = true,
3622
+ showToolbar = true,
3623
+ showInspector = true,
3624
+ showLayers = true,
3625
+ showAssetPicker = true,
3626
+ showCanvas = true,
3627
+ enableSnapGuides = true,
3628
+ enablePanZoom = true,
3629
+ backgroundImageUrl,
3630
+ hideElements = false,
3631
+ className = "",
3632
+ apiRef
3633
+ }) => {
3634
+ const elementList = React20.useMemo(() => {
3635
+ return [...defaultElements, ...mode?.registeredElements || [], ...customElements];
3636
+ }, [mode, customElements]);
3637
+ const registry = useElementRegistry(elementList);
3638
+ const { state, api, undo, redo, canUndo, canRedo, setCanvasSize } = useEditorState(mode || null);
3639
+ const clipboardRef = React20.useRef(null);
3640
+ const [snapGuidesEnabled, setSnapGuidesEnabled] = React20.useState(enableSnapGuides);
3641
+ const [backgroundColor, setBackgroundColor] = React20.useState(
3642
+ mode?.backgroundColor || "#1a1a1a"
3643
+ );
3644
+ const [backgroundImage, setBackgroundImage] = React20.useState(
3645
+ mode?.backgroundImage || ""
3646
+ );
3647
+ React20.useEffect(() => {
3648
+ setSnapGuidesEnabled(enableSnapGuides);
3649
+ }, [enableSnapGuides]);
3650
+ React20.useEffect(() => {
3651
+ if (mode?.backgroundColor) {
3652
+ setBackgroundColor(mode.backgroundColor);
3653
+ }
3654
+ }, [mode?.backgroundColor]);
3655
+ React20.useEffect(() => {
3656
+ setBackgroundImage(mode?.backgroundImage || "");
3657
+ }, [mode?.backgroundImage]);
3658
+ const canvasSize = state.canvasSize;
3659
+ useImperativeHandle(apiRef, () => api, [api]);
3660
+ React20.useEffect(() => {
3661
+ if (initialData && initialData.elements && initialData.elements.length > 0) {
3662
+ api.loadElements(initialData.elements);
3663
+ if (initialData.width && initialData.height) {
3664
+ setCanvasSize(initialData.width, initialData.height);
3665
+ }
3666
+ }
3667
+ }, []);
3668
+ const availableRenderers = Array.from(registry.getAll().values());
3669
+ const selectedElement = state.selectedElementId ? state.elements.find((el) => el.id === state.selectedElementId) || null : null;
3670
+ const selectedRenderer = selectedElement ? registry.get(selectedElement.type) || null : null;
3671
+ const handleExport = useCallback4(() => {
3672
+ const data = {
3673
+ width: canvasSize.width,
3674
+ height: canvasSize.height,
3675
+ elements: state.elements,
3676
+ metadata: {
3677
+ version: "1.0",
3678
+ mode: mode?.name || "default",
3679
+ created: (/* @__PURE__ */ new Date()).toISOString()
3680
+ }
3681
+ };
3682
+ const json = exportToJSON(data);
3683
+ const blob = new Blob([json], { type: "application/json" });
3684
+ const url = URL.createObjectURL(blob);
3685
+ const a = document.createElement("a");
3686
+ a.href = url;
3687
+ a.download = `canvas-${Date.now()}.json`;
3688
+ a.click();
3689
+ URL.revokeObjectURL(url);
3690
+ }, [state.elements, canvasSize, mode]);
3691
+ const handleImport = useCallback4(() => {
3692
+ const input = document.createElement("input");
3693
+ input.type = "file";
3694
+ input.accept = ".json";
3695
+ input.onchange = async (e) => {
3696
+ const file = e.target.files?.[0];
3697
+ if (!file) return;
3698
+ try {
3699
+ const text = await file.text();
3700
+ const imported = importFromJSON(text);
3701
+ if (imported) {
3702
+ state.elements.forEach((el) => api.removeElement(el.id));
3703
+ imported.elements.forEach((el) => api.addElement(el));
3704
+ if (imported.width && imported.height) {
3705
+ setCanvasSize(imported.width, imported.height);
3706
+ }
3707
+ }
3708
+ } catch (error) {
3709
+ console.error("Failed to import:", error);
3710
+ alert(`Failed to import: ${error.message}`);
3711
+ }
3712
+ };
3713
+ input.click();
3714
+ }, [state.elements, api]);
3715
+ const onChangeRef = React20.useRef(onChange);
3716
+ React20.useEffect(() => {
3717
+ onChangeRef.current = onChange;
3718
+ }, [onChange]);
3719
+ React20.useEffect(() => {
3720
+ if (onChangeRef.current) {
3721
+ onChangeRef.current({
3722
+ width: canvasSize.width,
3723
+ height: canvasSize.height,
3724
+ elements: state.elements,
3725
+ metadata: {
3726
+ backgroundColor,
3727
+ backgroundImage
3728
+ }
3729
+ });
3730
+ }
3731
+ }, [state.elements, canvasSize, backgroundColor, backgroundImage]);
3732
+ React20.useEffect(() => {
3733
+ const handleKeyDown = (e) => {
3734
+ if (readonly) return;
3735
+ if (e.ctrlKey && e.key === "z" && !e.shiftKey) {
3736
+ e.preventDefault();
3737
+ if (canUndo) undo();
3738
+ } else if (e.ctrlKey && e.key === "y" || e.ctrlKey && e.shiftKey && e.key === "Z") {
3739
+ e.preventDefault();
3740
+ if (canRedo) redo();
3741
+ } else if (e.key === "Delete" && selectedElement) {
3742
+ e.preventDefault();
3743
+ api.removeElement(selectedElement.id);
3744
+ } else if (e.key === "Escape") {
3745
+ e.preventDefault();
3746
+ api.selectElement(null);
3747
+ } else if (e.ctrlKey && e.key === "c" && selectedElement) {
3748
+ e.preventDefault();
3749
+ const copied = api.copyElement();
3750
+ if (copied) {
3751
+ clipboardRef.current = copied;
3752
+ }
3753
+ } else if (e.ctrlKey && e.key === "v" && clipboardRef.current) {
3754
+ e.preventDefault();
3755
+ api.pasteElement(clipboardRef.current);
3756
+ } else if (e.ctrlKey && e.key === "d" && selectedElement) {
3757
+ e.preventDefault();
3758
+ api.duplicateElement();
3759
+ }
3760
+ const isInputFocused = () => {
3761
+ const active = document.activeElement;
3762
+ if (!active) return false;
3763
+ if (active === document.body) return false;
3764
+ if (active.isContentEditable) return true;
3765
+ const tag = active.tagName?.toLowerCase();
3766
+ if (tag === "input" || tag === "textarea" || tag === "select") return true;
3767
+ const role = active.getAttribute ? active.getAttribute("role") : null;
3768
+ if (role === "textbox") return true;
3769
+ return false;
3770
+ };
3771
+ if (selectedElement && !isInputFocused()) {
3772
+ if (e.key === "ArrowUp") {
3773
+ e.preventDefault();
3774
+ api.moveElement(selectedElement.id, 0, -1);
3775
+ } else if (e.key === "ArrowDown") {
3776
+ e.preventDefault();
3777
+ api.moveElement(selectedElement.id, 0, 1);
3778
+ } else if (e.key === "ArrowLeft") {
3779
+ e.preventDefault();
3780
+ api.moveElement(selectedElement.id, -1, 0);
3781
+ } else if (e.key === "ArrowRight") {
3782
+ e.preventDefault();
3783
+ api.moveElement(selectedElement.id, 1, 0);
3784
+ }
3785
+ }
3786
+ };
3787
+ window.addEventListener("keydown", handleKeyDown);
3788
+ return () => window.removeEventListener("keydown", handleKeyDown);
3789
+ }, [canUndo, canRedo, undo, redo, selectedElement, api, readonly]);
3790
+ return /* @__PURE__ */ jsxs14("div", { className: `flex flex-col ${className}`, children: [
3791
+ showTopbar && /* @__PURE__ */ jsx26(
3792
+ Topbar,
3793
+ {
3794
+ api,
3795
+ canvasSize,
3796
+ canUndo,
3797
+ canRedo,
3798
+ onUndo: undo,
3799
+ onRedo: redo,
3800
+ onExport: handleExport,
3801
+ onImport: handleImport,
3802
+ setCanvasSize,
3803
+ backgroundColor,
3804
+ setBackgroundColor,
3805
+ backgroundImage,
3806
+ setBackgroundImage,
3807
+ imageUrls: mode?.context?.imageUrls,
3808
+ clipboardRef,
3809
+ enableSnapGuides: snapGuidesEnabled,
3810
+ onSnapGuidesChange: setSnapGuidesEnabled,
3811
+ config: mode?.topbarConfig
3812
+ }
3813
+ ),
3814
+ /* @__PURE__ */ jsxs14("div", { className: "flex w-full h-full overflow-hidden", children: [
3815
+ showToolbar && /* @__PURE__ */ jsx26(
3816
+ Toolbar,
3817
+ {
3818
+ api,
3819
+ elementRenderers: availableRenderers,
3820
+ canvasSize,
3821
+ config: mode?.toolbarConfig
3822
+ }
3823
+ ),
3824
+ showCanvas && /* @__PURE__ */ jsx26(
3825
+ Canvas,
3826
+ {
3827
+ canvasSize,
3828
+ elements: state.elements,
3829
+ selectedElementId: state.selectedElementId,
3830
+ registry,
3831
+ mode: mode ? { ...mode, backgroundColor } : void 0,
3832
+ readonly,
3833
+ enableSnapGuides: snapGuidesEnabled,
3834
+ enablePanZoom,
3835
+ backgroundImageUrl: backgroundImage && mode?.context?.imageUrls ? mode.context.imageUrls.get(backgroundImage) : void 0,
3836
+ hideElements,
3837
+ onSelectElement: (id) => api.selectElement(id),
3838
+ onTransformElement: (id, updates) => api.updateElement(id, updates)
3839
+ }
3840
+ ),
3841
+ showLayers && /* @__PURE__ */ jsx26(
3842
+ LayersPanel,
3843
+ {
3844
+ elements: state.elements,
3845
+ selectedElementId: state.selectedElementId,
3846
+ api,
3847
+ elementRenderers: registry.getMap(),
3848
+ className: "w-64"
3849
+ }
3850
+ ),
3851
+ showInspector && /* @__PURE__ */ jsxs14("div", { className: "w-80 h-full border-l bg-background flex flex-col", children: [
3852
+ /* @__PURE__ */ jsx26("div", { className: "flex-1 min-h-0 overflow-auto", children: /* @__PURE__ */ jsx26(
3853
+ Inspector,
3854
+ {
3855
+ selectedElement,
3856
+ elementRenderer: selectedRenderer,
3857
+ api,
3858
+ mode,
3859
+ canvasSize,
3860
+ setCanvasSize
3861
+ }
3862
+ ) }),
3863
+ showAssetPicker && mode?.assetPickerComponent && (!mode.assetPickerPosition || mode.assetPickerPosition === "bottom") && React20.createElement(mode.assetPickerComponent, {
3864
+ ...mode.assetPickerProps,
3865
+ api,
3866
+ className: mode.assetPickerHeight || "h-[40%]"
3867
+ })
3868
+ ] }),
3869
+ showAssetPicker && mode?.assetPickerComponent && mode.assetPickerPosition === "right" && React20.createElement(mode.assetPickerComponent, {
3870
+ ...mode.assetPickerProps,
3871
+ api,
3872
+ className: `border-l ${mode.assetPickerHeight || "w-80"}`
3873
+ })
3874
+ ] })
3875
+ ] });
3876
+ };
3877
+
3878
+ // src/components/AssetPicker.tsx
3879
+ import React21 from "react";
3880
+ import { Search } from "lucide-react";
3881
+ import { jsx as jsx27, jsxs as jsxs15 } from "react/jsx-runtime";
3882
+ var AssetPicker = ({
3883
+ assets,
3884
+ onAssetSelect,
3885
+ renderAsset,
3886
+ className = "",
3887
+ title = "Assets"
3888
+ }) => {
3889
+ const [searchQuery, setSearchQuery] = React21.useState("");
3890
+ const filteredAssets = React21.useMemo(() => {
3891
+ if (!searchQuery) return assets;
3892
+ const query = searchQuery.toLowerCase();
3893
+ return assets.filter((asset) => asset.name.toLowerCase().includes(query));
3894
+ }, [assets, searchQuery]);
3895
+ const defaultRenderAsset = React21.useCallback(
3896
+ (asset) => {
3897
+ const isImage = asset.name.match(/\.(jpg|jpeg|png|gif|svg|webp)$/i);
3898
+ return /* @__PURE__ */ jsxs15(
3899
+ "div",
3900
+ {
3901
+ className: "flex flex-col items-center p-2 border rounded hover:bg-accent cursor-pointer transition-colors",
3902
+ onClick: () => onAssetSelect?.(asset.path),
3903
+ title: asset.name,
3904
+ children: [
3905
+ isImage ? /* @__PURE__ */ jsx27("div", { className: "w-16 h-16 flex items-center justify-center bg-muted rounded mb-1", children: /* @__PURE__ */ jsx27(
3906
+ "img",
3907
+ {
3908
+ src: `asset:///${asset.path}`,
3909
+ alt: asset.name,
3910
+ className: "max-w-full max-h-full object-contain",
3911
+ onError: (e) => {
3912
+ e.currentTarget.style.display = "none";
3913
+ e.currentTarget.parentElement.innerHTML = `
3914
+ <div class="text-xs text-muted-foreground">Image</div>
3915
+ `;
3916
+ }
3917
+ }
3918
+ ) }) : /* @__PURE__ */ jsx27("div", { className: "w-16 h-16 flex items-center justify-center bg-muted rounded mb-1", children: /* @__PURE__ */ jsx27("div", { className: "text-xs text-muted-foreground", children: "File" }) }),
3919
+ /* @__PURE__ */ jsx27("div", { className: "text-xs text-center truncate w-full", children: asset.name })
3920
+ ]
3921
+ },
3922
+ asset.path
3923
+ );
3924
+ },
3925
+ [onAssetSelect]
3926
+ );
3927
+ const assetRenderer = renderAsset || defaultRenderAsset;
3928
+ return /* @__PURE__ */ jsxs15("div", { className: `flex flex-col bg-background border-t ${className || "h-full"}`, children: [
3929
+ /* @__PURE__ */ jsxs15("div", { className: "p-3 border-b", children: [
3930
+ /* @__PURE__ */ jsx27("h3", { className: "text-sm font-semibold mb-2", children: title }),
3931
+ /* @__PURE__ */ jsxs15("div", { className: "relative", children: [
3932
+ /* @__PURE__ */ jsx27(Search, { className: "absolute left-2 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
3933
+ /* @__PURE__ */ jsx27(
3934
+ Input,
3935
+ {
3936
+ type: "text",
3937
+ placeholder: "Search assets...",
3938
+ value: searchQuery,
3939
+ onChange: (e) => setSearchQuery(e.target.value),
3940
+ className: "pl-8 h-8 text-sm"
3941
+ }
3942
+ )
3943
+ ] })
3944
+ ] }),
3945
+ /* @__PURE__ */ jsx27(ScrollArea, { className: "flex-1", children: filteredAssets.length > 0 ? /* @__PURE__ */ jsx27("div", { className: "grid grid-cols-3 gap-2 p-3", children: filteredAssets.map((asset) => assetRenderer(asset)) }) : /* @__PURE__ */ jsx27("div", { className: "flex items-center justify-center h-32 text-sm text-muted-foreground", children: searchQuery ? "No assets found" : "No assets available" }) })
3946
+ ] });
3947
+ };
3948
+
3949
+ // src/index.ts
3950
+ init_editorUtils();
3951
+ export {
3952
+ AssetPicker,
3953
+ ElementRegistry,
3954
+ ImageElementRenderer,
3955
+ Inspector,
3956
+ LayersPanel,
3957
+ TextElementRenderer,
3958
+ VisualEditor,
3959
+ VisualEditorWorkspace,
3960
+ bringToFront,
3961
+ checkOverlap,
3962
+ clamp,
3963
+ cloneElement,
3964
+ constrainToCanvas,
3965
+ createElement,
3966
+ defaultElements,
3967
+ degToRad,
3968
+ distance,
3969
+ duplicateElement,
3970
+ exportToJSON,
3971
+ generateElementId,
3972
+ getElementCenter,
3973
+ getMaxZIndex,
3974
+ getRotatedBoundingBox,
3975
+ getSnappingPosition,
3976
+ globalElementRegistry,
3977
+ imageElementRenderer,
3978
+ importFromJSON,
3979
+ isValidCanvasExport,
3980
+ isValidElement,
3981
+ pointInRect,
3982
+ radToDeg,
3983
+ renderField,
3984
+ sendToBack,
3985
+ snapPositionToGrid,
3986
+ snapToGrid,
3987
+ sortByZIndex,
3988
+ textElementRenderer,
3989
+ useEditorState,
3990
+ useElementRegistry
3991
+ };
3992
+ //# sourceMappingURL=index.mjs.map