@elixpo/lixsketch 5.5.19 → 5.5.21

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.
Files changed (137) hide show
  1. package/dist/react/Circle-6POZCUZD.js +9 -0
  2. package/dist/react/{CopyPaste-JMIXWUUO.js → CopyPaste-FJN7K5LS.js} +43 -14
  3. package/dist/react/CopyPaste-FJN7K5LS.js.map +7 -0
  4. package/dist/react/{EventDispatcher-H5EA4KZN.js → EventDispatcher-O7HCQQNM.js} +14 -13
  5. package/dist/react/{EventDispatcher-H5EA4KZN.js.map → EventDispatcher-O7HCQQNM.js.map} +1 -1
  6. package/dist/react/Frame-RLSMJJSK.js +11 -0
  7. package/dist/react/FreehandStroke-U6GYZOM4.js +9 -0
  8. package/dist/react/{GraphEngine-KQXHNTZQ.js → GraphEngine-MM6ALA2N.js} +105 -148
  9. package/dist/react/{GraphEngine-KQXHNTZQ.js.map → GraphEngine-MM6ALA2N.js.map} +2 -2
  10. package/dist/react/IconShape-7OPXAKLK.js +10 -0
  11. package/dist/react/ImageShape-LRNJQCF3.js +10 -0
  12. package/dist/react/Line-WWQFBNX5.js +10 -0
  13. package/dist/react/Rectangle-S7XUKWYD.js +9 -0
  14. package/dist/react/{SceneSerializer-ARTKPZVR.js → SceneSerializer-GSJR7ZHC.js} +36 -11
  15. package/dist/react/SceneSerializer-GSJR7ZHC.js.map +7 -0
  16. package/dist/react/{Selection-YJNZRBON.js → Selection-ZL3G6IAO.js} +5 -4
  17. package/dist/react/{SketchEngine-ZGYBIZG7.js → SketchEngine-BFLHJPCG.js} +25 -25
  18. package/dist/react/{UndoRedo-BZ6W4QTV.js → UndoRedo-NL7KLR5B.js} +2 -2
  19. package/dist/react/{ZoomPan-NYPDJBD5.js → ZoomPan-FAWA5OPS.js} +5 -1
  20. package/dist/react/ZoomPan-FAWA5OPS.js.map +7 -0
  21. package/dist/react/{arrowTool-LKFSZKXC.js → arrowTool-CNVGM6RX.js} +3 -3
  22. package/dist/react/{chunk-RFHMFWVR.js → chunk-2FMFOPGW.js} +5 -6
  23. package/dist/react/{chunk-RFHMFWVR.js.map → chunk-2FMFOPGW.js.map} +2 -2
  24. package/dist/react/{chunk-QYNTPZON.js → chunk-2THKDSWW.js} +9 -5
  25. package/dist/react/chunk-2THKDSWW.js.map +7 -0
  26. package/dist/react/{chunk-JOPQ27YH.js → chunk-4BLMNGTR.js} +3 -3
  27. package/dist/react/{chunk-C62SXFBL.js → chunk-4TAS67SL.js} +23 -22
  28. package/dist/react/chunk-4TAS67SL.js.map +7 -0
  29. package/dist/react/{chunk-FV3EPR4L.js → chunk-AH4PWNEB.js} +19 -11
  30. package/dist/react/chunk-AH4PWNEB.js.map +7 -0
  31. package/dist/react/{chunk-XCJOF6BM.js → chunk-FETTJLCV.js} +5 -1
  32. package/dist/react/chunk-FETTJLCV.js.map +7 -0
  33. package/dist/react/{chunk-JJTII2UQ.js → chunk-GC3KTQDA.js} +78 -15
  34. package/dist/react/chunk-GC3KTQDA.js.map +7 -0
  35. package/dist/react/{chunk-2V7G2PLX.js → chunk-GPIYXREE.js} +3 -3
  36. package/dist/react/{chunk-3VSGFP6U.js → chunk-HFQZYCHJ.js} +8 -4
  37. package/dist/react/chunk-HFQZYCHJ.js.map +7 -0
  38. package/dist/react/{chunk-C72OHQLE.js → chunk-ILGAFR56.js} +3 -3
  39. package/dist/react/{chunk-CF7AK4ES.js → chunk-KVAQPCUH.js} +26 -3
  40. package/dist/react/chunk-KVAQPCUH.js.map +7 -0
  41. package/dist/react/{chunk-WEWMB35W.js → chunk-OFGJJKPH.js} +3 -3
  42. package/dist/react/chunk-PAKA6HUX.js +59 -0
  43. package/dist/react/chunk-PAKA6HUX.js.map +7 -0
  44. package/dist/react/{chunk-HKYUJUD7.js → chunk-PXFPPO3R.js} +5 -1
  45. package/dist/react/chunk-PXFPPO3R.js.map +7 -0
  46. package/dist/react/{chunk-6G3ITAS5.js → chunk-SZD7DKYJ.js} +18 -9
  47. package/dist/react/chunk-SZD7DKYJ.js.map +7 -0
  48. package/dist/react/{chunk-N7NZLF6P.js → chunk-TKXKJNTX.js} +5 -1
  49. package/dist/react/chunk-TKXKJNTX.js.map +7 -0
  50. package/dist/react/{chunk-MYYBFZD3.js → chunk-U3TB4KRS.js} +45 -6
  51. package/dist/react/chunk-U3TB4KRS.js.map +7 -0
  52. package/dist/react/{chunk-UQBT7F4K.js → chunk-V2QTYSBK.js} +2 -2
  53. package/dist/react/{chunk-73VA5ALZ.js → chunk-XFQECTTF.js} +48 -19
  54. package/dist/react/chunk-XFQECTTF.js.map +7 -0
  55. package/dist/react/{chunk-O64N5JPO.js → chunk-YF3TK7SC.js} +2 -2
  56. package/dist/react/{circleTool-RDOIMKKV.js → circleTool-KICNRQCI.js} +4 -4
  57. package/dist/react/{codeTool-O2Z7ZXGP.js → codeTool-NCQ73R6M.js} +5 -4
  58. package/dist/react/{frameTool-Z5ZDCX6H.js → frameTool-Y6KB67DO.js} +4 -4
  59. package/dist/react/{freehandTool-AAQCTMVM.js → freehandTool-MMULHCQ7.js} +4 -4
  60. package/dist/react/{iconTool-AUHRLOO2.js → iconTool-LSW2DYGB.js} +5 -4
  61. package/dist/react/{imageTool-DRKDCPUV.js → imageTool-2DSR32RX.js} +5 -4
  62. package/dist/react/index.js +141 -66
  63. package/dist/react/index.js.map +3 -3
  64. package/dist/react/{lineTool-XLOOZE3E.js → lineTool-N5I7IGT7.js} +4 -4
  65. package/dist/react/{rectangleTool-AEYDDJCA.js → rectangleTool-YUT7ZK2E.js} +4 -4
  66. package/dist/react/{textTool-UORBAAJE.js → textTool-IQOYPELP.js} +6 -5
  67. package/package.json +1 -1
  68. package/src/core/CopyPaste.js +34 -4
  69. package/src/core/GraphEngine.js +129 -185
  70. package/src/core/SceneSerializer.js +26 -0
  71. package/src/core/ScreenSpaceControls.js +60 -0
  72. package/src/core/Selection.js +19 -11
  73. package/src/core/UndoRedo.js +47 -3
  74. package/src/core/ZoomPan.js +3 -0
  75. package/src/react/components/canvas/MultiSelectActions.jsx +4 -1
  76. package/src/react/components/sidebars/IconSidebar.jsx +14 -23
  77. package/src/react/components/sidebars/ImageSidebar.jsx +47 -24
  78. package/src/react/store/useUIStore.js +71 -14
  79. package/src/shapes/Circle.js +2 -0
  80. package/src/shapes/Frame.js +26 -2
  81. package/src/shapes/FreehandStroke.js +2 -0
  82. package/src/shapes/IconShape.js +23 -25
  83. package/src/shapes/ImageShape.js +6 -2
  84. package/src/shapes/Rectangle.js +2 -0
  85. package/src/tools/codeTool.js +3 -2
  86. package/src/tools/frameTool.js +19 -9
  87. package/src/tools/iconTool.js +63 -26
  88. package/src/tools/imageTool.js +80 -14
  89. package/src/tools/textTool.js +3 -1
  90. package/dist/react/Circle-QQMKCKFR.js +0 -8
  91. package/dist/react/CopyPaste-JMIXWUUO.js.map +0 -7
  92. package/dist/react/Frame-VADA625S.js +0 -10
  93. package/dist/react/FreehandStroke-BOWDNAVC.js +0 -8
  94. package/dist/react/IconShape-32EYI2MA.js +0 -10
  95. package/dist/react/ImageShape-MYP2DVVH.js +0 -10
  96. package/dist/react/Line-FCCF2BOB.js +0 -10
  97. package/dist/react/Rectangle-MZ6ZB6KM.js +0 -8
  98. package/dist/react/SceneSerializer-ARTKPZVR.js.map +0 -7
  99. package/dist/react/ZoomPan-NYPDJBD5.js.map +0 -7
  100. package/dist/react/chunk-3VSGFP6U.js.map +0 -7
  101. package/dist/react/chunk-6G3ITAS5.js.map +0 -7
  102. package/dist/react/chunk-73VA5ALZ.js.map +0 -7
  103. package/dist/react/chunk-C62SXFBL.js.map +0 -7
  104. package/dist/react/chunk-CF7AK4ES.js.map +0 -7
  105. package/dist/react/chunk-FV3EPR4L.js.map +0 -7
  106. package/dist/react/chunk-HKYUJUD7.js.map +0 -7
  107. package/dist/react/chunk-JJTII2UQ.js.map +0 -7
  108. package/dist/react/chunk-MYYBFZD3.js.map +0 -7
  109. package/dist/react/chunk-N7NZLF6P.js.map +0 -7
  110. package/dist/react/chunk-QYNTPZON.js.map +0 -7
  111. package/dist/react/chunk-XCJOF6BM.js.map +0 -7
  112. /package/dist/react/{Circle-QQMKCKFR.js.map → Circle-6POZCUZD.js.map} +0 -0
  113. /package/dist/react/{Frame-VADA625S.js.map → Frame-RLSMJJSK.js.map} +0 -0
  114. /package/dist/react/{FreehandStroke-BOWDNAVC.js.map → FreehandStroke-U6GYZOM4.js.map} +0 -0
  115. /package/dist/react/{IconShape-32EYI2MA.js.map → IconShape-7OPXAKLK.js.map} +0 -0
  116. /package/dist/react/{ImageShape-MYP2DVVH.js.map → ImageShape-LRNJQCF3.js.map} +0 -0
  117. /package/dist/react/{Line-FCCF2BOB.js.map → Line-WWQFBNX5.js.map} +0 -0
  118. /package/dist/react/{Rectangle-MZ6ZB6KM.js.map → Rectangle-S7XUKWYD.js.map} +0 -0
  119. /package/dist/react/{Selection-YJNZRBON.js.map → Selection-ZL3G6IAO.js.map} +0 -0
  120. /package/dist/react/{SketchEngine-ZGYBIZG7.js.map → SketchEngine-BFLHJPCG.js.map} +0 -0
  121. /package/dist/react/{UndoRedo-BZ6W4QTV.js.map → UndoRedo-NL7KLR5B.js.map} +0 -0
  122. /package/dist/react/{arrowTool-LKFSZKXC.js.map → arrowTool-CNVGM6RX.js.map} +0 -0
  123. /package/dist/react/{chunk-JOPQ27YH.js.map → chunk-4BLMNGTR.js.map} +0 -0
  124. /package/dist/react/{chunk-2V7G2PLX.js.map → chunk-GPIYXREE.js.map} +0 -0
  125. /package/dist/react/{chunk-C72OHQLE.js.map → chunk-ILGAFR56.js.map} +0 -0
  126. /package/dist/react/{chunk-WEWMB35W.js.map → chunk-OFGJJKPH.js.map} +0 -0
  127. /package/dist/react/{chunk-UQBT7F4K.js.map → chunk-V2QTYSBK.js.map} +0 -0
  128. /package/dist/react/{chunk-O64N5JPO.js.map → chunk-YF3TK7SC.js.map} +0 -0
  129. /package/dist/react/{circleTool-RDOIMKKV.js.map → circleTool-KICNRQCI.js.map} +0 -0
  130. /package/dist/react/{codeTool-O2Z7ZXGP.js.map → codeTool-NCQ73R6M.js.map} +0 -0
  131. /package/dist/react/{frameTool-Z5ZDCX6H.js.map → frameTool-Y6KB67DO.js.map} +0 -0
  132. /package/dist/react/{freehandTool-AAQCTMVM.js.map → freehandTool-MMULHCQ7.js.map} +0 -0
  133. /package/dist/react/{iconTool-AUHRLOO2.js.map → iconTool-LSW2DYGB.js.map} +0 -0
  134. /package/dist/react/{imageTool-DRKDCPUV.js.map → imageTool-2DSR32RX.js.map} +0 -0
  135. /package/dist/react/{lineTool-XLOOZE3E.js.map → lineTool-N5I7IGT7.js.map} +0 -0
  136. /package/dist/react/{rectangleTool-AEYDDJCA.js.map → rectangleTool-YUT7ZK2E.js.map} +0 -0
  137. /package/dist/react/{textTool-UORBAAJE.js.map → textTool-IQOYPELP.js.map} +0 -0
@@ -278,24 +278,78 @@ function invertShapeColors(prevResolved, nextResolved) {
278
278
  };
279
279
  for (const shape of shapes) {
280
280
  let changed = false;
281
- if (shape.options) {
282
- if (normalize(shape.options.stroke) === from) {
283
- shape.options.stroke = to;
281
+ if (shape.shapeName === "icon" || shape.type === "icon") {
282
+ const isDark = nextResolved === "dark";
283
+ const targetDarkColor = "#1a1a2e";
284
+ const bgDarkColor = "#15111f";
285
+ if (shape.group) {
286
+ shape.group.querySelectorAll("*").forEach((element) => {
287
+ if (element.nodeType === 1) {
288
+ const fill = element.getAttribute("fill");
289
+ const stroke = element.getAttribute("stroke");
290
+ if (fill && fill !== "none" && fill !== "transparent") {
291
+ const normFill = fill.toLowerCase().trim();
292
+ if (isDark) {
293
+ if (normFill === targetDarkColor || normFill === "#000000" || normFill === "#000") {
294
+ element.setAttribute("fill", "#ffffff");
295
+ changed = true;
296
+ } else if (normFill === "#ffffff" || normFill === "#fff") {
297
+ element.setAttribute("fill", bgDarkColor);
298
+ changed = true;
299
+ }
300
+ } else {
301
+ if (normFill === "#ffffff" || normFill === "#fff") {
302
+ element.setAttribute("fill", targetDarkColor);
303
+ changed = true;
304
+ } else if (normFill === bgDarkColor) {
305
+ element.setAttribute("fill", "#ffffff");
306
+ changed = true;
307
+ }
308
+ }
309
+ }
310
+ if (stroke && stroke !== "none" && stroke !== "transparent") {
311
+ const normStroke = stroke.toLowerCase().trim();
312
+ if (isDark) {
313
+ if (normStroke === targetDarkColor || normStroke === "#000000" || normStroke === "#000") {
314
+ element.setAttribute("stroke", "#ffffff");
315
+ changed = true;
316
+ } else if (normStroke === "#ffffff" || normStroke === "#fff") {
317
+ element.setAttribute("stroke", bgDarkColor);
318
+ changed = true;
319
+ }
320
+ } else {
321
+ if (normStroke === "#ffffff" || normStroke === "#fff") {
322
+ element.setAttribute("stroke", targetDarkColor);
323
+ changed = true;
324
+ } else if (normStroke === bgDarkColor) {
325
+ element.setAttribute("stroke", "#ffffff");
326
+ changed = true;
327
+ }
328
+ }
329
+ }
330
+ }
331
+ });
332
+ }
333
+ } else {
334
+ if (shape.options) {
335
+ if (normalize(shape.options.stroke) === from) {
336
+ shape.options.stroke = to;
337
+ changed = true;
338
+ }
339
+ if (normalize(shape.options.fill) === from) {
340
+ shape.options.fill = to;
341
+ changed = true;
342
+ }
343
+ }
344
+ if (shape.color !== void 0 && normalize(shape.color) === from) {
345
+ shape.color = to;
284
346
  changed = true;
285
347
  }
286
- if (normalize(shape.options.fill) === from) {
287
- shape.options.fill = to;
348
+ if (shape.strokeColor !== void 0 && normalize(shape.strokeColor) === from) {
349
+ shape.strokeColor = to;
288
350
  changed = true;
289
351
  }
290
352
  }
291
- if (shape.color !== void 0 && normalize(shape.color) === from) {
292
- shape.color = to;
293
- changed = true;
294
- }
295
- if (shape.strokeColor !== void 0 && normalize(shape.strokeColor) === from) {
296
- shape.strokeColor = to;
297
- changed = true;
298
- }
299
353
  if (changed && typeof shape.draw === "function") {
300
354
  shape.draw();
301
355
  }
@@ -474,7 +528,7 @@ function useSketchEngine(svgRef, ready = true) {
474
528
  setZoom: (zoom) => useSketchStore_default.setState({ zoom }),
475
529
  getState: () => useSketchStore_default.getState()
476
530
  };
477
- const { SketchEngine } = await import("./SketchEngine-ZGYBIZG7.js");
531
+ const { SketchEngine } = await import("./SketchEngine-BFLHJPCG.js");
478
532
  if (cancelled) return;
479
533
  const engine = new SketchEngine(svgRef.current);
480
534
  await engine.init();
@@ -2798,6 +2852,13 @@ function IconSidebar() {
2798
2852
  const [icons, setIcons] = useState12([]);
2799
2853
  const [loading, setLoading] = useState12(false);
2800
2854
  const debounceRef = useRef5(null);
2855
+ const searchInputRef = useRef5(null);
2856
+ const requestSequenceRef = useRef5(0);
2857
+ useEffect8(() => {
2858
+ if (!visible) return;
2859
+ const frame = requestAnimationFrame(() => searchInputRef.current?.focus());
2860
+ return () => cancelAnimationFrame(frame);
2861
+ }, [visible]);
2801
2862
  useEffect8(() => {
2802
2863
  if (!visible) return;
2803
2864
  const handler = (e) => {
@@ -2818,6 +2879,7 @@ function IconSidebar() {
2818
2879
  return () => svgEl.removeEventListener("mousedown", handleCanvasClick);
2819
2880
  }, [visible, setActiveTool]);
2820
2881
  const fetchIcons = useCallback9(async (searchQuery, cat) => {
2882
+ const requestSequence = ++requestSequenceRef.current;
2821
2883
  const params = new URLSearchParams();
2822
2884
  if (searchQuery) params.set("q", searchQuery);
2823
2885
  if (cat) params.set("category", cat);
@@ -2825,6 +2887,7 @@ function IconSidebar() {
2825
2887
  const cacheKey = params.toString();
2826
2888
  if (iconResultCache.has(cacheKey)) {
2827
2889
  setIcons(iconResultCache.get(cacheKey));
2890
+ setLoading(false);
2828
2891
  return;
2829
2892
  }
2830
2893
  setLoading(true);
@@ -2834,38 +2897,21 @@ function IconSidebar() {
2834
2897
  const data = await res.json();
2835
2898
  const results = data.results || [];
2836
2899
  iconResultCache.set(cacheKey, results);
2837
- setIcons(results);
2900
+ if (requestSequence === requestSequenceRef.current) setIcons(results);
2838
2901
  }
2839
2902
  } catch (err) {
2840
2903
  console.error("Icon fetch failed:", err);
2841
2904
  }
2842
- setLoading(false);
2905
+ if (requestSequence === requestSequenceRef.current) setLoading(false);
2843
2906
  }, []);
2844
2907
  useEffect8(() => {
2845
2908
  if (!visible) return;
2846
2909
  if (debounceRef.current) clearTimeout(debounceRef.current);
2847
2910
  debounceRef.current = setTimeout(() => {
2848
2911
  fetchIcons(query, category);
2849
- }, query ? 300 : 0);
2912
+ }, query ? 80 : 0);
2850
2913
  return () => clearTimeout(debounceRef.current);
2851
2914
  }, [query, visible, category, fetchIcons]);
2852
- const hasPreloaded = useRef5(false);
2853
- useEffect8(() => {
2854
- if (!visible || hasPreloaded.current) return;
2855
- hasPreloaded.current = true;
2856
- CATEGORIES.forEach((cat) => {
2857
- const params = new URLSearchParams();
2858
- if (cat.value) params.set("category", cat.value);
2859
- params.set("inline", "1");
2860
- const key = params.toString();
2861
- if (!iconResultCache.has(key)) {
2862
- fetch(`${getIconsBaseUrl()}/api/icons/search?${key}`).then((r) => r.ok ? r.json() : null).then((data) => {
2863
- if (data?.results) iconResultCache.set(key, data.results);
2864
- }).catch(() => {
2865
- });
2866
- }
2867
- });
2868
- }, [visible]);
2869
2915
  const handleIconClick = useCallback9((icon) => {
2870
2916
  if (typeof window === "undefined") return;
2871
2917
  const place = (svgContent) => {
@@ -2905,6 +2951,7 @@ function IconSidebar() {
2905
2951
  /* @__PURE__ */ jsx14(
2906
2952
  "input",
2907
2953
  {
2954
+ ref: searchInputRef,
2908
2955
  id: "iconSearchInput",
2909
2956
  type: "text",
2910
2957
  value: query,
@@ -2967,11 +3014,19 @@ function IconSidebar() {
2967
3014
 
2968
3015
  // src/react/components/sidebars/ImageSidebar.jsx
2969
3016
  import { useCallback as useCallback10 } from "react";
2970
- import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
3017
+ import { Fragment as Fragment8, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
2971
3018
  function ImageSidebar() {
2972
3019
  const { t } = useTranslation();
2973
3020
  const selectedShapeSidebar = useSketchStore_default((s) => s.selectedShapeSidebar);
2974
3021
  const toggleImageGenerateModal = useUIStore_default((s) => s.toggleImageGenerateModal);
3022
+ const toggleAIModal = useUIStore_default((s) => s.toggleAIModal);
3023
+ const isGraph = typeof window !== "undefined" && window.currentShape?._frameType === "graph";
3024
+ const handleEditGraph = useCallback10(() => {
3025
+ const shape = window.currentShape;
3026
+ if (!shape || shape._frameType !== "graph") return;
3027
+ window.__aiEditTargetFrame = shape;
3028
+ toggleAIModal();
3029
+ }, [toggleAIModal]);
2975
3030
  const handleEditWithAI = useCallback10(() => {
2976
3031
  const shape = window.currentShape;
2977
3032
  if (!shape || shape.shapeName !== "image") return;
@@ -2989,32 +3044,49 @@ function ImageSidebar() {
2989
3044
  }
2990
3045
  }, []);
2991
3046
  return /* @__PURE__ */ jsxs15(ShapeSidebar, { visible: selectedShapeSidebar === "image", children: [
2992
- /* @__PURE__ */ jsxs15(
2993
- "button",
2994
- {
2995
- onClick: handleEditWithAI,
2996
- title: "Edit with AI",
2997
- className: "h-9 flex items-center gap-1.5 px-3 rounded-lg text-text-muted hover:text-purple-400 hover:bg-purple-500/10 transition-all duration-100",
2998
- children: [
2999
- /* @__PURE__ */ jsx15("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor", stroke: "none", children: /* @__PURE__ */ jsx15("path", { d: "M12 2l2.4 7.2L22 12l-7.6 2.8L12 22l-2.4-7.2L2 12l7.6-2.8L12 2z" }) }),
3000
- /* @__PURE__ */ jsx15("span", { className: "text-xs", children: "AI Edit" })
3001
- ]
3002
- }
3003
- ),
3004
- /* @__PURE__ */ jsx15(Divider, {}),
3005
- /* @__PURE__ */ jsxs15(
3006
- "button",
3007
- {
3008
- onClick: handleReplace,
3009
- title: "Replace image",
3010
- className: "h-9 flex items-center gap-1.5 px-3 rounded-lg text-text-muted hover:text-white hover:bg-white/[0.06] transition-all duration-100",
3011
- children: [
3012
- /* @__PURE__ */ jsx15("i", { className: "bx bx-upload text-base" }),
3013
- /* @__PURE__ */ jsx15("span", { className: "text-xs", children: "Replace" })
3014
- ]
3015
- }
3016
- ),
3017
- /* @__PURE__ */ jsx15(Divider, {}),
3047
+ isGraph && /* @__PURE__ */ jsxs15(Fragment8, { children: [
3048
+ /* @__PURE__ */ jsxs15(
3049
+ "button",
3050
+ {
3051
+ onClick: handleEditGraph,
3052
+ title: "Edit graph",
3053
+ className: "h-9 flex cursor-pointer items-center gap-1.5 px-3 rounded-lg text-text-muted hover:text-accent hover:bg-accent/10 transition-all duration-100",
3054
+ children: [
3055
+ /* @__PURE__ */ jsx15("i", { className: "bx bx-line-chart text-base" }),
3056
+ /* @__PURE__ */ jsx15("span", { className: "text-xs", children: "Edit graph" })
3057
+ ]
3058
+ }
3059
+ ),
3060
+ /* @__PURE__ */ jsx15(Divider, {})
3061
+ ] }),
3062
+ !isGraph && /* @__PURE__ */ jsxs15(Fragment8, { children: [
3063
+ /* @__PURE__ */ jsxs15(
3064
+ "button",
3065
+ {
3066
+ onClick: handleEditWithAI,
3067
+ title: "Edit with AI",
3068
+ className: "h-9 flex items-center gap-1.5 px-3 rounded-lg text-text-muted hover:text-purple-400 hover:bg-purple-500/10 transition-all duration-100",
3069
+ children: [
3070
+ /* @__PURE__ */ jsx15("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor", stroke: "none", children: /* @__PURE__ */ jsx15("path", { d: "M12 2l2.4 7.2L22 12l-7.6 2.8L12 22l-2.4-7.2L2 12l7.6-2.8L12 2z" }) }),
3071
+ /* @__PURE__ */ jsx15("span", { className: "text-xs", children: "AI Edit" })
3072
+ ]
3073
+ }
3074
+ ),
3075
+ /* @__PURE__ */ jsx15(Divider, {}),
3076
+ /* @__PURE__ */ jsxs15(
3077
+ "button",
3078
+ {
3079
+ onClick: handleReplace,
3080
+ title: "Replace image",
3081
+ className: "h-9 flex items-center gap-1.5 px-3 rounded-lg text-text-muted hover:text-white hover:bg-white/[0.06] transition-all duration-100",
3082
+ children: [
3083
+ /* @__PURE__ */ jsx15("i", { className: "bx bx-upload text-base" }),
3084
+ /* @__PURE__ */ jsx15("span", { className: "text-xs", children: "Replace" })
3085
+ ]
3086
+ }
3087
+ ),
3088
+ /* @__PURE__ */ jsx15(Divider, {})
3089
+ ] }),
3018
3090
  /* @__PURE__ */ jsx15(LayerControls, {})
3019
3091
  ] });
3020
3092
  }
@@ -3050,6 +3122,7 @@ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
3050
3122
  function MultiSelectActions() {
3051
3123
  const [count, setCount] = useState13(0);
3052
3124
  const [groupState, setGroupState] = useState13("none");
3125
+ const [frameEligible, setFrameEligible] = useState13(false);
3053
3126
  useEffect9(() => {
3054
3127
  const interval = setInterval(() => {
3055
3128
  const ms = window.multiSelection;
@@ -3058,6 +3131,7 @@ function MultiSelectActions() {
3058
3131
  setCount((prev) => prev !== n ? n : prev);
3059
3132
  if (n < 2) {
3060
3133
  setGroupState("none");
3134
+ setFrameEligible(false);
3061
3135
  return;
3062
3136
  }
3063
3137
  let withId = 0;
@@ -3068,6 +3142,7 @@ function MultiSelectActions() {
3068
3142
  groupIds.add(s.groupId);
3069
3143
  }
3070
3144
  });
3145
+ setFrameEligible(Array.from(sel).every((s) => s.shapeName !== "frame" && !s.parentFrame));
3071
3146
  if (withId === n && groupIds.size === 1) setGroupState("all");
3072
3147
  else if (withId > 0) setGroupState("partial");
3073
3148
  else setGroupState("none");
@@ -3119,7 +3194,7 @@ function MultiSelectActions() {
3119
3194
  grouped && /* @__PURE__ */ jsx16("span", { className: "ml-1 text-accent-blue", children: "\xB7 grouped" })
3120
3195
  ] }),
3121
3196
  /* @__PURE__ */ jsx16("div", { className: "w-px h-4 bg-border-light" }),
3122
- !grouped && /* @__PURE__ */ jsxs16(
3197
+ !grouped && frameEligible && /* @__PURE__ */ jsxs16(
3123
3198
  "button",
3124
3199
  {
3125
3200
  onClick: handleFrame,
@@ -3159,7 +3234,7 @@ function MultiSelectActions() {
3159
3234
 
3160
3235
  // src/react/components/canvas/ContextMenu.jsx
3161
3236
  import { useState as useState14, useEffect as useEffect10, useCallback as useCallback11, useRef as useRef6 } from "react";
3162
- import { Fragment as Fragment8, jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
3237
+ import { Fragment as Fragment9, jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
3163
3238
  function getCleanSVG() {
3164
3239
  const svgEl = window.svg;
3165
3240
  if (!svgEl) return null;
@@ -3504,7 +3579,7 @@ function ContextMenu() {
3504
3579
  onClick: (e) => e.stopPropagation(),
3505
3580
  children: isShape ? (
3506
3581
  /* ── Shape context menu ── */
3507
- /* @__PURE__ */ jsxs17(Fragment8, { children: [
3582
+ /* @__PURE__ */ jsxs17(Fragment9, { children: [
3508
3583
  /* @__PURE__ */ jsx17(MenuItem, { label: "Cut", shortcut: "Ctrl+X", onClick: handleCut }),
3509
3584
  /* @__PURE__ */ jsx17(MenuItem, { label: "Copy", shortcut: "Ctrl+C", onClick: handleCopy }),
3510
3585
  /* @__PURE__ */ jsx17(MenuItem, { label: "Paste", shortcut: "Ctrl+V", onClick: handlePaste }),
@@ -3529,7 +3604,7 @@ function ContextMenu() {
3529
3604
  ] })
3530
3605
  ) : (
3531
3606
  /* ── Canvas context menu ── */
3532
- /* @__PURE__ */ jsxs17(Fragment8, { children: [
3607
+ /* @__PURE__ */ jsxs17(Fragment9, { children: [
3533
3608
  /* @__PURE__ */ jsx17(MenuItem, { label: "Paste", shortcut: "Ctrl+V", onClick: handlePaste }),
3534
3609
  /* @__PURE__ */ jsx17(MenuItem, { label: "Copy to clipboard as PNG", shortcut: "Shift+Alt+C", onClick: handleCopyPNG }),
3535
3610
  /* @__PURE__ */ jsx17(MenuItem, { label: "Copy to clipboard as SVG", onClick: handleCopySVG }),
@@ -4713,7 +4788,7 @@ var _saveScene = null;
4713
4788
  var _loadScene = null;
4714
4789
  async function ensureSceneSerializer() {
4715
4790
  if (_saveScene && _loadScene) return;
4716
- const m = await import("./SceneSerializer-ARTKPZVR.js");
4791
+ const m = await import("./SceneSerializer-GSJR7ZHC.js");
4717
4792
  _saveScene = m.saveScene;
4718
4793
  _loadScene = m.loadScene;
4719
4794
  }