@kritzel/engine 0.4.31

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 (373) hide show
  1. package/LICENSE.md +50 -0
  2. package/dist/cjs/app-globals-V2Kpy_OQ.js +5 -0
  3. package/dist/cjs/engine.constants-CakmdKMW.js +28206 -0
  4. package/dist/cjs/index-BbbWU2aN.js +1742 -0
  5. package/dist/cjs/index.cjs.js +4232 -0
  6. package/dist/cjs/kritzel-awareness-cursors_5.cjs.entry.js +28004 -0
  7. package/dist/cjs/loader.cjs.js +13 -0
  8. package/dist/cjs/stencil.cjs.js +25 -0
  9. package/dist/collection/classes/core/core.class.js +1389 -0
  10. package/dist/collection/classes/core/reviver.class.js +124 -0
  11. package/dist/collection/classes/core/store.class.js +337 -0
  12. package/dist/collection/classes/core/viewport.class.js +794 -0
  13. package/dist/collection/classes/core/workspace.class.js +112 -0
  14. package/dist/collection/classes/handlers/base.handler.js +20 -0
  15. package/dist/collection/classes/handlers/context-menu.handler.js +170 -0
  16. package/dist/collection/classes/handlers/hover.handler.js +35 -0
  17. package/dist/collection/classes/handlers/key.handler.js +162 -0
  18. package/dist/collection/classes/handlers/line-handle.handler.js +475 -0
  19. package/dist/collection/classes/handlers/move.handler.js +295 -0
  20. package/dist/collection/classes/handlers/resize.handler.js +327 -0
  21. package/dist/collection/classes/handlers/rotation.handler.js +174 -0
  22. package/dist/collection/classes/handlers/selection.handler.js +526 -0
  23. package/dist/collection/classes/managers/anchor.manager.js +1476 -0
  24. package/dist/collection/classes/managers/browser-permission.manager.js +64 -0
  25. package/dist/collection/classes/managers/clipboard.manager.js +346 -0
  26. package/dist/collection/classes/managers/clipboard.types.js +1 -0
  27. package/dist/collection/classes/managers/cursor.manager.js +139 -0
  28. package/dist/collection/classes/managers/license.manager.js +341 -0
  29. package/dist/collection/classes/managers/localization.manager.js +227 -0
  30. package/dist/collection/classes/managers/theme.manager.js +206 -0
  31. package/dist/collection/classes/objects/base-object.class.js +462 -0
  32. package/dist/collection/classes/objects/dynamic-object.class.js +437 -0
  33. package/dist/collection/classes/objects/group.class.js +590 -0
  34. package/dist/collection/classes/objects/image.class.js +500 -0
  35. package/dist/collection/classes/objects/line.class.js +789 -0
  36. package/dist/collection/classes/objects/path.class.js +578 -0
  37. package/dist/collection/classes/objects/selection-box.class.js +32 -0
  38. package/dist/collection/classes/objects/selection-group.class.js +752 -0
  39. package/dist/collection/classes/objects/shape.class.js +628 -0
  40. package/dist/collection/classes/objects/text.class.js +461 -0
  41. package/dist/collection/classes/providers/assets/asset-resolver.class.js +536 -0
  42. package/dist/collection/classes/providers/assets/http-asset-provider.class.js +111 -0
  43. package/dist/collection/classes/providers/assets/indexeddb-asset-provider.class.js +241 -0
  44. package/dist/collection/classes/providers/assets/presigned-asset-provider.class.js +86 -0
  45. package/dist/collection/classes/providers/paste-intent/event-paste-intent.provider.js +48 -0
  46. package/dist/collection/classes/providers/paste-intent/ios-paste-intent.provider.js +16 -0
  47. package/dist/collection/classes/providers/paste-intent/navigator-paste-intent.provider.js +86 -0
  48. package/dist/collection/classes/providers/sync/broadcast-sync-provider.class.js +100 -0
  49. package/dist/collection/classes/providers/sync/hocuspocus-sync-provider.class.js +333 -0
  50. package/dist/collection/classes/providers/sync/in-memory-sync-provider.class.js +69 -0
  51. package/dist/collection/classes/providers/sync/indexeddb-sync-provider.class.js +39 -0
  52. package/dist/collection/classes/providers/sync/websocket-sync-provider.class.js +110 -0
  53. package/dist/collection/classes/registries/dynamic-object-renderer.registry.js +52 -0
  54. package/dist/collection/classes/registries/font-registry.class.js +161 -0
  55. package/dist/collection/classes/registries/icon-registry.class.js +118 -0
  56. package/dist/collection/classes/registries/paste-intent-provider.registry.js +26 -0
  57. package/dist/collection/classes/registries/tool.registry.js +69 -0
  58. package/dist/collection/classes/structures/app-state-map.structure.js +461 -0
  59. package/dist/collection/classes/structures/object-map.structure.js +1103 -0
  60. package/dist/collection/classes/structures/quadtree.structure.js +251 -0
  61. package/dist/collection/classes/tools/base-tool.class.js +88 -0
  62. package/dist/collection/classes/tools/brush-tool.class.js +236 -0
  63. package/dist/collection/classes/tools/eraser-tool.class.js +133 -0
  64. package/dist/collection/classes/tools/image-tool.class.js +152 -0
  65. package/dist/collection/classes/tools/line-tool.class.js +249 -0
  66. package/dist/collection/classes/tools/selection-tool.class.js +611 -0
  67. package/dist/collection/classes/tools/shape-tool.class.js +251 -0
  68. package/dist/collection/classes/tools/text-tool.class.js +179 -0
  69. package/dist/collection/collection-manifest.json +17 -0
  70. package/dist/collection/components/kritzel-awareness-cursors/kritzel-awareness-cursors.css +115 -0
  71. package/dist/collection/components/kritzel-awareness-cursors/kritzel-awareness-cursors.js +380 -0
  72. package/dist/collection/components/kritzel-cursor-trail/kritzel-cursor-trail.css +10 -0
  73. package/dist/collection/components/kritzel-cursor-trail/kritzel-cursor-trail.js +153 -0
  74. package/dist/collection/components/kritzel-engine/kritzel-engine.css +81 -0
  75. package/dist/collection/components/kritzel-engine/kritzel-engine.js +6345 -0
  76. package/dist/collection/components/kritzel-icon/kritzel-icon.css +18 -0
  77. package/dist/collection/components/kritzel-icon/kritzel-icon.js +93 -0
  78. package/dist/collection/components/kritzel-watermark/kritzel-watermark.css +29 -0
  79. package/dist/collection/components/kritzel-watermark/kritzel-watermark.js +83 -0
  80. package/dist/collection/configs/default-asset-storage.config.js +9 -0
  81. package/dist/collection/configs/default-brush-tool.config.js +9 -0
  82. package/dist/collection/configs/default-engine-config.js +73 -0
  83. package/dist/collection/configs/default-line-tool.config.js +12 -0
  84. package/dist/collection/configs/default-shape-tool.config.js +15 -0
  85. package/dist/collection/configs/default-sync.config.js +6 -0
  86. package/dist/collection/configs/default-text-tool.config.js +12 -0
  87. package/dist/collection/constants/color-palette.constants.js +40 -0
  88. package/dist/collection/constants/core.constants.js +1 -0
  89. package/dist/collection/constants/engine.constants.js +4 -0
  90. package/dist/collection/constants/license.constants.js +25 -0
  91. package/dist/collection/constants/schema.constants.js +4 -0
  92. package/dist/collection/constants/storage.constants.js +1 -0
  93. package/dist/collection/constants/stroke-size.constants.js +2 -0
  94. package/dist/collection/constants/text-tool-fonts.constants.js +18 -0
  95. package/dist/collection/constants/version.js +6 -0
  96. package/dist/collection/enums/alignment.enum.js +9 -0
  97. package/dist/collection/enums/event-button.enum.js +6 -0
  98. package/dist/collection/enums/handle-type.enum.js +7 -0
  99. package/dist/collection/enums/shape-type.enum.js +6 -0
  100. package/dist/collection/events/active-workspace-change.event.js +1 -0
  101. package/dist/collection/events/editor-is-ready.event.js +1 -0
  102. package/dist/collection/events/login.event.js +1 -0
  103. package/dist/collection/helpers/alignment.helper.js +73 -0
  104. package/dist/collection/helpers/class.helper.js +5 -0
  105. package/dist/collection/helpers/color.helper.js +106 -0
  106. package/dist/collection/helpers/cursor.helper.js +57 -0
  107. package/dist/collection/helpers/devices.helper.js +28 -0
  108. package/dist/collection/helpers/event.helper.js +58 -0
  109. package/dist/collection/helpers/geometry.helper.js +149 -0
  110. package/dist/collection/helpers/html.helper.js +116 -0
  111. package/dist/collection/helpers/image-file.helper.js +6 -0
  112. package/dist/collection/helpers/keyboard.helper.js +55 -0
  113. package/dist/collection/helpers/localization.helper.js +25 -0
  114. package/dist/collection/helpers/math.helper.js +8 -0
  115. package/dist/collection/helpers/migration.helper.js +45 -0
  116. package/dist/collection/helpers/object.helper.js +29 -0
  117. package/dist/collection/helpers/svg-export.helper.js +655 -0
  118. package/dist/collection/helpers/theme.helper.js +77 -0
  119. package/dist/collection/helpers/tool-config.helper.js +63 -0
  120. package/dist/collection/helpers/wheel.helper.js +113 -0
  121. package/dist/collection/index.js +107 -0
  122. package/dist/collection/interfaces/anchor.interface.js +1 -0
  123. package/dist/collection/interfaces/arrow-head.interface.js +1 -0
  124. package/dist/collection/interfaces/asset-provider.interface.js +1 -0
  125. package/dist/collection/interfaces/asset-storage-config.interface.js +1 -0
  126. package/dist/collection/interfaces/asset.interface.js +1 -0
  127. package/dist/collection/interfaces/bounding-box.interface.js +1 -0
  128. package/dist/collection/interfaces/clonable.interface.js +1 -0
  129. package/dist/collection/interfaces/context-menu-item.interface.js +1 -0
  130. package/dist/collection/interfaces/context-menu-state.interface.js +1 -0
  131. package/dist/collection/interfaces/debug-info.interface.js +1 -0
  132. package/dist/collection/interfaces/dialog.interface.js +1 -0
  133. package/dist/collection/interfaces/document-metadata.interface.js +1 -0
  134. package/dist/collection/interfaces/dynamic-object-renderer.interface.js +1 -0
  135. package/dist/collection/interfaces/engine-state.interface.js +1 -0
  136. package/dist/collection/interfaces/font.interface.js +1 -0
  137. package/dist/collection/interfaces/localization.interface.js +1 -0
  138. package/dist/collection/interfaces/login-config.interface.js +1 -0
  139. package/dist/collection/interfaces/master-detail.interface.js +1 -0
  140. package/dist/collection/interfaces/menu-item.interface.js +1 -0
  141. package/dist/collection/interfaces/migration.interface.js +1 -0
  142. package/dist/collection/interfaces/notification.interface.js +1 -0
  143. package/dist/collection/interfaces/object-change-event.interface.js +1 -0
  144. package/dist/collection/interfaces/object-export.interface.js +1 -0
  145. package/dist/collection/interfaces/object.interface.js +1 -0
  146. package/dist/collection/interfaces/paste-intent-provider.interface.js +1 -0
  147. package/dist/collection/interfaces/point.interface.js +1 -0
  148. package/dist/collection/interfaces/polygon.interface.js +1 -0
  149. package/dist/collection/interfaces/remote-cursor.interface.js +1 -0
  150. package/dist/collection/interfaces/selection-state.interface.js +1 -0
  151. package/dist/collection/interfaces/serializable.interface.js +1 -0
  152. package/dist/collection/interfaces/settings.interface.js +1 -0
  153. package/dist/collection/interfaces/share.interface.js +1 -0
  154. package/dist/collection/interfaces/shortcut.interface.js +1 -0
  155. package/dist/collection/interfaces/sync-config.interface.js +21 -0
  156. package/dist/collection/interfaces/sync-provider.interface.js +1 -0
  157. package/dist/collection/interfaces/theme.interface.js +1 -0
  158. package/dist/collection/interfaces/tool-config.interface.js +1 -0
  159. package/dist/collection/interfaces/tool-type.interface.js +1 -0
  160. package/dist/collection/interfaces/tool.interface.js +1 -0
  161. package/dist/collection/interfaces/toolbar-item.interface.js +1 -0
  162. package/dist/collection/interfaces/undo-state.interface.js +1 -0
  163. package/dist/collection/interfaces/user.interface.js +1 -0
  164. package/dist/collection/interfaces/viewport-state.interface.js +1 -0
  165. package/dist/collection/locales/de-locale.js +119 -0
  166. package/dist/collection/locales/en-locale.js +120 -0
  167. package/dist/collection/locales/fr-locale.js +119 -0
  168. package/dist/collection/migrations/app-state.migrations.js +47 -0
  169. package/dist/collection/migrations/workspace.migrations.js +67 -0
  170. package/dist/collection/themes/dark-theme.js +340 -0
  171. package/dist/collection/themes/light-theme.js +346 -0
  172. package/dist/collection/types/deep-readonly.type.js +1 -0
  173. package/dist/collection/types/state.types.js +1 -0
  174. package/dist/components/index.d.ts +47 -0
  175. package/dist/components/index.js +4143 -0
  176. package/dist/components/kritzel-awareness-cursors.d.ts +11 -0
  177. package/dist/components/kritzel-awareness-cursors.js +6 -0
  178. package/dist/components/kritzel-cursor-trail.d.ts +11 -0
  179. package/dist/components/kritzel-cursor-trail.js +6 -0
  180. package/dist/components/kritzel-engine.d.ts +11 -0
  181. package/dist/components/kritzel-engine.js +52641 -0
  182. package/dist/components/kritzel-icon.d.ts +11 -0
  183. package/dist/components/kritzel-icon.js +6 -0
  184. package/dist/components/kritzel-watermark.d.ts +11 -0
  185. package/dist/components/kritzel-watermark.js +6 -0
  186. package/dist/components/p-B2Htv9ou.js +49 -0
  187. package/dist/components/p-BvRGQvVI.js +3456 -0
  188. package/dist/components/p-CfSx6HU_.js +1469 -0
  189. package/dist/components/p-YP8wAcF4.js +122 -0
  190. package/dist/components/p-fx6msYHJ.js +167 -0
  191. package/dist/esm/app-globals-DQuL1Twl.js +3 -0
  192. package/dist/esm/engine.constants-Bl5-pbq-.js +28143 -0
  193. package/dist/esm/index-BU9Mv7y0.js +1715 -0
  194. package/dist/esm/index.js +4134 -0
  195. package/dist/esm/kritzel-awareness-cursors_5.entry.js +27978 -0
  196. package/dist/esm/loader.js +11 -0
  197. package/dist/esm/stencil.js +21 -0
  198. package/dist/index.cjs.js +1 -0
  199. package/dist/index.js +1 -0
  200. package/dist/stencil/index.esm.js +4134 -0
  201. package/dist/stencil/p-BU9Mv7y0.js +1702 -0
  202. package/dist/stencil/p-Bl5-pbq-.js +28143 -0
  203. package/dist/stencil/p-DQuL1Twl.js +3 -0
  204. package/dist/stencil/p-f0a8b7af.entry.js +27978 -0
  205. package/dist/stencil/stencil.esm.js +21 -0
  206. package/dist/types/classes/core/core.class.d.ts +532 -0
  207. package/dist/types/classes/core/reviver.class.d.ts +32 -0
  208. package/dist/types/classes/core/store.class.d.ts +198 -0
  209. package/dist/types/classes/core/viewport.class.d.ts +256 -0
  210. package/dist/types/classes/core/workspace.class.d.ts +95 -0
  211. package/dist/types/classes/handlers/base.handler.d.ts +18 -0
  212. package/dist/types/classes/handlers/context-menu.handler.d.ts +67 -0
  213. package/dist/types/classes/handlers/hover.handler.d.ts +23 -0
  214. package/dist/types/classes/handlers/key.handler.d.ts +44 -0
  215. package/dist/types/classes/handlers/line-handle.handler.d.ts +115 -0
  216. package/dist/types/classes/handlers/move.handler.d.ts +94 -0
  217. package/dist/types/classes/handlers/resize.handler.d.ts +95 -0
  218. package/dist/types/classes/handlers/rotation.handler.d.ts +50 -0
  219. package/dist/types/classes/handlers/selection.handler.d.ts +153 -0
  220. package/dist/types/classes/managers/anchor.manager.d.ts +513 -0
  221. package/dist/types/classes/managers/browser-permission.manager.d.ts +41 -0
  222. package/dist/types/classes/managers/clipboard.manager.d.ts +71 -0
  223. package/dist/types/classes/managers/clipboard.types.d.ts +23 -0
  224. package/dist/types/classes/managers/cursor.manager.d.ts +66 -0
  225. package/dist/types/classes/managers/license.manager.d.ts +147 -0
  226. package/dist/types/classes/managers/localization.manager.d.ts +145 -0
  227. package/dist/types/classes/managers/theme.manager.d.ts +112 -0
  228. package/dist/types/classes/objects/base-object.class.d.ts +266 -0
  229. package/dist/types/classes/objects/dynamic-object.class.d.ts +148 -0
  230. package/dist/types/classes/objects/group.class.d.ts +231 -0
  231. package/dist/types/classes/objects/image.class.d.ts +246 -0
  232. package/dist/types/classes/objects/line.class.d.ts +285 -0
  233. package/dist/types/classes/objects/path.class.d.ts +220 -0
  234. package/dist/types/classes/objects/selection-box.class.d.ts +19 -0
  235. package/dist/types/classes/objects/selection-group.class.d.ts +267 -0
  236. package/dist/types/classes/objects/shape.class.d.ts +322 -0
  237. package/dist/types/classes/objects/text.class.d.ts +197 -0
  238. package/dist/types/classes/providers/assets/asset-resolver.class.d.ts +141 -0
  239. package/dist/types/classes/providers/assets/http-asset-provider.class.d.ts +79 -0
  240. package/dist/types/classes/providers/assets/indexeddb-asset-provider.class.d.ts +69 -0
  241. package/dist/types/classes/providers/assets/presigned-asset-provider.class.d.ts +69 -0
  242. package/dist/types/classes/providers/paste-intent/event-paste-intent.provider.d.ts +8 -0
  243. package/dist/types/classes/providers/paste-intent/ios-paste-intent.provider.d.ts +6 -0
  244. package/dist/types/classes/providers/paste-intent/navigator-paste-intent.provider.d.ts +9 -0
  245. package/dist/types/classes/providers/sync/broadcast-sync-provider.class.d.ts +23 -0
  246. package/dist/types/classes/providers/sync/hocuspocus-sync-provider.class.d.ts +163 -0
  247. package/dist/types/classes/providers/sync/in-memory-sync-provider.class.d.ts +39 -0
  248. package/dist/types/classes/providers/sync/indexeddb-sync-provider.class.d.ts +25 -0
  249. package/dist/types/classes/providers/sync/websocket-sync-provider.class.d.ts +63 -0
  250. package/dist/types/classes/registries/dynamic-object-renderer.registry.d.ts +32 -0
  251. package/dist/types/classes/registries/font-registry.class.d.ts +19 -0
  252. package/dist/types/classes/registries/icon-registry.class.d.ts +39 -0
  253. package/dist/types/classes/registries/paste-intent-provider.registry.d.ts +10 -0
  254. package/dist/types/classes/registries/tool.registry.d.ts +53 -0
  255. package/dist/types/classes/structures/app-state-map.structure.d.ts +171 -0
  256. package/dist/types/classes/structures/object-map.structure.d.ts +403 -0
  257. package/dist/types/classes/structures/quadtree.structure.d.ts +128 -0
  258. package/dist/types/classes/tools/base-tool.class.d.ts +72 -0
  259. package/dist/types/classes/tools/brush-tool.class.d.ts +68 -0
  260. package/dist/types/classes/tools/eraser-tool.class.d.ts +38 -0
  261. package/dist/types/classes/tools/image-tool.class.d.ts +74 -0
  262. package/dist/types/classes/tools/line-tool.class.d.ts +63 -0
  263. package/dist/types/classes/tools/selection-tool.class.d.ts +188 -0
  264. package/dist/types/classes/tools/shape-tool.class.d.ts +90 -0
  265. package/dist/types/classes/tools/text-tool.class.d.ts +66 -0
  266. package/dist/types/components/kritzel-awareness-cursors/kritzel-awareness-cursors.d.ts +29 -0
  267. package/dist/types/components/kritzel-cursor-trail/kritzel-cursor-trail.d.ts +19 -0
  268. package/dist/types/components/kritzel-engine/kritzel-engine.d.ts +695 -0
  269. package/dist/types/components/kritzel-icon/kritzel-icon.d.ts +6 -0
  270. package/dist/types/components/kritzel-watermark/kritzel-watermark.d.ts +20 -0
  271. package/dist/types/components.d.ts +981 -0
  272. package/dist/types/configs/default-asset-storage.config.d.ts +8 -0
  273. package/dist/types/configs/default-brush-tool.config.d.ts +2 -0
  274. package/dist/types/configs/default-engine-config.d.ts +2 -0
  275. package/dist/types/configs/default-line-tool.config.d.ts +2 -0
  276. package/dist/types/configs/default-shape-tool.config.d.ts +2 -0
  277. package/dist/types/configs/default-sync.config.d.ts +5 -0
  278. package/dist/types/configs/default-text-tool.config.d.ts +2 -0
  279. package/dist/types/constants/color-palette.constants.d.ts +31 -0
  280. package/dist/types/constants/core.constants.d.ts +0 -0
  281. package/dist/types/constants/engine.constants.d.ts +4 -0
  282. package/dist/types/constants/license.constants.d.ts +25 -0
  283. package/dist/types/constants/schema.constants.d.ts +4 -0
  284. package/dist/types/constants/storage.constants.d.ts +1 -0
  285. package/dist/types/constants/stroke-size.constants.d.ts +2 -0
  286. package/dist/types/constants/text-tool-fonts.constants.d.ts +3 -0
  287. package/dist/types/constants/version.d.ts +6 -0
  288. package/dist/types/enums/alignment.enum.d.ts +8 -0
  289. package/dist/types/enums/event-button.enum.d.ts +5 -0
  290. package/dist/types/enums/handle-type.enum.d.ts +6 -0
  291. package/dist/types/enums/shape-type.enum.d.ts +5 -0
  292. package/dist/types/events/active-workspace-change.event.d.ts +7 -0
  293. package/dist/types/events/editor-is-ready.event.d.ts +18 -0
  294. package/dist/types/events/login.event.d.ts +3 -0
  295. package/dist/types/helpers/alignment.helper.d.ts +21 -0
  296. package/dist/types/helpers/class.helper.d.ts +3 -0
  297. package/dist/types/helpers/color.helper.d.ts +34 -0
  298. package/dist/types/helpers/cursor.helper.d.ts +22 -0
  299. package/dist/types/helpers/devices.helper.d.ts +8 -0
  300. package/dist/types/helpers/event.helper.d.ts +6 -0
  301. package/dist/types/helpers/geometry.helper.d.ts +38 -0
  302. package/dist/types/helpers/html.helper.d.ts +8 -0
  303. package/dist/types/helpers/image-file.helper.d.ts +3 -0
  304. package/dist/types/helpers/keyboard.helper.d.ts +6 -0
  305. package/dist/types/helpers/localization.helper.d.ts +18 -0
  306. package/dist/types/helpers/math.helper.d.ts +4 -0
  307. package/dist/types/helpers/migration.helper.d.ts +30 -0
  308. package/dist/types/helpers/object.helper.d.ts +9 -0
  309. package/dist/types/helpers/svg-export.helper.d.ts +231 -0
  310. package/dist/types/helpers/theme.helper.d.ts +42 -0
  311. package/dist/types/helpers/tool-config.helper.d.ts +4 -0
  312. package/dist/types/helpers/wheel.helper.d.ts +9 -0
  313. package/dist/types/index.d.ts +109 -0
  314. package/dist/types/interfaces/anchor.interface.d.ts +147 -0
  315. package/dist/types/interfaces/arrow-head.interface.d.ts +27 -0
  316. package/dist/types/interfaces/asset-provider.interface.d.ts +88 -0
  317. package/dist/types/interfaces/asset-storage-config.interface.d.ts +40 -0
  318. package/dist/types/interfaces/asset.interface.d.ts +43 -0
  319. package/dist/types/interfaces/bounding-box.interface.d.ts +8 -0
  320. package/dist/types/interfaces/clonable.interface.d.ts +3 -0
  321. package/dist/types/interfaces/context-menu-item.interface.d.ts +25 -0
  322. package/dist/types/interfaces/context-menu-state.interface.d.ts +16 -0
  323. package/dist/types/interfaces/debug-info.interface.d.ts +7 -0
  324. package/dist/types/interfaces/dialog.interface.d.ts +4 -0
  325. package/dist/types/interfaces/document-metadata.interface.d.ts +6 -0
  326. package/dist/types/interfaces/dynamic-object-renderer.interface.d.ts +40 -0
  327. package/dist/types/interfaces/engine-state.interface.d.ts +82 -0
  328. package/dist/types/interfaces/font.interface.d.ts +15 -0
  329. package/dist/types/interfaces/localization.interface.d.ts +143 -0
  330. package/dist/types/interfaces/login-config.interface.d.ts +24 -0
  331. package/dist/types/interfaces/master-detail.interface.d.ts +14 -0
  332. package/dist/types/interfaces/menu-item.interface.d.ts +27 -0
  333. package/dist/types/interfaces/migration.interface.d.ts +16 -0
  334. package/dist/types/interfaces/notification.interface.d.ts +10 -0
  335. package/dist/types/interfaces/object-change-event.interface.d.ts +31 -0
  336. package/dist/types/interfaces/object-export.interface.d.ts +20 -0
  337. package/dist/types/interfaces/object.interface.d.ts +53 -0
  338. package/dist/types/interfaces/paste-intent-provider.interface.d.ts +10 -0
  339. package/dist/types/interfaces/point.interface.d.ts +4 -0
  340. package/dist/types/interfaces/polygon.interface.d.ts +7 -0
  341. package/dist/types/interfaces/remote-cursor.interface.d.ts +18 -0
  342. package/dist/types/interfaces/selection-state.interface.d.ts +7 -0
  343. package/dist/types/interfaces/serializable.interface.d.ts +5 -0
  344. package/dist/types/interfaces/settings.interface.d.ts +25 -0
  345. package/dist/types/interfaces/share.interface.d.ts +9 -0
  346. package/dist/types/interfaces/shortcut.interface.d.ts +10 -0
  347. package/dist/types/interfaces/sync-config.interface.d.ts +41 -0
  348. package/dist/types/interfaces/sync-provider.interface.d.ts +66 -0
  349. package/dist/types/interfaces/theme.interface.d.ts +553 -0
  350. package/dist/types/interfaces/tool-config.interface.d.ts +24 -0
  351. package/dist/types/interfaces/tool-type.interface.d.ts +1 -0
  352. package/dist/types/interfaces/tool.interface.d.ts +8 -0
  353. package/dist/types/interfaces/toolbar-item.interface.d.ts +66 -0
  354. package/dist/types/interfaces/undo-state.interface.d.ts +6 -0
  355. package/dist/types/interfaces/user.interface.d.ts +22 -0
  356. package/dist/types/interfaces/viewport-state.interface.d.ts +7 -0
  357. package/dist/types/locales/de-locale.d.ts +5 -0
  358. package/dist/types/locales/en-locale.d.ts +6 -0
  359. package/dist/types/locales/fr-locale.d.ts +5 -0
  360. package/dist/types/migrations/app-state.migrations.d.ts +11 -0
  361. package/dist/types/migrations/workspace.migrations.d.ts +11 -0
  362. package/dist/types/stencil-public-runtime.d.ts +1873 -0
  363. package/dist/types/themes/dark-theme.d.ts +5 -0
  364. package/dist/types/themes/light-theme.d.ts +5 -0
  365. package/dist/types/types/deep-readonly.type.d.ts +7 -0
  366. package/dist/types/types/state.types.d.ts +3 -0
  367. package/loader/cdn.js +1 -0
  368. package/loader/index.cjs.js +1 -0
  369. package/loader/index.d.ts +24 -0
  370. package/loader/index.es2017.js +1 -0
  371. package/loader/index.js +2 -0
  372. package/package.json +101 -0
  373. package/readme.md +11 -0
@@ -0,0 +1,1103 @@
1
+ import * as Y from "yjs";
2
+ import { KritzelReviver } from "../core/reviver.class";
3
+ import { KritzelLine } from "../objects/line.class";
4
+ import { KritzelSelectionBox } from "../objects/selection-box.class";
5
+ import { KritzelSelectionGroup } from "../objects/selection-group.class";
6
+ import { DEFAULT_SYNC_CONFIG } from "../../configs/default-sync.config";
7
+ import { CURRENT_WORKSPACE_SCHEMA_VERSION } from "../../constants/schema.constants";
8
+ import { KritzelQuadtree } from "./quadtree.structure";
9
+ import { runMigrations } from "../../helpers/migration.helper";
10
+ import { WORKSPACE_MIGRATIONS } from "../../migrations/workspace.migrations";
11
+ /**
12
+ * A synchronized object map that stores and manages drawable objects on the canvas.
13
+ * This structure integrates with Yjs for real-time collaboration and persistence,
14
+ * uses a quadtree for efficient spatial queries, and provides undo/redo functionality.
15
+ *
16
+ * @template T - The type of objects stored, must extend KritzelBaseObject
17
+ */
18
+ export class KritzelObjectMap {
19
+ quadtree;
20
+ _ydoc = null;
21
+ _objectsMap = null;
22
+ _metadataMap = null;
23
+ _providers = [];
24
+ _undoManager = null;
25
+ _reviver = null;
26
+ _core = null;
27
+ _workspaceId = null;
28
+ _isReady = false;
29
+ _temporaryItemsCount = 0;
30
+ _idMap = new Map();
31
+ _awareness = null;
32
+ _networkSyncPromise = null;
33
+ /** When true this map is a background worker: it never renders, emits, or wires awareness. */
34
+ _headless = false;
35
+ // Store observer/listener references for cleanup
36
+ _objectsObserver = null;
37
+ _stackItemAddedHandler = null;
38
+ _stackItemPoppedHandler = null;
39
+ _awarenessChangeHandler = null;
40
+ _awarenessChangeCallbacks = [];
41
+ _objectsChangeCallbacks = [];
42
+ _lastAwarenessEmitTime = 0;
43
+ _awarenessEmitTimeout = null;
44
+ AWARENESS_THROTTLE_INTERVAL = 100; // milliseconds
45
+ /**
46
+ * When true, update() only modifies local state (quadtree + idMap)
47
+ * without writing to Yjs. Used during drag operations to avoid
48
+ * redundant full serializations of every child object per frame.
49
+ */
50
+ _localOnlyMode = false;
51
+ /**
52
+ * Tracks whether we're currently executing inside a `transaction()`
53
+ * callback. While true, `markUndoBoundary()` is a no-op so nested
54
+ * insert/remove calls don't split the in-progress undo step.
55
+ */
56
+ _inTransaction = false;
57
+ /**
58
+ * Indicates whether the object map has been initialized and is ready for use.
59
+ * @returns `true` if providers are connected and the map is operational
60
+ */
61
+ get isReady() {
62
+ return this._isReady;
63
+ }
64
+ /**
65
+ * Promise that settles when network providers finish connecting.
66
+ * Resolves immediately if no network providers. Awaited by the loading overlay.
67
+ */
68
+ whenNetworkSynced() {
69
+ return this._networkSyncPromise ?? Promise.resolve();
70
+ }
71
+ /**
72
+ * Whether this map runs detached from the rendered canvas.
73
+ */
74
+ get isHeadless() {
75
+ return this._headless;
76
+ }
77
+ /**
78
+ * Returns the Yjs Awareness instance, if a network provider is available.
79
+ */
80
+ get awareness() {
81
+ return this._awareness;
82
+ }
83
+ /**
84
+ * Whether a network awareness instance is available.
85
+ */
86
+ get hasAwareness() {
87
+ return !!this._awareness;
88
+ }
89
+ /**
90
+ * Returns the local client ID from the awareness instance.
91
+ * Used to filter out the local user when rendering remote cursors.
92
+ */
93
+ get localClientId() {
94
+ return this._awareness?.clientID ?? null;
95
+ }
96
+ /**
97
+ * Sets the local user identity in the awareness state.
98
+ * This broadcasts the user's identity to all connected peers.
99
+ */
100
+ setLocalUser(user) {
101
+ if (!this._awareness || !user) {
102
+ return;
103
+ }
104
+ const displayName = user.displayName || user.firstName || 'Anonymous';
105
+ this._awareness.setLocalStateField('user', {
106
+ id: user.id,
107
+ displayName,
108
+ color: user.color || this.generateColorFromName(displayName),
109
+ });
110
+ }
111
+ generateColorFromName(name) {
112
+ let hash = 0;
113
+ for (let i = 0; i < name.length; i++) {
114
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
115
+ }
116
+ const hue = Math.abs(hash % 360);
117
+ return `hsl(${hue}, 45%, 55%)`;
118
+ }
119
+ /**
120
+ * Updates the local cursor position in the awareness state.
121
+ * This broadcasts the cursor position to all connected peers.
122
+ */
123
+ updateCursorPosition(x, y) {
124
+ if (!this._awareness) {
125
+ return;
126
+ }
127
+ this._awareness.setLocalStateField('cursor', { x, y });
128
+ }
129
+ /**
130
+ * Clears the local cursor position (e.g., when the pointer leaves the canvas).
131
+ */
132
+ clearCursorPosition() {
133
+ if (!this._awareness) {
134
+ return;
135
+ }
136
+ this._awareness.setLocalStateField('cursor', null);
137
+ }
138
+ /**
139
+ * Sets the ID of the object currently being drawn by the local user.
140
+ * Remote clients use this to derive cursor position from the object's latest
141
+ * coordinates instead of the throttled awareness cursor, reducing desync.
142
+ * Pass `null` when drawing ends.
143
+ */
144
+ setActiveDrawingObject(objectId) {
145
+ if (!this._awareness) {
146
+ return;
147
+ }
148
+ this._awareness.setLocalStateField('activeObjectId', objectId);
149
+ }
150
+ /**
151
+ * Broadcasts the local user's selection box bounds via awareness.
152
+ * Remote clients use this to render the selection rectangle in the user's color.
153
+ */
154
+ setLocalSelectionBox(box) {
155
+ if (!this._awareness) {
156
+ return;
157
+ }
158
+ this._awareness.setLocalStateField('selectionBox', box);
159
+ }
160
+ /**
161
+ * Clears the local selection box from awareness (e.g., when selection completes or is cancelled).
162
+ */
163
+ clearLocalSelectionBox() {
164
+ if (!this._awareness) {
165
+ return;
166
+ }
167
+ this._awareness.setLocalStateField('selectionBox', null);
168
+ }
169
+ /**
170
+ * Removes selection groups whose owner is no longer present in awareness.
171
+ * Called when remote clients disconnect to prevent orphaned selection groups
172
+ * from persisting in the workspace state.
173
+ */
174
+ removeOrphanedSelectionGroups() {
175
+ if (!this._awareness) {
176
+ return;
177
+ }
178
+ const states = this._awareness.getStates();
179
+ const activeUserIds = new Set();
180
+ states.forEach(state => {
181
+ const userId = state.user?.id;
182
+ if (userId) {
183
+ activeUserIds.add(userId);
184
+ }
185
+ });
186
+ const localUserId = this._core?.user?.id;
187
+ const orphanedGroups = this.quadtree.filter(o => o instanceof KritzelSelectionGroup
188
+ && o.userId != null
189
+ && o.userId !== localUserId
190
+ && !activeUserIds.has(o.userId));
191
+ for (const group of orphanedGroups) {
192
+ this.quadtree.remove(o => o.id === group.id);
193
+ this._idMap.delete(group.id);
194
+ if (this._objectsMap) {
195
+ this._ydoc.transact(() => {
196
+ this._objectsMap.delete(group.id);
197
+ }, 'local');
198
+ }
199
+ }
200
+ if (orphanedGroups.length > 0) {
201
+ this._core?.store.invalidateSelectionCache();
202
+ this.requestRerender();
203
+ }
204
+ }
205
+ /** Rerenders the canvas unless this map is running headless. */
206
+ requestRerender() {
207
+ if (this._headless) {
208
+ return;
209
+ }
210
+ this._core?.rerender();
211
+ }
212
+ /**
213
+ * Registers a callback to be invoked when the awareness state changes.
214
+ * The callback receives the full awareness states map.
215
+ */
216
+ onAwarenessChange(callback) {
217
+ this._awarenessChangeCallbacks.push(callback);
218
+ return () => {
219
+ this._awarenessChangeCallbacks = this._awarenessChangeCallbacks.filter(existingCallback => existingCallback !== callback);
220
+ };
221
+ }
222
+ /**
223
+ * Registers a callback to be invoked when remote object changes are received.
224
+ * Used by the awareness cursors component to re-derive cursor positions
225
+ * from the latest object data when a remote user is actively drawing.
226
+ */
227
+ onObjectsChange(callback) {
228
+ this._objectsChangeCallbacks.push(callback);
229
+ return () => {
230
+ this._objectsChangeCallbacks = this._objectsChangeCallbacks.filter(existingCallback => existingCallback !== callback);
231
+ };
232
+ }
233
+ /**
234
+ * Returns the Yjs UndoManager instance for managing undo/redo operations.
235
+ * @returns The UndoManager instance, or `null` if not initialized
236
+ */
237
+ get undoManager() {
238
+ return this._undoManager;
239
+ }
240
+ /**
241
+ * Returns the unique identifier of the workspace this object map belongs to.
242
+ * @returns The workspace ID, or `null` if not initialized
243
+ */
244
+ get workspaceId() {
245
+ return this._workspaceId;
246
+ }
247
+ /**
248
+ * Returns the current state of the undo/redo system.
249
+ * @returns An object containing undo/redo availability and stack sizes
250
+ */
251
+ get undoState() {
252
+ return {
253
+ canUndo: this._undoManager ? this._undoManager.canUndo() : false,
254
+ canRedo: this._undoManager ? this._undoManager.canRedo() : false,
255
+ undoStackSize: this._undoManager ? this._undoManager.undoStack.length : 0,
256
+ redoStackSize: this._undoManager ? this._undoManager.redoStack.length : 0,
257
+ };
258
+ }
259
+ /**
260
+ * Returns the total number of objects stored in the map.
261
+ * @returns The count of all objects in the quadtree
262
+ */
263
+ get totalCount() {
264
+ return this.quadtree.size;
265
+ }
266
+ /**
267
+ * Retrieves an object from the map by its ID in O(1) time.
268
+ * @param id - The ID of the object to find
269
+ * @returns The object if found, or undefined
270
+ */
271
+ findById(id) {
272
+ return this._idMap.get(id);
273
+ }
274
+ /**
275
+ * Creates a new KritzelObjectMap instance with an empty quadtree.
276
+ * The map must be initialized with `initialize()` before use.
277
+ */
278
+ constructor() {
279
+ this.quadtree = new KritzelQuadtree({
280
+ x: -Infinity,
281
+ y: -Infinity,
282
+ z: 0,
283
+ width: Infinity,
284
+ height: Infinity,
285
+ }, 8);
286
+ }
287
+ /**
288
+ * Initializes the object map with Yjs document, sync providers, and undo manager.
289
+ * This must be called before using any other methods on the object map.
290
+ *
291
+ * @param core - The KritzelCore instance that owns this object map
292
+ * @param workspaceId - A unique identifier for the workspace, used to namespace the Yjs document
293
+ * @param config - Optional sync configuration; defaults to DEFAULT_SYNC_CONFIG if not provided
294
+ * @param options - Optional behavior flags
295
+ * @param options.headless - Run detached from the rendered canvas (no rerenders, events, or awareness)
296
+ * @param options.workspaceName - Workspace name to write into document metadata; defaults to the active workspace's name
297
+ * @returns A promise that resolves when all providers are connected and the map is ready
298
+ */
299
+ async initialize(core, workspaceId, config, options) {
300
+ this._core = core;
301
+ this._workspaceId = workspaceId;
302
+ this._headless = options?.headless ?? false;
303
+ this._reviver = new KritzelReviver(core);
304
+ // Create a dedicated Y.Doc for this workspace
305
+ this._ydoc = new Y.Doc();
306
+ this._objectsMap = this._ydoc.getMap('objects');
307
+ this._metadataMap = this._ydoc.getMap('metadata');
308
+ const docName = core.editorId ? `kritzel-workspace-${core.editorId}-${workspaceId}` : `kritzel-workspace-${workspaceId}`;
309
+ const finalConfig = config ?? DEFAULT_SYNC_CONFIG;
310
+ // Instantiate providers from configuration
311
+ const showSyncProviderInfo = !!core.store?.state?.debugInfo?.showSyncProviderInfo;
312
+ const quiet = !showSyncProviderInfo;
313
+ for (const providerConfig of finalConfig.providers) {
314
+ let provider;
315
+ // Check if it's a class constructor or a factory
316
+ if (typeof providerConfig === 'function') {
317
+ // It's a class constructor
318
+ provider = new providerConfig(docName, this._ydoc, { quiet });
319
+ }
320
+ else {
321
+ // It's a factory with a create method
322
+ provider = providerConfig.create(docName, this._ydoc, { quiet });
323
+ }
324
+ this._providers.push(provider);
325
+ }
326
+ if (showSyncProviderInfo) {
327
+ const providerList = this._providers.map(p => `${p.constructor.name} (${p.type})`).join(', ');
328
+ console.info(`[Kritzel] Workspace sync providers initialized for ${docName}: ${providerList || 'none'}`);
329
+ }
330
+ // captureTimeout is effectively infinite — undo-step boundaries are
331
+ // marked explicitly via markUndoBoundary() in insert/remove/reset and
332
+ // at stroke/gesture boundaries in the tools. This prevents a single
333
+ // brush stroke from being split when the user pauses after pointerdown.
334
+ this._undoManager = new Y.UndoManager([this._objectsMap], {
335
+ captureTimeout: Number.MAX_SAFE_INTEGER,
336
+ trackedOrigins: new Set(['local', 'temporary']),
337
+ ignoreRemoteMapChanges: true,
338
+ });
339
+ // Store handler references for cleanup
340
+ this._stackItemAddedHandler = event => {
341
+ if (event.type === 'undo') {
342
+ // Track if this was a temporary item
343
+ if (event.origin === 'temporary') {
344
+ this._temporaryItemsCount++;
345
+ }
346
+ }
347
+ };
348
+ this._stackItemPoppedHandler = event => {
349
+ if (event.type === 'undo') {
350
+ // Reduce temporary count when items are undone
351
+ if (event.origin === 'temporary' && this._temporaryItemsCount > 0) {
352
+ this._temporaryItemsCount--;
353
+ }
354
+ }
355
+ };
356
+ this._undoManager.on('stack-item-added', this._stackItemAddedHandler);
357
+ this._undoManager.on('stack-item-popped', this._stackItemPoppedHandler);
358
+ // Observe changes to objects and sync with application state
359
+ this._objectsObserver = event => {
360
+ this.handleObjectsChange(event);
361
+ };
362
+ this._objectsMap.observe(this._objectsObserver);
363
+ // Separate local providers (IndexedDB, BroadcastChannel) from network providers (Hocuspocus, WebSocket)
364
+ // Local providers are awaited so data is available immediately; network providers sync in the background
365
+ // via Yjs observers that are already registered above.
366
+ const localProviders = this._providers.filter(p => p.type === 'local');
367
+ const networkProviders = this._providers.filter(p => p.type === 'network');
368
+ // Await local providers for immediate data availability
369
+ const localResults = await Promise.allSettled(localProviders.map(p => p.connect()));
370
+ if (showSyncProviderInfo) {
371
+ const connectedLocalCount = localResults.filter(result => result.status === 'fulfilled').length;
372
+ console.info(`[Kritzel] Workspace local providers connected: ${connectedLocalCount}/${localProviders.length}`);
373
+ }
374
+ localResults.forEach((result, i) => {
375
+ if (showSyncProviderInfo && result.status === 'rejected') {
376
+ console.error(`[Kritzel] Sync provider "${localProviders[i]?.constructor.name}" failed to connect:`, result.reason);
377
+ }
378
+ });
379
+ // Connect network providers in the background (remote data arrives via Yjs observers)
380
+ if (showSyncProviderInfo && networkProviders.length > 0) {
381
+ console.info(`[Kritzel] Workspace network providers connecting in background: ${networkProviders.length}`);
382
+ }
383
+ const networkConnectPromises = networkProviders.map(provider => provider.connect().catch(err => {
384
+ if (showSyncProviderInfo) {
385
+ console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
386
+ }
387
+ }));
388
+ // Network sync awaited by loading overlay to show remote workspace hydration
389
+ this._networkSyncPromise = networkConnectPromises.length > 0 ? Promise.allSettled(networkConnectPromises).then(() => undefined) : null;
390
+ this._isReady = true;
391
+ // Find the first provider that exposes awareness (network providers)
392
+ // Awareness is available immediately after provider construction, before connect() resolves
393
+ for (const provider of networkProviders) {
394
+ if (provider.awareness) {
395
+ this._awareness = provider.awareness;
396
+ break;
397
+ }
398
+ }
399
+ // Subscribe to awareness changes
400
+ if (this._awareness && !this._headless) {
401
+ this._awarenessChangeHandler = (change) => {
402
+ // Clean up selection groups belonging to disconnected users
403
+ if (change.removed.length > 0) {
404
+ this.removeOrphanedSelectionGroups();
405
+ }
406
+ const now = Date.now();
407
+ const timeSinceLastEmit = now - this._lastAwarenessEmitTime;
408
+ // Clear any pending timeout since we have a new event
409
+ if (this._awarenessEmitTimeout !== null) {
410
+ clearTimeout(this._awarenessEmitTimeout);
411
+ this._awarenessEmitTimeout = null;
412
+ }
413
+ if (timeSinceLastEmit >= this.AWARENESS_THROTTLE_INTERVAL) {
414
+ // Enough time has passed, emit immediately
415
+ this._lastAwarenessEmitTime = now;
416
+ const states = this._awareness.getStates();
417
+ for (const callback of this._awarenessChangeCallbacks) {
418
+ callback(states);
419
+ }
420
+ }
421
+ else {
422
+ // Schedule emission for the remaining time
423
+ const delayMs = this.AWARENESS_THROTTLE_INTERVAL - timeSinceLastEmit;
424
+ this._awarenessEmitTimeout = setTimeout(() => {
425
+ this._lastAwarenessEmitTime = Date.now();
426
+ this._awarenessEmitTimeout = null;
427
+ const states = this._awareness.getStates();
428
+ for (const callback of this._awarenessChangeCallbacks) {
429
+ callback(states);
430
+ }
431
+ }, delayMs);
432
+ if (typeof this._awarenessEmitTimeout.unref === 'function') {
433
+ this._awarenessEmitTimeout.unref();
434
+ }
435
+ }
436
+ };
437
+ this._awareness.on('change', this._awarenessChangeHandler);
438
+ }
439
+ // Initialize document metadata (only if not already set by a previous client)
440
+ const workspaceName = options?.workspaceName ?? core.store?.state?.activeWorkspace?.name ?? '';
441
+ this.initializeMetadata('workspace', workspaceId, workspaceName);
442
+ // Run any pending schema migrations before loading data
443
+ const quietMigrations = !core.store?.state?.debugInfo?.showMigrationInfo;
444
+ runMigrations(this._ydoc, this._metadataMap, { objects: this._objectsMap }, WORKSPACE_MIGRATIONS, CURRENT_WORKSPACE_SCHEMA_VERSION, { quiet: quietMigrations });
445
+ // Load objects from Yjs
446
+ this.loadFromYjs();
447
+ }
448
+ /**
449
+ * Handles changes to the Yjs objects map, synchronizing remote changes with local state.
450
+ * For local changes, only triggers a re-render. For remote changes, deserializes objects
451
+ * and updates the quadtree, handling SelectionGroups specially to maintain correct dimensions.
452
+ *
453
+ * @param event - The Yjs map event containing information about changed keys and their actions
454
+ */
455
+ handleObjectsChange(event) {
456
+ // Skip Map updates for local changes (already done), but still trigger re-render
457
+ // 'temporary' is also a local change that shouldn't be re-deserialized
458
+ if (event.transaction.origin === 'local' || event.transaction.origin === 'temporary') {
459
+ this.requestRerender();
460
+ return;
461
+ }
462
+ // Skip migration transactions — data will be loaded fresh via loadFromYjs()
463
+ if (event.transaction.origin === 'migration') {
464
+ return;
465
+ }
466
+ const changedKeys = Array.from(event.keysChanged);
467
+ const objectsToUpdate = [];
468
+ const selectionGroupsToUpdate = [];
469
+ const objectsToDelete = [];
470
+ // The reverse anchor index (targetObjectId -> anchored lines) is derived runtime
471
+ // state that is NOT synced via Yjs. When line anchor connections arrive through a
472
+ // remote change it must be rebuilt, otherwise getLinesAnchoredTo/updateAnchorsForObject
473
+ // return stale/empty results on this client (e.g. anchored lines fail to follow a
474
+ // locally-moved target, leaving selection UI and anchors visually offset).
475
+ let anchorIndexNeedsRebuild = false;
476
+ changedKeys.forEach(key => {
477
+ const change = event.changes.keys.get(key);
478
+ if (change && change.action === 'delete') {
479
+ objectsToDelete.push(key);
480
+ if (this._idMap.get(key) instanceof KritzelLine) {
481
+ anchorIndexNeedsRebuild = true;
482
+ }
483
+ }
484
+ else {
485
+ const serialized = this._objectsMap?.get(key);
486
+ if (serialized) {
487
+ const object = this._reviver?.revive(serialized);
488
+ // Separate SelectionGroups to process them after regular objects
489
+ if (object instanceof KritzelSelectionGroup) {
490
+ selectionGroupsToUpdate.push(object);
491
+ }
492
+ else {
493
+ objectsToUpdate.push(object);
494
+ if (object instanceof KritzelLine) {
495
+ anchorIndexNeedsRebuild = true;
496
+ }
497
+ }
498
+ }
499
+ }
500
+ });
501
+ // Delete objects from local map
502
+ objectsToDelete.forEach(objectId => {
503
+ const existingObject = this._idMap.get(objectId);
504
+ if (existingObject instanceof KritzelSelectionGroup) {
505
+ // Remote deletion of a selection group must also clear the transient
506
+ // selection flags it had applied to its children locally.
507
+ existingObject.deselectAllChildren?.();
508
+ }
509
+ this.quadtree.remove(o => o.id === objectId);
510
+ this._idMap.delete(objectId);
511
+ });
512
+ // Track which objects are added vs updated for granular events
513
+ const addedObjects = [];
514
+ const updatedObjects = [];
515
+ // First, update or insert regular objects
516
+ objectsToUpdate.forEach(object => {
517
+ const existed = this._idMap.has(object.id);
518
+ if (existed) {
519
+ const previous = this._idMap.get(object.id);
520
+ if (previous && typeof object.adoptTransientStateFrom === 'function') {
521
+ object.adoptTransientStateFrom(previous);
522
+ }
523
+ this.quadtree.reindex(object);
524
+ this._idMap.set(object.id, object);
525
+ updatedObjects.push(object);
526
+ }
527
+ else {
528
+ this.quadtree.insert(object);
529
+ this._idMap.set(object.id, object);
530
+ addedObjects.push(object);
531
+ }
532
+ });
533
+ // Then, update or insert SelectionGroups
534
+ const updatedSelectionGroupIds = new Set();
535
+ selectionGroupsToUpdate.forEach(object => {
536
+ const existed = this._idMap.has(object.id);
537
+ if (existed) {
538
+ const previous = this._idMap.get(object.id);
539
+ if (previous && typeof object.adoptTransientStateFrom === 'function') {
540
+ object.adoptTransientStateFrom(previous);
541
+ }
542
+ this.quadtree.reindex(object);
543
+ this._idMap.set(object.id, object);
544
+ }
545
+ else {
546
+ this.quadtree.insert(object);
547
+ this._idMap.set(object.id, object);
548
+ }
549
+ updatedSelectionGroupIds.add(object.id);
550
+ // After undo/redo, the SelectionGroup's dimensions may be stale because:
551
+ // 1. The serialized state contains the old bounding box
552
+ // 2. The child objects have been reverted to their previous positions
553
+ // We need to invalidate the cache and recalculate dimensions
554
+ // skipPersist=true prevents creating new undo entries
555
+ object.invalidateObjectsCache();
556
+ object.refreshObjectDimensions(undefined, true);
557
+ object.captureUnchangedSnapshots();
558
+ // Update quadtree again with corrected dimensions
559
+ this.quadtree.reindex(object);
560
+ });
561
+ // If regular objects were changed but their SelectionGroup wasn't explicitly updated,
562
+ // we still need to refresh that SelectionGroup's dimensions
563
+ if (objectsToUpdate.length > 0 || objectsToDelete.length > 0) {
564
+ const changedObjectIds = new Set([
565
+ ...objectsToUpdate.map(o => o.id),
566
+ ...objectsToDelete
567
+ ]);
568
+ // Find any existing SelectionGroups that contain changed objects
569
+ const existingSelectionGroups = this.quadtree.filter(o => o instanceof KritzelSelectionGroup && !updatedSelectionGroupIds.has(o.id));
570
+ for (const selectionGroup of existingSelectionGroups) {
571
+ const hasChangedChildren = selectionGroup.objectIds.some(id => changedObjectIds.has(id));
572
+ if (hasChangedChildren) {
573
+ selectionGroup.invalidateObjectsCache();
574
+ selectionGroup.refreshObjectDimensions(undefined, true);
575
+ selectionGroup.captureUnchangedSnapshots();
576
+ // Update quadtree with corrected dimensions
577
+ this.quadtree.reindex(selectionGroup);
578
+ }
579
+ }
580
+ }
581
+ if (this._headless) {
582
+ return;
583
+ }
584
+ // Rebuild the anchor index if any line anchor connections may have changed, so that
585
+ // subsequent local operations (moving a target, refreshing selection UI) resolve the
586
+ // correct anchored lines on this client.
587
+ if (anchorIndexNeedsRebuild) {
588
+ this._core?.anchorManager.rebuildIndex();
589
+ }
590
+ // Invalidate the store's selection cache so it fetches the updated SelectionGroup
591
+ this._core?.store.invalidateSelectionCache();
592
+ this._core?.rerender();
593
+ // Emit granular lifecycle events
594
+ if (addedObjects.length > 0) {
595
+ this._core?.engine.emitObjectsAdded(addedObjects);
596
+ }
597
+ if (objectsToDelete.length > 0) {
598
+ // For deleted objects we only have IDs, not the full objects
599
+ // Create minimal object references with the IDs
600
+ const removedObjects = objectsToDelete.map(id => ({ id }));
601
+ this._core?.engine.emitObjectsRemoved(removedObjects);
602
+ }
603
+ if (updatedObjects.length > 0) {
604
+ this._core?.engine.emitObjectsUpdated(updatedObjects.map(obj => ({ object: obj, changedProperties: [] })));
605
+ }
606
+ // Notify subscribers of remote object changes (e.g., awareness cursors)
607
+ for (const callback of this._objectsChangeCallbacks) {
608
+ callback();
609
+ }
610
+ }
611
+ /**
612
+ * Initializes document metadata if not already set.
613
+ * Uses 'metadata' transaction origin which is NOT tracked by the UndoManager.
614
+ */
615
+ initializeMetadata(type, workspaceId, workspaceName) {
616
+ if (!this._metadataMap || !this._ydoc) {
617
+ return;
618
+ }
619
+ this._ydoc.transact(() => {
620
+ if (!this._metadataMap?.get('type')) {
621
+ this._metadataMap?.set('type', type);
622
+ }
623
+ if (this._metadataMap?.get('isPublic') === undefined) {
624
+ this._metadataMap?.set('isPublic', false);
625
+ }
626
+ if (this._metadataMap?.get('schemaVersion') === undefined) {
627
+ this._metadataMap?.set('schemaVersion', CURRENT_WORKSPACE_SCHEMA_VERSION);
628
+ }
629
+ this._metadataMap?.set('workspaceId', workspaceId);
630
+ this._metadataMap?.set('workspaceName', workspaceName);
631
+ }, 'metadata');
632
+ }
633
+ /**
634
+ * Returns whether the document is publicly accessible.
635
+ */
636
+ getIsPublic() {
637
+ return this._metadataMap?.get('isPublic') ?? false;
638
+ }
639
+ /**
640
+ * Sets whether the document is publicly accessible.
641
+ * This change is synced to all connected clients and the server,
642
+ * but is NOT tracked by the UndoManager.
643
+ */
644
+ setIsPublic(isPublic) {
645
+ if (!this._metadataMap || !this._ydoc) {
646
+ return;
647
+ }
648
+ this._ydoc?.transact(() => {
649
+ this._metadataMap?.set('isPublic', isPublic);
650
+ }, 'metadata');
651
+ }
652
+ /**
653
+ * Returns the document type stored in metadata.
654
+ */
655
+ getDocumentType() {
656
+ return this._metadataMap?.get('type') ?? 'workspace';
657
+ }
658
+ /**
659
+ * Returns the workspace name stored in metadata.
660
+ */
661
+ getWorkspaceName() {
662
+ return this._metadataMap?.get('workspaceName') ?? '';
663
+ }
664
+ /**
665
+ * Sets the workspace name in metadata.
666
+ * This change is synced to all connected clients and the server,
667
+ * but is NOT tracked by the UndoManager.
668
+ */
669
+ setWorkspaceName(name) {
670
+ if (!this._metadataMap || !this._ydoc) {
671
+ return;
672
+ }
673
+ this._ydoc?.transact(() => {
674
+ this._metadataMap?.set('workspaceName', name);
675
+ }, 'metadata');
676
+ }
677
+ /**
678
+ * Returns the workspace ID stored in metadata.
679
+ */
680
+ getMetadataWorkspaceId() {
681
+ return this._metadataMap?.get('workspaceId') ?? '';
682
+ }
683
+ /**
684
+ * Executes a callback within a Yjs transaction, grouping multiple operations
685
+ * into a single undo step. All changes made within the callback will be
686
+ * tracked as a single 'local' origin transaction.
687
+ *
688
+ * @param callback - The function containing operations to execute within the transaction
689
+ */
690
+ transaction(callback) {
691
+ if (this._ydoc) {
692
+ this._inTransaction = true;
693
+ try {
694
+ this._ydoc.transact(callback, 'local');
695
+ }
696
+ finally {
697
+ this._inTransaction = false;
698
+ }
699
+ }
700
+ }
701
+ /**
702
+ * Closes the current undo stack item so the next tracked change starts
703
+ * a new one. Call at gesture/stroke boundaries (e.g. drag/resize/rotate
704
+ * end, brush/line/shape stroke end).
705
+ *
706
+ * No-op while a transaction() is open.
707
+ */
708
+ stopUndoCapturing() {
709
+ this.markUndoBoundary();
710
+ }
711
+ /**
712
+ * Internal: closes the current undo stack item. No-op while inside a
713
+ * transaction() so wrapped multi-op work stays a single undo step.
714
+ */
715
+ markUndoBoundary() {
716
+ if (this._inTransaction) {
717
+ return;
718
+ }
719
+ this._undoManager?.stopCapturing();
720
+ }
721
+ /**
722
+ * Executes a callback where all update() calls only modify local state
723
+ * (quadtree + idMap) without writing to Yjs. This avoids expensive full
724
+ * serializations during continuous operations like dragging many objects.
725
+ *
726
+ * Objects updated inside this callback must be persisted later
727
+ * (e.g., via a normal update() call at drag end).
728
+ *
729
+ * @param callback - The function containing operations to execute locally
730
+ */
731
+ withLocalUpdatesOnly(callback) {
732
+ this._localOnlyMode = true;
733
+ try {
734
+ callback();
735
+ }
736
+ finally {
737
+ this._localOnlyMode = false;
738
+ }
739
+ }
740
+ /**
741
+ * Loads all objects from the Yjs objects map into the local quadtree.
742
+ * Clears the existing quadtree and repopulates it by deserializing each
743
+ * object stored in the Yjs map.
744
+ */
745
+ loadFromYjs() {
746
+ if (!this._objectsMap || !this._reviver) {
747
+ return;
748
+ }
749
+ this.quadtree.reset();
750
+ this._idMap.clear();
751
+ const localUserId = this._core?.user?.id;
752
+ const staleSelectionGroupIds = [];
753
+ this._objectsMap.forEach((serialized, key) => {
754
+ const object = this._reviver?.revive(serialized);
755
+ // Remove remote selection groups on startup — they are transient UI state
756
+ // that should not survive an app restart. The owning user's session is gone.
757
+ if (object instanceof KritzelSelectionGroup && object.userId != null && object.userId !== localUserId) {
758
+ staleSelectionGroupIds.push(key);
759
+ return;
760
+ }
761
+ if (!object) {
762
+ return;
763
+ }
764
+ this.quadtree.insert(object);
765
+ this._idMap.set(object.id, object);
766
+ });
767
+ // Clean up stale remote selection groups from Yjs
768
+ if (staleSelectionGroupIds.length > 0) {
769
+ this._ydoc.transact(() => {
770
+ for (const id of staleSelectionGroupIds) {
771
+ this._objectsMap.delete(id);
772
+ }
773
+ }, 'local');
774
+ }
775
+ }
776
+ /**
777
+ * Resets the object map by clearing both the local quadtree and the Yjs objects map.
778
+ * This removes all objects from the workspace. The operation is tracked as a 'local'
779
+ * transaction for undo/redo purposes.
780
+ */
781
+ reset() {
782
+ this.quadtree.reset();
783
+ this._idMap.clear();
784
+ this.markUndoBoundary();
785
+ this._ydoc?.transact(() => {
786
+ this._objectsMap?.clear();
787
+ }, 'local');
788
+ }
789
+ /**
790
+ * Inserts a new object into both the local quadtree and the Yjs objects map.
791
+ * The object must have a valid ID. Non-persistable objects (like SelectionBox)
792
+ * are only added to the quadtree, not to Yjs.
793
+ *
794
+ * @param object - The object to insert; must have a valid `id` property
795
+ * @returns `true` if the object was successfully inserted, `false` if the object has no ID
796
+ */
797
+ insert(object) {
798
+ if (!object.id) {
799
+ return false;
800
+ }
801
+ this.quadtree.insert(object);
802
+ this._idMap.set(object.id, object);
803
+ if (this._objectsMap && this.isPersistable(object)) {
804
+ const serialized = object.serialize();
805
+ this.markUndoBoundary();
806
+ this._ydoc?.transact(() => {
807
+ this._objectsMap?.set(object.id, serialized);
808
+ }, 'local');
809
+ }
810
+ return true;
811
+ }
812
+ /**
813
+ * Updates an existing object in both the local quadtree and the Yjs objects map.
814
+ * The object must already exist in the map. Supports temporary updates that can
815
+ * be consolidated later (e.g., for incremental text input).
816
+ *
817
+ * @param object - The object with updated properties; must have a valid `id` that exists in the map
818
+ * @param options - Optional settings for the update operation
819
+ * @param options.temporary - If `true`, marks this as a temporary change that can be consolidated
820
+ * with other temporary changes into a single undo step
821
+ * @returns `true` if the object was successfully updated, `false` if the object doesn't exist or has no ID
822
+ */
823
+ update(object, options = {}) {
824
+ if (!object.id) {
825
+ return false;
826
+ }
827
+ const existed = this._idMap.has(object.id);
828
+ if (!existed) {
829
+ return false;
830
+ }
831
+ if (this._localOnlyMode || options.skipReindex) {
832
+ this.quadtree.update(object);
833
+ }
834
+ else {
835
+ this.quadtree.reindex(object);
836
+ }
837
+ this._idMap.set(object.id, object);
838
+ if (!this._localOnlyMode && this._objectsMap && this.isPersistable(object)) {
839
+ const serialized = object.serialize();
840
+ const origin = options.temporary ? 'temporary' : 'local';
841
+ this._ydoc?.transact(() => {
842
+ this._objectsMap?.set(object.id, serialized);
843
+ }, origin);
844
+ }
845
+ return true;
846
+ }
847
+ /**
848
+ * Removes objects matching the given predicate from both the quadtree and Yjs map.
849
+ * Each removal is tracked as a separate 'local' transaction for undo/redo.
850
+ *
851
+ * @param predicate - A function that returns `true` for objects that should be removed
852
+ */
853
+ remove(predicate) {
854
+ const objectsToRemove = this.quadtree.filter(predicate);
855
+ if (objectsToRemove.length === 0) {
856
+ return;
857
+ }
858
+ this.markUndoBoundary();
859
+ for (const object of objectsToRemove) {
860
+ this.quadtree.remove(o => o.id === object.id);
861
+ this._idMap.delete(object.id);
862
+ if (this._objectsMap && this.isPersistable(object)) {
863
+ this._ydoc?.transact(() => {
864
+ this._objectsMap?.delete(object.id);
865
+ }, 'local');
866
+ }
867
+ }
868
+ }
869
+ /**
870
+ * Filters objects in the quadtree based on a predicate function.
871
+ *
872
+ * @param predicate - A function that returns `true` for objects to include in the result
873
+ * @returns An array of objects that match the predicate
874
+ */
875
+ filter(predicate) {
876
+ return this.quadtree.filter(predicate);
877
+ }
878
+ /**
879
+ * Returns all objects stored in the quadtree.
880
+ *
881
+ * @returns An array containing all objects in the map
882
+ */
883
+ allObjects() {
884
+ return this.quadtree.allObjects();
885
+ }
886
+ /**
887
+ * Queries the quadtree for objects within or intersecting a given 3D range.
888
+ * This is an efficient spatial query that avoids iterating over all objects.
889
+ *
890
+ * @param range - The bounding box to query
891
+ * @param range.x - The x-coordinate of the range's origin
892
+ * @param range.y - The y-coordinate of the range's origin
893
+ * @param range.z - The z-coordinate (layer) of the range's origin
894
+ * @param range.width - The width of the range
895
+ * @param range.height - The height of the range
896
+ * @param range.depth - Optional depth for 3D queries
897
+ * @returns An array of objects within or intersecting the specified range
898
+ */
899
+ query(range) {
900
+ return this.quadtree.query(range);
901
+ }
902
+ /**
903
+ * Determines whether an object should be persisted to the Yjs document.
904
+ * Certain objects like SelectionBox are transient and should not be synced.
905
+ *
906
+ * @param object - The object to check for persistability
907
+ * @returns `true` if the object should be persisted, `false` otherwise
908
+ */
909
+ isPersistable(object) {
910
+ if (object instanceof KritzelSelectionBox) {
911
+ return false;
912
+ }
913
+ return true;
914
+ }
915
+ /**
916
+ * Undoes the last operation if possible.
917
+ * Reverts the most recent change in the undo stack and emits an objects change event.
918
+ */
919
+ undo() {
920
+ if (this._undoManager && this._undoManager.canUndo()) {
921
+ this._undoManager.undo();
922
+ this._core?.anchorManager.rebuildIndex();
923
+ this._core?.engine.emitObjectsChange();
924
+ }
925
+ }
926
+ /**
927
+ * Redoes the last undone operation if possible.
928
+ * Reapplies the most recent change from the redo stack and emits an objects change event.
929
+ */
930
+ redo() {
931
+ if (this._undoManager && this._undoManager.canRedo()) {
932
+ this._undoManager.redo();
933
+ this._core?.anchorManager.rebuildIndex();
934
+ this._core?.engine.emitObjectsChange();
935
+ }
936
+ }
937
+ /**
938
+ * Checks whether an undo operation is available.
939
+ *
940
+ * @returns `true` if there are operations in the undo stack, `false` otherwise
941
+ */
942
+ canUndo() {
943
+ return this._undoManager ? this._undoManager.canUndo() : false;
944
+ }
945
+ /**
946
+ * Checks whether a redo operation is available.
947
+ *
948
+ * @returns `true` if there are operations in the redo stack, `false` otherwise
949
+ */
950
+ canRedo() {
951
+ return this._undoManager ? this._undoManager.canRedo() : false;
952
+ }
953
+ /**
954
+ * Clears all undo and redo history.
955
+ * After calling this, no undo or redo operations will be available.
956
+ * Also resets the temporary items counter.
957
+ */
958
+ clearHistory() {
959
+ if (this._undoManager) {
960
+ this._undoManager.clear();
961
+ this._temporaryItemsCount = 0;
962
+ }
963
+ }
964
+ /**
965
+ * Consolidates all temporary items in the undo stack into a single undo item.
966
+ * This is useful when you want to merge multiple incremental changes (e.g., individual
967
+ * keystrokes during text editing) into a single undoable action.
968
+ *
969
+ * The method works by undoing all temporary items and then redoing them within
970
+ * a single 'local' transaction, effectively merging them into one undo step.
971
+ * After consolidation, the temporary items counter is reset to zero.
972
+ *
973
+ * @example
974
+ * // After multiple temporary text updates
975
+ * objectMap.update(textObject, { temporary: true }); // keystroke 1
976
+ * objectMap.update(textObject, { temporary: true }); // keystroke 2
977
+ * objectMap.consolidateTemporaryItems(); // merge into single undo step
978
+ */
979
+ consolidateTemporaryItems() {
980
+ if (!this._undoManager || this._temporaryItemsCount === 0) {
981
+ return;
982
+ }
983
+ // Get current stack length
984
+ const stackLength = this._undoManager.undoStack.length;
985
+ if (stackLength === 0) {
986
+ this._temporaryItemsCount = 0;
987
+ return;
988
+ }
989
+ // Stop tracking temporarily
990
+ this._undoManager.stopCapturing();
991
+ // The temporary items should be at the end of the stack
992
+ // We'll need to undo them all, then redo as a single transaction
993
+ const itemsToConsolidate = Math.min(this._temporaryItemsCount, stackLength);
994
+ if (itemsToConsolidate > 1) {
995
+ // Store the current state before undoing
996
+ const undoneItems = [];
997
+ // Undo all temporary items
998
+ for (let i = 0; i < itemsToConsolidate; i++) {
999
+ if (this._undoManager.canUndo()) {
1000
+ undoneItems.push(this._undoManager.undoStack[this._undoManager.undoStack.length - 1]);
1001
+ this._undoManager.undo();
1002
+ }
1003
+ }
1004
+ // Now redo them all as a single 'local' transaction
1005
+ // This will consolidate them into one undo item
1006
+ this._ydoc?.transact(() => {
1007
+ for (let i = itemsToConsolidate - 1; i >= 0; i--) {
1008
+ if (this._undoManager?.canRedo()) {
1009
+ this._undoManager.redo();
1010
+ }
1011
+ }
1012
+ }, 'local');
1013
+ }
1014
+ else if (itemsToConsolidate === 1) {
1015
+ // If only one temporary item, just change its origin to 'local'
1016
+ // This is implicit - next transaction will be separate
1017
+ }
1018
+ this._temporaryItemsCount = 0;
1019
+ }
1020
+ /**
1021
+ * Removes all temporary items from the undo stack without consolidating them.
1022
+ * Use this to discard temporary changes completely when they should not be
1023
+ * preserved in the undo history (e.g., when cancelling an operation).
1024
+ *
1025
+ * Unlike `consolidateTemporaryItems`, this method does not preserve the changes
1026
+ * as a single undo step - it simply removes them from history entirely.
1027
+ */
1028
+ clearTemporaryItems() {
1029
+ if (!this._undoManager || this._temporaryItemsCount === 0) {
1030
+ return;
1031
+ }
1032
+ const stackLength = this._undoManager.undoStack.length;
1033
+ const itemsToRemove = Math.min(this._temporaryItemsCount, stackLength);
1034
+ // Remove items from the end of the stack
1035
+ for (let i = 0; i < itemsToRemove; i++) {
1036
+ if (this._undoManager.undoStack.length > 0) {
1037
+ this._undoManager.undoStack.pop();
1038
+ }
1039
+ }
1040
+ this._temporaryItemsCount = 0;
1041
+ }
1042
+ /**
1043
+ * Hot-swaps sync providers on the existing Y.Doc without destroying the document,
1044
+ * objects, undo manager, or quadtree. Tears down old awareness subscriptions,
1045
+ /**
1046
+ * Destroys the object map and releases all resources.
1047
+ * This removes all observers, destroys the undo manager, disconnects all providers,
1048
+ * and destroys the Yjs document. After calling this, the object map cannot be used.
1049
+ */
1050
+ destroy() {
1051
+ // Unobserve the objects map
1052
+ if (this._objectsMap && this._objectsObserver) {
1053
+ this._objectsMap.unobserve(this._objectsObserver);
1054
+ this._objectsObserver = null;
1055
+ }
1056
+ // Remove undo manager event listeners
1057
+ if (this._undoManager) {
1058
+ if (this._stackItemAddedHandler) {
1059
+ this._undoManager.off('stack-item-added', this._stackItemAddedHandler);
1060
+ this._stackItemAddedHandler = null;
1061
+ }
1062
+ if (this._stackItemPoppedHandler) {
1063
+ this._undoManager.off('stack-item-popped', this._stackItemPoppedHandler);
1064
+ this._stackItemPoppedHandler = null;
1065
+ }
1066
+ this._undoManager.destroy();
1067
+ this._undoManager = null;
1068
+ }
1069
+ // Reset quadtree
1070
+ this.quadtree.reset();
1071
+ this._idMap.clear();
1072
+ // Clean up awareness
1073
+ if (this._awareness && this._awarenessChangeHandler) {
1074
+ this._awareness.off('change', this._awarenessChangeHandler);
1075
+ this._awarenessChangeHandler = null;
1076
+ }
1077
+ if (this._awarenessEmitTimeout !== null) {
1078
+ clearTimeout(this._awarenessEmitTimeout);
1079
+ this._awarenessEmitTimeout = null;
1080
+ }
1081
+ this._awareness = null;
1082
+ this._awarenessChangeCallbacks = [];
1083
+ // Destroy providers (destroy handles disconnection internally)
1084
+ this._providers.forEach(p => {
1085
+ p.destroy();
1086
+ });
1087
+ this._providers = [];
1088
+ // Clear object map references
1089
+ this._objectsMap = null;
1090
+ this._metadataMap = null;
1091
+ // Destroy Y.Doc
1092
+ if (this._ydoc) {
1093
+ this._ydoc.destroy();
1094
+ this._ydoc = null;
1095
+ }
1096
+ // Clear remaining references
1097
+ this._core = null;
1098
+ this._reviver = null;
1099
+ this._workspaceId = null;
1100
+ this._isReady = false;
1101
+ this._temporaryItemsCount = 0;
1102
+ }
1103
+ }