@elixpo/lixsketch 5.6.9 → 5.6.11

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 (165) hide show
  1. package/dist/react/{AIRenderer-R5M2CIDO.js → AIRenderer-JNEZYDYQ.js} +4 -4
  2. package/dist/react/Arrow-QMR77LGQ.js +9 -0
  3. package/dist/react/Circle-5WL5HZA5.js +11 -0
  4. package/dist/react/{CopyPaste-PPDS3AD6.js → CopyPaste-V7AY56H3.js} +74 -30
  5. package/dist/react/CopyPaste-V7AY56H3.js.map +7 -0
  6. package/dist/react/{EventDispatcher-UV7LFSAS.js → EventDispatcher-E4OJCMY6.js} +154 -57
  7. package/dist/react/{EventDispatcher-UV7LFSAS.js.map → EventDispatcher-E4OJCMY6.js.map} +3 -3
  8. package/dist/react/Frame-OZCDVPMI.js +11 -0
  9. package/dist/react/FreehandStroke-QSSYWBFK.js +9 -0
  10. package/dist/react/IconShape-HXUV4VRT.js +10 -0
  11. package/dist/react/ImageShape-JGLT2UGG.js +10 -0
  12. package/dist/react/Line-YONPLUYT.js +11 -0
  13. package/dist/react/{MermaidFlowchartRenderer-EL3Q2MNW.js → MermaidFlowchartRenderer-FSH43RUP.js} +26 -7
  14. package/dist/react/{MermaidFlowchartRenderer-EL3Q2MNW.js.map → MermaidFlowchartRenderer-FSH43RUP.js.map} +2 -2
  15. package/dist/react/{MermaidSequenceRenderer-K4KHHEVC.js → MermaidSequenceRenderer-FRPNOF2V.js} +11 -9
  16. package/dist/react/{MermaidSequenceRenderer-K4KHHEVC.js.map → MermaidSequenceRenderer-FRPNOF2V.js.map} +2 -2
  17. package/dist/react/{MermaidStructuredRenderer-6LN3WISP.js → MermaidStructuredRenderer-BZGQLRIH.js} +13 -6
  18. package/dist/react/{MermaidStructuredRenderer-6LN3WISP.js.map → MermaidStructuredRenderer-BZGQLRIH.js.map} +2 -2
  19. package/dist/react/Rectangle-7KMA3SC6.js +11 -0
  20. package/dist/react/{SceneSerializer-H6KMQCO2.js → SceneSerializer-7O72LEZP.js} +13 -12
  21. package/dist/react/{SceneSerializer-H6KMQCO2.js.map → SceneSerializer-7O72LEZP.js.map} +1 -1
  22. package/dist/react/{Selection-CFWRGVKH.js → Selection-S4FYSZIF.js} +5 -5
  23. package/dist/react/{SketchEngine-UFLZJQXS.js → SketchEngine-VTWURP2K.js} +26 -26
  24. package/dist/react/{UndoRedo-RXO7YHA2.js → UndoRedo-YLQJ6DXW.js} +8 -2
  25. package/dist/react/{ZoomPan-RR7KUYZT.js → ZoomPan-AEKWKUCH.js} +2 -2
  26. package/dist/react/{arrowTool-DO327FKU.js → arrowTool-C4RDWQEX.js} +3 -3
  27. package/dist/react/{chunk-X5ENBMGL.js → chunk-2U47AU6Y.js} +3 -3
  28. package/dist/react/{chunk-X5ENBMGL.js.map → chunk-2U47AU6Y.js.map} +1 -1
  29. package/dist/react/{chunk-RK3NVAER.js → chunk-75OPJOWN.js} +7 -7
  30. package/dist/react/chunk-75OPJOWN.js.map +7 -0
  31. package/dist/react/{chunk-DODPOWNX.js → chunk-CWE5IFG7.js} +5 -5
  32. package/dist/react/{chunk-DODPOWNX.js.map → chunk-CWE5IFG7.js.map} +1 -1
  33. package/dist/react/{chunk-O7KXLFZN.js → chunk-IS2MKWGH.js} +8 -7
  34. package/dist/react/chunk-IS2MKWGH.js.map +7 -0
  35. package/dist/react/{chunk-ZNZAICWY.js → chunk-JEHOEQ6Z.js} +13 -5
  36. package/dist/react/chunk-JEHOEQ6Z.js.map +7 -0
  37. package/dist/react/{chunk-BOZEDUAF.js → chunk-JYBJXTLM.js} +40 -15
  38. package/dist/react/chunk-JYBJXTLM.js.map +7 -0
  39. package/dist/react/{chunk-6LWUR7XB.js → chunk-LXEHYCKH.js} +4 -4
  40. package/dist/react/{chunk-6LWUR7XB.js.map → chunk-LXEHYCKH.js.map} +1 -1
  41. package/dist/react/chunk-M7OYYJC6.js +17 -0
  42. package/dist/react/chunk-M7OYYJC6.js.map +7 -0
  43. package/dist/react/chunk-MS4TIQ6Z.js +131 -0
  44. package/dist/react/chunk-MS4TIQ6Z.js.map +7 -0
  45. package/dist/react/{chunk-2ALS5JGS.js → chunk-OCJN32IL.js} +2 -2
  46. package/dist/react/{chunk-KZBKYHZO.js → chunk-P7VIYR6R.js} +8 -5
  47. package/dist/react/chunk-P7VIYR6R.js.map +7 -0
  48. package/dist/react/{chunk-KC26KJW4.js → chunk-PX2635H5.js} +32 -10
  49. package/dist/react/chunk-PX2635H5.js.map +7 -0
  50. package/dist/react/{chunk-37PF7FG2.js → chunk-SDG4U7TP.js} +7 -6
  51. package/dist/react/chunk-SDG4U7TP.js.map +7 -0
  52. package/dist/react/{chunk-CGMUXYPJ.js → chunk-STJERZ25.js} +8 -4
  53. package/dist/react/chunk-STJERZ25.js.map +7 -0
  54. package/dist/react/{chunk-VZPH7OAM.js → chunk-TCWCC4Z4.js} +7 -3
  55. package/dist/react/chunk-TCWCC4Z4.js.map +7 -0
  56. package/dist/react/{chunk-VZX55VQL.js → chunk-TFCBCWL5.js} +90 -33
  57. package/dist/react/chunk-TFCBCWL5.js.map +7 -0
  58. package/dist/react/{chunk-GQLXUZHQ.js → chunk-TWHJ6PJK.js} +5 -3
  59. package/dist/react/chunk-TWHJ6PJK.js.map +7 -0
  60. package/dist/react/{chunk-NH3NVSWT.js → chunk-UCOK4V5Z.js} +4 -3
  61. package/dist/react/{chunk-NH3NVSWT.js.map → chunk-UCOK4V5Z.js.map} +2 -2
  62. package/dist/react/{chunk-W7XUY4GR.js → chunk-VJDNKJBX.js} +56 -4
  63. package/dist/react/chunk-VJDNKJBX.js.map +7 -0
  64. package/dist/react/{chunk-BT2EIJD4.js → chunk-WCA3FGWE.js} +6 -3
  65. package/dist/react/chunk-WCA3FGWE.js.map +7 -0
  66. package/dist/react/{chunk-I7TVGJFF.js → chunk-Y3FJMYPX.js} +4 -4
  67. package/dist/react/{chunk-I7TVGJFF.js.map → chunk-Y3FJMYPX.js.map} +1 -1
  68. package/dist/react/{chunk-7DD2JRBC.js → chunk-YCGLGY4C.js} +2 -2
  69. package/dist/react/{circleTool-NK4RVDAR.js → circleTool-QQV2QEVF.js} +4 -4
  70. package/dist/react/{codeTool-3WF4HEOW.js → codeTool-PTCPSZ4L.js} +5 -5
  71. package/dist/react/{frameTool-VCNV2NG2.js → frameTool-7GQPLU4Y.js} +4 -4
  72. package/dist/react/{freehandTool-TBAOD46K.js → freehandTool-3RJXTVAK.js} +4 -4
  73. package/dist/react/{iconTool-65JCAKYF.js → iconTool-DW5YJX7P.js} +5 -5
  74. package/dist/react/{imageTool-UQZUBOK2.js → imageTool-7GZQKWYT.js} +5 -5
  75. package/dist/react/index.js +68 -63
  76. package/dist/react/index.js.map +2 -2
  77. package/dist/react/{lineTool-762KBYOM.js → lineTool-5CY7BHND.js} +4 -4
  78. package/dist/react/{rectangleTool-IQK3HZK6.js → rectangleTool-DK4QL4QW.js} +4 -4
  79. package/dist/react/styles.css +4 -0
  80. package/dist/react/{textTool-ITG2SBJK.js → textTool-GP5QQYF3.js} +6 -6
  81. package/package.json +1 -1
  82. package/src/core/CopyPaste.js +66 -18
  83. package/src/core/EventDispatcher.js +71 -47
  84. package/src/core/MermaidFlowchartRenderer.js +34 -8
  85. package/src/core/MermaidSequenceRenderer.js +10 -8
  86. package/src/core/MermaidStructuredRenderer.js +12 -5
  87. package/src/core/ScreenSpaceControls.js +102 -13
  88. package/src/core/Selection.js +26 -7
  89. package/src/core/SelectionGesture.js +50 -0
  90. package/src/core/UndoRedo.js +108 -35
  91. package/src/react/components/sidebars/CircleSidebar.jsx +11 -5
  92. package/src/react/components/sidebars/IconSidebar.jsx +10 -4
  93. package/src/react/components/sidebars/RectangleSidebar.jsx +11 -5
  94. package/src/react/components/sidebars/ShapeSidebar.jsx +29 -37
  95. package/src/react/components/sidebars/TextSidebar.jsx +10 -4
  96. package/src/react/store/useUIStore.js +8 -1
  97. package/src/react/styles.css +13 -0
  98. package/src/react/utils/canvasBackgrounds.js +11 -2
  99. package/src/shapes/Arrow.js +4 -3
  100. package/src/shapes/Circle.js +58 -2
  101. package/src/shapes/Frame.js +11 -4
  102. package/src/shapes/FreehandStroke.js +2 -1
  103. package/src/shapes/Line.js +47 -20
  104. package/src/shapes/Rectangle.js +3 -1
  105. package/src/tools/arrowTool.js +1 -1
  106. package/src/tools/circleTool.js +5 -2
  107. package/src/tools/codeTool.js +1 -1
  108. package/src/tools/frameTool.js +3 -2
  109. package/src/tools/freehandTool.js +1 -1
  110. package/src/tools/iconTool.js +4 -3
  111. package/src/tools/imageTool.js +3 -2
  112. package/src/tools/lineTool.js +5 -1
  113. package/src/tools/rectangleTool.js +1 -1
  114. package/src/tools/shapeRecognitionTool.js +1 -1
  115. package/src/tools/textTool.js +2 -2
  116. package/src/utils/themeColors.js +13 -0
  117. package/dist/react/Arrow-EF7ALH2Y.js +0 -8
  118. package/dist/react/Circle-HMM5PKJ2.js +0 -10
  119. package/dist/react/CopyPaste-PPDS3AD6.js.map +0 -7
  120. package/dist/react/Frame-DELCMYV6.js +0 -11
  121. package/dist/react/FreehandStroke-WXIJKD5I.js +0 -9
  122. package/dist/react/IconShape-PZHMZSLR.js +0 -10
  123. package/dist/react/ImageShape-ZMBZARRI.js +0 -10
  124. package/dist/react/Line-VPVPL6ET.js +0 -10
  125. package/dist/react/Rectangle-4U4CD2ER.js +0 -10
  126. package/dist/react/chunk-37PF7FG2.js.map +0 -7
  127. package/dist/react/chunk-BOZEDUAF.js.map +0 -7
  128. package/dist/react/chunk-BT2EIJD4.js.map +0 -7
  129. package/dist/react/chunk-CGMUXYPJ.js.map +0 -7
  130. package/dist/react/chunk-GQLXUZHQ.js.map +0 -7
  131. package/dist/react/chunk-KC26KJW4.js.map +0 -7
  132. package/dist/react/chunk-KZBKYHZO.js.map +0 -7
  133. package/dist/react/chunk-O7KXLFZN.js.map +0 -7
  134. package/dist/react/chunk-PAKA6HUX.js +0 -59
  135. package/dist/react/chunk-PAKA6HUX.js.map +0 -7
  136. package/dist/react/chunk-RK3NVAER.js.map +0 -7
  137. package/dist/react/chunk-VZPH7OAM.js.map +0 -7
  138. package/dist/react/chunk-VZX55VQL.js.map +0 -7
  139. package/dist/react/chunk-W7XUY4GR.js.map +0 -7
  140. package/dist/react/chunk-ZNZAICWY.js.map +0 -7
  141. /package/dist/react/{AIRenderer-R5M2CIDO.js.map → AIRenderer-JNEZYDYQ.js.map} +0 -0
  142. /package/dist/react/{Arrow-EF7ALH2Y.js.map → Arrow-QMR77LGQ.js.map} +0 -0
  143. /package/dist/react/{Circle-HMM5PKJ2.js.map → Circle-5WL5HZA5.js.map} +0 -0
  144. /package/dist/react/{Frame-DELCMYV6.js.map → Frame-OZCDVPMI.js.map} +0 -0
  145. /package/dist/react/{FreehandStroke-WXIJKD5I.js.map → FreehandStroke-QSSYWBFK.js.map} +0 -0
  146. /package/dist/react/{IconShape-PZHMZSLR.js.map → IconShape-HXUV4VRT.js.map} +0 -0
  147. /package/dist/react/{ImageShape-ZMBZARRI.js.map → ImageShape-JGLT2UGG.js.map} +0 -0
  148. /package/dist/react/{Line-VPVPL6ET.js.map → Line-YONPLUYT.js.map} +0 -0
  149. /package/dist/react/{Rectangle-4U4CD2ER.js.map → Rectangle-7KMA3SC6.js.map} +0 -0
  150. /package/dist/react/{Selection-CFWRGVKH.js.map → Selection-S4FYSZIF.js.map} +0 -0
  151. /package/dist/react/{SketchEngine-UFLZJQXS.js.map → SketchEngine-VTWURP2K.js.map} +0 -0
  152. /package/dist/react/{UndoRedo-RXO7YHA2.js.map → UndoRedo-YLQJ6DXW.js.map} +0 -0
  153. /package/dist/react/{ZoomPan-RR7KUYZT.js.map → ZoomPan-AEKWKUCH.js.map} +0 -0
  154. /package/dist/react/{arrowTool-DO327FKU.js.map → arrowTool-C4RDWQEX.js.map} +0 -0
  155. /package/dist/react/{chunk-2ALS5JGS.js.map → chunk-OCJN32IL.js.map} +0 -0
  156. /package/dist/react/{chunk-7DD2JRBC.js.map → chunk-YCGLGY4C.js.map} +0 -0
  157. /package/dist/react/{circleTool-NK4RVDAR.js.map → circleTool-QQV2QEVF.js.map} +0 -0
  158. /package/dist/react/{codeTool-3WF4HEOW.js.map → codeTool-PTCPSZ4L.js.map} +0 -0
  159. /package/dist/react/{frameTool-VCNV2NG2.js.map → frameTool-7GQPLU4Y.js.map} +0 -0
  160. /package/dist/react/{freehandTool-TBAOD46K.js.map → freehandTool-3RJXTVAK.js.map} +0 -0
  161. /package/dist/react/{iconTool-65JCAKYF.js.map → iconTool-DW5YJX7P.js.map} +0 -0
  162. /package/dist/react/{imageTool-UQZUBOK2.js.map → imageTool-7GZQKWYT.js.map} +0 -0
  163. /package/dist/react/{lineTool-762KBYOM.js.map → lineTool-5CY7BHND.js.map} +0 -0
  164. /package/dist/react/{rectangleTool-IQK3HZK6.js.map → rectangleTool-DK4QL4QW.js.map} +0 -0
  165. /package/dist/react/{textTool-ITG2SBJK.js.map → textTool-GP5QQYF3.js.map} +0 -0
@@ -301,8 +301,8 @@ export function renderChartPreviewSVG(chart) {
301
301
  function push(shape, frame) {
302
302
  if (!shape) return null;
303
303
  window.shapes.push(shape);
304
- if (window.pushCreateAction) window.pushCreateAction(shape);
305
304
  if (frame?.addShapeToFrame) frame.addShapeToFrame(shape);
305
+ if (window.pushCreateAction) window.pushCreateAction(shape);
306
306
  return shape;
307
307
  }
308
308
 
@@ -343,7 +343,8 @@ function createFrame(x, y, width, height, name, type) {
343
343
  frameName: name, labelColor: TK.text,
344
344
  });
345
345
  frame._diagramType = type;
346
- push(frame);
346
+ window.shapes.push(frame);
347
+ if (window.pushCreateAction) window.pushCreateAction(frame, { frameCreation: true, containedShapes: [] });
347
348
  return frame;
348
349
  }
349
350
 
@@ -360,6 +361,7 @@ function selectOnlyFrame(frame) {
360
361
 
361
362
  export function renderEROnCanvas(diagram) {
362
363
  if (!diagram?.entities?.length || !window.Rectangle || !window.Arrow || !window.Frame) return false;
364
+ const undoBatch = window.beginUndoBatch?.();
363
365
  const TK = theme();
364
366
  const entities = layoutER(diagram);
365
367
  const width = Math.max(...entities.map(entity => entity.x + entity.width)) + 40;
@@ -411,6 +413,7 @@ export function renderEROnCanvas(diagram) {
411
413
  }
412
414
  }
413
415
  if (first?.selectShape) { window.currentShape = first; first.selectShape(); }
416
+ window.endUndoBatch?.(undoBatch, 'mermaid-er-create');
414
417
  return true;
415
418
  }
416
419
 
@@ -432,6 +435,7 @@ function rectBoundaryPoint(box, target) {
432
435
  export function renderChartOnCanvas(chart) {
433
436
  if (!chart?.series?.length || !window.Rectangle || !window.Circle || !window.Line || !window.FreehandStroke || !window.Frame) return false;
434
437
  if (chart.kind === 'pie') return renderPieOnCanvas(chart);
438
+ const undoBatch = window.beginUndoBatch?.();
435
439
  const TK = theme();
436
440
  const width = 720;
437
441
  const height = 450;
@@ -497,18 +501,20 @@ export function renderChartOnCanvas(chart) {
497
501
  }
498
502
  });
499
503
  selectOnlyFrame(frame);
504
+ window.endUndoBatch?.(undoBatch, 'mermaid-chart-create');
500
505
  return true;
501
506
  }
502
507
 
503
508
  function renderPieOnCanvas(chart) {
504
509
  if (!window.FreehandStroke || !window.TextShape) return false;
510
+ const values = chart.series[0].values.map(value => Math.max(0, value));
511
+ const total = values.reduce((sum, value) => sum + value, 0);
512
+ if (total <= 0) return false;
513
+ const undoBatch = window.beginUndoBatch?.();
505
514
  const width = 720;
506
515
  const height = 450;
507
516
  const origin = canvasOrigin(width, height);
508
517
  const frame = createFrame(origin.x, origin.y, width, height, chart.title, 'mermaid-pie');
509
- const values = chart.series[0].values.map(value => Math.max(0, value));
510
- const total = values.reduce((sum, value) => sum + value, 0);
511
- if (total <= 0) return false;
512
518
 
513
519
  const cx = origin.x + 255;
514
520
  const cy = origin.y + 235;
@@ -559,5 +565,6 @@ function renderPieOnCanvas(chart) {
559
565
  });
560
566
 
561
567
  selectOnlyFrame(frame);
568
+ window.endUndoBatch?.(undoBatch, 'mermaid-pie-create');
562
569
  return true;
563
570
  }
@@ -1,16 +1,38 @@
1
1
  /* eslint-disable */
2
2
 
3
3
  const ROTATION_SELECTOR = '[data-screen-space-rotation-anchor="true"]';
4
+ const OUTLINE_SELECTOR = '.selection-outline, .selection-box, .multi-selection-outline';
5
+ const HANDLE_SELECTOR = '.anchor, .resize-anchor, .resize-handle, .multi-selection-anchor';
6
+
7
+ export const SELECTION_CHROME = Object.freeze({
8
+ outlineWidth: 1.5,
9
+ outlineDash: '4 2',
10
+ handleSize: 10,
11
+ handleStrokeWidth: 2,
12
+ rotationRadius: 8,
13
+ rotationGap: 30,
14
+ });
15
+
16
+ export function getSelectionChromeMetrics(zoom = 1) {
17
+ const normalizedZoom = Math.max(0.001, Number(zoom) || 1);
18
+ return {
19
+ outlineWidth: SELECTION_CHROME.outlineWidth,
20
+ outlineDash: SELECTION_CHROME.outlineDash,
21
+ handleSize: SELECTION_CHROME.handleSize / normalizedZoom,
22
+ rotationRadius: SELECTION_CHROME.rotationRadius / normalizedZoom,
23
+ rotationGap: SELECTION_CHROME.rotationGap / normalizedZoom,
24
+ };
25
+ }
4
26
 
5
27
  function getZoom() {
6
- return Math.max(0.001, Number(window.currentZoom) || 1);
28
+ return Math.max(0.001, Number(globalThis.window?.currentZoom) || 1);
7
29
  }
8
30
 
9
31
  export function registerRotationAnchor(anchor, options = {}) {
10
32
  if (!anchor) return;
11
33
  const zoom = getZoom();
12
- const radius = Number(options.radius) || 8;
13
- const gap = Number(options.gap) || 30;
34
+ const radius = SELECTION_CHROME.rotationRadius;
35
+ const gap = SELECTION_CHROME.rotationGap;
14
36
  const edgeY = Number(options.edgeY);
15
37
 
16
38
  anchor.setAttribute('data-screen-space-rotation-anchor', 'true');
@@ -18,6 +40,9 @@ export function registerRotationAnchor(anchor, options = {}) {
18
40
  anchor.setAttribute('data-screen-gap', String(gap));
19
41
  anchor.setAttribute('data-applied-zoom', String(zoom));
20
42
  anchor.setAttribute('r', String(radius / zoom));
43
+ anchor.setAttribute('stroke', '#5B57D1');
44
+ anchor.setAttribute('stroke-width', String(SELECTION_CHROME.handleStrokeWidth));
45
+ anchor.setAttribute('vector-effect', 'non-scaling-stroke');
21
46
  if (Number.isFinite(edgeY)) anchor.setAttribute('cy', String(edgeY - gap / zoom));
22
47
 
23
48
  anchor.__rotationConnector = options.line || null;
@@ -31,6 +56,49 @@ function syncRotationConnector(anchor) {
31
56
  const end = anchor.__rotationConnectorEnd === '1' ? '1' : '2';
32
57
  line.setAttribute(`x${end}`, anchor.getAttribute('cx'));
33
58
  line.setAttribute(`y${end}`, anchor.getAttribute('cy'));
59
+ line.setAttribute('stroke', '#5B57D1');
60
+ line.setAttribute('stroke-width', '1');
61
+ line.setAttribute('stroke-dasharray', SELECTION_CHROME.outlineDash);
62
+ line.setAttribute('vector-effect', 'non-scaling-stroke');
63
+ }
64
+
65
+ function resizeHandleInScreenSpace(handle, zoom) {
66
+ const size = SELECTION_CHROME.handleSize / zoom;
67
+ const tagName = handle.tagName?.toLowerCase();
68
+
69
+ if (tagName === 'rect') {
70
+ const oldWidth = Number(handle.getAttribute('width')) || size;
71
+ const oldHeight = Number(handle.getAttribute('height')) || size;
72
+ const centerX = (Number(handle.getAttribute('x')) || 0) + oldWidth / 2;
73
+ const centerY = (Number(handle.getAttribute('y')) || 0) + oldHeight / 2;
74
+ handle.setAttribute('x', String(centerX - size / 2));
75
+ handle.setAttribute('y', String(centerY - size / 2));
76
+ handle.setAttribute('width', String(size));
77
+ handle.setAttribute('height', String(size));
78
+ } else if (tagName === 'circle' && !handle.matches(ROTATION_SELECTOR)) {
79
+ handle.setAttribute('r', String(size / 2));
80
+ }
81
+
82
+ handle.setAttribute('stroke', '#5B57D1');
83
+ handle.setAttribute('stroke-width', String(SELECTION_CHROME.handleStrokeWidth));
84
+ handle.setAttribute('vector-effect', 'non-scaling-stroke');
85
+ }
86
+
87
+ export function syncSelectionChromeToZoom(root = document) {
88
+ if (!root?.querySelectorAll) return;
89
+ const zoom = getZoom();
90
+
91
+ root.querySelectorAll(OUTLINE_SELECTOR).forEach((outline) => {
92
+ outline.setAttribute('fill', 'none');
93
+ outline.setAttribute('stroke', '#5B57D1');
94
+ outline.setAttribute('stroke-width', String(SELECTION_CHROME.outlineWidth));
95
+ outline.setAttribute('stroke-dasharray', SELECTION_CHROME.outlineDash);
96
+ outline.setAttribute('vector-effect', 'non-scaling-stroke');
97
+ });
98
+
99
+ root.querySelectorAll(HANDLE_SELECTOR).forEach((handle) => {
100
+ resizeHandleInScreenSpace(handle, zoom);
101
+ });
34
102
  }
35
103
 
36
104
  export function syncRotationAnchorsToZoom() {
@@ -38,23 +106,44 @@ export function syncRotationAnchorsToZoom() {
38
106
  const zoom = getZoom();
39
107
  document.querySelectorAll(ROTATION_SELECTOR).forEach((anchor) => {
40
108
  const previousZoom = Math.max(0.001, Number(anchor.getAttribute('data-applied-zoom')) || zoom);
41
- if (Math.abs(previousZoom - zoom) < 0.000001) return;
42
- const radius = Number(anchor.getAttribute('data-screen-radius')) || 8;
43
- const gap = Number(anchor.getAttribute('data-screen-gap')) || 30;
44
- const currentY = Number(anchor.getAttribute('cy')) || 0;
45
- const edgeY = currentY + gap / previousZoom;
46
-
47
- anchor.setAttribute('cy', String(edgeY - gap / zoom));
109
+ const radius = SELECTION_CHROME.rotationRadius;
110
+ const gap = SELECTION_CHROME.rotationGap;
111
+ if (Math.abs(previousZoom - zoom) >= 0.000001) {
112
+ const currentY = Number(anchor.getAttribute('cy')) || 0;
113
+ const edgeY = currentY + gap / previousZoom;
114
+ anchor.setAttribute('cy', String(edgeY - gap / zoom));
115
+ anchor.setAttribute('data-applied-zoom', String(zoom));
116
+ }
48
117
  anchor.setAttribute('r', String(radius / zoom));
49
- anchor.setAttribute('data-applied-zoom', String(zoom));
118
+ anchor.setAttribute('stroke', '#5B57D1');
119
+ anchor.setAttribute('stroke-width', String(SELECTION_CHROME.handleStrokeWidth));
120
+ anchor.setAttribute('vector-effect', 'non-scaling-stroke');
50
121
  syncRotationConnector(anchor);
51
122
  });
123
+ syncSelectionChromeToZoom(document);
52
124
  }
53
125
 
54
126
  export function installScreenSpaceControlSync(svgElement) {
55
127
  if (!svgElement || typeof MutationObserver === 'undefined') return;
56
128
  window.__screenSpaceControlObserver?.disconnect?.();
57
- const observer = new MutationObserver(syncRotationAnchorsToZoom);
58
- observer.observe(svgElement, { attributes: true, attributeFilter: ['viewBox'] });
129
+ let scheduled = false;
130
+ const scheduleSync = () => {
131
+ if (scheduled) return;
132
+ scheduled = true;
133
+ const run = () => {
134
+ scheduled = false;
135
+ syncRotationAnchorsToZoom();
136
+ };
137
+ if (typeof requestAnimationFrame === 'function') requestAnimationFrame(run);
138
+ else queueMicrotask(run);
139
+ };
140
+ const observer = new MutationObserver(scheduleSync);
141
+ observer.observe(svgElement, {
142
+ attributes: true,
143
+ attributeFilter: ['viewBox'],
144
+ childList: true,
145
+ subtree: true,
146
+ });
59
147
  window.__screenSpaceControlObserver = observer;
148
+ scheduleSync();
60
149
  }
@@ -2,7 +2,7 @@
2
2
  // Multi-selection system - copied from selection.js
3
3
 
4
4
  import { cleanupAttachments } from '../tools/arrowTool.js';
5
- import { beginUndoBatch, endUndoBatch, pushCreateAction, pushTransformAction, pushFrameAttachmentAction, pushDeleteAction } from './UndoRedo.js';
5
+ import { beginUndoBatch, captureFrameChildStates, endUndoBatch, pushCreateAction, pushTransformAction, pushFrameAttachmentAction, pushDeleteAction } from './UndoRedo.js';
6
6
  import { calculateSnap, clearSnapGuides } from './SnapGuides.js';
7
7
  import { registerRotationAnchor } from './ScreenSpaceControls.js';
8
8
 
@@ -270,6 +270,15 @@ class MultiSelection {
270
270
  }
271
271
 
272
272
  _captureShapeState(shape) {
273
+ if (shape.shapeName === 'frame') {
274
+ return {
275
+ x: shape.x, y: shape.y,
276
+ width: shape.width || 0, height: shape.height || 0,
277
+ rotation: shape.rotation || 0,
278
+ parentFrame: shape.parentFrame || null,
279
+ containedShapes: captureFrameChildStates(shape),
280
+ };
281
+ }
273
282
  switch (shape.shapeName) {
274
283
  case 'line':
275
284
  case 'arrow':
@@ -300,6 +309,13 @@ class MultiSelection {
300
309
  // Check if anything actually changed
301
310
  const changed = Object.keys(oldState).some(key => {
302
311
  if (key === 'points' || key === 'startPoint' || key === 'endPoint') return JSON.stringify(oldState[key]) !== JSON.stringify(newState[key]);
312
+ if (key === 'containedShapes') {
313
+ const compact = entries => (entries || []).map(entry => ({
314
+ shapeID: entry.shape?.shapeID,
315
+ state: entry.state,
316
+ }));
317
+ return JSON.stringify(compact(oldState[key])) !== JSON.stringify(compact(newState[key]));
318
+ }
303
319
  return oldState[key] !== newState[key];
304
320
  });
305
321
  if (changed) {
@@ -475,7 +491,6 @@ class MultiSelection {
475
491
  }
476
492
 
477
493
  createOutline(x, y, width, height) {
478
- const zoom = window.currentZoom || 1;
479
494
  const outlinePoints = [
480
495
  [x, y],
481
496
  [x + width, y],
@@ -485,11 +500,12 @@ class MultiSelection {
485
500
  ];
486
501
 
487
502
  this.outline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
503
+ this.outline.setAttribute('class', 'multi-selection-outline');
488
504
  this.outline.setAttribute('points', outlinePoints.map(p => p.join(',')).join(' '));
489
505
  this.outline.setAttribute('fill', 'none');
490
506
  this.outline.setAttribute('stroke', '#5B57D1');
491
- this.outline.setAttribute('stroke-width', 2);
492
- this.outline.setAttribute('stroke-dasharray', `${8 / zoom} ${4 / zoom}`);
507
+ this.outline.setAttribute('stroke-width', 1.5);
508
+ this.outline.setAttribute('stroke-dasharray', '4 2');
493
509
  this.outline.setAttribute('vector-effect', 'non-scaling-stroke');
494
510
  this.outline.setAttribute('style', 'pointer-events: none;');
495
511
  this.group.appendChild(this.outline);
@@ -497,7 +513,7 @@ class MultiSelection {
497
513
 
498
514
  createResizeAnchors(x, y, width, height) {
499
515
  const zoom = window.currentZoom || 1;
500
- const anchorSize = 12 / zoom;
516
+ const anchorSize = 10 / zoom;
501
517
  const anchorPositions = [
502
518
  { x: x, y: y, index: 0 },
503
519
  { x: x + width, y: y, index: 1 },
@@ -558,7 +574,7 @@ class MultiSelection {
558
574
  this.rotationLine.setAttribute('y2', y);
559
575
  this.rotationLine.setAttribute('stroke', '#5B57D1');
560
576
  this.rotationLine.setAttribute('stroke-width', 1);
561
- this.rotationLine.setAttribute('stroke-dasharray', `${3 / zoom} ${3 / zoom}`);
577
+ this.rotationLine.setAttribute('stroke-dasharray', '4 2');
562
578
  this.rotationLine.setAttribute('vector-effect', 'non-scaling-stroke');
563
579
  this.rotationLine.setAttribute('style', 'pointer-events: none;');
564
580
 
@@ -1036,10 +1052,12 @@ createRotatedControls(angleDiff = 0) {
1036
1052
  rect.setAttribute('width', b.width + 6);
1037
1053
  rect.setAttribute('height', b.height + 6);
1038
1054
  rect.setAttribute('fill', 'none');
1055
+ rect.setAttribute('class', 'selection-sub-outline');
1039
1056
  rect.setAttribute('stroke', '#5B57D1');
1040
1057
  rect.setAttribute('stroke-width', 1);
1041
1058
  rect.setAttribute('stroke-opacity', 0.35);
1042
- rect.setAttribute('stroke-dasharray', '4 3');
1059
+ rect.setAttribute('stroke-dasharray', '4 2');
1060
+ rect.setAttribute('vector-effect', 'non-scaling-stroke');
1043
1061
  rect.setAttribute('rx', 3);
1044
1062
  rect.setAttribute('style', 'pointer-events: none;');
1045
1063
  this.group.appendChild(rect);
@@ -1292,6 +1310,7 @@ createRotatedControls(angleDiff = 0) {
1292
1310
  this.selectedShapes.forEach(shape => {
1293
1311
  let shapeData;
1294
1312
  shape.removeSelection();
1313
+ if (shape.shapeName === 'line') shape.scheduleDragRender?.();
1295
1314
  switch (shape.shapeName) {
1296
1315
  case 'rectangle':
1297
1316
  case 'icon':
@@ -0,0 +1,50 @@
1
+ /* eslint-disable */
2
+
3
+ function normalizePointerId(pointerId) {
4
+ return Number.isFinite(Number(pointerId)) ? Number(pointerId) : null;
5
+ }
6
+
7
+ export function createSelectionGestureOwner() {
8
+ let activeGesture = null;
9
+
10
+ return {
11
+ captureShape(shape, pointerId) {
12
+ if (!shape) return null;
13
+ activeGesture = {
14
+ kind: 'shape',
15
+ shape,
16
+ shapeName: shape.shapeName,
17
+ pointerId: normalizePointerId(pointerId),
18
+ };
19
+ return activeGesture;
20
+ },
21
+
22
+ captureMulti(pointerId) {
23
+ activeGesture = {
24
+ kind: 'multi',
25
+ pointerId: normalizePointerId(pointerId),
26
+ };
27
+ return activeGesture;
28
+ },
29
+
30
+ get(pointerId) {
31
+ if (!activeGesture) return null;
32
+ const requestedPointer = normalizePointerId(pointerId);
33
+ if (activeGesture.pointerId !== null && requestedPointer !== null && activeGesture.pointerId !== requestedPointer) {
34
+ return null;
35
+ }
36
+ return activeGesture;
37
+ },
38
+
39
+ clear(pointerId) {
40
+ const gesture = this.get(pointerId);
41
+ if (!gesture) return false;
42
+ activeGesture = null;
43
+ return true;
44
+ },
45
+
46
+ clearAll() {
47
+ activeGesture = null;
48
+ },
49
+ };
50
+ }
@@ -55,6 +55,14 @@ export function pushCreateAction(shape, meta = null) {
55
55
  type: 'create',
56
56
  shape: shape
57
57
  };
58
+ const parentFrame = meta?.parentFrame || shape?.parentFrame || null;
59
+ if (parentFrame) {
60
+ action.parentFrame = parentFrame;
61
+ const frameIndex = Number.isInteger(meta?.frameIndex)
62
+ ? meta.frameIndex
63
+ : parentFrame.containedShapes?.indexOf(shape);
64
+ if (Number.isInteger(frameIndex) && frameIndex >= 0) action.frameIndex = frameIndex;
65
+ }
58
66
  if (meta?.frameCreation && shape?.shapeName === 'frame') {
59
67
  action.frameCreation = true;
60
68
  action.containedShapes = [...(meta.containedShapes || shape.containedShapes || [])];
@@ -108,6 +116,35 @@ export function clearUndoHistory() {
108
116
  redoStack.length = 0;
109
117
  }
110
118
 
119
+ // Collaboration scene loads rebuild shape instances. Keep this client's
120
+ // history private, but point its actions at the newly materialized objects so
121
+ // Ctrl+Z never mutates detached SVG nodes after a peer update.
122
+ export function rebindUndoHistory(currentShapes = []) {
123
+ const shapeById = new Map(currentShapes
124
+ .filter(shape => shape?.shapeID)
125
+ .map(shape => [shape.shapeID, shape]));
126
+ const visited = new WeakSet();
127
+
128
+ const rebind = (value) => {
129
+ if (!value || typeof value !== 'object') return value;
130
+ if (value.shapeID) return shapeById.get(value.shapeID) || value;
131
+ if (visited.has(value)) return value;
132
+ visited.add(value);
133
+ if (Array.isArray(value)) {
134
+ for (let index = 0; index < value.length; index += 1) value[index] = rebind(value[index]);
135
+ return value;
136
+ }
137
+ for (const key of Object.keys(value)) {
138
+ const child = value[key];
139
+ if (child && typeof child === 'object') value[key] = rebind(child);
140
+ }
141
+ return value;
142
+ };
143
+
144
+ undoStack.forEach(rebind);
145
+ redoStack.forEach(rebind);
146
+ }
147
+
111
148
  // Enhanced delete action to clean up attached arrows
112
149
  export function pushDeleteActionWithAttachments(shape) {
113
150
  let affectedArrows = [];
@@ -174,23 +211,20 @@ export function pushCreateActionWithAttachments(shape) {
174
211
 
175
212
  export function pushTransformAction(shape, oldPos, newPos) {
176
213
  if (shape.shapeName === 'frame') {
177
- // Handle frame transformation with contained shapes
214
+ // Frame transforms can resize and rotate heterogeneous children.
215
+ // Reconstructing their old geometry from the frame delta loses arrow
216
+ // endpoints, circle radii and freehand points. Prefer the snapshot
217
+ // captured at pointer-down, with a translation-only fallback for old
218
+ // callers that have not supplied one yet.
219
+ const oldSnapshots = new Map((oldPos.containedShapes || []).map(entry => [entry.shape, entry.state]));
178
220
  const containedShapesStates = shape.containedShapes.map(containedShape => ({
179
221
  shape: containedShape,
180
- oldState: {
181
- x: containedShape.x - (newPos.x - oldPos.x),
182
- y: containedShape.y - (newPos.y - oldPos.y),
183
- width: containedShape.width || 0,
184
- height: containedShape.height || 0,
185
- rotation: containedShape.rotation || 0
222
+ oldState: oldSnapshots.get(containedShape) || {
223
+ ...captureShapeGeometry(containedShape),
224
+ x: typeof containedShape.x === 'number' ? containedShape.x - (newPos.x - oldPos.x) : containedShape.x,
225
+ y: typeof containedShape.y === 'number' ? containedShape.y - (newPos.y - oldPos.y) : containedShape.y,
186
226
  },
187
- newState: {
188
- x: containedShape.x,
189
- y: containedShape.y,
190
- width: containedShape.width || 0,
191
- height: containedShape.height || 0,
192
- rotation: containedShape.rotation || 0
193
- }
227
+ newState: captureShapeGeometry(containedShape),
194
228
  }));
195
229
 
196
230
  undoStack.push({
@@ -451,6 +485,7 @@ export function pushTransformAction(shape, oldPos, newPos) {
451
485
  }))
452
486
  });
453
487
  }
488
+
454
489
  else if (shape.shapeName === 'freehandStroke') {
455
490
  // Handle freehand stroke transform
456
491
  undoStack.push({
@@ -472,6 +507,47 @@ export function pushTransformAction(shape, oldPos, newPos) {
472
507
  notifyCollaboration();
473
508
  }
474
509
 
510
+ function clonePoint(point) {
511
+ return point ? { ...point } : point;
512
+ }
513
+
514
+ export function captureShapeGeometry(shape) {
515
+ const state = {
516
+ x: shape.x,
517
+ y: shape.y,
518
+ width: shape.width,
519
+ height: shape.height,
520
+ rotation: shape.rotation || 0,
521
+ };
522
+ if (typeof shape.rx === 'number') state.rx = shape.rx;
523
+ if (typeof shape.ry === 'number') state.ry = shape.ry;
524
+ if (shape.startPoint) state.startPoint = clonePoint(shape.startPoint);
525
+ if (shape.endPoint) state.endPoint = clonePoint(shape.endPoint);
526
+ if (shape.controlPoint1) state.controlPoint1 = clonePoint(shape.controlPoint1);
527
+ if (shape.controlPoint2) state.controlPoint2 = clonePoint(shape.controlPoint2);
528
+ if (Array.isArray(shape.points)) state.points = shape.points.map(point => Array.isArray(point) ? [...point] : point);
529
+ if (typeof shape._moveOffsetX === 'number') state._moveOffsetX = shape._moveOffsetX;
530
+ if (typeof shape._moveOffsetY === 'number') state._moveOffsetY = shape._moveOffsetY;
531
+ return state;
532
+ }
533
+
534
+ export function captureFrameChildStates(frame) {
535
+ return (frame?.containedShapes || []).map(shape => ({ shape, state: captureShapeGeometry(shape) }));
536
+ }
537
+
538
+ function restoreShapeGeometry(shape, state) {
539
+ if (!shape || !state) return;
540
+ for (const key of ['x', 'y', 'width', 'height', 'rotation', 'rx', 'ry', '_moveOffsetX', '_moveOffsetY']) {
541
+ if (state[key] !== undefined) shape[key] = state[key];
542
+ }
543
+ for (const key of ['startPoint', 'endPoint', 'controlPoint1', 'controlPoint2']) {
544
+ if (state[key] !== undefined) shape[key] = clonePoint(state[key]);
545
+ }
546
+ if (state.points) shape.points = state.points.map(point => Array.isArray(point) ? [...point] : point);
547
+ if (typeof shape.updateBoundingBox === 'function') shape.updateBoundingBox();
548
+ if (typeof shape.draw === 'function') shape.draw();
549
+ }
550
+
475
551
  export function pushOptionsChangeAction(shape, oldOptions, newOptions = null) {
476
552
  undoStack.push({
477
553
  type: 'optionsChange',
@@ -533,6 +609,16 @@ function detachDeletedShapeFrame(action) {
533
609
  }
534
610
  }
535
611
 
612
+ function restoreCreatedShapeFrame(action) {
613
+ if (!action.parentFrame) return;
614
+ restoreDeletedShapeFrame(action);
615
+ }
616
+
617
+ function detachCreatedShapeFrame(action) {
618
+ if (!action.parentFrame) return;
619
+ detachDeletedShapeFrame(action);
620
+ }
621
+
536
622
  export function undo() {
537
623
  if (undoStack.length === 0) return;
538
624
  const action = undoStack.pop();
@@ -565,16 +651,7 @@ export function undo() {
565
651
 
566
652
  // Restore contained shapes to their old positions
567
653
  action.containedShapes.forEach(shapeData => {
568
- const shape = shapeData.shape;
569
- shape.x = shapeData.oldState.x;
570
- shape.y = shapeData.oldState.y;
571
- shape.width = shapeData.oldState.width;
572
- shape.height = shapeData.oldState.height;
573
- shape.rotation = shapeData.oldState.rotation;
574
-
575
- if (typeof shape.draw === 'function') {
576
- shape.draw();
577
- }
654
+ restoreShapeGeometry(shapeData.shape, shapeData.oldState);
578
655
  });
579
656
 
580
657
  action.shape.isSelected = false;
@@ -670,6 +747,7 @@ export function undo() {
670
747
  }
671
748
 
672
749
  if (action.type === 'create') {
750
+ detachCreatedShapeFrame(action);
673
751
  if (action.frameCreation && action.shape.shapeName === 'frame') {
674
752
  // Treat a manually-created frame and its initial membership as one
675
753
  // atomic action. Children remain on the canvas when the frame is
@@ -1104,16 +1182,7 @@ export function redo() {
1104
1182
 
1105
1183
  // Restore contained shapes to their new positions
1106
1184
  action.containedShapes.forEach(shapeData => {
1107
- const shape = shapeData.shape;
1108
- shape.x = shapeData.newState.x;
1109
- shape.y = shapeData.newState.y;
1110
- shape.width = shapeData.newState.width;
1111
- shape.height = shapeData.newState.height;
1112
- shape.rotation = shapeData.newState.rotation;
1113
-
1114
- if (typeof shape.draw === 'function') {
1115
- shape.draw();
1116
- }
1185
+ restoreShapeGeometry(shapeData.shape, shapeData.newState);
1117
1186
  });
1118
1187
 
1119
1188
  action.shape.isSelected = false;
@@ -1245,11 +1314,12 @@ export function redo() {
1245
1314
  }
1246
1315
  } else {
1247
1316
  // Handle other shape creation redo
1248
- shapes.push(action.shape);
1317
+ if (!shapes.includes(action.shape)) shapes.push(action.shape);
1249
1318
  if (svg) {
1250
1319
  svg.appendChild(action.shape.group);
1251
1320
  }
1252
1321
  }
1322
+ restoreCreatedShapeFrame(action);
1253
1323
  undoStack.push(action);
1254
1324
  return;
1255
1325
  }
@@ -1601,10 +1671,13 @@ document.getElementById('redo')?.addEventListener('click', redo);
1601
1671
 
1602
1672
  // Expose globally for plain scripts
1603
1673
  window.pushCreateAction = pushCreateAction;
1674
+ window.beginUndoBatch = beginUndoBatch;
1675
+ window.endUndoBatch = endUndoBatch;
1604
1676
  window.pushDeleteAction = pushDeleteAction;
1605
1677
  window.pushCanvasResetAction = pushCanvasResetAction;
1606
1678
  window.invalidateCanvasResetUndo = invalidateCanvasResetUndo;
1607
1679
  window.clearUndoHistory = clearUndoHistory;
1680
+ window.rebindUndoHistory = rebindUndoHistory;
1608
1681
  window.pushDeleteActionWithAttachments = pushDeleteActionWithAttachments;
1609
1682
  window.pushTransformAction = pushTransformAction;
1610
1683
  window.pushOptionsChangeAction = pushOptionsChangeAction;
@@ -4,8 +4,9 @@ import useSketchStore, { TOOLS } from '../../store/useSketchStore'
4
4
  import ShapeSidebar, { ToolbarButton, Divider, LayerControls } from './ShapeSidebar'
5
5
  import { useState, useCallback } from 'react'
6
6
  import { useTranslation } from '../../hooks/useTranslation'
7
+ import useUIStore from '../../store/useUIStore'
7
8
 
8
- const STROKE_COLORS = ['#1a1a20', '#ffffff', '#FF8383', '#3A994C', '#56A2E8', '#FFD700', '#FF69B4', '#A855F7']
9
+ const STROKE_COLORS = ['#000000', '#ffffff', '#FF8383', '#3A994C', '#56A2E8', '#FFD700', '#FF69B4', '#A855F7']
9
10
  const BG_COLORS = ['transparent', '#f0f0f0', '#ffcccb', '#90ee90', '#add8e6', '#FFE4B5', '#DDA0DD', '#2d2d2d']
10
11
 
11
12
  const FILLS = [
@@ -46,7 +47,12 @@ export default function CircleSidebar() {
46
47
  const { t } = useTranslation()
47
48
  const activeTool = useSketchStore((s) => s.activeTool)
48
49
  const selectedShapeSidebar = useSketchStore((s) => s.selectedShapeSidebar)
49
- const [strokeColor, setStrokeColor] = useState('#fff')
50
+ const theme = useUIStore((s) => s.theme)
51
+ const resolvedTheme = theme === 'system' && typeof window !== 'undefined'
52
+ ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
53
+ : theme
54
+ const [strokeColor, setStrokeColor] = useState(null)
55
+ const displayedStrokeColor = strokeColor ?? (resolvedTheme === 'light' ? '#000000' : '#ffffff')
50
56
  const [bgColor, setBgColor] = useState('transparent')
51
57
  const [thickness, setThickness] = useState(2)
52
58
  const [lineStyle, setLineStyle] = useState('solid')
@@ -62,10 +68,10 @@ export default function CircleSidebar() {
62
68
  <ShapeSidebar visible={activeTool === TOOLS.CIRCLE || selectedShapeSidebar === 'circle'}>
63
69
  <ToolbarButton
64
70
  tooltip={t('sidebar.strokeColor')}
65
- preview={<span className="w-4 h-4 rounded-md border border-white/20" style={{ backgroundColor: strokeColor }} />}
71
+ preview={<span className="w-4 h-4 rounded-md border border-white/20" style={{ backgroundColor: displayedStrokeColor }} />}
66
72
  >
67
73
  <p className="text-xs text-text-muted uppercase tracking-wider mb-2">{t('sidebar.sectionHeader.stroke')}</p>
68
- <ColorGrid colors={STROKE_COLORS} selected={strokeColor} onSelect={updateStroke} />
74
+ <ColorGrid colors={STROKE_COLORS} selected={displayedStrokeColor} onSelect={updateStroke} />
69
75
  </ToolbarButton>
70
76
 
71
77
  <Divider />
@@ -106,7 +112,7 @@ export default function CircleSidebar() {
106
112
  <button key={s.v} onClick={() => updateStyle(s.v)}
107
113
  className={`w-11 h-8 flex items-center justify-center rounded-lg transition-all duration-100 ${lineStyle === s.v ? 'bg-[#7667a8]/20' : 'hover:bg-white/[0.06]'}`}
108
114
  >
109
- <svg width="28" height="4" viewBox="0 0 28 4"><line x1="0" y1="2" x2="28" y2="2" stroke="#fff" strokeWidth="2" strokeDasharray={s.d} strokeLinecap="round" /></svg>
115
+ <svg width="28" height="4" viewBox="0 0 28 4"><line x1="0" y1="2" x2="28" y2="2" stroke="currentColor" strokeWidth="2" strokeDasharray={s.d} strokeLinecap="round" /></svg>
110
116
  </button>
111
117
  ))}
112
118
  </div>