@elixpo/lixsketch 5.4.7 → 5.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/dist/react/AIRenderer-M4LMJ66J.js +1105 -0
  2. package/dist/react/AIRenderer-M4LMJ66J.js.map +7 -0
  3. package/dist/react/Arrow-BJDAXPMH.js +8 -0
  4. package/dist/react/Arrow-BJDAXPMH.js.map +7 -0
  5. package/dist/react/Circle-KPG7TKIX.js +8 -0
  6. package/dist/react/Circle-KPG7TKIX.js.map +7 -0
  7. package/dist/react/CodeShape-K4DIZAIV.js +8 -0
  8. package/dist/react/CodeShape-K4DIZAIV.js.map +7 -0
  9. package/dist/react/CopyPaste-4CKJRHJ3.js +569 -0
  10. package/dist/react/CopyPaste-4CKJRHJ3.js.map +7 -0
  11. package/dist/react/EraserTrail-PA6IQLQN.js +20 -0
  12. package/dist/react/EraserTrail-PA6IQLQN.js.map +7 -0
  13. package/dist/react/EventDispatcher-SRFH6LQZ.js +463 -0
  14. package/dist/react/EventDispatcher-SRFH6LQZ.js.map +7 -0
  15. package/dist/react/Frame-6UR5XPAX.js +10 -0
  16. package/dist/react/Frame-6UR5XPAX.js.map +7 -0
  17. package/dist/react/FreehandStroke-CK24NUT3.js +8 -0
  18. package/dist/react/FreehandStroke-CK24NUT3.js.map +7 -0
  19. package/dist/react/GraphEngine-QE5B2PMR.js +340 -0
  20. package/dist/react/GraphEngine-QE5B2PMR.js.map +7 -0
  21. package/dist/react/GraphMathParser-2WMTY4IE.js +10 -0
  22. package/dist/react/GraphMathParser-2WMTY4IE.js.map +7 -0
  23. package/dist/react/IconShape-KMIA6T2H.js +10 -0
  24. package/dist/react/IconShape-KMIA6T2H.js.map +7 -0
  25. package/dist/react/ImageShape-CHHGBXUQ.js +10 -0
  26. package/dist/react/ImageShape-CHHGBXUQ.js.map +7 -0
  27. package/dist/react/LayerOrder-XCHF4MSS.js +66 -0
  28. package/dist/react/LayerOrder-XCHF4MSS.js.map +7 -0
  29. package/dist/react/Line-2KOQEEGA.js +10 -0
  30. package/dist/react/Line-2KOQEEGA.js.map +7 -0
  31. package/dist/react/LixScriptParser-JYWJX375.js +1033 -0
  32. package/dist/react/LixScriptParser-JYWJX375.js.map +7 -0
  33. package/dist/react/MermaidFlowchartRenderer-4QBAZ5MB.js +412 -0
  34. package/dist/react/MermaidFlowchartRenderer-4QBAZ5MB.js.map +7 -0
  35. package/dist/react/MermaidSequenceParser-UKG2NSZY.js +8 -0
  36. package/dist/react/MermaidSequenceParser-UKG2NSZY.js.map +7 -0
  37. package/dist/react/MermaidSequenceRenderer-HPLHY5II.js +387 -0
  38. package/dist/react/MermaidSequenceRenderer-HPLHY5II.js.map +7 -0
  39. package/dist/react/Rectangle-6FE7RN6W.js +8 -0
  40. package/dist/react/Rectangle-6FE7RN6W.js.map +7 -0
  41. package/dist/react/ResizeCode-WAMDTKSW.js +2 -0
  42. package/dist/react/ResizeCode-WAMDTKSW.js.map +7 -0
  43. package/dist/react/ResizeShapes-LKEQTITP.js +250 -0
  44. package/dist/react/ResizeShapes-LKEQTITP.js.map +7 -0
  45. package/dist/react/SceneSerializer-4U4B24JM.js +739 -0
  46. package/dist/react/SceneSerializer-4U4B24JM.js.map +7 -0
  47. package/dist/react/Selection-VVUHY5YJ.js +34 -0
  48. package/dist/react/Selection-VVUHY5YJ.js.map +7 -0
  49. package/dist/react/SketchEngine-YA4RG3KX.js +413 -0
  50. package/dist/react/SketchEngine-YA4RG3KX.js.map +7 -0
  51. package/dist/react/TextShape-54ZJ66E3.js +8 -0
  52. package/dist/react/TextShape-54ZJ66E3.js.map +7 -0
  53. package/dist/react/UndoRedo-M2JZUBKN.js +30 -0
  54. package/dist/react/UndoRedo-M2JZUBKN.js.map +7 -0
  55. package/dist/react/ZoomPan-U3TXC5AJ.js +186 -0
  56. package/dist/react/ZoomPan-U3TXC5AJ.js.map +7 -0
  57. package/dist/react/arrowTool-CWEMIRXK.js +17 -0
  58. package/dist/react/arrowTool-CWEMIRXK.js.map +7 -0
  59. package/dist/react/boxicons-5JNJQPUP.woff +0 -0
  60. package/dist/react/boxicons-6NVWQSCQ.eot +0 -0
  61. package/dist/react/boxicons-AQ5N6WP7.woff2 +0 -0
  62. package/dist/react/boxicons-GDR4JU3X.ttf +0 -0
  63. package/dist/react/boxicons-NNFHLMOR.svg +1660 -0
  64. package/dist/react/chunk-2OQXI7QB.js +564 -0
  65. package/dist/react/chunk-2OQXI7QB.js.map +7 -0
  66. package/dist/react/chunk-5OUBTHHO.js +187 -0
  67. package/dist/react/chunk-5OUBTHHO.js.map +7 -0
  68. package/dist/react/chunk-6PFXZKL2.js +628 -0
  69. package/dist/react/chunk-6PFXZKL2.js.map +7 -0
  70. package/dist/react/chunk-6SFNWGGZ.js +453 -0
  71. package/dist/react/chunk-6SFNWGGZ.js.map +7 -0
  72. package/dist/react/chunk-7TZSIR6V.js +210 -0
  73. package/dist/react/chunk-7TZSIR6V.js.map +7 -0
  74. package/dist/react/chunk-AJAZC37E.js +662 -0
  75. package/dist/react/chunk-AJAZC37E.js.map +7 -0
  76. package/dist/react/chunk-BU4D2WYQ.js +1213 -0
  77. package/dist/react/chunk-BU4D2WYQ.js.map +7 -0
  78. package/dist/react/chunk-BZE4CEBG.js +747 -0
  79. package/dist/react/chunk-BZE4CEBG.js.map +7 -0
  80. package/dist/react/chunk-CB2RANCB.js +212 -0
  81. package/dist/react/chunk-CB2RANCB.js.map +7 -0
  82. package/dist/react/chunk-DNAG7ML6.js +128 -0
  83. package/dist/react/chunk-DNAG7ML6.js.map +7 -0
  84. package/dist/react/chunk-HZWLPXFN.js +198 -0
  85. package/dist/react/chunk-HZWLPXFN.js.map +7 -0
  86. package/dist/react/chunk-I6HCCIXB.js +572 -0
  87. package/dist/react/chunk-I6HCCIXB.js.map +7 -0
  88. package/dist/react/chunk-IZ5OGWGW.js +235 -0
  89. package/dist/react/chunk-IZ5OGWGW.js.map +7 -0
  90. package/dist/react/chunk-JJCJNLNN.js +1490 -0
  91. package/dist/react/chunk-JJCJNLNN.js.map +7 -0
  92. package/dist/react/chunk-JQ3IRFDP.js +222 -0
  93. package/dist/react/chunk-JQ3IRFDP.js.map +7 -0
  94. package/dist/react/chunk-L5OARZJ7.js +1134 -0
  95. package/dist/react/chunk-L5OARZJ7.js.map +7 -0
  96. package/dist/react/chunk-LHRTXNVE.js +1600 -0
  97. package/dist/react/chunk-LHRTXNVE.js.map +7 -0
  98. package/dist/react/chunk-Q2GA4MBZ.js +1653 -0
  99. package/dist/react/chunk-Q2GA4MBZ.js.map +7 -0
  100. package/dist/react/chunk-QNSE5BAY.js +519 -0
  101. package/dist/react/chunk-QNSE5BAY.js.map +7 -0
  102. package/dist/react/chunk-QXFBG7OM.js +191 -0
  103. package/dist/react/chunk-QXFBG7OM.js.map +7 -0
  104. package/dist/react/chunk-T6ZVV5ZO.js +714 -0
  105. package/dist/react/chunk-T6ZVV5ZO.js.map +7 -0
  106. package/dist/react/chunk-URB6MY46.js +974 -0
  107. package/dist/react/chunk-URB6MY46.js.map +7 -0
  108. package/dist/react/chunk-VN62KLOC.js +931 -0
  109. package/dist/react/chunk-VN62KLOC.js.map +7 -0
  110. package/dist/react/chunk-VQVEPJSA.js +158 -0
  111. package/dist/react/chunk-VQVEPJSA.js.map +7 -0
  112. package/dist/react/chunk-XISEAGBV.js +1632 -0
  113. package/dist/react/chunk-XISEAGBV.js.map +7 -0
  114. package/dist/react/chunk-YKJUBFHE.js +575 -0
  115. package/dist/react/chunk-YKJUBFHE.js.map +7 -0
  116. package/dist/react/circleTool-NUATSEI2.js +14 -0
  117. package/dist/react/circleTool-NUATSEI2.js.map +7 -0
  118. package/dist/react/codeTool-MHCB2FNB.js +38 -0
  119. package/dist/react/codeTool-MHCB2FNB.js.map +7 -0
  120. package/dist/react/eraserTool-2GS2LQCS.js +103 -0
  121. package/dist/react/eraserTool-2GS2LQCS.js.map +7 -0
  122. package/dist/react/frameTool-PWVKKXSP.js +14 -0
  123. package/dist/react/frameTool-PWVKKXSP.js.map +7 -0
  124. package/dist/react/freehandTool-SLYGASVP.js +14 -0
  125. package/dist/react/freehandTool-SLYGASVP.js.map +7 -0
  126. package/dist/react/iconTool-GPZ7O7NH.js +18 -0
  127. package/dist/react/iconTool-GPZ7O7NH.js.map +7 -0
  128. package/dist/react/imageTool-D5R4XGEU.js +15 -0
  129. package/dist/react/imageTool-D5R4XGEU.js.map +7 -0
  130. package/dist/react/index.js +5349 -0
  131. package/dist/react/index.js.map +7 -0
  132. package/dist/react/laserTool-XVMV63AV.js +239 -0
  133. package/dist/react/laserTool-XVMV63AV.js.map +7 -0
  134. package/dist/react/lineTool-WNI5EMZ5.js +14 -0
  135. package/dist/react/lineTool-WNI5EMZ5.js.map +7 -0
  136. package/dist/react/lixCode-OJMVUXXI.ttf +0 -0
  137. package/dist/react/lixDefault-JEAPAUJL.ttf +0 -0
  138. package/dist/react/lixFancy-BPXYM5CQ.ttf +0 -0
  139. package/dist/react/lixFont-CPKEUDVM.woff2 +0 -0
  140. package/dist/react/rectangleTool-U7DZ6ZTA.js +14 -0
  141. package/dist/react/rectangleTool-U7DZ6ZTA.js.map +7 -0
  142. package/dist/react/styles.css +6627 -0
  143. package/dist/react/textTool-M7S6F3VC.js +21 -0
  144. package/dist/react/textTool-M7S6F3VC.js.map +7 -0
  145. package/package.json +39 -5
  146. package/src/EngineShortcuts.js +83 -27
  147. package/src/core/AIRenderer.js +30 -18
  148. package/src/core/GraphEngine.js +64 -41
  149. package/src/core/MermaidFlowchartRenderer.js +153 -88
  150. package/src/core/MermaidSequenceRenderer.js +168 -93
  151. package/src/core/SceneSerializer.js +86 -2
  152. package/src/core/Selection.js +119 -19
  153. package/src/core/UndoRedo.js +82 -15
  154. package/src/core/ZoomPan.js +15 -5
  155. package/src/index.js +14 -1
  156. package/src/react/LixSketchCanvas.jsx +347 -0
  157. package/src/react/components/AppMenu.jsx +482 -0
  158. package/src/react/components/Footer.jsx +85 -0
  159. package/src/react/components/Toolbar.jsx +140 -0
  160. package/src/react/components/canvas/CanvasLoadingOverlay.jsx +76 -0
  161. package/src/react/components/canvas/ContextMenu.jsx +466 -0
  162. package/src/react/components/canvas/FindBar.jsx +177 -0
  163. package/src/react/components/canvas/ImageSourcePicker.jsx +133 -0
  164. package/src/react/components/canvas/MultiSelectActions.jsx +120 -0
  165. package/src/react/components/canvas/SVGCanvas.jsx +145 -0
  166. package/src/react/components/canvas/SaveStatusIndicator.jsx +29 -0
  167. package/src/react/components/modals/CanvasPropertiesModal.jsx +358 -0
  168. package/src/react/components/modals/CommandPalette.jsx +307 -0
  169. package/src/react/components/modals/ExportImageModal.jsx +287 -0
  170. package/src/react/components/modals/GraphModal.jsx +174 -0
  171. package/src/react/components/modals/HelpModal.jsx +208 -0
  172. package/src/react/components/modals/LixScriptModal.jsx +130 -0
  173. package/src/react/components/modals/ShortcutsModal.jsx +125 -0
  174. package/src/react/components/sidebars/ArrowSidebar.jsx +173 -0
  175. package/src/react/components/sidebars/CircleSidebar.jsx +134 -0
  176. package/src/react/components/sidebars/FrameSidebar.jsx +263 -0
  177. package/src/react/components/sidebars/IconSidebar.jsx +279 -0
  178. package/src/react/components/sidebars/ImageSidebar.jsx +69 -0
  179. package/src/react/components/sidebars/LineSidebar.jsx +109 -0
  180. package/src/react/components/sidebars/PaintbrushSidebar.jsx +119 -0
  181. package/src/react/components/sidebars/RectangleSidebar.jsx +165 -0
  182. package/src/react/components/sidebars/ShapeSidebar.jsx +130 -0
  183. package/src/react/components/sidebars/TextSidebar.jsx +174 -0
  184. package/src/react/hooks/inertStores.js +58 -0
  185. package/src/react/hooks/useSketchEngine.js +76 -0
  186. package/src/react/hooks/useTranslation.js +92 -0
  187. package/src/react/index.js +41 -0
  188. package/src/react/store/useSketchStore.js +234 -0
  189. package/src/react/store/useUIStore.js +196 -0
  190. package/src/react/styles/arrowSidebar.css +263 -0
  191. package/src/react/styles/canvasStroke.css +47 -0
  192. package/src/react/styles/canvasTools.css +769 -0
  193. package/src/react/styles/circleSidebar.css +206 -0
  194. package/src/react/styles/fonts/fonts.css +29 -0
  195. package/src/react/styles/fonts/lixCode.ttf +0 -0
  196. package/src/react/styles/fonts/lixDefault.ttf +0 -0
  197. package/src/react/styles/fonts/lixDocs.ttf +0 -0
  198. package/src/react/styles/fonts/lixFancy.ttf +0 -0
  199. package/src/react/styles/fonts/lixFont.woff2 +0 -0
  200. package/src/react/styles/lineSidebar.css +207 -0
  201. package/src/react/styles/paintbrushSidebar.css +247 -0
  202. package/src/react/styles/squareSidebar.css +206 -0
  203. package/src/react/styles/writeText.css +149 -0
  204. package/src/react/styles.css +175 -0
  205. package/src/react/toolsEnum.js +20 -0
  206. package/src/react/utils/toast.js +18 -0
  207. package/src/shapes/Frame.js +7 -1
  208. package/src/shapes/Line.js +11 -5
  209. package/src/tools/arrowTool.js +30 -4
  210. package/src/tools/circleTool.js +33 -9
  211. package/src/tools/codeTool.js +5 -3
  212. package/src/tools/frameTool.js +47 -7
  213. package/src/tools/freehandTool.js +12 -4
  214. package/src/tools/iconTool.js +7 -1
  215. package/src/tools/imageTool.js +7 -1
  216. package/src/tools/lineTool.js +27 -3
  217. package/src/tools/rectangleTool.js +45 -8
  218. package/src/tools/textTool.js +23 -6
@@ -4,13 +4,15 @@ import { pushCreateAction, pushDeleteAction, pushOptionsChangeAction, pushTransf
4
4
  import { calculateSnap, clearSnapGuides } from '../core/SnapGuides.js';
5
5
 
6
6
 
7
+ function getThemeStroke() { if (typeof document === "undefined") return "#fff"; return document.body && document.body.classList.contains("theme-dark") ? "#fff" : "#1a1a2e"; }
8
+
7
9
  let arrowStartX, arrowStartY;
8
10
  let currentArrow = null;
9
11
  let isResizing = false;
10
12
  let isDragging = false;
11
13
  let activeAnchor = null;
12
14
  let isDrawingArrow = false;
13
- let arrowStrokeColor = "#fff";
15
+ let arrowStrokeColor = null;
14
16
  let arrowStrokeThickness = 2;
15
17
  let arrowOutlineStyle = "solid";
16
18
  let arrowCurved = "straight";
@@ -68,7 +70,7 @@ const handleMouseDown = (e) => {
68
70
  if (isArrowToolActive) {
69
71
  isDrawingArrow = true;
70
72
  currentArrow = new Arrow({ x, y }, { x, y }, {
71
- stroke: arrowStrokeColor,
73
+ stroke: arrowStrokeColor ?? getThemeStroke(),
72
74
  strokeWidth: arrowStrokeThickness,
73
75
  arrowOutlineStyle: arrowOutlineStyle,
74
76
  arrowHeadStyle: arrowHeadStyle,
@@ -226,6 +228,27 @@ const handleMouseMove = (e) => {
226
228
  startX = x;
227
229
  startY = y;
228
230
 
231
+ // Issue #24 bug #4: track containing frame during drag of an
232
+ // existing arrow so mouseUp re-parents correctly. Without this
233
+ // hoveredFrameArrow stays null and the arrow is detached the
234
+ // moment the user nudges it inside its own parent frame.
235
+ let newHover = null;
236
+ for (const f of shapes) {
237
+ if (f.shapeName !== 'frame') continue;
238
+ if (f === currentShape) continue;
239
+ if (typeof f.isShapeInFrame === 'function' && f.isShapeInFrame(currentShape)) {
240
+ newHover = f;
241
+ break;
242
+ }
243
+ }
244
+ if (hoveredFrameArrow && hoveredFrameArrow !== newHover && typeof hoveredFrameArrow.removeHighlight === 'function') {
245
+ hoveredFrameArrow.removeHighlight();
246
+ }
247
+ if (newHover && newHover !== hoveredFrameArrow && typeof newHover.highlightFrame === 'function') {
248
+ newHover.highlightFrame();
249
+ }
250
+ hoveredFrameArrow = newHover;
251
+
229
252
  // Snap guides
230
253
  if (window.__sketchStoreApi && window.__sketchStoreApi.getState().snapToObjects) {
231
254
  const snap = calculateSnap(currentShape, e.shiftKey, e.clientX, e.clientY);
@@ -319,10 +342,13 @@ if (isDrawingArrow && currentArrow) {
319
342
  }
320
343
 
321
344
  if (isDragging && dragOldPosArrow && currentShape) {
345
+ // Issue #34 bug #2: containment transfer happens later in this block,
346
+ // so currentShape.parentFrame is still the OLD frame here. hoveredFrameArrow
347
+ // tracks the actual destination during mousemove — use that as the truth.
322
348
  const newPos = {
323
349
  startPoint: { x: currentShape.startPoint.x, y: currentShape.startPoint.y },
324
350
  endPoint: { x: currentShape.endPoint.x, y: currentShape.endPoint.y },
325
- parentFrame: currentShape.parentFrame // Add this line
351
+ parentFrame: hoveredFrameArrow || null,
326
352
  };
327
353
  const oldPos = {
328
354
  ...dragOldPosArrow,
@@ -578,4 +604,4 @@ export {
578
604
  handleMouseUp as handleMouseUpArrow,
579
605
  cleanupAttachments,
580
606
  updateAttachedArrows
581
- };
607
+ };
@@ -4,6 +4,8 @@ import { pushCreateAction, pushDeleteAction, pushOptionsChangeAction, pushTransf
4
4
  import { cleanupAttachments } from './arrowTool.js';
5
5
  import { calculateSnap, clearSnapGuides } from '../core/SnapGuides.js';
6
6
 
7
+ function getThemeStroke() { if (typeof document === "undefined") return "#fff"; return document.body && document.body.classList.contains("theme-dark") ? "#fff" : "#1a1a2e"; }
8
+
7
9
  let isDrawingCircle = false;
8
10
  let isDraggingShapeCircle = false;
9
11
  let isResizingShapeCircle = false;
@@ -16,7 +18,7 @@ const rc = rough.svg(svg);
16
18
  let startX, startY;
17
19
 
18
20
 
19
- let circleStrokecolor = "#fff";
21
+ let circleStrokecolor = null;
20
22
  let circleBackgroundColor = "transparent";
21
23
  let circleFillStyleValue = "none";
22
24
  let circleStrokeThicknes = 2;
@@ -83,7 +85,7 @@ const handleMouseDown = (e) => {
83
85
  disableAllSideBars();
84
86
  }
85
87
  let initialOptions = {
86
- stroke: circleStrokecolor,
88
+ stroke: circleStrokecolor ?? getThemeStroke(),
87
89
  fill: circleBackgroundColor,
88
90
  fillStyle: circleFillStyleValue,
89
91
  strokeWidth: circleStrokeThicknes,
@@ -261,6 +263,25 @@ const handleMouseMove = (e) => {
261
263
  } else {
262
264
  clearSnapGuides();
263
265
  }
266
+
267
+ // Issue #24 bug #4: track containing frame during drag (see same
268
+ // block in rectangleTool for the rationale).
269
+ let newHover = null;
270
+ for (const f of shapes) {
271
+ if (f.shapeName !== 'frame') continue;
272
+ if (f === currentShape) continue;
273
+ if (typeof f.isShapeInFrame === 'function' && f.isShapeInFrame(currentShape)) {
274
+ newHover = f;
275
+ break;
276
+ }
277
+ }
278
+ if (hoveredFrameCircle && hoveredFrameCircle !== newHover && typeof hoveredFrameCircle.removeHighlight === 'function') {
279
+ hoveredFrameCircle.removeHighlight();
280
+ }
281
+ if (newHover && newHover !== hoveredFrameCircle && typeof newHover.highlightFrame === 'function') {
282
+ newHover.highlightFrame();
283
+ }
284
+ hoveredFrameCircle = newHover;
264
285
  }
265
286
 
266
287
  else if(isResizingShapeCircle && currentShape && currentShape.isSelected)
@@ -374,13 +395,16 @@ const handleMouseUp = (e) => {
374
395
  }
375
396
 
376
397
  if((isDraggingShapeCircle || isResizingShapeCircle || isRotatingShapeCircle) && dragOldPosCircle && currentShape) {
377
- const newPos = {
378
- x: currentShape.x,
379
- y: currentShape.y,
380
- rx: currentShape.rx,
381
- ry: currentShape.ry,
398
+ // Issue #34 bug #2: see rectangleTool — record the hovered frame
399
+ // as the destination, not the stale `parentFrame` (containment
400
+ // transfer happens later in this handler).
401
+ const newPos = {
402
+ x: currentShape.x,
403
+ y: currentShape.y,
404
+ rx: currentShape.rx,
405
+ ry: currentShape.ry,
382
406
  rotation: currentShape.rotation,
383
- parentFrame: currentShape.parentFrame
407
+ parentFrame: isDraggingShapeCircle ? (hoveredFrameCircle || null) : currentShape.parentFrame,
384
408
  };
385
409
  const oldPos = {
386
410
  ...dragOldPosCircle,
@@ -616,4 +640,4 @@ export {
616
640
  handleMouseDown as handleMouseDownCircle,
617
641
  handleMouseMove as handleMouseMoveCircle,
618
642
  handleMouseUp as handleMouseUpCircle,
619
- }
643
+ }
@@ -11,9 +11,11 @@ import {
11
11
  } from '../core/UndoRedo.js';
12
12
  import { cleanupAttachments, updateAttachedArrows } from './arrowTool.js';
13
13
 
14
+ function getThemeStroke() { if (typeof document === "undefined") return "#fff"; return document.body && document.body.classList.contains("theme-dark") ? "#fff" : "#1a1a2e"; }
15
+
14
16
  let codeTextSize = "25px";
15
17
  let codeTextFont = "lixCode";
16
- let codeTextColor = "#fff";
18
+ let codeTextColor = null;
17
19
  let codeTextAlign = "left";
18
20
  let codeLanguage = "auto";
19
21
 
@@ -118,7 +120,7 @@ function addCodeBlock(event) {
118
120
 
119
121
  codeElement.setAttribute("x", 0);
120
122
  codeElement.setAttribute("y", 0);
121
- codeElement.setAttribute("fill", codeTextColor);
123
+ codeElement.setAttribute("fill", codeTextColor ?? getThemeStroke());
122
124
  codeElement.setAttribute("font-size", codeTextSize);
123
125
  codeElement.setAttribute("font-family", codeTextFont);
124
126
  codeElement.setAttribute("text-anchor", textAlignElement);
@@ -131,7 +133,7 @@ function addCodeBlock(event) {
131
133
  gElement.setAttribute("data-y", y);
132
134
  codeElement.setAttribute("data-initial-size", codeTextSize);
133
135
  codeElement.setAttribute("data-initial-font", codeTextFont);
134
- codeElement.setAttribute("data-initial-color", codeTextColor);
136
+ codeElement.setAttribute("data-initial-color", codeTextColor ?? getThemeStroke());
135
137
  codeElement.setAttribute("data-initial-align", codeTextAlign);
136
138
  codeElement.setAttribute("data-language", codeLanguage);
137
139
  codeElement.setAttribute("data-type", "code");
@@ -61,9 +61,21 @@ const handleMouseDown = (e) => {
61
61
  }
62
62
  }
63
63
  }
64
- // If a contained shape was clicked, don't select the frame — let the
65
- // EventDispatcher fall through to shape-specific handlers
66
- if (clickedContainedShape) return;
64
+ // If a contained shape was clicked, don't select the frame —
65
+ // and crucially, clear `currentShape` so the EventDispatcher's
66
+ // `if (handled && prevShape && !currentShape) handled=false` gate
67
+ // triggers and the dispatcher falls through to shape-specific
68
+ // handlers. Without this clear, the dispatcher returns with
69
+ // `handled=true` and the child shape is never selected (Issue
70
+ // #22, bug #2).
71
+ if (clickedContainedShape) {
72
+ if (currentShape && currentShape.shapeName === 'frame'
73
+ && typeof currentShape.removeSelection === 'function') {
74
+ currentShape.removeSelection();
75
+ }
76
+ currentShape = null;
77
+ return;
78
+ }
67
79
 
68
80
  if (currentShape && currentShape !== clickedFrame) {
69
81
  currentShape.removeSelection();
@@ -216,12 +228,40 @@ Frame.prototype.isNearAnchor = function(x, y) {
216
228
  // Delete functionality
217
229
  function deleteCurrentFrame() {
218
230
  if (currentShape && currentShape.shapeName === 'frame') {
219
- const idx = shapes.indexOf(currentShape);
231
+ const frame = currentShape;
232
+ const isDiagramFrame = !!frame._diagramType;
233
+
234
+ // Issue #24 bug #7: regular frame → orphan children + snapshot
235
+ // (mirrors deleteSelectedShapes / EngineShortcuts).
236
+ let childSnapshot = null;
237
+ if (!isDiagramFrame) {
238
+ childSnapshot = [...(frame.containedShapes || [])];
239
+ for (const child of childSnapshot) {
240
+ if (!child || child === frame) continue;
241
+ const el = child.group || child.element;
242
+ if (el) {
243
+ if (frame.clipGroup && el.parentNode === frame.clipGroup) frame.clipGroup.removeChild(el);
244
+ if (window.svg && el.parentNode !== window.svg) window.svg.appendChild(el);
245
+ }
246
+ child.parentFrame = null;
247
+ delete child.isBeingMovedByFrame;
248
+ }
249
+ frame.containedShapes = [];
250
+ }
251
+
252
+ const idx = shapes.indexOf(frame);
220
253
  if (idx !== -1) shapes.splice(idx, 1);
221
- if (currentShape.group.parentNode) {
222
- currentShape.group.parentNode.removeChild(currentShape.group);
254
+
255
+ if (!isDiagramFrame) {
256
+ if (frame.group && frame.group.parentNode) frame.group.parentNode.removeChild(frame.group);
257
+ if (frame.clipGroup && frame.clipGroup.parentNode) frame.clipGroup.parentNode.removeChild(frame.clipGroup);
258
+ if (frame.clipPath && frame.clipPath.parentNode) frame.clipPath.parentNode.removeChild(frame.clipPath);
259
+ } else if (frame.group && frame.group.parentNode) {
260
+ frame.group.parentNode.removeChild(frame.group);
223
261
  }
224
- pushDeleteAction(currentShape);
262
+
263
+ if (childSnapshot) pushDeleteAction(frame, { childSnapshot });
264
+ else pushDeleteAction(frame);
225
265
  currentShape = null;
226
266
  }
227
267
  }
@@ -4,6 +4,8 @@ import { pushCreateAction, pushDeleteAction, pushOptionsChangeAction, pushTransf
4
4
  import { updateAttachedArrows as updateArrowsForShape, cleanupAttachments } from './arrowTool.js';
5
5
  import { calculateSnap, clearSnapGuides } from '../core/SnapGuides.js';
6
6
 
7
+ function getThemeStroke() { if (typeof document === "undefined") return "#fff"; return document.body && document.body.classList.contains("theme-dark") ? "#fff" : "#1a1a2e"; }
8
+
7
9
  // Expose updateArrowsForShape globally so FreehandStroke.updateAttachedArrows() can call it
8
10
  // (FreehandStroke.js is a separate module and cannot import freehandTool's local bindings)
9
11
  window.__updateArrowsForShape = updateArrowsForShape;
@@ -13,7 +15,7 @@ const strokeThicknesses = document.querySelectorAll(".strokeThickness span");
13
15
  const strokeStyles = document.querySelectorAll(".strokeStyleSpan");
14
16
  const strokeTapers = document.querySelectorAll(".strokeTaperSpan");
15
17
  const strokeRoughnesses = document.querySelectorAll(".strokeRoughnessSpan");
16
- let strokeColor = "#fff";
18
+ let strokeColor = null;
17
19
  let strokeThickness = 2;
18
20
  let strokeStyleValue = "solid";
19
21
  let strokeThinning = 0;
@@ -140,7 +142,7 @@ function handleMouseDown(e) {
140
142
  lastTime = Date.now();
141
143
 
142
144
  currentStroke = new FreehandStroke(points, {
143
- stroke: strokeColor,
145
+ stroke: strokeColor ?? getThemeStroke(),
144
146
  strokeWidth: strokeThickness
145
147
  });
146
148
 
@@ -411,9 +413,15 @@ function handleMouseUp(e) {
411
413
  ...dragOldPosStroke,
412
414
  parentFrame: draggedShapeInitialFrameStroke
413
415
  };
416
+ // Issue #34 bug #2: for drag, hoveredFrameStroke is the actual
417
+ // destination — currentShape.parentFrame at this point is still
418
+ // the OLD frame. Resize / rotate don't track hover, fall back to
419
+ // the shape's own parent.
414
420
  const newPosForUndo = {
415
421
  ...newPos,
416
- parentFrame: currentShape.parentFrame
422
+ parentFrame: isDraggingStroke
423
+ ? (hoveredFrameStroke || null)
424
+ : currentShape.parentFrame,
417
425
  };
418
426
 
419
427
  const frameChanged = oldPos.parentFrame !== newPosForUndo.parentFrame;
@@ -659,4 +667,4 @@ export
659
667
  handleMouseDown as handleFreehandMouseDown,
660
668
  handleMouseMove as handleFreehandMouseMove,
661
669
  handleMouseUp as handleFreehandMouseUp,
662
- }
670
+ }
@@ -1008,9 +1008,15 @@ function stopInteracting() {
1008
1008
  ...oldPos,
1009
1009
  parentFrame: draggedShapeInitialFrameIcon
1010
1010
  };
1011
+ // Issue #34 bug #2: for drag, hoveredFrameIcon tracks the actual
1012
+ // destination — currentShape.parentFrame is still the OLD frame.
1013
+ // Resize / rotate don't track hover, so fall back to whatever the
1014
+ // shape itself reports (parent doesn't change for those gestures).
1011
1015
  const newPosWithFrame = {
1012
1016
  ...newPos,
1013
- parentFrame: iconShape ? iconShape.parentFrame : null
1017
+ parentFrame: isDragging
1018
+ ? (hoveredFrameIcon || null)
1019
+ : (iconShape ? iconShape.parentFrame : null),
1014
1020
  };
1015
1021
 
1016
1022
  const stateChanged = newPos.x !== oldPos.x || newPos.y !== oldPos.y ||
@@ -1117,9 +1117,15 @@ function stopInteracting() {
1117
1117
  ...oldPos,
1118
1118
  parentFrame: draggedShapeInitialFrameImage
1119
1119
  };
1120
+ // Issue #34 bug #2: for drag, hoveredFrameImage tracks the actual
1121
+ // destination — imageShape.parentFrame is still the OLD frame here.
1122
+ // Resize / rotate don't move between frames, so fall back to the
1123
+ // shape's own parent for those.
1120
1124
  const newPosWithFrame = {
1121
1125
  ...newPos,
1122
- parentFrame: imageShape ? imageShape.parentFrame : null
1126
+ parentFrame: isDragging
1127
+ ? (hoveredFrameImage || null)
1128
+ : (imageShape ? imageShape.parentFrame : null),
1123
1129
  };
1124
1130
 
1125
1131
  // Only push transform action if something actually changed
@@ -4,12 +4,14 @@ import { pushCreateAction, pushDeleteAction, pushOptionsChangeAction, pushTransf
4
4
  import { updateAttachedArrows as updateArrowsForShape, cleanupAttachments } from './arrowTool.js';
5
5
  import { calculateSnap, clearSnapGuides } from '../core/SnapGuides.js';
6
6
 
7
+ function getThemeStroke() { if (typeof document === "undefined") return "#fff"; return document.body && document.body.classList.contains("theme-dark") ? "#fff" : "#1a1a2e"; }
8
+
7
9
  let isDrawingLine = false;
8
10
  let currentLine = null;
9
11
  let lineStartX = 0;
10
12
  let lineStartY = 0;
11
13
  let currentLineGroup = null;
12
- let lineColor = "#fff";
14
+ let lineColor = null;
13
15
  let lineStrokeWidth = 3;
14
16
  let lineStrokeStyle = "solid";
15
17
  let lineEdgeType = 1;
@@ -71,7 +73,7 @@ const handleMouseDown = (e) => {
71
73
  { x, y },
72
74
  { x, y },
73
75
  {
74
- stroke: lineColor,
76
+ stroke: lineColor ?? getThemeStroke(),
75
77
  strokeWidth: lineStrokeWidth,
76
78
  roughness: lineSktetchRate,
77
79
  bowing: lineEdgeType,
@@ -238,6 +240,26 @@ const handleMouseMove = (e) => {
238
240
  startX = x;
239
241
  startY = y;
240
242
 
243
+ // Issue #24 bug #4: track containing frame during existing-line
244
+ // drag so mouseUp re-parents from current geometry, not from a
245
+ // stale hoveredFrameLine.
246
+ let newHover = null;
247
+ for (const f of shapes) {
248
+ if (f.shapeName !== 'frame') continue;
249
+ if (f === currentShape) continue;
250
+ if (typeof f.isShapeInFrame === 'function' && f.isShapeInFrame(currentShape)) {
251
+ newHover = f;
252
+ break;
253
+ }
254
+ }
255
+ if (hoveredFrameLine && hoveredFrameLine !== newHover && typeof hoveredFrameLine.removeHighlight === 'function') {
256
+ hoveredFrameLine.removeHighlight();
257
+ }
258
+ if (newHover && newHover !== hoveredFrameLine && typeof newHover.highlightFrame === 'function') {
259
+ newHover.highlightFrame();
260
+ }
261
+ hoveredFrameLine = newHover;
262
+
241
263
  // Snap guides
242
264
  if (window.__sketchStoreApi && window.__sketchStoreApi.getState().snapToObjects) {
243
265
  const snap = calculateSnap(currentShape, e.shiftKey, e.clientX, e.clientY);
@@ -299,11 +321,13 @@ const handleMouseUp = (e) => {
299
321
  }
300
322
 
301
323
  if (isDraggingLine && dragOldPosLine && currentShape) {
324
+ // Issue #34 bug #2: use hoveredFrameLine (actual destination) instead
325
+ // of the stale parentFrame — containment transfer hasn't happened yet.
302
326
  const newPos = {
303
327
  startPoint: { x: currentShape.startPoint.x, y: currentShape.startPoint.y },
304
328
  endPoint: { x: currentShape.endPoint.x, y: currentShape.endPoint.y },
305
329
  controlPoint: currentShape.controlPoint ? { x: currentShape.controlPoint.x, y: currentShape.controlPoint.y } : null,
306
- parentFrame: currentShape.parentFrame
330
+ parentFrame: hoveredFrameLine || null,
307
331
  };
308
332
  const oldPos = {
309
333
  ...dragOldPosLine,
@@ -16,7 +16,16 @@ let startRotationMouseAngleSquare = 0;
16
16
  let startShapeRotationSquare = 0;
17
17
  const rc = rough.svg(svg);
18
18
  let startX, startY;
19
- let squareStrokecolor = "#fff";
19
+ // Issue #38 follow-up: null sentinel means "use theme default at draw
20
+ // time". The legacy `"#fff"` literal was invisible on the light canvas;
21
+ // reading the body's `theme-dark` class lets the same code produce a
22
+ // readable stroke in either mode. As soon as the user picks a colour
23
+ // from the sidebar this var holds that value verbatim.
24
+ function getThemeStroke() {
25
+ if (typeof document === 'undefined') return '#fff';
26
+ return document.body && document.body.classList.contains('theme-dark') ? '#fff' : '#1a1a2e';
27
+ }
28
+ let squareStrokecolor = null;
20
29
  let squareBackgroundColor = "transparent";
21
30
  let squareFillStyleValue = "none";
22
31
  let squareStrokeThicknes = 2;
@@ -76,7 +85,7 @@ const handleMouseDownRect = (e) => {
76
85
  }
77
86
 
78
87
  let initialOptions = {
79
- stroke: squareStrokecolor,
88
+ stroke: squareStrokecolor ?? getThemeStroke(),
80
89
  fill: squareBackgroundColor,
81
90
  fillStyle: squareFillStyleValue,
82
91
  strokeWidth: squareStrokeThicknes,
@@ -241,6 +250,28 @@ const handleMouseMoveRect = (e) => {
241
250
  } else {
242
251
  clearSnapGuides();
243
252
  }
253
+
254
+ // Issue #24 bug #4: track the actual containing frame at every
255
+ // move so the mouseUp logic re-parents correctly. Without this
256
+ // hoveredFrame stays at null for existing-shape drags and the
257
+ // shape is detached the moment the user nudges it by 1 px inside
258
+ // its own parent frame.
259
+ let newHover = null;
260
+ for (const f of shapes) {
261
+ if (f.shapeName !== 'frame') continue;
262
+ if (f === currentShape) continue;
263
+ if (typeof f.isShapeInFrame === 'function' && f.isShapeInFrame(currentShape)) {
264
+ newHover = f;
265
+ break;
266
+ }
267
+ }
268
+ if (hoveredFrame && hoveredFrame !== newHover && typeof hoveredFrame.removeHighlight === 'function') {
269
+ hoveredFrame.removeHighlight();
270
+ }
271
+ if (newHover && newHover !== hoveredFrame && typeof newHover.highlightFrame === 'function') {
272
+ newHover.highlightFrame();
273
+ }
274
+ hoveredFrame = newHover;
244
275
  } else if (isResizingShapeSquare && currentShape && currentShape.isSelected && resizingAnchorIndexSquare !== null) {
245
276
  currentShape.updatePosition(resizingAnchorIndexSquare, mouseX, mouseY);
246
277
  currentShape._skipAnchors = true;
@@ -341,13 +372,19 @@ const handleMouseUpRect = (e) => {
341
372
  }
342
373
 
343
374
  if ((isDraggingShapeSquare || isResizingShapeSquare || isRotatingShapeSquare) && dragOldPosSquare && currentShape) {
344
- const newPos = {
345
- x: currentShape.x,
346
- y: currentShape.y,
347
- width: currentShape.width,
348
- height: currentShape.height,
375
+ // Issue #34 bug #2: for drag operations the actual frame transfer
376
+ // happens AFTER this mouseUp block — so `currentShape.parentFrame`
377
+ // at this point still points at the OLD frame. Use the hovered
378
+ // frame (tracked during mousemove) as the authoritative new
379
+ // destination instead, so the undo action records a real
380
+ // attachment delta and Ctrl+Z re-attaches the shape correctly.
381
+ const newPos = {
382
+ x: currentShape.x,
383
+ y: currentShape.y,
384
+ width: currentShape.width,
385
+ height: currentShape.height,
349
386
  rotation: currentShape.rotation,
350
- parentFrame: currentShape.parentFrame
387
+ parentFrame: isDraggingShapeSquare ? (hoveredFrame || null) : currentShape.parentFrame,
351
388
  };
352
389
  const oldPos = {
353
390
  ...dragOldPosSquare,
@@ -11,6 +11,8 @@ import {
11
11
  updateSelectedElement
12
12
  } from '../core/UndoRedo.js';
13
13
  import { cleanupAttachments, updateAttachedArrows } from './arrowTool.js';
14
+
15
+ function getThemeStroke() { if (typeof document === "undefined") return "#fff"; return document.body && document.body.classList.contains("theme-dark") ? "#fff" : "#1a1a2e"; }
14
16
  import {
15
17
  addCodeBlock,
16
18
  wrapCodeElement,
@@ -27,7 +29,7 @@ import {
27
29
 
28
30
  let textSize = "30px";
29
31
  let textFont = "lixFont";
30
- let textColor = "#fff";
32
+ let textColor = null;
31
33
  let textAlign = "left";
32
34
 
33
35
  let textColorOptions = document.querySelectorAll(".textColorSpan");
@@ -118,20 +120,27 @@ function addText(event) {
118
120
 
119
121
  textElement.setAttribute("x", 0);
120
122
  textElement.setAttribute("y", 0);
121
- textElement.setAttribute("fill", textColor);
123
+ textElement.setAttribute("fill", textColor ?? getThemeStroke());
122
124
  textElement.setAttribute("font-size", textSize);
123
125
  textElement.setAttribute("font-family", textFont);
124
126
  textElement.setAttribute("text-anchor", textAlignElement);
125
127
  textElement.setAttribute("cursor", "default");
126
128
  textElement.setAttribute("white-space", "pre");
127
129
  textElement.setAttribute("dominant-baseline", "hanging");
130
+ // Issue #34 bug #5b: SVG <text> only fires pointer events on the
131
+ // painted glyphs by default — so a click between letters or in the
132
+ // padding around the text never bubbles to the <g> and the selection
133
+ // tool can't pick the shape up. `bounding-box` extends the hit area
134
+ // to the entire bounding box (SVG 2; supported in Chromium/WebKit/
135
+ // Firefox), which is what every other shape tool relies on.
136
+ textElement.setAttribute("pointer-events", "bounding-box");
128
137
  textElement.textContent = "";
129
138
 
130
139
  gElement.setAttribute("data-x", x);
131
140
  gElement.setAttribute("data-y", y);
132
141
  textElement.setAttribute("data-initial-size", textSize);
133
142
  textElement.setAttribute("data-initial-font", textFont);
134
- textElement.setAttribute("data-initial-color", textColor);
143
+ textElement.setAttribute("data-initial-color", textColor ?? getThemeStroke());
135
144
  textElement.setAttribute("data-initial-align", textAlign);
136
145
  textElement.setAttribute("data-type", "text");
137
146
  gElement.appendChild(textElement);
@@ -247,7 +256,12 @@ function makeTextEditable(textElement, groupElement) {
247
256
  input.style.lineHeight = "1.2em";
248
257
  input.style.textAlign = currentAnchor === "middle" ? "center" : currentAnchor === "end" ? "right" : "left";
249
258
  input.style.backgroundColor = "transparent";
250
- input.style.border = "none";
259
+ // Issue #34 bug #5a: dashed creation outline that grows with the
260
+ // text — same visual cue the frame tool uses while drawing. Inset
261
+ // padding-box border so the dashes follow the textarea size as the
262
+ // user types instead of clipping behind the glyphs.
263
+ input.style.border = "1px dashed rgba(255,255,255,0.55)";
264
+ input.style.borderRadius = "3px";
251
265
  input.style.outline = "none";
252
266
  document.body.appendChild(input);
253
267
 
@@ -1001,11 +1015,14 @@ const handleMouseUp = (event) => {
1001
1015
  rotation: extractRotationFromTransform(selectedElement) || 0,
1002
1016
  parentFrame: draggedShapeInitialFrameText
1003
1017
  };
1018
+ // Issue #34 bug #2: hoveredFrameText is the actual destination
1019
+ // tracked during drag — textShape.parentFrame is still the OLD
1020
+ // frame at this point.
1004
1021
  const newPosWithFrame = {
1005
1022
  x: finalTranslateX,
1006
1023
  y: finalTranslateY,
1007
1024
  rotation: extractRotationFromTransform(selectedElement) || 0,
1008
- parentFrame: textShape ? textShape.parentFrame : null
1025
+ parentFrame: hoveredFrameText || null,
1009
1026
  };
1010
1027
 
1011
1028
  const stateChanged = initialX !== finalTranslateX || initialY !== finalTranslateY;
@@ -1837,4 +1854,4 @@ window.__setCodeLanguage = function(lang) {
1837
1854
  }
1838
1855
  };
1839
1856
 
1840
- export { handleTextMouseDown, handleTextMouseMove, handleTextMouseUp, updateCodeToggleForShape, deselectElement as deselectTextElement, enterEditMode };
1857
+ export { handleTextMouseDown, handleTextMouseMove, handleTextMouseUp, updateCodeToggleForShape, deselectElement as deselectTextElement, enterEditMode };