kritzel-stencil 0.3.21 → 0.3.23

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 (104) hide show
  1. package/dist/cjs/index.cjs.js +3 -1
  2. package/dist/cjs/kritzel-active-users_45.cjs.entry.js +507 -288
  3. package/dist/cjs/{schema.constants-IBG5VW_J.js → schema.constants-Lr_zNkox.js} +713 -47
  4. package/dist/collection/classes/core/core.class.js +83 -5
  5. package/dist/collection/classes/core/store.class.js +9 -0
  6. package/dist/collection/classes/handlers/key.handler.js +1 -1
  7. package/dist/collection/classes/handlers/selection.handler.js +13 -9
  8. package/dist/collection/classes/objects/base-object.class.js +20 -8
  9. package/dist/collection/classes/objects/custom-element.class.js +330 -8
  10. package/dist/collection/classes/objects/image.class.js +0 -1
  11. package/dist/collection/classes/objects/selection-group.class.js +12 -0
  12. package/dist/collection/classes/providers/assets/asset-resolver.class.js +24 -14
  13. package/dist/collection/classes/providers/assets/indexeddb-asset-provider.class.js +0 -8
  14. package/dist/collection/classes/registries/custom-element-renderer.registry.js +52 -0
  15. package/dist/collection/classes/structures/app-state-map.structure.js +17 -3
  16. package/dist/collection/classes/structures/object-map.structure.js +17 -3
  17. package/dist/collection/classes/tools/selection-tool.class.js +34 -6
  18. package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +9 -8
  19. package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +211 -78
  20. package/dist/collection/components/ui/kritzel-settings/kritzel-settings.js +2 -1
  21. package/dist/collection/configs/default-engine-config.js +3 -2
  22. package/dist/collection/constants/version.js +1 -1
  23. package/dist/collection/helpers/html.helper.js +52 -0
  24. package/dist/collection/helpers/object.helper.js +3 -0
  25. package/dist/collection/helpers/svg-export.helper.js +35 -0
  26. package/dist/collection/helpers/wheel.helper.js +113 -0
  27. package/dist/collection/index.js +3 -0
  28. package/dist/collection/interfaces/custom-element-renderer.interface.js +1 -0
  29. package/dist/components/index.js +1 -1
  30. package/dist/components/kritzel-awareness-cursors.js +1 -1
  31. package/dist/components/kritzel-controls.js +1 -1
  32. package/dist/components/kritzel-current-user-dialog.js +1 -1
  33. package/dist/components/kritzel-current-user.js +1 -1
  34. package/dist/components/kritzel-dialog.js +1 -1
  35. package/dist/components/kritzel-editor.js +1 -1
  36. package/dist/components/kritzel-engine.js +1 -1
  37. package/dist/components/kritzel-export.js +1 -1
  38. package/dist/components/kritzel-login-dialog.js +1 -1
  39. package/dist/components/kritzel-menu-item.js +1 -1
  40. package/dist/components/kritzel-menu.js +1 -1
  41. package/dist/components/kritzel-more-menu.js +1 -1
  42. package/dist/components/kritzel-portal.js +1 -1
  43. package/dist/components/kritzel-settings.js +1 -1
  44. package/dist/components/kritzel-share-dialog.js +1 -1
  45. package/dist/components/kritzel-split-button.js +1 -1
  46. package/dist/components/kritzel-tool-config.js +1 -1
  47. package/dist/components/kritzel-tooltip.js +1 -1
  48. package/dist/components/kritzel-workspace-manager.js +1 -1
  49. package/dist/components/{p-BwuemYET.js → p-B4WfPDj0.js} +1 -1
  50. package/dist/components/p-BNu3Akp5.js +1 -0
  51. package/dist/components/p-BYveczcb.js +1 -0
  52. package/dist/components/{p-D28ZylHn.js → p-BixEMVtW.js} +1 -1
  53. package/dist/components/{p-Ndib1Jb3.js → p-BntMEYss.js} +1 -1
  54. package/dist/components/p-C48bv_0f.js +9 -0
  55. package/dist/components/{p-BO1CnEOT.js → p-CPMpf9ku.js} +1 -1
  56. package/dist/components/{p-BhODnvMO.js → p-CZQz_mul.js} +1 -1
  57. package/dist/components/{p-COj8EVzL.js → p-Ckpa5tHF.js} +1 -1
  58. package/dist/components/{p-BPoDE87Q.js → p-DJ3KI725.js} +1 -1
  59. package/dist/components/{p-DRsD38Nl.js → p-DPlEzPKO.js} +1 -1
  60. package/dist/components/{p-C-RiEw9X.js → p-DSd5xTAH.js} +1 -1
  61. package/dist/components/{p-CbMLbDou.js → p-DTqMF94b.js} +1 -1
  62. package/dist/components/{p-CHrSFOSI.js → p-DX2nriaq.js} +1 -1
  63. package/dist/components/{p-BAN5dnHX.js → p-Dd-pMvlr.js} +1 -1
  64. package/dist/components/{p-C5TZjIE1.js → p-M9m9-OEY.js} +1 -1
  65. package/dist/components/p-WUdQqYnu.js +1 -0
  66. package/dist/components/{p-YFR180v-.js → p-XDNjFYRB.js} +1 -1
  67. package/dist/components/{p-CJEIUs0x.js → p-X_pyPCe8.js} +1 -1
  68. package/dist/components/{p-CpR9lc-o.js → p-meY80XnF.js} +1 -1
  69. package/dist/components/{p-6Ca7Eolf.js → p-y5T0_Wvi.js} +1 -1
  70. package/dist/esm/index.js +2 -2
  71. package/dist/esm/kritzel-active-users_45.entry.js +500 -281
  72. package/dist/esm/{schema.constants-D35cwCYd.js → schema.constants-DMPBDrZh.js} +711 -47
  73. package/dist/stencil/index.esm.js +1 -1
  74. package/dist/stencil/p-27d41f6f.entry.js +9 -0
  75. package/dist/stencil/p-DMPBDrZh.js +1 -0
  76. package/dist/stencil/stencil.esm.js +1 -1
  77. package/dist/types/classes/core/core.class.d.ts +15 -0
  78. package/dist/types/classes/core/store.class.d.ts +6 -0
  79. package/dist/types/classes/objects/base-object.class.d.ts +3 -2
  80. package/dist/types/classes/objects/custom-element.class.d.ts +87 -21
  81. package/dist/types/classes/objects/image.class.d.ts +0 -1
  82. package/dist/types/classes/objects/selection-group.class.d.ts +1 -0
  83. package/dist/types/classes/providers/assets/asset-resolver.class.d.ts +3 -0
  84. package/dist/types/classes/providers/assets/indexeddb-asset-provider.class.d.ts +0 -1
  85. package/dist/types/classes/registries/custom-element-renderer.registry.d.ts +32 -0
  86. package/dist/types/components/core/kritzel-engine/kritzel-engine.d.ts +7 -1
  87. package/dist/types/components.d.ts +2 -2
  88. package/dist/types/constants/version.d.ts +1 -1
  89. package/dist/types/helpers/html.helper.d.ts +2 -0
  90. package/dist/types/helpers/object.helper.d.ts +1 -0
  91. package/dist/types/helpers/svg-export.helper.d.ts +9 -0
  92. package/dist/types/helpers/wheel.helper.d.ts +9 -0
  93. package/dist/types/index.d.ts +3 -0
  94. package/dist/types/interfaces/asset-storage-config.interface.d.ts +0 -4
  95. package/dist/types/interfaces/custom-element-renderer.interface.d.ts +40 -0
  96. package/dist/types/interfaces/debug-info.interface.d.ts +1 -0
  97. package/dist/types/interfaces/object.interface.d.ts +0 -1
  98. package/package.json +2 -2
  99. package/dist/components/p-BML28BJR.js +0 -1
  100. package/dist/components/p-BYzaazty.js +0 -1
  101. package/dist/components/p-CVV-HzFR.js +0 -1
  102. package/dist/components/p-DWhWFDcC.js +0 -9
  103. package/dist/stencil/p-60842099.entry.js +0 -9
  104. package/dist/stencil/p-D35cwCYd.js +0 -1
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var index = require('./index-Xav9JFHg.js');
4
- var schema_constants = require('./schema.constants-IBG5VW_J.js');
4
+ var schema_constants = require('./schema.constants-Lr_zNkox.js');
5
5
  var Y = require('yjs');
6
6
  require('y-indexeddb');
7
7
  require('y-websocket');
@@ -1400,71 +1400,6 @@ const KritzelCursorTrail = class {
1400
1400
  };
1401
1401
  KritzelCursorTrail.style = kritzelCursorTrailCss();
1402
1402
 
1403
- class KritzelHTMLHelper {
1404
- static getNumericValueFromStyle(element, property) {
1405
- const value = window.getComputedStyle(element).getPropertyValue(property);
1406
- return parseFloat(value) || 0;
1407
- }
1408
- static getScrollableParent(element) {
1409
- if (!element) {
1410
- return window;
1411
- }
1412
- const parent = element.parentNode?.host ?? element.parentElement;
1413
- if (!parent || parent.tagName === 'BODY') {
1414
- return window;
1415
- }
1416
- const style = window.getComputedStyle(parent);
1417
- if (style.overflow === 'auto' || style.overflowY === 'auto' || style.overflow === 'scroll' || style.overflowY === 'scroll') {
1418
- return parent;
1419
- }
1420
- return this.getScrollableParent(parent);
1421
- }
1422
- static isElementInViewport(element) {
1423
- if (!element) {
1424
- return false;
1425
- }
1426
- const scrollableParent = this.getScrollableParent(element);
1427
- const rect = element.getBoundingClientRect();
1428
- if (scrollableParent === window) {
1429
- const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
1430
- const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
1431
- return rect.top >= 0 && rect.left >= 0 && rect.bottom <= viewportHeight && rect.right <= viewportWidth;
1432
- }
1433
- const parentRect = scrollableParent.getBoundingClientRect();
1434
- return rect.top >= parentRect.top && rect.left >= parentRect.left && rect.bottom <= parentRect.bottom && rect.right <= parentRect.right;
1435
- }
1436
- static getFocusableElements(root) {
1437
- if (!root)
1438
- return [];
1439
- const focusableElements = [];
1440
- const focusableSelector = 'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), details, [tabindex]:not([tabindex="-1"])';
1441
- const isVisible = (el) => {
1442
- const style = window.getComputedStyle(el);
1443
- return style.display !== 'none' && style.visibility !== 'hidden';
1444
- };
1445
- const find = (element) => {
1446
- // Check if the element itself is focusable
1447
- if (element instanceof HTMLElement && element.matches(focusableSelector) && !element.hasAttribute('disabled') && isVisible(element)) {
1448
- focusableElements.push(element);
1449
- }
1450
- // Traverse shadow DOM
1451
- if (element.shadowRoot) {
1452
- Array.from(element.shadowRoot.children).forEach(find);
1453
- }
1454
- // Traverse slotted content
1455
- if (element.tagName === 'SLOT') {
1456
- element.assignedElements({ flatten: true }).forEach(find);
1457
- }
1458
- // Traverse light DOM children if there is no shadow root
1459
- if (!element.shadowRoot) {
1460
- Array.from(element.children).forEach(find);
1461
- }
1462
- };
1463
- find(root);
1464
- return focusableElements;
1465
- }
1466
- }
1467
-
1468
1403
  const kritzelDialogCss = () => `:host{display:contents}.dialog-content{text-align:start;line-height:normal}.backdrop{position:fixed;top:0;left:0;right:0;bottom:0;z-index:10002;display:flex;align-items:center;justify-content:center;background-color:var(--kritzel-dialog-backdrop-color, rgba(0, 0, 0, 0.5));opacity:1;transition:opacity 150ms ease-out}.backdrop.is-animating{opacity:0}.dialog-content{position:relative;display:flex;flex-direction:column;background-color:var(--kritzel-dialog-background-color, #ffffff);border-radius:var(--kritzel-dialog-border-radius, 12px);box-shadow:var(--kritzel-dialog-box-shadow, 0 4px 20px rgba(0, 0, 0, 0.15));border:var(--kritzel-dialog-border, 1px solid #ebebeb);max-height:var(--kritzel-dialog-max-height, 90vh);max-width:var(--kritzel-dialog-max-width, 90vw);overflow:hidden;transform:scale(1);opacity:1;transition:transform 200ms ease-out, opacity 200ms ease-out;font-family:var(--kritzel-dialog-font-family, sans-serif)}.dialog-content.is-animating{transform:scale(0.95);opacity:0}.dialog-content.size-small{width:var(--kritzel-dialog-width-small, 320px);height:var(--kritzel-dialog-height-small, auto)}.dialog-content.size-medium{width:var(--kritzel-dialog-width-medium, 480px);height:var(--kritzel-dialog-height-medium, auto)}.dialog-content.size-large{width:var(--kritzel-dialog-width-large, 640px);height:var(--kritzel-dialog-height-large, auto)}.dialog-content.size-fullscreen{width:100vw;height:100vh;height:100dvh;max-width:100vw;max-height:100vh;max-height:100dvh;border-radius:0}.backdrop.contained-fullscreen{background-color:transparent}.dialog-content.contained-fullscreen{border-radius:0}.dialog-content.contained-fullscreen .dialog-body{display:flex;flex-direction:column}.dialog-content.contained-fullscreen .dialog-body ::slotted(*){flex:1;min-height:0}@media (max-width: 576px), (max-height: 576px) and (orientation: landscape){.backdrop:has(.fullscreen-on-mobile){background-color:transparent}.dialog-content.fullscreen-on-mobile{width:100vw;height:100vh;height:100dvh;max-width:100vw;max-height:100vh;max-height:100dvh;border-radius:0}.dialog-content.fullscreen-on-mobile .dialog-body{display:flex;flex-direction:column}.dialog-content.fullscreen-on-mobile .dialog-body ::slotted(*){flex:1;min-height:0}}.dialog-header{display:flex;align-items:center;justify-content:space-between;padding:var(--kritzel-dialog-header-padding, 16px 20px);border-bottom:var(--kritzel-dialog-header-border, 1px solid #ebebeb);gap:12px}.dialog-title{margin:0;font-size:var(--kritzel-dialog-title-font-size, 18px);font-weight:var(--kritzel-dialog-title-font-weight, 600);color:var(--kritzel-dialog-title-color, #1a1a1a);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.close-button{display:flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:none;border-radius:var(--kritzel-dialog-close-button-border-radius, 6px);background-color:var(--kritzel-dialog-close-button-background, transparent);color:var(--kritzel-dialog-close-button-color, #666666);cursor:var(--kritzel-global-pointer-cursor, pointer);transition:background-color 150ms ease, color 150ms ease;flex-shrink:0;-webkit-tap-highlight-color:transparent}.close-button:hover{background-color:var(--kritzel-dialog-close-button-hover-background, #f5f5f5);color:var(--kritzel-dialog-close-button-hover-color, #1a1a1a)}.close-button:active{background-color:var(--kritzel-dialog-close-button-active-background, #ebebeb)}.close-button:focus-visible{outline:revert;outline-offset:revert}.dialog-body{padding:var(--kritzel-dialog-body-padding, 20px);overflow-y:auto;flex:1;min-height:0;scrollbar-color:var(--kritzel-global-scrollbar-thumb-color, #ebebeb) transparent;scrollbar-width:thin}.dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:var(--kritzel-dialog-footer-gap, 8px);padding:var(--kritzel-dialog-footer-padding, 16px 20px);border-top:var(--kritzel-dialog-footer-border, 1px solid #ebebeb)}::slotted([slot='header']){flex:1}::slotted([slot='footer']){display:contents}`;
1469
1404
 
1470
1405
  const KritzelDialog = class {
@@ -1747,7 +1682,7 @@ const KritzelDialog = class {
1747
1682
  const dialogContent = this.host.shadowRoot?.querySelector('.dialog-content');
1748
1683
  if (!dialogContent)
1749
1684
  return;
1750
- const focusableElements = KritzelHTMLHelper.getFocusableElements(dialogContent);
1685
+ const focusableElements = schema_constants.KritzelHTMLHelper.getFocusableElements(dialogContent);
1751
1686
  const slotElements = this.getSlottedFocusableElements();
1752
1687
  const allFocusable = [...focusableElements, ...slotElements];
1753
1688
  if (allFocusable.length > 0) {
@@ -1764,7 +1699,7 @@ const KritzelDialog = class {
1764
1699
  const assigned = slot.assignedElements({ flatten: true });
1765
1700
  assigned.forEach(el => {
1766
1701
  // Use KritzelHTMLHelper to find focusable elements including those in nested shadow DOMs
1767
- const elements = KritzelHTMLHelper.getFocusableElements(el);
1702
+ const elements = schema_constants.KritzelHTMLHelper.getFocusableElements(el);
1768
1703
  focusable.push(...elements);
1769
1704
  });
1770
1705
  });
@@ -2226,6 +2161,7 @@ const KritzelEditor = class {
2226
2161
  showObjectInfo: false,
2227
2162
  showSyncProviderInfo: true,
2228
2163
  showMigrationInfo: true,
2164
+ showAssetResolverInfo: false,
2229
2165
  };
2230
2166
  user;
2231
2167
  activeUsers;
@@ -3137,31 +3073,31 @@ const KritzelEditor = class {
3137
3073
  const isLoggedIn = this.isLoggedIn;
3138
3074
  const shouldShowCurrentUser = isLoggedIn;
3139
3075
  const shouldShowLoginButton = this.isReady && !!this.loginConfig && !isLoggedIn;
3140
- return (index.h(index.Host, { key: '07fe246b722ca64cfbdd8cc3474e6288c39d1128', style: {
3076
+ return (index.h(index.Host, { key: '56b172459e6d940229dc1d4f409157e403ad5f27', style: {
3141
3077
  opacity: this.isEditorVisible ? '1' : '0',
3142
3078
  visibility: this.isEditorVisible ? 'visible' : 'hidden',
3143
3079
  transition: 'opacity 0.2s ease-in-out, visibility 0.2s ease-in-out',
3144
- } }, index.h("div", { key: '2d0db1e78161edb061779b5875114e901f8e79f8', class: "top-left-buttons" }, index.h("kritzel-workspace-manager", { key: '8fee1d57b0b868b673c41015453fd39a19f8cb43', visible: this.isWorkspaceManagerVisible, workspaces: this.workspaces, activeWorkspace: this.activeWorkspace, terms: this.resolvedTerms, onWorkspaceChange: event => (this.activeWorkspace = event.detail), onIsWorkspaceManagerReady: () => (this.isWorkspaceManagerReady = true) }), index.h("kritzel-back-to-content", { key: 'f2a0305bf73bd14d7522974b7956a289a0a44258', visible: this.isBackToContentButtonVisible, text: this.resolvedTerms['backToContent.label'] ?? 'Back to content', onBackToContent: () => this.backToContent() })), this.activeNotification && (index.h("div", { key: '918fe80193999ec072042500cd1eaf3670f5ed3c', class: "top-center-notification-layer", role: "presentation" }, index.h("div", { key: '0a5609b2b32f9f04bac03618803c14d9a891742c', class: { 'top-center-notification-container': true, 'is-dismissing': this.isNotificationDismissing }, role: "presentation" }, index.h("kritzel-notification-card", { key: 'eb1ff7aeaee3a237baf3ba2d96a10ba03a8bf7fb', notification: this.activeNotification, locale: this.locale, onDismiss: this.dismissNotification, onHoverChange: event => this.handleNotificationHoverChange(event) })))), index.h("kritzel-engine", { key: '8783a87101f308029482ced926a8d2163aae1428', ref: el => {
3080
+ } }, index.h("div", { key: '93a2195c14402f38416b2d377c684e2650418edb', class: "top-left-buttons" }, index.h("kritzel-workspace-manager", { key: '1b42b21bc35e1fc56202e19a78053e46d87ca51a', visible: this.isWorkspaceManagerVisible, workspaces: this.workspaces, activeWorkspace: this.activeWorkspace, terms: this.resolvedTerms, onWorkspaceChange: event => (this.activeWorkspace = event.detail), onIsWorkspaceManagerReady: () => (this.isWorkspaceManagerReady = true) }), index.h("kritzel-back-to-content", { key: '3b90b69a5b83a6e5593d4f752c4094a2a3af8c4d', visible: this.isBackToContentButtonVisible, text: this.resolvedTerms['backToContent.label'] ?? 'Back to content', onBackToContent: () => this.backToContent() })), this.activeNotification && (index.h("div", { key: '9c2ee1e10212145bf2f97a1a7f27746aa18cfc98', class: "top-center-notification-layer", role: "presentation" }, index.h("div", { key: 'a77b025329da64e1f1223a2f749aa2fc60e5c469', class: { 'top-center-notification-container': true, 'is-dismissing': this.isNotificationDismissing }, role: "presentation" }, index.h("kritzel-notification-card", { key: '9ced0bc5213ec8c3387915cd71272a1f56161405', notification: this.activeNotification, locale: this.locale, onDismiss: this.dismissNotification, onHoverChange: event => this.handleNotificationHoverChange(event) })))), index.h("kritzel-engine", { key: '884b7f088e164a51c49158c409904ca5419f2d55', ref: el => {
3145
3081
  if (el) {
3146
3082
  this.engineRef = el;
3147
3083
  }
3148
- }, workspace: this.activeWorkspace, activeWorkspaceId: this.activeWorkspaceId, editorId: this.editorId, syncConfig: this.syncConfig, assetStorageConfig: this.assetStorageConfig, user: this.user, scaleMax: this.scaleMax, lockDrawingScale: this.lockDrawingScale, isObjectDistanceFadingActive: this.isObjectDistanceFadingActive, scaleMin: this.scaleMin, cursorTarget: this.cursorTarget, isLoading: this.isLoading, viewportBoundaryLeft: this.viewportBoundaryLeft, viewportBoundaryRight: this.viewportBoundaryRight, viewportBoundaryTop: this.viewportBoundaryTop, viewportBoundaryBottom: this.viewportBoundaryBottom, isPanningEnabled: this.isPanningEnabled, isZoomingEnabled: this.isZoomingEnabled, theme: this.theme, themes: this.themes, licenseKey: this.licenseKey, locale: this.locale, locales: this.locales, fallbackLocale: this.fallbackLocale, debugInfo: this.debugInfo, globalContextMenuItems: this.globalContextMenuItems, objectContextMenuItems: this.objectContextMenuItems, onIsEngineReady: event => this.onEngineReady(event), onWorkspacesChange: event => this.handleWorkspacesChange(event), onActiveWorkspaceChange: event => this.handleActiveWorkspaceChange(event), onObjectsChange: event => this.handleObjectsChange(event), onObjectsAdded: event => this.handleObjectsAdded(event), onObjectsRemoved: event => this.handleObjectsRemoved(event), onObjectsUpdated: event => this.handleObjectsUpdated(event), onUndoStateChange: event => this.handleUndoStateChange(event), onObjectsInViewportChange: event => this.handleObjectsInViewportChange(event), onViewportChange: event => this.handleViewportChange(event), onAwarenessChange: event => this.handleAwarenessChange(event), onNotificationsChange: event => this.handleNotificationsChange(event) }), index.h("kritzel-controls", { key: '73a94f6baef4e0e26be4540ecd44d24588f5761b', visible: this.isControlsVisible, class: { 'keyboard-open': this.isVirtualKeyboardOpen }, ref: el => {
3084
+ }, workspace: this.activeWorkspace, activeWorkspaceId: this.activeWorkspaceId, editorId: this.editorId, syncConfig: this.syncConfig, assetStorageConfig: this.assetStorageConfig, user: this.user, scaleMax: this.scaleMax, lockDrawingScale: this.lockDrawingScale, isObjectDistanceFadingActive: this.isObjectDistanceFadingActive, scaleMin: this.scaleMin, cursorTarget: this.cursorTarget, isLoading: this.isLoading, viewportBoundaryLeft: this.viewportBoundaryLeft, viewportBoundaryRight: this.viewportBoundaryRight, viewportBoundaryTop: this.viewportBoundaryTop, viewportBoundaryBottom: this.viewportBoundaryBottom, isPanningEnabled: this.isPanningEnabled, isZoomingEnabled: this.isZoomingEnabled, theme: this.theme, themes: this.themes, licenseKey: this.licenseKey, locale: this.locale, locales: this.locales, fallbackLocale: this.fallbackLocale, debugInfo: this.debugInfo, globalContextMenuItems: this.globalContextMenuItems, objectContextMenuItems: this.objectContextMenuItems, onIsEngineReady: event => this.onEngineReady(event), onWorkspacesChange: event => this.handleWorkspacesChange(event), onActiveWorkspaceChange: event => this.handleActiveWorkspaceChange(event), onObjectsChange: event => this.handleObjectsChange(event), onObjectsAdded: event => this.handleObjectsAdded(event), onObjectsRemoved: event => this.handleObjectsRemoved(event), onObjectsUpdated: event => this.handleObjectsUpdated(event), onUndoStateChange: event => this.handleUndoStateChange(event), onObjectsInViewportChange: event => this.handleObjectsInViewportChange(event), onViewportChange: event => this.handleViewportChange(event), onAwarenessChange: event => this.handleAwarenessChange(event), onNotificationsChange: event => this.handleNotificationsChange(event) }), index.h("kritzel-controls", { key: '3a7c22f037d551636c53190eb07b318c2b857162', visible: this.isControlsVisible, class: { 'keyboard-open': this.isVirtualKeyboardOpen }, ref: el => {
3149
3085
  if (el) {
3150
3086
  this.controlsRef = el;
3151
3087
  }
3152
- }, controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState ?? undefined, theme: this.theme, terms: this.resolvedTerms, onIsControlsReady: () => (this.isControlsReady = true) }), index.h("div", { key: 'b0653098e5e15b81f453a7f3c2b27f137eb38f2e', class: "bottom-left-buttons" }, index.h("kritzel-zoom-panel", { key: '33384aeb6bd32d17fb428bd9ed48e5d588ac60c0', visible: this.isZoomPanelVisible, disabled: !this.isZoomingEnabled, zoomPercent: this.currentZoomPercent, terms: this.resolvedTerms, onZoomIn: () => this.zoomIn(), onZoomOut: () => this.zoomOut() })), index.h("div", { key: '37bed04c062b63a99c930362fb8ce30b58ed8872', class: "top-right-buttons" }, index.h("kritzel-settings", { key: '4e339573e3d8d48bc9803b9dd40a549bd056d147', ref: el => {
3088
+ }, controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState ?? undefined, theme: this.theme, terms: this.resolvedTerms, onIsControlsReady: () => (this.isControlsReady = true) }), index.h("div", { key: 'c0dad9086679397c46f5550c38686a9b7d8bae6c', class: "bottom-left-buttons" }, index.h("kritzel-zoom-panel", { key: '1639d6d2092ecd3fbc657dfdcfc3f12c02539329', visible: this.isZoomPanelVisible, disabled: !this.isZoomingEnabled, zoomPercent: this.currentZoomPercent, terms: this.resolvedTerms, onZoomIn: () => this.zoomIn(), onZoomOut: () => this.zoomOut() })), index.h("div", { key: '45313b28a5e4bd75d09980e4765c2241d54fe1df', class: "top-right-buttons" }, index.h("kritzel-settings", { key: '33737a005cd6580505ee741be0dc40bd69873276', ref: el => {
3153
3089
  if (el) {
3154
3090
  this.settingsRef = el;
3155
3091
  }
3156
- }, shortcuts: this.shortcuts, availableThemes: this.themes && this.themes.length > 0 ? this.themes.map(t => t.name) : ['light', 'dark'], availableLocales: this.availableLocaleOptions, settings: this.currentSettingsConfig, terms: this.resolvedTerms, onSettingsChange: event => this.handleSettingsChange(event) }), index.h("kritzel-export", { key: '2a726ea5b56cd36ecf92bbab17d69d04baf73009', ref: el => {
3092
+ }, shortcuts: this.shortcuts, availableThemes: this.themes && this.themes.length > 0 ? this.themes.map(t => t.name) : ['light', 'dark'], availableLocales: this.availableLocaleOptions, settings: this.currentSettingsConfig, terms: this.resolvedTerms, onSettingsChange: event => this.handleSettingsChange(event) }), index.h("kritzel-export", { key: '9e13001850b15b9ce9d8a502466ed9fa05ac1518', ref: el => {
3157
3093
  if (el) {
3158
3094
  this.exportRef = el;
3159
3095
  }
3160
- }, workspaceName: this.activeWorkspace?.name || 'workspace', terms: this.resolvedTerms, onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), index.h("kritzel-active-users", { key: '26d9bbf922b1d4158a0875ba42e7b598011973d5', users: this.activeUsers }), shouldShowCurrentUser && index.h("kritzel-current-user", { key: '8179cc3e940c9fd8d97978af2bf52543f661a0fd', user: this.user, terms: this.resolvedTerms }), shouldShowLoginButton && (index.h("kritzel-button", { key: 'd4aa5bc108043eb103754d14f1b5df42ab73f8c4', onButtonClick: () => this.loginDialogRef?.open() }, this.resolvedTerms['login.dialogTitle'] ?? 'Sign in')), index.h("kritzel-more-menu", { key: '17ac617bde379d7484dbeed39ac7168349b634a1', items: this.moreMenuItems, visible: this.isMoreMenuVisible, terms: this.resolvedTerms }), index.h("kritzel-share-dialog", { key: '881729b36d34ef3ac3d87fc51ad828fc20fa36cd', ref: el => {
3096
+ }, workspaceName: this.activeWorkspace?.name || 'workspace', terms: this.resolvedTerms, onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), index.h("kritzel-active-users", { key: 'f2494af1241ecc068ace09b23cfd405c76bd1273', users: this.activeUsers }), shouldShowCurrentUser && index.h("kritzel-current-user", { key: '7f199e3493805977b5e5b8d2c736f31729919f96', user: this.user, terms: this.resolvedTerms }), shouldShowLoginButton && (index.h("kritzel-button", { key: 'c6c192ddb1245acc771ac550c1fe3e4f9bc08eab', onButtonClick: () => this.loginDialogRef?.open() }, this.resolvedTerms['login.dialogTitle'] ?? 'Sign in')), index.h("kritzel-more-menu", { key: '869194078a4c90d42c5ebd604bc58b4f55fc4cb6', items: this.moreMenuItems, visible: this.isMoreMenuVisible, terms: this.resolvedTerms }), index.h("kritzel-share-dialog", { key: '8daed8fd7267aca056b6a53626c721e2f23cead4', ref: el => {
3161
3097
  if (el) {
3162
3098
  this.shareDialogRef = el;
3163
3099
  }
3164
- }, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, terms: this.resolvedTerms, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (index.h("kritzel-login-dialog", { key: '5e34b647ac4c3e18981042d765b4d443422c5db3', ref: el => {
3100
+ }, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, terms: this.resolvedTerms, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (index.h("kritzel-login-dialog", { key: 'b65ea0940cee31b152b60881180fab2445c92b0c', ref: el => {
3165
3101
  if (el) {
3166
3102
  this.loginDialogRef = el;
3167
3103
  }
@@ -21366,7 +21302,7 @@ class KritzelKeyHandler extends schema_constants.KritzelBaseHandler {
21366
21302
  // of running global canvas shortcuts. Escape is the one exception — it
21367
21303
  // still runs through the shortcut system so it can collapse the text
21368
21304
  // selection (handled inside the shortcut's action).
21369
- if ((this._core.store.activeText || this._core.store.activeShape) && event.key !== 'Escape') {
21305
+ if ((this._core.store.activeText || this._core.store.activeShape || this._core.store.activeInteractiveCustomElement) && event.key !== 'Escape') {
21370
21306
  return;
21371
21307
  }
21372
21308
  const shortcut = this.shortcuts.find(s => s.key === event.key && !!s.ctrl === event.ctrlKey && !!s.shift === event.shiftKey && (!s.condition || s.condition(this._core)));
@@ -21566,120 +21502,6 @@ class KritzelContextMenuHandler extends schema_constants.KritzelBaseHandler {
21566
21502
  }
21567
21503
  }
21568
21504
 
21569
- /**
21570
- * Represents a custom HTML element that can be placed and manipulated on the Kritzel canvas.
21571
- * Extends KritzelBaseObject to inherit common object functionality like positioning,
21572
- * scaling, and lifecycle management.
21573
- */
21574
- class KritzelCustomElement extends schema_constants.KritzelBaseObject {
21575
- __class__ = 'KritzelCustomElement';
21576
- /** The HTML element to be rendered on the canvas. */
21577
- element;
21578
- /** Whether the element responds to user interactions. Defaults to true. */
21579
- isInteractive = true;
21580
- /**
21581
- * Creates a new KritzelCustomElement instance.
21582
- * @param config - Optional configuration object for the custom element.
21583
- * @param config.element - The HTML element to wrap.
21584
- * @param config.translateX - Initial X position offset. Defaults to 0.
21585
- * @param config.translateY - Initial Y position offset. Defaults to 0.
21586
- * @param config.scale - Initial scale factor. Defaults to 1.
21587
- * @param config.height - Initial height in pixels. Defaults to 0.
21588
- * @param config.width - Initial width in pixels. Defaults to 0.
21589
- */
21590
- constructor(config) {
21591
- super();
21592
- if (config) {
21593
- this.translateX = config.translateX || 0;
21594
- this.translateY = config.translateY || 0;
21595
- this.rotation = schema_constants.KritzelMathHelper.degreesToRadians(config.rotation ?? 0);
21596
- this.scale = config.scale || 1;
21597
- this.element = config.element;
21598
- this.height = config.height || 0;
21599
- this.width = config.width || 0;
21600
- }
21601
- }
21602
- /**
21603
- * Factory method to create a new KritzelCustomElement with proper core binding.
21604
- * Generates a unique ID and assigns the element to the active workspace.
21605
- * @param core - The KritzelCore instance for state management and ID generation.
21606
- * @param config - Optional configuration object for the custom element.
21607
- * @param config.element - The HTML element to wrap.
21608
- * @param config.translateX - Initial X position offset.
21609
- * @param config.translateY - Initial Y position offset.
21610
- * @param config.scale - Initial scale factor.
21611
- * @param config.height - Initial height in pixels.
21612
- * @param config.width - Initial width in pixels.
21613
- * @returns A new KritzelBaseObject instance bound to the core.
21614
- */
21615
- static create(core, config) {
21616
- const object = new KritzelCustomElement(config);
21617
- object._core = core;
21618
- object.id = object.generateId();
21619
- object.workspaceId = core.store.state.activeWorkspace.id;
21620
- object.userId = core.user?.id;
21621
- return object;
21622
- }
21623
- /**
21624
- * Mounts the custom element to a container element in the DOM.
21625
- * Clears any existing content in the container and appends this element.
21626
- * Skips mounting if already mounted to the same container with content attached.
21627
- * @param element - The container HTML element to mount this custom element into.
21628
- */
21629
- mount(element) {
21630
- if (element === null) {
21631
- return;
21632
- }
21633
- // If already mounted to the same element and content is still attached, skip
21634
- if (this.isMounted && this.elementRef === element && this.element.parentElement === element) {
21635
- return;
21636
- }
21637
- this.elementRef = element;
21638
- this.isMounted = true;
21639
- // Clear existing content and append the element
21640
- this.elementRef.innerHTML = '';
21641
- this.elementRef.appendChild(this.element);
21642
- }
21643
- /**
21644
- * Resizes and repositions the custom element on the canvas.
21645
- * Updates both the internal state and the DOM element's inline styles.
21646
- * Syncs changes to Yjs for collaborative editing and cross-tab propagation.
21647
- * @param x - The new X position (translateX) in canvas coordinates.
21648
- * @param y - The new Y position (translateY) in canvas coordinates.
21649
- * @param width - The new width in pixels. Must be greater than 1.
21650
- * @param height - The new height in pixels. Must be greater than 1.
21651
- */
21652
- resize(x, y, width, height) {
21653
- if (width <= 1 || height <= 1) {
21654
- return;
21655
- }
21656
- this.width = width;
21657
- this.height = height;
21658
- this.translateX = x;
21659
- this.translateY = y;
21660
- if (this.element) {
21661
- this.element.style.width = `${width}px`;
21662
- this.element.style.height = `${height}px`;
21663
- }
21664
- // Update to sync changes to y.js and propagate to other tabs
21665
- this._core.store.objects.update(this);
21666
- }
21667
- /**
21668
- * Creates a deep copy of this custom element.
21669
- * The copy receives a new unique ID, is marked as unmounted,
21670
- * and contains a cloned copy of the underlying HTML element.
21671
- * @returns A new KritzelCustomElement instance with copied properties and a cloned DOM element.
21672
- */
21673
- copy() {
21674
- const copiedObject = Object.create(Object.getPrototypeOf(this));
21675
- Object.assign(copiedObject, this);
21676
- copiedObject.id = this.generateId();
21677
- copiedObject.isMounted = false;
21678
- copiedObject.element = this.element.cloneNode(true);
21679
- return copiedObject;
21680
- }
21681
- }
21682
-
21683
21505
  /**
21684
21506
  * Handles deserialization/revival of Kritzel objects from plain JavaScript objects.
21685
21507
  * This class uses a pattern similar to JSON.parse reviver to reconstruct class instances
@@ -21740,7 +21562,7 @@ class KritzelReviver {
21740
21562
  revivedObj = schema_constants.KritzelImage.create(this._core).deserialize(obj);
21741
21563
  break;
21742
21564
  case 'KritzelCustomElement':
21743
- revivedObj = KritzelCustomElement.create(this._core).deserialize(obj);
21565
+ revivedObj = schema_constants.KritzelCustomElement.create(this._core).deserialize(obj);
21744
21566
  break;
21745
21567
  case 'KritzelSelectionGroup':
21746
21568
  revivedObj = schema_constants.KritzelSelectionGroup.create(this._core).deserialize(obj);
@@ -21827,8 +21649,9 @@ const DEFAULT_ENGINE_CONFIG = {
21827
21649
  debugInfo: {
21828
21650
  showObjectInfo: false,
21829
21651
  showViewportInfo: false,
21830
- showSyncProviderInfo: true,
21831
- showMigrationInfo: true
21652
+ showSyncProviderInfo: false,
21653
+ showAssetResolverInfo: false,
21654
+ showMigrationInfo: false,
21832
21655
  },
21833
21656
  host: null,
21834
21657
  pointerX: 0,
@@ -22509,7 +22332,8 @@ class KritzelObjectMap {
22509
22332
  const docName = core.editorId ? `kritzel-workspace-${core.editorId}-${workspaceId}` : `kritzel-workspace-${workspaceId}`;
22510
22333
  const finalConfig = config ?? DEFAULT_SYNC_CONFIG;
22511
22334
  // Instantiate providers from configuration
22512
- const quiet = !core.store?.state?.debugInfo?.showSyncProviderInfo;
22335
+ const showSyncProviderInfo = !!core.store?.state?.debugInfo?.showSyncProviderInfo;
22336
+ const quiet = !showSyncProviderInfo;
22513
22337
  for (const providerConfig of finalConfig.providers) {
22514
22338
  let provider;
22515
22339
  // Check if it's a class constructor or a factory
@@ -22523,6 +22347,10 @@ class KritzelObjectMap {
22523
22347
  }
22524
22348
  this._providers.push(provider);
22525
22349
  }
22350
+ if (showSyncProviderInfo) {
22351
+ const providerList = this._providers.map(p => `${p.constructor.name} (${p.type})`).join(', ');
22352
+ console.info(`[Kritzel] Workspace sync providers initialized for ${docName}: ${providerList || 'none'}`);
22353
+ }
22526
22354
  // captureTimeout is effectively infinite — undo-step boundaries are
22527
22355
  // marked explicitly via markUndoBoundary() in insert/remove/reset and
22528
22356
  // at stroke/gesture boundaries in the tools. This prevents a single
@@ -22563,15 +22391,24 @@ class KritzelObjectMap {
22563
22391
  const networkProviders = this._providers.filter(p => p.type === 'network');
22564
22392
  // Await local providers for immediate data availability
22565
22393
  const localResults = await Promise.allSettled(localProviders.map(p => p.connect()));
22394
+ if (showSyncProviderInfo) {
22395
+ const connectedLocalCount = localResults.filter(result => result.status === 'fulfilled').length;
22396
+ console.info(`[Kritzel] Workspace local providers connected: ${connectedLocalCount}/${localProviders.length}`);
22397
+ }
22566
22398
  localResults.forEach((result, i) => {
22567
- if (result.status === 'rejected') {
22399
+ if (showSyncProviderInfo && result.status === 'rejected') {
22568
22400
  console.error(`[Kritzel] Sync provider "${localProviders[i]?.constructor.name}" failed to connect:`, result.reason);
22569
22401
  }
22570
22402
  });
22571
22403
  // Connect network providers in the background (remote data arrives via Yjs observers)
22404
+ if (showSyncProviderInfo && networkProviders.length > 0) {
22405
+ console.info(`[Kritzel] Workspace network providers connecting in background: ${networkProviders.length}`);
22406
+ }
22572
22407
  for (const provider of networkProviders) {
22573
22408
  provider.connect().catch(err => {
22574
- console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
22409
+ if (showSyncProviderInfo) {
22410
+ console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
22411
+ }
22575
22412
  });
22576
22413
  }
22577
22414
  this._isReady = true;
@@ -23462,6 +23299,14 @@ class KritzelStore {
23462
23299
  const activeShapes = this.objects.filter(o => o instanceof schema_constants.KritzelShape && o.isEditing);
23463
23300
  return activeShapes.length > 0 ? activeShapes[0] : null;
23464
23301
  }
23302
+ /**
23303
+ * Gets the currently selected custom element whose focused descendant is an input-like element.
23304
+ * @returns The interactive custom element with focused input-like content, or null if none matches
23305
+ */
23306
+ get activeInteractiveCustomElement() {
23307
+ const interactiveCustomElement = this.selectedObjects.find(object => schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement') && object.hasFocusedInputElement());
23308
+ return interactiveCustomElement ?? null;
23309
+ }
23465
23310
  /**
23466
23311
  * Gets the path currently being drawn (not yet completed).
23467
23312
  * @returns The incomplete path object, or null if no path is being drawn
@@ -23631,7 +23476,8 @@ class KritzelAppStateMap {
23631
23476
  const docName = core.editorId ? `kritzel-app-state-${core.editorId}-${instanceId}` : `kritzel-app-state-${instanceId}`;
23632
23477
  const finalConfig = config ?? DEFAULT_SYNC_CONFIG;
23633
23478
  // Instantiate providers from configuration
23634
- const quiet = !core.store.state.debugInfo.showSyncProviderInfo;
23479
+ const showSyncProviderInfo = !!core.store.state.debugInfo.showSyncProviderInfo;
23480
+ const quiet = !showSyncProviderInfo;
23635
23481
  for (const providerConfig of finalConfig.providers) {
23636
23482
  let provider;
23637
23483
  // Check if it's a class constructor or a factory
@@ -23645,6 +23491,10 @@ class KritzelAppStateMap {
23645
23491
  }
23646
23492
  this._providers.push(provider);
23647
23493
  }
23494
+ if (showSyncProviderInfo) {
23495
+ const providerList = this._providers.map(p => `${p.constructor.name} (${p.type})`).join(', ');
23496
+ console.info(`[Kritzel] App-state sync providers initialized for ${docName}: ${providerList || 'none'}`);
23497
+ }
23648
23498
  // Observe changes to workspaces and sync with application state
23649
23499
  this._workspacesObserver = event => {
23650
23500
  this.handleWorkspacesChange(event);
@@ -23657,15 +23507,24 @@ class KritzelAppStateMap {
23657
23507
  const networkProviders = this._providers.filter(p => p.type === 'network');
23658
23508
  // Await local providers for immediate data availability
23659
23509
  const localResults = await Promise.allSettled(localProviders.map(p => p.connect()));
23510
+ if (showSyncProviderInfo) {
23511
+ const connectedLocalCount = localResults.filter(result => result.status === 'fulfilled').length;
23512
+ console.info(`[Kritzel] App-state local providers connected: ${connectedLocalCount}/${localProviders.length}`);
23513
+ }
23660
23514
  localResults.forEach((result, i) => {
23661
- if (result.status === 'rejected') {
23515
+ if (showSyncProviderInfo && result.status === 'rejected') {
23662
23516
  console.error(`[Kritzel] Sync provider "${localProviders[i]?.constructor.name}" failed to connect:`, result.reason);
23663
23517
  }
23664
23518
  });
23665
23519
  // Connect network providers in the background (remote data arrives via Yjs observers)
23520
+ if (showSyncProviderInfo && networkProviders.length > 0) {
23521
+ console.info(`[Kritzel] App-state network providers connecting in background: ${networkProviders.length}`);
23522
+ }
23666
23523
  for (const provider of networkProviders) {
23667
23524
  provider.connect().catch(err => {
23668
- console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
23525
+ if (showSyncProviderInfo) {
23526
+ console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
23527
+ }
23669
23528
  });
23670
23529
  }
23671
23530
  this._isReady = true;
@@ -24761,6 +24620,7 @@ class KritzelCore {
24761
24620
  this._clipboardManager = new KritzelClipboardManager(this);
24762
24621
  this._toolRegistry = new KritzelToolRegistry(this);
24763
24622
  this._assetResolver = new schema_constants.KritzelAssetResolver();
24623
+ this._assetResolver.setShowInfo(this._store.state.debugInfo?.showAssetResolverInfo ?? false);
24764
24624
  }
24765
24625
  /**
24766
24626
  * Gets the asset resolver responsible for storing and resolving
@@ -25132,6 +24992,10 @@ class KritzelCore {
25132
24992
  this.removeObject(child);
25133
24993
  });
25134
24994
  }
24995
+ // Unmount custom elements before removal
24996
+ if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
24997
+ object.unmount();
24998
+ }
25135
24999
  // Handle anchor cleanup
25136
25000
  if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelLine')) {
25137
25001
  // If removing a line, clean up its anchor index entries
@@ -25172,21 +25036,84 @@ class KritzelCore {
25172
25036
  * @param selectionGroup - The selection group to add
25173
25037
  */
25174
25038
  addSelectionGroup(selectionGroup) {
25175
- this.removeSelectionGroup();
25039
+ this.removeLocalSelectionGroups();
25176
25040
  this.removeSelectionBox();
25041
+ for (const object of selectionGroup.objects ?? []) {
25042
+ if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
25043
+ const customElementObject = object;
25044
+ if (typeof customElementObject.setIsInteractive === 'function') {
25045
+ customElementObject.setIsInteractive(true);
25046
+ }
25047
+ else {
25048
+ customElementObject.isInteractive = true;
25049
+ }
25050
+ }
25051
+ }
25177
25052
  this._store.objects.insert(selectionGroup);
25178
25053
  this._store.setSelectionGroup(selectionGroup);
25179
25054
  this._kritzelEngine.triggerSelectionChange();
25180
25055
  }
25056
+ /**
25057
+ * Returns whether the provided selection group belongs to the local user context.
25058
+ * Local groups are either explicitly owned by the current user or have no user ID.
25059
+ */
25060
+ isLocalSelectionGroup(selectionGroup) {
25061
+ const localUserId = this._user?.id;
25062
+ return localUserId == null || selectionGroup.userId === localUserId || selectionGroup.userId == null;
25063
+ }
25064
+ /**
25065
+ * Applies deselection teardown side effects to a selection group before removal.
25066
+ */
25067
+ teardownSelectionGroup(selectionGroup) {
25068
+ selectionGroup.deselectAllChildren?.();
25069
+ for (const object of selectionGroup.objects ?? []) {
25070
+ if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
25071
+ const customElementObject = object;
25072
+ if (typeof customElementObject.setIsInteractive === 'function') {
25073
+ customElementObject.setIsInteractive(false);
25074
+ }
25075
+ else {
25076
+ customElementObject.isInteractive = false;
25077
+ }
25078
+ }
25079
+ }
25080
+ }
25081
+ /**
25082
+ * Removes all local selection groups from the store, including orphaned groups
25083
+ * that are no longer referenced by store.selectionGroup.
25084
+ * @returns True when at least one local selection group was removed
25085
+ */
25086
+ removeLocalSelectionGroups() {
25087
+ const allSelectionGroupsInStore = this._store.objects
25088
+ .allObjects()
25089
+ .filter((object) => {
25090
+ return object instanceof schema_constants.KritzelSelectionGroup || schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup');
25091
+ });
25092
+ const allLocalSelectionGroups = allSelectionGroupsInStore.filter(selectionGroup => this.isLocalSelectionGroup(selectionGroup));
25093
+ const currentSelectionGroup = this._store.selectionGroup;
25094
+ if (currentSelectionGroup &&
25095
+ this.isLocalSelectionGroup(currentSelectionGroup) &&
25096
+ !allLocalSelectionGroups.some(group => group.id === currentSelectionGroup.id)) {
25097
+ allLocalSelectionGroups.push(currentSelectionGroup);
25098
+ }
25099
+ if (allLocalSelectionGroups.length === 0) {
25100
+ return false;
25101
+ }
25102
+ for (const selectionGroup of allLocalSelectionGroups) {
25103
+ this.teardownSelectionGroup(selectionGroup);
25104
+ }
25105
+ const localSelectionGroupIds = new Set(allLocalSelectionGroups.map(group => group.id));
25106
+ this._store.objects.remove(object => localSelectionGroupIds.has(object.id));
25107
+ this._store.setSelectionGroup(null);
25108
+ return true;
25109
+ }
25181
25110
  /**
25182
25111
  * Removes the current selection group from the canvas.
25183
25112
  * Clears the cached reference and triggers the selection change event.
25184
25113
  */
25185
25114
  removeSelectionGroup() {
25186
- const selectionGroup = this._store.selectionGroup;
25187
- if (selectionGroup) {
25188
- this._store.objects.remove(object => object.id === selectionGroup.id);
25189
- this._store.setSelectionGroup(null);
25115
+ const removedAnySelectionGroup = this.removeLocalSelectionGroups();
25116
+ if (removedAnySelectionGroup) {
25190
25117
  this._kritzelEngine.triggerSelectionChange();
25191
25118
  }
25192
25119
  }
@@ -25490,6 +25417,11 @@ class KritzelCore {
25490
25417
  (o instanceof schema_constants.KritzelSelectionGroup && (localUserId == null || o.userId === localUserId || o.userId == null)));
25491
25418
  this._store.setSelectionBox(null);
25492
25419
  this._store.setSelectionGroup(null);
25420
+ for (const object of this._store.allNonSelectionObjects) {
25421
+ if (object.isSelected) {
25422
+ object.isSelected = false;
25423
+ }
25424
+ }
25493
25425
  this._store.state.isSelecting = false;
25494
25426
  this._store.state.isResizeHandleSelected = false;
25495
25427
  this._store.state.isRotationHandleSelected = false;
@@ -25652,6 +25584,11 @@ class KritzelCore {
25652
25584
  // During selection phase (no group yet), hide UI for KritzelLine objects
25653
25585
  return !(object instanceof schema_constants.KritzelLine);
25654
25586
  }
25587
+ // Once a local selection group exists, only the selection-group object
25588
+ // should render group UI (children remain selected but don't render handles).
25589
+ if (!(object instanceof schema_constants.KritzelSelectionGroup) || object.id !== selectionGroup.id) {
25590
+ return false;
25591
+ }
25655
25592
  // Show UI if selection contains more than one object
25656
25593
  if (selectionGroup.objects.length > 1) {
25657
25594
  return true;
@@ -25937,6 +25874,9 @@ class KritzelSvgExportHelper {
25937
25874
  if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelImage')) {
25938
25875
  return this.imageToSvg(object, imageDataUrls);
25939
25876
  }
25877
+ if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
25878
+ return this.customElementToSvg(object, rasterSafeForCanvas);
25879
+ }
25940
25880
  if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelGroup')) {
25941
25881
  return this.groupToSvg(object, theme, imageDataUrls, rasterSafeForCanvas);
25942
25882
  }
@@ -26103,6 +26043,38 @@ class KritzelSvgExportHelper {
26103
26043
  return `<g transform="${transform}"${opacity}>
26104
26044
  <image href="${href}" x="0" y="0" width="${width}" height="${height}" preserveAspectRatio="xMidYMid meet"/>
26105
26045
  </g>`;
26046
+ }
26047
+ /**
26048
+ * Converts a KritzelCustomElement to SVG.
26049
+ * SVG mode: embeds HTML in foreignObject (best-effort).
26050
+ * PNG/raster mode: renders placeholder rect with warning.
26051
+ * @param obj - The custom element object
26052
+ * @param rasterSafeForCanvas - If true, render placeholder (for PNG export)
26053
+ * @returns SVG element string
26054
+ */
26055
+ static customElementToSvg(obj, rasterSafeForCanvas = false) {
26056
+ const transform = this.buildTransform(obj);
26057
+ const opacity = obj.opacity !== 1 ? ` opacity="${obj.opacity}"` : '';
26058
+ const scale = this.getObjectScale(obj);
26059
+ const width = obj.totalWidth / scale;
26060
+ const height = obj.totalHeight / scale;
26061
+ if (rasterSafeForCanvas) {
26062
+ // PNG export: render placeholder rect (foreignObject doesn't rasterize to PNG well)
26063
+ console.warn('[KritzelCustomElement SVG Export] Custom elements do not serialize well to PNG. Use SVG export or screenshot instead.');
26064
+ return `<g transform="${transform}"${opacity}>
26065
+ <rect x="0" y="0" width="${width}" height="${height}" fill="#f0f0f0" stroke="#999" stroke-width="1" />
26066
+ <text x="${width / 2}" y="${height / 2}" text-anchor="middle" dominant-baseline="middle" font-size="12" fill="#999">[Custom Element]</text>
26067
+ </g>`;
26068
+ }
26069
+ // SVG export: use foreignObject to embed HTML
26070
+ const html = obj.element?.outerHTML || '<div style="border:1px dashed #ccc">Empty</div>';
26071
+ return `<g transform="${transform}"${opacity}>
26072
+ <foreignObject x="0" y="0" width="${width}" height="${height}">
26073
+ <div xmlns="http://www.w3.org/1999/xhtml" style="width:100%;height:100%;overflow:hidden;">
26074
+ ${html}
26075
+ </div>
26076
+ </foreignObject>
26077
+ </g>`;
26106
26078
  }
26107
26079
  /**
26108
26080
  * Converts a KritzelGroup to SVG by recursively converting children.
@@ -27174,6 +27146,120 @@ async function toPng(node, options = {}) {
27174
27146
  return canvas.toDataURL();
27175
27147
  }
27176
27148
 
27149
+ class KritzelWheelHelper {
27150
+ static shouldKeepWheelInCustomElement(ev, customElement) {
27151
+ // Keep Ctrl+wheel available for viewport zoom gestures.
27152
+ if (ev.ctrlKey || customElement.isSelected !== true) {
27153
+ return false;
27154
+ }
27155
+ const path = typeof ev.composedPath === 'function' ? ev.composedPath() : [];
27156
+ const scrollContainer = this.getScrollableContainerFromEventPath(path, customElement);
27157
+ // No scroll container found inside the custom element: let the wheel
27158
+ // event pass through so the viewport can continue handling it.
27159
+ if (!scrollContainer) {
27160
+ return false;
27161
+ }
27162
+ return this.canElementConsumeWheel(scrollContainer, ev.deltaX, ev.deltaY);
27163
+ }
27164
+ static getScrollableContainerFromEventPath(path, customElement) {
27165
+ const potentialRoots = [];
27166
+ if (customElement.element instanceof HTMLElement) {
27167
+ potentialRoots.push(customElement.element);
27168
+ }
27169
+ if (customElement.elementRef instanceof HTMLElement) {
27170
+ potentialRoots.push(customElement.elementRef);
27171
+ }
27172
+ const legacyElementRef = customElement.elmeentRef;
27173
+ if (legacyElementRef instanceof HTMLElement) {
27174
+ potentialRoots.push(legacyElementRef);
27175
+ }
27176
+ for (const root of potentialRoots) {
27177
+ const rootIndex = path.findIndex(entry => entry === root);
27178
+ if (rootIndex === -1) {
27179
+ continue;
27180
+ }
27181
+ for (const entry of path.slice(0, rootIndex + 1)) {
27182
+ if (!(entry instanceof HTMLElement)) {
27183
+ continue;
27184
+ }
27185
+ if (this.isScrollableElement(entry)) {
27186
+ return entry;
27187
+ }
27188
+ }
27189
+ }
27190
+ // Fallback for synthetic/custom wheel events where composedPath is absent
27191
+ // or does not include the custom-element subtree.
27192
+ for (const root of potentialRoots) {
27193
+ if (this.isScrollableElement(root)) {
27194
+ return root;
27195
+ }
27196
+ const firstChild = root.firstElementChild;
27197
+ if (firstChild instanceof HTMLElement && this.isScrollableElement(firstChild)) {
27198
+ return firstChild;
27199
+ }
27200
+ }
27201
+ return null;
27202
+ }
27203
+ static isScrollableElement(element) {
27204
+ const canScrollVertically = element.scrollHeight - element.clientHeight > 0;
27205
+ const canScrollHorizontally = element.scrollWidth - element.clientWidth > 0;
27206
+ if (!canScrollVertically && !canScrollHorizontally) {
27207
+ return false;
27208
+ }
27209
+ const computedStyle = window.getComputedStyle(element);
27210
+ const overflowX = computedStyle.overflowX;
27211
+ const overflowY = computedStyle.overflowY;
27212
+ const allowsVerticalScroll = overflowY !== 'visible' && overflowY !== 'clip';
27213
+ const allowsHorizontalScroll = overflowX !== 'visible' && overflowX !== 'clip';
27214
+ return (canScrollVertically && allowsVerticalScroll) || (canScrollHorizontally && allowsHorizontalScroll);
27215
+ }
27216
+ static canElementConsumeWheel(element, deltaX, deltaY) {
27217
+ const canConsumeVertical = this.canElementConsumeVerticalWheel(element, deltaY);
27218
+ const canConsumeHorizontal = this.canElementConsumeHorizontalWheel(element, deltaX);
27219
+ // Keep wheel events inside the custom element whenever at least one
27220
+ // requested axis can still be consumed by its scroll container.
27221
+ return canConsumeVertical || canConsumeHorizontal;
27222
+ }
27223
+ static canElementConsumeVerticalWheel(element, deltaY) {
27224
+ if (deltaY === 0) {
27225
+ return false;
27226
+ }
27227
+ const maxScrollTop = Math.max(0, element.scrollHeight - element.clientHeight);
27228
+ if (maxScrollTop === 0) {
27229
+ return false;
27230
+ }
27231
+ const epsilon = 1;
27232
+ const scrollingDown = deltaY > 0;
27233
+ const scrollingUp = deltaY < 0;
27234
+ if (scrollingDown) {
27235
+ return element.scrollTop < maxScrollTop - epsilon;
27236
+ }
27237
+ if (scrollingUp) {
27238
+ return element.scrollTop > epsilon;
27239
+ }
27240
+ return false;
27241
+ }
27242
+ static canElementConsumeHorizontalWheel(element, deltaX) {
27243
+ if (deltaX === 0) {
27244
+ return false;
27245
+ }
27246
+ const maxScrollLeft = Math.max(0, element.scrollWidth - element.clientWidth);
27247
+ if (maxScrollLeft === 0) {
27248
+ return false;
27249
+ }
27250
+ const epsilon = 1;
27251
+ const scrollingRight = deltaX > 0;
27252
+ const scrollingLeft = deltaX < 0;
27253
+ if (scrollingRight) {
27254
+ return element.scrollLeft < maxScrollLeft - epsilon;
27255
+ }
27256
+ if (scrollingLeft) {
27257
+ return element.scrollLeft > epsilon;
27258
+ }
27259
+ return false;
27260
+ }
27261
+ }
27262
+
27177
27263
  const kritzelEngineCss = () => `:host{display:block;position:relative;height:100%;width:100%;overflow:hidden;background-color:var(--kritzel-engine-background-color, #ffffff)}:host,:host *{touch-action:none;user-select:none}.ProseMirror{outline:none}p,h1,h2,h3,h4,h5,h6,blockquote,pre{margin:0;padding:0}.workspace-loading-overlay{position:absolute;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;gap:10px;background-color:var(--kritzel-engine-loading-overlay-background, rgba(255, 255, 255, 0.6));color:var(--kritzel-engine-loading-overlay-color, #333);font-family:var(--kritzel-font-family, sans-serif);font-size:1.25rem;pointer-events:all;animation:workspace-loading-fade-in 200ms ease-out var(--kritzel-engine-loading-overlay-delay, 300ms) forwards;opacity:0}.workspace-loading-spinner{width:20px;height:20px;box-sizing:border-box;display:block;flex-shrink:0;border:2px solid var(--kritzel-engine-loading-overlay-spinner-color, #cccccc);border-top-color:var(--kritzel-engine-loading-overlay-spinner-active-color, #333333);border-radius:50%;animation:workspace-loading-spin 0.6s linear infinite}@keyframes workspace-loading-spin{to{transform:rotate(360deg)}}@keyframes workspace-loading-fade-in{to{opacity:1}}.debug-panel{position:absolute;pointer-events:none;top:0;right:0}.origin{position:relative;top:0;left:0;height:0;width:0;pointer-events:none;-webkit-transform-origin:top left;-moz-transform-origin:top left;transform-origin:top left;overflow:visible}.object{overflow:visible}.PlaygroundEditorTheme__quote{margin:0;margin-left:20px;margin-bottom:10px;font-size:15px;color:rgb(101, 103, 107);border-left-color:rgb(206, 208, 212);border-left-width:4px;border-left-style:solid;padding-left:16px}`;
27178
27264
 
27179
27265
  const KritzelEngine = class {
@@ -27362,6 +27448,7 @@ const KritzelEngine = class {
27362
27448
  onDebugInfoChange(newValue) {
27363
27449
  if (newValue) {
27364
27450
  this.core.store.state.debugInfo = newValue;
27451
+ this.core.assetResolver.setShowInfo(newValue.showAssetResolverInfo);
27365
27452
  }
27366
27453
  }
27367
27454
  /** When false, non-modified wheel events do not pan the viewport. */
@@ -27415,6 +27502,12 @@ const KritzelEngine = class {
27415
27502
  if (this.core.store.state.isContextMenuVisible) {
27416
27503
  this.hideContextMenu();
27417
27504
  }
27505
+ const hoveredObject = this.core.getObjectFromPointerEvent(ev);
27506
+ if (hoveredObject &&
27507
+ schema_constants.ObjectHelper.isClass(hoveredObject, 'KritzelCustomElement') &&
27508
+ KritzelWheelHelper.shouldKeepWheelInCustomElement(ev, hoveredObject)) {
27509
+ return;
27510
+ }
27418
27511
  if (ev.ctrlKey) {
27419
27512
  if (this.isZoomingEnabled) {
27420
27513
  this.viewport.handleWheel(ev);
@@ -27431,6 +27524,11 @@ const KritzelEngine = class {
27431
27524
  if (this.core.store.isDisabled) {
27432
27525
  return;
27433
27526
  }
27527
+ // Interactive custom-element content should receive native pointer events
27528
+ // whenever the object is marked interactive.
27529
+ if (this.isEmbeddedCustomElementInteraction(ev)) {
27530
+ return;
27531
+ }
27434
27532
  if (schema_constants.KritzelDevicesHelper.isTouchDevice()) {
27435
27533
  schema_constants.KritzelEventHelper.onLongPress(ev, (event) => this.longpress.emit(event));
27436
27534
  }
@@ -27447,6 +27545,10 @@ const KritzelEngine = class {
27447
27545
  if (this.core.store.isDisabled) {
27448
27546
  return;
27449
27547
  }
27548
+ // Ignore drag/move sequences when their pointerdown was intentionally bypassed.
27549
+ if (ev.buttons > 0 && !this.core.store.state.pointers.has(ev.pointerId)) {
27550
+ return;
27551
+ }
27450
27552
  if (this.core.store.state.pointers.has(ev.pointerId)) {
27451
27553
  this.core.store.state.pointers.set(ev.pointerId, ev);
27452
27554
  }
@@ -27495,6 +27597,56 @@ const KritzelEngine = class {
27495
27597
  this.viewport.handlePointerUp(ev);
27496
27598
  this.core.store.state?.activeTool?.handlePointerUp(ev);
27497
27599
  }
27600
+ isEmbeddedCustomElementInteraction(event) {
27601
+ const path = event.composedPath();
27602
+ const objectElement = path.find((entry) => {
27603
+ return entry instanceof HTMLElement && entry.classList.contains('object');
27604
+ });
27605
+ if (!objectElement?.id) {
27606
+ return false;
27607
+ }
27608
+ const object = this.core.store.objects.findById(objectElement.id);
27609
+ if (!object || !schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
27610
+ return false;
27611
+ }
27612
+ const isSelected = !!this.core.store.selectionGroup?.objects?.some(selectedObject => selectedObject.id === object.id);
27613
+ if (!isSelected) {
27614
+ return false;
27615
+ }
27616
+ if (object.isInteractive !== true) {
27617
+ return false;
27618
+ }
27619
+ const potentialRoots = [];
27620
+ if (object.element instanceof HTMLElement) {
27621
+ potentialRoots.push(object.element);
27622
+ }
27623
+ if (object.elementRef instanceof HTMLElement) {
27624
+ potentialRoots.push(object.elementRef);
27625
+ }
27626
+ if (potentialRoots.length === 0) {
27627
+ return false;
27628
+ }
27629
+ // Only bypass canvas tool handling when the pointer event came from an
27630
+ // actually interactive descendant inside the custom element subtree.
27631
+ return potentialRoots.some(root => this.isNativeInteractiveEmbeddedTarget(path, root));
27632
+ }
27633
+ isNativeInteractiveEmbeddedTarget(path, customElementRoot) {
27634
+ const interactiveSelector = 'input, button, textarea, select, a[href], [contenteditable="true"], [contenteditable=""]';
27635
+ const rootIndex = path.findIndex(entry => entry === customElementRoot);
27636
+ if (rootIndex === -1) {
27637
+ return false;
27638
+ }
27639
+ for (const entry of path.slice(0, rootIndex)) {
27640
+ if (!(entry instanceof HTMLElement)) {
27641
+ continue;
27642
+ }
27643
+ const interactiveEntry = entry;
27644
+ if (interactiveEntry.matches(interactiveSelector)) {
27645
+ return true;
27646
+ }
27647
+ }
27648
+ return false;
27649
+ }
27498
27650
  handlePointerLeave() {
27499
27651
  this.core.store.objects?.clearCursorPosition();
27500
27652
  }
@@ -27566,7 +27718,7 @@ const KritzelEngine = class {
27566
27718
  void this.core.clipboardManager.paste(undefined, undefined, ev);
27567
27719
  }
27568
27720
  /**
27569
- * Determines if the engine should handle a keyboard/clipboard shortcut event.
27721
+ * Determines if the engine should handle a keyboard/clipboard shortcut event.
27570
27722
  * Returns true only if:
27571
27723
  * - The event originates from within the engine component, OR
27572
27724
  * - The event target is not an interactive element that needs keyboard input
@@ -28780,6 +28932,8 @@ const KritzelEngine = class {
28780
28932
  _syncConfigRevision = 0;
28781
28933
  _assetStorageConfigRevision = 0;
28782
28934
  _isWorkspaceLoading = false;
28935
+ _hasLoggedUnlicensedWelcomeBanner = false;
28936
+ _previousVisibleCustomElements = new Set();
28783
28937
  _defaultUndoState = {
28784
28938
  canUndo: false,
28785
28939
  canRedo: false,
@@ -28843,6 +28997,14 @@ const KritzelEngine = class {
28843
28997
  }
28844
28998
  disconnectedCallback() {
28845
28999
  this.throttledPointerMoveMulti.cancel();
29000
+ // Unmount all remaining custom elements
29001
+ if (this.core.store.allObjects) {
29002
+ this.core.store.allObjects.forEach(object => {
29003
+ if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
29004
+ object.unmount();
29005
+ }
29006
+ });
29007
+ }
28846
29008
  // Clear cursor position in awareness before destroying
28847
29009
  this.core.store.objects?.clearCursorPosition();
28848
29010
  // Clean up current workspace's ObjectMap
@@ -28899,6 +29061,7 @@ const KritzelEngine = class {
28899
29061
  await this.initializeSyncAndWorkspace();
28900
29062
  if (this.core.store.state.isReady === false) {
28901
29063
  this.core.store.state.isReady = true;
29064
+ this.logUnlicensedWelcomeBanner();
28902
29065
  this.isEngineReady.emit(this.core.store.state);
28903
29066
  }
28904
29067
  }, 50);
@@ -28938,6 +29101,14 @@ const KritzelEngine = class {
28938
29101
  }
28939
29102
  this._isAssetStorageInitialized = true;
28940
29103
  }
29104
+ logUnlicensedWelcomeBanner() {
29105
+ if (this.core.licenseManager.isLicensed || this._hasLoggedUnlicensedWelcomeBanner) {
29106
+ return;
29107
+ }
29108
+ this._hasLoggedUnlicensedWelcomeBanner = true;
29109
+ console.info('%c👋 Welcome to Kritzel', 'color: #007AFF; font-weight: 700;');
29110
+ console.info(`This version of Kritzel is not licensed. Get a license at: https://kritzel.io`);
29111
+ }
28941
29112
  async doInitializeSyncAndWorkspace() {
28942
29113
  // Capture sync config revision to detect prop updates that happen while
28943
29114
  // initialization is in-flight.
@@ -29141,8 +29312,7 @@ const KritzelEngine = class {
29141
29312
  if (normalizedScale <= this._objectDistanceFadeFarScale) {
29142
29313
  return this._objectDistanceFadeMinOpacity;
29143
29314
  }
29144
- const progress = (this._objectDistanceFadeNearScale - normalizedScale) /
29145
- (this._objectDistanceFadeNearScale - this._objectDistanceFadeFarScale);
29315
+ const progress = (this._objectDistanceFadeNearScale - normalizedScale) / (this._objectDistanceFadeNearScale - this._objectDistanceFadeFarScale);
29146
29316
  const smooth = progress * progress * (3 - 2 * progress);
29147
29317
  return 1 - smooth * (1 - this._objectDistanceFadeMinOpacity);
29148
29318
  }
@@ -29153,8 +29323,7 @@ const KritzelEngine = class {
29153
29323
  if (normalizedScale >= this._objectDistanceFadeCloseMaxScale) {
29154
29324
  return this._objectDistanceFadeCloseMinOpacity;
29155
29325
  }
29156
- const progress = (normalizedScale - this._objectDistanceFadeCloseStartScale) /
29157
- (this._objectDistanceFadeCloseMaxScale - this._objectDistanceFadeCloseStartScale);
29326
+ const progress = (normalizedScale - this._objectDistanceFadeCloseStartScale) / (this._objectDistanceFadeCloseMaxScale - this._objectDistanceFadeCloseStartScale);
29158
29327
  const smooth = progress * progress * (3 - 2 * progress);
29159
29328
  return 1 - smooth * (1 - this._objectDistanceFadeCloseMinOpacity);
29160
29329
  }
@@ -29164,13 +29333,18 @@ const KritzelEngine = class {
29164
29333
  }
29165
29334
  getObjectEffectiveOpacity(object, viewportScale) {
29166
29335
  const safeObjectOpacity = Number.isFinite(object.opacity) ? object.opacity : 1;
29167
- const distanceFadeMultiplier = this.shouldApplyDistanceFade(object)
29168
- ? this.getObjectDistanceFadeMultiplier(object.scale, viewportScale)
29169
- : 1;
29336
+ const distanceFadeMultiplier = this.shouldApplyDistanceFade(object) ? this.getObjectDistanceFadeMultiplier(object.scale, viewportScale) : 1;
29170
29337
  const multiplied = (object.markedForRemoval ? 0.5 : 1) * safeObjectOpacity * distanceFadeMultiplier;
29171
29338
  const overridden = ((object.markedForRemoval ? 0.5 : safeObjectOpacity) * distanceFadeMultiplier).toString();
29172
29339
  return { multiplied, overridden };
29173
29340
  }
29341
+ getSelectionHandleCapabilities(object) {
29342
+ const selectedObjects = schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup') ? object.objects : [object];
29343
+ return {
29344
+ canRenderResizeHandles: selectedObjects.every(selectedObject => selectedObject.isResizable !== false),
29345
+ canRenderRotationHandle: selectedObjects.every(selectedObject => selectedObject.isRotatable !== false),
29346
+ };
29347
+ }
29174
29348
  render() {
29175
29349
  if (!this.viewport) {
29176
29350
  return (index.h(index.Host, null, this.core.store.state.isLoading && (index.h("div", { class: "workspace-loading-overlay" }, index.h("span", { class: "workspace-loading-spinner" }), this.core.localizationManager.translate('engine.loading')))));
@@ -29184,6 +29358,45 @@ const KritzelEngine = class {
29184
29358
  const viewportCenterX = this.core.store.state.viewportWidth / 2 + this.core.store.state.translateX;
29185
29359
  const viewportCenterY = this.core.store.state.viewportHeight / 2 + this.core.store.state.translateY;
29186
29360
  const visibleObjects = this.core.store.objectsInViewport;
29361
+ const getObjectWrapperPointerEvents = (object) => {
29362
+ if (this.core.store.state.isScaling) {
29363
+ return 'none';
29364
+ }
29365
+ if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup')) {
29366
+ return 'none';
29367
+ }
29368
+ return 'auto';
29369
+ };
29370
+ const getSelectionGroupOverlayPointerEvents = (object) => {
29371
+ if (object.markedForRemoval) {
29372
+ return 'none';
29373
+ }
29374
+ if (!schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup')) {
29375
+ return 'auto';
29376
+ }
29377
+ if (object.objects.length === 1 && schema_constants.KritzelClassHelper.isInstanceOf(object.objects[0], 'KritzelCustomElement')) {
29378
+ return 'none';
29379
+ }
29380
+ return 'auto';
29381
+ };
29382
+ // Track visible custom elements and unmount culled ones
29383
+ const currentVisibleCustomElements = new Set();
29384
+ visibleObjects.forEach(object => {
29385
+ if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
29386
+ currentVisibleCustomElements.add(object.id);
29387
+ }
29388
+ });
29389
+ // Unmount custom elements that left the viewport
29390
+ this._previousVisibleCustomElements.forEach(id => {
29391
+ if (!currentVisibleCustomElements.has(id)) {
29392
+ const object = this.core.store.objects.findById(id);
29393
+ if (object && schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
29394
+ object.unmount();
29395
+ }
29396
+ }
29397
+ });
29398
+ // Update tracking set for next render
29399
+ this._previousVisibleCustomElements = currentVisibleCustomElements;
29187
29400
  this.core.cursorManager.applyCursor();
29188
29401
  if (this.core.store.state.hasViewportChanged) {
29189
29402
  this.viewportChange.emit(this.viewport.getViewport());
@@ -29200,7 +29413,7 @@ const KritzelEngine = class {
29200
29413
  transformOrigin: 'top left',
29201
29414
  position: 'absolute',
29202
29415
  zIndex: object.zIndex.toString(),
29203
- pointerEvents: this.core.store.state.isScaling ? 'none' : 'auto',
29416
+ pointerEvents: getObjectWrapperPointerEvents(object),
29204
29417
  } }, schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelPath') && (index.h("svg", { ref: el => el && object.mount(el), xmlns: "http://www.w3.org/2000/svg", style: {
29205
29418
  height: object?.totalHeight + 'px',
29206
29419
  width: object?.totalWidth + 'px',
@@ -29289,7 +29502,7 @@ const KritzelEngine = class {
29289
29502
  transform: object.rotationDegrees !== 0 ? `rotate(${object.rotationDegrees}deg)` : undefined,
29290
29503
  transformOrigin: object.rotationDegrees !== 0 ? `${object.totalWidth / 2}px ${object.totalHeight / 2}px` : undefined,
29291
29504
  opacity: object.markedForRemoval ? '0.5' : object.opacity.toString(),
29292
- pointerEvents: object.markedForRemoval ? 'none' : 'auto',
29505
+ pointerEvents: getSelectionGroupOverlayPointerEvents(object),
29293
29506
  } })), schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionBox') && (index.h("div", { ref: el => el && object.mount(el), style: {
29294
29507
  position: 'absolute',
29295
29508
  left: '0',
@@ -29324,60 +29537,65 @@ const KritzelEngine = class {
29324
29537
  transform: `scale(${object.scaleFactor})`,
29325
29538
  backgroundColor: schema_constants.KritzelColorHelper.resolveThemeColor(object.backgroundColor, currentTheme),
29326
29539
  overflow: 'visible',
29327
- } }))), schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelShape') && (() => {
29328
- const fillResolved = schema_constants.KritzelColorHelper.resolveThemeColor(object.fillColor, currentTheme);
29329
- const strokeResolved = schema_constants.KritzelColorHelper.resolveThemeColor(object.strokeColor, currentTheme);
29330
- const isFilled = !!fillResolved && fillResolved !== 'transparent';
29331
- const strokeMatchesFill = isFilled && strokeResolved === fillResolved;
29332
- return (index.h("div", { ref: el => el && object.mount(el), onPointerDown: e => object.handlePointerDown(e), onPointerMove: e => object.handlePointerMove(e), onPointerUp: e => object.handlePointerUp(e), style: {
29333
- position: 'absolute',
29334
- left: '0',
29335
- top: '0',
29336
- width: object.totalWidth + 'px',
29337
- height: object.totalHeight + 'px',
29338
- transform: object.rotationDegrees !== 0 ? `rotate(${object.rotationDegrees}deg)` : undefined,
29339
- transformOrigin: object.rotationDegrees !== 0 ? `${object.totalWidth / 2}px ${object.totalHeight / 2}px` : undefined,
29340
- pointerEvents: object.markedForRemoval ? 'none' : 'auto',
29341
- overflow: 'visible',
29342
- } }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", style: {
29343
- position: 'absolute',
29344
- top: '0',
29345
- left: '0',
29346
- width: '100%',
29347
- height: '100%',
29348
- overflow: 'visible',
29349
- pointerEvents: 'none',
29350
- }, viewBox: object.viewBox, preserveAspectRatio: "none" }, index.h("path", { d: object.getSvgPath(), fill: schema_constants.KritzelColorHelper.applyOpacity(object.fillColor, effectiveOpacity, currentTheme), stroke: strokeMatchesFill ? 'none' : schema_constants.KritzelColorHelper.applyOpacity(object.strokeColor, effectiveOpacity, currentTheme), "stroke-width": strokeMatchesFill ? 0 : object.strokeWidth })), index.h("div", { ref: el => el && object.mountTextEditor(el), style: {
29351
- position: 'absolute',
29352
- top: '0',
29353
- left: '0',
29354
- width: '100%',
29355
- height: '100%',
29356
- display: 'flex',
29357
- alignItems: 'center',
29358
- justifyContent: 'center',
29359
- textAlign: 'center',
29360
- overflow: 'hidden',
29361
- pointerEvents: object.isEditing ? 'auto' : 'none',
29362
- } })));
29363
- })(), this.core.store.state.debugInfo.showObjectInfo && object.isDebugInfoVisible && (index.h("div", { style: {
29540
+ } }))), schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelShape') &&
29541
+ (() => {
29542
+ const fillResolved = schema_constants.KritzelColorHelper.resolveThemeColor(object.fillColor, currentTheme);
29543
+ const strokeResolved = schema_constants.KritzelColorHelper.resolveThemeColor(object.strokeColor, currentTheme);
29544
+ const isFilled = !!fillResolved && fillResolved !== 'transparent';
29545
+ const strokeMatchesFill = isFilled && strokeResolved === fillResolved;
29546
+ return (index.h("div", { ref: el => el && object.mount(el), onPointerDown: e => object.handlePointerDown(e), onPointerMove: e => object.handlePointerMove(e), onPointerUp: e => object.handlePointerUp(e), style: {
29547
+ position: 'absolute',
29548
+ left: '0',
29549
+ top: '0',
29550
+ width: object.totalWidth + 'px',
29551
+ height: object.totalHeight + 'px',
29552
+ transform: object.rotationDegrees !== 0 ? `rotate(${object.rotationDegrees}deg)` : undefined,
29553
+ transformOrigin: object.rotationDegrees !== 0 ? `${object.totalWidth / 2}px ${object.totalHeight / 2}px` : undefined,
29554
+ pointerEvents: object.markedForRemoval ? 'none' : 'auto',
29555
+ overflow: 'visible',
29556
+ } }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", style: {
29557
+ position: 'absolute',
29558
+ top: '0',
29559
+ left: '0',
29560
+ width: '100%',
29561
+ height: '100%',
29562
+ overflow: 'visible',
29563
+ pointerEvents: 'none',
29564
+ }, viewBox: object.viewBox, preserveAspectRatio: "none" }, index.h("path", { d: object.getSvgPath(), fill: schema_constants.KritzelColorHelper.applyOpacity(object.fillColor, effectiveOpacity, currentTheme), stroke: strokeMatchesFill ? 'none' : schema_constants.KritzelColorHelper.applyOpacity(object.strokeColor, effectiveOpacity, currentTheme), "stroke-width": strokeMatchesFill ? 0 : object.strokeWidth })), index.h("div", { ref: el => el && object.mountTextEditor(el), style: {
29565
+ position: 'absolute',
29566
+ top: '0',
29567
+ left: '0',
29568
+ width: '100%',
29569
+ height: '100%',
29570
+ display: 'flex',
29571
+ alignItems: 'center',
29572
+ justifyContent: 'center',
29573
+ textAlign: 'center',
29574
+ overflow: 'hidden',
29575
+ pointerEvents: object.isEditing ? 'auto' : 'none',
29576
+ } })));
29577
+ })(), this.core.store.state.debugInfo.showObjectInfo && (index.h("div", { style: {
29364
29578
  pointerEvents: 'none',
29365
29579
  position: 'absolute',
29366
29580
  left: `${object.totalWidth}px`,
29367
29581
  top: '0',
29368
29582
  zIndex: (object.zIndex + 2).toString(),
29369
- } }, index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "Id: ", object.id), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "userId: ", object.userId), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "width: ", object.width), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "height: ", object.height), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "translateX: ", object.translateX), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "translateY: ", object.translateY), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "scale: ", object.scale), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "rotationDegrees: ", object.rotationDegrees), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "zIndex: ", object.zIndex), schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelImage') && (index.h("div", null, index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "assetId: ", object.assetId), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "mimeType: ", object.mimeType))))), (this.core.displaySelectionGroupUI(object) || this.core.displaySelectionLineUI(object)) &&
29583
+ } }, index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "Id: ", object.id), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "userId: ", object.userId), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "width: ", object.width), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "height: ", object.height), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "translateX: ", object.translateX), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "translateY: ", object.translateY), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "scale: ", object.scale), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "rotationDegrees: ", object.rotationDegrees), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "zIndex: ", object.zIndex), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "isSelected: ", object.isSelected ? 'true' : 'false'), schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement') && (index.h("div", null, index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "isInteractive: ", object.isInteractive ? 'true' : 'false'))), schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelImage') && (index.h("div", null, index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "assetId: ", object.assetId), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "mimeType: ", object.mimeType))))), (this.core.displaySelectionGroupUI(object) || this.core.displaySelectionLineUI(object)) &&
29370
29584
  (() => {
29371
29585
  const isSelectionGroup = schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup');
29372
29586
  const localClientId = this.core.store.objects?.localClientId;
29373
29587
  const awarenessStates = this.core.store.objects?.awareness?.getStates();
29374
- const isRemoteSelection = isSelectionGroup && (
29375
- // Different user
29376
- (object.userId != null && this.core.user?.id != null && object.userId !== this.core.user.id) ||
29377
- // Same user but different client that is still actively connected (e.g. same account on another device)
29378
- (object.userId != null && this.core.user?.id != null && object.userId === this.core.user.id &&
29379
- object.clientId != null && localClientId != null && object.clientId !== localClientId &&
29380
- awarenessStates?.has(object.clientId) === true));
29588
+ const isRemoteSelection = isSelectionGroup &&
29589
+ // Different user
29590
+ ((object.userId != null && this.core.user?.id != null && object.userId !== this.core.user.id) ||
29591
+ // Same user but different client that is still actively connected (e.g. same account on another device)
29592
+ (object.userId != null &&
29593
+ this.core.user?.id != null &&
29594
+ object.userId === this.core.user.id &&
29595
+ object.clientId != null &&
29596
+ localClientId != null &&
29597
+ object.clientId !== localClientId &&
29598
+ awarenessStates?.has(object.clientId) === true));
29381
29599
  let remoteUserColor;
29382
29600
  if (isRemoteSelection) {
29383
29601
  if (awarenessStates) {
@@ -29396,10 +29614,10 @@ const KritzelEngine = class {
29396
29614
  }
29397
29615
  }
29398
29616
  }
29399
- const selectionBorderColor = remoteUserColor
29400
- ?? (schema_constants.KritzelColorHelper.resolveThemeColor(object.borderColor, currentTheme) || 'var(--kritzel-selection-border-color, #007AFF)');
29401
- const selectionHandleStrokeColor = remoteUserColor
29402
- ?? 'var(--kritzel-selection-handle-stroke-color, #007AFF)';
29617
+ const selectionBorderColor = remoteUserColor ?? (schema_constants.KritzelColorHelper.resolveThemeColor(object.borderColor, currentTheme) || 'var(--kritzel-selection-border-color, #007AFF)');
29618
+ const selectionHandleStrokeColor = remoteUserColor ?? 'var(--kritzel-selection-handle-stroke-color, #007AFF)';
29619
+ const handleScale = object.scale ?? 1;
29620
+ const { canRenderResizeHandles, canRenderRotationHandle } = this.getSelectionHandleCapabilities(object);
29403
29621
  return (index.h("svg", { xmlns: "http://www.w3.org/2000/svg", style: {
29404
29622
  zIndex: (object.zIndex + 1).toString(),
29405
29623
  height: object?.totalHeight.toString(),
@@ -29443,50 +29661,50 @@ const KritzelEngine = class {
29443
29661
  strokeWidth: `calc(var(--kritzel-selection-border-width, 2px) * ${object.scale} / ${this.core.store.state?.scale})`,
29444
29662
  strokeLinecap: 'square',
29445
29663
  } })));
29446
- })(), !this.isSelecting && !isRemoteSelection && (index.h("g", { class: "selection-group-handles", style: { pointerEvents: 'auto' } }, index.h("rect", { class: "resize-handle top-left", x: `${(-(baseHandleSize - 1) * object.scale) / this.core.store.state?.scale}`, y: `${(-(baseHandleSize - 1) * object.scale) / this.core.store.state?.scale}`, width: `${((baseHandleSize - 1) * 2 * object.scale) / this.core.store.state?.scale}`, height: `${((baseHandleSize - 1) * 2 * object.scale) / this.core.store.state?.scale}`, style: {
29664
+ })(), !this.isSelecting && !isRemoteSelection && (index.h("g", { class: "selection-group-handles", style: { pointerEvents: 'auto' } }, canRenderResizeHandles && (index.h("g", null, index.h("rect", { class: "resize-handle top-left", x: `${(-(baseHandleSize - 1) * handleScale) / this.core.store.state?.scale}`, y: `${(-(baseHandleSize - 1) * handleScale) / this.core.store.state?.scale}`, width: `${((baseHandleSize - 1) * 2 * handleScale) / this.core.store.state?.scale}`, height: `${((baseHandleSize - 1) * 2 * handleScale) / this.core.store.state?.scale}`, style: {
29447
29665
  fill: 'var(--kritzel-selection-handle-color, #ffffff)',
29448
29666
  stroke: selectionHandleStrokeColor,
29449
- strokeWidth: `calc(2px * ${object.scale} / ${this.core.store.state?.scale})`,
29667
+ strokeWidth: `calc(2px * ${handleScale} / ${this.core.store.state?.scale})`,
29450
29668
  paintOrder: 'fill',
29451
- } }), index.h("rect", { class: "resize-handle-overlay top-left", x: `${(-baseHandleTouchSize * object.scale) / this.core.store.state?.scale}`, y: `${(-baseHandleTouchSize * object.scale) / this.core.store.state?.scale}`, width: `${(baseHandleTouchSize * 2 * object.scale) / this.core.store.state?.scale}`, height: `${(baseHandleTouchSize * 2 * object.scale) / this.core.store.state?.scale}`, style: {
29669
+ } }), index.h("rect", { class: "resize-handle-overlay top-left", x: `${(-baseHandleTouchSize * handleScale) / this.core.store.state?.scale}`, y: `${(-baseHandleTouchSize * handleScale) / this.core.store.state?.scale}`, width: `${(baseHandleTouchSize * 2 * handleScale) / this.core.store.state?.scale}`, height: `${(baseHandleTouchSize * 2 * handleScale) / this.core.store.state?.scale}`, style: {
29452
29670
  fill: 'transparent',
29453
29671
  paintOrder: 'fill',
29454
- } }), index.h("rect", { class: "resize-handle top-right", x: `${object.totalWidth - ((baseHandleSize - 1) * object.scale) / this.core.store.state?.scale}`, y: `${(-(baseHandleSize - 1) * object.scale) / this.core.store.state?.scale}`, width: `${((baseHandleSize - 1) * 2 * object.scale) / this.core.store.state?.scale}`, height: `${((baseHandleSize - 1) * 2 * object.scale) / this.core.store.state?.scale}`, style: {
29672
+ } }), index.h("rect", { class: "resize-handle top-right", x: `${object.totalWidth - ((baseHandleSize - 1) * handleScale) / this.core.store.state?.scale}`, y: `${(-(baseHandleSize - 1) * handleScale) / this.core.store.state?.scale}`, width: `${((baseHandleSize - 1) * 2 * handleScale) / this.core.store.state?.scale}`, height: `${((baseHandleSize - 1) * 2 * handleScale) / this.core.store.state?.scale}`, style: {
29455
29673
  fill: 'var(--kritzel-selection-handle-color, #ffffff)',
29456
29674
  stroke: selectionHandleStrokeColor,
29457
- strokeWidth: `calc(2px * ${object.scale} / ${this.core.store.state?.scale})`,
29675
+ strokeWidth: `calc(2px * ${handleScale} / ${this.core.store.state?.scale})`,
29458
29676
  paintOrder: 'fill',
29459
- } }), index.h("rect", { class: "resize-handle-overlay top-right", x: `${object.totalWidth - (baseHandleTouchSize * object.scale) / this.core.store.state?.scale}`, y: `${(-baseHandleTouchSize * object.scale) / this.core.store.state?.scale}`, width: `${(baseHandleTouchSize * 2 * object.scale) / this.core.store.state?.scale}`, height: `${(baseHandleTouchSize * 2 * object.scale) / this.core.store.state?.scale}`, style: {
29677
+ } }), index.h("rect", { class: "resize-handle-overlay top-right", x: `${object.totalWidth - (baseHandleTouchSize * handleScale) / this.core.store.state?.scale}`, y: `${(-baseHandleTouchSize * handleScale) / this.core.store.state?.scale}`, width: `${(baseHandleTouchSize * 2 * handleScale) / this.core.store.state?.scale}`, height: `${(baseHandleTouchSize * 2 * handleScale) / this.core.store.state?.scale}`, style: {
29460
29678
  fill: 'transparent',
29461
29679
  paintOrder: 'fill',
29462
- } }), index.h("rect", { class: "resize-handle bottom-left", x: `${(-(baseHandleSize - 1) * object.scale) / this.core.store.state?.scale}`, y: `${object.totalHeight - ((baseHandleSize - 1) * object.scale) / this.core.store.state?.scale}`, width: `${((baseHandleSize - 1) * 2 * object.scale) / this.core.store.state?.scale}`, height: `${((baseHandleSize - 1) * 2 * object.scale) / this.core.store.state?.scale}`, style: {
29680
+ } }), index.h("rect", { class: "resize-handle bottom-left", x: `${(-(baseHandleSize - 1) * handleScale) / this.core.store.state?.scale}`, y: `${object.totalHeight - ((baseHandleSize - 1) * handleScale) / this.core.store.state?.scale}`, width: `${((baseHandleSize - 1) * 2 * handleScale) / this.core.store.state?.scale}`, height: `${((baseHandleSize - 1) * 2 * handleScale) / this.core.store.state?.scale}`, style: {
29463
29681
  fill: 'var(--kritzel-selection-handle-color, #ffffff)',
29464
29682
  stroke: selectionHandleStrokeColor,
29465
- strokeWidth: `calc(2px * ${object.scale} / ${this.core.store.state?.scale})`,
29683
+ strokeWidth: `calc(2px * ${handleScale} / ${this.core.store.state?.scale})`,
29466
29684
  paintOrder: 'fill',
29467
- } }), index.h("rect", { class: "resize-handle-overlay bottom-left", x: `${(-baseHandleTouchSize * object.scale) / this.core.store.state?.scale}`, y: `${object.totalHeight - (baseHandleTouchSize * object.scale) / this.core.store.state?.scale}`, width: `${(baseHandleTouchSize * 2 * object.scale) / this.core.store.state?.scale}`, height: `${(baseHandleTouchSize * 2 * object.scale) / this.core.store.state?.scale}`, style: {
29685
+ } }), index.h("rect", { class: "resize-handle-overlay bottom-left", x: `${(-baseHandleTouchSize * handleScale) / this.core.store.state?.scale}`, y: `${object.totalHeight - (baseHandleTouchSize * handleScale) / this.core.store.state?.scale}`, width: `${(baseHandleTouchSize * 2 * handleScale) / this.core.store.state?.scale}`, height: `${(baseHandleTouchSize * 2 * handleScale) / this.core.store.state?.scale}`, style: {
29468
29686
  fill: 'transparent',
29469
29687
  paintOrder: 'fill',
29470
- } }), index.h("rect", { class: "resize-handle bottom-right", x: `${object.totalWidth - ((baseHandleSize - 1) * object.scale) / this.core.store.state?.scale}`, y: `${object.totalHeight - ((baseHandleSize - 1) * object.scale) / this.core.store.state?.scale}`, width: `${((baseHandleSize - 1) * 2 * object.scale) / this.core.store.state?.scale}`, height: `${((baseHandleSize - 1) * 2 * object.scale) / this.core.store.state?.scale}`, style: {
29688
+ } }), index.h("rect", { class: "resize-handle bottom-right", x: `${object.totalWidth - ((baseHandleSize - 1) * handleScale) / this.core.store.state?.scale}`, y: `${object.totalHeight - ((baseHandleSize - 1) * handleScale) / this.core.store.state?.scale}`, width: `${((baseHandleSize - 1) * 2 * handleScale) / this.core.store.state?.scale}`, height: `${((baseHandleSize - 1) * 2 * handleScale) / this.core.store.state?.scale}`, style: {
29471
29689
  fill: 'var(--kritzel-selection-handle-color, #ffffff)',
29472
29690
  stroke: selectionHandleStrokeColor,
29473
- strokeWidth: `calc(2px * ${object.scale} / ${this.core.store.state?.scale})`,
29691
+ strokeWidth: `calc(2px * ${handleScale} / ${this.core.store.state?.scale})`,
29474
29692
  paintOrder: 'fill',
29475
- } }), index.h("rect", { class: "resize-handle-overlay bottom-right", x: `${object.totalWidth - (baseHandleTouchSize * object.scale) / this.core.store.state?.scale}`, y: `${object.totalHeight - (baseHandleTouchSize * object.scale) / this.core.store.state?.scale}`, width: `${(baseHandleTouchSize * 2 * object.scale) / this.core.store.state?.scale}`, height: `${(baseHandleTouchSize * 2 * object.scale) / this.core.store.state?.scale}`, style: {
29693
+ } }), index.h("rect", { class: "resize-handle-overlay bottom-right", x: `${object.totalWidth - (baseHandleTouchSize * handleScale) / this.core.store.state?.scale}`, y: `${object.totalHeight - (baseHandleTouchSize * handleScale) / this.core.store.state?.scale}`, width: `${(baseHandleTouchSize * 2 * handleScale) / this.core.store.state?.scale}`, height: `${(baseHandleTouchSize * 2 * handleScale) / this.core.store.state?.scale}`, style: {
29476
29694
  fill: 'transparent',
29477
29695
  paintOrder: 'fill',
29478
- } }), index.h("line", { x1: object.totalWidth / 2, y1: "0", x2: object.totalWidth / 2, y2: -((15 * object.scale) / this.core.store.state?.scale), style: {
29696
+ } }))), canRenderRotationHandle && (index.h("g", null, index.h("line", { x1: object.totalWidth / 2, y1: "0", x2: object.totalWidth / 2, y2: -((15 * handleScale) / this.core.store.state?.scale), style: {
29479
29697
  stroke: selectionBorderColor,
29480
- strokeWidth: `calc(var(--kritzel-selection-border-width, 2px) * ${object.scale} / ${this.core.store.state?.scale})`,
29481
- } }), index.h("circle", { class: "rotation-handle", cx: object.totalWidth / 2, cy: -((15 * object.scale) / this.core.store.state?.scale), r: `${((baseHandleSize - 1) * object.scale) / this.core.store.state?.scale}`, style: {
29698
+ strokeWidth: `calc(var(--kritzel-selection-border-width, 2px) * ${handleScale} / ${this.core.store.state?.scale})`,
29699
+ } }), index.h("circle", { class: "rotation-handle", cx: object.totalWidth / 2, cy: -((15 * handleScale) / this.core.store.state?.scale), r: `${((baseHandleSize - 1) * handleScale) / this.core.store.state?.scale}`, style: {
29482
29700
  fill: 'var(--kritzel-selection-handle-color, #ffffff)',
29483
29701
  stroke: selectionHandleStrokeColor,
29484
- strokeWidth: `calc(2px * ${object.scale} / ${this.core.store.state?.scale})`,
29702
+ strokeWidth: `calc(2px * ${handleScale} / ${this.core.store.state?.scale})`,
29485
29703
  paintOrder: 'fill',
29486
- } }), index.h("circle", { class: "rotation-handle-overlay", cx: object.totalWidth / 2, cy: -((15 * object.scale) / this.core.store.state?.scale), r: `${(baseHandleTouchSize * object.scale) / this.core.store.state?.scale}`, style: {
29704
+ } }), index.h("circle", { class: "rotation-handle-overlay", cx: object.totalWidth / 2, cy: -((15 * handleScale) / this.core.store.state?.scale), r: `${(baseHandleTouchSize * handleScale) / this.core.store.state?.scale}`, style: {
29487
29705
  fill: 'transparent',
29488
29706
  paintOrder: 'fill',
29489
- } }))))), this.core.displaySelectionLineUI(object) && schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelLine') && (index.h("g", { class: "selection-line-ui", style: { pointerEvents: 'none' } }, index.h("g", { class: "selection-line-borders" }, index.h("path", { class: "selection-line-border", d: this.core.anchorManager.computeClippedLinePath(object, true), style: {
29707
+ } }))))))), this.core.displaySelectionLineUI(object) && schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelLine') && (index.h("g", { class: "selection-line-ui", style: { pointerEvents: 'none' } }, index.h("g", { class: "selection-line-borders" }, index.h("path", { class: "selection-line-border", d: this.core.anchorManager.computeClippedLinePath(object, true), style: {
29490
29708
  stroke: selectionBorderColor,
29491
29709
  strokeWidth: `calc(var(--kritzel-selection-border-width, 2px) * ${object.scale} / ${this.core.store.state?.scale})`,
29492
29710
  strokeLinecap: 'round',
@@ -29611,7 +29829,7 @@ const KritzelEngine = class {
29611
29829
  }, this.core.store.selectionGroup?.objects || []);
29612
29830
  }
29613
29831
  this.hideContextMenu();
29614
- }, onClose: () => this.hideContextMenu() })), this.core.store.objects?.hasAwareness && index.h("kritzel-awareness-cursors", { core: this.core }), this.core.store.state?.activeTool instanceof schema_constants.KritzelEraserTool && !this.core.store.state.isScaling && index.h("kritzel-cursor-trail", { core: this.core }), !this.core.licenseManager.isLicensed && (index.h("kritzel-watermark", { core: this.core, label: this.core.localizationManager.translate('watermark.poweredBy') }))));
29832
+ }, onClose: () => this.hideContextMenu() })), this.core.store.objects?.hasAwareness && index.h("kritzel-awareness-cursors", { core: this.core }), this.core.store.state?.activeTool instanceof schema_constants.KritzelEraserTool && !this.core.store.state.isScaling && index.h("kritzel-cursor-trail", { core: this.core }), !this.core.licenseManager.isLicensed && index.h("kritzel-watermark", { core: this.core, label: this.core.localizationManager.translate('watermark.poweredBy') })));
29615
29833
  }
29616
29834
  static get watchers() { return {
29617
29835
  "workspace": [{
@@ -30801,7 +31019,7 @@ const KritzelPortal = class {
30801
31019
  this.calculatePosition();
30802
31020
  }
30803
31021
  handleWindowScroll() {
30804
- const isInViewport = KritzelHTMLHelper.isElementInViewport(this.anchor);
31022
+ const isInViewport = schema_constants.KritzelHTMLHelper.isElementInViewport(this.anchor);
30805
31023
  if (!isInViewport) {
30806
31024
  this.anchor?.blur();
30807
31025
  this.closePortal();
@@ -30827,7 +31045,7 @@ const KritzelPortal = class {
30827
31045
  return activeEl;
30828
31046
  }
30829
31047
  trapFocus(event) {
30830
- const focusableElements = KritzelHTMLHelper.getFocusableElements(this.host);
31048
+ const focusableElements = schema_constants.KritzelHTMLHelper.getFocusableElements(this.host);
30831
31049
  if (focusableElements.length === 0)
30832
31050
  return;
30833
31051
  const firstFocusable = focusableElements[0];
@@ -30853,7 +31071,7 @@ const KritzelPortal = class {
30853
31071
  const firstAssigned = slotEl?.assignedElements({ flatten: true })[0];
30854
31072
  if (!firstAssigned)
30855
31073
  return null;
30856
- const focusable = KritzelHTMLHelper.getFocusableElements(firstAssigned);
31074
+ const focusable = schema_constants.KritzelHTMLHelper.getFocusableElements(firstAssigned);
30857
31075
  return focusable[0] ?? firstAssigned;
30858
31076
  }
30859
31077
  get lastAddedPortal() {
@@ -31009,7 +31227,7 @@ const KritzelPortal = class {
31009
31227
  * This file is auto-generated by the version bump scripts.
31010
31228
  * Do not modify manually.
31011
31229
  */
31012
- const KRITZEL_VERSION = '0.3.21';
31230
+ const KRITZEL_VERSION = '0.3.23';
31013
31231
 
31014
31232
  const kritzelSettingsCss = () => `:host{display:contents}kritzel-dialog{--kritzel-dialog-body-padding:0;--kritzel-dialog-width-large:800px;--kritzel-dialog-height-large:500px}.footer-button{padding:8px 16px;border-radius:6px;cursor:pointer;font-size:14px}.cancel-button{border:1px solid #ebebeb;background:#fff;color:inherit}.cancel-button:hover{background:#f5f5f5}.settings-content{padding:0}.settings-content h3{margin:0 0 16px 0;font-size:18px;font-weight:600;color:var(--kritzel-settings-content-heading-color, #333333)}.settings-content p{margin:0;font-size:14px;color:var(--kritzel-settings-content-text-color, #666666);line-height:1.5}.settings-group{display:flex;flex-direction:column;gap:24px}.settings-item{display:flex;flex-direction:column;gap:8px}.settings-row{display:flex;align-items:center;justify-content:space-between;gap:16px}.settings-label{font-size:14px;font-weight:600;color:var(--kritzel-settings-label-color, #333333);margin:0 0 4px 0}.settings-description{font-size:12px;color:var(--kritzel-settings-description-color, #888888);margin:0;line-height:1.4}.shortcuts-list{display:flex;flex-direction:column;gap:24px}.shortcuts-category{display:flex;flex-direction:column;gap:8px}.shortcuts-category-title{font-size:14px;font-weight:600;color:var(--kritzel-settings-label-color, #333333);margin:0 0 4px 0}.shortcuts-group{display:flex;flex-direction:column;gap:4px}.shortcut-item{display:flex;justify-content:space-between;align-items:center;padding:6px 8px;border-radius:4px;background:var(--kritzel-settings-shortcut-item-bg, rgba(0, 0, 0, 0.02))}.shortcut-label{font-size:14px;color:var(--kritzel-settings-content-text-color, #666666)}.shortcut-key{font-family:monospace;font-size:12px;padding:2px 8px;border-radius:4px;background:var(--kritzel-settings-shortcut-key-bg, #f0f0f0);color:var(--kritzel-settings-shortcut-key-color, #333333);border:1px solid var(--kritzel-settings-shortcut-key-border, #ddd)}`;
31015
31233
 
@@ -31025,6 +31243,7 @@ const DEFAULT_DEBUG_INFO = {
31025
31243
  showObjectInfo: false,
31026
31244
  showSyncProviderInfo: true,
31027
31245
  showMigrationInfo: true,
31246
+ showAssetResolverInfo: false,
31028
31247
  };
31029
31248
  const SETTINGS_CATEGORY_IDS = ['general', 'viewport', 'shortcuts', 'developer', 'about'];
31030
31249
  const KritzelSettings = class {
@@ -31233,7 +31452,7 @@ const KritzelSettings = class {
31233
31452
  }
31234
31453
  }
31235
31454
  render() {
31236
- return (index.h(index.Host, { key: '6f26bd23bf7bb51653f09b96bafef221c51c2bfa' }, index.h("kritzel-dialog", { key: '49ffda0a422ae1c2c9c8e6e608bce532e7d4099e', isOpen: this.isDialogOpen, dialogTitle: this.t('settings.dialogTitle', 'Settings'), size: "large", contained: true, onDialogClose: this.closeDialog }, index.h("kritzel-master-detail", { key: 'ab5dc7952e555cae6f841ef4de2ab50d7039279b', items: this.categories, selectedItemId: this.selectedCategoryId, onItemSelect: this.handleCategorySelect }, this.renderCategoryContent()))));
31455
+ return (index.h(index.Host, { key: '8b96261465efeabb2eb0cf724d29699de5e230a8' }, index.h("kritzel-dialog", { key: '273a9df290bb97d59000a97f91821e7bad3f3896', isOpen: this.isDialogOpen, dialogTitle: this.t('settings.dialogTitle', 'Settings'), size: "large", contained: true, onDialogClose: this.closeDialog }, index.h("kritzel-master-detail", { key: '64d17dfaa2a0baf2d4a5d00fe5fc65ad40c96ec6', items: this.categories, selectedItemId: this.selectedCategoryId, onItemSelect: this.handleCategorySelect }, this.renderCategoryContent()))));
31237
31456
  }
31238
31457
  static get watchers() { return {
31239
31458
  "settings": [{
@@ -31858,7 +32077,7 @@ const KritzelTooltip = class {
31858
32077
  }
31859
32078
  }
31860
32079
  async focusContent() {
31861
- const focusableElements = KritzelHTMLHelper.getFocusableElements(this.host);
32080
+ const focusableElements = schema_constants.KritzelHTMLHelper.getFocusableElements(this.host);
31862
32081
  if (focusableElements.length > 0) {
31863
32082
  focusableElements[0].focus();
31864
32083
  }