@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,794 @@
1
+ import { KritzelMouseButton } from "../../enums/event-button.enum";
2
+ import { KritzelSelectionBox } from "../objects/selection-box.class";
3
+ import { debounce } from "lodash";
4
+ /**
5
+ * Manages viewport transformations including panning, zooming, and centering.
6
+ * Handles mouse, touch, and wheel events for viewport manipulation.
7
+ * Supports viewport boundaries and animated transitions.
8
+ */
9
+ export class KritzelViewport {
10
+ /** Reference to the KritzelCore instance */
11
+ _core;
12
+ /** Debounced function to persist viewport state to workspace */
13
+ _debounceUpdate;
14
+ /** Debounced function to end scaling state after pinch gestures */
15
+ _debounceEndScaling;
16
+ /** Animation frame ID for cancellable viewport animations */
17
+ _animationFrameId = null;
18
+ /** Initial distance between two touch points for pinch-to-zoom */
19
+ initialTouchDistance = 0;
20
+ /** Starting X position for pan/zoom gestures */
21
+ startX = 0;
22
+ /** Starting Y position for pan/zoom gestures */
23
+ startY = 0;
24
+ /** Minimum movement distance (in screen pixels) before broadcasting touch cursor position */
25
+ static TOUCH_CURSOR_BROADCAST_THRESHOLD = 5;
26
+ /** Screen X position where the current touch interaction started */
27
+ _touchStartScreenX = 0;
28
+ /** Screen Y position where the current touch interaction started */
29
+ _touchStartScreenY = 0;
30
+ /** Whether the touch movement threshold has been exceeded for cursor broadcasting */
31
+ _touchCursorBroadcastActive = false;
32
+ /**
33
+ * Creates a new KritzelViewport instance and initializes viewport state.
34
+ * Sets up debounced handlers for viewport updates and scaling end events.
35
+ * @param core - The KritzelCore instance to manage viewport for
36
+ * @param host - The HTML element hosting the canvas
37
+ */
38
+ constructor(core, host) {
39
+ this._core = core;
40
+ this._core.store.state.host = host;
41
+ this._core.store.state.viewportWidth = host.clientWidth;
42
+ this._core.store.state.viewportHeight = host.clientHeight;
43
+ this._core.store.state.startX = 0;
44
+ this._core.store.state.startY = 0;
45
+ this._core.store.state.translateX = 0;
46
+ this._core.store.state.translateY = 0;
47
+ this._debounceUpdate = debounce(() => {
48
+ this._core.updateWorkspaceViewport(this._core.store.state.translateX, this._core.store.state.translateY, this._core.store.state.scale);
49
+ }, 300);
50
+ this._debounceEndScaling = debounce(() => {
51
+ this._core.store.state.isScaling = false;
52
+ this._core.rerender();
53
+ }, 100);
54
+ }
55
+ /**
56
+ * Cancels any pending debounced viewport updates.
57
+ * Should be called before switching workspaces to prevent the old workspace's
58
+ * viewport state from being saved to the new workspace.
59
+ */
60
+ cancelPendingUpdates() {
61
+ this._debounceUpdate.cancel();
62
+ }
63
+ /**
64
+ * Clamps translateX and translateY to respect viewport boundaries.
65
+ * Boundaries are defined in world coordinates and converted to screen-space translate limits.
66
+ * @param translateX - The proposed translateX value
67
+ * @param translateY - The proposed translateY value
68
+ * @returns Clamped translate values
69
+ */
70
+ clampTranslate(translateX, translateY) {
71
+ const state = this._core.store.state;
72
+ const { scale, viewportWidth, viewportHeight } = state;
73
+ const { viewportBoundaryLeft, viewportBoundaryRight, viewportBoundaryTop, viewportBoundaryBottom } = state;
74
+ // Convert world boundaries to screen-space translate limits
75
+ // translateX = viewportX - worldX * scale, so:
76
+ // - To show boundaryLeft at left edge (viewportX=0): translateX = -boundaryLeft * scale
77
+ // - To show boundaryRight at right edge (viewportX=viewportWidth): translateX = viewportWidth - boundaryRight * scale
78
+ const translateXMax = -viewportBoundaryLeft * scale;
79
+ const translateXMin = viewportWidth - viewportBoundaryRight * scale;
80
+ const translateYMax = -viewportBoundaryTop * scale;
81
+ const translateYMin = viewportHeight - viewportBoundaryBottom * scale;
82
+ // Only clamp if boundaries are finite
83
+ let clampedX = translateX;
84
+ let clampedY = translateY;
85
+ if (Number.isFinite(translateXMin) && Number.isFinite(translateXMax)) {
86
+ clampedX = Math.max(translateXMin, Math.min(translateXMax, translateX));
87
+ }
88
+ else if (Number.isFinite(translateXMin)) {
89
+ clampedX = Math.max(translateXMin, translateX);
90
+ }
91
+ else if (Number.isFinite(translateXMax)) {
92
+ clampedX = Math.min(translateXMax, translateX);
93
+ }
94
+ if (Number.isFinite(translateYMin) && Number.isFinite(translateYMax)) {
95
+ clampedY = Math.max(translateYMin, Math.min(translateYMax, translateY));
96
+ }
97
+ else if (Number.isFinite(translateYMin)) {
98
+ clampedY = Math.max(translateYMin, translateY);
99
+ }
100
+ else if (Number.isFinite(translateYMax)) {
101
+ clampedY = Math.min(translateYMax, translateY);
102
+ }
103
+ return { translateX: clampedX, translateY: clampedY };
104
+ }
105
+ /**
106
+ * Calculates the effective minimum scale considering both the configured scaleMin
107
+ * and the minimum scale required to keep the viewport within boundaries.
108
+ * This prevents users from zooming out far enough to see past the boundaries.
109
+ * @returns The effective minimum scale value
110
+ */
111
+ getEffectiveMinScale() {
112
+ const state = this._core.store.state;
113
+ const { viewportWidth, viewportHeight, scaleMin } = state;
114
+ const { viewportBoundaryLeft, viewportBoundaryRight, viewportBoundaryTop, viewportBoundaryBottom } = state;
115
+ const boundaryWidth = viewportBoundaryRight - viewportBoundaryLeft;
116
+ const boundaryHeight = viewportBoundaryBottom - viewportBoundaryTop;
117
+ // If boundaries are infinite, just use the configured scaleMin
118
+ if (!Number.isFinite(boundaryWidth) || !Number.isFinite(boundaryHeight)) {
119
+ return scaleMin;
120
+ }
121
+ // Calculate minimum scale to fit viewport within boundaries
122
+ // scale >= viewportWidth / boundaryWidth and scale >= viewportHeight / boundaryHeight
123
+ const minScaleX = boundaryWidth > 0 ? viewportWidth / boundaryWidth : scaleMin;
124
+ const minScaleY = boundaryHeight > 0 ? viewportHeight / boundaryHeight : scaleMin;
125
+ const minScaleForBoundaries = Math.max(minScaleX, minScaleY);
126
+ return Math.max(scaleMin, minScaleForBoundaries);
127
+ }
128
+ /**
129
+ * Handles viewport resize events.
130
+ * Updates the stored viewport dimensions and triggers a rerender.
131
+ */
132
+ handleResize() {
133
+ this._core.store.state.viewportWidth = this._core.store.host.clientWidth;
134
+ this._core.store.state.viewportHeight = this._core.store.host.clientHeight;
135
+ this._core.store.state.hasViewportChanged = true;
136
+ this._core.rerender();
137
+ }
138
+ /**
139
+ * Handles pointer down events for initiating viewport interactions.
140
+ * For mouse: Right-click starts panning mode.
141
+ * For touch/pen: Two-finger touch initiates pinch-to-zoom.
142
+ * Cancels any ongoing viewport animation when user starts interacting.
143
+ * @param event - The pointer down event
144
+ */
145
+ handlePointerDown(event) {
146
+ // Cancel any ongoing viewport animation when user starts interacting
147
+ this.cancelViewportAnimation();
148
+ if (event.pointerType === 'mouse') {
149
+ const adjustedClientX = event.clientX - this._core.store.offsetX;
150
+ const adjustedClientY = event.clientY - this._core.store.offsetY;
151
+ if (event.button === KritzelMouseButton.Right) {
152
+ this._core.store.state.isPanning = true;
153
+ this._core.store.state.startX = adjustedClientX;
154
+ this._core.store.state.startY = adjustedClientY;
155
+ }
156
+ }
157
+ if (event.pointerType === 'touch' || event.pointerType === 'pen') {
158
+ const activePointers = Array.from(this._core.store.state.pointers.values());
159
+ if (activePointers.length === 1) {
160
+ this._touchStartScreenX = event.clientX;
161
+ this._touchStartScreenY = event.clientY;
162
+ this._touchCursorBroadcastActive = false;
163
+ }
164
+ if (activePointers.length === 2) {
165
+ this._core.store.objects?.clearCursorPosition();
166
+ const currentPath = this._core.store.currentPath;
167
+ if (currentPath) {
168
+ this._core.store.objects.remove(obj => obj.id === currentPath.id);
169
+ }
170
+ // Clear selection box when scaling starts to prevent it from remaining on screen
171
+ if (this._core.store.state.isSelecting) {
172
+ this._core.store.state.isSelecting = false;
173
+ this._core.store.objects.remove(obj => obj instanceof KritzelSelectionBox);
174
+ this._core.store.setSelectionBox(null);
175
+ // Clear selection preview state from objects - use selectedObjects for efficiency
176
+ this._core.store.selectedObjects.forEach(object => {
177
+ object.isSelected = false;
178
+ });
179
+ }
180
+ this._core.store.state.isScaling = true;
181
+ const firstTouchX = activePointers[0].clientX - this._core.store.offsetX;
182
+ const firstTouchY = activePointers[0].clientY - this._core.store.offsetY;
183
+ const secondTouchX = activePointers[1].clientX - this._core.store.offsetX;
184
+ const secondTouchY = activePointers[1].clientY - this._core.store.offsetY;
185
+ this.initialTouchDistance = Math.sqrt(Math.pow(firstTouchX - secondTouchX, 2) + Math.pow(firstTouchY - secondTouchY, 2));
186
+ this.startX = (firstTouchX + secondTouchX) / 2;
187
+ this.startY = (firstTouchY + secondTouchY) / 2;
188
+ this._core.rerender();
189
+ }
190
+ }
191
+ }
192
+ /**
193
+ * Handles pointer move events for viewport panning and zooming.
194
+ * For mouse: Updates pointer position and handles right-click pan dragging.
195
+ * For touch/pen: Handles two-finger pinch-to-zoom with translation.
196
+ * @param event - The pointer move event
197
+ */
198
+ handlePointerMove(event) {
199
+ if (event.pointerType === 'mouse') {
200
+ const hostRect = this._core.store.host.getBoundingClientRect();
201
+ const xRelativeToHost = event.clientX - hostRect.left;
202
+ const yRelativeToHost = event.clientY - hostRect.top;
203
+ this._core.store.state.pointerX = (xRelativeToHost - this._core.store.state.translateX) / this._core.store.state.scale;
204
+ this._core.store.state.pointerY = (yRelativeToHost - this._core.store.state.translateY) / this._core.store.state.scale;
205
+ this._core.store.objects?.updateCursorPosition(this._core.store.state.pointerX, this._core.store.state.pointerY);
206
+ if (this._core.store.state.isPanning) {
207
+ const dx = xRelativeToHost - this._core.store.state.startX;
208
+ const dy = yRelativeToHost - this._core.store.state.startY;
209
+ const newTranslateX = this._core.store.state.translateX + dx;
210
+ const newTranslateY = this._core.store.state.translateY + dy;
211
+ const clamped = this.clampTranslate(newTranslateX, newTranslateY);
212
+ this._core.store.state.translateX = clamped.translateX;
213
+ this._core.store.state.translateY = clamped.translateY;
214
+ this._core.store.state.startX = xRelativeToHost;
215
+ this._core.store.state.startY = yRelativeToHost;
216
+ this._core.store.state.hasViewportChanged = true;
217
+ this._core.store.state.skipContextMenu = true;
218
+ this._core.rerender();
219
+ this._debounceUpdate();
220
+ }
221
+ }
222
+ if (event.pointerType === 'touch' || event.pointerType === 'pen') {
223
+ const hostRect = this._core.store.host.getBoundingClientRect();
224
+ const xRelativeToHost = event.clientX - hostRect.left;
225
+ const yRelativeToHost = event.clientY - hostRect.top;
226
+ const activePointers = Array.from(this._core.store.state.pointers.values());
227
+ if (this._core.store.state.isScaling || activePointers.length > 1) {
228
+ this._core.store.objects?.clearCursorPosition();
229
+ }
230
+ else {
231
+ this._core.store.state.pointerX = (xRelativeToHost - this._core.store.state.translateX) / this._core.store.state.scale;
232
+ this._core.store.state.pointerY = (yRelativeToHost - this._core.store.state.translateY) / this._core.store.state.scale;
233
+ if (!this._touchCursorBroadcastActive) {
234
+ const dx = event.clientX - this._touchStartScreenX;
235
+ const dy = event.clientY - this._touchStartScreenY;
236
+ if (Math.sqrt(dx * dx + dy * dy) >= KritzelViewport.TOUCH_CURSOR_BROADCAST_THRESHOLD) {
237
+ this._touchCursorBroadcastActive = true;
238
+ }
239
+ }
240
+ if (this._touchCursorBroadcastActive) {
241
+ this._core.store.objects?.updateCursorPosition(this._core.store.state.pointerX, this._core.store.state.pointerY);
242
+ }
243
+ }
244
+ if (activePointers.length === 2) {
245
+ const firstTouchX = activePointers[0].clientX - this._core.store.offsetX;
246
+ const firstTouchY = activePointers[0].clientY - this._core.store.offsetY;
247
+ const secondTouchX = activePointers[1].clientX - this._core.store.offsetX;
248
+ const secondTouchY = activePointers[1].clientY - this._core.store.offsetY;
249
+ const currentTouchDistance = Math.sqrt(Math.pow(firstTouchX - secondTouchX, 2) + Math.pow(firstTouchY - secondTouchY, 2));
250
+ const midpointX = (firstTouchX + secondTouchX) / 2;
251
+ const midpointY = (firstTouchY + secondTouchY) / 2;
252
+ const scaleRatio = currentTouchDistance / this.initialTouchDistance;
253
+ const newScale = this._core.store.state.scale * scaleRatio;
254
+ const effectiveMinScale = this.getEffectiveMinScale();
255
+ if (newScale > this._core.store.state.scaleMax || newScale < effectiveMinScale) {
256
+ const newTranslateX = this._core.store.state.translateX + midpointX - this.startX;
257
+ const newTranslateY = this._core.store.state.translateY + midpointY - this.startY;
258
+ const clamped = this.clampTranslate(newTranslateX, newTranslateY);
259
+ this._core.store.state.translateX = clamped.translateX;
260
+ this._core.store.state.translateY = clamped.translateY;
261
+ }
262
+ else {
263
+ const translateXAdjustment = (midpointX - this._core.store.state.translateX) * (scaleRatio - 1);
264
+ const translateYAdjustment = (midpointY - this._core.store.state.translateY) * (scaleRatio - 1);
265
+ const newTranslateX = this._core.store.state.translateX + midpointX - this.startX - translateXAdjustment;
266
+ const newTranslateY = this._core.store.state.translateY + midpointY - this.startY - translateYAdjustment;
267
+ this._core.store.state.scale = newScale;
268
+ const clamped = this.clampTranslate(newTranslateX, newTranslateY);
269
+ this._core.store.state.translateX = clamped.translateX;
270
+ this._core.store.state.translateY = clamped.translateY;
271
+ this.initialTouchDistance = currentTouchDistance;
272
+ }
273
+ this.startX = midpointX;
274
+ this.startY = midpointY;
275
+ this._core.store.state.hasViewportChanged = true;
276
+ this._core.rerender();
277
+ this._debounceUpdate();
278
+ }
279
+ }
280
+ }
281
+ /**
282
+ * Handles pointer up events to end viewport interactions.
283
+ * For mouse: Ends panning mode on right-click release.
284
+ * For touch/pen: Triggers debounced end of scaling when all fingers are lifted.
285
+ * @param event - The pointer up event
286
+ */
287
+ handlePointerUp(event) {
288
+ if (event.pointerType === 'mouse') {
289
+ if (this._core.store.state.isPanning) {
290
+ this._core.store.state.isPanning = false;
291
+ this._core.rerender();
292
+ }
293
+ }
294
+ if (event.pointerType === 'touch' || event.pointerType === 'pen') {
295
+ this._touchCursorBroadcastActive = false;
296
+ if (this._core.store.state.pointers.size === 0) {
297
+ this._debounceEndScaling();
298
+ }
299
+ }
300
+ }
301
+ /**
302
+ * Handles mouse wheel events for panning and zooming.
303
+ * Ctrl+wheel triggers zoom; regular wheel triggers pan.
304
+ * Cancels any ongoing viewport animation when user scrolls.
305
+ * @param event - The wheel event
306
+ */
307
+ handleWheel(event) {
308
+ // Cancel any ongoing viewport animation when user scrolls
309
+ this.cancelViewportAnimation();
310
+ if (event.ctrlKey) {
311
+ this.handleZoom(event);
312
+ }
313
+ else {
314
+ this.handlePan(event);
315
+ }
316
+ }
317
+ /**
318
+ * Returns the current viewport state.
319
+ * @returns The current viewport position, scale, and dimensions.
320
+ */
321
+ getViewport() {
322
+ const state = this._core.store.state;
323
+ return {
324
+ translateX: state.translateX,
325
+ translateY: state.translateY,
326
+ scale: state.scale,
327
+ width: state.viewportWidth,
328
+ height: state.viewportHeight,
329
+ };
330
+ }
331
+ /**
332
+ * Converts screen-relative pixel coordinates to world coordinates.
333
+ * @param x - X position in screen pixels (relative to the host element)
334
+ * @param y - Y position in screen pixels (relative to the host element)
335
+ * @returns World coordinates { x, y }
336
+ */
337
+ screenToWorld(x, y) {
338
+ const state = this._core.store.state;
339
+ return {
340
+ x: (x - state.translateX) / state.scale,
341
+ y: (y - state.translateY) / state.scale,
342
+ };
343
+ }
344
+ /**
345
+ * Converts world coordinates to screen-relative pixel coordinates.
346
+ * @param x - X position in world coordinates
347
+ * @param y - Y position in world coordinates
348
+ * @returns Screen coordinates { x, y } relative to the host element
349
+ */
350
+ worldToScreen(x, y) {
351
+ const state = this._core.store.state;
352
+ return {
353
+ x: x * state.scale + state.translateX,
354
+ y: y * state.scale + state.translateY,
355
+ };
356
+ }
357
+ /**
358
+ * Animates the viewport to center on the given world coordinates at the specified scale.
359
+ * Respects scale limits and viewport boundaries.
360
+ * @param x - X position in world coordinates to center on
361
+ * @param y - Y position in world coordinates to center on
362
+ * @param scale - Target scale level
363
+ */
364
+ setViewport(x, y, scale) {
365
+ const state = this._core.store.state;
366
+ const clampedScale = Math.min(state.scaleMax, Math.max(this.getEffectiveMinScale(), scale));
367
+ const targetTranslateX = state.viewportWidth / 2 - x * clampedScale;
368
+ const targetTranslateY = state.viewportHeight / 2 - y * clampedScale;
369
+ this.animateViewportTo(targetTranslateX, targetTranslateY, clampedScale);
370
+ }
371
+ /**
372
+ * Animates the viewport to center on the given world coordinates without changing the scale.
373
+ * @param x - X position in world coordinates to center on
374
+ * @param y - Y position in world coordinates to center on
375
+ */
376
+ panTo(x, y) {
377
+ this.setViewport(x, y, this._core.store.state.scale);
378
+ }
379
+ /**
380
+ * Animates the viewport to the given scale level.
381
+ * Optionally centers on a world point; if omitted, zooms around the viewport center.
382
+ * @param scale - Target scale level
383
+ * @param worldX - Optional X position in world coordinates to center on
384
+ * @param worldY - Optional Y position in world coordinates to center on
385
+ */
386
+ zoomTo(scale, worldX, worldY) {
387
+ const state = this._core.store.state;
388
+ if (worldX !== undefined && worldY !== undefined) {
389
+ this.setViewport(worldX, worldY, scale);
390
+ }
391
+ else {
392
+ // Zoom around viewport center
393
+ const centerWorldX = (state.viewportWidth / 2 - state.translateX) / state.scale;
394
+ const centerWorldY = (state.viewportHeight / 2 - state.translateY) / state.scale;
395
+ this.setViewport(centerWorldX, centerWorldY, scale);
396
+ }
397
+ }
398
+ /**
399
+ * Zooms in by a fixed step, centered on the current viewport center,
400
+ * with a smooth animation. The resulting scale is clamped to the allowed range.
401
+ * @param factor - Multiplicative zoom-in step
402
+ * @param duration - Animation duration in milliseconds
403
+ */
404
+ zoomIn(factor, duration) {
405
+ this.animateZoomAroundViewportCenter(factor, duration);
406
+ }
407
+ /**
408
+ * Zooms out by a fixed step, centered on the current viewport center,
409
+ * with a smooth animation. The resulting scale is clamped to the allowed range.
410
+ * @param factor - Multiplicative zoom-out step
411
+ * @param duration - Animation duration in milliseconds
412
+ */
413
+ zoomOut(factor, duration) {
414
+ this.animateZoomAroundViewportCenter(1 / factor, duration);
415
+ }
416
+ /**
417
+ * Smoothly zooms around the viewport center, keeping the center world point
418
+ * fixed for the entire animation. The translation is recomputed from the fixed
419
+ * center point on every frame (rather than interpolated independently), which
420
+ * guarantees the center never drifts and the viewport does not jump.
421
+ * The resulting scale is clamped to the allowed range and the translation is
422
+ * clamped to the viewport boundaries.
423
+ * @param scaleFactor - Multiplicative scale factor (>1 zooms in, <1 zooms out)
424
+ * @param duration - Animation duration in milliseconds
425
+ */
426
+ animateZoomAroundViewportCenter(scaleFactor, duration) {
427
+ this.cancelViewportAnimation();
428
+ const state = this._core.store.state;
429
+ const startScale = state.scale;
430
+ const effectiveMinScale = this.getEffectiveMinScale();
431
+ const targetScale = Math.min(state.scaleMax, Math.max(effectiveMinScale, startScale * scaleFactor));
432
+ // The world point currently under the viewport center stays fixed.
433
+ const centerScreenX = state.viewportWidth / 2;
434
+ const centerScreenY = state.viewportHeight / 2;
435
+ const centerWorldX = (centerScreenX - state.translateX) / startScale;
436
+ const centerWorldY = (centerScreenY - state.translateY) / startScale;
437
+ if (targetScale === startScale || duration <= 0) {
438
+ this.applyZoomAroundCenter(targetScale, centerScreenX, centerScreenY, centerWorldX, centerWorldY);
439
+ state.isScaling = true;
440
+ this._core.rerender();
441
+ this._debounceUpdate();
442
+ this._debounceEndScaling();
443
+ return;
444
+ }
445
+ const startTime = performance.now();
446
+ state.isScaling = true;
447
+ const animate = (currentTime) => {
448
+ const elapsed = currentTime - startTime;
449
+ const progress = Math.min(elapsed / duration, 1);
450
+ // easeInOutCubic for a smooth ramp in and out
451
+ const eased = progress < 0.5 ? 4 * progress * progress * progress : 1 - Math.pow(-2 * progress + 2, 3) / 2;
452
+ const frameScale = startScale + (targetScale - startScale) * eased;
453
+ this.applyZoomAroundCenter(frameScale, centerScreenX, centerScreenY, centerWorldX, centerWorldY);
454
+ this._core.rerender();
455
+ if (progress < 1) {
456
+ this._animationFrameId = requestAnimationFrame(animate);
457
+ }
458
+ else {
459
+ this._animationFrameId = null;
460
+ state.isScaling = false;
461
+ this._core.rerender();
462
+ this._debounceUpdate();
463
+ }
464
+ };
465
+ this._animationFrameId = requestAnimationFrame(animate);
466
+ }
467
+ /**
468
+ * Applies a single zoom step that keeps the given world point anchored to the
469
+ * given screen point at the specified scale, clamped to the viewport boundaries.
470
+ */
471
+ applyZoomAroundCenter(scale, screenX, screenY, worldX, worldY) {
472
+ const state = this._core.store.state;
473
+ state.scale = scale;
474
+ const clamped = this.clampTranslate(screenX - worldX * scale, screenY - worldY * scale);
475
+ state.translateX = clamped.translateX;
476
+ state.translateY = clamped.translateY;
477
+ state.hasViewportChanged = true;
478
+ }
479
+ /**
480
+ * Centers a given object in the viewport without changing the scale.
481
+ * The object's center point will be positioned at the viewport center.
482
+ * Respects viewport boundary constraints.
483
+ * @param object - The object to center in the viewport
484
+ */
485
+ centerInViewport(object) {
486
+ const { scale, viewportWidth, viewportHeight } = this._core.store.state;
487
+ const { x, y, width, height } = object.rotatedBoundingBox;
488
+ const objectCenterX = x + width / 2;
489
+ const objectCenterY = y + height / 2;
490
+ const newTranslateX = viewportWidth / 2 - objectCenterX * scale;
491
+ const newTranslateY = viewportHeight / 2 - objectCenterY * scale;
492
+ const clamped = this.clampTranslate(newTranslateX, newTranslateY);
493
+ this._core.store.state.translateX = clamped.translateX;
494
+ this._core.store.state.translateY = clamped.translateY;
495
+ this._core.store.state.hasViewportChanged = true;
496
+ this._core.rerender();
497
+ this._debounceUpdate();
498
+ }
499
+ /**
500
+ * Centers and scales the viewport to fit a given object with 10% padding.
501
+ * Calculates the optimal scale to show the entire object while respecting
502
+ * scale limits and viewport boundaries.
503
+ * @param object - The object to fit in the viewport
504
+ */
505
+ centerFitInViewport(object) {
506
+ const scaleX = this._core.store.state.viewportWidth / (object.rotatedBoundingBox.width * 1.1);
507
+ const scaleY = this._core.store.state.viewportHeight / (object.rotatedBoundingBox.height * 1.1);
508
+ const newScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
509
+ const clampedScale = Math.max(newScale, this.getEffectiveMinScale());
510
+ this._core.store.state.scale = clampedScale;
511
+ this.centerInViewport(object);
512
+ }
513
+ /**
514
+ * Centers the viewport on a given object and zooms out only if the object
515
+ * does not fit at the current scale. Never zooms in — if the object already
516
+ * fits, the scale is left unchanged.
517
+ * @param object - The object to fit in the viewport
518
+ */
519
+ centerFitInViewportIfNeeded(object) {
520
+ const scaleX = this._core.store.state.viewportWidth / (object.rotatedBoundingBox.width * 1.1);
521
+ const scaleY = this._core.store.state.viewportHeight / (object.rotatedBoundingBox.height * 1.1);
522
+ const newScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
523
+ const clampedScale = Math.max(newScale, this.getEffectiveMinScale());
524
+ if (clampedScale < this._core.store.state.scale) {
525
+ this._core.store.state.scale = clampedScale;
526
+ }
527
+ this.centerInViewport(object);
528
+ }
529
+ /**
530
+ * Zooms out and centers the viewport on a given object only if the object
531
+ * does not fit at the current scale. If the object already fits, the
532
+ * viewport is left completely unchanged.
533
+ * @param object - The object to check and fit in the viewport
534
+ */
535
+ fitInViewportIfNeeded(object) {
536
+ const scaleX = this._core.store.state.viewportWidth / (object.rotatedBoundingBox.width * 1.1);
537
+ const scaleY = this._core.store.state.viewportHeight / (object.rotatedBoundingBox.height * 1.1);
538
+ const newScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
539
+ const clampedScale = Math.max(newScale, this.getEffectiveMinScale());
540
+ if (clampedScale < this._core.store.state.scale) {
541
+ this._core.store.state.scale = clampedScale;
542
+ this.centerInViewport(object);
543
+ }
544
+ }
545
+ /**
546
+ * Smoothly brings a given object into view if it is not fully visible within
547
+ * the current viewport. Zooms out only if the object is too large to fit at
548
+ * the current scale; never zooms in. If the object is already fully visible,
549
+ * the viewport is left unchanged.
550
+ * @param object - The object to bring into view
551
+ */
552
+ bringIntoViewIfNeeded(object) {
553
+ const { scale, translateX, translateY, viewportWidth, viewportHeight } = this._core.store.state;
554
+ const bounds = object.rotatedBoundingBox;
555
+ // Compute the object's bounding box in screen-space coordinates
556
+ const screenLeft = bounds.x * scale + translateX;
557
+ const screenTop = bounds.y * scale + translateY;
558
+ const screenRight = (bounds.x + bounds.width) * scale + translateX;
559
+ const screenBottom = (bounds.y + bounds.height) * scale + translateY;
560
+ // If fully visible, no adjustment needed
561
+ if (screenLeft >= 0 && screenTop >= 0 && screenRight <= viewportWidth && screenBottom <= viewportHeight) {
562
+ return;
563
+ }
564
+ // Calculate the scale needed to fit the object with padding; never zoom in
565
+ const scaleX = viewportWidth / (bounds.width * 1.1);
566
+ const scaleY = viewportHeight / (bounds.height * 1.1);
567
+ const fitScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
568
+ const clampedFitScale = Math.max(fitScale, this.getEffectiveMinScale());
569
+ const targetScale = Math.min(clampedFitScale, scale);
570
+ // Animate to center the object at the target scale
571
+ const objectCenterX = bounds.x + bounds.width / 2;
572
+ const objectCenterY = bounds.y + bounds.height / 2;
573
+ const targetTranslateX = viewportWidth / 2 - objectCenterX * targetScale;
574
+ const targetTranslateY = viewportHeight / 2 - objectCenterY * targetScale;
575
+ const clamped = this.clampTranslate(targetTranslateX, targetTranslateY);
576
+ this.animateViewportTo(clamped.translateX, clamped.translateY, targetScale);
577
+ }
578
+ /**
579
+ * Handles zoom operations triggered by wheel events with Ctrl key.
580
+ * Zooms around the cursor position, respecting scale limits.
581
+ * Also handles horizontal pan from trackpad pinch+drag gestures.
582
+ * @param event - The wheel event with Ctrl modifier
583
+ */
584
+ handleZoom(event) {
585
+ this._core.store.state.isScaling = true;
586
+ const rect = this._core.store.host.getBoundingClientRect();
587
+ const xRelativeToHost = event.clientX - rect.left;
588
+ const yRelativeToHost = event.clientY - rect.top;
589
+ this._core.store.state.pointerX = (xRelativeToHost - this._core.store.state.translateX) / this._core.store.state.scale;
590
+ this._core.store.state.pointerY = (yRelativeToHost - this._core.store.state.translateY) / this._core.store.state.scale;
591
+ this._core.store.objects?.updateCursorPosition(this._core.store.state.pointerX, this._core.store.state.pointerY);
592
+ const rawScaleFactor = 1 + (event.deltaY * -0.012);
593
+ const scaleFactor = Math.max(0.8, Math.min(1.2, rawScaleFactor));
594
+ const effectiveMinScale = this.getEffectiveMinScale();
595
+ const newScale = Math.min(this._core.store.state.scaleMax, Math.max(effectiveMinScale, this._core.store.state.scale * scaleFactor));
596
+ const scaleRatio = newScale / this._core.store.state.scale;
597
+ const translateXAdjustment = (xRelativeToHost - this._core.store.state.translateX) * (scaleRatio - 1);
598
+ const translateYAdjustment = (yRelativeToHost - this._core.store.state.translateY) * (scaleRatio - 1);
599
+ this._core.store.state.scale = newScale;
600
+ let newTranslateX = this._core.store.state.translateX - translateXAdjustment;
601
+ let newTranslateY = this._core.store.state.translateY - translateYAdjustment;
602
+ // Apply horizontal pan from trackpad pinch+drag gestures
603
+ if (event.deltaX !== 0) {
604
+ newTranslateX -= event.deltaX * 0.8;
605
+ }
606
+ const clamped = this.clampTranslate(newTranslateX, newTranslateY);
607
+ this._core.store.state.translateX = clamped.translateX;
608
+ this._core.store.state.translateY = clamped.translateY;
609
+ this._core.store.state.hasViewportChanged = true;
610
+ this._core.rerender();
611
+ this._debounceUpdate();
612
+ this._debounceEndScaling();
613
+ }
614
+ /**
615
+ * Handles pan operations triggered by wheel events (without Ctrl).
616
+ * Applies a pan speed multiplier and clamps to viewport boundaries.
617
+ * @param event - The wheel event without Ctrl modifier
618
+ */
619
+ handlePan(event) {
620
+ const panSpeed = 0.8;
621
+ const newTranslateX = this._core.store.state.translateX - event.deltaX * panSpeed;
622
+ const newTranslateY = this._core.store.state.translateY - event.deltaY * panSpeed;
623
+ const clamped = this.clampTranslate(newTranslateX, newTranslateY);
624
+ this._core.store.state.translateX = clamped.translateX;
625
+ this._core.store.state.translateY = clamped.translateY;
626
+ this._core.store.state.hasViewportChanged = true;
627
+ this._core.rerender();
628
+ this._debounceUpdate();
629
+ }
630
+ /**
631
+ * Cancels any ongoing viewport animation.
632
+ * Called when user interacts with the canvas during animation.
633
+ */
634
+ cancelViewportAnimation() {
635
+ if (this._animationFrameId !== null) {
636
+ cancelAnimationFrame(this._animationFrameId);
637
+ this._animationFrameId = null;
638
+ }
639
+ }
640
+ /**
641
+ * Animates the viewport to the specified position and scale.
642
+ * @param targetTranslateX - Target X translation
643
+ * @param targetTranslateY - Target Y translation
644
+ * @param targetScale - Target scale
645
+ * @param duration - Animation duration in milliseconds (default 150)
646
+ */
647
+ animateViewportTo(targetTranslateX, targetTranslateY, targetScale, duration = 150) {
648
+ // Cancel any existing animation
649
+ this.cancelViewportAnimation();
650
+ if (duration <= 0) {
651
+ // Apply immediately; interpolating with duration 0 divides by zero
652
+ // (rAF timestamps can precede performance.now()), producing NaN state.
653
+ this._core.store.state.scale = targetScale;
654
+ const clamped = this.clampTranslate(targetTranslateX, targetTranslateY);
655
+ this._core.store.state.translateX = clamped.translateX;
656
+ this._core.store.state.translateY = clamped.translateY;
657
+ this._core.store.state.hasViewportChanged = true;
658
+ this._core.rerender();
659
+ this._debounceUpdate();
660
+ return;
661
+ }
662
+ const startTranslateX = this._core.store.state.translateX;
663
+ const startTranslateY = this._core.store.state.translateY;
664
+ const startScale = this._core.store.state.scale;
665
+ const startTime = performance.now();
666
+ this._core.store.state.isScaling = true;
667
+ const animate = (currentTime) => {
668
+ const elapsed = currentTime - startTime;
669
+ // Clamp to [0, 1]; rAF timestamps can precede startTime, yielding negative progress
670
+ const progress = Math.min(Math.max(elapsed, 0) / duration, 1);
671
+ // easeOutCubic easing function
672
+ const eased = 1 - Math.pow(1 - progress, 3);
673
+ this._core.store.state.scale = startScale + (targetScale - startScale) * eased;
674
+ const newTranslateX = startTranslateX + (targetTranslateX - startTranslateX) * eased;
675
+ const newTranslateY = startTranslateY + (targetTranslateY - startTranslateY) * eased;
676
+ const clamped = this.clampTranslate(newTranslateX, newTranslateY);
677
+ this._core.store.state.translateX = clamped.translateX;
678
+ this._core.store.state.translateY = clamped.translateY;
679
+ this._core.store.state.hasViewportChanged = true;
680
+ this._core.rerender();
681
+ if (progress < 1) {
682
+ this._animationFrameId = requestAnimationFrame(animate);
683
+ }
684
+ else {
685
+ this._animationFrameId = null;
686
+ this._core.store.state.isScaling = false;
687
+ this._core.rerender();
688
+ this._debounceUpdate();
689
+ }
690
+ };
691
+ this._animationFrameId = requestAnimationFrame(animate);
692
+ }
693
+ /**
694
+ * Centers and fits the nearest content objects in the viewport.
695
+ * Finds up to maxObjects closest objects to the current viewport center
696
+ * and animates the viewport to show them.
697
+ * @param maxObjects - Maximum number of objects to consider (default 20)
698
+ * @returns true if content was found and viewport is animating, false if no content exists
699
+ */
700
+ centerFitNearestContent(maxObjects = 20) {
701
+ const allObjects = this._core.store.allNonSelectionObjects;
702
+ if (allObjects.length === 0) {
703
+ return false;
704
+ }
705
+ // Calculate current viewport center in canvas coordinates
706
+ const viewportCenterX = -this._core.store.state.translateX / this._core.store.state.scale + this._core.store.state.viewportWidth / 2 / this._core.store.state.scale;
707
+ const viewportCenterY = -this._core.store.state.translateY / this._core.store.state.scale + this._core.store.state.viewportHeight / 2 / this._core.store.state.scale;
708
+ // Sort objects by distance from viewport center (using object center)
709
+ const objectsWithDistance = allObjects.map(obj => {
710
+ const bounds = obj.rotatedBoundingBox;
711
+ const objectCenterX = bounds.x + bounds.width / 2;
712
+ const objectCenterY = bounds.y + bounds.height / 2;
713
+ const distance = Math.sqrt(Math.pow(objectCenterX - viewportCenterX, 2) + Math.pow(objectCenterY - viewportCenterY, 2));
714
+ return { obj, distance };
715
+ });
716
+ objectsWithDistance.sort((a, b) => a.distance - b.distance);
717
+ // Take up to maxObjects closest objects
718
+ const nearestObjects = objectsWithDistance.slice(0, maxObjects).map(item => item.obj);
719
+ // Calculate combined bounds of nearest objects
720
+ let minX = Infinity;
721
+ let minY = Infinity;
722
+ let maxX = -Infinity;
723
+ let maxY = -Infinity;
724
+ for (const obj of nearestObjects) {
725
+ const bounds = obj.rotatedBoundingBox;
726
+ minX = Math.min(minX, bounds.x);
727
+ minY = Math.min(minY, bounds.y);
728
+ maxX = Math.max(maxX, bounds.x + bounds.width);
729
+ maxY = Math.max(maxY, bounds.y + bounds.height);
730
+ }
731
+ const contentWidth = maxX - minX;
732
+ const contentHeight = maxY - minY;
733
+ const contentCenterX = minX + contentWidth / 2;
734
+ const contentCenterY = minY + contentHeight / 2;
735
+ // Calculate scale to fit with 10% padding
736
+ const scaleX = this._core.store.state.viewportWidth / (contentWidth * 1.1);
737
+ const scaleY = this._core.store.state.viewportHeight / (contentHeight * 1.1);
738
+ const newScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
739
+ const clampedScale = Math.max(newScale, this.getEffectiveMinScale());
740
+ // Calculate target translation to center content
741
+ const targetTranslateX = this._core.store.state.viewportWidth / 2 - contentCenterX * clampedScale;
742
+ const targetTranslateY = this._core.store.state.viewportHeight / 2 - contentCenterY * clampedScale;
743
+ // Animate to the target position
744
+ this.animateViewportTo(targetTranslateX, targetTranslateY, clampedScale);
745
+ return true;
746
+ }
747
+ /**
748
+ * Centers and fits the provided objects in the viewport.
749
+ * Calculates the combined bounding box of the provided objects and optionally animates the viewport to show them.
750
+ * @param objects - Objects to include in the fit operation.
751
+ * @param animate - Whether to animate the viewport transition (default: true)
752
+ * @returns true if at least one object was provided and viewport was adjusted, false otherwise
753
+ */
754
+ centerFitObjects(objects, animate = true) {
755
+ if (objects.length === 0) {
756
+ return false;
757
+ }
758
+ let minX = Infinity;
759
+ let minY = Infinity;
760
+ let maxX = -Infinity;
761
+ let maxY = -Infinity;
762
+ for (const obj of objects) {
763
+ const bounds = obj.rotatedBoundingBox;
764
+ minX = Math.min(minX, bounds.x);
765
+ minY = Math.min(minY, bounds.y);
766
+ maxX = Math.max(maxX, bounds.x + bounds.width);
767
+ maxY = Math.max(maxY, bounds.y + bounds.height);
768
+ }
769
+ const contentWidth = maxX - minX;
770
+ const contentHeight = maxY - minY;
771
+ const contentCenterX = minX + contentWidth / 2;
772
+ const contentCenterY = minY + contentHeight / 2;
773
+ // Calculate scale to fit with 10% padding
774
+ const scaleX = this._core.store.state.viewportWidth / (contentWidth * 1.1);
775
+ const scaleY = this._core.store.state.viewportHeight / (contentHeight * 1.1);
776
+ const newScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
777
+ const clampedScale = Math.max(newScale, this.getEffectiveMinScale());
778
+ // Calculate target translation to center content
779
+ const targetTranslateX = this._core.store.state.viewportWidth / 2 - contentCenterX * clampedScale;
780
+ const targetTranslateY = this._core.store.state.viewportHeight / 2 - contentCenterY * clampedScale;
781
+ // Animate or set immediately based on the animate parameter
782
+ this.animateViewportTo(targetTranslateX, targetTranslateY, clampedScale, animate ? 150 : 0);
783
+ return true;
784
+ }
785
+ /**
786
+ * Centers and fits ALL objects in the viewport, including objects not currently rendered.
787
+ * Calculates the combined bounding box of all objects and optionally animates the viewport to show them.
788
+ * @param animate - Whether to animate the viewport transition (default: true)
789
+ * @returns true if objects were found and viewport was adjusted, false if no objects exist
790
+ */
791
+ centerFitAllObjects(animate = true) {
792
+ return this.centerFitObjects(this._core.store.allNonSelectionObjects, animate);
793
+ }
794
+ }