kritzel-stencil 0.1.72 → 0.1.74

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 (177) hide show
  1. package/dist/cjs/index-Dc7LOVhs.js +2 -2
  2. package/dist/cjs/index.cjs.js +58 -18
  3. package/dist/cjs/{kritzel-active-users_41.cjs.entry.js → kritzel-active-users_42.cjs.entry.js} +586 -172
  4. package/dist/cjs/kritzel-brush-style.cjs.entry.js +1 -1
  5. package/dist/cjs/loader.cjs.js +1 -1
  6. package/dist/cjs/stencil.cjs.js +1 -1
  7. package/dist/cjs/{workspace.migrations-DcwqsqPC.js → workspace.migrations-Dyt35LBC.js} +58 -5
  8. package/dist/collection/classes/core/core.class.js +9 -3
  9. package/dist/collection/classes/core/store.class.js +20 -6
  10. package/dist/collection/classes/handlers/selection.handler.js +15 -2
  11. package/dist/collection/classes/objects/base-object.class.js +2 -0
  12. package/dist/collection/classes/objects/custom-element.class.js +1 -0
  13. package/dist/collection/classes/objects/group.class.js +1 -0
  14. package/dist/collection/classes/objects/image.class.js +1 -0
  15. package/dist/collection/classes/objects/line.class.js +1 -0
  16. package/dist/collection/classes/objects/path.class.js +1 -0
  17. package/dist/collection/classes/objects/selection-box.class.js +1 -0
  18. package/dist/collection/classes/objects/selection-group.class.js +13 -1
  19. package/dist/collection/classes/objects/shape.class.js +1 -0
  20. package/dist/collection/classes/objects/text.class.js +1 -0
  21. package/dist/collection/classes/providers/hocuspocus-sync-provider.class.js +57 -17
  22. package/dist/collection/classes/structures/object-map.structure.js +102 -7
  23. package/dist/collection/classes/tools/brush-tool.class.js +4 -0
  24. package/dist/collection/classes/tools/line-tool.class.js +4 -0
  25. package/dist/collection/classes/tools/shape-tool.class.js +2 -0
  26. package/dist/collection/collection-manifest.json +3 -2
  27. package/dist/collection/components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.css +110 -0
  28. package/dist/collection/components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.js +347 -0
  29. package/dist/collection/components/core/kritzel-cursor-trail/kritzel-cursor-trail.js +1 -1
  30. package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +3 -3
  31. package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +150 -109
  32. package/dist/collection/components/shared/kritzel-avatar/kritzel-avatar.js +3 -3
  33. package/dist/collection/components/shared/kritzel-brush-style/kritzel-brush-style.js +1 -1
  34. package/dist/collection/components/shared/kritzel-button/kritzel-button.js +2 -2
  35. package/dist/collection/components/shared/kritzel-color/kritzel-color.js +2 -2
  36. package/dist/collection/components/shared/kritzel-color-palette/kritzel-color-palette.js +1 -1
  37. package/dist/collection/components/shared/kritzel-dropdown/kritzel-dropdown.js +1 -1
  38. package/dist/collection/components/shared/kritzel-font/kritzel-font.js +1 -1
  39. package/dist/collection/components/shared/kritzel-font-size/kritzel-font-size.js +1 -1
  40. package/dist/collection/components/shared/kritzel-input/kritzel-input.js +1 -1
  41. package/dist/collection/components/shared/kritzel-master-detail/kritzel-master-detail.js +3 -3
  42. package/dist/collection/components/shared/kritzel-menu/kritzel-menu.js +1 -1
  43. package/dist/collection/components/shared/kritzel-menu-item/kritzel-menu-item.js +2 -2
  44. package/dist/collection/components/shared/kritzel-numeric-input/kritzel-numeric-input.js +1 -1
  45. package/dist/collection/components/shared/kritzel-opacity-slider/kritzel-opacity-slider.js +1 -1
  46. package/dist/collection/components/shared/kritzel-portal/kritzel-portal.js +1 -1
  47. package/dist/collection/components/shared/kritzel-slide-toggle/kritzel-slide-toggle.js +1 -1
  48. package/dist/collection/components/shared/kritzel-split-button/kritzel-split-button.js +1 -1
  49. package/dist/collection/components/shared/kritzel-stroke-size/kritzel-stroke-size.js +1 -1
  50. package/dist/collection/components/shared/kritzel-tooltip/kritzel-tooltip.js +2 -2
  51. package/dist/collection/components/ui/kritzel-back-to-content/kritzel-back-to-content.js +1 -1
  52. package/dist/collection/components/ui/kritzel-context-menu/kritzel-context-menu.js +1 -1
  53. package/dist/collection/components/ui/kritzel-controls/kritzel-controls.js +5 -5
  54. package/dist/collection/components/ui/kritzel-current-user/kritzel-current-user.js +1 -1
  55. package/dist/collection/components/ui/kritzel-current-user-dialog/kritzel-current-user-dialog.js +1 -1
  56. package/dist/collection/components/ui/kritzel-export/kritzel-export.js +1 -1
  57. package/dist/collection/components/ui/kritzel-login-dialog/kritzel-login-dialog.js +1 -1
  58. package/dist/collection/components/ui/kritzel-more-menu/kritzel-more-menu.js +1 -1
  59. package/dist/collection/components/ui/kritzel-settings/kritzel-settings.js +1 -1
  60. package/dist/collection/components/ui/kritzel-share-dialog/kritzel-share-dialog.js +2 -2
  61. package/dist/collection/components/ui/kritzel-utility-panel/kritzel-utility-panel.js +1 -1
  62. package/dist/collection/constants/schema.constants.js +1 -1
  63. package/dist/collection/constants/version.js +1 -1
  64. package/dist/collection/interfaces/remote-cursor.interface.js +1 -0
  65. package/dist/collection/migrations/workspace.migrations.js +10 -1
  66. package/dist/components/index.d.ts +2 -0
  67. package/dist/components/index.js +1 -1
  68. package/dist/components/kritzel-active-users.js +1 -1
  69. package/dist/components/kritzel-avatar.js +1 -1
  70. package/dist/components/kritzel-awareness-cursors.d.ts +11 -0
  71. package/dist/components/kritzel-awareness-cursors.js +1 -0
  72. package/dist/components/kritzel-back-to-content.js +1 -1
  73. package/dist/components/kritzel-brush-style.js +1 -1
  74. package/dist/components/kritzel-button.js +1 -1
  75. package/dist/components/kritzel-color-palette.js +1 -1
  76. package/dist/components/kritzel-color.js +1 -1
  77. package/dist/components/kritzel-context-menu.js +1 -1
  78. package/dist/components/kritzel-controls.js +1 -1
  79. package/dist/components/kritzel-current-user-dialog.js +1 -1
  80. package/dist/components/kritzel-current-user.js +1 -1
  81. package/dist/components/kritzel-cursor-trail.js +1 -1
  82. package/dist/components/kritzel-dropdown.js +1 -1
  83. package/dist/components/kritzel-editor.js +1 -1
  84. package/dist/components/kritzel-engine.js +1 -1
  85. package/dist/components/kritzel-export.js +1 -1
  86. package/dist/components/kritzel-font-family.js +1 -1
  87. package/dist/components/kritzel-font-size.js +1 -1
  88. package/dist/components/kritzel-font.js +1 -1
  89. package/dist/components/kritzel-input.js +1 -1
  90. package/dist/components/kritzel-login-dialog.js +1 -1
  91. package/dist/components/kritzel-master-detail.js +1 -1
  92. package/dist/components/kritzel-menu-item.js +1 -1
  93. package/dist/components/kritzel-menu.js +1 -1
  94. package/dist/components/kritzel-more-menu.js +1 -1
  95. package/dist/components/kritzel-numeric-input.js +1 -1
  96. package/dist/components/kritzel-opacity-slider.js +1 -1
  97. package/dist/components/kritzel-portal.js +1 -1
  98. package/dist/components/kritzel-settings.js +1 -1
  99. package/dist/components/kritzel-share-dialog.js +1 -1
  100. package/dist/components/kritzel-slide-toggle.js +1 -1
  101. package/dist/components/kritzel-split-button.js +1 -1
  102. package/dist/components/kritzel-stroke-size.js +1 -1
  103. package/dist/components/kritzel-tool-config.js +1 -1
  104. package/dist/components/kritzel-tooltip.js +1 -1
  105. package/dist/components/kritzel-utility-panel.js +1 -1
  106. package/dist/components/kritzel-workspace-manager.js +1 -1
  107. package/dist/components/{p-Dp8idtVD.js → p-0kShCfeb.js} +1 -1
  108. package/dist/components/{p-B47JuZiD.js → p-2OYw6GJ7.js} +1 -1
  109. package/dist/components/p-7o2FWtFx.js +1 -0
  110. package/dist/components/{p-dR_q96Q3.js → p-B4Oqnl55.js} +1 -1
  111. package/dist/components/{p-C5KuV1pK.js → p-BA0ayKqO.js} +1 -1
  112. package/dist/components/{p-NbNVTRk6.js → p-BEJQ2kP7.js} +1 -1
  113. package/dist/components/p-BSipRoFx.js +1 -0
  114. package/dist/components/{p-CDadAOMw.js → p-BeFUNGEI.js} +1 -1
  115. package/dist/components/{p-35nrk8s0.js → p-BiByyU2C.js} +1 -1
  116. package/dist/components/{p-CCAWSyDD.js → p-BiouZo1q.js} +1 -1
  117. package/dist/components/{p-CSExtYKI.js → p-ByR0VXeU.js} +1 -1
  118. package/dist/components/{p-1MGcXTLv.js → p-C1uR_ZNW.js} +1 -1
  119. package/dist/components/{p-x8PzaMuD.js → p-C69Stayh.js} +1 -1
  120. package/dist/components/{p-Ch0UlFwq.js → p-C7SBI_0T.js} +1 -1
  121. package/dist/components/{p-DEzfXrGX.js → p-CAIGuV2J.js} +1 -1
  122. package/dist/components/p-CJ2eHeoV.js +1 -0
  123. package/dist/components/p-CW-VyJgK.js +1 -0
  124. package/dist/components/{p-DW4ADV9w.js → p-CZhyKp-f.js} +1 -1
  125. package/dist/components/p-CsR4owzk.js +1 -0
  126. package/dist/components/{p-BG1IxseV.js → p-CsoDfhD5.js} +1 -1
  127. package/dist/components/{p-DpFu5yAt.js → p-D1O7DxL4.js} +1 -1
  128. package/dist/components/{p-B5ouV8EQ.js → p-DRbG92F9.js} +1 -1
  129. package/dist/components/{p-C3eaM9TB.js → p-DS0xx1eT.js} +1 -1
  130. package/dist/components/{p-jx8VOz7S.js → p-DSzQ6H2j.js} +1 -1
  131. package/dist/components/{p-DsIlDGDO.js → p-DXjuuVq9.js} +1 -1
  132. package/dist/components/p-DXpYcAnT.js +1 -0
  133. package/dist/components/{p-DiFVw6IQ.js → p-Da46jw3N.js} +1 -1
  134. package/dist/components/{p-C6kZf91d.js → p-Dj_Qjga5.js} +1 -1
  135. package/dist/components/{p-Do0Q5-iC.js → p-DvIEvoZu.js} +1 -1
  136. package/dist/components/{p-CnVzLD5e.js → p-GYI7sDxr.js} +1 -1
  137. package/dist/components/{p-CcBM_ClD.js → p-HLbqRJGs.js} +1 -1
  138. package/dist/components/{p-VHyNcODZ.js → p-KQzWumjB.js} +1 -1
  139. package/dist/components/p-RJWe82kG.js +9 -0
  140. package/dist/components/{p-VAkeZOZL.js → p-TyR-YTXm.js} +1 -1
  141. package/dist/components/{p-CHtn5xr6.js → p-b4gyXoju.js} +1 -1
  142. package/dist/components/p-iRL0wQHQ.js +1 -0
  143. package/dist/components/{p-CqLaHE27.js → p-kj9wbLY8.js} +1 -1
  144. package/dist/components/{p-DaHq4iG1.js → p-xM-_OeRO.js} +1 -1
  145. package/dist/esm/index-MV-81ybv.js +2 -2
  146. package/dist/esm/index.js +59 -19
  147. package/dist/esm/{kritzel-active-users_41.entry.js → kritzel-active-users_42.entry.js} +586 -173
  148. package/dist/esm/kritzel-brush-style.entry.js +1 -1
  149. package/dist/esm/loader.js +1 -1
  150. package/dist/esm/stencil.js +1 -1
  151. package/dist/esm/{workspace.migrations-BGixvB76.js → workspace.migrations-B99F1MdT.js} +58 -5
  152. package/dist/stencil/index.esm.js +1 -1
  153. package/dist/stencil/p-2a60e1bc.entry.js +9 -0
  154. package/dist/stencil/p-B99F1MdT.js +1 -0
  155. package/dist/stencil/{p-016ad76a.entry.js → p-fc21e29c.entry.js} +1 -1
  156. package/dist/stencil/stencil.esm.js +1 -1
  157. package/dist/types/classes/core/store.class.d.ts +10 -2
  158. package/dist/types/classes/objects/base-object.class.d.ts +1 -0
  159. package/dist/types/classes/objects/selection-group.class.d.ts +5 -0
  160. package/dist/types/classes/providers/hocuspocus-sync-provider.class.d.ts +3 -0
  161. package/dist/types/classes/structures/object-map.structure.d.ts +41 -0
  162. package/dist/types/components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.d.ts +26 -0
  163. package/dist/types/components.d.ts +39 -4
  164. package/dist/types/constants/schema.constants.d.ts +1 -1
  165. package/dist/types/constants/version.d.ts +1 -1
  166. package/dist/types/interfaces/object.interface.d.ts +1 -0
  167. package/dist/types/interfaces/remote-cursor.interface.d.ts +17 -0
  168. package/dist/types/interfaces/theme.interface.d.ts +7 -0
  169. package/package.json +1 -1
  170. package/dist/components/p-B2vjbWy-.js +0 -9
  171. package/dist/components/p-BvToKcu1.js +0 -1
  172. package/dist/components/p-CNro30tB.js +0 -1
  173. package/dist/components/p-Duv3EM3w.js +0 -1
  174. package/dist/components/p-KFsLHwYm.js +0 -1
  175. package/dist/components/p-hCORwbZh.js +0 -1
  176. package/dist/stencil/p-BGixvB76.js +0 -1
  177. package/dist/stencil/p-a0f5c4ad.entry.js +0 -9
@@ -28,6 +28,7 @@ export class KritzelBaseObject {
28
28
  rotation = 0;
29
29
  markedForRemoval = false;
30
30
  zIndex = 0;
31
+ userId;
31
32
  /** Whether the object is currently visible on the canvas */
32
33
  isVisible = true;
33
34
  isSelected = false;
@@ -208,6 +209,7 @@ export class KritzelBaseObject {
208
209
  object._core = core;
209
210
  object.zIndex = core.store.currentZIndex;
210
211
  object.workspaceId = core.store.state.activeWorkspace.id;
212
+ object.userId = core.user?.id;
211
213
  return object;
212
214
  }
213
215
  /**
@@ -49,6 +49,7 @@ export class KritzelCustomElement extends KritzelBaseObject {
49
49
  object._core = core;
50
50
  object.id = object.generateId();
51
51
  object.workspaceId = core.store.state.activeWorkspace.id;
52
+ object.userId = core.user?.id;
52
53
  return object;
53
54
  }
54
55
  /**
@@ -70,6 +70,7 @@ export class KritzelGroup extends KritzelBaseObject {
70
70
  group._core = core;
71
71
  group.id = group.generateId();
72
72
  group.workspaceId = core.store.state.activeWorkspace.id;
73
+ group.userId = core.user?.id;
73
74
  group.scale = core.store.state.scale;
74
75
  group.zIndex = core.store.currentZIndex;
75
76
  return group;
@@ -40,6 +40,7 @@ export class KritzelImage extends KritzelBaseObject {
40
40
  object._core = core;
41
41
  object.id = object.generateId();
42
42
  object.workspaceId = core.store.state.activeWorkspace.id;
43
+ object.userId = core.user?.id;
43
44
  object.x = 0;
44
45
  object.y = 0;
45
46
  object.translateX = 0;
@@ -80,6 +80,7 @@ export class KritzelLine extends KritzelBaseObject {
80
80
  object._core = core;
81
81
  object.id = object.generateId();
82
82
  object.workspaceId = core.store.state.activeWorkspace.id;
83
+ object.userId = core.user?.id;
83
84
  object.options = options;
84
85
  object.startX = options?.startX ?? 0;
85
86
  object.startY = options?.startY ?? 0;
@@ -62,6 +62,7 @@ export class KritzelPath extends KritzelBaseObject {
62
62
  object._core = core;
63
63
  object.id = object.generateId();
64
64
  object.workspaceId = core.store.state.activeWorkspace.id;
65
+ object.userId = core.user?.id;
65
66
  object.options = options;
66
67
  object.points = options?.points ?? [];
67
68
  object.translateX = options?.translateX ?? 0;
@@ -19,6 +19,7 @@ export class KritzelSelectionBox extends KritzelBaseObject {
19
19
  object._core = core;
20
20
  object.id = object.generateId();
21
21
  object.workspaceId = core.store.state.activeWorkspace.id;
22
+ object.userId = core.user?.id;
22
23
  object.scale = core.store.state.scale;
23
24
  object.zIndex = 99999;
24
25
  object.backgroundColor = { light: 'rgba(0, 122, 255, 0.2)', dark: 'rgba(0, 122, 255, 0.2)' };
@@ -24,6 +24,10 @@ export class KritzelSelectionGroup extends KritzelBaseObject {
24
24
  maxX;
25
25
  minY;
26
26
  maxY;
27
+ // Selection styling properties
28
+ handleColor;
29
+ handleStrokeColor;
30
+ handleSize = 6;
27
31
  /**
28
32
  * Gets the array of object IDs contained in this selection group.
29
33
  * @returns Array of string IDs for the selected objects
@@ -78,6 +82,7 @@ export class KritzelSelectionGroup extends KritzelBaseObject {
78
82
  /**
79
83
  * Factory method to create a new KritzelSelectionGroup instance.
80
84
  * Initializes the selection group with default properties and associates it with the core instance.
85
+ * Default styling uses values from the theme system (selection section).
81
86
  * @param core - The KritzelCore instance to associate with this selection group
82
87
  * @returns A new KritzelSelectionGroup instance configured with default settings
83
88
  */
@@ -86,8 +91,15 @@ export class KritzelSelectionGroup extends KritzelBaseObject {
86
91
  object._core = core;
87
92
  object.id = object.generateId();
88
93
  object.workspaceId = core.store.state.activeWorkspace.id;
94
+ object.userId = core.user?.id;
89
95
  object.scale = core.store.state.scale;
90
96
  object.zIndex = 99999;
97
+ // Initialize styling with theme-aware defaults
98
+ object.borderColor = { light: '#007AFF', dark: '#0A84FF' };
99
+ object.borderWidth = 2;
100
+ object.handleColor = { light: '#ffffff', dark: '#1a1a1a' };
101
+ object.handleStrokeColor = { light: '#007AFF', dark: '#0A84FF' };
102
+ object.handleSize = 6;
91
103
  return object;
92
104
  }
93
105
  /**
@@ -253,7 +265,7 @@ export class KritzelSelectionGroup extends KritzelBaseObject {
253
265
  * @returns A serializable object representation of the selection group
254
266
  */
255
267
  serialize() {
256
- const { _core, _elementRef, element, totalWidth, totalHeight, unchangedObjectSnapshots, _cachedObjects, _cachedObjectIdsHash, snapshotWidth, snapshotHeight, snapshotTranslateX, snapshotTranslateY, ...remainingProps } = this;
268
+ const { _core, _elementRef, element, totalWidth, totalHeight, unchangedObjectSnapshots, _cachedObjects, _cachedObjectIdsHash, snapshotWidth, snapshotHeight, snapshotTranslateX, snapshotTranslateY, snapshotRotation, ...remainingProps } = this;
257
269
  const clonedProps = structuredClone(remainingProps);
258
270
  // Ensure objectIds is serialized with the correct key name (getter returns _objectIds)
259
271
  clonedProps.objectIds = this.objectIds;
@@ -100,6 +100,7 @@ export class KritzelShape extends KritzelBaseObject {
100
100
  object._core = core;
101
101
  object.id = object.generateId();
102
102
  object.workspaceId = core.store.state.activeWorkspace.id;
103
+ object.userId = core.user?.id;
103
104
  object.x = config?.x ?? 0;
104
105
  object.y = config?.y ?? 0;
105
106
  object.translateX = config?.translateX ?? 0;
@@ -116,6 +116,7 @@ export class KritzelText extends KritzelBaseObject {
116
116
  object._core = core;
117
117
  object.id = object.generateId();
118
118
  object.workspaceId = core.store.state.activeWorkspace.id;
119
+ object.userId = core.user?.id;
119
120
  object.fontSize = fontSize;
120
121
  object.fontFamily = fontFamily;
121
122
  object.translateX = 0;
@@ -8,6 +8,9 @@ export class HocuspocusSyncProvider {
8
8
  isConnected = false;
9
9
  isSynced = false;
10
10
  usesSharedSocket = false;
11
+ isDestroyed = false;
12
+ connectTimeout = null;
13
+ pendingConnectReject = null;
11
14
  get awareness() {
12
15
  return this.provider.awareness;
13
16
  }
@@ -26,8 +29,13 @@ export class HocuspocusSyncProvider {
26
29
  name,
27
30
  document: doc,
28
31
  token: options?.token || null,
32
+ onStatus: (data) => {
33
+ if (options?.onStatus) {
34
+ options.onStatus(data);
35
+ }
36
+ },
29
37
  onConnect: () => {
30
- if (this.isConnected) {
38
+ if (this.isConnected || this.isDestroyed) {
31
39
  return;
32
40
  }
33
41
  this.isConnected = true;
@@ -39,7 +47,7 @@ export class HocuspocusSyncProvider {
39
47
  }
40
48
  },
41
49
  onDisconnect: () => {
42
- if (!this.isConnected && !this.isSynced) {
50
+ if (this.isDestroyed || (!this.isConnected && !this.isSynced)) {
43
51
  return;
44
52
  }
45
53
  this.isConnected = false;
@@ -52,7 +60,7 @@ export class HocuspocusSyncProvider {
52
60
  }
53
61
  },
54
62
  onSynced: () => {
55
- if (this.isSynced) {
63
+ if (this.isSynced || this.isDestroyed) {
56
64
  return;
57
65
  }
58
66
  this.isSynced = true;
@@ -71,9 +79,6 @@ export class HocuspocusSyncProvider {
71
79
  if (options?.onAuthenticationFailed) {
72
80
  config.onAuthenticationFailed = options.onAuthenticationFailed;
73
81
  }
74
- if (options?.onStatus) {
75
- config.onStatus = options.onStatus;
76
- }
77
82
  this.provider = new HocuspocusProvider(config);
78
83
  // Must call attach() explicitly when using shared socket
79
84
  this.provider.attach();
@@ -89,8 +94,14 @@ export class HocuspocusSyncProvider {
89
94
  name,
90
95
  document: doc,
91
96
  token: options?.token || null,
97
+ autoConnect: false,
98
+ onStatus: (data) => {
99
+ if (options?.onStatus) {
100
+ options.onStatus(data);
101
+ }
102
+ },
92
103
  onConnect: () => {
93
- if (this.isConnected) {
104
+ if (this.isConnected || this.isDestroyed) {
94
105
  return;
95
106
  }
96
107
  this.isConnected = true;
@@ -102,7 +113,7 @@ export class HocuspocusSyncProvider {
102
113
  }
103
114
  },
104
115
  onDisconnect: () => {
105
- if (!this.isConnected && !this.isSynced) {
116
+ if (this.isDestroyed || (!this.isConnected && !this.isSynced)) {
106
117
  return;
107
118
  }
108
119
  this.isConnected = false;
@@ -115,7 +126,7 @@ export class HocuspocusSyncProvider {
115
126
  }
116
127
  },
117
128
  onSynced: () => {
118
- if (this.isSynced) {
129
+ if (this.isSynced || this.isDestroyed) {
119
130
  return;
120
131
  }
121
132
  this.isSynced = true;
@@ -134,9 +145,6 @@ export class HocuspocusSyncProvider {
134
145
  if (options?.onAuthenticationFailed) {
135
146
  config.onAuthenticationFailed = options.onAuthenticationFailed;
136
147
  }
137
- if (options?.onStatus) {
138
- config.onStatus = options.onStatus;
139
- }
140
148
  if (options?.WebSocketPolyfill) {
141
149
  config.WebSocketPolyfill = options.WebSocketPolyfill;
142
150
  }
@@ -204,22 +212,36 @@ export class HocuspocusSyncProvider {
204
212
  };
205
213
  }
206
214
  async connect() {
207
- if (this.isSynced) {
215
+ if (this.isSynced || this.isDestroyed) {
208
216
  return;
209
217
  }
210
218
  return new Promise((resolve, reject) => {
211
- const timeout = setTimeout(() => {
219
+ // Store reject function so we can cancel the connection if destroyed
220
+ this.pendingConnectReject = reject;
221
+ this.connectTimeout = setTimeout(() => {
222
+ this.pendingConnectReject = null;
223
+ this.connectTimeout = null;
212
224
  reject(new Error('Hocuspocus connection timeout'));
213
225
  }, 10000); // 10 second timeout
214
226
  const syncHandler = () => {
215
- clearTimeout(timeout);
227
+ if (this.connectTimeout) {
228
+ clearTimeout(this.connectTimeout);
229
+ this.connectTimeout = null;
230
+ }
231
+ this.pendingConnectReject = null;
216
232
  this.provider.off('synced', syncHandler);
217
- resolve();
233
+ if (!this.isDestroyed) {
234
+ resolve();
235
+ }
218
236
  };
219
237
  this.provider.on('synced', syncHandler);
220
238
  // If already synced, resolve immediately
221
239
  if (this.provider.isSynced) {
222
- clearTimeout(timeout);
240
+ if (this.connectTimeout) {
241
+ clearTimeout(this.connectTimeout);
242
+ this.connectTimeout = null;
243
+ }
244
+ this.pendingConnectReject = null;
223
245
  this.provider.off('synced', syncHandler);
224
246
  resolve();
225
247
  return;
@@ -231,6 +253,14 @@ export class HocuspocusSyncProvider {
231
253
  });
232
254
  }
233
255
  disconnect() {
256
+ // Cancel any pending connection attempt
257
+ if (this.connectTimeout) {
258
+ clearTimeout(this.connectTimeout);
259
+ this.connectTimeout = null;
260
+ }
261
+ if (this.pendingConnectReject) {
262
+ this.pendingConnectReject = null; // Don't reject, just abandon the promise
263
+ }
234
264
  if (this.provider) {
235
265
  if (this.usesSharedSocket) {
236
266
  // Detach from shared socket instead of disconnecting
@@ -244,6 +274,16 @@ export class HocuspocusSyncProvider {
244
274
  this.isSynced = false;
245
275
  }
246
276
  destroy() {
277
+ // Mark as destroyed first to prevent any callbacks from doing work
278
+ this.isDestroyed = true;
279
+ // Cancel any pending connection attempt
280
+ if (this.connectTimeout) {
281
+ clearTimeout(this.connectTimeout);
282
+ this.connectTimeout = null;
283
+ }
284
+ if (this.pendingConnectReject) {
285
+ this.pendingConnectReject = null; // Don't reject, just abandon the promise
286
+ }
247
287
  if (this.provider) {
248
288
  this.provider.destroy();
249
289
  }
@@ -34,6 +34,10 @@ export class KritzelObjectMap {
34
34
  _stackItemPoppedHandler = null;
35
35
  _awarenessChangeHandler = null;
36
36
  _awarenessChangeCallbacks = [];
37
+ _objectsChangeCallbacks = [];
38
+ _lastAwarenessEmitTime = 0;
39
+ _awarenessEmitTimeout = null;
40
+ AWARENESS_THROTTLE_INTERVAL = 100; // milliseconds
37
41
  /**
38
42
  * Indicates whether the object map has been initialized and is ready for use.
39
43
  * @returns `true` if providers are connected and the map is operational
@@ -47,6 +51,19 @@ export class KritzelObjectMap {
47
51
  get awareness() {
48
52
  return this._awareness;
49
53
  }
54
+ /**
55
+ * Whether a network awareness instance is available.
56
+ */
57
+ get hasAwareness() {
58
+ return !!this._awareness;
59
+ }
60
+ /**
61
+ * Returns the local client ID from the awareness instance.
62
+ * Used to filter out the local user when rendering remote cursors.
63
+ */
64
+ get localClientId() {
65
+ return this._awareness?.clientID ?? null;
66
+ }
50
67
  /**
51
68
  * Sets the local user identity in the awareness state.
52
69
  * This broadcasts the user's identity to all connected peers.
@@ -55,12 +72,21 @@ export class KritzelObjectMap {
55
72
  if (!this._awareness || !user) {
56
73
  return;
57
74
  }
75
+ const displayName = user.displayName || user.firstName || 'Anonymous';
58
76
  this._awareness.setLocalStateField('user', {
59
77
  id: user.id,
60
- name: user.displayName || user.firstName || 'Anonymous',
61
- color: user.color,
78
+ displayName,
79
+ color: user.color || this.generateColorFromName(displayName),
62
80
  });
63
81
  }
82
+ generateColorFromName(name) {
83
+ let hash = 0;
84
+ for (let i = 0; i < name.length; i++) {
85
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
86
+ }
87
+ const hue = Math.abs(hash % 360);
88
+ return `hsl(${hue}, 45%, 55%)`;
89
+ }
64
90
  /**
65
91
  * Updates the local cursor position in the awareness state.
66
92
  * This broadcasts the cursor position to all connected peers.
@@ -80,6 +106,37 @@ export class KritzelObjectMap {
80
106
  }
81
107
  this._awareness.setLocalStateField('cursor', null);
82
108
  }
109
+ /**
110
+ * Sets the ID of the object currently being drawn by the local user.
111
+ * Remote clients use this to derive cursor position from the object's latest
112
+ * coordinates instead of the throttled awareness cursor, reducing desync.
113
+ * Pass `null` when drawing ends.
114
+ */
115
+ setActiveDrawingObject(objectId) {
116
+ if (!this._awareness) {
117
+ return;
118
+ }
119
+ this._awareness.setLocalStateField('activeObjectId', objectId);
120
+ }
121
+ /**
122
+ * Broadcasts the local user's selection box bounds via awareness.
123
+ * Remote clients use this to render the selection rectangle in the user's color.
124
+ */
125
+ setLocalSelectionBox(box) {
126
+ if (!this._awareness) {
127
+ return;
128
+ }
129
+ this._awareness.setLocalStateField('selectionBox', box);
130
+ }
131
+ /**
132
+ * Clears the local selection box from awareness (e.g., when selection completes or is cancelled).
133
+ */
134
+ clearLocalSelectionBox() {
135
+ if (!this._awareness) {
136
+ return;
137
+ }
138
+ this._awareness.setLocalStateField('selectionBox', null);
139
+ }
83
140
  /**
84
141
  * Registers a callback to be invoked when the awareness state changes.
85
142
  * The callback receives the full awareness states map.
@@ -87,6 +144,14 @@ export class KritzelObjectMap {
87
144
  onAwarenessChange(callback) {
88
145
  this._awarenessChangeCallbacks.push(callback);
89
146
  }
147
+ /**
148
+ * Registers a callback to be invoked when remote object changes are received.
149
+ * Used by the awareness cursors component to re-derive cursor positions
150
+ * from the latest object data when a remote user is actively drawing.
151
+ */
152
+ onObjectsChange(callback) {
153
+ this._objectsChangeCallbacks.push(callback);
154
+ }
90
155
  /**
91
156
  * Returns the Yjs UndoManager instance for managing undo/redo operations.
92
157
  * @returns The UndoManager instance, or `null` if not initialized
@@ -223,9 +288,32 @@ export class KritzelObjectMap {
223
288
  // Subscribe to awareness changes
224
289
  if (this._awareness) {
225
290
  this._awarenessChangeHandler = () => {
226
- const states = this._awareness.getStates();
227
- for (const callback of this._awarenessChangeCallbacks) {
228
- callback(states);
291
+ const now = Date.now();
292
+ const timeSinceLastEmit = now - this._lastAwarenessEmitTime;
293
+ // Clear any pending timeout since we have a new event
294
+ if (this._awarenessEmitTimeout !== null) {
295
+ clearTimeout(this._awarenessEmitTimeout);
296
+ this._awarenessEmitTimeout = null;
297
+ }
298
+ if (timeSinceLastEmit >= this.AWARENESS_THROTTLE_INTERVAL) {
299
+ // Enough time has passed, emit immediately
300
+ this._lastAwarenessEmitTime = now;
301
+ const states = this._awareness.getStates();
302
+ for (const callback of this._awarenessChangeCallbacks) {
303
+ callback(states);
304
+ }
305
+ }
306
+ else {
307
+ // Schedule emission for the remaining time
308
+ const delayMs = this.AWARENESS_THROTTLE_INTERVAL - timeSinceLastEmit;
309
+ this._awarenessEmitTimeout = setTimeout(() => {
310
+ this._lastAwarenessEmitTime = Date.now();
311
+ this._awarenessEmitTimeout = null;
312
+ const states = this._awareness.getStates();
313
+ for (const callback of this._awarenessChangeCallbacks) {
314
+ callback(states);
315
+ }
316
+ }, delayMs);
229
317
  }
230
318
  };
231
319
  this._awareness.on('change', this._awarenessChangeHandler);
@@ -362,6 +450,10 @@ export class KritzelObjectMap {
362
450
  if (updatedObjects.length > 0) {
363
451
  this._core?.engine.emitObjectsUpdated(updatedObjects.map(obj => ({ object: obj, changedProperties: [] })));
364
452
  }
453
+ // Notify subscribers of remote object changes (e.g., awareness cursors)
454
+ for (const callback of this._objectsChangeCallbacks) {
455
+ callback();
456
+ }
365
457
  }
366
458
  /**
367
459
  * Initializes document metadata if not already set.
@@ -752,11 +844,14 @@ export class KritzelObjectMap {
752
844
  this._awareness.off('change', this._awarenessChangeHandler);
753
845
  this._awarenessChangeHandler = null;
754
846
  }
847
+ if (this._awarenessEmitTimeout !== null) {
848
+ clearTimeout(this._awarenessEmitTimeout);
849
+ this._awarenessEmitTimeout = null;
850
+ }
755
851
  this._awareness = null;
756
852
  this._awarenessChangeCallbacks = [];
757
- // Disconnect and destroy providers
853
+ // Destroy providers (destroy handles disconnection internally)
758
854
  this._providers.forEach(p => {
759
- p.disconnect();
760
855
  p.destroy();
761
856
  });
762
857
  this._providers = [];
@@ -55,6 +55,7 @@ export class KritzelBrushTool extends KritzelBaseTool {
55
55
  path.isCompleted = false;
56
56
  this._currentPathId = path.id;
57
57
  this._core.store.state.objects.insert(path);
58
+ this._core.store.state.objects?.setActiveDrawingObject(path.id);
58
59
  }
59
60
  }
60
61
  if (event.pointerType === 'touch' || event.pointerType === 'pen') {
@@ -78,6 +79,7 @@ export class KritzelBrushTool extends KritzelBaseTool {
78
79
  path.isCompleted = false;
79
80
  this._currentPathId = path.id;
80
81
  this._core.store.state.objects.insert(path);
82
+ this._core.store.state.objects?.setActiveDrawingObject(path.id);
81
83
  }
82
84
  }
83
85
  }
@@ -168,6 +170,7 @@ export class KritzelBrushTool extends KritzelBaseTool {
168
170
  this._core.engine.emitObjectsChange();
169
171
  this._core.engine.emitObjectsAdded([currentPath]);
170
172
  }
173
+ this._core.store.state.objects?.setActiveDrawingObject(null);
171
174
  this._currentPathId = null;
172
175
  }
173
176
  }
@@ -183,6 +186,7 @@ export class KritzelBrushTool extends KritzelBaseTool {
183
186
  this._core.engine.emitObjectsChange();
184
187
  this._core.engine.emitObjectsAdded([currentPath]);
185
188
  }
189
+ this._core.store.state.objects?.setActiveDrawingObject(null);
186
190
  this._currentPathId = null;
187
191
  }
188
192
  }
@@ -70,6 +70,7 @@ export class KritzelLineTool extends KritzelBaseTool {
70
70
  line.isCompleted = false;
71
71
  this._currentLineId = line.id;
72
72
  this._core.store.state.objects.insert(line);
73
+ this._core.store.state.objects?.setActiveDrawingObject(line.id);
73
74
  }
74
75
  }
75
76
  if (event.pointerType === 'touch' || event.pointerType === 'pen') {
@@ -99,6 +100,7 @@ export class KritzelLineTool extends KritzelBaseTool {
99
100
  line.isCompleted = false;
100
101
  this._currentLineId = line.id;
101
102
  this._core.store.state.objects.insert(line);
103
+ this._core.store.state.objects?.setActiveDrawingObject(line.id);
102
104
  }
103
105
  }
104
106
  }
@@ -197,6 +199,7 @@ export class KritzelLineTool extends KritzelBaseTool {
197
199
  // Switch to selection tool and select the drawn line
198
200
  this.selectLineAndSwitchTool(currentLine);
199
201
  }
202
+ this._core.store.state.objects?.setActiveDrawingObject(null);
200
203
  this._currentLineId = null;
201
204
  }
202
205
  }
@@ -214,6 +217,7 @@ export class KritzelLineTool extends KritzelBaseTool {
214
217
  // Switch to selection tool and select the drawn line
215
218
  this.selectLineAndSwitchTool(currentLine);
216
219
  }
220
+ this._core.store.state.objects?.setActiveDrawingObject(null);
217
221
  this._currentLineId = null;
218
222
  }
219
223
  }
@@ -179,6 +179,7 @@ export class KritzelShapeTool extends KritzelBaseTool {
179
179
  scale: lockScale ? 1 : viewportScale,
180
180
  });
181
181
  this._core.store.state.objects.insert(this.currentShape);
182
+ this._core.store.state.objects?.setActiveDrawingObject(this.currentShape.id);
182
183
  this._core.rerender();
183
184
  }
184
185
  /**
@@ -239,6 +240,7 @@ export class KritzelShapeTool extends KritzelBaseTool {
239
240
  this._core.store.setState('activeTool', KritzelToolRegistry.getTool('selection'));
240
241
  }
241
242
  this.isDrawing = false;
243
+ this._core.store.state.objects?.setActiveDrawingObject(null);
242
244
  this.currentShape = null;
243
245
  this._core.rerender();
244
246
  }
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "entries": [
3
- "components/shared/kritzel-dropdown/kritzel-dropdown.js",
4
- "components/shared/kritzel-brush-style/kritzel-brush-style.js",
5
3
  "components/core/kritzel-engine/kritzel-engine.js",
6
4
  "components/ui/kritzel-context-menu/kritzel-context-menu.js",
5
+ "components/shared/kritzel-dropdown/kritzel-dropdown.js",
6
+ "components/shared/kritzel-brush-style/kritzel-brush-style.js",
7
7
  "components/shared/kritzel-font-family/kritzel-font-family.js",
8
8
  "components/shared/kritzel-line-endings/kritzel-line-endings.js",
9
9
  "components/shared/kritzel-pill-tabs/kritzel-pill-tabs.js",
10
10
  "components/shared/kritzel-shape-fill/kritzel-shape-fill.js",
11
+ "components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.js",
11
12
  "components/core/kritzel-cursor-trail/kritzel-cursor-trail.js",
12
13
  "components/core/kritzel-editor/kritzel-editor.js",
13
14
  "components/shared/kritzel-avatar/kritzel-avatar.js",
@@ -0,0 +1,110 @@
1
+ :host {
2
+ display: block;
3
+ position: fixed;
4
+ top: 0;
5
+ left: 0;
6
+ width: 100vw;
7
+ height: 100vh;
8
+ pointer-events: none;
9
+ z-index: 9500;
10
+ }
11
+
12
+ .awareness-cursor {
13
+ position: absolute;
14
+ top: 0;
15
+ left: 0;
16
+ transition: transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out,
17
+ opacity 300ms ease;
18
+ will-change: transform;
19
+ }
20
+
21
+ .awareness-cursor.stale {
22
+ opacity: 0.3;
23
+ }
24
+
25
+ .awareness-cursor.tracking-object {
26
+ transition-duration: 0ms;
27
+ }
28
+
29
+ .cursor-arrow {
30
+ filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3));
31
+ }
32
+
33
+ .cursor-label {
34
+ position: absolute;
35
+ left: 16px;
36
+ top: 16px;
37
+ white-space: nowrap;
38
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
39
+ font-size: var(--kritzel-awareness-cursor-label-font-size, 12px);
40
+ color: var(--kritzel-awareness-cursor-label-text-color, #ffffff);
41
+ padding: 2px 8px;
42
+ border-radius: 4px;
43
+ line-height: 1.4;
44
+ font-weight: 500;
45
+ pointer-events: none;
46
+ user-select: none;
47
+ }
48
+
49
+ .edge-indicator {
50
+ position: absolute;
51
+ top: -12px;
52
+ left: -12px;
53
+ width: 24px;
54
+ height: 24px;
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ transition: transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out,
59
+ opacity 300ms ease;
60
+ will-change: transform;
61
+ pointer-events: auto;
62
+ user-select: none;
63
+ cursor: pointer;
64
+ }
65
+
66
+ .edge-indicator.stale {
67
+ opacity: 0.3;
68
+ }
69
+
70
+ .edge-indicator.tracking-object {
71
+ transition-duration: 0ms;
72
+ }
73
+
74
+ .edge-arrow {
75
+ position: absolute;
76
+ filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.3));
77
+ }
78
+
79
+ .edge-label {
80
+ position: absolute;
81
+ white-space: nowrap;
82
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
83
+ font-size: var(--kritzel-awareness-cursor-label-font-size, 12px);
84
+ color: var(--kritzel-awareness-cursor-label-text-color, #ffffff);
85
+ padding: 2px 8px;
86
+ border-radius: 4px;
87
+ line-height: 1.4;
88
+ font-weight: 500;
89
+ pointer-events: none;
90
+ opacity: 0;
91
+ transform-origin: center;
92
+ transition: opacity 150ms ease;
93
+ }
94
+
95
+ .edge-indicator:hover .edge-label {
96
+ opacity: 1;
97
+ }
98
+
99
+ .remote-selection-box {
100
+ position: absolute;
101
+ top: 0;
102
+ left: 0;
103
+ border-width: 2px;
104
+ border-style: solid;
105
+ pointer-events: none;
106
+ will-change: transform, width, height;
107
+ transition: transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out,
108
+ width var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out,
109
+ height var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out;
110
+ }