@kritzel/engine 0.4.31 → 0.4.33

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 (84) hide show
  1. package/LICENSE.md +49 -49
  2. package/dist/cjs/{engine.constants-CakmdKMW.js → engine.constants-VyFrUl1W.js} +627 -129
  3. package/dist/cjs/{index-BbbWU2aN.js → index-BP5Iy6FQ.js} +27 -9
  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 +3 -3
  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 +4 -2
  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 +755 -161
  40. package/dist/components/kritzel-icon.js +1 -1
  41. package/dist/components/kritzel-watermark.js +1 -1
  42. package/dist/components/{p-B2Htv9ou.js → p-BHVbfJrf.js} +3 -3
  43. package/dist/components/{p-YP8wAcF4.js → p-BTU9qcSW.js} +2 -2
  44. package/dist/components/{p-CfSx6HU_.js → p-BWfRbEIq.js} +16 -4
  45. package/dist/components/p-NAEiq8CK.js +214 -0
  46. package/dist/components/{p-BvRGQvVI.js → p-k0LxV9Bg.js} +18 -14
  47. package/dist/esm/{engine.constants-Bl5-pbq-.js → engine.constants-BHTr2qPx.js} +626 -130
  48. package/dist/esm/{index-BU9Mv7y0.js → index-C8PnHbLt.js} +27 -10
  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 +4 -4
  53. package/dist/stencil/index.esm.js +3 -3
  54. package/dist/stencil/{p-f0a8b7af.entry.js → p-40097a38.entry.js} +257 -113
  55. package/dist/stencil/{p-Bl5-pbq-.js → p-BHTr2qPx.js} +626 -130
  56. package/dist/stencil/{p-BU9Mv7y0.js → p-C8PnHbLt.js} +27 -10
  57. package/dist/stencil/stencil.esm.js +4 -4
  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/dist/types/stencil-public-runtime.d.ts +61 -55
  82. package/package.json +3 -1
  83. package/readme.md +10 -10
  84. 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-BWfRbEIq.js';
2
+ import { a as KritzelMouseButton, d as defineCustomElement$4 } from './p-BTU9qcSW.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-k0LxV9Bg.js';
4
+ import { A as ABSOLUTE_SCALE_MAX, a as ABSOLUTE_SCALE_MIN, d as defineCustomElement$2 } from './p-BHVbfJrf.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-NAEiq8CK.js';
7
7
 
8
8
  class KritzelDevicesHelper {
9
9
  static isTouchDevice() {
@@ -739,7 +739,6 @@ class Mark {
739
739
  if (!type)
740
740
  throw new RangeError(`There is no mark type ${json.type} in this schema`);
741
741
  let mark = type.create(json.attrs);
742
- type.checkAttrs(mark.attrs);
743
742
  return mark;
744
743
  }
745
744
  /**
@@ -1719,11 +1718,11 @@ class Node {
1719
1718
  */
1720
1719
  check() {
1721
1720
  this.type.checkContent(this.content);
1722
- this.type.checkAttrs(this.attrs);
1721
+ checkAttrs(this.type.attrs, this.attrs, "node", this.type.name);
1723
1722
  let copy = Mark.none;
1724
1723
  for (let i = 0; i < this.marks.length; i++) {
1725
1724
  let mark = this.marks[i];
1726
- mark.type.checkAttrs(mark.attrs);
1725
+ checkAttrs(mark.type.attrs, mark.attrs, "mark", mark.type.name);
1727
1726
  copy = mark.addToSet(copy);
1728
1727
  }
1729
1728
  if (!Mark.sameSet(copy, this.marks))
@@ -1764,7 +1763,6 @@ class Node {
1764
1763
  }
1765
1764
  let content = Fragment.fromJSON(schema, json.content);
1766
1765
  let node = schema.nodeType(json.type).create(json.attrs, content, marks);
1767
- node.type.checkAttrs(node.attrs);
1768
1766
  return node;
1769
1767
  }
1770
1768
  }
@@ -1814,6 +1812,15 @@ function wrapMarks(marks, str) {
1814
1812
  str = marks[i].type.name + "(" + str + ")";
1815
1813
  return str;
1816
1814
  }
1815
+ function checkAttrs(attrs, values, type, name) {
1816
+ for (let attr in values)
1817
+ if (!(attr in attrs))
1818
+ throw new RangeError(`Unsupported attribute ${attr} for ${type} of type ${name}`);
1819
+ for (let attr in attrs) {
1820
+ if (attrs[attr].validate)
1821
+ attrs[attr].validate(values[attr]);
1822
+ }
1823
+ }
1817
1824
 
1818
1825
  /**
1819
1826
  Instances of this class represent a match state of a node type's
@@ -2268,27 +2275,21 @@ function defaultAttrs(attrs) {
2268
2275
  function computeAttrs(attrs, value) {
2269
2276
  let built = Object.create(null);
2270
2277
  for (let name in attrs) {
2278
+ let attr = attrs[name];
2271
2279
  let given = value && value[name];
2272
2280
  if (given === undefined) {
2273
- let attr = attrs[name];
2274
2281
  if (attr.hasDefault)
2275
2282
  given = attr.default;
2276
2283
  else
2277
2284
  throw new RangeError("No value supplied for attribute " + name);
2278
2285
  }
2286
+ else if (attr.validate) {
2287
+ attr.validate(given);
2288
+ }
2279
2289
  built[name] = given;
2280
2290
  }
2281
2291
  return built;
2282
2292
  }
2283
- function checkAttrs(attrs, values, type, name) {
2284
- for (let attr in values)
2285
- if (!(attr in attrs))
2286
- throw new RangeError(`Unsupported attribute ${attr} for ${type} of type ${name}`);
2287
- for (let attr in attrs) {
2288
- if (attrs[attr].validate)
2289
- attrs[attr].validate(values[attr]);
2290
- }
2291
- }
2292
2293
  function initAttrs(typeName, attrs) {
2293
2294
  let result = Object.create(null);
2294
2295
  if (attrs)
@@ -2460,11 +2461,9 @@ let NodeType$1 = class NodeType {
2460
2461
  throw new RangeError(`Invalid content for node ${this.name}: ${content.toString().slice(0, 50)}`);
2461
2462
  }
2462
2463
  /**
2463
- @internal
2464
+ @internal no longer useful, but called by old prosemirror-view versions
2464
2465
  */
2465
- checkAttrs(attrs) {
2466
- checkAttrs(this.attrs, attrs, "node", this.name);
2467
- }
2466
+ checkAttrs(attrs) { }
2468
2467
  /**
2469
2468
  Check whether the given mark type is allowed in this node.
2470
2469
  */
@@ -2611,12 +2610,6 @@ class MarkType {
2611
2610
  return set[i];
2612
2611
  }
2613
2612
  /**
2614
- @internal
2615
- */
2616
- checkAttrs(attrs) {
2617
- checkAttrs(this.attrs, attrs, "mark", this.name);
2618
- }
2619
- /**
2620
2613
  Queries whether a given mark type is
2621
2614
  [excluded](https://prosemirror.net/docs/ref/#model.MarkSpec.excludes) by this one.
2622
2615
  */
@@ -9308,7 +9301,8 @@ function nodeLen(node) {
9308
9301
  }
9309
9302
  function isIgnorable(dom, dir) {
9310
9303
  let desc = dom.pmViewDesc;
9311
- return desc && desc.size == 0 && (dir < 0 || dom.nextSibling || dom.nodeName != "BR");
9304
+ return desc ? desc.size == 0 && (dir < 0 || dom.nextSibling || dom.nodeName != "BR")
9305
+ : dom.nodeType == 1 && dom.contentEditable == "false";
9312
9306
  }
9313
9307
  function skipIgnoredNodes(view, dir) {
9314
9308
  return dir < 0 ? skipIgnoredNodesBefore(view) : skipIgnoredNodesAfter(view);
@@ -9897,12 +9891,11 @@ function addContext(slice, context) {
9897
9891
  if (!type || type.hasRequiredAttrs())
9898
9892
  break;
9899
9893
  try {
9900
- type.checkAttrs(array[i + 1]);
9894
+ content = Fragment.from(type.create(array[i + 1], content));
9901
9895
  }
9902
9896
  catch (e) {
9903
9897
  break;
9904
9898
  }
9905
- content = Fragment.from(type.create(array[i + 1], content));
9906
9899
  openStart++;
9907
9900
  openEnd++;
9908
9901
  }
@@ -10382,7 +10375,7 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
10382
10375
  if (!view.composing) {
10383
10376
  view.domObserver.flush();
10384
10377
  let { state } = view, $pos = state.selection.$to;
10385
- if (state.selection instanceof TextSelection &&
10378
+ if (state.selection instanceof TextSelection && state.selection.empty &&
10386
10379
  (state.storedMarks ||
10387
10380
  (!$pos.textOffset && $pos.parentOffset && $pos.nodeBefore.marks.some(m => m.type.spec.inclusive === false)) ||
10388
10381
  chrome && windows$1 && selectionBeforeUneditable(view))) { // Issue #1500
@@ -12019,9 +12012,7 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
12019
12012
  // as being an iOS enter press), just dispatch an Enter key instead.
12020
12013
  if (((ios && view.input.lastIOSEnter > Date.now() - 225 &&
12021
12014
  (!inlineChange || addedNodes.some(n => n.nodeName == "DIV" || n.nodeName == "P"))) ||
12022
- (!inlineChange && $from.pos < parse.doc.content.size &&
12023
- (!$from.sameParent($to) || !$from.parent.inlineContent) &&
12024
- $from.pos < $to.pos && !/\S/.test(parse.doc.textBetween($from.pos, $to.pos, "", "")))) &&
12015
+ (!inlineChange && looksLikeEnter(parse.doc, $from.pos, $to.pos))) &&
12025
12016
  view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter")))) {
12026
12017
  view.input.lastIOSEnter = 0;
12027
12018
  return;
@@ -12172,6 +12163,16 @@ function looksLikeBackspace(old, start, end, $newStart, $newEnd) {
12172
12163
  // The fragments after the join point must match
12173
12164
  return $newStart.parent.content.cut($newStart.parentOffset).eq($next.parent.content);
12174
12165
  }
12166
+ function looksLikeEnter(content, from, to) {
12167
+ let newTextlines = 0, newLeaves = 0;
12168
+ content.nodesBetween(from, to, (node, pos) => {
12169
+ if (node.isTextblock && pos >= from && pos < to)
12170
+ newTextlines++;
12171
+ else if (node.isText ? /\S/.test(node.text.slice(Math.max(0, from - pos), Math.min(node.nodeSize, to - pos))) : node.isLeaf)
12172
+ newLeaves++;
12173
+ });
12174
+ return newLeaves == 0 && newTextlines == 1;
12175
+ }
12175
12176
  function skipClosingAndOpening($pos, fromEnd, mayOpen) {
12176
12177
  let depth = $pos.depth, end = fromEnd ? $pos.end() : $pos.pos;
12177
12178
  while (depth > 0 && (fromEnd || $pos.indexAfter(depth) == $pos.node(depth).childCount)) {
@@ -17181,7 +17182,7 @@ class KritzelDynamicObject extends KritzelBaseObject {
17181
17182
  }
17182
17183
  applyInteractiveState() {
17183
17184
  const pointerEvents = this.isInteractive ? 'auto' : 'none';
17184
- const contentCursor = this.isInteractive ? 'auto' : '';
17185
+ const contentCursor = this.isInteractive ? 'var(--kritzel-global-default-cursor, auto)' : '';
17185
17186
  if (this.element) {
17186
17187
  this.element.style.pointerEvents = pointerEvents;
17187
17188
  this.element.style.cursor = contentCursor;
@@ -17217,7 +17218,7 @@ class KritzelDynamicObject extends KritzelBaseObject {
17217
17218
  if (!this.isInteractive) {
17218
17219
  return false;
17219
17220
  }
17220
- const focusHosts = this.getFocusHosts();
17221
+ const focusHosts = this.getEmbeddedContentRoots();
17221
17222
  // Primary path: locate the focused input-like element by walking from each host through
17222
17223
  // `:focus` (piercing open shadow roots). This works in real browsers even when the engine
17223
17224
  // and editor are nested web components, because it never relies on the document-level
@@ -17234,7 +17235,7 @@ class KritzelDynamicObject extends KritzelBaseObject {
17234
17235
  }
17235
17236
  return false;
17236
17237
  }
17237
- getFocusHosts() {
17238
+ getEmbeddedContentRoots() {
17238
17239
  const hosts = [];
17239
17240
  if (this.element) {
17240
17241
  hosts.push(this.element);
@@ -17447,11 +17448,11 @@ class KritzelDynamicObject extends KritzelBaseObject {
17447
17448
  * @returns A serializable object with htmlString and dimension snapshots
17448
17449
  */
17449
17450
  serialize() {
17450
- const { _core, _elementRef, element, totalWidth, totalHeight, isSelected, isHovered, isMounted, isInteractive, resizing, markedForRemoval, isVisible, ...props } = this;
17451
+ const { _core, _elementRef, element, totalWidth, totalHeight, isSelected, isHovered, isMounted, isInteractive, resizing, markedForRemoval, isVisible, rendererData, ...props } = this;
17451
17452
  const cloned = structuredClone(props);
17452
17453
  // Include rendererKey and rendererData for lifecycle-driven rendering
17453
17454
  cloned.rendererKey = this.rendererKey;
17454
- cloned.rendererData = this.rendererData;
17455
+ cloned.rendererData = this.serializeRendererData();
17455
17456
  // Store HTML as string for fallback/compatibility when renderer is unavailable
17456
17457
  cloned.htmlString = element ? element.outerHTML : '';
17457
17458
  // Capture dimension snapshot at serialization time
@@ -17459,6 +17460,19 @@ class KritzelDynamicObject extends KritzelBaseObject {
17459
17460
  cloned.serializedHeight = this.height;
17460
17461
  return cloned;
17461
17462
  }
17463
+ serializeRendererData() {
17464
+ if (this.rendererKey) {
17465
+ const renderer = KritzelDynamicObjectRendererRegistry.get(this.rendererKey);
17466
+ if (renderer?.onSerialize) {
17467
+ return structuredClone(renderer.onSerialize({
17468
+ object: this,
17469
+ container: this.isMounted && this.elementRef instanceof HTMLElement ? this.elementRef : null,
17470
+ data: this.rendererData,
17471
+ }));
17472
+ }
17473
+ }
17474
+ return structuredClone(this.rendererData);
17475
+ }
17462
17476
  /**
17463
17477
  * Deserializes the dynamic object from a plain object.
17464
17478
  * Reconstructs the element from stored HTML string with fallback to placeholder.
@@ -17491,6 +17505,452 @@ class KritzelDynamicObject extends KritzelBaseObject {
17491
17505
  }
17492
17506
  }
17493
17507
 
17508
+ const IFRAME_STATE_MESSAGE = 'kritzel-iframe-state';
17509
+ const IFRAME_PARENT_STATE_MESSAGE = 'kritzel-iframe-parent-state';
17510
+ const IFRAME_POINTER_MESSAGE = 'kritzel-iframe-pointer';
17511
+ const IFRAME_CONTEXT_MENU_MESSAGE = 'kritzel-iframe-contextmenu';
17512
+ const SUPPORTED_SANDBOX_TOKENS = new Set([
17513
+ 'allow-downloads',
17514
+ 'allow-forms',
17515
+ 'allow-modals',
17516
+ 'allow-orientation-lock',
17517
+ 'allow-pointer-lock',
17518
+ 'allow-popups',
17519
+ 'allow-popups-to-escape-sandbox',
17520
+ 'allow-presentation',
17521
+ 'allow-same-origin',
17522
+ 'allow-scripts',
17523
+ 'allow-top-navigation-by-user-activation',
17524
+ ]);
17525
+ const iframePointerBridgeStarts = new WeakMap();
17526
+ class KritzelIframeObject extends KritzelBaseObject {
17527
+ __class__ = 'KritzelIframeObject';
17528
+ content = { html: '', css: '', script: '' };
17529
+ loadingContent;
17530
+ isLoading = false;
17531
+ state = {};
17532
+ title = 'Embedded content';
17533
+ sandbox = ['allow-scripts'];
17534
+ borderRadius;
17535
+ boxShadow;
17536
+ get html() { return this.content.html; }
17537
+ set html(value) { this.content = { ...this.content, html: value }; }
17538
+ get css() { return this.content.css; }
17539
+ set css(value) { this.content = { ...this.content, css: value }; }
17540
+ get script() { return this.content.script; }
17541
+ set script(value) { this.content = { ...this.content, script: value }; }
17542
+ messageListener = null;
17543
+ constructor(config) {
17544
+ super();
17545
+ this.content = {
17546
+ html: config?.content?.html ?? config?.html ?? '',
17547
+ css: config?.content?.css ?? config?.css ?? '',
17548
+ script: config?.content?.script ?? config?.script ?? '',
17549
+ };
17550
+ this.loadingContent = config?.loadingContent ? {
17551
+ html: config.loadingContent.html ?? '',
17552
+ css: config.loadingContent.css ?? '',
17553
+ script: config.loadingContent.script ?? '',
17554
+ } : undefined;
17555
+ this.isLoading = config?.isLoading ?? false;
17556
+ this.state = structuredClone(config?.state ?? {});
17557
+ this.title = config?.title ?? 'Embedded content';
17558
+ this.sandbox = this.normalizeSandbox(config?.sandbox);
17559
+ this.borderRadius = config?.borderRadius;
17560
+ this.boxShadow = config?.boxShadow;
17561
+ this.translateX = config?.translateX ?? 0;
17562
+ this.translateY = config?.translateY ?? 0;
17563
+ this.rotation = KritzelMathHelper.degreesToRadians(config?.rotation ?? 0);
17564
+ this.scale = config?.scale ?? 1;
17565
+ this.width = config?.width ?? 0;
17566
+ this.height = config?.height ?? 0;
17567
+ }
17568
+ static create(core, config) {
17569
+ const object = new KritzelIframeObject(config);
17570
+ object._core = core;
17571
+ object.id = object.generateId();
17572
+ object.workspaceId = core.store.state.activeWorkspace.id;
17573
+ object.userId = core.user?.id;
17574
+ object.scale = config?.scale ?? core.store.state.scale;
17575
+ object.zIndex = core.store.currentZIndex;
17576
+ return object;
17577
+ }
17578
+ get sandboxAttribute() {
17579
+ return this.sandbox.join(' ');
17580
+ }
17581
+ mount(iframe) {
17582
+ if (this.isMounted && this.elementRef === iframe) {
17583
+ return;
17584
+ }
17585
+ if (this.isMounted) {
17586
+ this.unmount();
17587
+ }
17588
+ this.elementRef = iframe;
17589
+ this.isMounted = true;
17590
+ iframe.title = this.title;
17591
+ iframe.setAttribute('sandbox', this.sandboxAttribute);
17592
+ iframe.style.pointerEvents = this.isInteractive ? 'auto' : 'none';
17593
+ this.messageListener = event => this.handleMessage(event);
17594
+ window.addEventListener('message', this.messageListener);
17595
+ iframe.srcdoc = this.createSourceDocument();
17596
+ this.onMount(this.createLifecycleContext());
17597
+ }
17598
+ unmount() {
17599
+ if (!this.isMounted) {
17600
+ return;
17601
+ }
17602
+ const nextState = this.onUnmount(this.createLifecycleContext());
17603
+ if (nextState !== undefined) {
17604
+ this.state = structuredClone(nextState);
17605
+ }
17606
+ if (this.messageListener) {
17607
+ window.removeEventListener('message', this.messageListener);
17608
+ this.messageListener = null;
17609
+ }
17610
+ this.isMounted = false;
17611
+ this._elementRef = null;
17612
+ }
17613
+ setContent(content) {
17614
+ this.content = {
17615
+ html: content.html ?? this.content.html,
17616
+ css: content.css ?? this.content.css,
17617
+ script: content.script ?? this.content.script,
17618
+ };
17619
+ this.refreshDocument();
17620
+ this.persistUpdate();
17621
+ }
17622
+ setLoadingContent(content) {
17623
+ this.loadingContent = {
17624
+ html: content.html ?? this.loadingContent?.html ?? '',
17625
+ css: content.css ?? this.loadingContent?.css ?? '',
17626
+ script: content.script ?? this.loadingContent?.script ?? '',
17627
+ };
17628
+ this.refreshDocument();
17629
+ this.persistUpdate();
17630
+ }
17631
+ setLoading(isLoading) {
17632
+ this.isLoading = isLoading;
17633
+ this.refreshDocument();
17634
+ this.persistUpdate();
17635
+ }
17636
+ getActiveContent() {
17637
+ const activeLoadingContent = this.isLoading ? this.loadingContent : undefined;
17638
+ return {
17639
+ html: activeLoadingContent?.html ?? this.content.html,
17640
+ css: activeLoadingContent?.css ?? this.content.css,
17641
+ script: activeLoadingContent?.script ?? this.content.script,
17642
+ };
17643
+ }
17644
+ setState(state) {
17645
+ this.state = structuredClone(state);
17646
+ this.elementRef?.contentWindow?.postMessage({ type: IFRAME_PARENT_STATE_MESSAGE, state: this.state }, '*');
17647
+ this.persistUpdate();
17648
+ }
17649
+ setIsInteractive(isInteractive) {
17650
+ this.isInteractive = isInteractive;
17651
+ if (this.isMounted && this.elementRef) {
17652
+ this.elementRef.style.pointerEvents = isInteractive ? 'auto' : 'none';
17653
+ if (!isInteractive) {
17654
+ this.elementRef.blur();
17655
+ }
17656
+ }
17657
+ }
17658
+ hasFocusedInputElement() {
17659
+ return this.isInteractive && this.isMounted && document.activeElement === this.elementRef;
17660
+ }
17661
+ getEmbeddedContentRoots() {
17662
+ return this.isMounted && this.elementRef instanceof HTMLElement && this.elementRef.tagName === 'IFRAME' ? [this.elementRef] : [];
17663
+ }
17664
+ onMount(_context) { }
17665
+ onSerialize(context) {
17666
+ return context.state;
17667
+ }
17668
+ onUnmount(context) {
17669
+ return context.state;
17670
+ }
17671
+ serialize() {
17672
+ const nextState = this.onSerialize(this.createLifecycleContext());
17673
+ this.state = structuredClone(nextState);
17674
+ const messageListener = this.messageListener;
17675
+ this.messageListener = null;
17676
+ try {
17677
+ const serialized = super.serialize();
17678
+ delete serialized.messageListener;
17679
+ return serialized;
17680
+ }
17681
+ finally {
17682
+ this.messageListener = messageListener;
17683
+ }
17684
+ }
17685
+ deserialize(object) {
17686
+ super.deserialize(object);
17687
+ const nextContent = object.content && typeof object.content === 'object' ? object.content : {};
17688
+ const nextLoadingContent = object.loadingContent && typeof object.loadingContent === 'object' ? object.loadingContent : undefined;
17689
+ this.content = {
17690
+ html: typeof nextContent.html === 'string' ? nextContent.html : typeof object.html === 'string' ? object.html : '',
17691
+ css: typeof nextContent.css === 'string' ? nextContent.css : typeof object.css === 'string' ? object.css : '',
17692
+ script: typeof nextContent.script === 'string' ? nextContent.script : typeof object.script === 'string' ? object.script : '',
17693
+ };
17694
+ this.loadingContent = nextLoadingContent ? {
17695
+ html: typeof nextLoadingContent.html === 'string' ? nextLoadingContent.html : '',
17696
+ css: typeof nextLoadingContent.css === 'string' ? nextLoadingContent.css : '',
17697
+ script: typeof nextLoadingContent.script === 'string' ? nextLoadingContent.script : '',
17698
+ } : undefined;
17699
+ this.isLoading = typeof object.isLoading === 'boolean' ? object.isLoading : false;
17700
+ this.state = object.state && typeof object.state === 'object' ? structuredClone(object.state) : {};
17701
+ this.title = typeof object.title === 'string' ? object.title : 'Embedded content';
17702
+ this.sandbox = this.normalizeSandbox(Array.isArray(object.sandbox) ? object.sandbox.filter((token) => typeof token === 'string') : undefined);
17703
+ this.borderRadius = (typeof object.borderRadius === 'number' || typeof object.borderRadius === 'string') ? object.borderRadius : undefined;
17704
+ this.boxShadow = typeof object.boxShadow === 'string' ? object.boxShadow : undefined;
17705
+ this.messageListener = null;
17706
+ return this;
17707
+ }
17708
+ copy() {
17709
+ const copiedObject = super.copy();
17710
+ copiedObject.content = { ...this.content };
17711
+ copiedObject.loadingContent = this.loadingContent ? { ...this.loadingContent } : undefined;
17712
+ copiedObject.isLoading = this.isLoading;
17713
+ copiedObject.state = structuredClone(this.state);
17714
+ copiedObject.sandbox = [...this.sandbox];
17715
+ copiedObject.borderRadius = this.borderRadius;
17716
+ copiedObject.boxShadow = this.boxShadow;
17717
+ copiedObject.messageListener = null;
17718
+ copiedObject._elementRef = null;
17719
+ return copiedObject;
17720
+ }
17721
+ clone() {
17722
+ const clonedObject = super.clone();
17723
+ clonedObject.content = { ...this.content };
17724
+ clonedObject.loadingContent = this.loadingContent ? { ...this.loadingContent } : undefined;
17725
+ clonedObject.isLoading = this.isLoading;
17726
+ clonedObject.state = structuredClone(this.state);
17727
+ clonedObject.sandbox = [...this.sandbox];
17728
+ clonedObject.borderRadius = this.borderRadius;
17729
+ clonedObject.boxShadow = this.boxShadow;
17730
+ clonedObject.messageListener = null;
17731
+ clonedObject.isMounted = false;
17732
+ clonedObject._elementRef = null;
17733
+ return clonedObject;
17734
+ }
17735
+ adoptTransientStateFrom(previous) {
17736
+ if (!(previous instanceof KritzelIframeObject)) {
17737
+ return;
17738
+ }
17739
+ this.isInteractive = previous.isInteractive;
17740
+ this.isSelected = previous.isSelected;
17741
+ this.isHovered = previous.isHovered;
17742
+ if (!previous.isMounted || !(previous.elementRef instanceof HTMLElement) || previous.elementRef.tagName !== 'IFRAME') {
17743
+ return;
17744
+ }
17745
+ if (previous.messageListener) {
17746
+ window.removeEventListener('message', previous.messageListener);
17747
+ }
17748
+ this._elementRef = previous.elementRef;
17749
+ this.isMounted = true;
17750
+ this.messageListener = event => this.handleMessage(event);
17751
+ window.addEventListener('message', this.messageListener);
17752
+ this.elementRef.style.pointerEvents = this.isInteractive ? 'auto' : 'none';
17753
+ previous.messageListener = null;
17754
+ previous.isMounted = false;
17755
+ previous._elementRef = null;
17756
+ }
17757
+ createLifecycleContext() {
17758
+ return {
17759
+ object: this,
17760
+ iframe: this.isMounted ? this.elementRef : null,
17761
+ state: structuredClone(this.state),
17762
+ };
17763
+ }
17764
+ normalizeSandbox(tokens) {
17765
+ const requestedTokens = tokens ?? ['allow-scripts'];
17766
+ const normalized = requestedTokens.filter(token => SUPPORTED_SANDBOX_TOKENS.has(token));
17767
+ return [...new Set(normalized.length > 0 ? normalized : ['allow-scripts'])];
17768
+ }
17769
+ createSourceDocument() {
17770
+ const activeContent = this.getActiveContent();
17771
+ const state = JSON.stringify(this.state).replace(/</g, '\\u003c');
17772
+ const css = activeContent.css.replace(/<\/style/gi, '<\\/style');
17773
+ const script = activeContent.script.replace(/<\/script/gi, '<\\/script');
17774
+ const computedStyle = this.isMounted ? getComputedStyle(this.elementRef) : null;
17775
+ const defaultCursor = computedStyle?.getPropertyValue('--kritzel-global-default-cursor').trim() || 'auto';
17776
+ const pointerCursor = computedStyle?.getPropertyValue('--kritzel-global-pointer-cursor').trim() || defaultCursor;
17777
+ return `<!doctype html>
17778
+ <html>
17779
+ <head>
17780
+ <meta charset="utf-8">
17781
+ <style>
17782
+ :root {
17783
+ --kritzel-iframe-default-cursor: ${defaultCursor};
17784
+ --kritzel-iframe-pointer-cursor: ${pointerCursor};
17785
+ }
17786
+ html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; cursor: var(--kritzel-iframe-default-cursor); }
17787
+ button, button *, a, a *, input, textarea, select { cursor: var(--kritzel-iframe-pointer-cursor) !important; }
17788
+ ${css}
17789
+ </style>
17790
+ </head>
17791
+ <body>
17792
+ ${activeContent.html}
17793
+ <script>
17794
+ const kritzel = globalThis.kritzel = {
17795
+ state: ${state},
17796
+ setState(nextState) {
17797
+ this.state = nextState;
17798
+ parent.postMessage({ type: '${IFRAME_STATE_MESSAGE}', state: nextState }, '*');
17799
+ },
17800
+ };
17801
+ globalThis.addEventListener('message', event => {
17802
+ if (event.data?.type === '${IFRAME_PARENT_STATE_MESSAGE}' && event.data.state && typeof event.data.state === 'object') {
17803
+ kritzel.state = event.data.state;
17804
+ globalThis.dispatchEvent(new CustomEvent('kritzelstatechange', { detail: kritzel.state }));
17805
+ }
17806
+ });
17807
+ let bridgedPointerId = null;
17808
+ const interactiveSelector = 'button, input, textarea, select, a[href], [contenteditable="true"], [contenteditable=""]';
17809
+ const bridgePointerEvent = event => parent.postMessage({
17810
+ type: '${IFRAME_POINTER_MESSAGE}',
17811
+ eventType: event.type,
17812
+ pointerId: event.pointerId,
17813
+ pointerType: event.pointerType,
17814
+ clientX: event.clientX,
17815
+ clientY: event.clientY,
17816
+ screenX: event.screenX,
17817
+ screenY: event.screenY,
17818
+ button: event.button,
17819
+ buttons: event.buttons,
17820
+ shiftKey: event.shiftKey,
17821
+ ctrlKey: event.ctrlKey,
17822
+ altKey: event.altKey,
17823
+ metaKey: event.metaKey,
17824
+ }, '*');
17825
+ document.addEventListener('pointerdown', event => {
17826
+ if (event.target instanceof Element && event.target.closest(interactiveSelector)) return;
17827
+ bridgedPointerId = event.pointerId;
17828
+ event.preventDefault();
17829
+ bridgePointerEvent(event);
17830
+ });
17831
+ document.addEventListener('pointermove', event => {
17832
+ if (event.pointerId !== bridgedPointerId) return;
17833
+ event.preventDefault();
17834
+ bridgePointerEvent(event);
17835
+ });
17836
+ const finishPointerBridge = event => {
17837
+ if (event.pointerId !== bridgedPointerId) return;
17838
+ event.preventDefault();
17839
+ bridgePointerEvent(event);
17840
+ bridgedPointerId = null;
17841
+ };
17842
+ document.addEventListener('pointerup', finishPointerBridge);
17843
+ document.addEventListener('pointercancel', finishPointerBridge);
17844
+ document.addEventListener('contextmenu', event => {
17845
+ event.preventDefault();
17846
+ parent.postMessage({
17847
+ type: '${IFRAME_CONTEXT_MENU_MESSAGE}',
17848
+ clientX: event.clientX,
17849
+ clientY: event.clientY,
17850
+ button: event.button,
17851
+ shiftKey: event.shiftKey,
17852
+ ctrlKey: event.ctrlKey,
17853
+ altKey: event.altKey,
17854
+ metaKey: event.metaKey,
17855
+ }, '*');
17856
+ });
17857
+ ${script}
17858
+ <\/script>
17859
+ </body>
17860
+ </html>`;
17861
+ }
17862
+ handleMessage(event) {
17863
+ if (event.source !== this.elementRef?.contentWindow || !event.data || typeof event.data !== 'object') {
17864
+ return;
17865
+ }
17866
+ const message = event.data;
17867
+ if (message.type === IFRAME_POINTER_MESSAGE) {
17868
+ this.dispatchPointerMessage(event.data);
17869
+ return;
17870
+ }
17871
+ if (message.type === IFRAME_CONTEXT_MENU_MESSAGE) {
17872
+ this.dispatchContextMenuMessage(event.data);
17873
+ return;
17874
+ }
17875
+ if (message.type !== IFRAME_STATE_MESSAGE || !message.state || typeof message.state !== 'object') {
17876
+ return;
17877
+ }
17878
+ this.state = structuredClone(message.state);
17879
+ this.persistUpdate();
17880
+ }
17881
+ dispatchPointerMessage(message) {
17882
+ if (!this.isMounted || !this.elementRef || !['pointerdown', 'pointermove', 'pointerup', 'pointercancel'].includes(message.eventType)) {
17883
+ return;
17884
+ }
17885
+ const iframeRect = this.elementRef.getBoundingClientRect();
17886
+ if (message.eventType === 'pointerdown') {
17887
+ const scaleX = this.elementRef.offsetWidth > 0 ? iframeRect.width / this.elementRef.offsetWidth : 1;
17888
+ const scaleY = this.elementRef.offsetHeight > 0 ? iframeRect.height / this.elementRef.offsetHeight : 1;
17889
+ iframePointerBridgeStarts.set(this.elementRef, {
17890
+ pointerId: message.pointerId,
17891
+ clientX: iframeRect.left + message.clientX * scaleX,
17892
+ clientY: iframeRect.top + message.clientY * scaleY,
17893
+ screenX: message.screenX,
17894
+ screenY: message.screenY,
17895
+ });
17896
+ }
17897
+ const bridgeStart = iframePointerBridgeStarts.get(this.elementRef);
17898
+ const hasBridgeStart = bridgeStart?.pointerId === message.pointerId;
17899
+ const clientX = hasBridgeStart ? bridgeStart.clientX + message.screenX - bridgeStart.screenX : iframeRect.left + message.clientX;
17900
+ const clientY = hasBridgeStart ? bridgeStart.clientY + message.screenY - bridgeStart.screenY : iframeRect.top + message.clientY;
17901
+ this.elementRef.dispatchEvent(new PointerEvent(message.eventType, {
17902
+ bubbles: true,
17903
+ composed: true,
17904
+ cancelable: true,
17905
+ pointerId: message.pointerId,
17906
+ pointerType: message.pointerType,
17907
+ clientX,
17908
+ clientY,
17909
+ screenX: message.screenX,
17910
+ screenY: message.screenY,
17911
+ button: message.button,
17912
+ buttons: message.buttons,
17913
+ shiftKey: message.shiftKey,
17914
+ ctrlKey: message.ctrlKey,
17915
+ altKey: message.altKey,
17916
+ metaKey: message.metaKey,
17917
+ }));
17918
+ if (message.eventType === 'pointerup' || message.eventType === 'pointercancel') {
17919
+ iframePointerBridgeStarts.delete(this.elementRef);
17920
+ }
17921
+ }
17922
+ dispatchContextMenuMessage(message) {
17923
+ if (!this.isMounted || !this.elementRef) {
17924
+ return;
17925
+ }
17926
+ const iframeRect = this.elementRef.getBoundingClientRect();
17927
+ // Dispatched as a MouseEvent (not PointerEvent) to mirror how browsers
17928
+ // natively raise 'contextmenu' for both mouse right-click and touch long-press.
17929
+ this.elementRef.dispatchEvent(new MouseEvent('contextmenu', {
17930
+ bubbles: true,
17931
+ composed: true,
17932
+ cancelable: true,
17933
+ clientX: iframeRect.left + message.clientX,
17934
+ clientY: iframeRect.top + message.clientY,
17935
+ button: message.button,
17936
+ shiftKey: message.shiftKey,
17937
+ ctrlKey: message.ctrlKey,
17938
+ altKey: message.altKey,
17939
+ metaKey: message.metaKey,
17940
+ }));
17941
+ }
17942
+ refreshDocument() {
17943
+ if (this.isMounted && this.elementRef) {
17944
+ this.elementRef.srcdoc = this.createSourceDocument();
17945
+ }
17946
+ }
17947
+ persistUpdate() {
17948
+ if (this._core?.store?.objects) {
17949
+ this.update();
17950
+ }
17951
+ }
17952
+ }
17953
+
17494
17954
  /**
17495
17955
  * Represents a selection group that manages multiple selected objects on the canvas.
17496
17956
  * The selection group allows batch operations like move, resize, and rotate on multiple objects simultaneously.
@@ -19631,7 +20091,7 @@ class KritzelCursorHelper {
19631
20091
  * The icon is retrieved from the KritzelIconRegistry.
19632
20092
  */
19633
20093
  static getCursor(options) {
19634
- const iconName = options.iconName === 'default' ? 'mouse-pointer' : options.iconName;
20094
+ const iconName = options.iconName === 'default' ? 'mousePointer' : options.iconName;
19635
20095
  const iconSvg = KritzelIconRegistry.get(iconName);
19636
20096
  if (!iconSvg) {
19637
20097
  console.warn(`Icon "${iconName}" not found in registry.`);
@@ -19640,7 +20100,7 @@ class KritzelCursorHelper {
19640
20100
  const size = options.size || 24;
19641
20101
  const rotation = options.rotation || 0;
19642
20102
  const color = options.color || 'black';
19643
- // Default cursor (mouse-pointer) has hotspot at (4, 4), others at center
20103
+ // Default cursor (mousePointer) has hotspot at (4, 4), others at center
19644
20104
  const hotspot = options.hotspot || (options.iconName === 'default' ? { x: 4, y: 4 } : { x: size / 2, y: size / 2 });
19645
20105
  // Modify the SVG string to set size and color
19646
20106
  // We replace width and height to match the requested size
@@ -19653,12 +20113,12 @@ class KritzelCursorHelper {
19653
20113
  // We rotate around the center of the SVG canvas
19654
20114
  const center = size / 2;
19655
20115
  // 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>
20116
+ const svg = `
20117
+ <svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
20118
+ <g transform="rotate(${rotation} ${center} ${center})">
20119
+ ${content}
20120
+ </g>
20121
+ </svg>
19662
20122
  `;
19663
20123
  // Encode the SVG for use in a data URI
19664
20124
  // We need to be careful with encoding to ensure it works across browsers
@@ -21874,9 +22334,8 @@ class KritzelSelectionTool extends KritzelBaseTool {
21874
22334
  }
21875
22335
  if (selectedObject && selectedObject.isSelected === false && selectedObject?.objects.length === 1) {
21876
22336
  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.
22337
+ if (ObjectHelper.isKritzelEmbeddedObject(firstObj)) {
22338
+ // Selected embedded objects hand pointer interaction to their content.
21880
22339
  if (firstObj.isSelected && this._core.store.selectionGroup?.objects.length === 1)
21881
22340
  return;
21882
22341
  }
@@ -43322,6 +43781,9 @@ class KritzelReviver {
43322
43781
  case 'KritzelDynamicObject':
43323
43782
  revivedObj = KritzelDynamicObject.create(this._core).deserialize(obj);
43324
43783
  break;
43784
+ case 'KritzelIframeObject':
43785
+ revivedObj = KritzelIframeObject.create(this._core).deserialize(obj);
43786
+ break;
43325
43787
  case 'KritzelSelectionGroup':
43326
43788
  revivedObj = KritzelSelectionGroup.create(this._core).deserialize(obj);
43327
43789
  break;
@@ -45146,9 +45608,16 @@ class KritzelStore {
45146
45608
  * Gets the currently selected dynamic object whose focused descendant is an input-like element.
45147
45609
  * @returns The interactive dynamic object with focused input-like content, or null if none matches
45148
45610
  */
45611
+ get activeInteractiveEmbeddedObject() {
45612
+ for (const object of this.selectedObjects) {
45613
+ if (ObjectHelper.isKritzelEmbeddedObject(object) && object.hasFocusedInputElement()) {
45614
+ return object;
45615
+ }
45616
+ }
45617
+ return null;
45618
+ }
45149
45619
  get activeInteractiveDynamicObject() {
45150
- const interactiveDynamicObject = this.selectedObjects.find(object => KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject') && object.hasFocusedInputElement());
45151
- return interactiveDynamicObject ?? null;
45620
+ return this.activeInteractiveEmbeddedObject;
45152
45621
  }
45153
45622
  /**
45154
45623
  * Gets the path currently being drawn (not yet completed).
@@ -45736,11 +46205,13 @@ class KritzelCursorManager {
45736
46205
  // Reset cursor on old target
45737
46206
  if (this._targetElement) {
45738
46207
  this._targetElement.style.cursor = '';
46208
+ this._targetElement.style.removeProperty('--kritzel-global-default-cursor');
45739
46209
  this._targetElement.style.removeProperty('--kritzel-global-pointer-cursor');
45740
46210
  }
45741
46211
  this._targetElement = element;
45742
46212
  // Set the pointer cursor CSS variable for child components to use
45743
46213
  if (this._targetElement) {
46214
+ this._targetElement.style.setProperty('--kritzel-global-default-cursor', KritzelCursorHelper.getCursor({ iconName: 'default' }));
45744
46215
  this._targetElement.style.setProperty('--kritzel-global-pointer-cursor', KritzelCursorHelper.getPointerCursor());
45745
46216
  }
45746
46217
  }
@@ -45785,8 +46256,10 @@ class KritzelCursorManager {
45785
46256
  if (!this._shadowRoot)
45786
46257
  return;
45787
46258
  const elementsAtPoint = this._shadowRoot.elementsFromPoint(ev.clientX, ev.clientY);
45788
- if (!elementsAtPoint || elementsAtPoint.length === 0)
46259
+ if (!elementsAtPoint || elementsAtPoint.length === 0) {
46260
+ this.resetToDefault();
45789
46261
  return;
46262
+ }
45790
46263
  // Check for resize handle overlays (selection group)
45791
46264
  const resizeHandleOverlay = elementsAtPoint.find(el => el.classList.contains('resize-handle-overlay'));
45792
46265
  if (resizeHandleOverlay) {
@@ -45796,19 +46269,19 @@ class KritzelCursorManager {
45796
46269
  const isTopLeft = resizeHandleOverlay.classList.contains('top-left');
45797
46270
  const isBottomRight = resizeHandleOverlay.classList.contains('bottom-right');
45798
46271
  const rotationOffset = (isTopLeft || isBottomRight) ? -45 : 45;
45799
- this._core.store.state.cursor = { icon: 'move-vertical', rotation: rotationDegrees + rotationOffset };
46272
+ this._core.store.state.cursor = { icon: 'moveVertical', rotation: rotationDegrees + rotationOffset };
45800
46273
  return;
45801
46274
  }
45802
46275
  // Check for rotation handle overlay
45803
46276
  const rotationHandleOverlay = elementsAtPoint.find(el => el.classList.contains('rotation-handle-overlay'));
45804
46277
  if (rotationHandleOverlay) {
45805
- this._core.store.state.cursor = { icon: 'hand', iconActive: 'hand-grab' };
46278
+ this._core.store.state.cursor = { icon: 'hand', iconActive: 'handGrab' };
45806
46279
  return;
45807
46280
  }
45808
46281
  // Check for line handle overlays (selection line)
45809
46282
  const lineHandleOverlay = elementsAtPoint.find(el => el.classList.contains('selection-line-handle-overlay'));
45810
46283
  if (lineHandleOverlay) {
45811
- this._core.store.state.cursor = { icon: 'hand', iconActive: 'hand-grab' };
46284
+ this._core.store.state.cursor = { icon: 'hand', iconActive: 'handGrab' };
45812
46285
  return;
45813
46286
  }
45814
46287
  // Reset cursor if not hovering any handle
@@ -45843,7 +46316,8 @@ class KritzelCursorManager {
45843
46316
  cleanup() {
45844
46317
  if (this._targetElement) {
45845
46318
  this._targetElement.style.cursor = '';
45846
- this._targetElement.style.removeProperty('--kritzel-pointer-cursor');
46319
+ this._targetElement.style.removeProperty('--kritzel-global-default-cursor');
46320
+ this._targetElement.style.removeProperty('--kritzel-global-pointer-cursor');
45847
46321
  }
45848
46322
  this._targetElement = null;
45849
46323
  this._shadowRoot = null;
@@ -46768,7 +47242,7 @@ class KritzelCore {
46768
47242
  this._store.state.translateY = viewportHeight / 2 - (viewport.centerWorldY ?? 0) * scale;
46769
47243
  this._store.state.scale = scale;
46770
47244
  await objectsMap.initialize(this, activeWorkspace.id, this._syncConfig);
46771
- this.seedWorkspaceObjects(activeWorkspace, objectsMap);
47245
+ await this.seedWorkspaceObjects(activeWorkspace, objectsMap);
46772
47246
  // Set local user in awareness after object map is initialized
46773
47247
  if (this._user) {
46774
47248
  objectsMap.setLocalUser(this._user);
@@ -46782,15 +47256,21 @@ class KritzelCore {
46782
47256
  this.engine.emitObjectsChange();
46783
47257
  this.rerender();
46784
47258
  }
46785
- seedWorkspaceObjects(workspace, objectsMap) {
47259
+ async seedWorkspaceObjects(workspace, objectsMap) {
46786
47260
  if (!workspace.objects?.length) {
46787
47261
  return;
46788
47262
  }
47263
+ const pendingObjects = workspace.objects.filter(object => !objectsMap.findById(object.id));
47264
+ // Mirrors engine.addObject()/addObjects(): image asset upload is async and must
47265
+ // happen before the (synchronous) Yjs transaction that inserts the objects.
47266
+ for (const object of pendingObjects) {
47267
+ object._core = this;
47268
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelImage')) {
47269
+ await object.prepareForInsert();
47270
+ }
47271
+ }
46789
47272
  objectsMap.transaction(() => {
46790
- for (const object of workspace.objects) {
46791
- if (objectsMap.findById(object.id)) {
46792
- continue;
46793
- }
47273
+ for (const object of pendingObjects) {
46794
47274
  this.prepareObjectForWorkspace(object, workspace.id, this._store.currentZIndex, this._store.state.scale);
46795
47275
  objectsMap.insert(object);
46796
47276
  }
@@ -46829,16 +47309,30 @@ class KritzelCore {
46829
47309
  * @param scaleFallback - Scale applied to objects that do not define one
46830
47310
  * @returns The objects that were added, updated and removed
46831
47311
  */
46832
- reconcileWorkspaceObjects(objectsMap, workspaceId, desiredObjects, nextZIndex, scaleFallback) {
47312
+ async reconcileWorkspaceObjects(objectsMap, workspaceId, desiredObjects, nextZIndex, scaleFallback) {
46833
47313
  const existingObjects = objectsMap.allObjects().filter(o => !(o instanceof KritzelSelectionGroup) && !(o instanceof KritzelSelectionBox));
46834
47314
  const existingById = new Map(existingObjects.map(object => [object.id, object]));
46835
47315
  const desiredIds = new Set();
46836
47316
  const diff = { added: [], updated: [], removed: [] };
47317
+ // Assign IDs up front so the insert-vs-update lookup below is stable, and so the
47318
+ // async image-prepare pass and the (synchronous) transaction agree on identity.
47319
+ for (const object of desiredObjects) {
47320
+ if (!object.id) {
47321
+ object.id = object.generateId();
47322
+ }
47323
+ }
47324
+ // Mirrors engine.addObject()/addObjects(): image asset upload is async and must
47325
+ // happen before the (synchronous) Yjs transaction that inserts the objects.
47326
+ for (const object of desiredObjects) {
47327
+ const existingObject = existingById.get(object.id);
47328
+ const isNewOrReplaced = !existingObject || existingObject !== object;
47329
+ if (isNewOrReplaced && KritzelClassHelper.isInstanceOf(object, 'KritzelImage')) {
47330
+ object._core = this;
47331
+ await object.prepareForInsert();
47332
+ }
47333
+ }
46837
47334
  objectsMap.transaction(() => {
46838
47335
  for (const object of desiredObjects) {
46839
- if (!object.id) {
46840
- object.id = object.generateId();
46841
- }
46842
47336
  desiredIds.add(object.id);
46843
47337
  const existingObject = existingById.get(object.id);
46844
47338
  if (!existingObject) {
@@ -46876,7 +47370,7 @@ class KritzelCore {
46876
47370
  * object set stays exactly what the caller declared.
46877
47371
  */
46878
47372
  detachObject(object) {
46879
- if (KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
47373
+ if (ObjectHelper.isKritzelEmbeddedObject(object)) {
46880
47374
  object.unmount();
46881
47375
  }
46882
47376
  object.isMounted = false;
@@ -47052,13 +47546,13 @@ class KritzelCore {
47052
47546
  activeWorkspace.viewport = { centerWorldX, centerWorldY, scale };
47053
47547
  }
47054
47548
  }
47055
- applyObjectsToActiveWorkspace(workspace, desiredObjects) {
47549
+ async applyObjectsToActiveWorkspace(workspace, desiredObjects) {
47056
47550
  const objectsMap = this._store.objects;
47057
47551
  if (!objectsMap) {
47058
47552
  return null;
47059
47553
  }
47060
47554
  this.clearSelection();
47061
- const diff = this.reconcileWorkspaceObjects(objectsMap, workspace.id, desiredObjects, () => this._store.currentZIndex, this._store.state.scale);
47555
+ const diff = await this.reconcileWorkspaceObjects(objectsMap, workspace.id, desiredObjects, () => this._store.currentZIndex, this._store.state.scale);
47062
47556
  this._anchorManager.rebuildIndex();
47063
47557
  this.rerender();
47064
47558
  return diff;
@@ -47077,7 +47571,7 @@ class KritzelCore {
47077
47571
  try {
47078
47572
  await objectsMap.initialize(this, workspace.id, this._syncConfig, { headless: true, workspaceName: workspace.name });
47079
47573
  await objectsMap.whenNetworkSynced();
47080
- return this.reconcileWorkspaceObjects(objectsMap, workspace.id, desiredObjects, () => this.nextZIndexOf(objectsMap), this._store.state.scale);
47574
+ return await this.reconcileWorkspaceObjects(objectsMap, workspace.id, desiredObjects, () => this.nextZIndexOf(objectsMap), this._store.state.scale);
47081
47575
  }
47082
47576
  finally {
47083
47577
  objectsMap.destroy();
@@ -47163,8 +47657,7 @@ class KritzelCore {
47163
47657
  this.removeObject(child);
47164
47658
  });
47165
47659
  }
47166
- // Unmount dynamic objects before removal
47167
- if (KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
47660
+ if (ObjectHelper.isKritzelEmbeddedObject(object)) {
47168
47661
  object.unmount();
47169
47662
  }
47170
47663
  // Handle anchor cleanup
@@ -47210,14 +47703,8 @@ class KritzelCore {
47210
47703
  this.removeLocalSelectionGroups();
47211
47704
  this.removeSelectionBox();
47212
47705
  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
- }
47706
+ if (ObjectHelper.isKritzelEmbeddedObject(object)) {
47707
+ object.setIsInteractive(true);
47221
47708
  }
47222
47709
  }
47223
47710
  this._store.objects.insert(selectionGroup);
@@ -47238,14 +47725,8 @@ class KritzelCore {
47238
47725
  teardownSelectionGroup(selectionGroup) {
47239
47726
  selectionGroup.deselectAllChildren?.();
47240
47727
  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
- }
47728
+ if (ObjectHelper.isKritzelEmbeddedObject(object)) {
47729
+ object.setIsInteractive(false);
47249
47730
  }
47250
47731
  }
47251
47732
  }
@@ -48691,13 +49172,7 @@ class KritzelWheelHelper {
48691
49172
  return this.canElementConsumeWheel(scrollContainer, ev.deltaX, ev.deltaY);
48692
49173
  }
48693
49174
  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
- }
49175
+ const potentialRoots = dynamicObject.getEmbeddedContentRoots();
48701
49176
  const legacyElementRef = dynamicObject.elmeentRef;
48702
49177
  if (legacyElementRef instanceof HTMLElement) {
48703
49178
  potentialRoots.push(legacyElementRef);
@@ -48855,8 +49330,8 @@ class KritzelSvgExportHelper {
48855
49330
  .join('\n ');
48856
49331
  const xmlDecl = includeXmlDeclaration ? '<?xml version="1.0" encoding="UTF-8"?>\n' : '';
48857
49332
  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}
49333
+ return `${xmlDecl}<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBoxX} ${viewBoxY} ${viewBoxWidth} ${viewBoxHeight}" width="${viewBoxWidth}" height="${viewBoxHeight}">${defsContent}
49334
+ ${elements}
48860
49335
  </svg>`;
48861
49336
  }
48862
49337
  /**
@@ -49008,8 +49483,8 @@ class KritzelSvgExportHelper {
49008
49483
  const orient = end === 'start' ? 'auto-start-reverse' : 'auto';
49009
49484
  const refX = 0;
49010
49485
  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})"/>
49486
+ return `<marker id="${markerId}" markerWidth="${size}" markerHeight="${size}" refX="${refX}" refY="${refY}" orient="${orient}" markerUnits="userSpaceOnUse">
49487
+ <path d="${path}" fill="${fill}" transform="scale(${size / 10})"/>
49013
49488
  </marker>`;
49014
49489
  }
49015
49490
  /**
@@ -49038,6 +49513,9 @@ class KritzelSvgExportHelper {
49038
49513
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
49039
49514
  return this.dynamicObjectToSvg(object, rasterSafeForCanvas);
49040
49515
  }
49516
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelIframeObject')) {
49517
+ return this.iframeObjectToSvg(object);
49518
+ }
49041
49519
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelGroup')) {
49042
49520
  return this.groupToSvg(object, theme, imageDataUrls, rasterSafeForCanvas);
49043
49521
  }
@@ -49056,10 +49534,10 @@ class KritzelSvgExportHelper {
49056
49534
  const opacity = path.opacity !== 1 ? ` opacity="${path.opacity}"` : '';
49057
49535
  const scale = this.getObjectScale(path);
49058
49536
  // 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>
49537
+ return `<g transform="${transform}"${opacity}>
49538
+ <svg viewBox="${path.viewBox}" width="${path.totalWidth / scale}" height="${path.totalHeight / scale}" overflow="visible">
49539
+ <path d="${path.d}" fill="${fill || 'none'}" stroke="${stroke || 'none'}"${path.strokeWidth ? ` stroke-width="${path.strokeWidth}"` : ''}/>
49540
+ </svg>
49063
49541
  </g>`;
49064
49542
  }
49065
49543
  /**
@@ -49075,10 +49553,10 @@ class KritzelSvgExportHelper {
49075
49553
  const scale = this.getObjectScale(line);
49076
49554
  const markerStart = line.hasStartArrow ? ` marker-start="url(#${line.startMarkerId})"` : '';
49077
49555
  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>
49556
+ return `<g transform="${transform}"${opacity}>
49557
+ <svg viewBox="${line.viewBox}" width="${line.totalWidth / scale}" height="${line.totalHeight / scale}" overflow="visible">
49558
+ <path d="${line.d}" fill="none" stroke="${stroke}" stroke-width="${line.strokeWidth}" stroke-linecap="round"${markerStart}${markerEnd}/>
49559
+ </svg>
49082
49560
  </g>`;
49083
49561
  }
49084
49562
  /**
@@ -49102,18 +49580,18 @@ class KritzelSvgExportHelper {
49102
49580
  const textHtml = this.prosemirrorToHtml(shape.content, shape, theme);
49103
49581
  if (textHtml) {
49104
49582
  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>
49583
+ textContent = `
49584
+ <foreignObject x="${shape.x}" y="${shape.y}" width="${shape.width}" height="${shape.height}">
49585
+ <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;">
49586
+ ${textHtml}
49587
+ </div>
49110
49588
  </foreignObject>`;
49111
49589
  }
49112
49590
  }
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>
49591
+ return `<g transform="${transform}"${opacity}>
49592
+ <svg viewBox="${shape.viewBox}" width="${shape.totalWidth / scale}" height="${shape.totalHeight / scale}" overflow="visible" preserveAspectRatio="none">
49593
+ <path d="${pathD}" fill="${fill || 'transparent'}" stroke="${stroke}" stroke-width="${shape.strokeWidth}"/>${textContent}
49594
+ </svg>
49117
49595
  </g>`;
49118
49596
  }
49119
49597
  /**
@@ -49144,10 +49622,10 @@ class KritzelSvgExportHelper {
49144
49622
  // adjusted font-size already accounts for it – keeping both would double-scale
49145
49623
  // the content and overflow the foreignObject.
49146
49624
  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>
49625
+ return `<g transform="${transform}"${opacity}>
49626
+ <foreignObject x="0" y="0" width="${width}" height="${height}" overflow="visible">
49627
+ <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>
49628
+ </foreignObject>
49151
49629
  </g>`;
49152
49630
  }
49153
49631
  /**
@@ -49181,9 +49659,9 @@ class KritzelSvgExportHelper {
49181
49659
  return `<tspan x="0" y="${y}" xml:space="preserve">${this.escapeHtml(line)}</tspan>`;
49182
49660
  })
49183
49661
  .join('');
49184
- return `<g transform="${transform}"${opacity}>
49185
- ${bgRect}
49186
- <text font-family="${this.escapeHtml(text.fontFamily)}" font-size="${resolvedFontSizePx}" fill="${fontColor}">${tspans}</text>
49662
+ return `<g transform="${transform}"${opacity}>
49663
+ ${bgRect}
49664
+ <text font-family="${this.escapeHtml(text.fontFamily)}" font-size="${resolvedFontSizePx}" fill="${fontColor}">${tspans}</text>
49187
49665
  </g>`;
49188
49666
  }
49189
49667
  /**
@@ -49211,8 +49689,8 @@ class KritzelSvgExportHelper {
49211
49689
  const width = image.totalWidth / scale;
49212
49690
  const height = image.totalHeight / scale;
49213
49691
  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"/>
49692
+ return `<g transform="${transform}"${opacity}>
49693
+ <image href="${href}" x="0" y="0" width="${width}" height="${height}" preserveAspectRatio="xMidYMid meet"/>
49216
49694
  </g>`;
49217
49695
  }
49218
49696
  /**
@@ -49232,19 +49710,30 @@ class KritzelSvgExportHelper {
49232
49710
  if (rasterSafeForCanvas) {
49233
49711
  // PNG export: render placeholder rect (foreignObject doesn't rasterize to PNG well)
49234
49712
  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>
49713
+ return `<g transform="${transform}"${opacity}>
49714
+ <rect x="0" y="0" width="${width}" height="${height}" fill="#f0f0f0" stroke="#999" stroke-width="1" />
49715
+ <text x="${width / 2}" y="${height / 2}" text-anchor="middle" dominant-baseline="middle" font-size="12" fill="#999">[Dynamic Object]</text>
49238
49716
  </g>`;
49239
49717
  }
49240
49718
  // SVG export: use foreignObject to embed HTML
49241
49719
  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>
49720
+ return `<g transform="${transform}"${opacity}>
49721
+ <foreignObject x="0" y="0" width="${width}" height="${height}">
49722
+ <div xmlns="http://www.w3.org/1999/xhtml" style="width:100%;height:100%;overflow:hidden;">
49723
+ ${html}
49724
+ </div>
49725
+ </foreignObject>
49726
+ </g>`;
49727
+ }
49728
+ static iframeObjectToSvg(object) {
49729
+ const transform = this.buildTransform(object);
49730
+ const opacity = object.opacity !== 1 ? ` opacity="${object.opacity}"` : '';
49731
+ const scale = this.getObjectScale(object);
49732
+ const width = object.totalWidth / scale;
49733
+ const height = object.totalHeight / scale;
49734
+ return `<g transform="${transform}"${opacity}>
49735
+ <rect x="0" y="0" width="${width}" height="${height}" fill="#f0f0f0" stroke="#999" stroke-width="1" />
49736
+ <text x="${width / 2}" y="${height / 2}" text-anchor="middle" dominant-baseline="middle" font-size="12" fill="#999">[Iframe]</text>
49248
49737
  </g>`;
49249
49738
  }
49250
49739
  /**
@@ -49264,8 +49753,8 @@ class KritzelSvgExportHelper {
49264
49753
  return '';
49265
49754
  }
49266
49755
  const opacity = group.opacity !== 1 ? ` opacity="${group.opacity}"` : '';
49267
- return `<g${opacity}>
49268
- ${children}
49756
+ return `<g${opacity}>
49757
+ ${children}
49269
49758
  </g>`;
49270
49759
  }
49271
49760
  /**
@@ -49450,12 +49939,26 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
49450
49939
  /** Authoritative workspace catalog for this engine instance. When provided, provider-only workspaces are hidden but not deleted. */
49451
49940
  workspaces;
49452
49941
  async onWorkspacesPropChange() {
49453
- if (!this._isYjsInitialized) {
49942
+ this._workspacesRevision++;
49943
+ if (!this._isYjsInitialized || this._syncInitPromise) {
49454
49944
  return;
49455
49945
  }
49456
- await this.reconcileProvidedWorkspaces();
49457
- await this.ensureActiveWorkspaceIsVisible();
49458
- this.workspacesChange.emit(this.getVisibleWorkspaces());
49946
+ const previousReconciliation = this._workspacePropReconciliationPromise;
49947
+ const reconciliationPromise = (async () => {
49948
+ await previousReconciliation;
49949
+ await this.reconcileProvidedWorkspaces();
49950
+ await this.ensureActiveWorkspaceIsVisible();
49951
+ this.workspacesChange.emit(this.getVisibleWorkspaces());
49952
+ })();
49953
+ this._workspacePropReconciliationPromise = reconciliationPromise;
49954
+ try {
49955
+ await reconciliationPromise;
49956
+ }
49957
+ finally {
49958
+ if (this._workspacePropReconciliationPromise === reconciliationPromise) {
49959
+ this._workspacePropReconciliationPromise = null;
49960
+ }
49961
+ }
49459
49962
  }
49460
49963
  /** Optional unique identifier for namespacing storage keys across multiple editor instances. */
49461
49964
  editorId;
@@ -49509,6 +50012,12 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
49509
50012
  onUserChange(newValue) {
49510
50013
  this.core.setUser(newValue);
49511
50014
  }
50015
+ /** Tool definitions to register with this engine instance. */
50016
+ tools = [];
50017
+ onToolsChange() {
50018
+ this._toolsRevision++;
50019
+ void this.registerConfiguredTools();
50020
+ }
49512
50021
  /** Context menu items shown when right-clicking the canvas background. */
49513
50022
  globalContextMenuItems;
49514
50023
  onGlobalContextMenuItemsChange(newValue) {
@@ -49699,7 +50208,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
49699
50208
  }
49700
50209
  const hoveredObject = this.core.getObjectFromPointerEvent(ev);
49701
50210
  if (hoveredObject &&
49702
- ObjectHelper.isClass(hoveredObject, 'KritzelDynamicObject') &&
50211
+ ObjectHelper.isKritzelEmbeddedObject(hoveredObject) &&
49703
50212
  KritzelWheelHelper.shouldKeepWheelInDynamicObject(ev, hoveredObject)) {
49704
50213
  return;
49705
50214
  }
@@ -49801,7 +50310,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
49801
50310
  return false;
49802
50311
  }
49803
50312
  const object = this.core.store.objects.findById(objectElement.id);
49804
- if (!object || !KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
50313
+ if (!object || !ObjectHelper.isKritzelEmbeddedObject(object)) {
49805
50314
  return false;
49806
50315
  }
49807
50316
  const isSelected = !!this.core.store.selectionGroup?.objects?.some(selectedObject => selectedObject.id === object.id);
@@ -49811,13 +50320,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
49811
50320
  if (object.isInteractive !== true) {
49812
50321
  return false;
49813
50322
  }
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
- }
50323
+ const potentialRoots = object.getEmbeddedContentRoots();
49821
50324
  if (potentialRoots.length === 0) {
49822
50325
  return false;
49823
50326
  }
@@ -49991,7 +50494,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
49991
50494
  * Switches the active drawing tool by its registered name.
49992
50495
  * @param toolName - The name the tool was registered with (e.g., 'brush', 'eraser').
49993
50496
  */
49994
- async changeActiveTool(toolName) {
50497
+ async setActiveTool(toolName) {
49995
50498
  const tool = this.core.toolRegistry.getTool(toolName);
49996
50499
  if (!tool) {
49997
50500
  console.warn(`[KritzelEngine] No tool registered with name: ${toolName}`);
@@ -51297,9 +51800,13 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51297
51800
  _stateChangeListenersRegistered = false;
51298
51801
  _workspaceInitializationPromise = null;
51299
51802
  _workspaceInitializationTargetKey = null;
51803
+ _workspacePropReconciliationPromise = null;
51300
51804
  _syncInitPromise = null;
51805
+ _toolsInitPromise = null;
51806
+ _toolsRevision = 0;
51301
51807
  _syncConfigRevision = 0;
51302
51808
  _assetStorageConfigRevision = 0;
51809
+ _workspacesRevision = 0;
51303
51810
  _hasLoggedUnlicensedWelcomeBanner = false;
51304
51811
  _previousVisibleDynamicObjects = new Set();
51305
51812
  _revealedImageKeys = new Set();
@@ -51312,6 +51819,37 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51312
51819
  undoStackSize: 0,
51313
51820
  redoStackSize: 0,
51314
51821
  };
51822
+ /** Returns a tool registered in this engine instance. */
51823
+ async getTool(toolName) {
51824
+ await this.registerConfiguredTools();
51825
+ return this.core.toolRegistry.getTool(toolName);
51826
+ }
51827
+ async registerConfiguredTools() {
51828
+ if (this._toolsInitPromise) {
51829
+ return this._toolsInitPromise;
51830
+ }
51831
+ this._toolsInitPromise = (async () => {
51832
+ do {
51833
+ const revision = this._toolsRevision;
51834
+ for (const definition of this.tools) {
51835
+ let toolClass = definition.tool;
51836
+ if (typeof toolClass !== 'function') {
51837
+ toolClass = toolClass.constructor;
51838
+ }
51839
+ await this.registerTool(definition.name, toolClass, definition.config);
51840
+ }
51841
+ if (revision === this._toolsRevision) {
51842
+ break;
51843
+ }
51844
+ } while (true);
51845
+ })();
51846
+ try {
51847
+ await this._toolsInitPromise;
51848
+ }
51849
+ finally {
51850
+ this._toolsInitPromise = null;
51851
+ }
51852
+ }
51315
51853
  setLoadingState(isLoading) {
51316
51854
  if (this.core.store.state.isLoading === isLoading) {
51317
51855
  return;
@@ -51443,6 +51981,8 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51443
51981
  this.core.licenseManager.destroy();
51444
51982
  }
51445
51983
  componentWillLoad() {
51984
+ this._toolsRevision++;
51985
+ void this.registerConfiguredTools();
51446
51986
  this.core.setEditorId(this.editorId);
51447
51987
  if (this.themes && this.themes.length > 0) {
51448
51988
  this.core.themeManager.registerThemes(this.themes);
@@ -51482,7 +52022,9 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51482
52022
  const editorElement = this.host.closest('kritzel-editor');
51483
52023
  this.core.themeManager.setTargetElement(editorElement || this.host);
51484
52024
  // Initialize sync config and Yjs
52025
+ await this.registerConfiguredTools();
51485
52026
  await this.initializeSyncAndWorkspace();
52027
+ await this.waitForWorkspaceAndSceneReady();
51486
52028
  if (this.core.store.state.isReady === false) {
51487
52029
  this.core.store.state.isReady = true;
51488
52030
  this.logUnlicensedWelcomeBanner();
@@ -51504,6 +52046,20 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51504
52046
  this._syncInitPromise = null;
51505
52047
  }
51506
52048
  }
52049
+ async waitForSceneRender() {
52050
+ await new Promise(resolve => {
52051
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
52052
+ });
52053
+ }
52054
+ async waitForWorkspaceAndSceneReady() {
52055
+ let observedRevision;
52056
+ do {
52057
+ observedRevision = this._workspacesRevision;
52058
+ await this._workspacePropReconciliationPromise;
52059
+ await this.waitForSceneRender();
52060
+ } while (observedRevision !== this._workspacesRevision ||
52061
+ this._workspacePropReconciliationPromise);
52062
+ }
51507
52063
  /**
51508
52064
  * Initializes the asset storage layer with the latest config. Mirrors
51509
52065
  * the in-flight replay logic of {@link doInitializeSyncAndWorkspace} for
@@ -51886,7 +52442,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51886
52442
  if (!KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup')) {
51887
52443
  return 'auto';
51888
52444
  }
51889
- if (object.objects.length === 1 && KritzelClassHelper.isInstanceOf(object.objects[0], 'KritzelDynamicObject')) {
52445
+ if (object.objects.length === 1 && ObjectHelper.isKritzelEmbeddedObject(object.objects[0])) {
51890
52446
  return 'none';
51891
52447
  }
51892
52448
  return 'auto';
@@ -51894,7 +52450,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51894
52450
  // Track visible dynamic objects and unmount culled ones
51895
52451
  const currentVisibleDynamicObjects = new Set();
51896
52452
  visibleObjects.forEach(object => {
51897
- if (KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
52453
+ if (ObjectHelper.isKritzelEmbeddedObject(object)) {
51898
52454
  currentVisibleDynamicObjects.add(object.id);
51899
52455
  }
51900
52456
  });
@@ -51902,7 +52458,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51902
52458
  this._previousVisibleDynamicObjects.forEach(id => {
51903
52459
  if (!currentVisibleDynamicObjects.has(id)) {
51904
52460
  const object = this.core.store.objects.findById(id);
51905
- if (object && KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject')) {
52461
+ if (object && ObjectHelper.isKritzelEmbeddedObject(object)) {
51906
52462
  object.unmount();
51907
52463
  }
51908
52464
  }
@@ -51995,7 +52551,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
51995
52551
  display: 'flex',
51996
52552
  alignItems: 'center',
51997
52553
  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: {
52554
+ } }, h("kritzel-icon", { name: object.loadState === 'error' ? 'imageOff' : 'image', size: Math.max(16, Math.min(object.totalWidth, object.totalHeight) * 0.3), style: {
51999
52555
  color: KritzelColorHelper.resolveThemeColor({ light: '#9ca3af', dark: '#6b7280' }, currentTheme),
52000
52556
  } }))), KritzelClassHelper.isInstanceOf(object, 'KritzelDynamicObject') && (h("div", { ref: el => el && object.mount(el), style: {
52001
52557
  position: 'absolute',
@@ -52014,7 +52570,37 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
52014
52570
  padding: object.padding + 'px',
52015
52571
  overflow: 'visible',
52016
52572
  display: 'block',
52017
- } })), KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup') && !this.core.displaySelectionLineUI(object) && (h("div", { ref: el => el && object.mount(el), style: {
52573
+ } })), KritzelClassHelper.isInstanceOf(object, 'KritzelIframeObject') && (h("div", { style: {
52574
+ position: 'absolute',
52575
+ left: '0',
52576
+ top: '0',
52577
+ width: object.totalWidth + 'px',
52578
+ height: object.totalHeight + 'px',
52579
+ transform: object.rotationDegrees !== 0 ? `rotate(${object.rotationDegrees}deg)` : undefined,
52580
+ transformOrigin: object.rotationDegrees !== 0 ? `${object.totalWidth / 2}px ${object.totalHeight / 2}px` : undefined,
52581
+ opacity: effectiveOpacityString,
52582
+ backgroundColor: KritzelColorHelper.resolveThemeColor(object.backgroundColor, currentTheme),
52583
+ borderColor: KritzelColorHelper.resolveThemeColor(object.borderColor, currentTheme),
52584
+ borderWidth: object.borderWidth + 'px',
52585
+ borderStyle: 'solid',
52586
+ borderRadius: object.borderRadius !== undefined ? (typeof object.borderRadius === 'number' ? `${object.borderRadius}px` : object.borderRadius) : undefined,
52587
+ boxShadow: object.boxShadow,
52588
+ overflow: 'hidden',
52589
+ display: 'block',
52590
+ pointerEvents: object.markedForRemoval ? 'none' : 'auto',
52591
+ } }, h("iframe", { ref: element => element && object.mount(element), title: object.title, sandbox: object.sandboxAttribute, style: {
52592
+ display: 'block',
52593
+ width: '100%',
52594
+ height: '100%',
52595
+ border: '0',
52596
+ borderRadius: object.borderRadius !== undefined ? (typeof object.borderRadius === 'number' ? `${object.borderRadius}px` : object.borderRadius) : undefined,
52597
+ pointerEvents: object.isInteractive ? 'auto' : 'none',
52598
+ } }), h("div", { style: {
52599
+ position: 'absolute',
52600
+ inset: '0',
52601
+ borderRadius: object.borderRadius !== undefined ? (typeof object.borderRadius === 'number' ? `${object.borderRadius}px` : object.borderRadius) : undefined,
52602
+ pointerEvents: object.isInteractive ? 'none' : 'auto',
52603
+ } }))), KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup') && !this.core.displaySelectionLineUI(object) && (h("div", { ref: el => el && object.mount(el), style: {
52018
52604
  position: 'absolute',
52019
52605
  left: '0',
52020
52606
  top: '0',
@@ -52372,6 +52958,9 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
52372
52958
  "user": [{
52373
52959
  "onUserChange": 0
52374
52960
  }],
52961
+ "tools": [{
52962
+ "onToolsChange": 0
52963
+ }],
52375
52964
  "globalContextMenuItems": [{
52376
52965
  "onGlobalContextMenuItemsChange": 0
52377
52966
  }],
@@ -52432,6 +53021,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
52432
53021
  "syncConfig": [16],
52433
53022
  "assetStorageConfig": [16],
52434
53023
  "user": [16],
53024
+ "tools": [16],
52435
53025
  "globalContextMenuItems": [16],
52436
53026
  "objectContextMenuItems": [16],
52437
53027
  "scaleMax": [1026, "scale-max"],
@@ -52456,7 +53046,7 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
52456
53046
  "beginSceneBootstrap": [64],
52457
53047
  "endSceneBootstrap": [64],
52458
53048
  "registerTool": [64],
52459
- "changeActiveTool": [64],
53049
+ "setActiveTool": [64],
52460
53050
  "disable": [64],
52461
53051
  "enable": [64],
52462
53052
  "delete": [64],
@@ -52532,7 +53122,8 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
52532
53122
  "getAvailableLocales": [64],
52533
53123
  "getAvailableLocaleOptions": [64],
52534
53124
  "t": [64],
52535
- "getResolvedTerms": [64]
53125
+ "getResolvedTerms": [64],
53126
+ "getTool": [64]
52536
53127
  }, [[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
53128
  "workspaces": [{
52538
53129
  "onWorkspacesPropChange": 0
@@ -52549,6 +53140,9 @@ const KritzelEngine$1 = /*@__PURE__*/ proxyCustomElement(class KritzelEngine ext
52549
53140
  "user": [{
52550
53141
  "onUserChange": 0
52551
53142
  }],
53143
+ "tools": [{
53144
+ "onToolsChange": 0
53145
+ }],
52552
53146
  "globalContextMenuItems": [{
52553
53147
  "onGlobalContextMenuItemsChange": 0
52554
53148
  }],
@@ -52638,4 +53232,4 @@ function defineCustomElement$1() {
52638
53232
  const KritzelEngine = KritzelEngine$1;
52639
53233
  const defineCustomElement = defineCustomElement$1;
52640
53234
 
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 };
53235
+ 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 };