@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,526 @@
1
+ import { KritzelEventHelper } from "../../helpers/event.helper";
2
+ import { KritzelSelectionBox } from "../objects/selection-box.class";
3
+ import { KritzelSelectionGroup } from "../objects/selection-group.class";
4
+ import { KritzelGroup } from "../objects/group.class";
5
+ import { KritzelBaseHandler } from "./base.handler";
6
+ /**
7
+ * Handles object selection interactions on the canvas.
8
+ * Supports both click-to-select and drag-to-select (marquee) selection modes
9
+ * for mouse, touch, and pen input types. Also handles additive selection
10
+ * via shift/ctrl modifiers.
11
+ */
12
+ export class KritzelSelectionHandler extends KritzelBaseHandler {
13
+ startX = 0;
14
+ startY = 0;
15
+ touchStartX = 0;
16
+ touchStartY = 0;
17
+ touchStartTimeout = null;
18
+ /**
19
+ * Tracks previously selected objects for efficient deselection.
20
+ * Uses a Set for O(1) lookup when determining which objects need to be deselected.
21
+ */
22
+ _previouslySelectedObjects = new Set();
23
+ /**
24
+ * Indicates whether the current selection action is a single click (no drag).
25
+ * @returns `true` if a selection box exists with zero width and height
26
+ */
27
+ get isSelectionClick() {
28
+ const selectionBox = this._core.store.selectionBox;
29
+ return selectionBox && selectionBox.width === 0 && selectionBox.height === 0;
30
+ }
31
+ /**
32
+ * Indicates whether the current selection action is a drag selection (marquee).
33
+ * @returns `true` if a selection box exists with non-zero width or height
34
+ */
35
+ get isSelectionDrag() {
36
+ const selectionBox = this._core.store.selectionBox;
37
+ return selectionBox && (selectionBox.width > 0 || selectionBox.height > 0);
38
+ }
39
+ /**
40
+ * Creates a new selection handler instance.
41
+ * @param core - The KritzelCore instance providing access to store, engine, and other core services
42
+ */
43
+ constructor(core) {
44
+ super(core);
45
+ }
46
+ /**
47
+ * Handles the pointer down event to initiate selection.
48
+ * For mouse input, starts selection on left-click when no selection group exists or in additive mode.
49
+ * For touch/pen input, initiates selection after a brief delay (80ms) to distinguish from other gestures.
50
+ * @param event - The pointer event containing input type, coordinates, and modifier keys
51
+ */
52
+ handlePointerDown(event) {
53
+ if (this._core.store.state.isResizeHandleSelected || this._core.store.state.isRotationHandleSelected) {
54
+ return;
55
+ }
56
+ const isAdditive = event.shiftKey || event.ctrlKey;
57
+ if (event.pointerType === 'mouse') {
58
+ if (KritzelEventHelper.isLeftClick(event) && (!this._core.store.selectionGroup || isAdditive)) {
59
+ this.startMouseSelection(event, isAdditive);
60
+ }
61
+ }
62
+ if (event.pointerType === 'touch' || event.pointerType === 'pen') {
63
+ this.touchStartTimeout = setTimeout(() => {
64
+ if (this._core.store.state.pointers.size === 1 && !this._core.store.state.isScaling && (!this._core.store.selectionGroup || isAdditive)) {
65
+ this.startTouchSelection(event, isAdditive);
66
+ this.updateTouchSelection();
67
+ }
68
+ }, 80);
69
+ }
70
+ }
71
+ /**
72
+ * Handles the pointer move event to update the selection box during drag selection.
73
+ * For mouse input, updates selection when actively selecting.
74
+ * For touch/pen input, updates selection and cancels long-touch timeout if movement exceeds threshold.
75
+ * @param event - The pointer event containing current pointer position and input type
76
+ */
77
+ handlePointerMove(event) {
78
+ if (event.pointerType === 'mouse') {
79
+ if (this._core.store.state.isSelecting) {
80
+ this.updateMouseSelection(event);
81
+ }
82
+ }
83
+ if (event.pointerType === 'touch' || event.pointerType === 'pen') {
84
+ const activePointers = Array.from(this._core.store.state.pointers.values());
85
+ const x = Math.round(activePointers[0].clientX - this._core.store.offsetX);
86
+ const y = Math.round(activePointers[0].clientY - this._core.store.offsetY);
87
+ const moveDeltaX = Math.abs(x - this.touchStartX);
88
+ const moveDeltaY = Math.abs(y - this.touchStartY);
89
+ const moveThreshold = 5;
90
+ if ((moveDeltaX > moveThreshold || moveDeltaY > moveThreshold) && this._core.store.state.isSelecting) {
91
+ this.updateTouchSelection();
92
+ const timeout = this._core.store.state.longTouchTimeout;
93
+ if (timeout) {
94
+ globalThis.clearTimeout?.(timeout);
95
+ }
96
+ }
97
+ }
98
+ }
99
+ /**
100
+ * Handles the pointer up event to complete the selection action.
101
+ * Determines whether the action was a click or drag selection and processes accordingly.
102
+ * Triggers object edit mode when a single object is selected without movement.
103
+ * Emits objects change event after selection completes.
104
+ * @param event - The pointer event containing input type, coordinates, and modifier keys
105
+ */
106
+ handlePointerUp(event) {
107
+ if (event.pointerType === 'mouse') {
108
+ if (KritzelEventHelper.isLeftClick(event)) {
109
+ const hasObjectsMoved = this._core.store.state.hasObjectsChanged;
110
+ const selectionGroup = this._core.store.selectionGroup;
111
+ const isAdditive = event.shiftKey || event.ctrlKey;
112
+ if (selectionGroup?.objects.length === 1 && hasObjectsMoved === false && !isAdditive) {
113
+ selectionGroup.objects[0].edit(event);
114
+ }
115
+ this._core.store.state.hasObjectsChanged = false;
116
+ if (this._core.store.state.isSelecting) {
117
+ if (this.isSelectionClick) {
118
+ this.handleSelectionClick(event);
119
+ }
120
+ if (this.isSelectionDrag) {
121
+ const isAdditive = event.shiftKey || event.ctrlKey;
122
+ this.updateMouseSelection(event);
123
+ this.addSelectedObjectsToSelectionGroup(isAdditive);
124
+ this.removeSelectionBox();
125
+ }
126
+ this._core.engine.emitObjectsChange();
127
+ }
128
+ }
129
+ }
130
+ if (event.pointerType === 'touch' || event.pointerType === 'pen') {
131
+ clearTimeout(this.touchStartTimeout);
132
+ const hasObjectsMoved = this._core.store.state.hasObjectsChanged;
133
+ const selectionGroup = this._core.store.selectionGroup;
134
+ const isAdditive = event.shiftKey || event.ctrlKey;
135
+ if (selectionGroup?.objects.length === 1 && hasObjectsMoved === false && !isAdditive) {
136
+ selectionGroup.objects[0].edit(event);
137
+ }
138
+ this._core.store.state.hasObjectsChanged = false;
139
+ if (this._core.store.state.isSelecting) {
140
+ if (this.isSelectionClick) {
141
+ this.handleSelectionClick(event);
142
+ }
143
+ if (this.isSelectionDrag) {
144
+ const isAdditive = event.shiftKey || event.ctrlKey;
145
+ this.updateTouchSelection();
146
+ this.addSelectedObjectsToSelectionGroup(isAdditive);
147
+ this.removeSelectionBox();
148
+ }
149
+ this._core.engine.emitObjectsChange();
150
+ this._core.store.state.skipContextMenu = false;
151
+ }
152
+ }
153
+ }
154
+ /**
155
+ * Removes the selection box from the canvas and resets selection state.
156
+ * Clears the selecting flag and removes the selection box object from the store.
157
+ */
158
+ removeSelectionBox() {
159
+ this._core.store.state.isSelecting = false;
160
+ this._core.store.objects.remove(o => o instanceof KritzelSelectionBox);
161
+ this._core.store.setSelectionBox(null);
162
+ this._core.store.objects.clearLocalSelectionBox();
163
+ }
164
+ /**
165
+ * Initiates a mouse-based selection by creating a selection box at the click position.
166
+ * Calculates canvas coordinates accounting for offset, translation, and scale.
167
+ * In non-additive mode, clears any existing selection group.
168
+ * @param event - The mouse event containing click coordinates
169
+ * @param isAdditive - When `true`, preserves existing selection group for adding objects
170
+ */
171
+ startMouseSelection(event, isAdditive = false) {
172
+ let clientX, clientY;
173
+ clientX = event.clientX - this._core.store.offsetX;
174
+ clientY = event.clientY - this._core.store.offsetY;
175
+ const selectionBox = KritzelSelectionBox.create(this._core);
176
+ this.startX = (clientX - this._core.store.state.translateX) / this._core.store.state.scale;
177
+ this.startY = (clientY - this._core.store.state.translateY) / this._core.store.state.scale;
178
+ selectionBox.translateX = this.startX;
179
+ selectionBox.translateY = this.startY;
180
+ this._core.store.state.isSelecting = true;
181
+ this._core.removeSelectionBox();
182
+ if (!isAdditive) {
183
+ this._core.removeSelectionGroup();
184
+ }
185
+ this._core.store.objects.insert(selectionBox);
186
+ this._core.store.setSelectionBox(selectionBox);
187
+ }
188
+ /**
189
+ * Initiates a touch/pen-based selection by creating a selection box at the touch position.
190
+ * Uses the first active pointer from the pointers map to determine coordinates.
191
+ * In non-additive mode, clears any existing selection group.
192
+ * @param _event - The pointer event (unused, coordinates from active pointers)
193
+ * @param isAdditive - When `true`, preserves existing selection group for adding objects
194
+ */
195
+ startTouchSelection(_event, isAdditive = false) {
196
+ const activePointers = Array.from(this._core.store.state.pointers.values());
197
+ const firstTouch = activePointers[0];
198
+ if (!firstTouch) {
199
+ return;
200
+ }
201
+ let clientX, clientY;
202
+ clientX = Math.round(firstTouch.clientX - this._core.store.offsetX);
203
+ clientY = Math.round(firstTouch.clientY - this._core.store.offsetY);
204
+ this.touchStartX = clientX;
205
+ this.touchStartY = clientY;
206
+ const selectionBox = KritzelSelectionBox.create(this._core);
207
+ this.startX = (clientX - this._core.store.state.translateX) / this._core.store.state.scale;
208
+ this.startY = (clientY - this._core.store.state.translateY) / this._core.store.state.scale;
209
+ selectionBox.translateX = this.startX;
210
+ selectionBox.translateY = this.startY;
211
+ this._core.store.state.isSelecting = true;
212
+ this._core.removeSelectionBox();
213
+ if (!isAdditive) {
214
+ this._core.removeSelectionGroup();
215
+ }
216
+ this._core.store.objects.insert(selectionBox);
217
+ this._core.store.setSelectionBox(selectionBox);
218
+ }
219
+ /**
220
+ * Updates the selection box dimensions during mouse drag.
221
+ * Calculates the new width, height, and position based on current mouse position
222
+ * relative to the start position. Supports selection in any direction.
223
+ * @param event - The mouse event containing current cursor position
224
+ */
225
+ updateMouseSelection(event) {
226
+ let clientX, clientY;
227
+ clientX = event.clientX - this._core.store.offsetX;
228
+ clientY = event.clientY - this._core.store.offsetY;
229
+ const selectionBox = this._core.store.selectionBox;
230
+ if (selectionBox) {
231
+ const currentX = (clientX - this._core.store.state.translateX) / selectionBox.scale;
232
+ const currentY = (clientY - this._core.store.state.translateY) / selectionBox.scale;
233
+ const width = Math.abs(currentX - this.startX) * selectionBox.scale;
234
+ const height = Math.abs(currentY - this.startY) * selectionBox.scale;
235
+ selectionBox.width = width;
236
+ selectionBox.height = height;
237
+ selectionBox.translateX = Math.min(currentX, this.startX);
238
+ selectionBox.translateY = Math.min(currentY, this.startY);
239
+ this._core.store.objects.setLocalSelectionBox({
240
+ x: selectionBox.translateX,
241
+ y: selectionBox.translateY,
242
+ width: width / selectionBox.scale,
243
+ height: height / selectionBox.scale,
244
+ });
245
+ if (width > 0 || height > 0) {
246
+ this.updateSelectedObjects();
247
+ }
248
+ else {
249
+ this.clearSelectionPreview();
250
+ }
251
+ }
252
+ }
253
+ /**
254
+ * Updates the selection box dimensions during touch/pen drag.
255
+ * Uses the first active pointer to calculate new dimensions.
256
+ * Supports selection in any direction by using absolute width/height
257
+ * and positioning at the minimum coordinates.
258
+ */
259
+ updateTouchSelection() {
260
+ const activePointers = Array.from(this._core.store.state.pointers.values());
261
+ const firstTouch = activePointers[0];
262
+ if (!firstTouch) {
263
+ return;
264
+ }
265
+ let clientX, clientY;
266
+ clientX = Math.round(firstTouch.clientX - this._core.store.offsetX);
267
+ clientY = Math.round(firstTouch.clientY - this._core.store.offsetY);
268
+ const selectionBox = this._core.store.selectionBox;
269
+ if (selectionBox) {
270
+ const currentX = (clientX - this._core.store.state.translateX) / selectionBox.scale;
271
+ const currentY = (clientY - this._core.store.state.translateY) / selectionBox.scale;
272
+ const width = Math.abs(currentX - this.startX) * selectionBox.scale;
273
+ const height = Math.abs(currentY - this.startY) * selectionBox.scale;
274
+ selectionBox.width = width;
275
+ selectionBox.height = height;
276
+ selectionBox.translateX = Math.min(currentX, this.startX);
277
+ selectionBox.translateY = Math.min(currentY, this.startY);
278
+ this._core.store.objects.setLocalSelectionBox({
279
+ x: selectionBox.translateX,
280
+ y: selectionBox.translateY,
281
+ width: width / selectionBox.scale,
282
+ height: height / selectionBox.scale,
283
+ });
284
+ if (width > 0 || height > 0) {
285
+ this.updateSelectedObjects();
286
+ }
287
+ else {
288
+ this.clearSelectionPreview();
289
+ }
290
+ }
291
+ }
292
+ /**
293
+ * Updates the selection state of objects intersecting with the selection box.
294
+ * Uses quadtree spatial indexing for efficient O(log n + k) candidate lookup.
295
+ * Performs polygon-based hit testing for accurate rotated selection support.
296
+ * Efficiently deselects only previously selected objects that no longer intersect.
297
+ */
298
+ updateSelectedObjects() {
299
+ const selectionBox = this._core.store.selectionBox;
300
+ if (!selectionBox)
301
+ return;
302
+ // Compute rotatedPolygon once per update (not per object)
303
+ const selectionPolygon = selectionBox.rotatedPolygon;
304
+ // Calculate bounding box from the polygon we already have to avoid recomputing it
305
+ // calling selectionBox.rotatedBoundingBox would trigger another rotatedPolygon computation
306
+ const xValues = [selectionPolygon.topLeft.x, selectionPolygon.topRight.x, selectionPolygon.bottomRight.x, selectionPolygon.bottomLeft.x];
307
+ const yValues = [selectionPolygon.topLeft.y, selectionPolygon.topRight.y, selectionPolygon.bottomRight.y, selectionPolygon.bottomLeft.y];
308
+ const minX = Math.min(...xValues);
309
+ const maxX = Math.max(...xValues);
310
+ const minY = Math.min(...yValues);
311
+ const maxY = Math.max(...yValues);
312
+ // Use quadtree spatial query to get only nearby candidates - O(log n + k) instead of O(n)
313
+ const selectionBounds = {
314
+ x: minX,
315
+ y: minY,
316
+ z: selectionBox.scale,
317
+ width: maxX - minX,
318
+ height: maxY - minY,
319
+ };
320
+ const candidates = this._core.store.objects.query(selectionBounds);
321
+ // Track newly selected objects for efficient comparison
322
+ const newlySelectedObjects = new Set();
323
+ // Only test candidates that overlap the bounding box
324
+ for (const object of candidates) {
325
+ if (object instanceof KritzelSelectionBox || object instanceof KritzelSelectionGroup)
326
+ continue;
327
+ const isHit = object.hitTestPolygon(selectionPolygon);
328
+ object.isSelected = isHit;
329
+ if (isHit) {
330
+ newlySelectedObjects.add(object);
331
+ }
332
+ }
333
+ // Deselect only objects that were previously selected but aren't anymore
334
+ // This is O(k) where k is the number of previously selected objects, not O(n)
335
+ for (const prevObj of this._previouslySelectedObjects) {
336
+ if (!newlySelectedObjects.has(prevObj)) {
337
+ prevObj.isSelected = false;
338
+ }
339
+ }
340
+ this._previouslySelectedObjects = newlySelectedObjects;
341
+ }
342
+ /**
343
+ * Handles a single-click selection action.
344
+ * Finds the topmost object at the click position and adds it to the selection group.
345
+ * Supports additive selection mode via shift/ctrl modifier keys.
346
+ * @param event - The pointer event containing click coordinates and modifier keys
347
+ */
348
+ handleSelectionClick(event) {
349
+ const selectedObject = this.getTopmostHitObject(event);
350
+ const isAdditive = event.shiftKey || event.ctrlKey;
351
+ this.clearSelectionPreview();
352
+ if (selectedObject) {
353
+ this.addObjectToSelectionGroup(selectedObject, isAdditive);
354
+ }
355
+ this.removeSelectionBox();
356
+ }
357
+ /**
358
+ * Finds the topmost (highest z-order) object at the pointer position.
359
+ * Iterates through overlapping objects from top to bottom and performs hit testing.
360
+ * Excludes selection-related objects (selection box and selection group).
361
+ * @param event - The pointer event used to query overlapping DOM elements
362
+ * @returns The topmost object that passes hit testing, or `null` if none found
363
+ */
364
+ getTopmostHitObject(event) {
365
+ const pointerX = this._core.store.state.pointerX;
366
+ const pointerY = this._core.store.state.pointerY;
367
+ const overlappingObjects = this._core.getObjectsFromPointerEvent(event, '.object');
368
+ for (const object of overlappingObjects) {
369
+ if (object instanceof KritzelSelectionBox || object instanceof KritzelSelectionGroup)
370
+ continue;
371
+ if (object.hitTest(pointerX, pointerY)) {
372
+ return object;
373
+ }
374
+ }
375
+ return null;
376
+ }
377
+ /**
378
+ * Clears the visual selection preview by deselecting all previously selected objects.
379
+ * Operates in O(k) time where k is the number of previously selected objects,
380
+ * rather than O(n) by iterating only tracked objects.
381
+ */
382
+ clearSelectionPreview() {
383
+ // Only clear previously tracked selected objects - O(k) instead of O(n)
384
+ const selectedGroupIds = new Set(this._core.store.selectionGroup?.objectIds ?? []);
385
+ for (const obj of this._previouslySelectedObjects) {
386
+ if (!selectedGroupIds.has(obj.id)) {
387
+ obj.isSelected = false;
388
+ }
389
+ }
390
+ this._previouslySelectedObjects.clear();
391
+ }
392
+ /**
393
+ * Adds a single object to the selection group.
394
+ * Resolves child objects to their parent groups before adding.
395
+ * In additive mode (shift), toggles object presence in existing group.
396
+ * In non-additive mode, creates a new selection group with the object.
397
+ * @param object - The object to add to selection (may be a child of a group)
398
+ * @param shiftKey - When `true`, adds to/removes from existing selection group
399
+ */
400
+ addObjectToSelectionGroup(object, shiftKey = false) {
401
+ if (!object) {
402
+ return;
403
+ }
404
+ // Resolve child objects to their parent groups
405
+ const parentGroup = KritzelGroup.findParentGroup(this._core, object.id);
406
+ const objectToSelect = parentGroup || object;
407
+ // When the click resolves to a parent group, clear the child's own flag so
408
+ // it does not linger as selected and re-select the group on the next interaction.
409
+ if (parentGroup) {
410
+ object.isSelected = false;
411
+ }
412
+ const existingGroup = this._core.store.selectionGroup;
413
+ if (shiftKey && existingGroup) {
414
+ existingGroup.addOrRemove(objectToSelect);
415
+ this.removeDescendantsOfSelectedGroups(existingGroup);
416
+ if (existingGroup.objects.length === 0) {
417
+ this._core.removeSelectionGroup();
418
+ }
419
+ else {
420
+ this._core.rerender();
421
+ }
422
+ }
423
+ else {
424
+ const selectionGroup = KritzelSelectionGroup.create(this._core);
425
+ selectionGroup.addOrRemove(objectToSelect);
426
+ selectionGroup.isSelected = true;
427
+ this._core.addSelectionGroup(selectionGroup);
428
+ this._core.rerender();
429
+ }
430
+ }
431
+ /**
432
+ * Adds all currently selected objects to a selection group.
433
+ * Builds a parent group lookup map for efficient O(n) resolution of child objects.
434
+ * Deduplicates objects by ID to prevent duplicate entries from group resolution.
435
+ * In additive mode, adds to existing group; otherwise creates a new selection group.
436
+ * Captures unchanged snapshots and refreshes dimensions once at the end for O(n) performance.
437
+ * @param shiftKey - When `true`, adds objects to existing selection group
438
+ */
439
+ addSelectedObjectsToSelectionGroup(shiftKey = false) {
440
+ const selectedObjects = this._core.store.selectedObjects;
441
+ if (selectedObjects.length === 0) {
442
+ return;
443
+ }
444
+ // Build parent group lookup map ONCE - O(g * c) instead of O(n * g * c)
445
+ const allGroups = this._core.store.objects.filter(obj => obj.__class__ === 'KritzelGroup');
446
+ const childToParentMap = new Map();
447
+ for (const group of allGroups) {
448
+ for (const childId of group.childIds) {
449
+ childToParentMap.set(childId, group);
450
+ }
451
+ }
452
+ // Resolve child objects to their parent groups in a single pass - O(n)
453
+ const resolvedObjects = new Map();
454
+ for (const obj of selectedObjects) {
455
+ const parentGroup = childToParentMap.get(obj.id);
456
+ if (parentGroup) {
457
+ // The child was selected (e.g. via marquee preview) but selection is
458
+ // represented by its parent group. Clear the child's flag so it does
459
+ // not linger as selected and re-select the group on the next interaction.
460
+ obj.isSelected = false;
461
+ resolvedObjects.set(parentGroup.id, parentGroup);
462
+ }
463
+ else {
464
+ resolvedObjects.set(obj.id, obj);
465
+ }
466
+ }
467
+ const unrolledObjects = Array.from(resolvedObjects.values());
468
+ const filteredObjects = this.filterDescendantsOfSelectedGroups(unrolledObjects);
469
+ const existingGroup = this._core.store.selectionGroup;
470
+ if (shiftKey && existingGroup) {
471
+ existingGroup.addObjects(filteredObjects);
472
+ this.removeDescendantsOfSelectedGroups(existingGroup);
473
+ this._previouslySelectedObjects.clear();
474
+ this._core.rerender();
475
+ }
476
+ else {
477
+ // Create selection group and set all IDs at once (no per-item refresh)
478
+ const selectionGroup = KritzelSelectionGroup.create(this._core);
479
+ selectionGroup.objectIds = filteredObjects.map(o => o.id);
480
+ filteredObjects.forEach(obj => {
481
+ obj.isSelected = true;
482
+ });
483
+ // Only refresh dimensions and capture snapshots ONCE at the end - O(n) instead of O(n²)
484
+ if (selectionGroup.length === 1) {
485
+ selectionGroup.rotation = selectionGroup.objects[0].rotation;
486
+ }
487
+ selectionGroup.captureUnchangedSnapshots();
488
+ selectionGroup.refreshObjectDimensions();
489
+ selectionGroup.isSelected = true;
490
+ this._core.addSelectionGroup(selectionGroup);
491
+ this._previouslySelectedObjects.clear();
492
+ this._core.rerender();
493
+ }
494
+ }
495
+ /**
496
+ * Removes objects that are descendants of any KritzelGroup also present in the list.
497
+ * Prevents an ancestor group and one of its descendants from coexisting in a selection,
498
+ * which would otherwise cause the descendant to be transformed twice (once via the
499
+ * ancestor group, and once directly).
500
+ * @param objects - The candidate objects for the selection.
501
+ * @returns A new array with descendants of selected groups removed.
502
+ */
503
+ filterDescendantsOfSelectedGroups(objects) {
504
+ const descendantIds = new Set();
505
+ for (const obj of objects) {
506
+ if (obj instanceof KritzelGroup) {
507
+ KritzelGroup.collectDescendantIds(obj, descendantIds);
508
+ }
509
+ }
510
+ if (descendantIds.size === 0) {
511
+ return objects;
512
+ }
513
+ return objects.filter(o => !descendantIds.has(o.id));
514
+ }
515
+ /**
516
+ * Mutates an existing selection group to remove any descendants of groups
517
+ * already present in it. See {@link filterDescendantsOfSelectedGroups}.
518
+ * @param selectionGroup - The selection group to clean up in place.
519
+ */
520
+ removeDescendantsOfSelectedGroups(selectionGroup) {
521
+ const filtered = this.filterDescendantsOfSelectedGroups(selectionGroup.objects);
522
+ if (filtered.length !== selectionGroup.objects.length) {
523
+ selectionGroup.objectIds = filtered.map(o => o.id);
524
+ }
525
+ }
526
+ }