kritzel-stencil 0.1.74 → 0.1.76

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 (43) hide show
  1. package/dist/cjs/index.cjs.js +131 -86
  2. package/dist/cjs/kritzel-active-users_42.cjs.entry.js +132 -19
  3. package/dist/cjs/{workspace.migrations-Dyt35LBC.js → workspace.migrations-DkmVO6dE.js} +106 -44
  4. package/dist/collection/classes/core/viewport.class.js +32 -3
  5. package/dist/collection/classes/managers/anchor.manager.js +101 -44
  6. package/dist/collection/classes/providers/broadcast-sync-provider.class.js +5 -0
  7. package/dist/collection/classes/providers/hocuspocus-sync-provider.class.js +120 -85
  8. package/dist/collection/classes/providers/indexeddb-sync-provider.class.js +5 -0
  9. package/dist/collection/classes/providers/websocket-sync-provider.class.js +5 -0
  10. package/dist/collection/classes/structures/app-state-map.structure.js +15 -4
  11. package/dist/collection/classes/structures/object-map.structure.js +75 -7
  12. package/dist/collection/components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.css +2 -2
  13. package/dist/collection/components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.js +7 -2
  14. package/dist/collection/constants/version.js +1 -1
  15. package/dist/components/index.js +1 -1
  16. package/dist/components/kritzel-awareness-cursors.js +1 -1
  17. package/dist/components/kritzel-editor.js +1 -1
  18. package/dist/components/kritzel-engine.js +1 -1
  19. package/dist/components/kritzel-settings.js +1 -1
  20. package/dist/components/{p-B4Oqnl55.js → p-31FVoNWR.js} +1 -1
  21. package/dist/components/p-jdYmu4SA.js +9 -0
  22. package/dist/components/p-xNwOWoiT.js +1 -0
  23. package/dist/esm/index.js +132 -87
  24. package/dist/esm/kritzel-active-users_42.entry.js +132 -19
  25. package/dist/esm/{workspace.migrations-B99F1MdT.js → workspace.migrations-D48_Bqvh.js} +106 -44
  26. package/dist/stencil/index.esm.js +1 -1
  27. package/dist/stencil/p-775a7246.entry.js +9 -0
  28. package/dist/stencil/{p-B99F1MdT.js → p-D48_Bqvh.js} +1 -1
  29. package/dist/stencil/stencil.esm.js +1 -1
  30. package/dist/types/classes/core/viewport.class.d.ts +8 -0
  31. package/dist/types/classes/managers/anchor.manager.d.ts +4 -0
  32. package/dist/types/classes/providers/broadcast-sync-provider.class.d.ts +2 -0
  33. package/dist/types/classes/providers/hocuspocus-sync-provider.class.d.ts +37 -1
  34. package/dist/types/classes/providers/indexeddb-sync-provider.class.d.ts +2 -0
  35. package/dist/types/classes/providers/websocket-sync-provider.class.d.ts +2 -0
  36. package/dist/types/classes/structures/object-map.structure.d.ts +6 -0
  37. package/dist/types/constants/version.d.ts +1 -1
  38. package/dist/types/interfaces/remote-cursor.interface.d.ts +1 -0
  39. package/dist/types/interfaces/sync-provider.interface.d.ts +16 -0
  40. package/package.json +1 -1
  41. package/dist/components/p-BSipRoFx.js +0 -1
  42. package/dist/components/p-RJWe82kG.js +0 -9
  43. package/dist/stencil/p-2a60e1bc.entry.js +0 -9
@@ -22386,6 +22386,7 @@ class KritzelSelectionTool extends KritzelBaseTool {
22386
22386
  * IndexedDB sync provider for local persistence
22387
22387
  */
22388
22388
  class IndexedDBSyncProvider {
22389
+ type = 'local';
22389
22390
  provider;
22390
22391
  isConnected = false;
22391
22392
  constructor(docName, doc, options) {
@@ -22407,6 +22408,10 @@ class IndexedDBSyncProvider {
22407
22408
  // IndexedDB doesn't need explicit disconnect
22408
22409
  this.isConnected = false;
22409
22410
  }
22411
+ async reconnect() {
22412
+ this.disconnect();
22413
+ return this.connect();
22414
+ }
22410
22415
  destroy() {
22411
22416
  if (this.provider) {
22412
22417
  this.provider.destroy();
@@ -25602,16 +25607,22 @@ class KritzelAnchorManager {
25602
25607
  if (!KritzelClassHelper.isInstanceOf(selectedObject, 'KritzelLine'))
25603
25608
  return null;
25604
25609
  const line = selectedObject;
25605
- const startAnchorViz = this.computeAnchorVisualization(line, 'start');
25606
- const endAnchorViz = this.computeAnchorVisualization(line, 'end');
25610
+ const startAnchorVizRaw = this.computeAnchorVisualization(line, 'start');
25611
+ const endAnchorVizRaw = this.computeAnchorVisualization(line, 'end');
25612
+ const startAnchorViz = this.isFiniteAnchorVisualization(startAnchorVizRaw) ? startAnchorVizRaw : null;
25613
+ const endAnchorViz = this.isFiniteAnchorVisualization(endAnchorVizRaw) ? endAnchorVizRaw : null;
25607
25614
  if (!startAnchorViz && !endAnchorViz)
25608
25615
  return null;
25609
- const scale = this._core.store.state.scale;
25610
- const lineStrokeWidth = line.strokeWidth / line.scale;
25616
+ const scale = this.getSafeScale(this._core.store.state.scale);
25617
+ const lineScale = this.getSafeScale(line.scale);
25618
+ const lineStrokeWidth = line.strokeWidth / lineScale;
25611
25619
  const indicatorStrokeWidth = `${2 / scale}`;
25612
25620
  const dashLength = Math.max(lineStrokeWidth * 2, 4 / scale);
25613
25621
  const dashArray = `${dashLength} ${dashLength}`;
25614
25622
  const indicatorRadius = 8 / scale;
25623
+ if (!this.areFiniteNumbers(lineStrokeWidth, dashLength, indicatorRadius)) {
25624
+ return null;
25625
+ }
25615
25626
  return {
25616
25627
  lineStrokeWidth,
25617
25628
  indicatorStrokeWidth,
@@ -25633,31 +25644,39 @@ class KritzelAnchorManager {
25633
25644
  const snapCandidate = this.getSnapCandidate();
25634
25645
  if (!snapCandidate)
25635
25646
  return null;
25636
- const scale = this._core.store.state.scale;
25647
+ if (!this.areFiniteNumbers(snapCandidate.centerX, snapCandidate.centerY, snapCandidate.lineEndpointX, snapCandidate.lineEndpointY)) {
25648
+ return null;
25649
+ }
25650
+ const scale = this.getSafeScale(this._core.store.state.scale);
25637
25651
  const indicatorRadius = 8 / scale;
25638
25652
  const indicatorStrokeWidth = `${2 / scale}`;
25639
- const lineStrokeWidth = snapCandidate.lineStrokeWidth
25640
- ? `${snapCandidate.lineStrokeWidth}`
25641
- : `${4 / scale}`;
25642
- const lineStrokeWidthNum = snapCandidate.lineStrokeWidth || (4 / scale);
25653
+ const lineStrokeWidthNum = this.areFiniteNumbers(snapCandidate.lineStrokeWidth) && snapCandidate.lineStrokeWidth > 0
25654
+ ? snapCandidate.lineStrokeWidth
25655
+ : (4 / scale);
25656
+ const lineStrokeWidth = `${lineStrokeWidthNum}`;
25643
25657
  const dashLength = Math.max(lineStrokeWidthNum * 2, 4 / scale);
25644
25658
  const dashArray = `${dashLength} ${dashLength}`;
25645
25659
  const lineStroke = snapCandidate.lineStroke || '#000000';
25646
- let solidLineEndX = snapCandidate.edgeX;
25647
- let solidLineEndY = snapCandidate.edgeY;
25660
+ let edgeX = this.areFiniteNumbers(snapCandidate.edgeX) ? snapCandidate.edgeX : undefined;
25661
+ let edgeY = this.areFiniteNumbers(snapCandidate.edgeY) ? snapCandidate.edgeY : undefined;
25662
+ let solidLineEndX = edgeX;
25663
+ let solidLineEndY = edgeY;
25648
25664
  let arrowPoints;
25649
- if (snapCandidate.arrowOffset && snapCandidate.edgeX !== undefined && snapCandidate.edgeY !== undefined) {
25650
- const dx = snapCandidate.lineEndpointX - snapCandidate.edgeX;
25651
- const dy = snapCandidate.lineEndpointY - snapCandidate.edgeY;
25665
+ const arrowOffset = this.areFiniteNumbers(snapCandidate.arrowOffset) && snapCandidate.arrowOffset > 0
25666
+ ? snapCandidate.arrowOffset
25667
+ : undefined;
25668
+ if (arrowOffset !== undefined && edgeX !== undefined && edgeY !== undefined) {
25669
+ const dx = snapCandidate.lineEndpointX - edgeX;
25670
+ const dy = snapCandidate.lineEndpointY - edgeY;
25652
25671
  const length = Math.sqrt(dx * dx + dy * dy);
25653
- if (length > snapCandidate.arrowOffset) {
25654
- solidLineEndX = snapCandidate.edgeX + (dx / length) * snapCandidate.arrowOffset;
25655
- solidLineEndY = snapCandidate.edgeY + (dy / length) * snapCandidate.arrowOffset;
25672
+ if (length > arrowOffset) {
25673
+ solidLineEndX = edgeX + (dx / length) * arrowOffset;
25674
+ solidLineEndY = edgeY + (dy / length) * arrowOffset;
25656
25675
  }
25657
25676
  // Calculate arrow head points
25658
25677
  // Direction from line endpoint to edge (arrow direction)
25659
- const arrowDx = snapCandidate.edgeX - snapCandidate.lineEndpointX;
25660
- const arrowDy = snapCandidate.edgeY - snapCandidate.lineEndpointY;
25678
+ const arrowDx = edgeX - snapCandidate.lineEndpointX;
25679
+ const arrowDy = edgeY - snapCandidate.lineEndpointY;
25661
25680
  const arrowLengthTotal = Math.sqrt(arrowDx * arrowDx + arrowDy * arrowDy);
25662
25681
  if (arrowLengthTotal > 0) {
25663
25682
  const ux = arrowDx / arrowLengthTotal;
@@ -25666,11 +25685,11 @@ class KritzelAnchorManager {
25666
25685
  const px = -uy;
25667
25686
  const py = ux;
25668
25687
  // Arrow dimensions
25669
- const arrowLength = snapCandidate.arrowOffset;
25688
+ const arrowLength = arrowOffset;
25670
25689
  const arrowWidth = arrowLength; // 1:1 ratio
25671
25690
  // Arrow tip at edge
25672
- const tipX = snapCandidate.edgeX;
25673
- const tipY = snapCandidate.edgeY;
25691
+ const tipX = edgeX;
25692
+ const tipY = edgeY;
25674
25693
  // Arrow base
25675
25694
  const baseX = tipX - ux * arrowLength;
25676
25695
  const baseY = tipY - uy * arrowLength;
@@ -25679,9 +25698,31 @@ class KritzelAnchorManager {
25679
25698
  const leftY = baseY + py * arrowWidth / 2;
25680
25699
  const rightX = baseX - px * arrowWidth / 2;
25681
25700
  const rightY = baseY - py * arrowWidth / 2;
25682
- arrowPoints = `${tipX},${tipY} ${leftX},${leftY} ${rightX},${rightY}`;
25701
+ if (this.areFiniteNumbers(tipX, tipY, leftX, leftY, rightX, rightY)) {
25702
+ arrowPoints = `${tipX},${tipY} ${leftX},${leftY} ${rightX},${rightY}`;
25703
+ }
25683
25704
  }
25684
25705
  }
25706
+ if (!this.areFiniteNumbers(indicatorRadius, lineStrokeWidthNum, dashLength)) {
25707
+ return null;
25708
+ }
25709
+ const snapLinePath = (() => {
25710
+ if (snapCandidate.controlX !== undefined &&
25711
+ snapCandidate.controlY !== undefined &&
25712
+ snapCandidate.t !== undefined &&
25713
+ this.areFiniteNumbers(snapCandidate.controlX, snapCandidate.controlY, snapCandidate.t)) {
25714
+ const startT = snapCandidate.endpoint === 'start' ? 1 - snapCandidate.t : snapCandidate.t;
25715
+ // Ensure meaningful range
25716
+ if (startT >= 1)
25717
+ return undefined;
25718
+ const segment = this.extractQuadraticSegment({ x: snapCandidate.lineEndpointX, y: snapCandidate.lineEndpointY }, { x: snapCandidate.controlX, y: snapCandidate.controlY }, { x: snapCandidate.centerX, y: snapCandidate.centerY }, startT, 1);
25719
+ if (!this.areFiniteNumbers(segment.start.x, segment.start.y, segment.control.x, segment.control.y, segment.end.x, segment.end.y)) {
25720
+ return undefined;
25721
+ }
25722
+ return `M ${segment.start.x} ${segment.start.y} Q ${segment.control.x} ${segment.control.y} ${segment.end.x} ${segment.end.y}`;
25723
+ }
25724
+ return undefined;
25725
+ })();
25685
25726
  return {
25686
25727
  indicatorRadius,
25687
25728
  indicatorStrokeWidth,
@@ -25692,27 +25733,15 @@ class KritzelAnchorManager {
25692
25733
  centerY: snapCandidate.centerY,
25693
25734
  lineEndpointX: snapCandidate.lineEndpointX,
25694
25735
  lineEndpointY: snapCandidate.lineEndpointY,
25695
- edgeX: snapCandidate.edgeX,
25696
- edgeY: snapCandidate.edgeY,
25697
- arrowOffset: snapCandidate.arrowOffset,
25736
+ edgeX,
25737
+ edgeY,
25738
+ arrowOffset,
25698
25739
  arrowStyle: snapCandidate.arrowStyle,
25699
25740
  arrowFill: snapCandidate.arrowFill,
25700
25741
  solidLineEndX,
25701
25742
  solidLineEndY,
25702
25743
  arrowPoints,
25703
- snapLinePath: (() => {
25704
- if (snapCandidate.controlX !== undefined &&
25705
- snapCandidate.controlY !== undefined &&
25706
- snapCandidate.t !== undefined) {
25707
- const startT = snapCandidate.endpoint === 'start' ? 1 - snapCandidate.t : snapCandidate.t;
25708
- // Ensure meaningful range
25709
- if (startT >= 1)
25710
- return undefined;
25711
- const segment = this.extractQuadraticSegment({ x: snapCandidate.lineEndpointX, y: snapCandidate.lineEndpointY }, { x: snapCandidate.controlX, y: snapCandidate.controlY }, { x: snapCandidate.centerX, y: snapCandidate.centerY }, startT, 1);
25712
- return `M ${segment.start.x} ${segment.start.y} Q ${segment.control.x} ${segment.control.y} ${segment.end.x} ${segment.end.y}`;
25713
- }
25714
- return undefined;
25715
- })(),
25744
+ snapLinePath,
25716
25745
  };
25717
25746
  }
25718
25747
  // ============================================
@@ -25890,12 +25919,19 @@ class KritzelAnchorManager {
25890
25919
  return null;
25891
25920
  const centerX = targetObject.centerX;
25892
25921
  const centerY = targetObject.centerY;
25922
+ if (!this.areFiniteNumbers(clipInfo.worldX, clipInfo.worldY, centerX, centerY)) {
25923
+ return null;
25924
+ }
25925
+ const pathD = this.buildAnchorPath(line, endpoint, clipInfo, targetObject) ?? undefined;
25926
+ if (pathD && !this.isFinitePathData(pathD)) {
25927
+ return null;
25928
+ }
25893
25929
  return {
25894
25930
  edgeX: clipInfo.worldX,
25895
25931
  edgeY: clipInfo.worldY,
25896
25932
  centerX,
25897
25933
  centerY,
25898
- pathD: this.buildAnchorPath(line, endpoint, clipInfo, targetObject) ?? undefined,
25934
+ pathD,
25899
25935
  };
25900
25936
  }
25901
25937
  /**
@@ -26608,9 +26644,10 @@ class KritzelAnchorManager {
26608
26644
  const sin = Math.sin(line.rotation);
26609
26645
  const rotatedX = (px - cx) * cos - (py - cy) * sin + cx;
26610
26646
  const rotatedY = (px - cx) * sin + (py - cy) * cos + cy;
26647
+ const safeLineScale = this.getSafeScale(line.scale);
26611
26648
  return {
26612
- x: rotatedX / line.scale + line.translateX,
26613
- y: rotatedY / line.scale + line.translateY,
26649
+ x: rotatedX / safeLineScale + line.translateX,
26650
+ y: rotatedY / safeLineScale + line.translateY,
26614
26651
  };
26615
26652
  }
26616
26653
  /**
@@ -26624,8 +26661,9 @@ class KritzelAnchorManager {
26624
26661
  * @returns Object with x and y coordinates in the line's local SVG space.
26625
26662
  */
26626
26663
  lineWorldToLocal(line, worldX, worldY) {
26627
- const dx = (worldX - line.translateX) * line.scale;
26628
- const dy = (worldY - line.translateY) * line.scale;
26664
+ const safeLineScale = this.getSafeScale(line.scale);
26665
+ const dx = (worldX - line.translateX) * safeLineScale;
26666
+ const dy = (worldY - line.translateY) * safeLineScale;
26629
26667
  const cx = line.totalWidth / 2;
26630
26668
  const cy = line.totalHeight / 2;
26631
26669
  const cos = Math.cos(-line.rotation);
@@ -26637,6 +26675,30 @@ class KritzelAnchorManager {
26637
26675
  y: rotatedY + line.y,
26638
26676
  };
26639
26677
  }
26678
+ getSafeScale(scale) {
26679
+ if (Number.isFinite(scale) && Math.abs(scale) > 1e-6) {
26680
+ return scale;
26681
+ }
26682
+ return 1;
26683
+ }
26684
+ areFiniteNumbers(...values) {
26685
+ return values.every(value => value !== undefined && Number.isFinite(value));
26686
+ }
26687
+ isFiniteAnchorVisualization(visualization) {
26688
+ if (!visualization) {
26689
+ return false;
26690
+ }
26691
+ if (!this.areFiniteNumbers(visualization.edgeX, visualization.edgeY, visualization.centerX, visualization.centerY)) {
26692
+ return false;
26693
+ }
26694
+ if (visualization.pathD && !this.isFinitePathData(visualization.pathD)) {
26695
+ return false;
26696
+ }
26697
+ return true;
26698
+ }
26699
+ isFinitePathData(pathD) {
26700
+ return !pathD.includes('Infinity') && !pathD.includes('NaN');
26701
+ }
26640
26702
  /**
26641
26703
  * Checks if an object can be used as an anchor target.
26642
26704
  * Excludes selection-related objects (SelectionBox, SelectionGroup) and
@@ -21,6 +21,14 @@ export class KritzelViewport {
21
21
  startX = 0;
22
22
  /** Starting Y position for pan/zoom gestures */
23
23
  startY = 0;
24
+ /** Minimum movement distance (in screen pixels) before broadcasting touch cursor position */
25
+ static TOUCH_CURSOR_BROADCAST_THRESHOLD = 5;
26
+ /** Screen X position where the current touch interaction started */
27
+ _touchStartScreenX = 0;
28
+ /** Screen Y position where the current touch interaction started */
29
+ _touchStartScreenY = 0;
30
+ /** Whether the touch movement threshold has been exceeded for cursor broadcasting */
31
+ _touchCursorBroadcastActive = false;
24
32
  /**
25
33
  * Creates a new KritzelViewport instance and initializes viewport state.
26
34
  * Sets up debounced handlers for viewport updates and scaling end events.
@@ -148,7 +156,13 @@ export class KritzelViewport {
148
156
  }
149
157
  if (event.pointerType === 'touch' || event.pointerType === 'pen') {
150
158
  const activePointers = Array.from(this._core.store.state.pointers.values());
159
+ if (activePointers.length === 1) {
160
+ this._touchStartScreenX = event.clientX;
161
+ this._touchStartScreenY = event.clientY;
162
+ this._touchCursorBroadcastActive = false;
163
+ }
151
164
  if (activePointers.length === 2) {
165
+ this._core.store.state.objects?.clearCursorPosition();
152
166
  const currentPath = this._core.store.currentPath;
153
167
  if (currentPath) {
154
168
  this._core.store.state.objects.remove(obj => obj.id === currentPath.id);
@@ -209,10 +223,24 @@ export class KritzelViewport {
209
223
  const hostRect = this._core.store.state.host.getBoundingClientRect();
210
224
  const xRelativeToHost = event.clientX - hostRect.left;
211
225
  const yRelativeToHost = event.clientY - hostRect.top;
212
- this._core.store.state.pointerX = (xRelativeToHost - this._core.store.state.translateX) / this._core.store.state.scale;
213
- this._core.store.state.pointerY = (yRelativeToHost - this._core.store.state.translateY) / this._core.store.state.scale;
214
- this._core.store.state.objects?.updateCursorPosition(this._core.store.state.pointerX, this._core.store.state.pointerY);
215
226
  const activePointers = Array.from(this._core.store.state.pointers.values());
227
+ if (this._core.store.state.isScaling || activePointers.length > 1) {
228
+ this._core.store.state.objects?.clearCursorPosition();
229
+ }
230
+ else {
231
+ this._core.store.state.pointerX = (xRelativeToHost - this._core.store.state.translateX) / this._core.store.state.scale;
232
+ this._core.store.state.pointerY = (yRelativeToHost - this._core.store.state.translateY) / this._core.store.state.scale;
233
+ if (!this._touchCursorBroadcastActive) {
234
+ const dx = event.clientX - this._touchStartScreenX;
235
+ const dy = event.clientY - this._touchStartScreenY;
236
+ if (Math.sqrt(dx * dx + dy * dy) >= KritzelViewport.TOUCH_CURSOR_BROADCAST_THRESHOLD) {
237
+ this._touchCursorBroadcastActive = true;
238
+ }
239
+ }
240
+ if (this._touchCursorBroadcastActive) {
241
+ this._core.store.state.objects?.updateCursorPosition(this._core.store.state.pointerX, this._core.store.state.pointerY);
242
+ }
243
+ }
216
244
  if (activePointers.length === 2) {
217
245
  const firstTouchX = activePointers[0].clientX - this._core.store.offsetX;
218
246
  const firstTouchY = activePointers[0].clientY - this._core.store.offsetY;
@@ -264,6 +292,7 @@ export class KritzelViewport {
264
292
  }
265
293
  }
266
294
  if (event.pointerType === 'touch' || event.pointerType === 'pen') {
295
+ this._touchCursorBroadcastActive = false;
267
296
  if (this._core.store.state.pointers.size === 0) {
268
297
  this._debounceEndScaling();
269
298
  }
@@ -286,16 +286,22 @@ export class KritzelAnchorManager {
286
286
  if (!KritzelClassHelper.isInstanceOf(selectedObject, 'KritzelLine'))
287
287
  return null;
288
288
  const line = selectedObject;
289
- const startAnchorViz = this.computeAnchorVisualization(line, 'start');
290
- const endAnchorViz = this.computeAnchorVisualization(line, 'end');
289
+ const startAnchorVizRaw = this.computeAnchorVisualization(line, 'start');
290
+ const endAnchorVizRaw = this.computeAnchorVisualization(line, 'end');
291
+ const startAnchorViz = this.isFiniteAnchorVisualization(startAnchorVizRaw) ? startAnchorVizRaw : null;
292
+ const endAnchorViz = this.isFiniteAnchorVisualization(endAnchorVizRaw) ? endAnchorVizRaw : null;
291
293
  if (!startAnchorViz && !endAnchorViz)
292
294
  return null;
293
- const scale = this._core.store.state.scale;
294
- const lineStrokeWidth = line.strokeWidth / line.scale;
295
+ const scale = this.getSafeScale(this._core.store.state.scale);
296
+ const lineScale = this.getSafeScale(line.scale);
297
+ const lineStrokeWidth = line.strokeWidth / lineScale;
295
298
  const indicatorStrokeWidth = `${2 / scale}`;
296
299
  const dashLength = Math.max(lineStrokeWidth * 2, 4 / scale);
297
300
  const dashArray = `${dashLength} ${dashLength}`;
298
301
  const indicatorRadius = 8 / scale;
302
+ if (!this.areFiniteNumbers(lineStrokeWidth, dashLength, indicatorRadius)) {
303
+ return null;
304
+ }
299
305
  return {
300
306
  lineStrokeWidth,
301
307
  indicatorStrokeWidth,
@@ -317,31 +323,39 @@ export class KritzelAnchorManager {
317
323
  const snapCandidate = this.getSnapCandidate();
318
324
  if (!snapCandidate)
319
325
  return null;
320
- const scale = this._core.store.state.scale;
326
+ if (!this.areFiniteNumbers(snapCandidate.centerX, snapCandidate.centerY, snapCandidate.lineEndpointX, snapCandidate.lineEndpointY)) {
327
+ return null;
328
+ }
329
+ const scale = this.getSafeScale(this._core.store.state.scale);
321
330
  const indicatorRadius = 8 / scale;
322
331
  const indicatorStrokeWidth = `${2 / scale}`;
323
- const lineStrokeWidth = snapCandidate.lineStrokeWidth
324
- ? `${snapCandidate.lineStrokeWidth}`
325
- : `${4 / scale}`;
326
- const lineStrokeWidthNum = snapCandidate.lineStrokeWidth || (4 / scale);
332
+ const lineStrokeWidthNum = this.areFiniteNumbers(snapCandidate.lineStrokeWidth) && snapCandidate.lineStrokeWidth > 0
333
+ ? snapCandidate.lineStrokeWidth
334
+ : (4 / scale);
335
+ const lineStrokeWidth = `${lineStrokeWidthNum}`;
327
336
  const dashLength = Math.max(lineStrokeWidthNum * 2, 4 / scale);
328
337
  const dashArray = `${dashLength} ${dashLength}`;
329
338
  const lineStroke = snapCandidate.lineStroke || '#000000';
330
- let solidLineEndX = snapCandidate.edgeX;
331
- let solidLineEndY = snapCandidate.edgeY;
339
+ let edgeX = this.areFiniteNumbers(snapCandidate.edgeX) ? snapCandidate.edgeX : undefined;
340
+ let edgeY = this.areFiniteNumbers(snapCandidate.edgeY) ? snapCandidate.edgeY : undefined;
341
+ let solidLineEndX = edgeX;
342
+ let solidLineEndY = edgeY;
332
343
  let arrowPoints;
333
- if (snapCandidate.arrowOffset && snapCandidate.edgeX !== undefined && snapCandidate.edgeY !== undefined) {
334
- const dx = snapCandidate.lineEndpointX - snapCandidate.edgeX;
335
- const dy = snapCandidate.lineEndpointY - snapCandidate.edgeY;
344
+ const arrowOffset = this.areFiniteNumbers(snapCandidate.arrowOffset) && snapCandidate.arrowOffset > 0
345
+ ? snapCandidate.arrowOffset
346
+ : undefined;
347
+ if (arrowOffset !== undefined && edgeX !== undefined && edgeY !== undefined) {
348
+ const dx = snapCandidate.lineEndpointX - edgeX;
349
+ const dy = snapCandidate.lineEndpointY - edgeY;
336
350
  const length = Math.sqrt(dx * dx + dy * dy);
337
- if (length > snapCandidate.arrowOffset) {
338
- solidLineEndX = snapCandidate.edgeX + (dx / length) * snapCandidate.arrowOffset;
339
- solidLineEndY = snapCandidate.edgeY + (dy / length) * snapCandidate.arrowOffset;
351
+ if (length > arrowOffset) {
352
+ solidLineEndX = edgeX + (dx / length) * arrowOffset;
353
+ solidLineEndY = edgeY + (dy / length) * arrowOffset;
340
354
  }
341
355
  // Calculate arrow head points
342
356
  // Direction from line endpoint to edge (arrow direction)
343
- const arrowDx = snapCandidate.edgeX - snapCandidate.lineEndpointX;
344
- const arrowDy = snapCandidate.edgeY - snapCandidate.lineEndpointY;
357
+ const arrowDx = edgeX - snapCandidate.lineEndpointX;
358
+ const arrowDy = edgeY - snapCandidate.lineEndpointY;
345
359
  const arrowLengthTotal = Math.sqrt(arrowDx * arrowDx + arrowDy * arrowDy);
346
360
  if (arrowLengthTotal > 0) {
347
361
  const ux = arrowDx / arrowLengthTotal;
@@ -350,11 +364,11 @@ export class KritzelAnchorManager {
350
364
  const px = -uy;
351
365
  const py = ux;
352
366
  // Arrow dimensions
353
- const arrowLength = snapCandidate.arrowOffset;
367
+ const arrowLength = arrowOffset;
354
368
  const arrowWidth = arrowLength; // 1:1 ratio
355
369
  // Arrow tip at edge
356
- const tipX = snapCandidate.edgeX;
357
- const tipY = snapCandidate.edgeY;
370
+ const tipX = edgeX;
371
+ const tipY = edgeY;
358
372
  // Arrow base
359
373
  const baseX = tipX - ux * arrowLength;
360
374
  const baseY = tipY - uy * arrowLength;
@@ -363,9 +377,31 @@ export class KritzelAnchorManager {
363
377
  const leftY = baseY + py * arrowWidth / 2;
364
378
  const rightX = baseX - px * arrowWidth / 2;
365
379
  const rightY = baseY - py * arrowWidth / 2;
366
- arrowPoints = `${tipX},${tipY} ${leftX},${leftY} ${rightX},${rightY}`;
380
+ if (this.areFiniteNumbers(tipX, tipY, leftX, leftY, rightX, rightY)) {
381
+ arrowPoints = `${tipX},${tipY} ${leftX},${leftY} ${rightX},${rightY}`;
382
+ }
367
383
  }
368
384
  }
385
+ if (!this.areFiniteNumbers(indicatorRadius, lineStrokeWidthNum, dashLength)) {
386
+ return null;
387
+ }
388
+ const snapLinePath = (() => {
389
+ if (snapCandidate.controlX !== undefined &&
390
+ snapCandidate.controlY !== undefined &&
391
+ snapCandidate.t !== undefined &&
392
+ this.areFiniteNumbers(snapCandidate.controlX, snapCandidate.controlY, snapCandidate.t)) {
393
+ const startT = snapCandidate.endpoint === 'start' ? 1 - snapCandidate.t : snapCandidate.t;
394
+ // Ensure meaningful range
395
+ if (startT >= 1)
396
+ return undefined;
397
+ const segment = this.extractQuadraticSegment({ x: snapCandidate.lineEndpointX, y: snapCandidate.lineEndpointY }, { x: snapCandidate.controlX, y: snapCandidate.controlY }, { x: snapCandidate.centerX, y: snapCandidate.centerY }, startT, 1);
398
+ if (!this.areFiniteNumbers(segment.start.x, segment.start.y, segment.control.x, segment.control.y, segment.end.x, segment.end.y)) {
399
+ return undefined;
400
+ }
401
+ return `M ${segment.start.x} ${segment.start.y} Q ${segment.control.x} ${segment.control.y} ${segment.end.x} ${segment.end.y}`;
402
+ }
403
+ return undefined;
404
+ })();
369
405
  return {
370
406
  indicatorRadius,
371
407
  indicatorStrokeWidth,
@@ -376,27 +412,15 @@ export class KritzelAnchorManager {
376
412
  centerY: snapCandidate.centerY,
377
413
  lineEndpointX: snapCandidate.lineEndpointX,
378
414
  lineEndpointY: snapCandidate.lineEndpointY,
379
- edgeX: snapCandidate.edgeX,
380
- edgeY: snapCandidate.edgeY,
381
- arrowOffset: snapCandidate.arrowOffset,
415
+ edgeX,
416
+ edgeY,
417
+ arrowOffset,
382
418
  arrowStyle: snapCandidate.arrowStyle,
383
419
  arrowFill: snapCandidate.arrowFill,
384
420
  solidLineEndX,
385
421
  solidLineEndY,
386
422
  arrowPoints,
387
- snapLinePath: (() => {
388
- if (snapCandidate.controlX !== undefined &&
389
- snapCandidate.controlY !== undefined &&
390
- snapCandidate.t !== undefined) {
391
- const startT = snapCandidate.endpoint === 'start' ? 1 - snapCandidate.t : snapCandidate.t;
392
- // Ensure meaningful range
393
- if (startT >= 1)
394
- return undefined;
395
- const segment = this.extractQuadraticSegment({ x: snapCandidate.lineEndpointX, y: snapCandidate.lineEndpointY }, { x: snapCandidate.controlX, y: snapCandidate.controlY }, { x: snapCandidate.centerX, y: snapCandidate.centerY }, startT, 1);
396
- return `M ${segment.start.x} ${segment.start.y} Q ${segment.control.x} ${segment.control.y} ${segment.end.x} ${segment.end.y}`;
397
- }
398
- return undefined;
399
- })(),
423
+ snapLinePath,
400
424
  };
401
425
  }
402
426
  // ============================================
@@ -574,12 +598,19 @@ export class KritzelAnchorManager {
574
598
  return null;
575
599
  const centerX = targetObject.centerX;
576
600
  const centerY = targetObject.centerY;
601
+ if (!this.areFiniteNumbers(clipInfo.worldX, clipInfo.worldY, centerX, centerY)) {
602
+ return null;
603
+ }
604
+ const pathD = this.buildAnchorPath(line, endpoint, clipInfo, targetObject) ?? undefined;
605
+ if (pathD && !this.isFinitePathData(pathD)) {
606
+ return null;
607
+ }
577
608
  return {
578
609
  edgeX: clipInfo.worldX,
579
610
  edgeY: clipInfo.worldY,
580
611
  centerX,
581
612
  centerY,
582
- pathD: this.buildAnchorPath(line, endpoint, clipInfo, targetObject) ?? undefined,
613
+ pathD,
583
614
  };
584
615
  }
585
616
  /**
@@ -1292,9 +1323,10 @@ export class KritzelAnchorManager {
1292
1323
  const sin = Math.sin(line.rotation);
1293
1324
  const rotatedX = (px - cx) * cos - (py - cy) * sin + cx;
1294
1325
  const rotatedY = (px - cx) * sin + (py - cy) * cos + cy;
1326
+ const safeLineScale = this.getSafeScale(line.scale);
1295
1327
  return {
1296
- x: rotatedX / line.scale + line.translateX,
1297
- y: rotatedY / line.scale + line.translateY,
1328
+ x: rotatedX / safeLineScale + line.translateX,
1329
+ y: rotatedY / safeLineScale + line.translateY,
1298
1330
  };
1299
1331
  }
1300
1332
  /**
@@ -1308,8 +1340,9 @@ export class KritzelAnchorManager {
1308
1340
  * @returns Object with x and y coordinates in the line's local SVG space.
1309
1341
  */
1310
1342
  lineWorldToLocal(line, worldX, worldY) {
1311
- const dx = (worldX - line.translateX) * line.scale;
1312
- const dy = (worldY - line.translateY) * line.scale;
1343
+ const safeLineScale = this.getSafeScale(line.scale);
1344
+ const dx = (worldX - line.translateX) * safeLineScale;
1345
+ const dy = (worldY - line.translateY) * safeLineScale;
1313
1346
  const cx = line.totalWidth / 2;
1314
1347
  const cy = line.totalHeight / 2;
1315
1348
  const cos = Math.cos(-line.rotation);
@@ -1321,6 +1354,30 @@ export class KritzelAnchorManager {
1321
1354
  y: rotatedY + line.y,
1322
1355
  };
1323
1356
  }
1357
+ getSafeScale(scale) {
1358
+ if (Number.isFinite(scale) && Math.abs(scale) > 1e-6) {
1359
+ return scale;
1360
+ }
1361
+ return 1;
1362
+ }
1363
+ areFiniteNumbers(...values) {
1364
+ return values.every(value => value !== undefined && Number.isFinite(value));
1365
+ }
1366
+ isFiniteAnchorVisualization(visualization) {
1367
+ if (!visualization) {
1368
+ return false;
1369
+ }
1370
+ if (!this.areFiniteNumbers(visualization.edgeX, visualization.edgeY, visualization.centerX, visualization.centerY)) {
1371
+ return false;
1372
+ }
1373
+ if (visualization.pathD && !this.isFinitePathData(visualization.pathD)) {
1374
+ return false;
1375
+ }
1376
+ return true;
1377
+ }
1378
+ isFinitePathData(pathD) {
1379
+ return !pathD.includes('Infinity') && !pathD.includes('NaN');
1380
+ }
1324
1381
  /**
1325
1382
  * Checks if an object can be used as an anchor target.
1326
1383
  * Excludes selection-related objects (SelectionBox, SelectionGroup) and
@@ -6,6 +6,7 @@ import * as decoding from "lib0/decoding";
6
6
  * This is a lightweight alternative to y-webrtc for browser-tab-only sync
7
7
  */
8
8
  export class BroadcastSyncProvider {
9
+ type = 'local';
9
10
  doc;
10
11
  channel;
11
12
  _synced = false;
@@ -87,6 +88,10 @@ export class BroadcastSyncProvider {
87
88
  disconnect() {
88
89
  // BroadcastChannel doesn't have explicit disconnect
89
90
  }
91
+ async reconnect() {
92
+ this.disconnect();
93
+ return this.connect();
94
+ }
90
95
  destroy() {
91
96
  this.doc.off('update', this.handleDocUpdate);
92
97
  this.channel.close();