@kritzel/engine 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 (83) hide show
  1. package/LICENSE.md +49 -49
  2. package/dist/cjs/{engine.constants-CakmdKMW.js → engine.constants-cfbepvaI.js} +595 -98
  3. package/dist/cjs/{index-BbbWU2aN.js → index-BrjM5aN2.js} +12 -0
  4. package/dist/cjs/index.cjs.js +4 -2
  5. package/dist/cjs/kritzel-awareness-cursors_5.cjs.entry.js +257 -113
  6. package/dist/cjs/loader.cjs.js +2 -2
  7. package/dist/cjs/stencil.cjs.js +2 -2
  8. package/dist/collection/classes/core/core.class.js +40 -32
  9. package/dist/collection/classes/core/reviver.class.js +4 -0
  10. package/dist/collection/classes/core/store.class.js +10 -3
  11. package/dist/collection/classes/managers/cursor.manager.js +10 -5
  12. package/dist/collection/classes/objects/dynamic-object.class.js +18 -5
  13. package/dist/collection/classes/objects/iframe.class.js +447 -0
  14. package/dist/collection/classes/registries/icon-registry.class.js +100 -67
  15. package/dist/collection/classes/tools/selection-tool.class.js +3 -3
  16. package/dist/collection/collection-manifest.json +3 -1
  17. package/dist/collection/components/kritzel-awareness-cursors/kritzel-awareness-cursors.css +115 -115
  18. package/dist/collection/components/kritzel-awareness-cursors/kritzel-awareness-cursors.js +1 -1
  19. package/dist/collection/components/kritzel-cursor-trail/kritzel-cursor-trail.css +10 -10
  20. package/dist/collection/components/kritzel-cursor-trail/kritzel-cursor-trail.js +1 -1
  21. package/dist/collection/components/kritzel-engine/kritzel-engine.css +80 -80
  22. package/dist/collection/components/kritzel-engine/kritzel-engine.js +196 -44
  23. package/dist/collection/components/kritzel-icon/kritzel-icon.css +18 -18
  24. package/dist/collection/components/kritzel-icon/kritzel-icon.js +25 -4
  25. package/dist/collection/components/kritzel-watermark/kritzel-watermark.css +29 -29
  26. package/dist/collection/components/kritzel-watermark/kritzel-watermark.js +2 -2
  27. package/dist/collection/constants/version.js +1 -1
  28. package/dist/collection/helpers/cursor.helper.js +8 -8
  29. package/dist/collection/helpers/object.helper.js +13 -0
  30. package/dist/collection/helpers/svg-export.helper.js +55 -41
  31. package/dist/collection/helpers/wheel.helper.js +1 -7
  32. package/dist/collection/index.js +3 -0
  33. package/dist/collection/interfaces/embedded-object.interface.js +1 -0
  34. package/dist/collection/themes/dark-theme.js +1 -5
  35. package/dist/collection/themes/light-theme.js +1 -6
  36. package/dist/components/index.js +7 -7
  37. package/dist/components/kritzel-awareness-cursors.js +1 -1
  38. package/dist/components/kritzel-cursor-trail.js +1 -1
  39. package/dist/components/kritzel-engine.js +723 -130
  40. package/dist/components/kritzel-icon.js +1 -1
  41. package/dist/components/kritzel-watermark.js +1 -1
  42. package/dist/components/{p-YP8wAcF4.js → p-9YjkpHyU.js} +2 -2
  43. package/dist/components/p-BIUT0gBe.js +214 -0
  44. package/dist/components/{p-B2Htv9ou.js → p-Bnuu1qip.js} +3 -3
  45. package/dist/components/{p-CfSx6HU_.js → p-CJ5B8cMK.js} +12 -1
  46. package/dist/components/{p-BvRGQvVI.js → p-DXnF4FeS.js} +18 -14
  47. package/dist/esm/{engine.constants-Bl5-pbq-.js → engine.constants-DPDQhv6S.js} +594 -99
  48. package/dist/esm/{index-BU9Mv7y0.js → index-Hqbtnnex.js} +12 -1
  49. package/dist/esm/index.js +3 -3
  50. package/dist/esm/kritzel-awareness-cursors_5.entry.js +257 -113
  51. package/dist/esm/loader.js +3 -3
  52. package/dist/esm/stencil.js +3 -3
  53. package/dist/stencil/index.esm.js +3 -3
  54. package/dist/stencil/{p-Bl5-pbq-.js → p-DPDQhv6S.js} +594 -99
  55. package/dist/stencil/{p-BU9Mv7y0.js → p-Hqbtnnex.js} +12 -1
  56. package/dist/stencil/{p-f0a8b7af.entry.js → p-d678528e.entry.js} +257 -113
  57. package/dist/stencil/stencil.esm.js +3 -3
  58. package/dist/types/classes/core/store.class.d.ts +3 -2
  59. package/dist/types/classes/objects/dynamic-object.class.d.ts +4 -2
  60. package/dist/types/classes/objects/iframe.class.d.ts +76 -0
  61. package/dist/types/classes/registries/icon-registry.class.d.ts +75 -6
  62. package/dist/types/components/kritzel-engine/kritzel-engine.d.ts +14 -1
  63. package/dist/types/components/kritzel-icon/kritzel-icon.d.ts +5 -1
  64. package/dist/types/components.d.ts +26 -8
  65. package/dist/types/constants/version.d.ts +1 -1
  66. package/dist/types/helpers/cursor.helper.d.ts +2 -1
  67. package/dist/types/helpers/object.helper.d.ts +2 -0
  68. package/dist/types/helpers/svg-export.helper.d.ts +1 -0
  69. package/dist/types/helpers/wheel.helper.d.ts +2 -2
  70. package/dist/types/index.d.ts +3 -0
  71. package/dist/types/interfaces/context-menu-item.interface.d.ts +2 -1
  72. package/dist/types/interfaces/dynamic-object-renderer.interface.d.ts +7 -0
  73. package/dist/types/interfaces/embedded-object.interface.d.ts +9 -0
  74. package/dist/types/interfaces/engine-state.interface.d.ts +3 -2
  75. package/dist/types/interfaces/login-config.interface.d.ts +2 -1
  76. package/dist/types/interfaces/master-detail.interface.d.ts +2 -1
  77. package/dist/types/interfaces/menu-item.interface.d.ts +2 -1
  78. package/dist/types/interfaces/theme.interface.d.ts +6 -7
  79. package/dist/types/interfaces/tool.interface.d.ts +9 -0
  80. package/dist/types/interfaces/toolbar-item.interface.d.ts +3 -2
  81. package/package.json +1 -1
  82. package/readme.md +10 -10
  83. package/dist/components/p-fx6msYHJ.js +0 -167
@@ -1,9 +1,9 @@
1
- import { p as proxyCustomElement, H, c as createEvent, h, d as Host, t as transformTag } from './p-CfSx6HU_.js';
2
- import { a as KritzelMouseButton, d as defineCustomElement$4 } from './p-YP8wAcF4.js';
3
- import { K as KritzelBaseObject, h as KritzelMathHelper, c as KritzelColorHelper, i as KritzelGeometryHelper, D as DEFAULT_COLOR_PALETTE, a as KritzelPath, b as KritzelLine, O as ObjectHelper, d as KritzelThemeManager, g as defineCustomElement$5 } from './p-BvRGQvVI.js';
4
- import { A as ABSOLUTE_SCALE_MAX, a as ABSOLUTE_SCALE_MIN, d as defineCustomElement$2 } from './p-B2Htv9ou.js';
1
+ import { p as proxyCustomElement, H, c as createEvent, h, d as Host, t as transformTag } from './p-CJ5B8cMK.js';
2
+ import { a as KritzelMouseButton, d as defineCustomElement$4 } from './p-9YjkpHyU.js';
3
+ import { K as KritzelBaseObject, h as KritzelMathHelper, c as KritzelColorHelper, i as KritzelGeometryHelper, D as DEFAULT_COLOR_PALETTE, a as KritzelPath, b as KritzelLine, O as ObjectHelper, d as KritzelThemeManager, g as defineCustomElement$5 } from './p-DXnF4FeS.js';
4
+ import { A as ABSOLUTE_SCALE_MAX, a as ABSOLUTE_SCALE_MIN, d as defineCustomElement$2 } from './p-Bnuu1qip.js';
5
5
  import * as Y from 'yjs';
6
- import { K as KritzelIconRegistry, d as defineCustomElement$3 } from './p-fx6msYHJ.js';
6
+ import { K as KritzelIconRegistry, a as defineCustomElement$3 } from './p-BIUT0gBe.js';
7
7
 
8
8
  class KritzelDevicesHelper {
9
9
  static isTouchDevice() {
@@ -17181,7 +17181,7 @@ class KritzelDynamicObject extends KritzelBaseObject {
17181
17181
  }
17182
17182
  applyInteractiveState() {
17183
17183
  const pointerEvents = this.isInteractive ? 'auto' : 'none';
17184
- const contentCursor = this.isInteractive ? 'auto' : '';
17184
+ const contentCursor = this.isInteractive ? 'var(--kritzel-global-default-cursor, auto)' : '';
17185
17185
  if (this.element) {
17186
17186
  this.element.style.pointerEvents = pointerEvents;
17187
17187
  this.element.style.cursor = contentCursor;
@@ -17217,7 +17217,7 @@ class KritzelDynamicObject extends KritzelBaseObject {
17217
17217
  if (!this.isInteractive) {
17218
17218
  return false;
17219
17219
  }
17220
- const focusHosts = this.getFocusHosts();
17220
+ const focusHosts = this.getEmbeddedContentRoots();
17221
17221
  // Primary path: locate the focused input-like element by walking from each host through
17222
17222
  // `:focus` (piercing open shadow roots). This works in real browsers even when the engine
17223
17223
  // and editor are nested web components, because it never relies on the document-level
@@ -17234,7 +17234,7 @@ class KritzelDynamicObject extends KritzelBaseObject {
17234
17234
  }
17235
17235
  return false;
17236
17236
  }
17237
- getFocusHosts() {
17237
+ getEmbeddedContentRoots() {
17238
17238
  const hosts = [];
17239
17239
  if (this.element) {
17240
17240
  hosts.push(this.element);
@@ -17447,11 +17447,11 @@ class KritzelDynamicObject extends KritzelBaseObject {
17447
17447
  * @returns A serializable object with htmlString and dimension snapshots
17448
17448
  */
17449
17449
  serialize() {
17450
- const { _core, _elementRef, element, totalWidth, totalHeight, isSelected, isHovered, isMounted, isInteractive, resizing, markedForRemoval, isVisible, ...props } = this;
17450
+ const { _core, _elementRef, element, totalWidth, totalHeight, isSelected, isHovered, isMounted, isInteractive, resizing, markedForRemoval, isVisible, rendererData, ...props } = this;
17451
17451
  const cloned = structuredClone(props);
17452
17452
  // Include rendererKey and rendererData for lifecycle-driven rendering
17453
17453
  cloned.rendererKey = this.rendererKey;
17454
- cloned.rendererData = this.rendererData;
17454
+ cloned.rendererData = this.serializeRendererData();
17455
17455
  // Store HTML as string for fallback/compatibility when renderer is unavailable
17456
17456
  cloned.htmlString = element ? element.outerHTML : '';
17457
17457
  // Capture dimension snapshot at serialization time
@@ -17459,6 +17459,19 @@ class KritzelDynamicObject extends KritzelBaseObject {
17459
17459
  cloned.serializedHeight = this.height;
17460
17460
  return cloned;
17461
17461
  }
17462
+ serializeRendererData() {
17463
+ if (this.rendererKey) {
17464
+ const renderer = KritzelDynamicObjectRendererRegistry.get(this.rendererKey);
17465
+ if (renderer?.onSerialize) {
17466
+ return structuredClone(renderer.onSerialize({
17467
+ object: this,
17468
+ container: this.isMounted && this.elementRef instanceof HTMLElement ? this.elementRef : null,
17469
+ data: this.rendererData,
17470
+ }));
17471
+ }
17472
+ }
17473
+ return structuredClone(this.rendererData);
17474
+ }
17462
17475
  /**
17463
17476
  * Deserializes the dynamic object from a plain object.
17464
17477
  * Reconstructs the element from stored HTML string with fallback to placeholder.
@@ -17491,6 +17504,452 @@ class KritzelDynamicObject extends KritzelBaseObject {
17491
17504
  }
17492
17505
  }
17493
17506
 
17507
+ const IFRAME_STATE_MESSAGE = 'kritzel-iframe-state';
17508
+ const IFRAME_PARENT_STATE_MESSAGE = 'kritzel-iframe-parent-state';
17509
+ const IFRAME_POINTER_MESSAGE = 'kritzel-iframe-pointer';
17510
+ const IFRAME_CONTEXT_MENU_MESSAGE = 'kritzel-iframe-contextmenu';
17511
+ const SUPPORTED_SANDBOX_TOKENS = new Set([
17512
+ 'allow-downloads',
17513
+ 'allow-forms',
17514
+ 'allow-modals',
17515
+ 'allow-orientation-lock',
17516
+ 'allow-pointer-lock',
17517
+ 'allow-popups',
17518
+ 'allow-popups-to-escape-sandbox',
17519
+ 'allow-presentation',
17520
+ 'allow-same-origin',
17521
+ 'allow-scripts',
17522
+ 'allow-top-navigation-by-user-activation',
17523
+ ]);
17524
+ const iframePointerBridgeStarts = new WeakMap();
17525
+ class KritzelIframeObject extends KritzelBaseObject {
17526
+ __class__ = 'KritzelIframeObject';
17527
+ content = { html: '', css: '', script: '' };
17528
+ loadingContent;
17529
+ isLoading = false;
17530
+ state = {};
17531
+ title = 'Embedded content';
17532
+ sandbox = ['allow-scripts'];
17533
+ borderRadius;
17534
+ boxShadow;
17535
+ get html() { return this.content.html; }
17536
+ set html(value) { this.content = { ...this.content, html: value }; }
17537
+ get css() { return this.content.css; }
17538
+ set css(value) { this.content = { ...this.content, css: value }; }
17539
+ get script() { return this.content.script; }
17540
+ set script(value) { this.content = { ...this.content, script: value }; }
17541
+ messageListener = null;
17542
+ constructor(config) {
17543
+ super();
17544
+ this.content = {
17545
+ html: config?.content?.html ?? config?.html ?? '',
17546
+ css: config?.content?.css ?? config?.css ?? '',
17547
+ script: config?.content?.script ?? config?.script ?? '',
17548
+ };
17549
+ this.loadingContent = config?.loadingContent ? {
17550
+ html: config.loadingContent.html ?? '',
17551
+ css: config.loadingContent.css ?? '',
17552
+ script: config.loadingContent.script ?? '',
17553
+ } : undefined;
17554
+ this.isLoading = config?.isLoading ?? false;
17555
+ this.state = structuredClone(config?.state ?? {});
17556
+ this.title = config?.title ?? 'Embedded content';
17557
+ this.sandbox = this.normalizeSandbox(config?.sandbox);
17558
+ this.borderRadius = config?.borderRadius;
17559
+ this.boxShadow = config?.boxShadow;
17560
+ this.translateX = config?.translateX ?? 0;
17561
+ this.translateY = config?.translateY ?? 0;
17562
+ this.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
17563
+ this.scale = config?.scale ?? 1;
17564
+ this.width = config?.width ?? 0;
17565
+ this.height = config?.height ?? 0;
17566
+ }
17567
+ static create(core, config) {
17568
+ const object = new KritzelIframeObject(config);
17569
+ object._core = core;
17570
+ object.id = object.generateId();
17571
+ object.workspaceId = core.store.state.activeWorkspace.id;
17572
+ object.userId = core.user?.id;
17573
+ object.scale = config?.scale ?? core.store.state.scale;
17574
+ object.zIndex = core.store.currentZIndex;
17575
+ return object;
17576
+ }
17577
+ get sandboxAttribute() {
17578
+ return this.sandbox.join(' ');
17579
+ }
17580
+ mount(iframe) {
17581
+ if (this.isMounted && this.elementRef === iframe) {
17582
+ return;
17583
+ }
17584
+ if (this.isMounted) {
17585
+ this.unmount();
17586
+ }
17587
+ this.elementRef = iframe;
17588
+ this.isMounted = true;
17589
+ iframe.title = this.title;
17590
+ iframe.setAttribute('sandbox', this.sandboxAttribute);
17591
+ iframe.style.pointerEvents = this.isInteractive ? 'auto' : 'none';
17592
+ this.messageListener = event => this.handleMessage(event);
17593
+ window.addEventListener('message', this.messageListener);
17594
+ iframe.srcdoc = this.createSourceDocument();
17595
+ this.onMount(this.createLifecycleContext());
17596
+ }
17597
+ unmount() {
17598
+ if (!this.isMounted) {
17599
+ return;
17600
+ }
17601
+ const nextState = this.onUnmount(this.createLifecycleContext());
17602
+ if (nextState !== undefined) {
17603
+ this.state = structuredClone(nextState);
17604
+ }
17605
+ if (this.messageListener) {
17606
+ window.removeEventListener('message', this.messageListener);
17607
+ this.messageListener = null;
17608
+ }
17609
+ this.isMounted = false;
17610
+ this._elementRef = null;
17611
+ }
17612
+ setContent(content) {
17613
+ this.content = {
17614
+ html: content.html ?? this.content.html,
17615
+ css: content.css ?? this.content.css,
17616
+ script: content.script ?? this.content.script,
17617
+ };
17618
+ this.refreshDocument();
17619
+ this.persistUpdate();
17620
+ }
17621
+ setLoadingContent(content) {
17622
+ this.loadingContent = {
17623
+ html: content.html ?? this.loadingContent?.html ?? '',
17624
+ css: content.css ?? this.loadingContent?.css ?? '',
17625
+ script: content.script ?? this.loadingContent?.script ?? '',
17626
+ };
17627
+ this.refreshDocument();
17628
+ this.persistUpdate();
17629
+ }
17630
+ setLoading(isLoading) {
17631
+ this.isLoading = isLoading;
17632
+ this.refreshDocument();
17633
+ this.persistUpdate();
17634
+ }
17635
+ getActiveContent() {
17636
+ const activeLoadingContent = this.isLoading ? this.loadingContent : undefined;
17637
+ return {
17638
+ html: activeLoadingContent?.html ?? this.content.html,
17639
+ css: activeLoadingContent?.css ?? this.content.css,
17640
+ script: activeLoadingContent?.script ?? this.content.script,
17641
+ };
17642
+ }
17643
+ setState(state) {
17644
+ this.state = structuredClone(state);
17645
+ this.elementRef?.contentWindow?.postMessage({ type: IFRAME_PARENT_STATE_MESSAGE, state: this.state }, '*');
17646
+ this.persistUpdate();
17647
+ }
17648
+ setIsInteractive(isInteractive) {
17649
+ this.isInteractive = isInteractive;
17650
+ if (this.isMounted && this.elementRef) {
17651
+ this.elementRef.style.pointerEvents = isInteractive ? 'auto' : 'none';
17652
+ if (!isInteractive) {
17653
+ this.elementRef.blur();
17654
+ }
17655
+ }
17656
+ }
17657
+ hasFocusedInputElement() {
17658
+ return this.isInteractive && this.isMounted && document.activeElement === this.elementRef;
17659
+ }
17660
+ getEmbeddedContentRoots() {
17661
+ return this.isMounted && this.elementRef instanceof HTMLElement && this.elementRef.tagName === 'IFRAME' ? [this.elementRef] : [];
17662
+ }
17663
+ onMount(_context) { }
17664
+ onSerialize(context) {
17665
+ return context.state;
17666
+ }
17667
+ onUnmount(context) {
17668
+ return context.state;
17669
+ }
17670
+ serialize() {
17671
+ const nextState = this.onSerialize(this.createLifecycleContext());
17672
+ this.state = structuredClone(nextState);
17673
+ const messageListener = this.messageListener;
17674
+ this.messageListener = null;
17675
+ try {
17676
+ const serialized = super.serialize();
17677
+ delete serialized.messageListener;
17678
+ return serialized;
17679
+ }
17680
+ finally {
17681
+ this.messageListener = messageListener;
17682
+ }
17683
+ }
17684
+ deserialize(object) {
17685
+ super.deserialize(object);
17686
+ const nextContent = object.content && typeof object.content === 'object' ? object.content : {};
17687
+ const nextLoadingContent = object.loadingContent && typeof object.loadingContent === 'object' ? object.loadingContent : undefined;
17688
+ this.content = {
17689
+ html: typeof nextContent.html === 'string' ? nextContent.html : typeof object.html === 'string' ? object.html : '',
17690
+ css: typeof nextContent.css === 'string' ? nextContent.css : typeof object.css === 'string' ? object.css : '',
17691
+ script: typeof nextContent.script === 'string' ? nextContent.script : typeof object.script === 'string' ? object.script : '',
17692
+ };
17693
+ this.loadingContent = nextLoadingContent ? {
17694
+ html: typeof nextLoadingContent.html === 'string' ? nextLoadingContent.html : '',
17695
+ css: typeof nextLoadingContent.css === 'string' ? nextLoadingContent.css : '',
17696
+ script: typeof nextLoadingContent.script === 'string' ? nextLoadingContent.script : '',
17697
+ } : undefined;
17698
+ this.isLoading = typeof object.isLoading === 'boolean' ? object.isLoading : false;
17699
+ this.state = object.state && typeof object.state === 'object' ? structuredClone(object.state) : {};
17700
+ this.title = typeof object.title === 'string' ? object.title : 'Embedded content';
17701
+ this.sandbox = this.normalizeSandbox(Array.isArray(object.sandbox) ? object.sandbox.filter((token) => typeof token === 'string') : undefined);
17702
+ this.borderRadius = (typeof object.borderRadius === 'number' || typeof object.borderRadius === 'string') ? object.borderRadius : undefined;
17703
+ this.boxShadow = typeof object.boxShadow === 'string' ? object.boxShadow : undefined;
17704
+ this.messageListener = null;
17705
+ return this;
17706
+ }
17707
+ copy() {
17708
+ const copiedObject = super.copy();
17709
+ copiedObject.content = { ...this.content };
17710
+ copiedObject.loadingContent = this.loadingContent ? { ...this.loadingContent } : undefined;
17711
+ copiedObject.isLoading = this.isLoading;
17712
+ copiedObject.state = structuredClone(this.state);
17713
+ copiedObject.sandbox = [...this.sandbox];
17714
+ copiedObject.borderRadius = this.borderRadius;
17715
+ copiedObject.boxShadow = this.boxShadow;
17716
+ copiedObject.messageListener = null;
17717
+ copiedObject._elementRef = null;
17718
+ return copiedObject;
17719
+ }
17720
+ clone() {
17721
+ const clonedObject = super.clone();
17722
+ clonedObject.content = { ...this.content };
17723
+ clonedObject.loadingContent = this.loadingContent ? { ...this.loadingContent } : undefined;
17724
+ clonedObject.isLoading = this.isLoading;
17725
+ clonedObject.state = structuredClone(this.state);
17726
+ clonedObject.sandbox = [...this.sandbox];
17727
+ clonedObject.borderRadius = this.borderRadius;
17728
+ clonedObject.boxShadow = this.boxShadow;
17729
+ clonedObject.messageListener = null;
17730
+ clonedObject.isMounted = false;
17731
+ clonedObject._elementRef = null;
17732
+ return clonedObject;
17733
+ }
17734
+ adoptTransientStateFrom(previous) {
17735
+ if (!(previous instanceof KritzelIframeObject)) {
17736
+ return;
17737
+ }
17738
+ this.isInteractive = previous.isInteractive;
17739
+ this.isSelected = previous.isSelected;
17740
+ this.isHovered = previous.isHovered;
17741
+ if (!previous.isMounted || !(previous.elementRef instanceof HTMLElement) || previous.elementRef.tagName !== 'IFRAME') {
17742
+ return;
17743
+ }
17744
+ if (previous.messageListener) {
17745
+ window.removeEventListener('message', previous.messageListener);
17746
+ }
17747
+ this._elementRef = previous.elementRef;
17748
+ this.isMounted = true;
17749
+ this.messageListener = event => this.handleMessage(event);
17750
+ window.addEventListener('message', this.messageListener);
17751
+ this.elementRef.style.pointerEvents = this.isInteractive ? 'auto' : 'none';
17752
+ previous.messageListener = null;
17753
+ previous.isMounted = false;
17754
+ previous._elementRef = null;
17755
+ }
17756
+ createLifecycleContext() {
17757
+ return {
17758
+ object: this,
17759
+ iframe: this.isMounted ? this.elementRef : null,
17760
+ state: structuredClone(this.state),
17761
+ };
17762
+ }
17763
+ normalizeSandbox(tokens) {
17764
+ const requestedTokens = tokens ?? ['allow-scripts'];
17765
+ const normalized = requestedTokens.filter(token => SUPPORTED_SANDBOX_TOKENS.has(token));
17766
+ return [...new Set(normalized.length > 0 ? normalized : ['allow-scripts'])];
17767
+ }
17768
+ createSourceDocument() {
17769
+ const activeContent = this.getActiveContent();
17770
+ const state = JSON.stringify(this.state).replace(/</g, '\\u003c');
17771
+ const css = activeContent.css.replace(/<\/style/gi, '<\\/style');
17772
+ const script = activeContent.script.replace(/<\/script/gi, '<\\/script');
17773
+ const computedStyle = this.isMounted ? getComputedStyle(this.elementRef) : null;
17774
+ const defaultCursor = computedStyle?.getPropertyValue('--kritzel-global-default-cursor').trim() || 'auto';
17775
+ const pointerCursor = computedStyle?.getPropertyValue('--kritzel-global-pointer-cursor').trim() || defaultCursor;
17776
+ return `<!doctype html>
17777
+ <html>
17778
+ <head>
17779
+ <meta charset="utf-8">
17780
+ <style>
17781
+ :root {
17782
+ --kritzel-iframe-default-cursor: ${defaultCursor};
17783
+ --kritzel-iframe-pointer-cursor: ${pointerCursor};
17784
+ }
17785
+ html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; cursor: var(--kritzel-iframe-default-cursor); }
17786
+ button, button *, a, a *, input, textarea, select { cursor: var(--kritzel-iframe-pointer-cursor) !important; }
17787
+ ${css}
17788
+ </style>
17789
+ </head>
17790
+ <body>
17791
+ ${activeContent.html}
17792
+ <script>
17793
+ const kritzel = globalThis.kritzel = {
17794
+ state: ${state},
17795
+ setState(nextState) {
17796
+ this.state = nextState;
17797
+ parent.postMessage({ type: '${IFRAME_STATE_MESSAGE}', state: nextState }, '*');
17798
+ },
17799
+ };
17800
+ globalThis.addEventListener('message', event => {
17801
+ if (event.data?.type === '${IFRAME_PARENT_STATE_MESSAGE}' && event.data.state && typeof event.data.state === 'object') {
17802
+ kritzel.state = event.data.state;
17803
+ globalThis.dispatchEvent(new CustomEvent('kritzelstatechange', { detail: kritzel.state }));
17804
+ }
17805
+ });
17806
+ let bridgedPointerId = null;
17807
+ const interactiveSelector = 'button, input, textarea, select, a[href], [contenteditable="true"], [contenteditable=""]';
17808
+ const bridgePointerEvent = event => parent.postMessage({
17809
+ type: '${IFRAME_POINTER_MESSAGE}',
17810
+ eventType: event.type,
17811
+ pointerId: event.pointerId,
17812
+ pointerType: event.pointerType,
17813
+ clientX: event.clientX,
17814
+ clientY: event.clientY,
17815
+ screenX: event.screenX,
17816
+ screenY: event.screenY,
17817
+ button: event.button,
17818
+ buttons: event.buttons,
17819
+ shiftKey: event.shiftKey,
17820
+ ctrlKey: event.ctrlKey,
17821
+ altKey: event.altKey,
17822
+ metaKey: event.metaKey,
17823
+ }, '*');
17824
+ document.addEventListener('pointerdown', event => {
17825
+ if (event.target instanceof Element && event.target.closest(interactiveSelector)) return;
17826
+ bridgedPointerId = event.pointerId;
17827
+ event.preventDefault();
17828
+ bridgePointerEvent(event);
17829
+ });
17830
+ document.addEventListener('pointermove', event => {
17831
+ if (event.pointerId !== bridgedPointerId) return;
17832
+ event.preventDefault();
17833
+ bridgePointerEvent(event);
17834
+ });
17835
+ const finishPointerBridge = event => {
17836
+ if (event.pointerId !== bridgedPointerId) return;
17837
+ event.preventDefault();
17838
+ bridgePointerEvent(event);
17839
+ bridgedPointerId = null;
17840
+ };
17841
+ document.addEventListener('pointerup', finishPointerBridge);
17842
+ document.addEventListener('pointercancel', finishPointerBridge);
17843
+ document.addEventListener('contextmenu', event => {
17844
+ event.preventDefault();
17845
+ parent.postMessage({
17846
+ type: '${IFRAME_CONTEXT_MENU_MESSAGE}',
17847
+ clientX: event.clientX,
17848
+ clientY: event.clientY,
17849
+ button: event.button,
17850
+ shiftKey: event.shiftKey,
17851
+ ctrlKey: event.ctrlKey,
17852
+ altKey: event.altKey,
17853
+ metaKey: event.metaKey,
17854
+ }, '*');
17855
+ });
17856
+ ${script}
17857
+ <\/script>
17858
+ </body>
17859
+ </html>`;
17860
+ }
17861
+ handleMessage(event) {
17862
+ if (event.source !== this.elementRef?.contentWindow || !event.data || typeof event.data !== 'object') {
17863
+ return;
17864
+ }
17865
+ const message = event.data;
17866
+ if (message.type === IFRAME_POINTER_MESSAGE) {
17867
+ this.dispatchPointerMessage(event.data);
17868
+ return;
17869
+ }
17870
+ if (message.type === IFRAME_CONTEXT_MENU_MESSAGE) {
17871
+ this.dispatchContextMenuMessage(event.data);
17872
+ return;
17873
+ }
17874
+ if (message.type !== IFRAME_STATE_MESSAGE || !message.state || typeof message.state !== 'object') {
17875
+ return;
17876
+ }
17877
+ this.state = structuredClone(message.state);
17878
+ this.persistUpdate();
17879
+ }
17880
+ dispatchPointerMessage(message) {
17881
+ if (!this.isMounted || !this.elementRef || !['pointerdown', 'pointermove', 'pointerup', 'pointercancel'].includes(message.eventType)) {
17882
+ return;
17883
+ }
17884
+ const iframeRect = this.elementRef.getBoundingClientRect();
17885
+ if (message.eventType === 'pointerdown') {
17886
+ const scaleX = this.elementRef.offsetWidth > 0 ? iframeRect.width / this.elementRef.offsetWidth : 1;
17887
+ const scaleY = this.elementRef.offsetHeight > 0 ? iframeRect.height / this.elementRef.offsetHeight : 1;
17888
+ iframePointerBridgeStarts.set(this.elementRef, {
17889
+ pointerId: message.pointerId,
17890
+ clientX: iframeRect.left + message.clientX * scaleX,
17891
+ clientY: iframeRect.top + message.clientY * scaleY,
17892
+ screenX: message.screenX,
17893
+ screenY: message.screenY,
17894
+ });
17895
+ }
17896
+ const bridgeStart = iframePointerBridgeStarts.get(this.elementRef);
17897
+ const hasBridgeStart = bridgeStart?.pointerId === message.pointerId;
17898
+ const clientX = hasBridgeStart ? bridgeStart.clientX + message.screenX - bridgeStart.screenX : iframeRect.left + message.clientX;
17899
+ const clientY = hasBridgeStart ? bridgeStart.clientY + message.screenY - bridgeStart.screenY : iframeRect.top + message.clientY;
17900
+ this.elementRef.dispatchEvent(new PointerEvent(message.eventType, {
17901
+ bubbles: true,
17902
+ composed: true,
17903
+ cancelable: true,
17904
+ pointerId: message.pointerId,
17905
+ pointerType: message.pointerType,
17906
+ clientX,
17907
+ clientY,
17908
+ screenX: message.screenX,
17909
+ screenY: message.screenY,
17910
+ button: message.button,
17911
+ buttons: message.buttons,
17912
+ shiftKey: message.shiftKey,
17913
+ ctrlKey: message.ctrlKey,
17914
+ altKey: message.altKey,
17915
+ metaKey: message.metaKey,
17916
+ }));
17917
+ if (message.eventType === 'pointerup' || message.eventType === 'pointercancel') {
17918
+ iframePointerBridgeStarts.delete(this.elementRef);
17919
+ }
17920
+ }
17921
+ dispatchContextMenuMessage(message) {
17922
+ if (!this.isMounted || !this.elementRef) {
17923
+ return;
17924
+ }
17925
+ const iframeRect = this.elementRef.getBoundingClientRect();
17926
+ // Dispatched as a MouseEvent (not PointerEvent) to mirror how browsers
17927
+ // natively raise 'contextmenu' for both mouse right-click and touch long-press.
17928
+ this.elementRef.dispatchEvent(new MouseEvent('contextmenu', {
17929
+ bubbles: true,
17930
+ composed: true,
17931
+ cancelable: true,
17932
+ clientX: iframeRect.left + message.clientX,
17933
+ clientY: iframeRect.top + message.clientY,
17934
+ button: message.button,
17935
+ shiftKey: message.shiftKey,
17936
+ ctrlKey: message.ctrlKey,
17937
+ altKey: message.altKey,
17938
+ metaKey: message.metaKey,
17939
+ }));
17940
+ }
17941
+ refreshDocument() {
17942
+ if (this.isMounted && this.elementRef) {
17943
+ this.elementRef.srcdoc = this.createSourceDocument();
17944
+ }
17945
+ }
17946
+ persistUpdate() {
17947
+ if (this._core?.store?.objects) {
17948
+ this.update();
17949
+ }
17950
+ }
17951
+ }
17952
+
17494
17953
  /**
17495
17954
  * Represents a selection group that manages multiple selected objects on the canvas.
17496
17955
  * The selection group allows batch operations like move, resize, and rotate on multiple objects simultaneously.
@@ -19631,7 +20090,7 @@ class KritzelCursorHelper {
19631
20090
  * The icon is retrieved from the KritzelIconRegistry.
19632
20091
  */
19633
20092
  static getCursor(options) {
19634
- const iconName = options.iconName === 'default' ? 'mouse-pointer' : options.iconName;
20093
+ const iconName = options.iconName === 'default' ? 'mousePointer' : options.iconName;
19635
20094
  const iconSvg = KritzelIconRegistry.get(iconName);
19636
20095
  if (!iconSvg) {
19637
20096
  console.warn(`Icon "${iconName}" not found in registry.`);
@@ -19640,7 +20099,7 @@ class KritzelCursorHelper {
19640
20099
  const size = options.size || 24;
19641
20100
  const rotation = options.rotation || 0;
19642
20101
  const color = options.color || 'black';
19643
- // Default cursor (mouse-pointer) has hotspot at (4, 4), others at center
20102
+ // Default cursor (mousePointer) has hotspot at (4, 4), others at center
19644
20103
  const hotspot = options.hotspot || (options.iconName === 'default' ? { x: 4, y: 4 } : { x: size / 2, y: size / 2 });
19645
20104
  // Modify the SVG string to set size and color
19646
20105
  // We replace width and height to match the requested size
@@ -19653,12 +20112,12 @@ class KritzelCursorHelper {
19653
20112
  // We rotate around the center of the SVG canvas
19654
20113
  const center = size / 2;
19655
20114
  // We use a group to apply the rotation
19656
- const svg = `
19657
- <svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
19658
- <g transform="rotate(${rotation} ${center} ${center})">
19659
- ${content}
19660
- </g>
19661
- </svg>
20115
+ const svg = `
20116
+ <svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
20117
+ <g transform="rotate(${rotation} ${center} ${center})">
20118
+ ${content}
20119
+ </g>
20120
+ </svg>
19662
20121
  `;
19663
20122
  // Encode the SVG for use in a data URI
19664
20123
  // We need to be careful with encoding to ensure it works across browsers
@@ -21874,9 +22333,8 @@ class KritzelSelectionTool extends KritzelBaseTool {
21874
22333
  }
21875
22334
  if (selectedObject && selectedObject.isSelected === false && selectedObject?.objects.length === 1) {
21876
22335
  const firstObj = selectedObject.objects[0];
21877
- if (KritzelClassHelper.isInstanceOf(firstObj, 'KritzelDynamicObject')) {
21878
- // Selected single dynamic objects hand pointer interaction to their
21879
- // embedded DOM content. Unselected dynamic objects remain selectable.
22336
+ if (ObjectHelper.isKritzelEmbeddedObject(firstObj)) {
22337
+ // Selected embedded objects hand pointer interaction to their content.
21880
22338
  if (firstObj.isSelected && this._core.store.selectionGroup?.objects.length === 1)
21881
22339
  return;
21882
22340
  }
@@ -43322,6 +43780,9 @@ class KritzelReviver {
43322
43780
  case 'KritzelDynamicObject':
43323
43781
  revivedObj = KritzelDynamicObject.create(this._core).deserialize(obj);
43324
43782
  break;
43783
+ case 'KritzelIframeObject':
43784
+ revivedObj = KritzelIframeObject.create(this._core).deserialize(obj);
43785
+ break;
43325
43786
  case 'KritzelSelectionGroup':
43326
43787
  revivedObj = KritzelSelectionGroup.create(this._core).deserialize(obj);
43327
43788
  break;
@@ -45146,9 +45607,16 @@ class KritzelStore {
45146
45607
  * Gets the currently selected dynamic object whose focused descendant is an input-like element.
45147
45608
  * @returns The interactive dynamic object with focused input-like content, or null if none matches
45148
45609
  */
45610
+ get activeInteractiveEmbeddedObject() {
45611
+ for (const object of this.selectedObjects) {
45612
+ if (ObjectHelper.isKritzelEmbeddedObject(object) && object.hasFocusedInputElement()) {
45613
+ return object;
45614
+ }
45615
+ }
45616
+ return null;
45617
+ }
45149
45618
  get activeInteractiveDynamicObject() {
45150
- const interactiveDynamicObject = this.selectedObjects.find(object => KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject') && object.hasFocusedInputElement());
45151
- return interactiveDynamicObject ?? null;
45619
+ return this.activeInteractiveEmbeddedObject;
45152
45620
  }
45153
45621
  /**
45154
45622
  * Gets the path currently being drawn (not yet completed).
@@ -45736,11 +46204,13 @@ class KritzelCursorManager {
45736
46204
  // Reset cursor on old target
45737
46205
  if (this._targetElement) {
45738
46206
  this._targetElement.style.cursor = '';
46207
+ this._targetElement.style.removeProperty('--kritzel-global-default-cursor');
45739
46208
  this._targetElement.style.removeProperty('--kritzel-global-pointer-cursor');
45740
46209
  }
45741
46210
  this._targetElement = element;
45742
46211
  // Set the pointer cursor CSS variable for child components to use
45743
46212
  if (this._targetElement) {
46213
+ this._targetElement.style.setProperty('--kritzel-global-default-cursor', KritzelCursorHelper.getCursor({ iconName: 'default' }));
45744
46214
  this._targetElement.style.setProperty('--kritzel-global-pointer-cursor', KritzelCursorHelper.getPointerCursor());
45745
46215
  }
45746
46216
  }
@@ -45785,8 +46255,10 @@ class KritzelCursorManager {
45785
46255
  if (!this._shadowRoot)
45786
46256
  return;
45787
46257
  const elementsAtPoint = this._shadowRoot.elementsFromPoint(ev.clientX, ev.clientY);
45788
- if (!elementsAtPoint || elementsAtPoint.length === 0)
46258
+ if (!elementsAtPoint || elementsAtPoint.length === 0) {
46259
+ this.resetToDefault();
45789
46260
  return;
46261
+ }
45790
46262
  // Check for resize handle overlays (selection group)
45791
46263
  const resizeHandleOverlay = elementsAtPoint.find(el => el.classList.contains('resize-handle-overlay'));
45792
46264
  if (resizeHandleOverlay) {
@@ -45796,19 +46268,19 @@ class KritzelCursorManager {
45796
46268
  const isTopLeft = resizeHandleOverlay.classList.contains('top-left');
45797
46269
  const isBottomRight = resizeHandleOverlay.classList.contains('bottom-right');
45798
46270
  const rotationOffset = (isTopLeft || isBottomRight) ? -45 : 45;
45799
- this._core.store.state.cursor = { icon: 'move-vertical', rotation: rotationDegrees + rotationOffset };
46271
+ this._core.store.state.cursor = { icon: 'moveVertical', rotation: rotationDegrees + rotationOffset };
45800
46272
  return;
45801
46273
  }
45802
46274
  // Check for rotation handle overlay
45803
46275
  const rotationHandleOverlay = elementsAtPoint.find(el => el.classList.contains('rotation-handle-overlay'));
45804
46276
  if (rotationHandleOverlay) {
45805
- this._core.store.state.cursor = { icon: 'hand', iconActive: 'hand-grab' };
46277
+ this._core.store.state.cursor = { icon: 'hand', iconActive: 'handGrab' };
45806
46278
  return;
45807
46279
  }
45808
46280
  // Check for line handle overlays (selection line)
45809
46281
  const lineHandleOverlay = elementsAtPoint.find(el => el.classList.contains('selection-line-handle-overlay'));
45810
46282
  if (lineHandleOverlay) {
45811
- this._core.store.state.cursor = { icon: 'hand', iconActive: 'hand-grab' };
46283
+ this._core.store.state.cursor = { icon: 'hand', iconActive: 'handGrab' };
45812
46284
  return;
45813
46285
  }
45814
46286
  // Reset cursor if not hovering any handle
@@ -45843,7 +46315,8 @@ class KritzelCursorManager {
45843
46315
  cleanup() {
45844
46316
  if (this._targetElement) {
45845
46317
  this._targetElement.style.cursor = '';
45846
- this._targetElement.style.removeProperty('--kritzel-pointer-cursor');
46318
+ this._targetElement.style.removeProperty('--kritzel-global-default-cursor');
46319
+ this._targetElement.style.removeProperty('--kritzel-global-pointer-cursor');
45847
46320
  }
45848
46321
  this._targetElement = null;
45849
46322
  this._shadowRoot = null;
@@ -46768,7 +47241,7 @@ class KritzelCore {
46768
47241
  this._store.state.translateY = viewportHeight / 2 - (viewport.centerWorldY ?? 0) * scale;
46769
47242
  this._store.state.scale = scale;
46770
47243
  await objectsMap.initialize(this, activeWorkspace.id, this._syncConfig);
46771
- this.seedWorkspaceObjects(activeWorkspace, objectsMap);
47244
+ await this.seedWorkspaceObjects(activeWorkspace, objectsMap);
46772
47245
  // Set local user in awareness after object map is initialized
46773
47246
  if (this._user) {
46774
47247
  objectsMap.setLocalUser(this._user);
@@ -46782,15 +47255,21 @@ class KritzelCore {
46782
47255
  this.engine.emitObjectsChange();
46783
47256
  this.rerender();
46784
47257
  }
46785
- seedWorkspaceObjects(workspace, objectsMap) {
47258
+ async seedWorkspaceObjects(workspace, objectsMap) {
46786
47259
  if (!workspace.objects?.length) {
46787
47260
  return;
46788
47261
  }
47262
+ const pendingObjects = workspace.objects.filter(object => !objectsMap.findById(object.id));
47263
+ // Mirrors engine.addObject()/addObjects(): image asset upload is async and must
47264
+ // happen before the (synchronous) Yjs transaction that inserts the objects.
47265
+ for (const object of pendingObjects) {
47266
+ object._core = this;
47267
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelImage')) {
47268
+ await object.prepareForInsert();
47269
+ }
47270
+ }
46789
47271
  objectsMap.transaction(() => {
46790
- for (const object of workspace.objects) {
46791
- if (objectsMap.findById(object.id)) {
46792
- continue;
46793
- }
47272
+ for (const object of pendingObjects) {
46794
47273
  this.prepareObjectForWorkspace(object, workspace.id, this._store.currentZIndex, this._store.state.scale);
46795
47274
  objectsMap.insert(object);
46796
47275
  }
@@ -46829,16 +47308,30 @@ class KritzelCore {
46829
47308
  * @param scaleFallback - Scale applied to objects that do not define one
46830
47309
  * @returns The objects that were added, updated and removed
46831
47310
  */
46832
- reconcileWorkspaceObjects(objectsMap, workspaceId, desiredObjects, nextZIndex, scaleFallback) {
47311
+ async reconcileWorkspaceObjects(objectsMap, workspaceId, desiredObjects, nextZIndex, scaleFallback) {
46833
47312
  const existingObjects = objectsMap.allObjects().filter(o => !(o instanceof KritzelSelectionGroup) && !(o instanceof KritzelSelectionBox));
46834
47313
  const existingById = new Map(existingObjects.map(object => [object.id, object]));
46835
47314
  const desiredIds = new Set();
46836
47315
  const diff = { added: [], updated: [], removed: [] };
47316
+ // Assign IDs up front so the insert-vs-update lookup below is stable, and so the
47317
+ // async image-prepare pass and the (synchronous) transaction agree on identity.
47318
+ for (const object of desiredObjects) {
47319
+ if (!object.id) {
47320
+ object.id = object.generateId();
47321
+ }
47322
+ }
47323
+ // Mirrors engine.addObject()/addObjects(): image asset upload is async and must
47324
+ // happen before the (synchronous) Yjs transaction that inserts the objects.
47325
+ for (const object of desiredObjects) {
47326
+ const existingObject = existingById.get(object.id);
47327
+ const isNewOrReplaced = !existingObject || existingObject !== object;
47328
+ if (isNewOrReplaced && KritzelClassHelper.isInstanceOf(object, 'KritzelImage')) {
47329
+ object._core = this;
47330
+ await object.prepareForInsert();
47331
+ }
47332
+ }
46837
47333
  objectsMap.transaction(() => {
46838
47334
  for (const object of desiredObjects) {
46839
- if (!object.id) {
46840
- object.id = object.generateId();
46841
- }
46842
47335
  desiredIds.add(object.id);
46843
47336
  const existingObject = existingById.get(object.id);
46844
47337
  if (!existingObject) {
@@ -46876,7 +47369,7 @@ class KritzelCore {
46876
47369
  * object set stays exactly what the caller declared.
46877
47370
  */
46878
47371
  detachObject(object) {
46879
- if (KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
47372
+ if (ObjectHelper.isKritzelEmbeddedObject(object)) {
46880
47373
  object.unmount();
46881
47374
  }
46882
47375
  object.isMounted = false;
@@ -47052,13 +47545,13 @@ class KritzelCore {
47052
47545
  activeWorkspace.viewport = { centerWorldX, centerWorldY, scale };
47053
47546
  }
47054
47547
  }
47055
- applyObjectsToActiveWorkspace(workspace, desiredObjects) {
47548
+ async applyObjectsToActiveWorkspace(workspace, desiredObjects) {
47056
47549
  const objectsMap = this._store.objects;
47057
47550
  if (!objectsMap) {
47058
47551
  return null;
47059
47552
  }
47060
47553
  this.clearSelection();
47061
- const diff = this.reconcileWorkspaceObjects(objectsMap, workspace.id, desiredObjects, () => this._store.currentZIndex, this._store.state.scale);
47554
+ const diff = await this.reconcileWorkspaceObjects(objectsMap, workspace.id, desiredObjects, () => this._store.currentZIndex, this._store.state.scale);
47062
47555
  this._anchorManager.rebuildIndex();
47063
47556
  this.rerender();
47064
47557
  return diff;
@@ -47077,7 +47570,7 @@ class KritzelCore {
47077
47570
  try {
47078
47571
  await objectsMap.initialize(this, workspace.id, this._syncConfig, { headless: true, workspaceName: workspace.name });
47079
47572
  await objectsMap.whenNetworkSynced();
47080
- return this.reconcileWorkspaceObjects(objectsMap, workspace.id, desiredObjects, () => this.nextZIndexOf(objectsMap), this._store.state.scale);
47573
+ return await this.reconcileWorkspaceObjects(objectsMap, workspace.id, desiredObjects, () => this.nextZIndexOf(objectsMap), this._store.state.scale);
47081
47574
  }
47082
47575
  finally {
47083
47576
  objectsMap.destroy();
@@ -47163,8 +47656,7 @@ class KritzelCore {
47163
47656
  this.removeObject(child);
47164
47657
  });
47165
47658
  }
47166
- // Unmount dynamic objects before removal
47167
- if (KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
47659
+ if (ObjectHelper.isKritzelEmbeddedObject(object)) {
47168
47660
  object.unmount();
47169
47661
  }
47170
47662
  // Handle anchor cleanup
@@ -47210,14 +47702,8 @@ class KritzelCore {
47210
47702
  this.removeLocalSelectionGroups();
47211
47703
  this.removeSelectionBox();
47212
47704
  for (const object of selectionGroup.objects ?? []) {
47213
- if (KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
47214
- const dynamicObject = object;
47215
- if (typeof dynamicObject.setIsInteractive === 'function') {
47216
- dynamicObject.setIsInteractive(true);
47217
- }
47218
- else {
47219
- dynamicObject.isInteractive = true;
47220
- }
47705
+ if (ObjectHelper.isKritzelEmbeddedObject(object)) {
47706
+ object.setIsInteractive(true);
47221
47707
  }
47222
47708
  }
47223
47709
  this._store.objects.insert(selectionGroup);
@@ -47238,14 +47724,8 @@ class KritzelCore {
47238
47724
  teardownSelectionGroup(selectionGroup) {
47239
47725
  selectionGroup.deselectAllChildren?.();
47240
47726
  for (const object of selectionGroup.objects ?? []) {
47241
- if (KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
47242
- const dynamicObject = object;
47243
- if (typeof dynamicObject.setIsInteractive === 'function') {
47244
- dynamicObject.setIsInteractive(false);
47245
- }
47246
- else {
47247
- dynamicObject.isInteractive = false;
47248
- }
47727
+ if (ObjectHelper.isKritzelEmbeddedObject(object)) {
47728
+ object.setIsInteractive(false);
47249
47729
  }
47250
47730
  }
47251
47731
  }
@@ -48691,13 +49171,7 @@ class KritzelWheelHelper {
48691
49171
  return this.canElementConsumeWheel(scrollContainer, ev.deltaX, ev.deltaY);
48692
49172
  }
48693
49173
  static getScrollableContainerFromEventPath(path, dynamicObject) {
48694
- const potentialRoots = [];
48695
- if (dynamicObject.element instanceof HTMLElement) {
48696
- potentialRoots.push(dynamicObject.element);
48697
- }
48698
- if (dynamicObject.elementRef instanceof HTMLElement) {
48699
- potentialRoots.push(dynamicObject.elementRef);
48700
- }
49174
+ const potentialRoots = dynamicObject.getEmbeddedContentRoots();
48701
49175
  const legacyElementRef = dynamicObject.elmeentRef;
48702
49176
  if (legacyElementRef instanceof HTMLElement) {
48703
49177
  potentialRoots.push(legacyElementRef);
@@ -48855,8 +49329,8 @@ class KritzelSvgExportHelper {
48855
49329
  .join('\n ');
48856
49330
  const xmlDecl = includeXmlDeclaration ? '<?xml version="1.0" encoding="UTF-8"?>\n' : '';
48857
49331
  const defsContent = defs.length > 0 ? `\n <defs>\n ${defs.join('\n ')}\n </defs>` : '';
48858
- return `${xmlDecl}<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBoxX} ${viewBoxY} ${viewBoxWidth} ${viewBoxHeight}" width="${viewBoxWidth}" height="${viewBoxHeight}">${defsContent}
48859
- ${elements}
49332
+ return `${xmlDecl}<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBoxX} ${viewBoxY} ${viewBoxWidth} ${viewBoxHeight}" width="${viewBoxWidth}" height="${viewBoxHeight}">${defsContent}
49333
+ ${elements}
48860
49334
  </svg>`;
48861
49335
  }
48862
49336
  /**
@@ -49008,8 +49482,8 @@ class KritzelSvgExportHelper {
49008
49482
  const orient = end === 'start' ? 'auto-start-reverse' : 'auto';
49009
49483
  const refX = 0;
49010
49484
  const refY = size / 2;
49011
- return `<marker id="${markerId}" markerWidth="${size}" markerHeight="${size}" refX="${refX}" refY="${refY}" orient="${orient}" markerUnits="userSpaceOnUse">
49012
- <path d="${path}" fill="${fill}" transform="scale(${size / 10})"/>
49485
+ return `<marker id="${markerId}" markerWidth="${size}" markerHeight="${size}" refX="${refX}" refY="${refY}" orient="${orient}" markerUnits="userSpaceOnUse">
49486
+ <path d="${path}" fill="${fill}" transform="scale(${size / 10})"/>
49013
49487
  </marker>`;
49014
49488
  }
49015
49489
  /**
@@ -49038,6 +49512,9 @@ class KritzelSvgExportHelper {
49038
49512
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
49039
49513
  return this.dynamicObjectToSvg(object, rasterSafeForCanvas);
49040
49514
  }
49515
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelIframeObject')) {
49516
+ return this.iframeObjectToSvg(object);
49517
+ }
49041
49518
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelGroup')) {
49042
49519
  return this.groupToSvg(object, theme, imageDataUrls, rasterSafeForCanvas);
49043
49520
  }
@@ -49056,10 +49533,10 @@ class KritzelSvgExportHelper {
49056
49533
  const opacity = path.opacity !== 1 ? ` opacity="${path.opacity}"` : '';
49057
49534
  const scale = this.getObjectScale(path);
49058
49535
  // Path needs to be wrapped in a g with translation since path.d uses local coordinates
49059
- return `<g transform="${transform}"${opacity}>
49060
- <svg viewBox="${path.viewBox}" width="${path.totalWidth / scale}" height="${path.totalHeight / scale}" overflow="visible">
49061
- <path d="${path.d}" fill="${fill || 'none'}" stroke="${stroke || 'none'}"${path.strokeWidth ? ` stroke-width="${path.strokeWidth}"` : ''}/>
49062
- </svg>
49536
+ return `<g transform="${transform}"${opacity}>
49537
+ <svg viewBox="${path.viewBox}" width="${path.totalWidth / scale}" height="${path.totalHeight / scale}" overflow="visible">
49538
+ <path d="${path.d}" fill="${fill || 'none'}" stroke="${stroke || 'none'}"${path.strokeWidth ? ` stroke-width="${path.strokeWidth}"` : ''}/>
49539
+ </svg>
49063
49540
  </g>`;
49064
49541
  }
49065
49542
  /**
@@ -49075,10 +49552,10 @@ class KritzelSvgExportHelper {
49075
49552
  const scale = this.getObjectScale(line);
49076
49553
  const markerStart = line.hasStartArrow ? ` marker-start="url(#${line.startMarkerId})"` : '';
49077
49554
  const markerEnd = line.hasEndArrow ? ` marker-end="url(#${line.endMarkerId})"` : '';
49078
- return `<g transform="${transform}"${opacity}>
49079
- <svg viewBox="${line.viewBox}" width="${line.totalWidth / scale}" height="${line.totalHeight / scale}" overflow="visible">
49080
- <path d="${line.d}" fill="none" stroke="${stroke}" stroke-width="${line.strokeWidth}" stroke-linecap="round"${markerStart}${markerEnd}/>
49081
- </svg>
49555
+ return `<g transform="${transform}"${opacity}>
49556
+ <svg viewBox="${line.viewBox}" width="${line.totalWidth / scale}" height="${line.totalHeight / scale}" overflow="visible">
49557
+ <path d="${line.d}" fill="none" stroke="${stroke}" stroke-width="${line.strokeWidth}" stroke-linecap="round"${markerStart}${markerEnd}/>
49558
+ </svg>
49082
49559
  </g>`;
49083
49560
  }
49084
49561
  /**
@@ -49102,18 +49579,18 @@ class KritzelSvgExportHelper {
49102
49579
  const textHtml = this.prosemirrorToHtml(shape.content, shape, theme);
49103
49580
  if (textHtml) {
49104
49581
  const fontColor = KritzelColorHelper.resolveThemeColor(shape.fontColor, theme);
49105
- textContent = `
49106
- <foreignObject x="${shape.x}" y="${shape.y}" width="${shape.width}" height="${shape.height}">
49107
- <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;">
49108
- ${textHtml}
49109
- </div>
49582
+ textContent = `
49583
+ <foreignObject x="${shape.x}" y="${shape.y}" width="${shape.width}" height="${shape.height}">
49584
+ <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;">
49585
+ ${textHtml}
49586
+ </div>
49110
49587
  </foreignObject>`;
49111
49588
  }
49112
49589
  }
49113
- return `<g transform="${transform}"${opacity}>
49114
- <svg viewBox="${shape.viewBox}" width="${shape.totalWidth / scale}" height="${shape.totalHeight / scale}" overflow="visible" preserveAspectRatio="none">
49115
- <path d="${pathD}" fill="${fill || 'transparent'}" stroke="${stroke}" stroke-width="${shape.strokeWidth}"/>${textContent}
49116
- </svg>
49590
+ return `<g transform="${transform}"${opacity}>
49591
+ <svg viewBox="${shape.viewBox}" width="${shape.totalWidth / scale}" height="${shape.totalHeight / scale}" overflow="visible" preserveAspectRatio="none">
49592
+ <path d="${pathD}" fill="${fill || 'transparent'}" stroke="${stroke}" stroke-width="${shape.strokeWidth}"/>${textContent}
49593
+ </svg>
49117
49594
  </g>`;
49118
49595
  }
49119
49596
  /**
@@ -49144,10 +49621,10 @@ class KritzelSvgExportHelper {
49144
49621
  // adjusted font-size already accounts for it – keeping both would double-scale
49145
49622
  // the content and overflow the foreignObject.
49146
49623
  const svgFontSizePx = (text.fontSize * (4 / 3) * (text.scaleFactor || 1) / scale).toFixed(4);
49147
- return `<g transform="${transform}"${opacity}>
49148
- <foreignObject x="0" y="0" width="${width}" height="${height}" overflow="visible">
49149
- <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>
49150
- </foreignObject>
49624
+ return `<g transform="${transform}"${opacity}>
49625
+ <foreignObject x="0" y="0" width="${width}" height="${height}" overflow="visible">
49626
+ <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>
49627
+ </foreignObject>
49151
49628
  </g>`;
49152
49629
  }
49153
49630
  /**
@@ -49181,9 +49658,9 @@ class KritzelSvgExportHelper {
49181
49658
  return `<tspan x="0" y="${y}" xml:space="preserve">${this.escapeHtml(line)}</tspan>`;
49182
49659
  })
49183
49660
  .join('');
49184
- return `<g transform="${transform}"${opacity}>
49185
- ${bgRect}
49186
- <text font-family="${this.escapeHtml(text.fontFamily)}" font-size="${resolvedFontSizePx}" fill="${fontColor}">${tspans}</text>
49661
+ return `<g transform="${transform}"${opacity}>
49662
+ ${bgRect}
49663
+ <text font-family="${this.escapeHtml(text.fontFamily)}" font-size="${resolvedFontSizePx}" fill="${fontColor}">${tspans}</text>
49187
49664
  </g>`;
49188
49665
  }
49189
49666
  /**
@@ -49211,8 +49688,8 @@ class KritzelSvgExportHelper {
49211
49688
  const width = image.totalWidth / scale;
49212
49689
  const height = image.totalHeight / scale;
49213
49690
  const href = imageDataUrls?.get(image.id) || image.resolvedSrc || image.src || '';
49214
- return `<g transform="${transform}"${opacity}>
49215
- <image href="${href}" x="0" y="0" width="${width}" height="${height}" preserveAspectRatio="xMidYMid meet"/>
49691
+ return `<g transform="${transform}"${opacity}>
49692
+ <image href="${href}" x="0" y="0" width="${width}" height="${height}" preserveAspectRatio="xMidYMid meet"/>
49216
49693
  </g>`;
49217
49694
  }
49218
49695
  /**
@@ -49232,19 +49709,30 @@ class KritzelSvgExportHelper {
49232
49709
  if (rasterSafeForCanvas) {
49233
49710
  // PNG export: render placeholder rect (foreignObject doesn't rasterize to PNG well)
49234
49711
  console.warn('[KritzelDynamicObject SVG Export] Dynamic objects do not serialize well to PNG. Use SVG export or screenshot instead.');
49235
- return `<g transform="${transform}"${opacity}>
49236
- <rect x="0" y="0" width="${width}" height="${height}" fill="#f0f0f0" stroke="#999" stroke-width="1" />
49237
- <text x="${width / 2}" y="${height / 2}" text-anchor="middle" dominant-baseline="middle" font-size="12" fill="#999">[Dynamic Object]</text>
49712
+ return `<g transform="${transform}"${opacity}>
49713
+ <rect x="0" y="0" width="${width}" height="${height}" fill="#f0f0f0" stroke="#999" stroke-width="1" />
49714
+ <text x="${width / 2}" y="${height / 2}" text-anchor="middle" dominant-baseline="middle" font-size="12" fill="#999">[Dynamic Object]</text>
49238
49715
  </g>`;
49239
49716
  }
49240
49717
  // SVG export: use foreignObject to embed HTML
49241
49718
  const html = obj.element?.outerHTML || '<div style="border:1px dashed #ccc">Empty</div>';
49242
- return `<g transform="${transform}"${opacity}>
49243
- <foreignObject x="0" y="0" width="${width}" height="${height}">
49244
- <div xmlns="http://www.w3.org/1999/xhtml" style="width:100%;height:100%;overflow:hidden;">
49245
- ${html}
49246
- </div>
49247
- </foreignObject>
49719
+ return `<g transform="${transform}"${opacity}>
49720
+ <foreignObject x="0" y="0" width="${width}" height="${height}">
49721
+ <div xmlns="http://www.w3.org/1999/xhtml" style="width:100%;height:100%;overflow:hidden;">
49722
+ ${html}
49723
+ </div>
49724
+ </foreignObject>
49725
+ </g>`;
49726
+ }
49727
+ static iframeObjectToSvg(object) {
49728
+ const transform = this.buildTransform(object);
49729
+ const opacity = object.opacity !== 1 ? ` opacity="${object.opacity}"` : '';
49730
+ const scale = this.getObjectScale(object);
49731
+ const width = object.totalWidth / scale;
49732
+ const height = object.totalHeight / scale;
49733
+ return `<g transform="${transform}"${opacity}>
49734
+ <rect x="0" y="0" width="${width}" height="${height}" fill="#f0f0f0" stroke="#999" stroke-width="1" />
49735
+ <text x="${width / 2}" y="${height / 2}" text-anchor="middle" dominant-baseline="middle" font-size="12" fill="#999">[Iframe]</text>
49248
49736
  </g>`;
49249
49737
  }
49250
49738
  /**
@@ -49264,8 +49752,8 @@ class KritzelSvgExportHelper {
49264
49752
  return '';
49265
49753
  }
49266
49754
  const opacity = group.opacity !== 1 ? ` opacity="${group.opacity}"` : '';
49267
- return `<g${opacity}>
49268
- ${children}
49755
+ return `<g${opacity}>
49756
+ ${children}
49269
49757
  </g>`;
49270
49758
  }
49271
49759
  /**
@@ -49450,12 +49938,26 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
49450
49938
  /** Authoritative workspace catalog for this engine instance. When provided, provider-only workspaces are hidden but not deleted. */
49451
49939
  workspaces;
49452
49940
  async onWorkspacesPropChange() {
49453
- if (!this._isYjsInitialized) {
49941
+ this._workspacesRevision++;
49942
+ if (!this._isYjsInitialized || this._syncInitPromise) {
49454
49943
  return;
49455
49944
  }
49456
- await this.reconcileProvidedWorkspaces();
49457
- await this.ensureActiveWorkspaceIsVisible();
49458
- this.workspacesChange.emit(this.getVisibleWorkspaces());
49945
+ const previousReconciliation = this._workspacePropReconciliationPromise;
49946
+ const reconciliationPromise = (async () => {
49947
+ await previousReconciliation;
49948
+ await this.reconcileProvidedWorkspaces();
49949
+ await this.ensureActiveWorkspaceIsVisible();
49950
+ this.workspacesChange.emit(this.getVisibleWorkspaces());
49951
+ })();
49952
+ this._workspacePropReconciliationPromise = reconciliationPromise;
49953
+ try {
49954
+ await reconciliationPromise;
49955
+ }
49956
+ finally {
49957
+ if (this._workspacePropReconciliationPromise === reconciliationPromise) {
49958
+ this._workspacePropReconciliationPromise = null;
49959
+ }
49960
+ }
49459
49961
  }
49460
49962
  /** Optional unique identifier for namespacing storage keys across multiple editor instances. */
49461
49963
  editorId;
@@ -49509,6 +50011,12 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
49509
50011
  onUserChange(newValue) {
49510
50012
  this.core.setUser(newValue);
49511
50013
  }
50014
+ /** Tool definitions to register with this engine instance. */
50015
+ tools = [];
50016
+ onToolsChange() {
50017
+ this._toolsRevision++;
50018
+ void this.registerConfiguredTools();
50019
+ }
49512
50020
  /** Context menu items shown when right-clicking the canvas background. */
49513
50021
  globalContextMenuItems;
49514
50022
  onGlobalContextMenuItemsChange(newValue) {
@@ -49699,7 +50207,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
49699
50207
  }
49700
50208
  const hoveredObject = this.core.getObjectFromPointerEvent(ev);
49701
50209
  if (hoveredObject &&
49702
- ObjectHelper.isClass(hoveredObject, 'KritzelDynamicObject') &&
50210
+ ObjectHelper.isKritzelEmbeddedObject(hoveredObject) &&
49703
50211
  KritzelWheelHelper.shouldKeepWheelInDynamicObject(ev, hoveredObject)) {
49704
50212
  return;
49705
50213
  }
@@ -49801,7 +50309,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
49801
50309
  return false;
49802
50310
  }
49803
50311
  const object = this.core.store.objects.findById(objectElement.id);
49804
- if (!object || !KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
50312
+ if (!object || !ObjectHelper.isKritzelEmbeddedObject(object)) {
49805
50313
  return false;
49806
50314
  }
49807
50315
  const isSelected = !!this.core.store.selectionGroup?.objects?.some(selectedObject => selectedObject.id === object.id);
@@ -49811,13 +50319,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
49811
50319
  if (object.isInteractive !== true) {
49812
50320
  return false;
49813
50321
  }
49814
- const potentialRoots = [];
49815
- if (object.element instanceof H) {
49816
- potentialRoots.push(object.element);
49817
- }
49818
- if (object.elementRef instanceof H) {
49819
- potentialRoots.push(object.elementRef);
49820
- }
50322
+ const potentialRoots = object.getEmbeddedContentRoots();
49821
50323
  if (potentialRoots.length === 0) {
49822
50324
  return false;
49823
50325
  }
@@ -49991,7 +50493,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
49991
50493
  * Switches the active drawing tool by its registered name.
49992
50494
  * @param toolName - The name the tool was registered with (e.g., 'brush', 'eraser').
49993
50495
  */
49994
- async changeActiveTool(toolName) {
50496
+ async setActiveTool(toolName) {
49995
50497
  const tool = this.core.toolRegistry.getTool(toolName);
49996
50498
  if (!tool) {
49997
50499
  console.warn(`[KritzelEngine] No tool registered with name: ${toolName}`);
@@ -51297,9 +51799,13 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51297
51799
  _stateChangeListenersRegistered = false;
51298
51800
  _workspaceInitializationPromise = null;
51299
51801
  _workspaceInitializationTargetKey = null;
51802
+ _workspacePropReconciliationPromise = null;
51300
51803
  _syncInitPromise = null;
51804
+ _toolsInitPromise = null;
51805
+ _toolsRevision = 0;
51301
51806
  _syncConfigRevision = 0;
51302
51807
  _assetStorageConfigRevision = 0;
51808
+ _workspacesRevision = 0;
51303
51809
  _hasLoggedUnlicensedWelcomeBanner = false;
51304
51810
  _previousVisibleDynamicObjects = new Set();
51305
51811
  _revealedImageKeys = new Set();
@@ -51312,6 +51818,37 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51312
51818
  undoStackSize: 0,
51313
51819
  redoStackSize: 0,
51314
51820
  };
51821
+ /** Returns a tool registered in this engine instance. */
51822
+ async getTool(toolName) {
51823
+ await this.registerConfiguredTools();
51824
+ return this.core.toolRegistry.getTool(toolName);
51825
+ }
51826
+ async registerConfiguredTools() {
51827
+ if (this._toolsInitPromise) {
51828
+ return this._toolsInitPromise;
51829
+ }
51830
+ this._toolsInitPromise = (async () => {
51831
+ do {
51832
+ const revision = this._toolsRevision;
51833
+ for (const definition of this.tools) {
51834
+ let toolClass = definition.tool;
51835
+ if (typeof toolClass !== 'function') {
51836
+ toolClass = toolClass.constructor;
51837
+ }
51838
+ await this.registerTool(definition.name, toolClass, definition.config);
51839
+ }
51840
+ if (revision === this._toolsRevision) {
51841
+ break;
51842
+ }
51843
+ } while (true);
51844
+ })();
51845
+ try {
51846
+ await this._toolsInitPromise;
51847
+ }
51848
+ finally {
51849
+ this._toolsInitPromise = null;
51850
+ }
51851
+ }
51315
51852
  setLoadingState(isLoading) {
51316
51853
  if (this.core.store.state.isLoading === isLoading) {
51317
51854
  return;
@@ -51443,6 +51980,8 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51443
51980
  this.core.licenseManager.destroy();
51444
51981
  }
51445
51982
  componentWillLoad() {
51983
+ this._toolsRevision++;
51984
+ void this.registerConfiguredTools();
51446
51985
  this.core.setEditorId(this.editorId);
51447
51986
  if (this.themes && this.themes.length > 0) {
51448
51987
  this.core.themeManager.registerThemes(this.themes);
@@ -51482,7 +52021,9 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51482
52021
  const editorElement = this.host.closest('kritzel-editor');
51483
52022
  this.core.themeManager.setTargetElement(editorElement || this.host);
51484
52023
  // Initialize sync config and Yjs
52024
+ await this.registerConfiguredTools();
51485
52025
  await this.initializeSyncAndWorkspace();
52026
+ await this.waitForWorkspaceAndSceneReady();
51486
52027
  if (this.core.store.state.isReady === false) {
51487
52028
  this.core.store.state.isReady = true;
51488
52029
  this.logUnlicensedWelcomeBanner();
@@ -51504,6 +52045,20 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51504
52045
  this._syncInitPromise = null;
51505
52046
  }
51506
52047
  }
52048
+ async waitForSceneRender() {
52049
+ await new Promise(resolve => {
52050
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
52051
+ });
52052
+ }
52053
+ async waitForWorkspaceAndSceneReady() {
52054
+ let observedRevision;
52055
+ do {
52056
+ observedRevision = this._workspacesRevision;
52057
+ await this._workspacePropReconciliationPromise;
52058
+ await this.waitForSceneRender();
52059
+ } while (observedRevision !== this._workspacesRevision ||
52060
+ this._workspacePropReconciliationPromise);
52061
+ }
51507
52062
  /**
51508
52063
  * Initializes the asset storage layer with the latest config. Mirrors
51509
52064
  * the in-flight replay logic of {@link doInitializeSyncAndWorkspace} for
@@ -51886,7 +52441,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51886
52441
  if (!KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup')) {
51887
52442
  return 'auto';
51888
52443
  }
51889
- if (object.objects.length === 1 && KritzelClassHelper.isInstanceOf(object.objects[0], 'KritzelDynamicObject')) {
52444
+ if (object.objects.length === 1 && ObjectHelper.isKritzelEmbeddedObject(object.objects[0])) {
51890
52445
  return 'none';
51891
52446
  }
51892
52447
  return 'auto';
@@ -51894,7 +52449,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51894
52449
  // Track visible dynamic objects and unmount culled ones
51895
52450
  const currentVisibleDynamicObjects = new Set();
51896
52451
  visibleObjects.forEach(object => {
51897
- if (KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
52452
+ if (ObjectHelper.isKritzelEmbeddedObject(object)) {
51898
52453
  currentVisibleDynamicObjects.add(object.id);
51899
52454
  }
51900
52455
  });
@@ -51902,7 +52457,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51902
52457
  this._previousVisibleDynamicObjects.forEach(id => {
51903
52458
  if (!currentVisibleDynamicObjects.has(id)) {
51904
52459
  const object = this.core.store.objects.findById(id);
51905
- if (object && KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
52460
+ if (object && ObjectHelper.isKritzelEmbeddedObject(object)) {
51906
52461
  object.unmount();
51907
52462
  }
51908
52463
  }
@@ -51995,7 +52550,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51995
52550
  display: 'flex',
51996
52551
  alignItems: 'center',
51997
52552
  justifyContent: 'center',
51998
- } }, h("kritzel-icon", { name: object.loadState === 'error' ? 'image-off' : 'image', size: Math.max(16, Math.min(object.totalWidth, object.totalHeight) * 0.3), style: {
52553
+ } }, h("kritzel-icon", { name: object.loadState === 'error' ? 'imageOff' : 'image', size: Math.max(16, Math.min(object.totalWidth, object.totalHeight) * 0.3), style: {
51999
52554
  color: KritzelColorHelper.resolveThemeColor({ light: '#9ca3af', dark: '#6b7280' }, currentTheme),
52000
52555
  } }))), KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject') && (h("div", { ref: el => el && object.mount(el), style: {
52001
52556
  position: 'absolute',
@@ -52014,7 +52569,37 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
52014
52569
  padding: object.padding + 'px',
52015
52570
  overflow: 'visible',
52016
52571
  display: 'block',
52017
- } })), KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup') && !this.core.displaySelectionLineUI(object) && (h("div", { ref: el => el && object.mount(el), style: {
52572
+ } })), KritzelClassHelper.isInstanceOf(object, 'KritzelIframeObject') && (h("div", { style: {
52573
+ position: 'absolute',
52574
+ left: '0',
52575
+ top: '0',
52576
+ width: object.totalWidth + 'px',
52577
+ height: object.totalHeight + 'px',
52578
+ transform: object.rotationDegrees !== 0 ? `rotate(${object.rotationDegrees}deg)` : undefined,
52579
+ transformOrigin: object.rotationDegrees !== 0 ? `${object.totalWidth / 2}px ${object.totalHeight / 2}px` : undefined,
52580
+ opacity: effectiveOpacityString,
52581
+ backgroundColor: KritzelColorHelper.resolveThemeColor(object.backgroundColor, currentTheme),
52582
+ borderColor: KritzelColorHelper.resolveThemeColor(object.borderColor, currentTheme),
52583
+ borderWidth: object.borderWidth + 'px',
52584
+ borderStyle: 'solid',
52585
+ borderRadius: object.borderRadius !== undefined ? (typeof object.borderRadius === 'number' ? `${object.borderRadius}px` : object.borderRadius) : undefined,
52586
+ boxShadow: object.boxShadow,
52587
+ overflow: 'hidden',
52588
+ display: 'block',
52589
+ pointerEvents: object.markedForRemoval ? 'none' : 'auto',
52590
+ } }, h("iframe", { ref: element => element && object.mount(element), title: object.title, sandbox: object.sandboxAttribute, style: {
52591
+ display: 'block',
52592
+ width: '100%',
52593
+ height: '100%',
52594
+ border: '0',
52595
+ borderRadius: object.borderRadius !== undefined ? (typeof object.borderRadius === 'number' ? `${object.borderRadius}px` : object.borderRadius) : undefined,
52596
+ pointerEvents: object.isInteractive ? 'auto' : 'none',
52597
+ } }), h("div", { style: {
52598
+ position: 'absolute',
52599
+ inset: '0',
52600
+ borderRadius: object.borderRadius !== undefined ? (typeof object.borderRadius === 'number' ? `${object.borderRadius}px` : object.borderRadius) : undefined,
52601
+ pointerEvents: object.isInteractive ? 'none' : 'auto',
52602
+ } }))), KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup') && !this.core.displaySelectionLineUI(object) && (h("div", { ref: el => el && object.mount(el), style: {
52018
52603
  position: 'absolute',
52019
52604
  left: '0',
52020
52605
  top: '0',
@@ -52372,6 +52957,9 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
52372
52957
  "user": [{
52373
52958
  "onUserChange": 0
52374
52959
  }],
52960
+ "tools": [{
52961
+ "onToolsChange": 0
52962
+ }],
52375
52963
  "globalContextMenuItems": [{
52376
52964
  "onGlobalContextMenuItemsChange": 0
52377
52965
  }],
@@ -52432,6 +53020,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
52432
53020
  "syncConfig": [16],
52433
53021
  "assetStorageConfig": [16],
52434
53022
  "user": [16],
53023
+ "tools": [16],
52435
53024
  "globalContextMenuItems": [16],
52436
53025
  "objectContextMenuItems": [16],
52437
53026
  "scaleMax": [1026, "scale-max"],
@@ -52456,7 +53045,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
52456
53045
  "beginSceneBootstrap": [64],
52457
53046
  "endSceneBootstrap": [64],
52458
53047
  "registerTool": [64],
52459
- "changeActiveTool": [64],
53048
+ "setActiveTool": [64],
52460
53049
  "disable": [64],
52461
53050
  "enable": [64],
52462
53051
  "delete": [64],
@@ -52532,7 +53121,8 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
52532
53121
  "getAvailableLocales": [64],
52533
53122
  "getAvailableLocaleOptions": [64],
52534
53123
  "t": [64],
52535
- "getResolvedTerms": [64]
53124
+ "getResolvedTerms": [64],
53125
+ "getTool": [64]
52536
53126
  }, [[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"]], {
52537
53127
  "workspaces": [{
52538
53128
  "onWorkspacesPropChange": 0
@@ -52549,6 +53139,9 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
52549
53139
  "user": [{
52550
53140
  "onUserChange": 0
52551
53141
  }],
53142
+ "tools": [{
53143
+ "onToolsChange": 0
53144
+ }],
52552
53145
  "globalContextMenuItems": [{
52553
53146
  "onGlobalContextMenuItemsChange": 0
52554
53147
  }],
@@ -52638,4 +53231,4 @@ function defineCustomElement$1() {
52638
53231
  const KritzelEngine = KritzelEngine$1;
52639
53232
  const defineCustomElement = defineCustomElement$1;
52640
53233
 
52641
- export { AssetNotFoundError as A, KritzelFontRegistry as B, DEFAULT_SYNC_CONFIG as C, DEFAULT_STROKE_SIZES as D, EN_LOCALE as E, DE_LOCALE as F, FR_LOCALE as G, KritzelAlignment as H, IndexedDBAssetProvider as I, runMigrations as J, KritzelText as K, KritzelEngine, APP_STATE_MIGRATIONS as L, WORKSPACE_MIGRATIONS as M, CURRENT_APP_STATE_SCHEMA_VERSION as N, CURRENT_WORKSPACE_SCHEMA_VERSION as O, resolveTextToolAvailableFonts as P, ShapeType as S, WORKSPACE_EXPORT_VERSION as W, DEFAULT_FONT_SIZES as a, DEFAULT_TEXT_TOOL_AVAILABLE_FONTS as b, KritzelImage as c, KritzelGroup as d, defineCustomElement, KritzelShape as e, KritzelDynamicObject as f, KritzelSelectionGroup as g, KritzelSelectionBox as h, KritzelBaseTool as i, KritzelBrushTool as j, KritzelLineTool as k, KritzelEraserTool as l, KritzelImageTool as m, KritzelTextTool as n, KritzelShapeTool as o, KritzelCursorHelper as p, KritzelKeyboardHelper as q, KritzelDevicesHelper as r, KritzelHTMLHelper as s, KritzelSelectionTool as t, KritzelAssetResolver as u, KritzelWorkspace as v, KritzelAnchorManager as w, KritzelLocalizationManager as x, KritzelLicenseManager as y, KritzelDynamicObjectRendererRegistry as z };
53234
+ export { AssetNotFoundError as A, KritzelDynamicObjectRendererRegistry as B, KritzelFontRegistry as C, DEFAULT_STROKE_SIZES as D, DEFAULT_SYNC_CONFIG as E, EN_LOCALE as F, DE_LOCALE as G, FR_LOCALE as H, IndexedDBAssetProvider as I, KritzelAlignment as J, KritzelText as K, KritzelEngine, runMigrations as L, APP_STATE_MIGRATIONS as M, WORKSPACE_MIGRATIONS as N, CURRENT_APP_STATE_SCHEMA_VERSION as O, CURRENT_WORKSPACE_SCHEMA_VERSION as P, resolveTextToolAvailableFonts as Q, ShapeType as S, WORKSPACE_EXPORT_VERSION as W, DEFAULT_FONT_SIZES as a, DEFAULT_TEXT_TOOL_AVAILABLE_FONTS as b, KritzelImage as c, KritzelGroup as d, defineCustomElement, KritzelShape as e, KritzelDynamicObject as f, KritzelIframeObject as g, KritzelSelectionGroup as h, KritzelSelectionBox as i, KritzelBaseTool as j, KritzelBrushTool as k, KritzelLineTool as l, KritzelEraserTool as m, KritzelImageTool as n, KritzelTextTool as o, KritzelShapeTool as p, KritzelCursorHelper as q, KritzelKeyboardHelper as r, KritzelDevicesHelper as s, KritzelHTMLHelper as t, KritzelSelectionTool as u, KritzelAssetResolver as v, KritzelWorkspace as w, KritzelAnchorManager as x, KritzelLocalizationManager as y, KritzelLicenseManager as z };