@kritzel/editor 0.4.31 → 0.4.32

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 (195) hide show
  1. package/LICENSE.md +50 -50
  2. package/README.md +5 -5
  3. package/dist/cjs/{index-B92VHWr4.js → index-q_Tg9KUw.js} +12 -0
  4. package/dist/cjs/kritzel-active-users_46.cjs.entry.js +981 -322
  5. package/dist/cjs/kritzel-brush-style.cjs.entry.js +2 -2
  6. package/dist/cjs/kritzel-editor.cjs.js +2 -2
  7. package/dist/cjs/loader.cjs.js +2 -2
  8. package/dist/collection/components/kritzel-active-users/kritzel-active-users.css +65 -65
  9. package/dist/collection/components/kritzel-avatar/kritzel-avatar.css +58 -58
  10. package/dist/collection/components/kritzel-avatar/kritzel-avatar.js +3 -3
  11. package/dist/collection/components/kritzel-back-to-content/kritzel-back-to-content.css +42 -42
  12. package/dist/collection/components/kritzel-back-to-content/kritzel-back-to-content.js +1 -1
  13. package/dist/collection/components/kritzel-brush-style/kritzel-brush-style.css +42 -42
  14. package/dist/collection/components/kritzel-brush-style/kritzel-brush-style.js +3 -3
  15. package/dist/collection/components/kritzel-button/kritzel-button.css +73 -73
  16. package/dist/collection/components/kritzel-button/kritzel-button.js +2 -2
  17. package/dist/collection/components/kritzel-color/kritzel-color.css +24 -24
  18. package/dist/collection/components/kritzel-color/kritzel-color.js +2 -2
  19. package/dist/collection/components/kritzel-color-palette/kritzel-color-palette.css +46 -46
  20. package/dist/collection/components/kritzel-color-palette/kritzel-color-palette.js +1 -1
  21. package/dist/collection/components/kritzel-context-menu/kritzel-context-menu.css +113 -112
  22. package/dist/collection/components/kritzel-context-menu/kritzel-context-menu.js +1 -1
  23. package/dist/collection/components/kritzel-current-user/kritzel-current-user.css +11 -11
  24. package/dist/collection/components/kritzel-current-user/kritzel-current-user.js +1 -1
  25. package/dist/collection/components/kritzel-current-user-dialog/kritzel-current-user-dialog.css +39 -39
  26. package/dist/collection/components/kritzel-current-user-dialog/kritzel-current-user-dialog.js +1 -1
  27. package/dist/collection/components/kritzel-dialog/kritzel-dialog.css +209 -209
  28. package/dist/collection/components/kritzel-dropdown/kritzel-dropdown.css +190 -190
  29. package/dist/collection/components/kritzel-dropdown/kritzel-dropdown.js +2 -2
  30. package/dist/collection/components/kritzel-editor/kritzel-editor.css +160 -160
  31. package/dist/collection/components/kritzel-editor/kritzel-editor.js +72 -46
  32. package/dist/collection/components/kritzel-export/kritzel-export.css +89 -89
  33. package/dist/collection/components/kritzel-export/kritzel-export.js +1 -1
  34. package/dist/collection/components/kritzel-font/kritzel-font.css +10 -10
  35. package/dist/collection/components/kritzel-font/kritzel-font.js +1 -1
  36. package/dist/collection/components/kritzel-font-family/kritzel-font-family.css +48 -48
  37. package/dist/collection/components/kritzel-font-family/kritzel-font-family.js +2 -2
  38. package/dist/collection/components/kritzel-font-size/kritzel-font-size.css +30 -30
  39. package/dist/collection/components/kritzel-font-size/kritzel-font-size.js +1 -1
  40. package/dist/collection/components/kritzel-input/kritzel-input.css +69 -69
  41. package/dist/collection/components/kritzel-input/kritzel-input.js +1 -1
  42. package/dist/collection/components/kritzel-line-endings/kritzel-line-endings.css +60 -60
  43. package/dist/collection/components/kritzel-line-endings/kritzel-line-endings.js +3 -3
  44. package/dist/collection/components/kritzel-loading-overlay/kritzel-loading-overlay.css +54 -54
  45. package/dist/collection/components/kritzel-loading-overlay/kritzel-loading-overlay.js +1 -1
  46. package/dist/collection/components/kritzel-login-dialog/kritzel-login-dialog.css +98 -98
  47. package/dist/collection/components/kritzel-login-dialog/kritzel-login-dialog.js +1 -1
  48. package/dist/collection/components/kritzel-master-detail/kritzel-master-detail.css +190 -190
  49. package/dist/collection/components/kritzel-master-detail/kritzel-master-detail.js +3 -3
  50. package/dist/collection/components/kritzel-menu/kritzel-menu.css +35 -34
  51. package/dist/collection/components/kritzel-menu/kritzel-menu.js +1 -1
  52. package/dist/collection/components/kritzel-menu-item/kritzel-menu-item.css +166 -166
  53. package/dist/collection/components/kritzel-menu-item/kritzel-menu-item.js +3 -3
  54. package/dist/collection/components/kritzel-more-menu/kritzel-more-menu.css +58 -58
  55. package/dist/collection/components/kritzel-more-menu/kritzel-more-menu.js +4 -4
  56. package/dist/collection/components/kritzel-notification-card/kritzel-notification-card.css +101 -101
  57. package/dist/collection/components/kritzel-numeric-input/kritzel-numeric-input.css +102 -102
  58. package/dist/collection/components/kritzel-numeric-input/kritzel-numeric-input.js +1 -1
  59. package/dist/collection/components/kritzel-opacity-slider/kritzel-opacity-slider.css +85 -85
  60. package/dist/collection/components/kritzel-opacity-slider/kritzel-opacity-slider.js +1 -1
  61. package/dist/collection/components/kritzel-pill-tabs/kritzel-pill-tabs.css +68 -68
  62. package/dist/collection/components/kritzel-pill-tabs/kritzel-pill-tabs.js +2 -2
  63. package/dist/collection/components/kritzel-portal/kritzel-portal.js +1 -1
  64. package/dist/collection/components/kritzel-settings/kritzel-settings.css +137 -137
  65. package/dist/collection/components/kritzel-settings/kritzel-settings.js +1 -1
  66. package/dist/collection/components/kritzel-shape-fill/kritzel-shape-fill.css +47 -47
  67. package/dist/collection/components/kritzel-shape-fill/kritzel-shape-fill.js +4 -4
  68. package/dist/collection/components/kritzel-share-dialog/kritzel-share-dialog.css +150 -150
  69. package/dist/collection/components/kritzel-share-dialog/kritzel-share-dialog.js +2 -2
  70. package/dist/collection/components/kritzel-slide-toggle/kritzel-slide-toggle.css +48 -48
  71. package/dist/collection/components/kritzel-slide-toggle/kritzel-slide-toggle.js +1 -1
  72. package/dist/collection/components/kritzel-split-button/kritzel-split-button.css +78 -78
  73. package/dist/collection/components/kritzel-split-button/kritzel-split-button.js +3 -3
  74. package/dist/collection/components/kritzel-stroke-size/kritzel-stroke-size.css +37 -37
  75. package/dist/collection/components/kritzel-stroke-size/kritzel-stroke-size.js +1 -1
  76. package/dist/collection/components/kritzel-tool-config/kritzel-tool-config.css +38 -38
  77. package/dist/collection/components/kritzel-tool-config/kritzel-tool-config.js +1 -1
  78. package/dist/collection/components/kritzel-toolbar/kritzel-toolbar.css +347 -347
  79. package/dist/collection/components/kritzel-toolbar/kritzel-toolbar.js +12 -16
  80. package/dist/collection/components/kritzel-tooltip/kritzel-tooltip.css +18 -18
  81. package/dist/collection/components/kritzel-tooltip/kritzel-tooltip.js +2 -2
  82. package/dist/collection/components/kritzel-utility-panel/kritzel-utility-panel.css +45 -45
  83. package/dist/collection/components/kritzel-utility-panel/kritzel-utility-panel.js +1 -1
  84. package/dist/collection/components/kritzel-workspace-manager/kritzel-workspace-manager.css +16 -16
  85. package/dist/collection/components/kritzel-workspace-manager/kritzel-workspace-manager.js +1 -1
  86. package/dist/collection/components/kritzel-zoom-panel/kritzel-zoom-panel.css +72 -72
  87. package/dist/collection/components/kritzel-zoom-panel/kritzel-zoom-panel.js +1 -1
  88. package/dist/components/index.js +1 -1
  89. package/dist/components/kritzel-active-users.js +1 -1
  90. package/dist/components/kritzel-avatar.js +1 -1
  91. package/dist/components/kritzel-awareness-cursors.js +1 -1
  92. package/dist/components/kritzel-back-to-content.js +1 -1
  93. package/dist/components/kritzel-brush-style.js +4 -4
  94. package/dist/components/kritzel-button.js +1 -1
  95. package/dist/components/kritzel-color-palette.js +1 -1
  96. package/dist/components/kritzel-color.js +1 -1
  97. package/dist/components/kritzel-context-menu.js +1 -1
  98. package/dist/components/kritzel-current-user-dialog.js +1 -1
  99. package/dist/components/kritzel-current-user.js +1 -1
  100. package/dist/components/kritzel-cursor-trail.js +1 -1
  101. package/dist/components/kritzel-dialog.js +1 -1
  102. package/dist/components/kritzel-dropdown.js +1 -1
  103. package/dist/components/kritzel-editor.js +108 -81
  104. package/dist/components/kritzel-engine.js +1 -1
  105. package/dist/components/kritzel-export.js +1 -1
  106. package/dist/components/kritzel-font-family.js +1 -1
  107. package/dist/components/kritzel-font-size.js +1 -1
  108. package/dist/components/kritzel-font.js +1 -1
  109. package/dist/components/kritzel-icon.js +1 -1
  110. package/dist/components/kritzel-input.js +1 -1
  111. package/dist/components/kritzel-line-endings.js +1 -1
  112. package/dist/components/kritzel-loading-overlay.js +1 -1
  113. package/dist/components/kritzel-login-dialog.js +1 -1
  114. package/dist/components/kritzel-master-detail.js +1 -1
  115. package/dist/components/kritzel-menu-item.js +1 -1
  116. package/dist/components/kritzel-menu.js +1 -1
  117. package/dist/components/kritzel-more-menu.js +1 -1
  118. package/dist/components/kritzel-notification-card.js +1 -1
  119. package/dist/components/kritzel-numeric-input.js +1 -1
  120. package/dist/components/kritzel-opacity-slider.js +1 -1
  121. package/dist/components/kritzel-pill-tabs.js +1 -1
  122. package/dist/components/kritzel-portal.js +1 -1
  123. package/dist/components/kritzel-settings.js +1 -1
  124. package/dist/components/kritzel-shape-fill.js +1 -1
  125. package/dist/components/kritzel-share-dialog.js +1 -1
  126. package/dist/components/kritzel-slide-toggle.js +1 -1
  127. package/dist/components/kritzel-split-button.js +1 -1
  128. package/dist/components/kritzel-stroke-size.js +1 -1
  129. package/dist/components/kritzel-tool-config.js +1 -1
  130. package/dist/components/kritzel-toolbar.js +1 -1
  131. package/dist/components/kritzel-tooltip.js +1 -1
  132. package/dist/components/kritzel-utility-panel.js +1 -1
  133. package/dist/components/kritzel-watermark.js +1 -1
  134. package/dist/components/kritzel-workspace-manager.js +1 -1
  135. package/dist/components/kritzel-zoom-panel.js +1 -1
  136. package/dist/components/{p-B_gT0lCn.js → p-5tEZmBli.js} +3 -3
  137. package/dist/components/{p-B6UJztrf.js → p-B6U2JNDq.js} +4 -4
  138. package/dist/components/{p-DXuf225S.js → p-BK5rE1rd.js} +3 -3
  139. package/dist/components/{p-BZ_KnHdR.js → p-BbXTqI4t.js} +7 -7
  140. package/dist/components/{p-McY5wZ0h.js → p-BhVCoiyK.js} +2 -2
  141. package/dist/components/{p-SLnqczCS.js → p-BowLPDbq.js} +3 -3
  142. package/dist/components/{p-hQwVRh0t.js → p-BuwiCK1I.js} +7 -7
  143. package/dist/components/{p-CoNexfpB.js → p-C01XOYLF.js} +3 -3
  144. package/dist/components/{p-ClfOtiNp.js → p-C5Hw8Wv3.js} +3 -3
  145. package/dist/components/{p-CmjXRLdr.js → p-C5fgp5y4.js} +7 -7
  146. package/dist/components/{p-BfMlwIcG.js → p-CHelQjUo.js} +3 -3
  147. package/dist/components/{p-B5xYV1o3.js → p-CIm1iROq.js} +2 -2
  148. package/dist/components/{p-BjFrt1q0.js → p-COGlcEiV.js} +3 -3
  149. package/dist/components/{p-4-qLegHb.js → p-COaLQ4Z-.js} +4 -4
  150. package/dist/components/{p-CVxGF4bJ.js → p-CPfTzGnn.js} +5 -5
  151. package/dist/components/p-CViWMGoM.js +55 -0
  152. package/dist/components/{p-Blq9a1ni.js → p-CY3fGZlt.js} +6 -6
  153. package/dist/components/{p-Cy_cLtOP.js → p-CdwD9F0m.js} +18 -14
  154. package/dist/components/{p-GbCxpbQ6.js → p-CgCVe3_P.js} +3 -3
  155. package/dist/components/{p-DZsSiMnA.js → p-CiSc7ASZ.js} +722 -129
  156. package/dist/components/{p-DVv8ChhE.js → p-D0lYLWkj.js} +4 -4
  157. package/dist/components/{p-QEue33R5.js → p-D1sxfQVN.js} +3 -3
  158. package/dist/components/{p-BvG3JO85.js → p-D53Q7EDf.js} +3 -3
  159. package/dist/components/{p-CzUeIini.js → p-D5OzFEtr.js} +13 -13
  160. package/dist/components/{p-B3Tqoyl9.js → p-DAr69onU.js} +3 -3
  161. package/dist/components/{p-h_uh_Njf.js → p-DE6S-FN0.js} +3 -3
  162. package/dist/components/p-DSi2HiZW.js +214 -0
  163. package/dist/components/{p-Ckqh1tqY.js → p-DX4FbgMi.js} +27 -31
  164. package/dist/components/{p-CwBxnzYu.js → p-DYNFqwX4.js} +3 -3
  165. package/dist/components/{p-Cp4e4oTE.js → p-DZ0S2l6Z.js} +2 -2
  166. package/dist/components/{p-BrGJIoGK.js → p-DZ4rAt6k.js} +3 -3
  167. package/dist/components/{p-BDjRSoAc.js → p-Ddk64LEI.js} +2 -2
  168. package/dist/components/{p-DJxgPPfs.js → p-DoNH4OhF.js} +3 -3
  169. package/dist/components/{p-D0tPTPkR.js → p-Dtm4CEHf.js} +3 -3
  170. package/dist/components/{p-BN8VWPlq.js → p-Dwg30A00.js} +7 -7
  171. package/dist/components/{p-DXZBWJBt.js → p-DxHrSwYy.js} +4 -4
  172. package/dist/components/{p-CvaxFYS0.js → p-EpqroVCx.js} +8 -8
  173. package/dist/components/{p-R1D8Sr0l.js → p-Fzi_dVqn.js} +3 -3
  174. package/dist/components/{p-CS3YZVYE.js → p-Qk844WR4.js} +2 -2
  175. package/dist/components/{p-peZjvnkK.js → p-g6VqjbL_.js} +2 -2
  176. package/dist/components/{p-DyRJFg4w.js → p-hKGrOeEa.js} +2 -2
  177. package/dist/components/{p-5qkOM1RJ.js → p-l1BZ4pYI.js} +12 -1
  178. package/dist/components/{p-CrIEmItB.js → p-p2H4pOpF.js} +5 -5
  179. package/dist/components/{p-DqOC_Yck.js → p-pWWRmEe8.js} +6 -6
  180. package/dist/components/{p-Ds0uNtwQ.js → p-piYX2tVg.js} +4 -4
  181. package/dist/components/{p-kOINnzaR.js → p-xPpMDzPX.js} +7 -7
  182. package/dist/esm/{index-OjbYJz-P.js → index-2x7dfLlM.js} +12 -1
  183. package/dist/esm/kritzel-active-users_46.entry.js +981 -322
  184. package/dist/esm/kritzel-brush-style.entry.js +2 -2
  185. package/dist/esm/kritzel-editor.js +3 -3
  186. package/dist/esm/loader.js +3 -3
  187. package/dist/kritzel-editor/kritzel-editor.esm.js +3 -3
  188. package/dist/kritzel-editor/{p-501047d8.entry.js → p-285cdc18.entry.js} +2 -2
  189. package/dist/kritzel-editor/{p-OjbYJz-P.js → p-2x7dfLlM.js} +12 -1
  190. package/dist/kritzel-editor/{p-60319edb.entry.js → p-d5439a4f.entry.js} +981 -322
  191. package/dist/types/components/kritzel-editor/kritzel-editor.d.ts +9 -2
  192. package/dist/types/components.d.ts +15 -15
  193. package/package.json +3 -3
  194. package/dist/components/p-BGmTmdLe.js +0 -167
  195. package/dist/components/p-DOJCDPVD.js +0 -55
@@ -1,9 +1,9 @@
1
- import { p as proxyCustomElement, H, c as createEvent, h, d as Host, t as transformTag } from './p-5qkOM1RJ.js';
2
- import { a as KritzelMouseButton, d as defineCustomElement$3 } from './p-Cp4e4oTE.js';
3
- import { a as KritzelBaseObject, b as KritzelLine, c as KritzelMathHelper, e as KritzelGeometryHelper, f as KritzelColorHelper, D as DEFAULT_COLOR_PALETTE, g as KritzelPath, O as ObjectHelper, h as KritzelThemeManager, d as defineCustomElement$4 } from './p-Cy_cLtOP.js';
1
+ import { p as proxyCustomElement, H, c as createEvent, h, d as Host, t as transformTag } from './p-l1BZ4pYI.js';
2
+ import { a as KritzelMouseButton, d as defineCustomElement$3 } from './p-DZ0S2l6Z.js';
3
+ import { a as KritzelBaseObject, b as KritzelLine, c as KritzelMathHelper, e as KritzelGeometryHelper, f as KritzelColorHelper, D as DEFAULT_COLOR_PALETTE, g as KritzelPath, O as ObjectHelper, h as KritzelThemeManager, d as defineCustomElement$4 } from './p-CdwD9F0m.js';
4
4
  import { c as commonjsGlobal } from './p-BFTU3MAI.js';
5
- import { A as ABSOLUTE_SCALE_MAX, a as ABSOLUTE_SCALE_MIN, d as defineCustomElement$1 } from './p-SLnqczCS.js';
6
- import { a as KritzelIconRegistry, d as defineCustomElement$2 } from './p-BGmTmdLe.js';
5
+ import { A as ABSOLUTE_SCALE_MAX, a as ABSOLUTE_SCALE_MIN, d as defineCustomElement$1 } from './p-BowLPDbq.js';
6
+ import { a as KritzelIconRegistry, d as defineCustomElement$2 } from './p-DSi2HiZW.js';
7
7
 
8
8
  /**
9
9
  * Represents a visual selection box used for multi-object selection on the canvas.
@@ -38067,9 +38067,8 @@ class KritzelSelectionTool extends KritzelBaseTool {
38067
38067
  }
38068
38068
  if (selectedObject && selectedObject.isSelected === false && selectedObject?.objects.length === 1) {
38069
38069
  const firstObj = selectedObject.objects[0];
38070
- if (KritzelClassHelper.isInstanceOf(firstObj, 'KritzelDynamicObject')) {
38071
- // Selected single dynamic objects hand pointer interaction to their
38072
- // embedded DOM content. Unselected dynamic objects remain selectable.
38070
+ if (ObjectHelper.isKritzelEmbeddedObject(firstObj)) {
38071
+ // Selected embedded objects hand pointer interaction to their content.
38073
38072
  if (firstObj.isSelected && this._core.store.selectionGroup?.objects.length === 1)
38074
38073
  return;
38075
38074
  }
@@ -40238,7 +40237,7 @@ class KritzelDynamicObject extends KritzelBaseObject {
40238
40237
  }
40239
40238
  applyInteractiveState() {
40240
40239
  const pointerEvents = this.isInteractive ? 'auto' : 'none';
40241
- const contentCursor = this.isInteractive ? 'auto' : '';
40240
+ const contentCursor = this.isInteractive ? 'var(--kritzel-global-default-cursor, auto)' : '';
40242
40241
  if (this.element) {
40243
40242
  this.element.style.pointerEvents = pointerEvents;
40244
40243
  this.element.style.cursor = contentCursor;
@@ -40274,7 +40273,7 @@ class KritzelDynamicObject extends KritzelBaseObject {
40274
40273
  if (!this.isInteractive) {
40275
40274
  return false;
40276
40275
  }
40277
- const focusHosts = this.getFocusHosts();
40276
+ const focusHosts = this.getEmbeddedContentRoots();
40278
40277
  // Primary path: locate the focused input-like element by walking from each host through
40279
40278
  // `:focus` (piercing open shadow roots). This works in real browsers even when the engine
40280
40279
  // and editor are nested web components, because it never relies on the document-level
@@ -40291,7 +40290,7 @@ class KritzelDynamicObject extends KritzelBaseObject {
40291
40290
  }
40292
40291
  return false;
40293
40292
  }
40294
- getFocusHosts() {
40293
+ getEmbeddedContentRoots() {
40295
40294
  const hosts = [];
40296
40295
  if (this.element) {
40297
40296
  hosts.push(this.element);
@@ -40504,11 +40503,11 @@ class KritzelDynamicObject extends KritzelBaseObject {
40504
40503
  * @returns A serializable object with htmlString and dimension snapshots
40505
40504
  */
40506
40505
  serialize() {
40507
- const { _core, _elementRef, element, totalWidth, totalHeight, isSelected, isHovered, isMounted, isInteractive, resizing, markedForRemoval, isVisible, ...props } = this;
40506
+ const { _core, _elementRef, element, totalWidth, totalHeight, isSelected, isHovered, isMounted, isInteractive, resizing, markedForRemoval, isVisible, rendererData, ...props } = this;
40508
40507
  const cloned = structuredClone(props);
40509
40508
  // Include rendererKey and rendererData for lifecycle-driven rendering
40510
40509
  cloned.rendererKey = this.rendererKey;
40511
- cloned.rendererData = this.rendererData;
40510
+ cloned.rendererData = this.serializeRendererData();
40512
40511
  // Store HTML as string for fallback/compatibility when renderer is unavailable
40513
40512
  cloned.htmlString = element ? element.outerHTML : '';
40514
40513
  // Capture dimension snapshot at serialization time
@@ -40516,6 +40515,19 @@ class KritzelDynamicObject extends KritzelBaseObject {
40516
40515
  cloned.serializedHeight = this.height;
40517
40516
  return cloned;
40518
40517
  }
40518
+ serializeRendererData() {
40519
+ if (this.rendererKey) {
40520
+ const renderer = KritzelDynamicObjectRendererRegistry.get(this.rendererKey);
40521
+ if (renderer?.onSerialize) {
40522
+ return structuredClone(renderer.onSerialize({
40523
+ object: this,
40524
+ container: this.isMounted && this.elementRef instanceof HTMLElement ? this.elementRef : null,
40525
+ data: this.rendererData,
40526
+ }));
40527
+ }
40528
+ }
40529
+ return structuredClone(this.rendererData);
40530
+ }
40519
40531
  /**
40520
40532
  * Deserializes the dynamic object from a plain object.
40521
40533
  * Reconstructs the element from stored HTML string with fallback to placeholder.
@@ -40548,6 +40560,452 @@ class KritzelDynamicObject extends KritzelBaseObject {
40548
40560
  }
40549
40561
  }
40550
40562
 
40563
+ const IFRAME_STATE_MESSAGE = 'kritzel-iframe-state';
40564
+ const IFRAME_PARENT_STATE_MESSAGE = 'kritzel-iframe-parent-state';
40565
+ const IFRAME_POINTER_MESSAGE = 'kritzel-iframe-pointer';
40566
+ const IFRAME_CONTEXT_MENU_MESSAGE = 'kritzel-iframe-contextmenu';
40567
+ const SUPPORTED_SANDBOX_TOKENS = new Set([
40568
+ 'allow-downloads',
40569
+ 'allow-forms',
40570
+ 'allow-modals',
40571
+ 'allow-orientation-lock',
40572
+ 'allow-pointer-lock',
40573
+ 'allow-popups',
40574
+ 'allow-popups-to-escape-sandbox',
40575
+ 'allow-presentation',
40576
+ 'allow-same-origin',
40577
+ 'allow-scripts',
40578
+ 'allow-top-navigation-by-user-activation',
40579
+ ]);
40580
+ const iframePointerBridgeStarts = new WeakMap();
40581
+ class KritzelIframeObject extends KritzelBaseObject {
40582
+ __class__ = 'KritzelIframeObject';
40583
+ content = { html: '', css: '', script: '' };
40584
+ loadingContent;
40585
+ isLoading = false;
40586
+ state = {};
40587
+ title = 'Embedded content';
40588
+ sandbox = ['allow-scripts'];
40589
+ borderRadius;
40590
+ boxShadow;
40591
+ get html() { return this.content.html; }
40592
+ set html(value) { this.content = { ...this.content, html: value }; }
40593
+ get css() { return this.content.css; }
40594
+ set css(value) { this.content = { ...this.content, css: value }; }
40595
+ get script() { return this.content.script; }
40596
+ set script(value) { this.content = { ...this.content, script: value }; }
40597
+ messageListener = null;
40598
+ constructor(config) {
40599
+ super();
40600
+ this.content = {
40601
+ html: config?.content?.html ?? config?.html ?? '',
40602
+ css: config?.content?.css ?? config?.css ?? '',
40603
+ script: config?.content?.script ?? config?.script ?? '',
40604
+ };
40605
+ this.loadingContent = config?.loadingContent ? {
40606
+ html: config.loadingContent.html ?? '',
40607
+ css: config.loadingContent.css ?? '',
40608
+ script: config.loadingContent.script ?? '',
40609
+ } : undefined;
40610
+ this.isLoading = config?.isLoading ?? false;
40611
+ this.state = structuredClone(config?.state ?? {});
40612
+ this.title = config?.title ?? 'Embedded content';
40613
+ this.sandbox = this.normalizeSandbox(config?.sandbox);
40614
+ this.borderRadius = config?.borderRadius;
40615
+ this.boxShadow = config?.boxShadow;
40616
+ this.translateX = config?.translateX ?? 0;
40617
+ this.translateY = config?.translateY ?? 0;
40618
+ this.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
40619
+ this.scale = config?.scale ?? 1;
40620
+ this.width = config?.width ?? 0;
40621
+ this.height = config?.height ?? 0;
40622
+ }
40623
+ static create(core, config) {
40624
+ const object = new KritzelIframeObject(config);
40625
+ object._core = core;
40626
+ object.id = object.generateId();
40627
+ object.workspaceId = core.store.state.activeWorkspace.id;
40628
+ object.userId = core.user?.id;
40629
+ object.scale = config?.scale ?? core.store.state.scale;
40630
+ object.zIndex = core.store.currentZIndex;
40631
+ return object;
40632
+ }
40633
+ get sandboxAttribute() {
40634
+ return this.sandbox.join(' ');
40635
+ }
40636
+ mount(iframe) {
40637
+ if (this.isMounted && this.elementRef === iframe) {
40638
+ return;
40639
+ }
40640
+ if (this.isMounted) {
40641
+ this.unmount();
40642
+ }
40643
+ this.elementRef = iframe;
40644
+ this.isMounted = true;
40645
+ iframe.title = this.title;
40646
+ iframe.setAttribute('sandbox', this.sandboxAttribute);
40647
+ iframe.style.pointerEvents = this.isInteractive ? 'auto' : 'none';
40648
+ this.messageListener = event => this.handleMessage(event);
40649
+ window.addEventListener('message', this.messageListener);
40650
+ iframe.srcdoc = this.createSourceDocument();
40651
+ this.onMount(this.createLifecycleContext());
40652
+ }
40653
+ unmount() {
40654
+ if (!this.isMounted) {
40655
+ return;
40656
+ }
40657
+ const nextState = this.onUnmount(this.createLifecycleContext());
40658
+ if (nextState !== undefined) {
40659
+ this.state = structuredClone(nextState);
40660
+ }
40661
+ if (this.messageListener) {
40662
+ window.removeEventListener('message', this.messageListener);
40663
+ this.messageListener = null;
40664
+ }
40665
+ this.isMounted = false;
40666
+ this._elementRef = null;
40667
+ }
40668
+ setContent(content) {
40669
+ this.content = {
40670
+ html: content.html ?? this.content.html,
40671
+ css: content.css ?? this.content.css,
40672
+ script: content.script ?? this.content.script,
40673
+ };
40674
+ this.refreshDocument();
40675
+ this.persistUpdate();
40676
+ }
40677
+ setLoadingContent(content) {
40678
+ this.loadingContent = {
40679
+ html: content.html ?? this.loadingContent?.html ?? '',
40680
+ css: content.css ?? this.loadingContent?.css ?? '',
40681
+ script: content.script ?? this.loadingContent?.script ?? '',
40682
+ };
40683
+ this.refreshDocument();
40684
+ this.persistUpdate();
40685
+ }
40686
+ setLoading(isLoading) {
40687
+ this.isLoading = isLoading;
40688
+ this.refreshDocument();
40689
+ this.persistUpdate();
40690
+ }
40691
+ getActiveContent() {
40692
+ const activeLoadingContent = this.isLoading ? this.loadingContent : undefined;
40693
+ return {
40694
+ html: activeLoadingContent?.html ?? this.content.html,
40695
+ css: activeLoadingContent?.css ?? this.content.css,
40696
+ script: activeLoadingContent?.script ?? this.content.script,
40697
+ };
40698
+ }
40699
+ setState(state) {
40700
+ this.state = structuredClone(state);
40701
+ this.elementRef?.contentWindow?.postMessage({ type: IFRAME_PARENT_STATE_MESSAGE, state: this.state }, '*');
40702
+ this.persistUpdate();
40703
+ }
40704
+ setIsInteractive(isInteractive) {
40705
+ this.isInteractive = isInteractive;
40706
+ if (this.isMounted && this.elementRef) {
40707
+ this.elementRef.style.pointerEvents = isInteractive ? 'auto' : 'none';
40708
+ if (!isInteractive) {
40709
+ this.elementRef.blur();
40710
+ }
40711
+ }
40712
+ }
40713
+ hasFocusedInputElement() {
40714
+ return this.isInteractive && this.isMounted && document.activeElement === this.elementRef;
40715
+ }
40716
+ getEmbeddedContentRoots() {
40717
+ return this.isMounted && this.elementRef instanceof HTMLElement && this.elementRef.tagName === 'IFRAME' ? [this.elementRef] : [];
40718
+ }
40719
+ onMount(_context) { }
40720
+ onSerialize(context) {
40721
+ return context.state;
40722
+ }
40723
+ onUnmount(context) {
40724
+ return context.state;
40725
+ }
40726
+ serialize() {
40727
+ const nextState = this.onSerialize(this.createLifecycleContext());
40728
+ this.state = structuredClone(nextState);
40729
+ const messageListener = this.messageListener;
40730
+ this.messageListener = null;
40731
+ try {
40732
+ const serialized = super.serialize();
40733
+ delete serialized.messageListener;
40734
+ return serialized;
40735
+ }
40736
+ finally {
40737
+ this.messageListener = messageListener;
40738
+ }
40739
+ }
40740
+ deserialize(object) {
40741
+ super.deserialize(object);
40742
+ const nextContent = object.content && typeof object.content === 'object' ? object.content : {};
40743
+ const nextLoadingContent = object.loadingContent && typeof object.loadingContent === 'object' ? object.loadingContent : undefined;
40744
+ this.content = {
40745
+ html: typeof nextContent.html === 'string' ? nextContent.html : typeof object.html === 'string' ? object.html : '',
40746
+ css: typeof nextContent.css === 'string' ? nextContent.css : typeof object.css === 'string' ? object.css : '',
40747
+ script: typeof nextContent.script === 'string' ? nextContent.script : typeof object.script === 'string' ? object.script : '',
40748
+ };
40749
+ this.loadingContent = nextLoadingContent ? {
40750
+ html: typeof nextLoadingContent.html === 'string' ? nextLoadingContent.html : '',
40751
+ css: typeof nextLoadingContent.css === 'string' ? nextLoadingContent.css : '',
40752
+ script: typeof nextLoadingContent.script === 'string' ? nextLoadingContent.script : '',
40753
+ } : undefined;
40754
+ this.isLoading = typeof object.isLoading === 'boolean' ? object.isLoading : false;
40755
+ this.state = object.state && typeof object.state === 'object' ? structuredClone(object.state) : {};
40756
+ this.title = typeof object.title === 'string' ? object.title : 'Embedded content';
40757
+ this.sandbox = this.normalizeSandbox(Array.isArray(object.sandbox) ? object.sandbox.filter((token) => typeof token === 'string') : undefined);
40758
+ this.borderRadius = (typeof object.borderRadius === 'number' || typeof object.borderRadius === 'string') ? object.borderRadius : undefined;
40759
+ this.boxShadow = typeof object.boxShadow === 'string' ? object.boxShadow : undefined;
40760
+ this.messageListener = null;
40761
+ return this;
40762
+ }
40763
+ copy() {
40764
+ const copiedObject = super.copy();
40765
+ copiedObject.content = { ...this.content };
40766
+ copiedObject.loadingContent = this.loadingContent ? { ...this.loadingContent } : undefined;
40767
+ copiedObject.isLoading = this.isLoading;
40768
+ copiedObject.state = structuredClone(this.state);
40769
+ copiedObject.sandbox = [...this.sandbox];
40770
+ copiedObject.borderRadius = this.borderRadius;
40771
+ copiedObject.boxShadow = this.boxShadow;
40772
+ copiedObject.messageListener = null;
40773
+ copiedObject._elementRef = null;
40774
+ return copiedObject;
40775
+ }
40776
+ clone() {
40777
+ const clonedObject = super.clone();
40778
+ clonedObject.content = { ...this.content };
40779
+ clonedObject.loadingContent = this.loadingContent ? { ...this.loadingContent } : undefined;
40780
+ clonedObject.isLoading = this.isLoading;
40781
+ clonedObject.state = structuredClone(this.state);
40782
+ clonedObject.sandbox = [...this.sandbox];
40783
+ clonedObject.borderRadius = this.borderRadius;
40784
+ clonedObject.boxShadow = this.boxShadow;
40785
+ clonedObject.messageListener = null;
40786
+ clonedObject.isMounted = false;
40787
+ clonedObject._elementRef = null;
40788
+ return clonedObject;
40789
+ }
40790
+ adoptTransientStateFrom(previous) {
40791
+ if (!(previous instanceof KritzelIframeObject)) {
40792
+ return;
40793
+ }
40794
+ this.isInteractive = previous.isInteractive;
40795
+ this.isSelected = previous.isSelected;
40796
+ this.isHovered = previous.isHovered;
40797
+ if (!previous.isMounted || !(previous.elementRef instanceof HTMLElement) || previous.elementRef.tagName !== 'IFRAME') {
40798
+ return;
40799
+ }
40800
+ if (previous.messageListener) {
40801
+ window.removeEventListener('message', previous.messageListener);
40802
+ }
40803
+ this._elementRef = previous.elementRef;
40804
+ this.isMounted = true;
40805
+ this.messageListener = event => this.handleMessage(event);
40806
+ window.addEventListener('message', this.messageListener);
40807
+ this.elementRef.style.pointerEvents = this.isInteractive ? 'auto' : 'none';
40808
+ previous.messageListener = null;
40809
+ previous.isMounted = false;
40810
+ previous._elementRef = null;
40811
+ }
40812
+ createLifecycleContext() {
40813
+ return {
40814
+ object: this,
40815
+ iframe: this.isMounted ? this.elementRef : null,
40816
+ state: structuredClone(this.state),
40817
+ };
40818
+ }
40819
+ normalizeSandbox(tokens) {
40820
+ const requestedTokens = tokens ?? ['allow-scripts'];
40821
+ const normalized = requestedTokens.filter(token => SUPPORTED_SANDBOX_TOKENS.has(token));
40822
+ return [...new Set(normalized.length > 0 ? normalized : ['allow-scripts'])];
40823
+ }
40824
+ createSourceDocument() {
40825
+ const activeContent = this.getActiveContent();
40826
+ const state = JSON.stringify(this.state).replace(/</g, '\\u003c');
40827
+ const css = activeContent.css.replace(/<\/style/gi, '<\\/style');
40828
+ const script = activeContent.script.replace(/<\/script/gi, '<\\/script');
40829
+ const computedStyle = this.isMounted ? getComputedStyle(this.elementRef) : null;
40830
+ const defaultCursor = computedStyle?.getPropertyValue('--kritzel-global-default-cursor').trim() || 'auto';
40831
+ const pointerCursor = computedStyle?.getPropertyValue('--kritzel-global-pointer-cursor').trim() || defaultCursor;
40832
+ return `<!doctype html>
40833
+ <html>
40834
+ <head>
40835
+ <meta charset="utf-8">
40836
+ <style>
40837
+ :root {
40838
+ --kritzel-iframe-default-cursor: ${defaultCursor};
40839
+ --kritzel-iframe-pointer-cursor: ${pointerCursor};
40840
+ }
40841
+ html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; cursor: var(--kritzel-iframe-default-cursor); }
40842
+ button, button *, a, a *, input, textarea, select { cursor: var(--kritzel-iframe-pointer-cursor) !important; }
40843
+ ${css}
40844
+ </style>
40845
+ </head>
40846
+ <body>
40847
+ ${activeContent.html}
40848
+ <script>
40849
+ const kritzel = globalThis.kritzel = {
40850
+ state: ${state},
40851
+ setState(nextState) {
40852
+ this.state = nextState;
40853
+ parent.postMessage({ type: '${IFRAME_STATE_MESSAGE}', state: nextState }, '*');
40854
+ },
40855
+ };
40856
+ globalThis.addEventListener('message', event => {
40857
+ if (event.data?.type === '${IFRAME_PARENT_STATE_MESSAGE}' && event.data.state && typeof event.data.state === 'object') {
40858
+ kritzel.state = event.data.state;
40859
+ globalThis.dispatchEvent(new CustomEvent('kritzelstatechange', { detail: kritzel.state }));
40860
+ }
40861
+ });
40862
+ let bridgedPointerId = null;
40863
+ const interactiveSelector = 'button, input, textarea, select, a[href], [contenteditable="true"], [contenteditable=""]';
40864
+ const bridgePointerEvent = event => parent.postMessage({
40865
+ type: '${IFRAME_POINTER_MESSAGE}',
40866
+ eventType: event.type,
40867
+ pointerId: event.pointerId,
40868
+ pointerType: event.pointerType,
40869
+ clientX: event.clientX,
40870
+ clientY: event.clientY,
40871
+ screenX: event.screenX,
40872
+ screenY: event.screenY,
40873
+ button: event.button,
40874
+ buttons: event.buttons,
40875
+ shiftKey: event.shiftKey,
40876
+ ctrlKey: event.ctrlKey,
40877
+ altKey: event.altKey,
40878
+ metaKey: event.metaKey,
40879
+ }, '*');
40880
+ document.addEventListener('pointerdown', event => {
40881
+ if (event.target instanceof Element && event.target.closest(interactiveSelector)) return;
40882
+ bridgedPointerId = event.pointerId;
40883
+ event.preventDefault();
40884
+ bridgePointerEvent(event);
40885
+ });
40886
+ document.addEventListener('pointermove', event => {
40887
+ if (event.pointerId !== bridgedPointerId) return;
40888
+ event.preventDefault();
40889
+ bridgePointerEvent(event);
40890
+ });
40891
+ const finishPointerBridge = event => {
40892
+ if (event.pointerId !== bridgedPointerId) return;
40893
+ event.preventDefault();
40894
+ bridgePointerEvent(event);
40895
+ bridgedPointerId = null;
40896
+ };
40897
+ document.addEventListener('pointerup', finishPointerBridge);
40898
+ document.addEventListener('pointercancel', finishPointerBridge);
40899
+ document.addEventListener('contextmenu', event => {
40900
+ event.preventDefault();
40901
+ parent.postMessage({
40902
+ type: '${IFRAME_CONTEXT_MENU_MESSAGE}',
40903
+ clientX: event.clientX,
40904
+ clientY: event.clientY,
40905
+ button: event.button,
40906
+ shiftKey: event.shiftKey,
40907
+ ctrlKey: event.ctrlKey,
40908
+ altKey: event.altKey,
40909
+ metaKey: event.metaKey,
40910
+ }, '*');
40911
+ });
40912
+ ${script}
40913
+ <\/script>
40914
+ </body>
40915
+ </html>`;
40916
+ }
40917
+ handleMessage(event) {
40918
+ if (event.source !== this.elementRef?.contentWindow || !event.data || typeof event.data !== 'object') {
40919
+ return;
40920
+ }
40921
+ const message = event.data;
40922
+ if (message.type === IFRAME_POINTER_MESSAGE) {
40923
+ this.dispatchPointerMessage(event.data);
40924
+ return;
40925
+ }
40926
+ if (message.type === IFRAME_CONTEXT_MENU_MESSAGE) {
40927
+ this.dispatchContextMenuMessage(event.data);
40928
+ return;
40929
+ }
40930
+ if (message.type !== IFRAME_STATE_MESSAGE || !message.state || typeof message.state !== 'object') {
40931
+ return;
40932
+ }
40933
+ this.state = structuredClone(message.state);
40934
+ this.persistUpdate();
40935
+ }
40936
+ dispatchPointerMessage(message) {
40937
+ if (!this.isMounted || !this.elementRef || !['pointerdown', 'pointermove', 'pointerup', 'pointercancel'].includes(message.eventType)) {
40938
+ return;
40939
+ }
40940
+ const iframeRect = this.elementRef.getBoundingClientRect();
40941
+ if (message.eventType === 'pointerdown') {
40942
+ const scaleX = this.elementRef.offsetWidth > 0 ? iframeRect.width / this.elementRef.offsetWidth : 1;
40943
+ const scaleY = this.elementRef.offsetHeight > 0 ? iframeRect.height / this.elementRef.offsetHeight : 1;
40944
+ iframePointerBridgeStarts.set(this.elementRef, {
40945
+ pointerId: message.pointerId,
40946
+ clientX: iframeRect.left + message.clientX * scaleX,
40947
+ clientY: iframeRect.top + message.clientY * scaleY,
40948
+ screenX: message.screenX,
40949
+ screenY: message.screenY,
40950
+ });
40951
+ }
40952
+ const bridgeStart = iframePointerBridgeStarts.get(this.elementRef);
40953
+ const hasBridgeStart = bridgeStart?.pointerId === message.pointerId;
40954
+ const clientX = hasBridgeStart ? bridgeStart.clientX + message.screenX - bridgeStart.screenX : iframeRect.left + message.clientX;
40955
+ const clientY = hasBridgeStart ? bridgeStart.clientY + message.screenY - bridgeStart.screenY : iframeRect.top + message.clientY;
40956
+ this.elementRef.dispatchEvent(new PointerEvent(message.eventType, {
40957
+ bubbles: true,
40958
+ composed: true,
40959
+ cancelable: true,
40960
+ pointerId: message.pointerId,
40961
+ pointerType: message.pointerType,
40962
+ clientX,
40963
+ clientY,
40964
+ screenX: message.screenX,
40965
+ screenY: message.screenY,
40966
+ button: message.button,
40967
+ buttons: message.buttons,
40968
+ shiftKey: message.shiftKey,
40969
+ ctrlKey: message.ctrlKey,
40970
+ altKey: message.altKey,
40971
+ metaKey: message.metaKey,
40972
+ }));
40973
+ if (message.eventType === 'pointerup' || message.eventType === 'pointercancel') {
40974
+ iframePointerBridgeStarts.delete(this.elementRef);
40975
+ }
40976
+ }
40977
+ dispatchContextMenuMessage(message) {
40978
+ if (!this.isMounted || !this.elementRef) {
40979
+ return;
40980
+ }
40981
+ const iframeRect = this.elementRef.getBoundingClientRect();
40982
+ // Dispatched as a MouseEvent (not PointerEvent) to mirror how browsers
40983
+ // natively raise 'contextmenu' for both mouse right-click and touch long-press.
40984
+ this.elementRef.dispatchEvent(new MouseEvent('contextmenu', {
40985
+ bubbles: true,
40986
+ composed: true,
40987
+ cancelable: true,
40988
+ clientX: iframeRect.left + message.clientX,
40989
+ clientY: iframeRect.top + message.clientY,
40990
+ button: message.button,
40991
+ shiftKey: message.shiftKey,
40992
+ ctrlKey: message.ctrlKey,
40993
+ altKey: message.altKey,
40994
+ metaKey: message.metaKey,
40995
+ }));
40996
+ }
40997
+ refreshDocument() {
40998
+ if (this.isMounted && this.elementRef) {
40999
+ this.elementRef.srcdoc = this.createSourceDocument();
41000
+ }
41001
+ }
41002
+ persistUpdate() {
41003
+ if (this._core?.store?.objects) {
41004
+ this.update();
41005
+ }
41006
+ }
41007
+ }
41008
+
40551
41009
  /**
40552
41010
  * Handles deserialization/revival of Kritzel objects from plain JavaScript objects.
40553
41011
  * This class uses a pattern similar to JSON.parse reviver to reconstruct class instances
@@ -40610,6 +41068,9 @@ class KritzelReviver {
40610
41068
  case 'KritzelDynamicObject':
40611
41069
  revivedObj = KritzelDynamicObject.create(this._core).deserialize(obj);
40612
41070
  break;
41071
+ case 'KritzelIframeObject':
41072
+ revivedObj = KritzelIframeObject.create(this._core).deserialize(obj);
41073
+ break;
40613
41074
  case 'KritzelSelectionGroup':
40614
41075
  revivedObj = KritzelSelectionGroup.create(this._core).deserialize(obj);
40615
41076
  break;
@@ -52971,9 +53432,16 @@ class KritzelStore {
52971
53432
  * Gets the currently selected dynamic object whose focused descendant is an input-like element.
52972
53433
  * @returns The interactive dynamic object with focused input-like content, or null if none matches
52973
53434
  */
53435
+ get activeInteractiveEmbeddedObject() {
53436
+ for (const object of this.selectedObjects) {
53437
+ if (ObjectHelper.isKritzelEmbeddedObject(object) && object.hasFocusedInputElement()) {
53438
+ return object;
53439
+ }
53440
+ }
53441
+ return null;
53442
+ }
52974
53443
  get activeInteractiveDynamicObject() {
52975
- const interactiveDynamicObject = this.selectedObjects.find(object => KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject') && object.hasFocusedInputElement());
52976
- return interactiveDynamicObject ?? null;
53444
+ return this.activeInteractiveEmbeddedObject;
52977
53445
  }
52978
53446
  /**
52979
53447
  * Gets the path currently being drawn (not yet completed).
@@ -55073,7 +55541,7 @@ class KritzelCursorHelper {
55073
55541
  * The icon is retrieved from the KritzelIconRegistry.
55074
55542
  */
55075
55543
  static getCursor(options) {
55076
- const iconName = options.iconName === 'default' ? 'mouse-pointer' : options.iconName;
55544
+ const iconName = options.iconName === 'default' ? 'mousePointer' : options.iconName;
55077
55545
  const iconSvg = KritzelIconRegistry.get(iconName);
55078
55546
  if (!iconSvg) {
55079
55547
  console.warn(`Icon "${iconName}" not found in registry.`);
@@ -55082,7 +55550,7 @@ class KritzelCursorHelper {
55082
55550
  const size = options.size || 24;
55083
55551
  const rotation = options.rotation || 0;
55084
55552
  const color = options.color || 'black';
55085
- // Default cursor (mouse-pointer) has hotspot at (4, 4), others at center
55553
+ // Default cursor (mousePointer) has hotspot at (4, 4), others at center
55086
55554
  const hotspot = options.hotspot || (options.iconName === 'default' ? { x: 4, y: 4 } : { x: size / 2, y: size / 2 });
55087
55555
  // Modify the SVG string to set size and color
55088
55556
  // We replace width and height to match the requested size
@@ -55095,12 +55563,12 @@ class KritzelCursorHelper {
55095
55563
  // We rotate around the center of the SVG canvas
55096
55564
  const center = size / 2;
55097
55565
  // We use a group to apply the rotation
55098
- const svg = `
55099
- <svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
55100
- <g transform="rotate(${rotation} ${center} ${center})">
55101
- ${content}
55102
- </g>
55103
- </svg>
55566
+ const svg = `
55567
+ <svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
55568
+ <g transform="rotate(${rotation} ${center} ${center})">
55569
+ ${content}
55570
+ </g>
55571
+ </svg>
55104
55572
  `;
55105
55573
  // Encode the SVG for use in a data URI
55106
55574
  // We need to be careful with encoding to ensure it works across browsers
@@ -55138,11 +55606,13 @@ class KritzelCursorManager {
55138
55606
  // Reset cursor on old target
55139
55607
  if (this._targetElement) {
55140
55608
  this._targetElement.style.cursor = '';
55609
+ this._targetElement.style.removeProperty('--kritzel-global-default-cursor');
55141
55610
  this._targetElement.style.removeProperty('--kritzel-global-pointer-cursor');
55142
55611
  }
55143
55612
  this._targetElement = element;
55144
55613
  // Set the pointer cursor CSS variable for child components to use
55145
55614
  if (this._targetElement) {
55615
+ this._targetElement.style.setProperty('--kritzel-global-default-cursor', KritzelCursorHelper.getCursor({ iconName: 'default' }));
55146
55616
  this._targetElement.style.setProperty('--kritzel-global-pointer-cursor', KritzelCursorHelper.getPointerCursor());
55147
55617
  }
55148
55618
  }
@@ -55187,8 +55657,10 @@ class KritzelCursorManager {
55187
55657
  if (!this._shadowRoot)
55188
55658
  return;
55189
55659
  const elementsAtPoint = this._shadowRoot.elementsFromPoint(ev.clientX, ev.clientY);
55190
- if (!elementsAtPoint || elementsAtPoint.length === 0)
55660
+ if (!elementsAtPoint || elementsAtPoint.length === 0) {
55661
+ this.resetToDefault();
55191
55662
  return;
55663
+ }
55192
55664
  // Check for resize handle overlays (selection group)
55193
55665
  const resizeHandleOverlay = elementsAtPoint.find(el => el.classList.contains('resize-handle-overlay'));
55194
55666
  if (resizeHandleOverlay) {
@@ -55198,19 +55670,19 @@ class KritzelCursorManager {
55198
55670
  const isTopLeft = resizeHandleOverlay.classList.contains('top-left');
55199
55671
  const isBottomRight = resizeHandleOverlay.classList.contains('bottom-right');
55200
55672
  const rotationOffset = (isTopLeft || isBottomRight) ? -45 : 45;
55201
- this._core.store.state.cursor = { icon: 'move-vertical', rotation: rotationDegrees + rotationOffset };
55673
+ this._core.store.state.cursor = { icon: 'moveVertical', rotation: rotationDegrees + rotationOffset };
55202
55674
  return;
55203
55675
  }
55204
55676
  // Check for rotation handle overlay
55205
55677
  const rotationHandleOverlay = elementsAtPoint.find(el => el.classList.contains('rotation-handle-overlay'));
55206
55678
  if (rotationHandleOverlay) {
55207
- this._core.store.state.cursor = { icon: 'hand', iconActive: 'hand-grab' };
55679
+ this._core.store.state.cursor = { icon: 'hand', iconActive: 'handGrab' };
55208
55680
  return;
55209
55681
  }
55210
55682
  // Check for line handle overlays (selection line)
55211
55683
  const lineHandleOverlay = elementsAtPoint.find(el => el.classList.contains('selection-line-handle-overlay'));
55212
55684
  if (lineHandleOverlay) {
55213
- this._core.store.state.cursor = { icon: 'hand', iconActive: 'hand-grab' };
55685
+ this._core.store.state.cursor = { icon: 'hand', iconActive: 'handGrab' };
55214
55686
  return;
55215
55687
  }
55216
55688
  // Reset cursor if not hovering any handle
@@ -55245,7 +55717,8 @@ class KritzelCursorManager {
55245
55717
  cleanup() {
55246
55718
  if (this._targetElement) {
55247
55719
  this._targetElement.style.cursor = '';
55248
- this._targetElement.style.removeProperty('--kritzel-pointer-cursor');
55720
+ this._targetElement.style.removeProperty('--kritzel-global-default-cursor');
55721
+ this._targetElement.style.removeProperty('--kritzel-global-pointer-cursor');
55249
55722
  }
55250
55723
  this._targetElement = null;
55251
55724
  this._shadowRoot = null;
@@ -57148,7 +57621,7 @@ class KritzelCore {
57148
57621
  this._store.state.translateY = viewportHeight / 2 - (viewport.centerWorldY ?? 0) * scale;
57149
57622
  this._store.state.scale = scale;
57150
57623
  await objectsMap.initialize(this, activeWorkspace.id, this._syncConfig);
57151
- this.seedWorkspaceObjects(activeWorkspace, objectsMap);
57624
+ await this.seedWorkspaceObjects(activeWorkspace, objectsMap);
57152
57625
  // Set local user in awareness after object map is initialized
57153
57626
  if (this._user) {
57154
57627
  objectsMap.setLocalUser(this._user);
@@ -57162,15 +57635,21 @@ class KritzelCore {
57162
57635
  this.engine.emitObjectsChange();
57163
57636
  this.rerender();
57164
57637
  }
57165
- seedWorkspaceObjects(workspace, objectsMap) {
57638
+ async seedWorkspaceObjects(workspace, objectsMap) {
57166
57639
  if (!workspace.objects?.length) {
57167
57640
  return;
57168
57641
  }
57642
+ const pendingObjects = workspace.objects.filter(object => !objectsMap.findById(object.id));
57643
+ // Mirrors engine.addObject()/addObjects(): image asset upload is async and must
57644
+ // happen before the (synchronous) Yjs transaction that inserts the objects.
57645
+ for (const object of pendingObjects) {
57646
+ object._core = this;
57647
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelImage')) {
57648
+ await object.prepareForInsert();
57649
+ }
57650
+ }
57169
57651
  objectsMap.transaction(() => {
57170
- for (const object of workspace.objects) {
57171
- if (objectsMap.findById(object.id)) {
57172
- continue;
57173
- }
57652
+ for (const object of pendingObjects) {
57174
57653
  this.prepareObjectForWorkspace(object, workspace.id, this._store.currentZIndex, this._store.state.scale);
57175
57654
  objectsMap.insert(object);
57176
57655
  }
@@ -57209,16 +57688,30 @@ class KritzelCore {
57209
57688
  * @param scaleFallback - Scale applied to objects that do not define one
57210
57689
  * @returns The objects that were added, updated and removed
57211
57690
  */
57212
- reconcileWorkspaceObjects(objectsMap, workspaceId, desiredObjects, nextZIndex, scaleFallback) {
57691
+ async reconcileWorkspaceObjects(objectsMap, workspaceId, desiredObjects, nextZIndex, scaleFallback) {
57213
57692
  const existingObjects = objectsMap.allObjects().filter(o => !(o instanceof KritzelSelectionGroup) && !(o instanceof KritzelSelectionBox));
57214
57693
  const existingById = new Map(existingObjects.map(object => [object.id, object]));
57215
57694
  const desiredIds = new Set();
57216
57695
  const diff = { added: [], updated: [], removed: [] };
57696
+ // Assign IDs up front so the insert-vs-update lookup below is stable, and so the
57697
+ // async image-prepare pass and the (synchronous) transaction agree on identity.
57698
+ for (const object of desiredObjects) {
57699
+ if (!object.id) {
57700
+ object.id = object.generateId();
57701
+ }
57702
+ }
57703
+ // Mirrors engine.addObject()/addObjects(): image asset upload is async and must
57704
+ // happen before the (synchronous) Yjs transaction that inserts the objects.
57705
+ for (const object of desiredObjects) {
57706
+ const existingObject = existingById.get(object.id);
57707
+ const isNewOrReplaced = !existingObject || existingObject !== object;
57708
+ if (isNewOrReplaced && KritzelClassHelper.isInstanceOf(object, 'KritzelImage')) {
57709
+ object._core = this;
57710
+ await object.prepareForInsert();
57711
+ }
57712
+ }
57217
57713
  objectsMap.transaction(() => {
57218
57714
  for (const object of desiredObjects) {
57219
- if (!object.id) {
57220
- object.id = object.generateId();
57221
- }
57222
57715
  desiredIds.add(object.id);
57223
57716
  const existingObject = existingById.get(object.id);
57224
57717
  if (!existingObject) {
@@ -57256,7 +57749,7 @@ class KritzelCore {
57256
57749
  * object set stays exactly what the caller declared.
57257
57750
  */
57258
57751
  detachObject(object) {
57259
- if (KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
57752
+ if (ObjectHelper.isKritzelEmbeddedObject(object)) {
57260
57753
  object.unmount();
57261
57754
  }
57262
57755
  object.isMounted = false;
@@ -57432,13 +57925,13 @@ class KritzelCore {
57432
57925
  activeWorkspace.viewport = { centerWorldX, centerWorldY, scale };
57433
57926
  }
57434
57927
  }
57435
- applyObjectsToActiveWorkspace(workspace, desiredObjects) {
57928
+ async applyObjectsToActiveWorkspace(workspace, desiredObjects) {
57436
57929
  const objectsMap = this._store.objects;
57437
57930
  if (!objectsMap) {
57438
57931
  return null;
57439
57932
  }
57440
57933
  this.clearSelection();
57441
- const diff = this.reconcileWorkspaceObjects(objectsMap, workspace.id, desiredObjects, () => this._store.currentZIndex, this._store.state.scale);
57934
+ const diff = await this.reconcileWorkspaceObjects(objectsMap, workspace.id, desiredObjects, () => this._store.currentZIndex, this._store.state.scale);
57442
57935
  this._anchorManager.rebuildIndex();
57443
57936
  this.rerender();
57444
57937
  return diff;
@@ -57457,7 +57950,7 @@ class KritzelCore {
57457
57950
  try {
57458
57951
  await objectsMap.initialize(this, workspace.id, this._syncConfig, { headless: true, workspaceName: workspace.name });
57459
57952
  await objectsMap.whenNetworkSynced();
57460
- return this.reconcileWorkspaceObjects(objectsMap, workspace.id, desiredObjects, () => this.nextZIndexOf(objectsMap), this._store.state.scale);
57953
+ return await this.reconcileWorkspaceObjects(objectsMap, workspace.id, desiredObjects, () => this.nextZIndexOf(objectsMap), this._store.state.scale);
57461
57954
  }
57462
57955
  finally {
57463
57956
  objectsMap.destroy();
@@ -57543,8 +58036,7 @@ class KritzelCore {
57543
58036
  this.removeObject(child);
57544
58037
  });
57545
58038
  }
57546
- // Unmount dynamic objects before removal
57547
- if (KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
58039
+ if (ObjectHelper.isKritzelEmbeddedObject(object)) {
57548
58040
  object.unmount();
57549
58041
  }
57550
58042
  // Handle anchor cleanup
@@ -57590,14 +58082,8 @@ class KritzelCore {
57590
58082
  this.removeLocalSelectionGroups();
57591
58083
  this.removeSelectionBox();
57592
58084
  for (const object of selectionGroup.objects ?? []) {
57593
- if (KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
57594
- const dynamicObject = object;
57595
- if (typeof dynamicObject.setIsInteractive === 'function') {
57596
- dynamicObject.setIsInteractive(true);
57597
- }
57598
- else {
57599
- dynamicObject.isInteractive = true;
57600
- }
58085
+ if (ObjectHelper.isKritzelEmbeddedObject(object)) {
58086
+ object.setIsInteractive(true);
57601
58087
  }
57602
58088
  }
57603
58089
  this._store.objects.insert(selectionGroup);
@@ -57618,14 +58104,8 @@ class KritzelCore {
57618
58104
  teardownSelectionGroup(selectionGroup) {
57619
58105
  selectionGroup.deselectAllChildren?.();
57620
58106
  for (const object of selectionGroup.objects ?? []) {
57621
- if (KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
57622
- const dynamicObject = object;
57623
- if (typeof dynamicObject.setIsInteractive === 'function') {
57624
- dynamicObject.setIsInteractive(false);
57625
- }
57626
- else {
57627
- dynamicObject.isInteractive = false;
57628
- }
58107
+ if (ObjectHelper.isKritzelEmbeddedObject(object)) {
58108
+ object.setIsInteractive(false);
57629
58109
  }
57630
58110
  }
57631
58111
  }
@@ -59071,13 +59551,7 @@ class KritzelWheelHelper {
59071
59551
  return this.canElementConsumeWheel(scrollContainer, ev.deltaX, ev.deltaY);
59072
59552
  }
59073
59553
  static getScrollableContainerFromEventPath(path, dynamicObject) {
59074
- const potentialRoots = [];
59075
- if (dynamicObject.element instanceof HTMLElement) {
59076
- potentialRoots.push(dynamicObject.element);
59077
- }
59078
- if (dynamicObject.elementRef instanceof HTMLElement) {
59079
- potentialRoots.push(dynamicObject.elementRef);
59080
- }
59554
+ const potentialRoots = dynamicObject.getEmbeddedContentRoots();
59081
59555
  const legacyElementRef = dynamicObject.elmeentRef;
59082
59556
  if (legacyElementRef instanceof HTMLElement) {
59083
59557
  potentialRoots.push(legacyElementRef);
@@ -59254,8 +59728,8 @@ class KritzelSvgExportHelper {
59254
59728
  .join('\n ');
59255
59729
  const xmlDecl = includeXmlDeclaration ? '<?xml version="1.0" encoding="UTF-8"?>\n' : '';
59256
59730
  const defsContent = defs.length > 0 ? `\n <defs>\n ${defs.join('\n ')}\n </defs>` : '';
59257
- return `${xmlDecl}<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBoxX} ${viewBoxY} ${viewBoxWidth} ${viewBoxHeight}" width="${viewBoxWidth}" height="${viewBoxHeight}">${defsContent}
59258
- ${elements}
59731
+ return `${xmlDecl}<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBoxX} ${viewBoxY} ${viewBoxWidth} ${viewBoxHeight}" width="${viewBoxWidth}" height="${viewBoxHeight}">${defsContent}
59732
+ ${elements}
59259
59733
  </svg>`;
59260
59734
  }
59261
59735
  /**
@@ -59407,8 +59881,8 @@ class KritzelSvgExportHelper {
59407
59881
  const orient = end === 'start' ? 'auto-start-reverse' : 'auto';
59408
59882
  const refX = 0;
59409
59883
  const refY = size / 2;
59410
- return `<marker id="${markerId}" markerWidth="${size}" markerHeight="${size}" refX="${refX}" refY="${refY}" orient="${orient}" markerUnits="userSpaceOnUse">
59411
- <path d="${path}" fill="${fill}" transform="scale(${size / 10})"/>
59884
+ return `<marker id="${markerId}" markerWidth="${size}" markerHeight="${size}" refX="${refX}" refY="${refY}" orient="${orient}" markerUnits="userSpaceOnUse">
59885
+ <path d="${path}" fill="${fill}" transform="scale(${size / 10})"/>
59412
59886
  </marker>`;
59413
59887
  }
59414
59888
  /**
@@ -59437,6 +59911,9 @@ class KritzelSvgExportHelper {
59437
59911
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
59438
59912
  return this.dynamicObjectToSvg(object, rasterSafeForCanvas);
59439
59913
  }
59914
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelIframeObject')) {
59915
+ return this.iframeObjectToSvg(object);
59916
+ }
59440
59917
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelGroup')) {
59441
59918
  return this.groupToSvg(object, theme, imageDataUrls, rasterSafeForCanvas);
59442
59919
  }
@@ -59455,10 +59932,10 @@ class KritzelSvgExportHelper {
59455
59932
  const opacity = path.opacity !== 1 ? ` opacity="${path.opacity}"` : '';
59456
59933
  const scale = this.getObjectScale(path);
59457
59934
  // Path needs to be wrapped in a g with translation since path.d uses local coordinates
59458
- return `<g transform="${transform}"${opacity}>
59459
- <svg viewBox="${path.viewBox}" width="${path.totalWidth / scale}" height="${path.totalHeight / scale}" overflow="visible">
59460
- <path d="${path.d}" fill="${fill || 'none'}" stroke="${stroke || 'none'}"${path.strokeWidth ? ` stroke-width="${path.strokeWidth}"` : ''}/>
59461
- </svg>
59935
+ return `<g transform="${transform}"${opacity}>
59936
+ <svg viewBox="${path.viewBox}" width="${path.totalWidth / scale}" height="${path.totalHeight / scale}" overflow="visible">
59937
+ <path d="${path.d}" fill="${fill || 'none'}" stroke="${stroke || 'none'}"${path.strokeWidth ? ` stroke-width="${path.strokeWidth}"` : ''}/>
59938
+ </svg>
59462
59939
  </g>`;
59463
59940
  }
59464
59941
  /**
@@ -59474,10 +59951,10 @@ class KritzelSvgExportHelper {
59474
59951
  const scale = this.getObjectScale(line);
59475
59952
  const markerStart = line.hasStartArrow ? ` marker-start="url(#${line.startMarkerId})"` : '';
59476
59953
  const markerEnd = line.hasEndArrow ? ` marker-end="url(#${line.endMarkerId})"` : '';
59477
- return `<g transform="${transform}"${opacity}>
59478
- <svg viewBox="${line.viewBox}" width="${line.totalWidth / scale}" height="${line.totalHeight / scale}" overflow="visible">
59479
- <path d="${line.d}" fill="none" stroke="${stroke}" stroke-width="${line.strokeWidth}" stroke-linecap="round"${markerStart}${markerEnd}/>
59480
- </svg>
59954
+ return `<g transform="${transform}"${opacity}>
59955
+ <svg viewBox="${line.viewBox}" width="${line.totalWidth / scale}" height="${line.totalHeight / scale}" overflow="visible">
59956
+ <path d="${line.d}" fill="none" stroke="${stroke}" stroke-width="${line.strokeWidth}" stroke-linecap="round"${markerStart}${markerEnd}/>
59957
+ </svg>
59481
59958
  </g>`;
59482
59959
  }
59483
59960
  /**
@@ -59501,18 +59978,18 @@ class KritzelSvgExportHelper {
59501
59978
  const textHtml = this.prosemirrorToHtml(shape.content, shape, theme);
59502
59979
  if (textHtml) {
59503
59980
  const fontColor = KritzelColorHelper.resolveThemeColor(shape.fontColor, theme);
59504
- textContent = `
59505
- <foreignObject x="${shape.x}" y="${shape.y}" width="${shape.width}" height="${shape.height}">
59506
- <div xmlns="http://www.w3.org/1999/xhtml" style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; text-align: center; font-family: ${shape.fontFamily}; font-size: ${shape.fontSize}px; color: ${fontColor}; overflow: hidden;">
59507
- ${textHtml}
59508
- </div>
59981
+ textContent = `
59982
+ <foreignObject x="${shape.x}" y="${shape.y}" width="${shape.width}" height="${shape.height}">
59983
+ <div xmlns="http://www.w3.org/1999/xhtml" style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; text-align: center; font-family: ${shape.fontFamily}; font-size: ${shape.fontSize}px; color: ${fontColor}; overflow: hidden;">
59984
+ ${textHtml}
59985
+ </div>
59509
59986
  </foreignObject>`;
59510
59987
  }
59511
59988
  }
59512
- return `<g transform="${transform}"${opacity}>
59513
- <svg viewBox="${shape.viewBox}" width="${shape.totalWidth / scale}" height="${shape.totalHeight / scale}" overflow="visible" preserveAspectRatio="none">
59514
- <path d="${pathD}" fill="${fill || 'transparent'}" stroke="${stroke}" stroke-width="${shape.strokeWidth}"/>${textContent}
59515
- </svg>
59989
+ return `<g transform="${transform}"${opacity}>
59990
+ <svg viewBox="${shape.viewBox}" width="${shape.totalWidth / scale}" height="${shape.totalHeight / scale}" overflow="visible" preserveAspectRatio="none">
59991
+ <path d="${pathD}" fill="${fill || 'transparent'}" stroke="${stroke}" stroke-width="${shape.strokeWidth}"/>${textContent}
59992
+ </svg>
59516
59993
  </g>`;
59517
59994
  }
59518
59995
  /**
@@ -59543,10 +60020,10 @@ class KritzelSvgExportHelper {
59543
60020
  // adjusted font-size already accounts for it – keeping both would double-scale
59544
60021
  // the content and overflow the foreignObject.
59545
60022
  const svgFontSizePx = (text.fontSize * (4 / 3) * (text.scaleFactor || 1) / scale).toFixed(4);
59546
- return `<g transform="${transform}"${opacity}>
59547
- <foreignObject x="0" y="0" width="${width}" height="${height}" overflow="visible">
59548
- <div xmlns="http://www.w3.org/1999/xhtml" style="font-family: ${text.fontFamily}; font-size: ${svgFontSizePx}px; color: ${fontColor}; background-color: ${bgColor || 'transparent'}; white-space: pre-wrap; word-wrap: break-word; width: 100%; margin: 0; padding: 0; box-sizing: border-box;">${htmlContent.trim()}</div>
59549
- </foreignObject>
60023
+ return `<g transform="${transform}"${opacity}>
60024
+ <foreignObject x="0" y="0" width="${width}" height="${height}" overflow="visible">
60025
+ <div xmlns="http://www.w3.org/1999/xhtml" style="font-family: ${text.fontFamily}; font-size: ${svgFontSizePx}px; color: ${fontColor}; background-color: ${bgColor || 'transparent'}; white-space: pre-wrap; word-wrap: break-word; width: 100%; margin: 0; padding: 0; box-sizing: border-box;">${htmlContent.trim()}</div>
60026
+ </foreignObject>
59550
60027
  </g>`;
59551
60028
  }
59552
60029
  /**
@@ -59580,9 +60057,9 @@ class KritzelSvgExportHelper {
59580
60057
  return `<tspan x="0" y="${y}" xml:space="preserve">${this.escapeHtml(line)}</tspan>`;
59581
60058
  })
59582
60059
  .join('');
59583
- return `<g transform="${transform}"${opacity}>
59584
- ${bgRect}
59585
- <text font-family="${this.escapeHtml(text.fontFamily)}" font-size="${resolvedFontSizePx}" fill="${fontColor}">${tspans}</text>
60060
+ return `<g transform="${transform}"${opacity}>
60061
+ ${bgRect}
60062
+ <text font-family="${this.escapeHtml(text.fontFamily)}" font-size="${resolvedFontSizePx}" fill="${fontColor}">${tspans}</text>
59586
60063
  </g>`;
59587
60064
  }
59588
60065
  /**
@@ -59610,8 +60087,8 @@ class KritzelSvgExportHelper {
59610
60087
  const width = image.totalWidth / scale;
59611
60088
  const height = image.totalHeight / scale;
59612
60089
  const href = imageDataUrls?.get(image.id) || image.resolvedSrc || image.src || '';
59613
- return `<g transform="${transform}"${opacity}>
59614
- <image href="${href}" x="0" y="0" width="${width}" height="${height}" preserveAspectRatio="xMidYMid meet"/>
60090
+ return `<g transform="${transform}"${opacity}>
60091
+ <image href="${href}" x="0" y="0" width="${width}" height="${height}" preserveAspectRatio="xMidYMid meet"/>
59615
60092
  </g>`;
59616
60093
  }
59617
60094
  /**
@@ -59631,19 +60108,30 @@ class KritzelSvgExportHelper {
59631
60108
  if (rasterSafeForCanvas) {
59632
60109
  // PNG export: render placeholder rect (foreignObject doesn't rasterize to PNG well)
59633
60110
  console.warn('[KritzelDynamicObject SVG Export] Dynamic objects do not serialize well to PNG. Use SVG export or screenshot instead.');
59634
- return `<g transform="${transform}"${opacity}>
59635
- <rect x="0" y="0" width="${width}" height="${height}" fill="#f0f0f0" stroke="#999" stroke-width="1" />
59636
- <text x="${width / 2}" y="${height / 2}" text-anchor="middle" dominant-baseline="middle" font-size="12" fill="#999">[Dynamic Object]</text>
60111
+ return `<g transform="${transform}"${opacity}>
60112
+ <rect x="0" y="0" width="${width}" height="${height}" fill="#f0f0f0" stroke="#999" stroke-width="1" />
60113
+ <text x="${width / 2}" y="${height / 2}" text-anchor="middle" dominant-baseline="middle" font-size="12" fill="#999">[Dynamic Object]</text>
59637
60114
  </g>`;
59638
60115
  }
59639
60116
  // SVG export: use foreignObject to embed HTML
59640
60117
  const html = obj.element?.outerHTML || '<div style="border:1px dashed #ccc">Empty</div>';
59641
- return `<g transform="${transform}"${opacity}>
59642
- <foreignObject x="0" y="0" width="${width}" height="${height}">
59643
- <div xmlns="http://www.w3.org/1999/xhtml" style="width:100%;height:100%;overflow:hidden;">
59644
- ${html}
59645
- </div>
59646
- </foreignObject>
60118
+ return `<g transform="${transform}"${opacity}>
60119
+ <foreignObject x="0" y="0" width="${width}" height="${height}">
60120
+ <div xmlns="http://www.w3.org/1999/xhtml" style="width:100%;height:100%;overflow:hidden;">
60121
+ ${html}
60122
+ </div>
60123
+ </foreignObject>
60124
+ </g>`;
60125
+ }
60126
+ static iframeObjectToSvg(object) {
60127
+ const transform = this.buildTransform(object);
60128
+ const opacity = object.opacity !== 1 ? ` opacity="${object.opacity}"` : '';
60129
+ const scale = this.getObjectScale(object);
60130
+ const width = object.totalWidth / scale;
60131
+ const height = object.totalHeight / scale;
60132
+ return `<g transform="${transform}"${opacity}>
60133
+ <rect x="0" y="0" width="${width}" height="${height}" fill="#f0f0f0" stroke="#999" stroke-width="1" />
60134
+ <text x="${width / 2}" y="${height / 2}" text-anchor="middle" dominant-baseline="middle" font-size="12" fill="#999">[Iframe]</text>
59647
60135
  </g>`;
59648
60136
  }
59649
60137
  /**
@@ -59663,8 +60151,8 @@ class KritzelSvgExportHelper {
59663
60151
  return '';
59664
60152
  }
59665
60153
  const opacity = group.opacity !== 1 ? ` opacity="${group.opacity}"` : '';
59666
- return `<g${opacity}>
59667
- ${children}
60154
+ return `<g${opacity}>
60155
+ ${children}
59668
60156
  </g>`;
59669
60157
  }
59670
60158
  /**
@@ -59849,12 +60337,26 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
59849
60337
  /** Authoritative workspace catalog for this engine instance. When provided, provider-only workspaces are hidden but not deleted. */
59850
60338
  workspaces;
59851
60339
  async onWorkspacesPropChange() {
59852
- if (!this._isYjsInitialized) {
60340
+ this._workspacesRevision++;
60341
+ if (!this._isYjsInitialized || this._syncInitPromise) {
59853
60342
  return;
59854
60343
  }
59855
- await this.reconcileProvidedWorkspaces();
59856
- await this.ensureActiveWorkspaceIsVisible();
59857
- this.workspacesChange.emit(this.getVisibleWorkspaces());
60344
+ const previousReconciliation = this._workspacePropReconciliationPromise;
60345
+ const reconciliationPromise = (async () => {
60346
+ await previousReconciliation;
60347
+ await this.reconcileProvidedWorkspaces();
60348
+ await this.ensureActiveWorkspaceIsVisible();
60349
+ this.workspacesChange.emit(this.getVisibleWorkspaces());
60350
+ })();
60351
+ this._workspacePropReconciliationPromise = reconciliationPromise;
60352
+ try {
60353
+ await reconciliationPromise;
60354
+ }
60355
+ finally {
60356
+ if (this._workspacePropReconciliationPromise === reconciliationPromise) {
60357
+ this._workspacePropReconciliationPromise = null;
60358
+ }
60359
+ }
59858
60360
  }
59859
60361
  /** Optional unique identifier for namespacing storage keys across multiple editor instances. */
59860
60362
  editorId;
@@ -59908,6 +60410,12 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
59908
60410
  onUserChange(newValue) {
59909
60411
  this.core.setUser(newValue);
59910
60412
  }
60413
+ /** Tool definitions to register with this engine instance. */
60414
+ tools = [];
60415
+ onToolsChange() {
60416
+ this._toolsRevision++;
60417
+ void this.registerConfiguredTools();
60418
+ }
59911
60419
  /** Context menu items shown when right-clicking the canvas background. */
59912
60420
  globalContextMenuItems;
59913
60421
  onGlobalContextMenuItemsChange(newValue) {
@@ -60098,7 +60606,7 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
60098
60606
  }
60099
60607
  const hoveredObject = this.core.getObjectFromPointerEvent(ev);
60100
60608
  if (hoveredObject &&
60101
- ObjectHelper.isClass(hoveredObject, 'KritzelDynamicObject') &&
60609
+ ObjectHelper.isKritzelEmbeddedObject(hoveredObject) &&
60102
60610
  KritzelWheelHelper.shouldKeepWheelInDynamicObject(ev, hoveredObject)) {
60103
60611
  return;
60104
60612
  }
@@ -60200,7 +60708,7 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
60200
60708
  return false;
60201
60709
  }
60202
60710
  const object = this.core.store.objects.findById(objectElement.id);
60203
- if (!object || !KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
60711
+ if (!object || !ObjectHelper.isKritzelEmbeddedObject(object)) {
60204
60712
  return false;
60205
60713
  }
60206
60714
  const isSelected = !!this.core.store.selectionGroup?.objects?.some(selectedObject => selectedObject.id === object.id);
@@ -60210,13 +60718,7 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
60210
60718
  if (object.isInteractive !== true) {
60211
60719
  return false;
60212
60720
  }
60213
- const potentialRoots = [];
60214
- if (object.element instanceof H) {
60215
- potentialRoots.push(object.element);
60216
- }
60217
- if (object.elementRef instanceof H) {
60218
- potentialRoots.push(object.elementRef);
60219
- }
60721
+ const potentialRoots = object.getEmbeddedContentRoots();
60220
60722
  if (potentialRoots.length === 0) {
60221
60723
  return false;
60222
60724
  }
@@ -60390,7 +60892,7 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
60390
60892
  * Switches the active drawing tool by its registered name.
60391
60893
  * @param toolName - The name the tool was registered with (e.g., 'brush', 'eraser').
60392
60894
  */
60393
- async changeActiveTool(toolName) {
60895
+ async setActiveTool(toolName) {
60394
60896
  const tool = this.core.toolRegistry.getTool(toolName);
60395
60897
  if (!tool) {
60396
60898
  console.warn(`[KritzelEngine] No tool registered with name: ${toolName}`);
@@ -61696,9 +62198,13 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
61696
62198
  _stateChangeListenersRegistered = false;
61697
62199
  _workspaceInitializationPromise = null;
61698
62200
  _workspaceInitializationTargetKey = null;
62201
+ _workspacePropReconciliationPromise = null;
61699
62202
  _syncInitPromise = null;
62203
+ _toolsInitPromise = null;
62204
+ _toolsRevision = 0;
61700
62205
  _syncConfigRevision = 0;
61701
62206
  _assetStorageConfigRevision = 0;
62207
+ _workspacesRevision = 0;
61702
62208
  _hasLoggedUnlicensedWelcomeBanner = false;
61703
62209
  _previousVisibleDynamicObjects = new Set();
61704
62210
  _revealedImageKeys = new Set();
@@ -61711,6 +62217,37 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
61711
62217
  undoStackSize: 0,
61712
62218
  redoStackSize: 0,
61713
62219
  };
62220
+ /** Returns a tool registered in this engine instance. */
62221
+ async getTool(toolName) {
62222
+ await this.registerConfiguredTools();
62223
+ return this.core.toolRegistry.getTool(toolName);
62224
+ }
62225
+ async registerConfiguredTools() {
62226
+ if (this._toolsInitPromise) {
62227
+ return this._toolsInitPromise;
62228
+ }
62229
+ this._toolsInitPromise = (async () => {
62230
+ do {
62231
+ const revision = this._toolsRevision;
62232
+ for (const definition of this.tools) {
62233
+ let toolClass = definition.tool;
62234
+ if (typeof toolClass !== 'function') {
62235
+ toolClass = toolClass.constructor;
62236
+ }
62237
+ await this.registerTool(definition.name, toolClass, definition.config);
62238
+ }
62239
+ if (revision === this._toolsRevision) {
62240
+ break;
62241
+ }
62242
+ } while (true);
62243
+ })();
62244
+ try {
62245
+ await this._toolsInitPromise;
62246
+ }
62247
+ finally {
62248
+ this._toolsInitPromise = null;
62249
+ }
62250
+ }
61714
62251
  setLoadingState(isLoading) {
61715
62252
  if (this.core.store.state.isLoading === isLoading) {
61716
62253
  return;
@@ -61842,6 +62379,8 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
61842
62379
  this.core.licenseManager.destroy();
61843
62380
  }
61844
62381
  componentWillLoad() {
62382
+ this._toolsRevision++;
62383
+ void this.registerConfiguredTools();
61845
62384
  this.core.setEditorId(this.editorId);
61846
62385
  if (this.themes && this.themes.length > 0) {
61847
62386
  this.core.themeManager.registerThemes(this.themes);
@@ -61881,7 +62420,9 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
61881
62420
  const editorElement = this.host.closest('kritzel-editor');
61882
62421
  this.core.themeManager.setTargetElement(editorElement || this.host);
61883
62422
  // Initialize sync config and Yjs
62423
+ await this.registerConfiguredTools();
61884
62424
  await this.initializeSyncAndWorkspace();
62425
+ await this.waitForWorkspaceAndSceneReady();
61885
62426
  if (this.core.store.state.isReady === false) {
61886
62427
  this.core.store.state.isReady = true;
61887
62428
  this.logUnlicensedWelcomeBanner();
@@ -61903,6 +62444,20 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
61903
62444
  this._syncInitPromise = null;
61904
62445
  }
61905
62446
  }
62447
+ async waitForSceneRender() {
62448
+ await new Promise(resolve => {
62449
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
62450
+ });
62451
+ }
62452
+ async waitForWorkspaceAndSceneReady() {
62453
+ let observedRevision;
62454
+ do {
62455
+ observedRevision = this._workspacesRevision;
62456
+ await this._workspacePropReconciliationPromise;
62457
+ await this.waitForSceneRender();
62458
+ } while (observedRevision !== this._workspacesRevision ||
62459
+ this._workspacePropReconciliationPromise);
62460
+ }
61906
62461
  /**
61907
62462
  * Initializes the asset storage layer with the latest config. Mirrors
61908
62463
  * the in-flight replay logic of {@link doInitializeSyncAndWorkspace} for
@@ -62285,7 +62840,7 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
62285
62840
  if (!KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup')) {
62286
62841
  return 'auto';
62287
62842
  }
62288
- if (object.objects.length === 1 && KritzelClassHelper.isInstanceOf(object.objects[0], 'KritzelDynamicObject')) {
62843
+ if (object.objects.length === 1 && ObjectHelper.isKritzelEmbeddedObject(object.objects[0])) {
62289
62844
  return 'none';
62290
62845
  }
62291
62846
  return 'auto';
@@ -62293,7 +62848,7 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
62293
62848
  // Track visible dynamic objects and unmount culled ones
62294
62849
  const currentVisibleDynamicObjects = new Set();
62295
62850
  visibleObjects.forEach(object => {
62296
- if (KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
62851
+ if (ObjectHelper.isKritzelEmbeddedObject(object)) {
62297
62852
  currentVisibleDynamicObjects.add(object.id);
62298
62853
  }
62299
62854
  });
@@ -62301,7 +62856,7 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
62301
62856
  this._previousVisibleDynamicObjects.forEach(id => {
62302
62857
  if (!currentVisibleDynamicObjects.has(id)) {
62303
62858
  const object = this.core.store.objects.findById(id);
62304
- if (object && KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
62859
+ if (object && ObjectHelper.isKritzelEmbeddedObject(object)) {
62305
62860
  object.unmount();
62306
62861
  }
62307
62862
  }
@@ -62394,7 +62949,7 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
62394
62949
  display: 'flex',
62395
62950
  alignItems: 'center',
62396
62951
  justifyContent: 'center',
62397
- } }, h("kritzel-icon", { name: object.loadState === 'error' ? 'image-off' : 'image', size: Math.max(16, Math.min(object.totalWidth, object.totalHeight) * 0.3), style: {
62952
+ } }, h("kritzel-icon", { name: object.loadState === 'error' ? 'imageOff' : 'image', size: Math.max(16, Math.min(object.totalWidth, object.totalHeight) * 0.3), style: {
62398
62953
  color: KritzelColorHelper.resolveThemeColor({ light: '#9ca3af', dark: '#6b7280' }, currentTheme),
62399
62954
  } }))), KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject') && (h("div", { ref: el => el && object.mount(el), style: {
62400
62955
  position: 'absolute',
@@ -62413,7 +62968,37 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
62413
62968
  padding: object.padding + 'px',
62414
62969
  overflow: 'visible',
62415
62970
  display: 'block',
62416
- } })), KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup') && !this.core.displaySelectionLineUI(object) && (h("div", { ref: el => el && object.mount(el), style: {
62971
+ } })), KritzelClassHelper.isInstanceOf(object, 'KritzelIframeObject') && (h("div", { style: {
62972
+ position: 'absolute',
62973
+ left: '0',
62974
+ top: '0',
62975
+ width: object.totalWidth + 'px',
62976
+ height: object.totalHeight + 'px',
62977
+ transform: object.rotationDegrees !== 0 ? `rotate(${object.rotationDegrees}deg)` : undefined,
62978
+ transformOrigin: object.rotationDegrees !== 0 ? `${object.totalWidth / 2}px ${object.totalHeight / 2}px` : undefined,
62979
+ opacity: effectiveOpacityString,
62980
+ backgroundColor: KritzelColorHelper.resolveThemeColor(object.backgroundColor, currentTheme),
62981
+ borderColor: KritzelColorHelper.resolveThemeColor(object.borderColor, currentTheme),
62982
+ borderWidth: object.borderWidth + 'px',
62983
+ borderStyle: 'solid',
62984
+ borderRadius: object.borderRadius !== undefined ? (typeof object.borderRadius === 'number' ? `${object.borderRadius}px` : object.borderRadius) : undefined,
62985
+ boxShadow: object.boxShadow,
62986
+ overflow: 'hidden',
62987
+ display: 'block',
62988
+ pointerEvents: object.markedForRemoval ? 'none' : 'auto',
62989
+ } }, h("iframe", { ref: element => element && object.mount(element), title: object.title, sandbox: object.sandboxAttribute, style: {
62990
+ display: 'block',
62991
+ width: '100%',
62992
+ height: '100%',
62993
+ border: '0',
62994
+ borderRadius: object.borderRadius !== undefined ? (typeof object.borderRadius === 'number' ? `${object.borderRadius}px` : object.borderRadius) : undefined,
62995
+ pointerEvents: object.isInteractive ? 'auto' : 'none',
62996
+ } }), h("div", { style: {
62997
+ position: 'absolute',
62998
+ inset: '0',
62999
+ borderRadius: object.borderRadius !== undefined ? (typeof object.borderRadius === 'number' ? `${object.borderRadius}px` : object.borderRadius) : undefined,
63000
+ pointerEvents: object.isInteractive ? 'none' : 'auto',
63001
+ } }))), KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup') && !this.core.displaySelectionLineUI(object) && (h("div", { ref: el => el && object.mount(el), style: {
62417
63002
  position: 'absolute',
62418
63003
  left: '0',
62419
63004
  top: '0',
@@ -62771,6 +63356,9 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
62771
63356
  "user": [{
62772
63357
  "onUserChange": 0
62773
63358
  }],
63359
+ "tools": [{
63360
+ "onToolsChange": 0
63361
+ }],
62774
63362
  "globalContextMenuItems": [{
62775
63363
  "onGlobalContextMenuItemsChange": 0
62776
63364
  }],
@@ -62831,6 +63419,7 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
62831
63419
  "syncConfig": [16],
62832
63420
  "assetStorageConfig": [16],
62833
63421
  "user": [16],
63422
+ "tools": [16],
62834
63423
  "globalContextMenuItems": [16],
62835
63424
  "objectContextMenuItems": [16],
62836
63425
  "scaleMax": [1026, "scale-max"],
@@ -62855,7 +63444,7 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
62855
63444
  "beginSceneBootstrap": [64],
62856
63445
  "endSceneBootstrap": [64],
62857
63446
  "registerTool": [64],
62858
- "changeActiveTool": [64],
63447
+ "setActiveTool": [64],
62859
63448
  "disable": [64],
62860
63449
  "enable": [64],
62861
63450
  "delete": [64],
@@ -62931,7 +63520,8 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
62931
63520
  "getAvailableLocales": [64],
62932
63521
  "getAvailableLocaleOptions": [64],
62933
63522
  "t": [64],
62934
- "getResolvedTerms": [64]
63523
+ "getResolvedTerms": [64],
63524
+ "getTool": [64]
62935
63525
  }, [[1, "wheel", "handleWheel"], [0, "pointerdown", "handlePointerDown"], [0, "pointermove", "handlePointerMove"], [0, "pointerup", "handlePointerUp"], [0, "pointercancel", "handlePointerCancel"], [1, "pointerleave", "handlePointerLeave"], [1, "longpress", "handleLongPress"], [0, "contextmenu", "handleContextMenu"], [9, "resize", "handleResize"], [8, "keydown", "handleKeyDown"], [8, "keyup", "handleKeyUp"], [8, "copy", "handleCopy"], [8, "cut", "handleCut"], [8, "paste", "handlePaste"], [4, "dblclick", "preventDoubleTapZoomOnTouchDevices"]], {
62936
63526
  "workspaces": [{
62937
63527
  "onWorkspacesPropChange": 0
@@ -62948,6 +63538,9 @@ const KritzelEngine = /*@__PURE__*/ proxyCustomElement(class KritzelEngine exten
62948
63538
  "user": [{
62949
63539
  "onUserChange": 0
62950
63540
  }],
63541
+ "tools": [{
63542
+ "onToolsChange": 0
63543
+ }],
62951
63544
  "globalContextMenuItems": [{
62952
63545
  "onGlobalContextMenuItemsChange": 0
62953
63546
  }],