kritzel-stencil 0.3.22 → 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 +1 -1
  99. package/dist/components/p-BML28BJR.js +0 -1
  100. package/dist/components/p-CVV-HzFR.js +0 -1
  101. package/dist/components/p-DWhWFDcC.js +0 -9
  102. package/dist/components/p-Dd_EODkk.js +0 -1
  103. package/dist/stencil/p-D35cwCYd.js +0 -1
  104. package/dist/stencil/p-fcbf7965.entry.js +0 -9
@@ -1,5 +1,5 @@
1
1
  import { r as registerInstance, h, H as Host, c as createEvent, g as getElement } from './index-Dhio9uis.js';
2
- import { c as KritzelPath, e as KritzelLine, P as KritzelColorHelper, Q as KritzelDevicesHelper, p as KritzelSelectionTool, m as KritzelTextTool, R as KritzelMouseButton, T as DEFAULT_STROKE_SIZES, U as DEFAULT_COLOR_PALETTE, S as ShapeType, D as DEFAULT_BRUSH_CONFIG, i as KritzelBrushTool, k as KritzelEraserTool, x as DEFAULT_LINE_TOOL_CONFIG, j as KritzelLineTool, n as KritzelShapeTool, w as DEFAULT_TEXT_CONFIG, l as KritzelImageTool, G as KritzelAlignment, y as DEFAULT_ASSET_STORAGE_CONFIG, V as ObjectHelper, X as ThemeHelper, B as darkTheme, z as lightTheme, Y as KritzelSelectionGroup, Z as KritzelSelectionBox, _ as KritzelIconRegistry, $ as KritzelKeyboardHelper, a0 as KritzelBaseHandler, K as KritzelBaseObject, a1 as KritzelMathHelper, r as KritzelWorkspace, f as KritzelGroup, d as KritzelImage, g as KritzelShape, b as KritzelText, J as runMigrations, O as CURRENT_WORKSPACE_SCHEMA_VERSION, M as WORKSPACE_MIGRATIONS, N as CURRENT_APP_STATE_SCHEMA_VERSION, L as APP_STATE_MIGRATIONS, o as KritzelCursorHelper, a2 as KritzelClassHelper, a3 as imageCompression, s as KritzelAnchorManager, t as KritzelThemeManager, u as KritzelLocalizationManager, v as KritzelLicenseManager, q as KritzelAssetResolver, a4 as KritzelEventHelper, W as WORKSPACE_EXPORT_VERSION } from './schema.constants-D35cwCYd.js';
2
+ import { c as KritzelPath, e as KritzelLine, R as KritzelColorHelper, T as KritzelDevicesHelper, q as KritzelSelectionTool, n as KritzelTextTool, U as KritzelMouseButton, V as KritzelHTMLHelper, X as DEFAULT_STROKE_SIZES, Y as DEFAULT_COLOR_PALETTE, S as ShapeType, D as DEFAULT_BRUSH_CONFIG, j as KritzelBrushTool, l as KritzelEraserTool, z as DEFAULT_LINE_TOOL_CONFIG, k as KritzelLineTool, o as KritzelShapeTool, y as DEFAULT_TEXT_CONFIG, m as KritzelImageTool, L as KritzelAlignment, B as DEFAULT_ASSET_STORAGE_CONFIG, Z as ObjectHelper, _ as ThemeHelper, E as darkTheme, C as lightTheme, $ as KritzelSelectionGroup, a0 as KritzelSelectionBox, a1 as KritzelIconRegistry, a2 as KritzelKeyboardHelper, a3 as KritzelBaseHandler, s as KritzelWorkspace, f as KritzelGroup, h as KritzelCustomElement, d as KritzelImage, g as KritzelShape, b as KritzelText, M as runMigrations, Q as CURRENT_WORKSPACE_SCHEMA_VERSION, O as WORKSPACE_MIGRATIONS, a4 as KritzelClassHelper, P as CURRENT_APP_STATE_SCHEMA_VERSION, N as APP_STATE_MIGRATIONS, p as KritzelCursorHelper, a5 as imageCompression, t as KritzelAnchorManager, u as KritzelThemeManager, v as KritzelLocalizationManager, w as KritzelLicenseManager, r as KritzelAssetResolver, a6 as KritzelEventHelper, W as WORKSPACE_EXPORT_VERSION } from './schema.constants-DMPBDrZh.js';
3
3
  import * as Y from 'yjs';
4
4
  import 'y-indexeddb';
5
5
  import 'y-websocket';
@@ -1378,71 +1378,6 @@ const KritzelCursorTrail = class {
1378
1378
  };
1379
1379
  KritzelCursorTrail.style = kritzelCursorTrailCss();
1380
1380
 
1381
- class KritzelHTMLHelper {
1382
- static getNumericValueFromStyle(element, property) {
1383
- const value = window.getComputedStyle(element).getPropertyValue(property);
1384
- return parseFloat(value) || 0;
1385
- }
1386
- static getScrollableParent(element) {
1387
- if (!element) {
1388
- return window;
1389
- }
1390
- const parent = element.parentNode?.host ?? element.parentElement;
1391
- if (!parent || parent.tagName === 'BODY') {
1392
- return window;
1393
- }
1394
- const style = window.getComputedStyle(parent);
1395
- if (style.overflow === 'auto' || style.overflowY === 'auto' || style.overflow === 'scroll' || style.overflowY === 'scroll') {
1396
- return parent;
1397
- }
1398
- return this.getScrollableParent(parent);
1399
- }
1400
- static isElementInViewport(element) {
1401
- if (!element) {
1402
- return false;
1403
- }
1404
- const scrollableParent = this.getScrollableParent(element);
1405
- const rect = element.getBoundingClientRect();
1406
- if (scrollableParent === window) {
1407
- const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
1408
- const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
1409
- return rect.top >= 0 && rect.left >= 0 && rect.bottom <= viewportHeight && rect.right <= viewportWidth;
1410
- }
1411
- const parentRect = scrollableParent.getBoundingClientRect();
1412
- return rect.top >= parentRect.top && rect.left >= parentRect.left && rect.bottom <= parentRect.bottom && rect.right <= parentRect.right;
1413
- }
1414
- static getFocusableElements(root) {
1415
- if (!root)
1416
- return [];
1417
- const focusableElements = [];
1418
- const focusableSelector = 'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), details, [tabindex]:not([tabindex="-1"])';
1419
- const isVisible = (el) => {
1420
- const style = window.getComputedStyle(el);
1421
- return style.display !== 'none' && style.visibility !== 'hidden';
1422
- };
1423
- const find = (element) => {
1424
- // Check if the element itself is focusable
1425
- if (element instanceof HTMLElement && element.matches(focusableSelector) && !element.hasAttribute('disabled') && isVisible(element)) {
1426
- focusableElements.push(element);
1427
- }
1428
- // Traverse shadow DOM
1429
- if (element.shadowRoot) {
1430
- Array.from(element.shadowRoot.children).forEach(find);
1431
- }
1432
- // Traverse slotted content
1433
- if (element.tagName === 'SLOT') {
1434
- element.assignedElements({ flatten: true }).forEach(find);
1435
- }
1436
- // Traverse light DOM children if there is no shadow root
1437
- if (!element.shadowRoot) {
1438
- Array.from(element.children).forEach(find);
1439
- }
1440
- };
1441
- find(root);
1442
- return focusableElements;
1443
- }
1444
- }
1445
-
1446
1381
  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}`;
1447
1382
 
1448
1383
  const KritzelDialog = class {
@@ -2204,6 +2139,7 @@ const KritzelEditor = class {
2204
2139
  showObjectInfo: false,
2205
2140
  showSyncProviderInfo: true,
2206
2141
  showMigrationInfo: true,
2142
+ showAssetResolverInfo: false,
2207
2143
  };
2208
2144
  user;
2209
2145
  activeUsers;
@@ -3115,31 +3051,31 @@ const KritzelEditor = class {
3115
3051
  const isLoggedIn = this.isLoggedIn;
3116
3052
  const shouldShowCurrentUser = isLoggedIn;
3117
3053
  const shouldShowLoginButton = this.isReady && !!this.loginConfig && !isLoggedIn;
3118
- return (h(Host, { key: '07fe246b722ca64cfbdd8cc3474e6288c39d1128', style: {
3054
+ return (h(Host, { key: '56b172459e6d940229dc1d4f409157e403ad5f27', style: {
3119
3055
  opacity: this.isEditorVisible ? '1' : '0',
3120
3056
  visibility: this.isEditorVisible ? 'visible' : 'hidden',
3121
3057
  transition: 'opacity 0.2s ease-in-out, visibility 0.2s ease-in-out',
3122
- } }, h("div", { key: '2d0db1e78161edb061779b5875114e901f8e79f8', class: "top-left-buttons" }, 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) }), h("kritzel-back-to-content", { key: 'f2a0305bf73bd14d7522974b7956a289a0a44258', visible: this.isBackToContentButtonVisible, text: this.resolvedTerms['backToContent.label'] ?? 'Back to content', onBackToContent: () => this.backToContent() })), this.activeNotification && (h("div", { key: '918fe80193999ec072042500cd1eaf3670f5ed3c', class: "top-center-notification-layer", role: "presentation" }, h("div", { key: '0a5609b2b32f9f04bac03618803c14d9a891742c', class: { 'top-center-notification-container': true, 'is-dismissing': this.isNotificationDismissing }, role: "presentation" }, h("kritzel-notification-card", { key: 'eb1ff7aeaee3a237baf3ba2d96a10ba03a8bf7fb', notification: this.activeNotification, locale: this.locale, onDismiss: this.dismissNotification, onHoverChange: event => this.handleNotificationHoverChange(event) })))), h("kritzel-engine", { key: '8783a87101f308029482ced926a8d2163aae1428', ref: el => {
3058
+ } }, h("div", { key: '93a2195c14402f38416b2d377c684e2650418edb', class: "top-left-buttons" }, 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) }), h("kritzel-back-to-content", { key: '3b90b69a5b83a6e5593d4f752c4094a2a3af8c4d', visible: this.isBackToContentButtonVisible, text: this.resolvedTerms['backToContent.label'] ?? 'Back to content', onBackToContent: () => this.backToContent() })), this.activeNotification && (h("div", { key: '9c2ee1e10212145bf2f97a1a7f27746aa18cfc98', class: "top-center-notification-layer", role: "presentation" }, h("div", { key: 'a77b025329da64e1f1223a2f749aa2fc60e5c469', class: { 'top-center-notification-container': true, 'is-dismissing': this.isNotificationDismissing }, role: "presentation" }, h("kritzel-notification-card", { key: '9ced0bc5213ec8c3387915cd71272a1f56161405', notification: this.activeNotification, locale: this.locale, onDismiss: this.dismissNotification, onHoverChange: event => this.handleNotificationHoverChange(event) })))), h("kritzel-engine", { key: '884b7f088e164a51c49158c409904ca5419f2d55', ref: el => {
3123
3059
  if (el) {
3124
3060
  this.engineRef = el;
3125
3061
  }
3126
- }, 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) }), h("kritzel-controls", { key: '73a94f6baef4e0e26be4540ecd44d24588f5761b', visible: this.isControlsVisible, class: { 'keyboard-open': this.isVirtualKeyboardOpen }, ref: el => {
3062
+ }, 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) }), h("kritzel-controls", { key: '3a7c22f037d551636c53190eb07b318c2b857162', visible: this.isControlsVisible, class: { 'keyboard-open': this.isVirtualKeyboardOpen }, ref: el => {
3127
3063
  if (el) {
3128
3064
  this.controlsRef = el;
3129
3065
  }
3130
- }, controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState ?? undefined, theme: this.theme, terms: this.resolvedTerms, onIsControlsReady: () => (this.isControlsReady = true) }), h("div", { key: 'b0653098e5e15b81f453a7f3c2b27f137eb38f2e', class: "bottom-left-buttons" }, h("kritzel-zoom-panel", { key: '33384aeb6bd32d17fb428bd9ed48e5d588ac60c0', visible: this.isZoomPanelVisible, disabled: !this.isZoomingEnabled, zoomPercent: this.currentZoomPercent, terms: this.resolvedTerms, onZoomIn: () => this.zoomIn(), onZoomOut: () => this.zoomOut() })), h("div", { key: '37bed04c062b63a99c930362fb8ce30b58ed8872', class: "top-right-buttons" }, h("kritzel-settings", { key: '4e339573e3d8d48bc9803b9dd40a549bd056d147', ref: el => {
3066
+ }, controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState ?? undefined, theme: this.theme, terms: this.resolvedTerms, onIsControlsReady: () => (this.isControlsReady = true) }), h("div", { key: 'c0dad9086679397c46f5550c38686a9b7d8bae6c', class: "bottom-left-buttons" }, h("kritzel-zoom-panel", { key: '1639d6d2092ecd3fbc657dfdcfc3f12c02539329', visible: this.isZoomPanelVisible, disabled: !this.isZoomingEnabled, zoomPercent: this.currentZoomPercent, terms: this.resolvedTerms, onZoomIn: () => this.zoomIn(), onZoomOut: () => this.zoomOut() })), h("div", { key: '45313b28a5e4bd75d09980e4765c2241d54fe1df', class: "top-right-buttons" }, h("kritzel-settings", { key: '33737a005cd6580505ee741be0dc40bd69873276', ref: el => {
3131
3067
  if (el) {
3132
3068
  this.settingsRef = el;
3133
3069
  }
3134
- }, 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) }), h("kritzel-export", { key: '2a726ea5b56cd36ecf92bbab17d69d04baf73009', ref: el => {
3070
+ }, 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) }), h("kritzel-export", { key: '9e13001850b15b9ce9d8a502466ed9fa05ac1518', ref: el => {
3135
3071
  if (el) {
3136
3072
  this.exportRef = el;
3137
3073
  }
3138
- }, workspaceName: this.activeWorkspace?.name || 'workspace', terms: this.resolvedTerms, onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), h("kritzel-active-users", { key: '26d9bbf922b1d4158a0875ba42e7b598011973d5', users: this.activeUsers }), shouldShowCurrentUser && h("kritzel-current-user", { key: '8179cc3e940c9fd8d97978af2bf52543f661a0fd', user: this.user, terms: this.resolvedTerms }), shouldShowLoginButton && (h("kritzel-button", { key: 'd4aa5bc108043eb103754d14f1b5df42ab73f8c4', onButtonClick: () => this.loginDialogRef?.open() }, this.resolvedTerms['login.dialogTitle'] ?? 'Sign in')), h("kritzel-more-menu", { key: '17ac617bde379d7484dbeed39ac7168349b634a1', items: this.moreMenuItems, visible: this.isMoreMenuVisible, terms: this.resolvedTerms }), h("kritzel-share-dialog", { key: '881729b36d34ef3ac3d87fc51ad828fc20fa36cd', ref: el => {
3074
+ }, workspaceName: this.activeWorkspace?.name || 'workspace', terms: this.resolvedTerms, onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), h("kritzel-active-users", { key: 'f2494af1241ecc068ace09b23cfd405c76bd1273', users: this.activeUsers }), shouldShowCurrentUser && h("kritzel-current-user", { key: '7f199e3493805977b5e5b8d2c736f31729919f96', user: this.user, terms: this.resolvedTerms }), shouldShowLoginButton && (h("kritzel-button", { key: 'c6c192ddb1245acc771ac550c1fe3e4f9bc08eab', onButtonClick: () => this.loginDialogRef?.open() }, this.resolvedTerms['login.dialogTitle'] ?? 'Sign in')), h("kritzel-more-menu", { key: '869194078a4c90d42c5ebd604bc58b4f55fc4cb6', items: this.moreMenuItems, visible: this.isMoreMenuVisible, terms: this.resolvedTerms }), h("kritzel-share-dialog", { key: '8daed8fd7267aca056b6a53626c721e2f23cead4', ref: el => {
3139
3075
  if (el) {
3140
3076
  this.shareDialogRef = el;
3141
3077
  }
3142
- }, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, terms: this.resolvedTerms, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (h("kritzel-login-dialog", { key: '5e34b647ac4c3e18981042d765b4d443422c5db3', ref: el => {
3078
+ }, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, terms: this.resolvedTerms, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (h("kritzel-login-dialog", { key: 'b65ea0940cee31b152b60881180fab2445c92b0c', ref: el => {
3143
3079
  if (el) {
3144
3080
  this.loginDialogRef = el;
3145
3081
  }
@@ -21344,7 +21280,7 @@ class KritzelKeyHandler extends KritzelBaseHandler {
21344
21280
  // of running global canvas shortcuts. Escape is the one exception — it
21345
21281
  // still runs through the shortcut system so it can collapse the text
21346
21282
  // selection (handled inside the shortcut's action).
21347
- if ((this._core.store.activeText || this._core.store.activeShape) && event.key !== 'Escape') {
21283
+ if ((this._core.store.activeText || this._core.store.activeShape || this._core.store.activeInteractiveCustomElement) && event.key !== 'Escape') {
21348
21284
  return;
21349
21285
  }
21350
21286
  const shortcut = this.shortcuts.find(s => s.key === event.key && !!s.ctrl === event.ctrlKey && !!s.shift === event.shiftKey && (!s.condition || s.condition(this._core)));
@@ -21544,120 +21480,6 @@ class KritzelContextMenuHandler extends KritzelBaseHandler {
21544
21480
  }
21545
21481
  }
21546
21482
 
21547
- /**
21548
- * Represents a custom HTML element that can be placed and manipulated on the Kritzel canvas.
21549
- * Extends KritzelBaseObject to inherit common object functionality like positioning,
21550
- * scaling, and lifecycle management.
21551
- */
21552
- class KritzelCustomElement extends KritzelBaseObject {
21553
- __class__ = 'KritzelCustomElement';
21554
- /** The HTML element to be rendered on the canvas. */
21555
- element;
21556
- /** Whether the element responds to user interactions. Defaults to true. */
21557
- isInteractive = true;
21558
- /**
21559
- * Creates a new KritzelCustomElement instance.
21560
- * @param config - Optional configuration object for the custom element.
21561
- * @param config.element - The HTML element to wrap.
21562
- * @param config.translateX - Initial X position offset. Defaults to 0.
21563
- * @param config.translateY - Initial Y position offset. Defaults to 0.
21564
- * @param config.scale - Initial scale factor. Defaults to 1.
21565
- * @param config.height - Initial height in pixels. Defaults to 0.
21566
- * @param config.width - Initial width in pixels. Defaults to 0.
21567
- */
21568
- constructor(config) {
21569
- super();
21570
- if (config) {
21571
- this.translateX = config.translateX || 0;
21572
- this.translateY = config.translateY || 0;
21573
- this.rotation = KritzelMathHelper.degreesToRadians(config.rotation ?? 0);
21574
- this.scale = config.scale || 1;
21575
- this.element = config.element;
21576
- this.height = config.height || 0;
21577
- this.width = config.width || 0;
21578
- }
21579
- }
21580
- /**
21581
- * Factory method to create a new KritzelCustomElement with proper core binding.
21582
- * Generates a unique ID and assigns the element to the active workspace.
21583
- * @param core - The KritzelCore instance for state management and ID generation.
21584
- * @param config - Optional configuration object for the custom element.
21585
- * @param config.element - The HTML element to wrap.
21586
- * @param config.translateX - Initial X position offset.
21587
- * @param config.translateY - Initial Y position offset.
21588
- * @param config.scale - Initial scale factor.
21589
- * @param config.height - Initial height in pixels.
21590
- * @param config.width - Initial width in pixels.
21591
- * @returns A new KritzelBaseObject instance bound to the core.
21592
- */
21593
- static create(core, config) {
21594
- const object = new KritzelCustomElement(config);
21595
- object._core = core;
21596
- object.id = object.generateId();
21597
- object.workspaceId = core.store.state.activeWorkspace.id;
21598
- object.userId = core.user?.id;
21599
- return object;
21600
- }
21601
- /**
21602
- * Mounts the custom element to a container element in the DOM.
21603
- * Clears any existing content in the container and appends this element.
21604
- * Skips mounting if already mounted to the same container with content attached.
21605
- * @param element - The container HTML element to mount this custom element into.
21606
- */
21607
- mount(element) {
21608
- if (element === null) {
21609
- return;
21610
- }
21611
- // If already mounted to the same element and content is still attached, skip
21612
- if (this.isMounted && this.elementRef === element && this.element.parentElement === element) {
21613
- return;
21614
- }
21615
- this.elementRef = element;
21616
- this.isMounted = true;
21617
- // Clear existing content and append the element
21618
- this.elementRef.innerHTML = '';
21619
- this.elementRef.appendChild(this.element);
21620
- }
21621
- /**
21622
- * Resizes and repositions the custom element on the canvas.
21623
- * Updates both the internal state and the DOM element's inline styles.
21624
- * Syncs changes to Yjs for collaborative editing and cross-tab propagation.
21625
- * @param x - The new X position (translateX) in canvas coordinates.
21626
- * @param y - The new Y position (translateY) in canvas coordinates.
21627
- * @param width - The new width in pixels. Must be greater than 1.
21628
- * @param height - The new height in pixels. Must be greater than 1.
21629
- */
21630
- resize(x, y, width, height) {
21631
- if (width <= 1 || height <= 1) {
21632
- return;
21633
- }
21634
- this.width = width;
21635
- this.height = height;
21636
- this.translateX = x;
21637
- this.translateY = y;
21638
- if (this.element) {
21639
- this.element.style.width = `${width}px`;
21640
- this.element.style.height = `${height}px`;
21641
- }
21642
- // Update to sync changes to y.js and propagate to other tabs
21643
- this._core.store.objects.update(this);
21644
- }
21645
- /**
21646
- * Creates a deep copy of this custom element.
21647
- * The copy receives a new unique ID, is marked as unmounted,
21648
- * and contains a cloned copy of the underlying HTML element.
21649
- * @returns A new KritzelCustomElement instance with copied properties and a cloned DOM element.
21650
- */
21651
- copy() {
21652
- const copiedObject = Object.create(Object.getPrototypeOf(this));
21653
- Object.assign(copiedObject, this);
21654
- copiedObject.id = this.generateId();
21655
- copiedObject.isMounted = false;
21656
- copiedObject.element = this.element.cloneNode(true);
21657
- return copiedObject;
21658
- }
21659
- }
21660
-
21661
21483
  /**
21662
21484
  * Handles deserialization/revival of Kritzel objects from plain JavaScript objects.
21663
21485
  * This class uses a pattern similar to JSON.parse reviver to reconstruct class instances
@@ -21805,8 +21627,9 @@ const DEFAULT_ENGINE_CONFIG = {
21805
21627
  debugInfo: {
21806
21628
  showObjectInfo: false,
21807
21629
  showViewportInfo: false,
21808
- showSyncProviderInfo: true,
21809
- showMigrationInfo: true
21630
+ showSyncProviderInfo: false,
21631
+ showAssetResolverInfo: false,
21632
+ showMigrationInfo: false,
21810
21633
  },
21811
21634
  host: null,
21812
21635
  pointerX: 0,
@@ -22487,7 +22310,8 @@ class KritzelObjectMap {
22487
22310
  const docName = core.editorId ? `kritzel-workspace-${core.editorId}-${workspaceId}` : `kritzel-workspace-${workspaceId}`;
22488
22311
  const finalConfig = config ?? DEFAULT_SYNC_CONFIG;
22489
22312
  // Instantiate providers from configuration
22490
- const quiet = !core.store?.state?.debugInfo?.showSyncProviderInfo;
22313
+ const showSyncProviderInfo = !!core.store?.state?.debugInfo?.showSyncProviderInfo;
22314
+ const quiet = !showSyncProviderInfo;
22491
22315
  for (const providerConfig of finalConfig.providers) {
22492
22316
  let provider;
22493
22317
  // Check if it's a class constructor or a factory
@@ -22501,6 +22325,10 @@ class KritzelObjectMap {
22501
22325
  }
22502
22326
  this._providers.push(provider);
22503
22327
  }
22328
+ if (showSyncProviderInfo) {
22329
+ const providerList = this._providers.map(p => `${p.constructor.name} (${p.type})`).join(', ');
22330
+ console.info(`[Kritzel] Workspace sync providers initialized for ${docName}: ${providerList || 'none'}`);
22331
+ }
22504
22332
  // captureTimeout is effectively infinite — undo-step boundaries are
22505
22333
  // marked explicitly via markUndoBoundary() in insert/remove/reset and
22506
22334
  // at stroke/gesture boundaries in the tools. This prevents a single
@@ -22541,15 +22369,24 @@ class KritzelObjectMap {
22541
22369
  const networkProviders = this._providers.filter(p => p.type === 'network');
22542
22370
  // Await local providers for immediate data availability
22543
22371
  const localResults = await Promise.allSettled(localProviders.map(p => p.connect()));
22372
+ if (showSyncProviderInfo) {
22373
+ const connectedLocalCount = localResults.filter(result => result.status === 'fulfilled').length;
22374
+ console.info(`[Kritzel] Workspace local providers connected: ${connectedLocalCount}/${localProviders.length}`);
22375
+ }
22544
22376
  localResults.forEach((result, i) => {
22545
- if (result.status === 'rejected') {
22377
+ if (showSyncProviderInfo && result.status === 'rejected') {
22546
22378
  console.error(`[Kritzel] Sync provider "${localProviders[i]?.constructor.name}" failed to connect:`, result.reason);
22547
22379
  }
22548
22380
  });
22549
22381
  // Connect network providers in the background (remote data arrives via Yjs observers)
22382
+ if (showSyncProviderInfo && networkProviders.length > 0) {
22383
+ console.info(`[Kritzel] Workspace network providers connecting in background: ${networkProviders.length}`);
22384
+ }
22550
22385
  for (const provider of networkProviders) {
22551
22386
  provider.connect().catch(err => {
22552
- console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
22387
+ if (showSyncProviderInfo) {
22388
+ console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
22389
+ }
22553
22390
  });
22554
22391
  }
22555
22392
  this._isReady = true;
@@ -23440,6 +23277,14 @@ class KritzelStore {
23440
23277
  const activeShapes = this.objects.filter(o => o instanceof KritzelShape && o.isEditing);
23441
23278
  return activeShapes.length > 0 ? activeShapes[0] : null;
23442
23279
  }
23280
+ /**
23281
+ * Gets the currently selected custom element whose focused descendant is an input-like element.
23282
+ * @returns The interactive custom element with focused input-like content, or null if none matches
23283
+ */
23284
+ get activeInteractiveCustomElement() {
23285
+ const interactiveCustomElement = this.selectedObjects.find(object => KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement') && object.hasFocusedInputElement());
23286
+ return interactiveCustomElement ?? null;
23287
+ }
23443
23288
  /**
23444
23289
  * Gets the path currently being drawn (not yet completed).
23445
23290
  * @returns The incomplete path object, or null if no path is being drawn
@@ -23609,7 +23454,8 @@ class KritzelAppStateMap {
23609
23454
  const docName = core.editorId ? `kritzel-app-state-${core.editorId}-${instanceId}` : `kritzel-app-state-${instanceId}`;
23610
23455
  const finalConfig = config ?? DEFAULT_SYNC_CONFIG;
23611
23456
  // Instantiate providers from configuration
23612
- const quiet = !core.store.state.debugInfo.showSyncProviderInfo;
23457
+ const showSyncProviderInfo = !!core.store.state.debugInfo.showSyncProviderInfo;
23458
+ const quiet = !showSyncProviderInfo;
23613
23459
  for (const providerConfig of finalConfig.providers) {
23614
23460
  let provider;
23615
23461
  // Check if it's a class constructor or a factory
@@ -23623,6 +23469,10 @@ class KritzelAppStateMap {
23623
23469
  }
23624
23470
  this._providers.push(provider);
23625
23471
  }
23472
+ if (showSyncProviderInfo) {
23473
+ const providerList = this._providers.map(p => `${p.constructor.name} (${p.type})`).join(', ');
23474
+ console.info(`[Kritzel] App-state sync providers initialized for ${docName}: ${providerList || 'none'}`);
23475
+ }
23626
23476
  // Observe changes to workspaces and sync with application state
23627
23477
  this._workspacesObserver = event => {
23628
23478
  this.handleWorkspacesChange(event);
@@ -23635,15 +23485,24 @@ class KritzelAppStateMap {
23635
23485
  const networkProviders = this._providers.filter(p => p.type === 'network');
23636
23486
  // Await local providers for immediate data availability
23637
23487
  const localResults = await Promise.allSettled(localProviders.map(p => p.connect()));
23488
+ if (showSyncProviderInfo) {
23489
+ const connectedLocalCount = localResults.filter(result => result.status === 'fulfilled').length;
23490
+ console.info(`[Kritzel] App-state local providers connected: ${connectedLocalCount}/${localProviders.length}`);
23491
+ }
23638
23492
  localResults.forEach((result, i) => {
23639
- if (result.status === 'rejected') {
23493
+ if (showSyncProviderInfo && result.status === 'rejected') {
23640
23494
  console.error(`[Kritzel] Sync provider "${localProviders[i]?.constructor.name}" failed to connect:`, result.reason);
23641
23495
  }
23642
23496
  });
23643
23497
  // Connect network providers in the background (remote data arrives via Yjs observers)
23498
+ if (showSyncProviderInfo && networkProviders.length > 0) {
23499
+ console.info(`[Kritzel] App-state network providers connecting in background: ${networkProviders.length}`);
23500
+ }
23644
23501
  for (const provider of networkProviders) {
23645
23502
  provider.connect().catch(err => {
23646
- console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
23503
+ if (showSyncProviderInfo) {
23504
+ console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
23505
+ }
23647
23506
  });
23648
23507
  }
23649
23508
  this._isReady = true;
@@ -24739,6 +24598,7 @@ class KritzelCore {
24739
24598
  this._clipboardManager = new KritzelClipboardManager(this);
24740
24599
  this._toolRegistry = new KritzelToolRegistry(this);
24741
24600
  this._assetResolver = new KritzelAssetResolver();
24601
+ this._assetResolver.setShowInfo(this._store.state.debugInfo?.showAssetResolverInfo ?? false);
24742
24602
  }
24743
24603
  /**
24744
24604
  * Gets the asset resolver responsible for storing and resolving
@@ -25110,6 +24970,10 @@ class KritzelCore {
25110
24970
  this.removeObject(child);
25111
24971
  });
25112
24972
  }
24973
+ // Unmount custom elements before removal
24974
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
24975
+ object.unmount();
24976
+ }
25113
24977
  // Handle anchor cleanup
25114
24978
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelLine')) {
25115
24979
  // If removing a line, clean up its anchor index entries
@@ -25150,21 +25014,84 @@ class KritzelCore {
25150
25014
  * @param selectionGroup - The selection group to add
25151
25015
  */
25152
25016
  addSelectionGroup(selectionGroup) {
25153
- this.removeSelectionGroup();
25017
+ this.removeLocalSelectionGroups();
25154
25018
  this.removeSelectionBox();
25019
+ for (const object of selectionGroup.objects ?? []) {
25020
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
25021
+ const customElementObject = object;
25022
+ if (typeof customElementObject.setIsInteractive === 'function') {
25023
+ customElementObject.setIsInteractive(true);
25024
+ }
25025
+ else {
25026
+ customElementObject.isInteractive = true;
25027
+ }
25028
+ }
25029
+ }
25155
25030
  this._store.objects.insert(selectionGroup);
25156
25031
  this._store.setSelectionGroup(selectionGroup);
25157
25032
  this._kritzelEngine.triggerSelectionChange();
25158
25033
  }
25034
+ /**
25035
+ * Returns whether the provided selection group belongs to the local user context.
25036
+ * Local groups are either explicitly owned by the current user or have no user ID.
25037
+ */
25038
+ isLocalSelectionGroup(selectionGroup) {
25039
+ const localUserId = this._user?.id;
25040
+ return localUserId == null || selectionGroup.userId === localUserId || selectionGroup.userId == null;
25041
+ }
25042
+ /**
25043
+ * Applies deselection teardown side effects to a selection group before removal.
25044
+ */
25045
+ teardownSelectionGroup(selectionGroup) {
25046
+ selectionGroup.deselectAllChildren?.();
25047
+ for (const object of selectionGroup.objects ?? []) {
25048
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
25049
+ const customElementObject = object;
25050
+ if (typeof customElementObject.setIsInteractive === 'function') {
25051
+ customElementObject.setIsInteractive(false);
25052
+ }
25053
+ else {
25054
+ customElementObject.isInteractive = false;
25055
+ }
25056
+ }
25057
+ }
25058
+ }
25059
+ /**
25060
+ * Removes all local selection groups from the store, including orphaned groups
25061
+ * that are no longer referenced by store.selectionGroup.
25062
+ * @returns True when at least one local selection group was removed
25063
+ */
25064
+ removeLocalSelectionGroups() {
25065
+ const allSelectionGroupsInStore = this._store.objects
25066
+ .allObjects()
25067
+ .filter((object) => {
25068
+ return object instanceof KritzelSelectionGroup || KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup');
25069
+ });
25070
+ const allLocalSelectionGroups = allSelectionGroupsInStore.filter(selectionGroup => this.isLocalSelectionGroup(selectionGroup));
25071
+ const currentSelectionGroup = this._store.selectionGroup;
25072
+ if (currentSelectionGroup &&
25073
+ this.isLocalSelectionGroup(currentSelectionGroup) &&
25074
+ !allLocalSelectionGroups.some(group => group.id === currentSelectionGroup.id)) {
25075
+ allLocalSelectionGroups.push(currentSelectionGroup);
25076
+ }
25077
+ if (allLocalSelectionGroups.length === 0) {
25078
+ return false;
25079
+ }
25080
+ for (const selectionGroup of allLocalSelectionGroups) {
25081
+ this.teardownSelectionGroup(selectionGroup);
25082
+ }
25083
+ const localSelectionGroupIds = new Set(allLocalSelectionGroups.map(group => group.id));
25084
+ this._store.objects.remove(object => localSelectionGroupIds.has(object.id));
25085
+ this._store.setSelectionGroup(null);
25086
+ return true;
25087
+ }
25159
25088
  /**
25160
25089
  * Removes the current selection group from the canvas.
25161
25090
  * Clears the cached reference and triggers the selection change event.
25162
25091
  */
25163
25092
  removeSelectionGroup() {
25164
- const selectionGroup = this._store.selectionGroup;
25165
- if (selectionGroup) {
25166
- this._store.objects.remove(object => object.id === selectionGroup.id);
25167
- this._store.setSelectionGroup(null);
25093
+ const removedAnySelectionGroup = this.removeLocalSelectionGroups();
25094
+ if (removedAnySelectionGroup) {
25168
25095
  this._kritzelEngine.triggerSelectionChange();
25169
25096
  }
25170
25097
  }
@@ -25468,6 +25395,11 @@ class KritzelCore {
25468
25395
  (o instanceof KritzelSelectionGroup && (localUserId == null || o.userId === localUserId || o.userId == null)));
25469
25396
  this._store.setSelectionBox(null);
25470
25397
  this._store.setSelectionGroup(null);
25398
+ for (const object of this._store.allNonSelectionObjects) {
25399
+ if (object.isSelected) {
25400
+ object.isSelected = false;
25401
+ }
25402
+ }
25471
25403
  this._store.state.isSelecting = false;
25472
25404
  this._store.state.isResizeHandleSelected = false;
25473
25405
  this._store.state.isRotationHandleSelected = false;
@@ -25630,6 +25562,11 @@ class KritzelCore {
25630
25562
  // During selection phase (no group yet), hide UI for KritzelLine objects
25631
25563
  return !(object instanceof KritzelLine);
25632
25564
  }
25565
+ // Once a local selection group exists, only the selection-group object
25566
+ // should render group UI (children remain selected but don't render handles).
25567
+ if (!(object instanceof KritzelSelectionGroup) || object.id !== selectionGroup.id) {
25568
+ return false;
25569
+ }
25633
25570
  // Show UI if selection contains more than one object
25634
25571
  if (selectionGroup.objects.length > 1) {
25635
25572
  return true;
@@ -25915,6 +25852,9 @@ class KritzelSvgExportHelper {
25915
25852
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelImage')) {
25916
25853
  return this.imageToSvg(object, imageDataUrls);
25917
25854
  }
25855
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
25856
+ return this.customElementToSvg(object, rasterSafeForCanvas);
25857
+ }
25918
25858
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelGroup')) {
25919
25859
  return this.groupToSvg(object, theme, imageDataUrls, rasterSafeForCanvas);
25920
25860
  }
@@ -26081,6 +26021,38 @@ class KritzelSvgExportHelper {
26081
26021
  return `<g transform="${transform}"${opacity}>
26082
26022
  <image href="${href}" x="0" y="0" width="${width}" height="${height}" preserveAspectRatio="xMidYMid meet"/>
26083
26023
  </g>`;
26024
+ }
26025
+ /**
26026
+ * Converts a KritzelCustomElement to SVG.
26027
+ * SVG mode: embeds HTML in foreignObject (best-effort).
26028
+ * PNG/raster mode: renders placeholder rect with warning.
26029
+ * @param obj - The custom element object
26030
+ * @param rasterSafeForCanvas - If true, render placeholder (for PNG export)
26031
+ * @returns SVG element string
26032
+ */
26033
+ static customElementToSvg(obj, rasterSafeForCanvas = false) {
26034
+ const transform = this.buildTransform(obj);
26035
+ const opacity = obj.opacity !== 1 ? ` opacity="${obj.opacity}"` : '';
26036
+ const scale = this.getObjectScale(obj);
26037
+ const width = obj.totalWidth / scale;
26038
+ const height = obj.totalHeight / scale;
26039
+ if (rasterSafeForCanvas) {
26040
+ // PNG export: render placeholder rect (foreignObject doesn't rasterize to PNG well)
26041
+ console.warn('[KritzelCustomElement SVG Export] Custom elements do not serialize well to PNG. Use SVG export or screenshot instead.');
26042
+ return `<g transform="${transform}"${opacity}>
26043
+ <rect x="0" y="0" width="${width}" height="${height}" fill="#f0f0f0" stroke="#999" stroke-width="1" />
26044
+ <text x="${width / 2}" y="${height / 2}" text-anchor="middle" dominant-baseline="middle" font-size="12" fill="#999">[Custom Element]</text>
26045
+ </g>`;
26046
+ }
26047
+ // SVG export: use foreignObject to embed HTML
26048
+ const html = obj.element?.outerHTML || '<div style="border:1px dashed #ccc">Empty</div>';
26049
+ return `<g transform="${transform}"${opacity}>
26050
+ <foreignObject x="0" y="0" width="${width}" height="${height}">
26051
+ <div xmlns="http://www.w3.org/1999/xhtml" style="width:100%;height:100%;overflow:hidden;">
26052
+ ${html}
26053
+ </div>
26054
+ </foreignObject>
26055
+ </g>`;
26084
26056
  }
26085
26057
  /**
26086
26058
  * Converts a KritzelGroup to SVG by recursively converting children.
@@ -27152,6 +27124,120 @@ async function toPng(node, options = {}) {
27152
27124
  return canvas.toDataURL();
27153
27125
  }
27154
27126
 
27127
+ class KritzelWheelHelper {
27128
+ static shouldKeepWheelInCustomElement(ev, customElement) {
27129
+ // Keep Ctrl+wheel available for viewport zoom gestures.
27130
+ if (ev.ctrlKey || customElement.isSelected !== true) {
27131
+ return false;
27132
+ }
27133
+ const path = typeof ev.composedPath === 'function' ? ev.composedPath() : [];
27134
+ const scrollContainer = this.getScrollableContainerFromEventPath(path, customElement);
27135
+ // No scroll container found inside the custom element: let the wheel
27136
+ // event pass through so the viewport can continue handling it.
27137
+ if (!scrollContainer) {
27138
+ return false;
27139
+ }
27140
+ return this.canElementConsumeWheel(scrollContainer, ev.deltaX, ev.deltaY);
27141
+ }
27142
+ static getScrollableContainerFromEventPath(path, customElement) {
27143
+ const potentialRoots = [];
27144
+ if (customElement.element instanceof HTMLElement) {
27145
+ potentialRoots.push(customElement.element);
27146
+ }
27147
+ if (customElement.elementRef instanceof HTMLElement) {
27148
+ potentialRoots.push(customElement.elementRef);
27149
+ }
27150
+ const legacyElementRef = customElement.elmeentRef;
27151
+ if (legacyElementRef instanceof HTMLElement) {
27152
+ potentialRoots.push(legacyElementRef);
27153
+ }
27154
+ for (const root of potentialRoots) {
27155
+ const rootIndex = path.findIndex(entry => entry === root);
27156
+ if (rootIndex === -1) {
27157
+ continue;
27158
+ }
27159
+ for (const entry of path.slice(0, rootIndex + 1)) {
27160
+ if (!(entry instanceof HTMLElement)) {
27161
+ continue;
27162
+ }
27163
+ if (this.isScrollableElement(entry)) {
27164
+ return entry;
27165
+ }
27166
+ }
27167
+ }
27168
+ // Fallback for synthetic/custom wheel events where composedPath is absent
27169
+ // or does not include the custom-element subtree.
27170
+ for (const root of potentialRoots) {
27171
+ if (this.isScrollableElement(root)) {
27172
+ return root;
27173
+ }
27174
+ const firstChild = root.firstElementChild;
27175
+ if (firstChild instanceof HTMLElement && this.isScrollableElement(firstChild)) {
27176
+ return firstChild;
27177
+ }
27178
+ }
27179
+ return null;
27180
+ }
27181
+ static isScrollableElement(element) {
27182
+ const canScrollVertically = element.scrollHeight - element.clientHeight > 0;
27183
+ const canScrollHorizontally = element.scrollWidth - element.clientWidth > 0;
27184
+ if (!canScrollVertically && !canScrollHorizontally) {
27185
+ return false;
27186
+ }
27187
+ const computedStyle = window.getComputedStyle(element);
27188
+ const overflowX = computedStyle.overflowX;
27189
+ const overflowY = computedStyle.overflowY;
27190
+ const allowsVerticalScroll = overflowY !== 'visible' && overflowY !== 'clip';
27191
+ const allowsHorizontalScroll = overflowX !== 'visible' && overflowX !== 'clip';
27192
+ return (canScrollVertically && allowsVerticalScroll) || (canScrollHorizontally && allowsHorizontalScroll);
27193
+ }
27194
+ static canElementConsumeWheel(element, deltaX, deltaY) {
27195
+ const canConsumeVertical = this.canElementConsumeVerticalWheel(element, deltaY);
27196
+ const canConsumeHorizontal = this.canElementConsumeHorizontalWheel(element, deltaX);
27197
+ // Keep wheel events inside the custom element whenever at least one
27198
+ // requested axis can still be consumed by its scroll container.
27199
+ return canConsumeVertical || canConsumeHorizontal;
27200
+ }
27201
+ static canElementConsumeVerticalWheel(element, deltaY) {
27202
+ if (deltaY === 0) {
27203
+ return false;
27204
+ }
27205
+ const maxScrollTop = Math.max(0, element.scrollHeight - element.clientHeight);
27206
+ if (maxScrollTop === 0) {
27207
+ return false;
27208
+ }
27209
+ const epsilon = 1;
27210
+ const scrollingDown = deltaY > 0;
27211
+ const scrollingUp = deltaY < 0;
27212
+ if (scrollingDown) {
27213
+ return element.scrollTop < maxScrollTop - epsilon;
27214
+ }
27215
+ if (scrollingUp) {
27216
+ return element.scrollTop > epsilon;
27217
+ }
27218
+ return false;
27219
+ }
27220
+ static canElementConsumeHorizontalWheel(element, deltaX) {
27221
+ if (deltaX === 0) {
27222
+ return false;
27223
+ }
27224
+ const maxScrollLeft = Math.max(0, element.scrollWidth - element.clientWidth);
27225
+ if (maxScrollLeft === 0) {
27226
+ return false;
27227
+ }
27228
+ const epsilon = 1;
27229
+ const scrollingRight = deltaX > 0;
27230
+ const scrollingLeft = deltaX < 0;
27231
+ if (scrollingRight) {
27232
+ return element.scrollLeft < maxScrollLeft - epsilon;
27233
+ }
27234
+ if (scrollingLeft) {
27235
+ return element.scrollLeft > epsilon;
27236
+ }
27237
+ return false;
27238
+ }
27239
+ }
27240
+
27155
27241
  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}`;
27156
27242
 
27157
27243
  const KritzelEngine = class {
@@ -27340,6 +27426,7 @@ const KritzelEngine = class {
27340
27426
  onDebugInfoChange(newValue) {
27341
27427
  if (newValue) {
27342
27428
  this.core.store.state.debugInfo = newValue;
27429
+ this.core.assetResolver.setShowInfo(newValue.showAssetResolverInfo);
27343
27430
  }
27344
27431
  }
27345
27432
  /** When false, non-modified wheel events do not pan the viewport. */
@@ -27393,6 +27480,12 @@ const KritzelEngine = class {
27393
27480
  if (this.core.store.state.isContextMenuVisible) {
27394
27481
  this.hideContextMenu();
27395
27482
  }
27483
+ const hoveredObject = this.core.getObjectFromPointerEvent(ev);
27484
+ if (hoveredObject &&
27485
+ ObjectHelper.isClass(hoveredObject, 'KritzelCustomElement') &&
27486
+ KritzelWheelHelper.shouldKeepWheelInCustomElement(ev, hoveredObject)) {
27487
+ return;
27488
+ }
27396
27489
  if (ev.ctrlKey) {
27397
27490
  if (this.isZoomingEnabled) {
27398
27491
  this.viewport.handleWheel(ev);
@@ -27409,6 +27502,11 @@ const KritzelEngine = class {
27409
27502
  if (this.core.store.isDisabled) {
27410
27503
  return;
27411
27504
  }
27505
+ // Interactive custom-element content should receive native pointer events
27506
+ // whenever the object is marked interactive.
27507
+ if (this.isEmbeddedCustomElementInteraction(ev)) {
27508
+ return;
27509
+ }
27412
27510
  if (KritzelDevicesHelper.isTouchDevice()) {
27413
27511
  KritzelEventHelper.onLongPress(ev, (event) => this.longpress.emit(event));
27414
27512
  }
@@ -27425,6 +27523,10 @@ const KritzelEngine = class {
27425
27523
  if (this.core.store.isDisabled) {
27426
27524
  return;
27427
27525
  }
27526
+ // Ignore drag/move sequences when their pointerdown was intentionally bypassed.
27527
+ if (ev.buttons > 0 && !this.core.store.state.pointers.has(ev.pointerId)) {
27528
+ return;
27529
+ }
27428
27530
  if (this.core.store.state.pointers.has(ev.pointerId)) {
27429
27531
  this.core.store.state.pointers.set(ev.pointerId, ev);
27430
27532
  }
@@ -27473,6 +27575,56 @@ const KritzelEngine = class {
27473
27575
  this.viewport.handlePointerUp(ev);
27474
27576
  this.core.store.state?.activeTool?.handlePointerUp(ev);
27475
27577
  }
27578
+ isEmbeddedCustomElementInteraction(event) {
27579
+ const path = event.composedPath();
27580
+ const objectElement = path.find((entry) => {
27581
+ return entry instanceof HTMLElement && entry.classList.contains('object');
27582
+ });
27583
+ if (!objectElement?.id) {
27584
+ return false;
27585
+ }
27586
+ const object = this.core.store.objects.findById(objectElement.id);
27587
+ if (!object || !KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
27588
+ return false;
27589
+ }
27590
+ const isSelected = !!this.core.store.selectionGroup?.objects?.some(selectedObject => selectedObject.id === object.id);
27591
+ if (!isSelected) {
27592
+ return false;
27593
+ }
27594
+ if (object.isInteractive !== true) {
27595
+ return false;
27596
+ }
27597
+ const potentialRoots = [];
27598
+ if (object.element instanceof HTMLElement) {
27599
+ potentialRoots.push(object.element);
27600
+ }
27601
+ if (object.elementRef instanceof HTMLElement) {
27602
+ potentialRoots.push(object.elementRef);
27603
+ }
27604
+ if (potentialRoots.length === 0) {
27605
+ return false;
27606
+ }
27607
+ // Only bypass canvas tool handling when the pointer event came from an
27608
+ // actually interactive descendant inside the custom element subtree.
27609
+ return potentialRoots.some(root => this.isNativeInteractiveEmbeddedTarget(path, root));
27610
+ }
27611
+ isNativeInteractiveEmbeddedTarget(path, customElementRoot) {
27612
+ const interactiveSelector = 'input, button, textarea, select, a[href], [contenteditable="true"], [contenteditable=""]';
27613
+ const rootIndex = path.findIndex(entry => entry === customElementRoot);
27614
+ if (rootIndex === -1) {
27615
+ return false;
27616
+ }
27617
+ for (const entry of path.slice(0, rootIndex)) {
27618
+ if (!(entry instanceof HTMLElement)) {
27619
+ continue;
27620
+ }
27621
+ const interactiveEntry = entry;
27622
+ if (interactiveEntry.matches(interactiveSelector)) {
27623
+ return true;
27624
+ }
27625
+ }
27626
+ return false;
27627
+ }
27476
27628
  handlePointerLeave() {
27477
27629
  this.core.store.objects?.clearCursorPosition();
27478
27630
  }
@@ -27544,7 +27696,7 @@ const KritzelEngine = class {
27544
27696
  void this.core.clipboardManager.paste(undefined, undefined, ev);
27545
27697
  }
27546
27698
  /**
27547
- * Determines if the engine should handle a keyboard/clipboard shortcut event.
27699
+ * Determines if the engine should handle a keyboard/clipboard shortcut event.
27548
27700
  * Returns true only if:
27549
27701
  * - The event originates from within the engine component, OR
27550
27702
  * - The event target is not an interactive element that needs keyboard input
@@ -28758,6 +28910,8 @@ const KritzelEngine = class {
28758
28910
  _syncConfigRevision = 0;
28759
28911
  _assetStorageConfigRevision = 0;
28760
28912
  _isWorkspaceLoading = false;
28913
+ _hasLoggedUnlicensedWelcomeBanner = false;
28914
+ _previousVisibleCustomElements = new Set();
28761
28915
  _defaultUndoState = {
28762
28916
  canUndo: false,
28763
28917
  canRedo: false,
@@ -28821,6 +28975,14 @@ const KritzelEngine = class {
28821
28975
  }
28822
28976
  disconnectedCallback() {
28823
28977
  this.throttledPointerMoveMulti.cancel();
28978
+ // Unmount all remaining custom elements
28979
+ if (this.core.store.allObjects) {
28980
+ this.core.store.allObjects.forEach(object => {
28981
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
28982
+ object.unmount();
28983
+ }
28984
+ });
28985
+ }
28824
28986
  // Clear cursor position in awareness before destroying
28825
28987
  this.core.store.objects?.clearCursorPosition();
28826
28988
  // Clean up current workspace's ObjectMap
@@ -28877,6 +29039,7 @@ const KritzelEngine = class {
28877
29039
  await this.initializeSyncAndWorkspace();
28878
29040
  if (this.core.store.state.isReady === false) {
28879
29041
  this.core.store.state.isReady = true;
29042
+ this.logUnlicensedWelcomeBanner();
28880
29043
  this.isEngineReady.emit(this.core.store.state);
28881
29044
  }
28882
29045
  }, 50);
@@ -28916,6 +29079,14 @@ const KritzelEngine = class {
28916
29079
  }
28917
29080
  this._isAssetStorageInitialized = true;
28918
29081
  }
29082
+ logUnlicensedWelcomeBanner() {
29083
+ if (this.core.licenseManager.isLicensed || this._hasLoggedUnlicensedWelcomeBanner) {
29084
+ return;
29085
+ }
29086
+ this._hasLoggedUnlicensedWelcomeBanner = true;
29087
+ console.info('%c👋 Welcome to Kritzel', 'color: #007AFF; font-weight: 700;');
29088
+ console.info(`This version of Kritzel is not licensed. Get a license at: https://kritzel.io`);
29089
+ }
28919
29090
  async doInitializeSyncAndWorkspace() {
28920
29091
  // Capture sync config revision to detect prop updates that happen while
28921
29092
  // initialization is in-flight.
@@ -29119,8 +29290,7 @@ const KritzelEngine = class {
29119
29290
  if (normalizedScale <= this._objectDistanceFadeFarScale) {
29120
29291
  return this._objectDistanceFadeMinOpacity;
29121
29292
  }
29122
- const progress = (this._objectDistanceFadeNearScale - normalizedScale) /
29123
- (this._objectDistanceFadeNearScale - this._objectDistanceFadeFarScale);
29293
+ const progress = (this._objectDistanceFadeNearScale - normalizedScale) / (this._objectDistanceFadeNearScale - this._objectDistanceFadeFarScale);
29124
29294
  const smooth = progress * progress * (3 - 2 * progress);
29125
29295
  return 1 - smooth * (1 - this._objectDistanceFadeMinOpacity);
29126
29296
  }
@@ -29131,8 +29301,7 @@ const KritzelEngine = class {
29131
29301
  if (normalizedScale >= this._objectDistanceFadeCloseMaxScale) {
29132
29302
  return this._objectDistanceFadeCloseMinOpacity;
29133
29303
  }
29134
- const progress = (normalizedScale - this._objectDistanceFadeCloseStartScale) /
29135
- (this._objectDistanceFadeCloseMaxScale - this._objectDistanceFadeCloseStartScale);
29304
+ const progress = (normalizedScale - this._objectDistanceFadeCloseStartScale) / (this._objectDistanceFadeCloseMaxScale - this._objectDistanceFadeCloseStartScale);
29136
29305
  const smooth = progress * progress * (3 - 2 * progress);
29137
29306
  return 1 - smooth * (1 - this._objectDistanceFadeCloseMinOpacity);
29138
29307
  }
@@ -29142,13 +29311,18 @@ const KritzelEngine = class {
29142
29311
  }
29143
29312
  getObjectEffectiveOpacity(object, viewportScale) {
29144
29313
  const safeObjectOpacity = Number.isFinite(object.opacity) ? object.opacity : 1;
29145
- const distanceFadeMultiplier = this.shouldApplyDistanceFade(object)
29146
- ? this.getObjectDistanceFadeMultiplier(object.scale, viewportScale)
29147
- : 1;
29314
+ const distanceFadeMultiplier = this.shouldApplyDistanceFade(object) ? this.getObjectDistanceFadeMultiplier(object.scale, viewportScale) : 1;
29148
29315
  const multiplied = (object.markedForRemoval ? 0.5 : 1) * safeObjectOpacity * distanceFadeMultiplier;
29149
29316
  const overridden = ((object.markedForRemoval ? 0.5 : safeObjectOpacity) * distanceFadeMultiplier).toString();
29150
29317
  return { multiplied, overridden };
29151
29318
  }
29319
+ getSelectionHandleCapabilities(object) {
29320
+ const selectedObjects = KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup') ? object.objects : [object];
29321
+ return {
29322
+ canRenderResizeHandles: selectedObjects.every(selectedObject => selectedObject.isResizable !== false),
29323
+ canRenderRotationHandle: selectedObjects.every(selectedObject => selectedObject.isRotatable !== false),
29324
+ };
29325
+ }
29152
29326
  render() {
29153
29327
  if (!this.viewport) {
29154
29328
  return (h(Host, null, this.core.store.state.isLoading && (h("div", { class: "workspace-loading-overlay" }, h("span", { class: "workspace-loading-spinner" }), this.core.localizationManager.translate('engine.loading')))));
@@ -29162,6 +29336,45 @@ const KritzelEngine = class {
29162
29336
  const viewportCenterX = this.core.store.state.viewportWidth / 2 + this.core.store.state.translateX;
29163
29337
  const viewportCenterY = this.core.store.state.viewportHeight / 2 + this.core.store.state.translateY;
29164
29338
  const visibleObjects = this.core.store.objectsInViewport;
29339
+ const getObjectWrapperPointerEvents = (object) => {
29340
+ if (this.core.store.state.isScaling) {
29341
+ return 'none';
29342
+ }
29343
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup')) {
29344
+ return 'none';
29345
+ }
29346
+ return 'auto';
29347
+ };
29348
+ const getSelectionGroupOverlayPointerEvents = (object) => {
29349
+ if (object.markedForRemoval) {
29350
+ return 'none';
29351
+ }
29352
+ if (!KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup')) {
29353
+ return 'auto';
29354
+ }
29355
+ if (object.objects.length === 1 && KritzelClassHelper.isInstanceOf(object.objects[0], 'KritzelCustomElement')) {
29356
+ return 'none';
29357
+ }
29358
+ return 'auto';
29359
+ };
29360
+ // Track visible custom elements and unmount culled ones
29361
+ const currentVisibleCustomElements = new Set();
29362
+ visibleObjects.forEach(object => {
29363
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
29364
+ currentVisibleCustomElements.add(object.id);
29365
+ }
29366
+ });
29367
+ // Unmount custom elements that left the viewport
29368
+ this._previousVisibleCustomElements.forEach(id => {
29369
+ if (!currentVisibleCustomElements.has(id)) {
29370
+ const object = this.core.store.objects.findById(id);
29371
+ if (object && KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
29372
+ object.unmount();
29373
+ }
29374
+ }
29375
+ });
29376
+ // Update tracking set for next render
29377
+ this._previousVisibleCustomElements = currentVisibleCustomElements;
29165
29378
  this.core.cursorManager.applyCursor();
29166
29379
  if (this.core.store.state.hasViewportChanged) {
29167
29380
  this.viewportChange.emit(this.viewport.getViewport());
@@ -29178,7 +29391,7 @@ const KritzelEngine = class {
29178
29391
  transformOrigin: 'top left',
29179
29392
  position: 'absolute',
29180
29393
  zIndex: object.zIndex.toString(),
29181
- pointerEvents: this.core.store.state.isScaling ? 'none' : 'auto',
29394
+ pointerEvents: getObjectWrapperPointerEvents(object),
29182
29395
  } }, KritzelClassHelper.isInstanceOf(object, 'KritzelPath') && (h("svg", { ref: el => el && object.mount(el), xmlns: "http://www.w3.org/2000/svg", style: {
29183
29396
  height: object?.totalHeight + 'px',
29184
29397
  width: object?.totalWidth + 'px',
@@ -29267,7 +29480,7 @@ const KritzelEngine = class {
29267
29480
  transform: object.rotationDegrees !== 0 ? `rotate(${object.rotationDegrees}deg)` : undefined,
29268
29481
  transformOrigin: object.rotationDegrees !== 0 ? `${object.totalWidth / 2}px ${object.totalHeight / 2}px` : undefined,
29269
29482
  opacity: object.markedForRemoval ? '0.5' : object.opacity.toString(),
29270
- pointerEvents: object.markedForRemoval ? 'none' : 'auto',
29483
+ pointerEvents: getSelectionGroupOverlayPointerEvents(object),
29271
29484
  } })), KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionBox') && (h("div", { ref: el => el && object.mount(el), style: {
29272
29485
  position: 'absolute',
29273
29486
  left: '0',
@@ -29302,60 +29515,65 @@ const KritzelEngine = class {
29302
29515
  transform: `scale(${object.scaleFactor})`,
29303
29516
  backgroundColor: KritzelColorHelper.resolveThemeColor(object.backgroundColor, currentTheme),
29304
29517
  overflow: 'visible',
29305
- } }))), KritzelClassHelper.isInstanceOf(object, 'KritzelShape') && (() => {
29306
- const fillResolved = KritzelColorHelper.resolveThemeColor(object.fillColor, currentTheme);
29307
- const strokeResolved = KritzelColorHelper.resolveThemeColor(object.strokeColor, currentTheme);
29308
- const isFilled = !!fillResolved && fillResolved !== 'transparent';
29309
- const strokeMatchesFill = isFilled && strokeResolved === fillResolved;
29310
- return (h("div", { ref: el => el && object.mount(el), onPointerDown: e => object.handlePointerDown(e), onPointerMove: e => object.handlePointerMove(e), onPointerUp: e => object.handlePointerUp(e), style: {
29311
- position: 'absolute',
29312
- left: '0',
29313
- top: '0',
29314
- width: object.totalWidth + 'px',
29315
- height: object.totalHeight + 'px',
29316
- transform: object.rotationDegrees !== 0 ? `rotate(${object.rotationDegrees}deg)` : undefined,
29317
- transformOrigin: object.rotationDegrees !== 0 ? `${object.totalWidth / 2}px ${object.totalHeight / 2}px` : undefined,
29318
- pointerEvents: object.markedForRemoval ? 'none' : 'auto',
29319
- overflow: 'visible',
29320
- } }, h("svg", { xmlns: "http://www.w3.org/2000/svg", style: {
29321
- position: 'absolute',
29322
- top: '0',
29323
- left: '0',
29324
- width: '100%',
29325
- height: '100%',
29326
- overflow: 'visible',
29327
- pointerEvents: 'none',
29328
- }, viewBox: object.viewBox, preserveAspectRatio: "none" }, h("path", { d: object.getSvgPath(), fill: KritzelColorHelper.applyOpacity(object.fillColor, effectiveOpacity, currentTheme), stroke: strokeMatchesFill ? 'none' : KritzelColorHelper.applyOpacity(object.strokeColor, effectiveOpacity, currentTheme), "stroke-width": strokeMatchesFill ? 0 : object.strokeWidth })), h("div", { ref: el => el && object.mountTextEditor(el), style: {
29329
- position: 'absolute',
29330
- top: '0',
29331
- left: '0',
29332
- width: '100%',
29333
- height: '100%',
29334
- display: 'flex',
29335
- alignItems: 'center',
29336
- justifyContent: 'center',
29337
- textAlign: 'center',
29338
- overflow: 'hidden',
29339
- pointerEvents: object.isEditing ? 'auto' : 'none',
29340
- } })));
29341
- })(), this.core.store.state.debugInfo.showObjectInfo && object.isDebugInfoVisible && (h("div", { style: {
29518
+ } }))), KritzelClassHelper.isInstanceOf(object, 'KritzelShape') &&
29519
+ (() => {
29520
+ const fillResolved = KritzelColorHelper.resolveThemeColor(object.fillColor, currentTheme);
29521
+ const strokeResolved = KritzelColorHelper.resolveThemeColor(object.strokeColor, currentTheme);
29522
+ const isFilled = !!fillResolved && fillResolved !== 'transparent';
29523
+ const strokeMatchesFill = isFilled && strokeResolved === fillResolved;
29524
+ return (h("div", { ref: el => el && object.mount(el), onPointerDown: e => object.handlePointerDown(e), onPointerMove: e => object.handlePointerMove(e), onPointerUp: e => object.handlePointerUp(e), style: {
29525
+ position: 'absolute',
29526
+ left: '0',
29527
+ top: '0',
29528
+ width: object.totalWidth + 'px',
29529
+ height: object.totalHeight + 'px',
29530
+ transform: object.rotationDegrees !== 0 ? `rotate(${object.rotationDegrees}deg)` : undefined,
29531
+ transformOrigin: object.rotationDegrees !== 0 ? `${object.totalWidth / 2}px ${object.totalHeight / 2}px` : undefined,
29532
+ pointerEvents: object.markedForRemoval ? 'none' : 'auto',
29533
+ overflow: 'visible',
29534
+ } }, h("svg", { xmlns: "http://www.w3.org/2000/svg", style: {
29535
+ position: 'absolute',
29536
+ top: '0',
29537
+ left: '0',
29538
+ width: '100%',
29539
+ height: '100%',
29540
+ overflow: 'visible',
29541
+ pointerEvents: 'none',
29542
+ }, viewBox: object.viewBox, preserveAspectRatio: "none" }, h("path", { d: object.getSvgPath(), fill: KritzelColorHelper.applyOpacity(object.fillColor, effectiveOpacity, currentTheme), stroke: strokeMatchesFill ? 'none' : KritzelColorHelper.applyOpacity(object.strokeColor, effectiveOpacity, currentTheme), "stroke-width": strokeMatchesFill ? 0 : object.strokeWidth })), h("div", { ref: el => el && object.mountTextEditor(el), style: {
29543
+ position: 'absolute',
29544
+ top: '0',
29545
+ left: '0',
29546
+ width: '100%',
29547
+ height: '100%',
29548
+ display: 'flex',
29549
+ alignItems: 'center',
29550
+ justifyContent: 'center',
29551
+ textAlign: 'center',
29552
+ overflow: 'hidden',
29553
+ pointerEvents: object.isEditing ? 'auto' : 'none',
29554
+ } })));
29555
+ })(), this.core.store.state.debugInfo.showObjectInfo && (h("div", { style: {
29342
29556
  pointerEvents: 'none',
29343
29557
  position: 'absolute',
29344
29558
  left: `${object.totalWidth}px`,
29345
29559
  top: '0',
29346
29560
  zIndex: (object.zIndex + 2).toString(),
29347
- } }, h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "Id: ", object.id), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "userId: ", object.userId), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "width: ", object.width), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "height: ", object.height), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "translateX: ", object.translateX), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "translateY: ", object.translateY), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "scale: ", object.scale), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "rotationDegrees: ", object.rotationDegrees), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "zIndex: ", object.zIndex), KritzelClassHelper.isInstanceOf(object, 'KritzelImage') && (h("div", null, h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "assetId: ", object.assetId), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "mimeType: ", object.mimeType))))), (this.core.displaySelectionGroupUI(object) || this.core.displaySelectionLineUI(object)) &&
29561
+ } }, h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "Id: ", object.id), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "userId: ", object.userId), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "width: ", object.width), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "height: ", object.height), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "translateX: ", object.translateX), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "translateY: ", object.translateY), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "scale: ", object.scale), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "rotationDegrees: ", object.rotationDegrees), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "zIndex: ", object.zIndex), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "isSelected: ", object.isSelected ? 'true' : 'false'), KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement') && (h("div", null, h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "isInteractive: ", object.isInteractive ? 'true' : 'false'))), KritzelClassHelper.isInstanceOf(object, 'KritzelImage') && (h("div", null, h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "assetId: ", object.assetId), h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "mimeType: ", object.mimeType))))), (this.core.displaySelectionGroupUI(object) || this.core.displaySelectionLineUI(object)) &&
29348
29562
  (() => {
29349
29563
  const isSelectionGroup = KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup');
29350
29564
  const localClientId = this.core.store.objects?.localClientId;
29351
29565
  const awarenessStates = this.core.store.objects?.awareness?.getStates();
29352
- const isRemoteSelection = isSelectionGroup && (
29353
- // Different user
29354
- (object.userId != null && this.core.user?.id != null && object.userId !== this.core.user.id) ||
29355
- // Same user but different client that is still actively connected (e.g. same account on another device)
29356
- (object.userId != null && this.core.user?.id != null && object.userId === this.core.user.id &&
29357
- object.clientId != null && localClientId != null && object.clientId !== localClientId &&
29358
- awarenessStates?.has(object.clientId) === true));
29566
+ const isRemoteSelection = isSelectionGroup &&
29567
+ // Different user
29568
+ ((object.userId != null && this.core.user?.id != null && object.userId !== this.core.user.id) ||
29569
+ // Same user but different client that is still actively connected (e.g. same account on another device)
29570
+ (object.userId != null &&
29571
+ this.core.user?.id != null &&
29572
+ object.userId === this.core.user.id &&
29573
+ object.clientId != null &&
29574
+ localClientId != null &&
29575
+ object.clientId !== localClientId &&
29576
+ awarenessStates?.has(object.clientId) === true));
29359
29577
  let remoteUserColor;
29360
29578
  if (isRemoteSelection) {
29361
29579
  if (awarenessStates) {
@@ -29374,10 +29592,10 @@ const KritzelEngine = class {
29374
29592
  }
29375
29593
  }
29376
29594
  }
29377
- const selectionBorderColor = remoteUserColor
29378
- ?? (KritzelColorHelper.resolveThemeColor(object.borderColor, currentTheme) || 'var(--kritzel-selection-border-color, #007AFF)');
29379
- const selectionHandleStrokeColor = remoteUserColor
29380
- ?? 'var(--kritzel-selection-handle-stroke-color, #007AFF)';
29595
+ const selectionBorderColor = remoteUserColor ?? (KritzelColorHelper.resolveThemeColor(object.borderColor, currentTheme) || 'var(--kritzel-selection-border-color, #007AFF)');
29596
+ const selectionHandleStrokeColor = remoteUserColor ?? 'var(--kritzel-selection-handle-stroke-color, #007AFF)';
29597
+ const handleScale = object.scale ?? 1;
29598
+ const { canRenderResizeHandles, canRenderRotationHandle } = this.getSelectionHandleCapabilities(object);
29381
29599
  return (h("svg", { xmlns: "http://www.w3.org/2000/svg", style: {
29382
29600
  zIndex: (object.zIndex + 1).toString(),
29383
29601
  height: object?.totalHeight.toString(),
@@ -29421,50 +29639,50 @@ const KritzelEngine = class {
29421
29639
  strokeWidth: `calc(var(--kritzel-selection-border-width, 2px) * ${object.scale} / ${this.core.store.state?.scale})`,
29422
29640
  strokeLinecap: 'square',
29423
29641
  } })));
29424
- })(), !this.isSelecting && !isRemoteSelection && (h("g", { class: "selection-group-handles", style: { pointerEvents: 'auto' } }, 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: {
29642
+ })(), !this.isSelecting && !isRemoteSelection && (h("g", { class: "selection-group-handles", style: { pointerEvents: 'auto' } }, canRenderResizeHandles && (h("g", null, 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: {
29425
29643
  fill: 'var(--kritzel-selection-handle-color, #ffffff)',
29426
29644
  stroke: selectionHandleStrokeColor,
29427
- strokeWidth: `calc(2px * ${object.scale} / ${this.core.store.state?.scale})`,
29645
+ strokeWidth: `calc(2px * ${handleScale} / ${this.core.store.state?.scale})`,
29428
29646
  paintOrder: 'fill',
29429
- } }), 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: {
29647
+ } }), 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: {
29430
29648
  fill: 'transparent',
29431
29649
  paintOrder: 'fill',
29432
- } }), 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: {
29650
+ } }), 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: {
29433
29651
  fill: 'var(--kritzel-selection-handle-color, #ffffff)',
29434
29652
  stroke: selectionHandleStrokeColor,
29435
- strokeWidth: `calc(2px * ${object.scale} / ${this.core.store.state?.scale})`,
29653
+ strokeWidth: `calc(2px * ${handleScale} / ${this.core.store.state?.scale})`,
29436
29654
  paintOrder: 'fill',
29437
- } }), 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: {
29655
+ } }), 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: {
29438
29656
  fill: 'transparent',
29439
29657
  paintOrder: 'fill',
29440
- } }), 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: {
29658
+ } }), 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: {
29441
29659
  fill: 'var(--kritzel-selection-handle-color, #ffffff)',
29442
29660
  stroke: selectionHandleStrokeColor,
29443
- strokeWidth: `calc(2px * ${object.scale} / ${this.core.store.state?.scale})`,
29661
+ strokeWidth: `calc(2px * ${handleScale} / ${this.core.store.state?.scale})`,
29444
29662
  paintOrder: 'fill',
29445
- } }), 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: {
29663
+ } }), 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: {
29446
29664
  fill: 'transparent',
29447
29665
  paintOrder: 'fill',
29448
- } }), 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: {
29666
+ } }), 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: {
29449
29667
  fill: 'var(--kritzel-selection-handle-color, #ffffff)',
29450
29668
  stroke: selectionHandleStrokeColor,
29451
- strokeWidth: `calc(2px * ${object.scale} / ${this.core.store.state?.scale})`,
29669
+ strokeWidth: `calc(2px * ${handleScale} / ${this.core.store.state?.scale})`,
29452
29670
  paintOrder: 'fill',
29453
- } }), 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: {
29671
+ } }), 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: {
29454
29672
  fill: 'transparent',
29455
29673
  paintOrder: 'fill',
29456
- } }), h("line", { x1: object.totalWidth / 2, y1: "0", x2: object.totalWidth / 2, y2: -((15 * object.scale) / this.core.store.state?.scale), style: {
29674
+ } }))), canRenderRotationHandle && (h("g", null, h("line", { x1: object.totalWidth / 2, y1: "0", x2: object.totalWidth / 2, y2: -((15 * handleScale) / this.core.store.state?.scale), style: {
29457
29675
  stroke: selectionBorderColor,
29458
- strokeWidth: `calc(var(--kritzel-selection-border-width, 2px) * ${object.scale} / ${this.core.store.state?.scale})`,
29459
- } }), 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: {
29676
+ strokeWidth: `calc(var(--kritzel-selection-border-width, 2px) * ${handleScale} / ${this.core.store.state?.scale})`,
29677
+ } }), 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: {
29460
29678
  fill: 'var(--kritzel-selection-handle-color, #ffffff)',
29461
29679
  stroke: selectionHandleStrokeColor,
29462
- strokeWidth: `calc(2px * ${object.scale} / ${this.core.store.state?.scale})`,
29680
+ strokeWidth: `calc(2px * ${handleScale} / ${this.core.store.state?.scale})`,
29463
29681
  paintOrder: 'fill',
29464
- } }), 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: {
29682
+ } }), 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: {
29465
29683
  fill: 'transparent',
29466
29684
  paintOrder: 'fill',
29467
- } }))))), this.core.displaySelectionLineUI(object) && KritzelClassHelper.isInstanceOf(object, 'KritzelLine') && (h("g", { class: "selection-line-ui", style: { pointerEvents: 'none' } }, h("g", { class: "selection-line-borders" }, h("path", { class: "selection-line-border", d: this.core.anchorManager.computeClippedLinePath(object, true), style: {
29685
+ } }))))))), this.core.displaySelectionLineUI(object) && KritzelClassHelper.isInstanceOf(object, 'KritzelLine') && (h("g", { class: "selection-line-ui", style: { pointerEvents: 'none' } }, h("g", { class: "selection-line-borders" }, h("path", { class: "selection-line-border", d: this.core.anchorManager.computeClippedLinePath(object, true), style: {
29468
29686
  stroke: selectionBorderColor,
29469
29687
  strokeWidth: `calc(var(--kritzel-selection-border-width, 2px) * ${object.scale} / ${this.core.store.state?.scale})`,
29470
29688
  strokeLinecap: 'round',
@@ -29589,7 +29807,7 @@ const KritzelEngine = class {
29589
29807
  }, this.core.store.selectionGroup?.objects || []);
29590
29808
  }
29591
29809
  this.hideContextMenu();
29592
- }, onClose: () => this.hideContextMenu() })), this.core.store.objects?.hasAwareness && h("kritzel-awareness-cursors", { core: this.core }), this.core.store.state?.activeTool instanceof KritzelEraserTool && !this.core.store.state.isScaling && h("kritzel-cursor-trail", { core: this.core }), !this.core.licenseManager.isLicensed && (h("kritzel-watermark", { core: this.core, label: this.core.localizationManager.translate('watermark.poweredBy') }))));
29810
+ }, onClose: () => this.hideContextMenu() })), this.core.store.objects?.hasAwareness && h("kritzel-awareness-cursors", { core: this.core }), this.core.store.state?.activeTool instanceof KritzelEraserTool && !this.core.store.state.isScaling && h("kritzel-cursor-trail", { core: this.core }), !this.core.licenseManager.isLicensed && h("kritzel-watermark", { core: this.core, label: this.core.localizationManager.translate('watermark.poweredBy') })));
29593
29811
  }
29594
29812
  static get watchers() { return {
29595
29813
  "workspace": [{
@@ -30987,7 +31205,7 @@ const KritzelPortal = class {
30987
31205
  * This file is auto-generated by the version bump scripts.
30988
31206
  * Do not modify manually.
30989
31207
  */
30990
- const KRITZEL_VERSION = '0.3.22';
31208
+ const KRITZEL_VERSION = '0.3.23';
30991
31209
 
30992
31210
  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)}`;
30993
31211
 
@@ -31003,6 +31221,7 @@ const DEFAULT_DEBUG_INFO = {
31003
31221
  showObjectInfo: false,
31004
31222
  showSyncProviderInfo: true,
31005
31223
  showMigrationInfo: true,
31224
+ showAssetResolverInfo: false,
31006
31225
  };
31007
31226
  const SETTINGS_CATEGORY_IDS = ['general', 'viewport', 'shortcuts', 'developer', 'about'];
31008
31227
  const KritzelSettings = class {
@@ -31211,7 +31430,7 @@ const KritzelSettings = class {
31211
31430
  }
31212
31431
  }
31213
31432
  render() {
31214
- return (h(Host, { key: '6f26bd23bf7bb51653f09b96bafef221c51c2bfa' }, h("kritzel-dialog", { key: '49ffda0a422ae1c2c9c8e6e608bce532e7d4099e', isOpen: this.isDialogOpen, dialogTitle: this.t('settings.dialogTitle', 'Settings'), size: "large", contained: true, onDialogClose: this.closeDialog }, h("kritzel-master-detail", { key: 'ab5dc7952e555cae6f841ef4de2ab50d7039279b', items: this.categories, selectedItemId: this.selectedCategoryId, onItemSelect: this.handleCategorySelect }, this.renderCategoryContent()))));
31433
+ return (h(Host, { key: '8b96261465efeabb2eb0cf724d29699de5e230a8' }, h("kritzel-dialog", { key: '273a9df290bb97d59000a97f91821e7bad3f3896', isOpen: this.isDialogOpen, dialogTitle: this.t('settings.dialogTitle', 'Settings'), size: "large", contained: true, onDialogClose: this.closeDialog }, h("kritzel-master-detail", { key: '64d17dfaa2a0baf2d4a5d00fe5fc65ad40c96ec6', items: this.categories, selectedItemId: this.selectedCategoryId, onItemSelect: this.handleCategorySelect }, this.renderCategoryContent()))));
31215
31434
  }
31216
31435
  static get watchers() { return {
31217
31436
  "settings": [{