kritzel-stencil 0.3.18 → 0.3.20

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 (159) hide show
  1. package/dist/cjs/index-Xav9JFHg.js +2 -2
  2. package/dist/cjs/index.cjs.js +1 -1
  3. package/dist/cjs/{kritzel-active-users_44.cjs.entry.js → kritzel-active-users_45.cjs.entry.js} +739 -310
  4. package/dist/cjs/loader.cjs.js +1 -1
  5. package/dist/cjs/{schema.constants-DrHO_CYF.js → schema.constants-IBG5VW_J.js} +116 -59
  6. package/dist/cjs/stencil.cjs.js +1 -1
  7. package/dist/collection/classes/core/core.class.js +22 -210
  8. package/dist/collection/classes/handlers/key.handler.js +1 -22
  9. package/dist/collection/classes/managers/browser-permission.manager.js +64 -0
  10. package/dist/collection/classes/managers/clipboard.manager.js +401 -0
  11. package/dist/collection/classes/objects/image.class.js +42 -0
  12. package/dist/collection/classes/tools/image-tool.class.js +1 -59
  13. package/dist/collection/collection-manifest.json +1 -0
  14. package/dist/collection/components/core/kritzel-editor/kritzel-editor.css +53 -0
  15. package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +133 -53
  16. package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +101 -43
  17. package/dist/collection/components/core/kritzel-watermark/kritzel-watermark.js +1 -1
  18. package/dist/collection/components/shared/kritzel-avatar/kritzel-avatar.js +3 -3
  19. package/dist/collection/components/shared/kritzel-button/kritzel-button.js +2 -2
  20. package/dist/collection/components/shared/kritzel-color/kritzel-color.js +2 -2
  21. package/dist/collection/components/shared/kritzel-color-palette/kritzel-color-palette.js +1 -1
  22. package/dist/collection/components/shared/kritzel-font/kritzel-font.js +1 -1
  23. package/dist/collection/components/shared/kritzel-font-size/kritzel-font-size.js +1 -1
  24. package/dist/collection/components/shared/kritzel-input/kritzel-input.js +1 -1
  25. package/dist/collection/components/shared/kritzel-master-detail/kritzel-master-detail.js +3 -3
  26. package/dist/collection/components/shared/kritzel-menu/kritzel-menu.js +1 -1
  27. package/dist/collection/components/shared/kritzel-menu-item/kritzel-menu-item.js +2 -2
  28. package/dist/collection/components/shared/kritzel-numeric-input/kritzel-numeric-input.js +1 -1
  29. package/dist/collection/components/shared/kritzel-opacity-slider/kritzel-opacity-slider.js +1 -1
  30. package/dist/collection/components/shared/kritzel-portal/kritzel-portal.js +1 -1
  31. package/dist/collection/components/shared/kritzel-slide-toggle/kritzel-slide-toggle.js +1 -1
  32. package/dist/collection/components/shared/kritzel-split-button/kritzel-split-button.js +1 -1
  33. package/dist/collection/components/shared/kritzel-stroke-size/kritzel-stroke-size.js +1 -1
  34. package/dist/collection/components/shared/kritzel-tooltip/kritzel-tooltip.js +2 -2
  35. package/dist/collection/components/ui/kritzel-back-to-content/kritzel-back-to-content.js +1 -1
  36. package/dist/collection/components/ui/kritzel-controls/kritzel-controls.js +6 -6
  37. package/dist/collection/components/ui/kritzel-current-user/kritzel-current-user.js +1 -1
  38. package/dist/collection/components/ui/kritzel-current-user-dialog/kritzel-current-user-dialog.js +1 -1
  39. package/dist/collection/components/ui/kritzel-export/kritzel-export.js +1 -1
  40. package/dist/collection/components/ui/kritzel-login-dialog/kritzel-login-dialog.js +1 -1
  41. package/dist/collection/components/ui/kritzel-more-menu/kritzel-more-menu.js +1 -1
  42. package/dist/collection/components/ui/kritzel-notification-card/kritzel-notification-card.css +101 -0
  43. package/dist/collection/components/ui/kritzel-notification-card/kritzel-notification-card.js +173 -0
  44. package/dist/collection/components/ui/kritzel-settings/kritzel-settings.js +1 -1
  45. package/dist/collection/components/ui/kritzel-share-dialog/kritzel-share-dialog.js +2 -2
  46. package/dist/collection/components/ui/kritzel-utility-panel/kritzel-utility-panel.js +1 -1
  47. package/dist/collection/components/ui/kritzel-zoom-panel/kritzel-zoom-panel.js +1 -1
  48. package/dist/collection/configs/default-engine-config.js +0 -1
  49. package/dist/collection/constants/version.js +1 -1
  50. package/dist/collection/interfaces/notification.interface.js +1 -0
  51. package/dist/collection/themes/dark-theme.js +36 -0
  52. package/dist/collection/themes/light-theme.js +36 -0
  53. package/dist/components/index.d.ts +2 -0
  54. package/dist/components/index.js +1 -1
  55. package/dist/components/kritzel-active-users.js +1 -1
  56. package/dist/components/kritzel-avatar.js +1 -1
  57. package/dist/components/kritzel-awareness-cursors.js +1 -1
  58. package/dist/components/kritzel-back-to-content.js +1 -1
  59. package/dist/components/kritzel-button.js +1 -1
  60. package/dist/components/kritzel-color-palette.js +1 -1
  61. package/dist/components/kritzel-color.js +1 -1
  62. package/dist/components/kritzel-controls.js +1 -1
  63. package/dist/components/kritzel-current-user-dialog.js +1 -1
  64. package/dist/components/kritzel-current-user.js +1 -1
  65. package/dist/components/kritzel-editor.js +1 -1
  66. package/dist/components/kritzel-engine.js +1 -1
  67. package/dist/components/kritzel-export.js +1 -1
  68. package/dist/components/kritzel-font-size.js +1 -1
  69. package/dist/components/kritzel-font.js +1 -1
  70. package/dist/components/kritzel-input.js +1 -1
  71. package/dist/components/kritzel-login-dialog.js +1 -1
  72. package/dist/components/kritzel-master-detail.js +1 -1
  73. package/dist/components/kritzel-menu-item.js +1 -1
  74. package/dist/components/kritzel-menu.js +1 -1
  75. package/dist/components/kritzel-more-menu.js +1 -1
  76. package/dist/components/kritzel-notification-card.d.ts +11 -0
  77. package/dist/components/kritzel-notification-card.js +1 -0
  78. package/dist/components/kritzel-numeric-input.js +1 -1
  79. package/dist/components/kritzel-opacity-slider.js +1 -1
  80. package/dist/components/kritzel-portal.js +1 -1
  81. package/dist/components/kritzel-settings.js +1 -1
  82. package/dist/components/kritzel-share-dialog.js +1 -1
  83. package/dist/components/kritzel-slide-toggle.js +1 -1
  84. package/dist/components/kritzel-split-button.js +1 -1
  85. package/dist/components/kritzel-stroke-size.js +1 -1
  86. package/dist/components/kritzel-tool-config.js +1 -1
  87. package/dist/components/kritzel-tooltip.js +1 -1
  88. package/dist/components/kritzel-utility-panel.js +1 -1
  89. package/dist/components/kritzel-watermark.js +1 -1
  90. package/dist/components/kritzel-workspace-manager.js +1 -1
  91. package/dist/components/kritzel-zoom-panel.js +1 -1
  92. package/dist/components/{p-BvRrA4hN.js → p-6Ca7Eolf.js} +1 -1
  93. package/dist/components/{p-BpnIvNvq.js → p-BO1CnEOT.js} +1 -1
  94. package/dist/components/{p-CyqRcqsO.js → p-BPoDE87Q.js} +1 -1
  95. package/dist/components/p-BX-nQAyH.js +1 -0
  96. package/dist/components/{p-DNdXJp8F.js → p-Bg0xIZiR.js} +1 -1
  97. package/dist/components/{p-DmWSRsjK.js → p-BhODnvMO.js} +1 -1
  98. package/dist/components/{p-mz3pUWW4.js → p-Bm0aKWPw.js} +1 -1
  99. package/dist/components/{p-3HxnBrCM.js → p-BrAeYciD.js} +1 -1
  100. package/dist/components/{p-f8aW1ye7.js → p-BwuemYET.js} +1 -1
  101. package/dist/components/{p-U4oawa1x.js → p-C-RiEw9X.js} +1 -1
  102. package/dist/components/{p-DdH1cKED.js → p-C1jg5IQi.js} +1 -1
  103. package/dist/components/{p-SDZNC8GF.js → p-C4-Som48.js} +1 -1
  104. package/dist/components/{p-C9HGoDHE.js → p-C5TZjIE1.js} +1 -1
  105. package/dist/components/{p-7NsK0uHu.js → p-CC0J_kEP.js} +1 -1
  106. package/dist/components/{p-CIcLzcfA.js → p-CJEIUs0x.js} +1 -1
  107. package/dist/components/{p-Bzv9Px8v.js → p-CO0eEzos.js} +1 -1
  108. package/dist/components/{p-DDkmsPpV.js → p-COdwK_mi.js} +1 -1
  109. package/dist/components/{p-CPtDfadX.js → p-COj8EVzL.js} +1 -1
  110. package/dist/components/{p-CEnEDaix.js → p-CVV-HzFR.js} +1 -1
  111. package/dist/components/{p-6RjeGuvH.js → p-Cb5YwXbC.js} +1 -1
  112. package/dist/components/{p-DX5K8xnh.js → p-CbMLbDou.js} +1 -1
  113. package/dist/components/{p-DZdgXCAx.js → p-CdkNxxut.js} +1 -1
  114. package/dist/components/{p-BKJSh8qQ.js → p-CpR9lc-o.js} +1 -1
  115. package/dist/components/p-CpzicliK.js +1 -0
  116. package/dist/components/{p-IpoC5EEY.js → p-D0RYktBV.js} +1 -1
  117. package/dist/components/{p-DI4vQRE3.js → p-D0v9WP5w.js} +1 -1
  118. package/dist/components/{p-DgmtCdnL.js → p-D28ZylHn.js} +1 -1
  119. package/dist/components/{p-F5_X4dZG.js → p-DEpnJBA3.js} +1 -1
  120. package/dist/components/{p-BCNyR5Sw.js → p-DRsD38Nl.js} +1 -1
  121. package/dist/components/p-DWhWFDcC.js +9 -0
  122. package/dist/components/{p-Dz-Ti24X.js → p-DeqALrzV.js} +1 -1
  123. package/dist/components/{p-BKvHg9cv.js → p-Ndib1Jb3.js} +1 -1
  124. package/dist/components/{p-vAeiXe6c.js → p-NwN6r0N0.js} +1 -1
  125. package/dist/components/{p-NuLP1xHe.js → p-W1ZwImdd.js} +1 -1
  126. package/dist/components/{p-Jn6TNdfe.js → p-YFR180v-.js} +1 -1
  127. package/dist/components/p-pagW8YF7.js +1 -0
  128. package/dist/components/{p-v7dxxrL5.js → p-vf28uSsc.js} +1 -1
  129. package/dist/esm/index-Dhio9uis.js +2 -2
  130. package/dist/esm/index.js +2 -2
  131. package/dist/esm/{kritzel-active-users_44.entry.js → kritzel-active-users_45.entry.js} +739 -311
  132. package/dist/esm/loader.js +1 -1
  133. package/dist/esm/{schema.constants-DchTXG3V.js → schema.constants-D35cwCYd.js} +116 -60
  134. package/dist/esm/stencil.js +1 -1
  135. package/dist/stencil/index.esm.js +1 -1
  136. package/dist/stencil/p-D35cwCYd.js +1 -0
  137. package/dist/stencil/p-e8e69a16.entry.js +9 -0
  138. package/dist/stencil/stencil.esm.js +1 -1
  139. package/dist/types/classes/core/core.class.d.ts +16 -19
  140. package/dist/types/classes/handlers/key.handler.d.ts +1 -1
  141. package/dist/types/classes/managers/browser-permission.manager.d.ts +41 -0
  142. package/dist/types/classes/managers/clipboard.manager.d.ts +86 -0
  143. package/dist/types/classes/objects/image.class.d.ts +10 -0
  144. package/dist/types/classes/tools/image-tool.class.d.ts +0 -14
  145. package/dist/types/components/core/kritzel-editor/kritzel-editor.d.ts +20 -1
  146. package/dist/types/components/core/kritzel-engine/kritzel-engine.d.ts +8 -4
  147. package/dist/types/components/ui/kritzel-notification-card/kritzel-notification-card.d.ts +22 -0
  148. package/dist/types/components.d.ts +83 -9
  149. package/dist/types/constants/version.d.ts +1 -1
  150. package/dist/types/interfaces/engine-state.interface.d.ts +0 -3
  151. package/dist/types/interfaces/notification.interface.d.ts +10 -0
  152. package/dist/types/interfaces/theme.interface.d.ts +43 -0
  153. package/package.json +8 -2
  154. package/readme.md +7 -111
  155. package/dist/components/p-Bc55X65h.js +0 -1
  156. package/dist/components/p-C_fKgKHu.js +0 -9
  157. package/dist/components/p-Cu9KYyoq.js +0 -1
  158. package/dist/stencil/p-86348986.entry.js +0 -9
  159. package/dist/stencil/p-DchTXG3V.js +0 -1
@@ -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 ThemeHelper, B as darkTheme, z as lightTheme, X as KritzelSelectionGroup, Y as KritzelSelectionBox, Z as KritzelIconRegistry, _ as KritzelKeyboardHelper, $ as KritzelBaseHandler, K as KritzelBaseObject, a0 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, a1 as ObjectHelper, o as KritzelCursorHelper, s as KritzelAnchorManager, t as KritzelThemeManager, u as KritzelLocalizationManager, v as KritzelLicenseManager, q as KritzelAssetResolver, a2 as KritzelClassHelper, a3 as KritzelEventHelper, W as WORKSPACE_EXPORT_VERSION } from './schema.constants-DchTXG3V.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';
3
3
  import * as Y from 'yjs';
4
4
  import 'y-indexeddb';
5
5
  import 'y-websocket';
@@ -132,16 +132,16 @@ const KritzelAvatar = class {
132
132
  height: `${this.size}px`,
133
133
  fontSize: `${Math.round(this.size * 0.4)}px`,
134
134
  };
135
- return (h(Host, { key: '0d372a5443f41835c2e8e5b33b58bcb6c1292e89', style: containerStyles, class: {
135
+ return (h(Host, { key: '4bdbe2c1d5d3a1708189166498ae4e36c19c46ff', style: containerStyles, class: {
136
136
  'has-image': !!showImage,
137
137
  'has-initials': !!showInitials,
138
138
  'has-default': !!showDefaultIcon,
139
- }, role: "img", "aria-label": this.getDisplayName() || 'User avatar' }, showImage && (h("img", { key: 'deb5f12115dd28b4b4ab2157cb2bbc9e48bb2a3f', src: imageUrl, alt: "", class: "avatar-image", ref: (el) => {
139
+ }, role: "img", "aria-label": this.getDisplayName() || 'User avatar' }, showImage && (h("img", { key: '55c3a13c9d514ea700c6b1a9339cd0dff1828b2b', src: imageUrl, alt: "", class: "avatar-image", ref: (el) => {
140
140
  if (el) {
141
141
  el.referrerPolicy = 'no-referrer';
142
142
  el.crossOrigin = 'anonymous';
143
143
  }
144
- }, onError: this.handleImageError })), showInitials && (h("span", { key: '6fc12f2d32923f33df5a8ec743d51434f67e6934', class: "avatar-initials", style: { backgroundColor: this.getBackgroundColor() } }, initials)), showDefaultIcon && (h("span", { key: 'd803b385f7eaa0659d7452231bfd46b1634f204d', class: "avatar-default" }, h("svg", { key: 'a3275ab33f58440abcd15c296d9c027dfab6495c', viewBox: "0 0 24 24", fill: "currentColor" }, h("path", { key: '31ac7e63fe6b1778c3d0cbd71b5c85791b79d084', d: "M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" }))))));
144
+ }, onError: this.handleImageError })), showInitials && (h("span", { key: 'b60cbe2b94ea1d2cd2a09d10c928290fc1469a4a', class: "avatar-initials", style: { backgroundColor: this.getBackgroundColor() } }, initials)), showDefaultIcon && (h("span", { key: 'e8a0f49952b5b7e5e4a1fe3d67e68a56081d5e4c', class: "avatar-default" }, h("svg", { key: '1fdc256fd53aa5c866bb0e81fdd40393c8a7d69d', viewBox: "0 0 24 24", fill: "currentColor" }, h("path", { key: '8f3a5a6775154e46a2f331534fe75671b9ad081a', d: "M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" }))))));
145
145
  }
146
146
  static get watchers() { return {
147
147
  "user": [{
@@ -451,7 +451,7 @@ const KritzelBackToContent = class {
451
451
  this.backToContent.emit();
452
452
  };
453
453
  render() {
454
- return (h(Host, { key: '5ef034156f28bdd1861d99343d51a237498db46b' }, h("button", { key: 'caa1c08d04f1a3a84d694b7df5d318a686d3a1fb', class: { 'back-to-content-button': true, visible: this.visible }, onClick: this.handleClick, "aria-label": this.text }, h("kritzel-icon", { key: '0c7cf5e18294ade600be3052ca7c57c8d933a736', name: "chevrons-left" }))));
454
+ return (h(Host, { key: 'a7d52616642f45a0c0e7b76deb3cc5994184b968' }, h("button", { key: '9e7acae2b62f7f66ee0127545ee8ab115f9d340e', class: { 'back-to-content-button': true, visible: this.visible }, onClick: this.handleClick, "aria-label": this.text }, h("kritzel-icon", { key: '6c404376643554cdb86c33957c066dd4580f19f1', name: "chevrons-left" }))));
455
455
  }
456
456
  };
457
457
  KritzelBackToContent.style = kritzelBackToContentCss();
@@ -478,11 +478,11 @@ const KritzelButton = class {
478
478
  this.buttonClick.emit();
479
479
  };
480
480
  render() {
481
- return (h(Host, { key: 'aa366907b97e648ac73182a43d099baf457e0ba3' }, h("button", { key: 'e80875396a791606461b1a20e0f7e1c896f54073', type: this.type, class: {
481
+ return (h(Host, { key: '0de2b5dcf75de9f5b7de0c6b876bd18733bf21b9' }, h("button", { key: '5ab9d80167c6b90add7779d4b1c3ebb80fafaed5', type: this.type, class: {
482
482
  'kritzel-button': true,
483
483
  [this.variant]: true,
484
484
  'disabled': this.disabled,
485
- }, disabled: this.disabled, onClick: this.handleClick }, h("slot", { key: '8f4e57a73837adfbf91f678c54558fe4c737aa91' }))));
485
+ }, disabled: this.disabled, onClick: this.handleClick }, h("slot", { key: 'dc105f7d1f87aa55c314d942a1cc6522b272526a' }))));
486
486
  }
487
487
  };
488
488
  KritzelButton.style = kritzelButtonCss();
@@ -530,13 +530,13 @@ const KritzelColorComponent = class {
530
530
  render() {
531
531
  const resolvedColor = this.resolveColor();
532
532
  const isColorVeryLight = this.isLightColor(resolvedColor);
533
- return (h(Host, { key: '21ce0ecb616266ec4953468ae9bbe0b72259c2da' }, h("div", { key: '02c8e8684b3b0819755efd1f160bdee662428ddd', class: "checkerboard-bg", style: {
533
+ return (h(Host, { key: '0d56c7ad01eddd523ebcf11a32e68367c96090bd' }, h("div", { key: '0bef4cb73804b98e6eea6b3cf006c19c8786bdb9', class: "checkerboard-bg", style: {
534
534
  width: `${this.size}px`,
535
535
  height: `${this.size}px`,
536
536
  borderRadius: '50%',
537
537
  display: 'inline-block',
538
538
  position: 'relative',
539
- } }, h("div", { key: '0063e9a701994869adbd3022b5ace542e85dfd95', class: {
539
+ } }, h("div", { key: '5be9714c3ddd56ae77c2870333ff97a59b58bd45', class: {
540
540
  'color-circle': true,
541
541
  'white': isColorVeryLight,
542
542
  }, style: {
@@ -592,7 +592,7 @@ const KritzelColorPalette = class {
592
592
  render() {
593
593
  const displayedColors = this.isExpanded ? this.colors : this.colors.slice(0, 6);
594
594
  const expandedHeight = this.isExpanded ? this.calculateHeight() : '32px';
595
- return (h(Host, { key: '9910a765816f45d420ad27c8fc7b02811380783b' }, h("div", { key: '91f3dae04338f55ab21831e4cac65f9d855c58c6', class: {
595
+ return (h(Host, { key: '6d003ec5b92f586532782144c25ca5a733741f20' }, h("div", { key: '031838b45da4916aa91d87c066ac950cfe3a1ddc', class: {
596
596
  'color-grid': true,
597
597
  'expanded': this.isExpanded,
598
598
  }, style: {
@@ -1160,13 +1160,13 @@ const KritzelControls = class {
1160
1160
  // Separate tool controls from config control
1161
1161
  const toolControls = this.internalControls.filter(c => c.type === 'tool' || c.type === 'separator');
1162
1162
  const configControl = this.internalControls.find(c => c.type === 'config' && c.name === this.firstConfig?.name);
1163
- return (h(Host, { key: '7247a377d1cb75153e35f5308e81b2bd00d98578', style: { display: this.visible ? '' : 'none' }, class: {
1163
+ return (h(Host, { key: '266e38cdeffb8c22a7f778e865e5fab6b461f5b6', style: { display: this.visible ? '' : 'none' }, class: {
1164
1164
  mobile: this.isTouchDevice,
1165
- } }, this.isUtilityPanelVisible && (h("kritzel-utility-panel", { key: '075577cd15f3e577a1a216b5b1f1874e82e0d123', style: {
1165
+ } }, this.isUtilityPanelVisible && (h("kritzel-utility-panel", { key: '5e75ece000ac02675d3d226461b7f522dcf5ead9', style: {
1166
1166
  position: 'absolute',
1167
1167
  bottom: '56px',
1168
1168
  left: '12px',
1169
- }, undoState: this.undoState, terms: this.terms, onUndo: () => this.kritzelEngine?.undo(), onRedo: () => this.kritzelEngine?.redo(), onDelete: () => this.kritzelEngine?.delete() })), h("div", { key: '7fb1041763c7b64917e337d68bbb29b36ed86b9a', class: "kritzel-controls" }, h("div", { key: '8577ed9c6f43a667aaba7dce1da519456c8ad210', class: { 'scroll-indicator-left': true, 'visible': this.canScrollLeft } }), h("div", { key: '42f8f2b85055705674f9e319056bd48856dbd197', class: "kritzel-tools-scroll", ref: el => (this.toolsScrollRef = el), onScroll: this.handleToolsScroll }, toolControls.map(control => {
1169
+ }, undoState: this.undoState, terms: this.terms, onUndo: () => this.kritzelEngine?.undo(), onRedo: () => this.kritzelEngine?.redo(), onDelete: () => this.kritzelEngine?.delete() })), h("div", { key: 'fc98b1675afe1abb6a8d391c4ace34c7f0e46677', class: "kritzel-controls" }, h("div", { key: 'b17bc9e9a65646735c56f78a326a281229fa9790', class: { 'scroll-indicator-left': true, 'visible': this.canScrollLeft } }), h("div", { key: '9ab9d5c44e515551d75f27c31875de72e9d3407c', class: "kritzel-tools-scroll", ref: el => (this.toolsScrollRef = el), onScroll: this.handleToolsScroll }, toolControls.map(control => {
1170
1170
  // Check if this control has sub-options (split-button)
1171
1171
  if (control.subOptions?.length) {
1172
1172
  const selectedSubOption = this.getSelectedSubOption(control);
@@ -1196,10 +1196,10 @@ const KritzelControls = class {
1196
1196
  'kritzel-control': true,
1197
1197
  'selected': this.activeControl?.name === control?.name,
1198
1198
  }, key: control.name, "data-testid": `tool-${control.name}`, onClick: _event => this.handleControlClick?.(control), "aria-label": control.name.charAt(0).toUpperCase() + control.name.slice(1) }, h("kritzel-icon", { name: control.icon })));
1199
- })), h("div", { key: '20007273e0f701193502320cf58099cbaedb834e', class: { 'scroll-indicator-right': true, 'visible': this.canScrollRight && !(configControl && this.activeControl && hasConfigUI) } }), configControl && this.activeControl && (h("div", { class: {
1199
+ })), h("div", { key: 'fad7512f5d1aa68905a56b8f22d498380f166f9f', class: { 'scroll-indicator-right': true, 'visible': this.canScrollRight && !(configControl && this.activeControl && hasConfigUI) } }), configControl && this.activeControl && (h("div", { class: {
1200
1200
  'kritzel-config-container': true,
1201
1201
  'visible': hasConfigUI,
1202
- }, key: configControl.name }, h("div", { key: '2901fc2507e5b02fbd603bd2d2a4e2b07e35c970', class: { 'config-gradient-left': true, 'visible': this.needsScrolling } }), h("kritzel-tooltip", { key: '653e6a9b49146a2f297dbb5b9debe8709efd59ad', anchorElement: this.host.shadowRoot?.querySelector('.kritzel-config-container'), triggerElement: this.configTriggerRef }, h("kritzel-tool-config", { key: '6fae38444dff1978647e111643ee6e29c9cc1653', tool: this.activeControl.tool, theme: this.theme, engine: this.kritzelEngine, terms: this.terms, onToolChange: event => this.handleToolChange?.(event), onDisplayValuesChange: this.handleDisplayValuesChange, style: { width: '100%', height: '100%' } })), h("div", { key: '951a52f625c929634de5102b83a14ad0cf1abc8d', tabIndex: hasConfigUI ? 0 : -1, class: "kritzel-config", "data-testid": "tool-config", ref: el => {
1202
+ }, key: configControl.name }, h("div", { key: '2ff7cb3ad18608102f3d8188b5936cf00b00451f', class: { 'config-gradient-left': true, 'visible': this.needsScrolling } }), h("kritzel-tooltip", { key: '10f15f717c8573cbad6ea53b412ad56254099ad1', anchorElement: this.host.shadowRoot?.querySelector('.kritzel-config-container'), triggerElement: this.configTriggerRef }, h("kritzel-tool-config", { key: 'a1b0ed8ecc2696a591e32add642a3d5cb2bf7f4a', tool: this.activeControl.tool, theme: this.theme, engine: this.kritzelEngine, terms: this.terms, onToolChange: event => this.handleToolChange?.(event), onDisplayValuesChange: this.handleDisplayValuesChange, style: { width: '100%', height: '100%' } })), h("div", { key: '7ac8214a3dbad5323a38cad3ff7324b57f9dbfb5', tabIndex: hasConfigUI ? 0 : -1, class: "kritzel-config", "data-testid": "tool-config", ref: el => {
1203
1203
  if (el)
1204
1204
  this.configTriggerRef = el;
1205
1205
  }, onKeyDown: event => {
@@ -1208,7 +1208,7 @@ const KritzelControls = class {
1208
1208
  }
1209
1209
  }, style: {
1210
1210
  cursor: 'pointer',
1211
- } }, this.displayValues && (h("div", { key: 'b8fff73b9055e86b312a9f0798fff4177cf85970', class: "color-container" }, h("kritzel-color", { key: 'fcd474e557d887f94383b7e583ab1c95a675ce23', value: this.displayValues.color, theme: this.theme, size: 18, style: {
1211
+ } }, this.displayValues && (h("div", { key: '7e64f79859f7f2b03d72bb7a93fb295e7d2b2ac0', class: "color-container" }, h("kritzel-color", { key: 'b87d9861949379d84954ef1ecb554e13317b3287', value: this.displayValues.color, theme: this.theme, size: 18, style: {
1212
1212
  borderRadius: '50%',
1213
1213
  border: 'none',
1214
1214
  } })))))))));
@@ -1248,7 +1248,7 @@ const KritzelCurrentUser = class {
1248
1248
  this.dialogRef?.open();
1249
1249
  };
1250
1250
  render() {
1251
- return (h(Host, { key: '7e45048e532db84347e2531de2df19f5537d62ca' }, h("kritzel-avatar", { key: '5a67d1dbae041e16cdaeaf4bc678b6b00c56f442', user: this.user, size: this.avatarSize, onClick: this.handleAvatarClick }), h("kritzel-current-user-dialog", { key: '8e35219e282811b8bbafb058c02c8c0474574009', ref: el => (this.dialogRef = el), user: this.user, terms: this.terms })));
1251
+ return (h(Host, { key: 'bdee6b71c08d6ea22aec40e88ba56077882b301d' }, h("kritzel-avatar", { key: '97bcaaa6b683c0d26d547ac5cbd9df7814f0cd80', user: this.user, size: this.avatarSize, onClick: this.handleAvatarClick }), h("kritzel-current-user-dialog", { key: 'c984ea1803bcf2b06569fd66d12e4e7054235225', ref: el => (this.dialogRef = el), user: this.user, terms: this.terms })));
1252
1252
  }
1253
1253
  };
1254
1254
  KritzelCurrentUser.style = kritzelCurrentUserCss();
@@ -1282,7 +1282,7 @@ const KritzelCurrentUserDialog = class {
1282
1282
  }
1283
1283
  render() {
1284
1284
  const displayName = this.getDisplayName();
1285
- return (h(Host, { key: '7c9e5c19249d400e2d670c60f5d6716c742adc62' }, h("kritzel-dialog", { key: '811da6bff0ce03914f545dd289878ba04924e85c', dialogTitle: this.terms['currentUser.dialogTitle'] ?? 'Account', isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, size: "small", contained: true }, h("div", { key: '03a101b04d61882732547d91e81a2bacb3aa4df8', class: "user-info" }, h("kritzel-avatar", { key: '4033d0e2322d7a25231f01115ec33f20e4fb0d4c', user: this.user, size: 80 }), displayName && h("div", { key: 'cf3a3aecb84152736c9de119f82a79f1b2b32f6f', class: "user-name" }, displayName), this.user?.email && h("div", { key: '5725d71dd80468c64a2b0bb1d9d967232513fb63', class: "user-email" }, this.user.email)))));
1285
+ return (h(Host, { key: '042455d68a432e0e218d5b8750fb1d22b0d202fc' }, h("kritzel-dialog", { key: '058abbc8e1c38c647cc02e85c272e89693cab22a', dialogTitle: this.terms['currentUser.dialogTitle'] ?? 'Account', isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, size: "small", contained: true }, h("div", { key: 'ee141b2e91f093527e2d8f6649bade8e1a14f130', class: "user-info" }, h("kritzel-avatar", { key: '2734bb0d3c69df14d26c204eeaf726174afaabc0', user: this.user, size: 80 }), displayName && h("div", { key: '56cbad7a51264ebada0cb029e6455aabb9cb4ae9', class: "user-name" }, displayName), this.user?.email && h("div", { key: 'ef9075dfdeb52ce6f90bb41d1348fbdf7dbb93ee', class: "user-email" }, this.user.email)))));
1286
1286
  }
1287
1287
  };
1288
1288
  KritzelCurrentUserDialog.style = kritzelCurrentUserDialogCss();
@@ -2171,7 +2171,7 @@ const DEFAULT_SYNC_CONFIG = {
2171
2171
  providers: [],
2172
2172
  };
2173
2173
 
2174
- const kritzelEditorCss = () => `kritzel-editor{display:flex;margin:0;position:relative;container-type:inline-size;overflow:hidden;width:100%;height:100%;align-items:center;justify-content:center;touch-action:manipulation;user-select:none;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-align:start;line-height:normal}kritzel-controls{position:absolute;left:0;right:0;margin-inline:auto;width:max-content;max-width:calc(100% - 16px);bottom:var(--kritzel-editor-controls-bottom, 14px);transition:transform var(--kritzel-editor-controls-transition-duration, 0.1s) var(--kritzel-editor-controls-transition, ease-in-out)}kritzel-controls.keyboard-open{transform:var(--kritzel-editor-controls-transform, translateY(300%))}.top-left-buttons{position:absolute;top:var(--kritzel-editor-top-left-buttons-top, 14px);left:var(--kritzel-editor-top-left-buttons-left, 14px);display:flex;align-items:flex-start;gap:8px}.top-right-buttons{position:absolute;top:var(--kritzel-editor-top-right-buttons-top, 14px);right:var(--kritzel-editor-top-right-buttons-right, 14px);display:flex;align-items:center;gap:8px}.bottom-left-buttons{position:absolute;left:var(--kritzel-editor-top-left-buttons-left, 14px);bottom:var(--kritzel-editor-controls-bottom, 14px);display:flex;align-items:flex-end}@container (max-width: 767px){.bottom-left-buttons{display:none}}.top-right-button{display:flex;align-items:center;justify-content:center;width:50px;height:50px;padding:0;border:var(--kritzel-split-button-border, 1px solid #ebebeb);border-radius:var(--kritzel-split-button-border-radius, 12px);background-color:var(--kritzel-split-button-background-color, #ffffff);cursor:var(--kritzel-global-pointer-cursor, pointer);box-shadow:var(--kritzel-split-button-box-shadow, 0 0 3px rgba(0, 0, 0, 0.08));transition:background-color 150ms ease;-webkit-tap-highlight-color:transparent}.top-right-button:hover{background-color:#f5f5f5}.top-right-button:active{background-color:#ebebeb}`;
2174
+ const kritzelEditorCss = () => `kritzel-editor{display:flex;margin:0;position:relative;container-type:inline-size;overflow:hidden;width:100%;height:100%;align-items:center;justify-content:center;touch-action:manipulation;user-select:none;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-align:start;line-height:normal}kritzel-controls{position:absolute;left:0;right:0;margin-inline:auto;width:max-content;max-width:calc(100% - 16px);bottom:var(--kritzel-editor-controls-bottom, 14px);transition:transform var(--kritzel-editor-controls-transition-duration, 0.1s) var(--kritzel-editor-controls-transition, ease-in-out)}kritzel-controls.keyboard-open{transform:var(--kritzel-editor-controls-transform, translateY(300%))}.top-left-buttons{position:absolute;top:var(--kritzel-editor-top-left-buttons-top, 14px);left:var(--kritzel-editor-top-left-buttons-left, 14px);display:flex;align-items:flex-start;gap:8px}.top-center-notification-layer{position:absolute;top:var(--kritzel-editor-top-center-notification-top, 14px);left:0;right:0;z-index:var(--kritzel-editor-top-center-notification-z-index, 10001);display:flex;justify-content:center;pointer-events:none}.top-center-notification-container{width:min(90vw, 420px);pointer-events:auto;animation:kritzel-notification-slide-in var(--kritzel-editor-notification-animation-duration, 220ms) var(--kritzel-editor-notification-animation-easing, cubic-bezier(0.22, 1, 0.36, 1))}.top-center-notification-container.is-dismissing{animation:kritzel-notification-fade-out var(--kritzel-editor-notification-dismiss-animation-duration, 180ms) var(--kritzel-editor-notification-dismiss-animation-easing, ease-in) forwards}@keyframes kritzel-notification-slide-in{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes kritzel-notification-fade-out{from{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-6px)}}@media (prefers-reduced-motion: reduce){.top-center-notification-container{animation:none}}.top-right-buttons{position:absolute;top:var(--kritzel-editor-top-right-buttons-top, 14px);right:var(--kritzel-editor-top-right-buttons-right, 14px);display:flex;align-items:center;gap:8px}.bottom-left-buttons{position:absolute;left:var(--kritzel-editor-top-left-buttons-left, 14px);bottom:var(--kritzel-editor-controls-bottom, 14px);display:flex;align-items:flex-end}@container (max-width: 767px){.bottom-left-buttons{display:none}}.top-right-button{display:flex;align-items:center;justify-content:center;width:50px;height:50px;padding:0;border:var(--kritzel-split-button-border, 1px solid #ebebeb);border-radius:var(--kritzel-split-button-border-radius, 12px);background-color:var(--kritzel-split-button-background-color, #ffffff);cursor:var(--kritzel-global-pointer-cursor, pointer);box-shadow:var(--kritzel-split-button-box-shadow, 0 0 3px rgba(0, 0, 0, 0.08));transition:background-color 150ms ease;-webkit-tap-highlight-color:transparent}.top-right-button:hover{background-color:#f5f5f5}.top-right-button:active{background-color:#ebebeb}`;
2175
2175
 
2176
2176
  const KritzelEditor = class {
2177
2177
  constructor(hostRef) {
@@ -2269,13 +2269,11 @@ const KritzelEditor = class {
2269
2269
  {
2270
2270
  label: 'menu.paste',
2271
2271
  icon: 'paste',
2272
- disabled: async () => (await this.engineRef.getCopiedObjects()).length === 0,
2273
2272
  action: menu => this.engineRef.paste(menu.x, menu.y),
2274
2273
  },
2275
2274
  {
2276
2275
  label: 'menu.selectAll',
2277
2276
  icon: 'select-all',
2278
- disabled: async () => (await this.engineRef.getObjectsInViewport()).length === 0,
2279
2277
  action: () => this.selectAllObjectsInViewport(),
2280
2278
  },
2281
2279
  ];
@@ -2286,7 +2284,6 @@ const KritzelEditor = class {
2286
2284
  label: 'menu.paste',
2287
2285
  icon: 'paste',
2288
2286
  group: 'clipboard',
2289
- disabled: async () => (await this.engineRef.getCopiedObjects()).length === 0,
2290
2287
  action: (menu, _) => this.engineRef.paste(menu.x, menu.y),
2291
2288
  },
2292
2289
  {
@@ -2304,7 +2301,6 @@ const KritzelEditor = class {
2304
2301
  label: 'menu.align',
2305
2302
  icon: 'align',
2306
2303
  group: 'other',
2307
- disabled: async () => (await this.engineRef.getSelectedObjects()).length < 2,
2308
2304
  children: [
2309
2305
  { label: 'menu.alignLeft', icon: 'align-start-vertical', action: () => this.engineRef.alignObjects(KritzelAlignment.StartHorizontal) },
2310
2306
  { label: 'menu.alignCenterHorizontal', icon: 'align-center-horizontal', action: () => this.engineRef.alignObjects(KritzelAlignment.CenterHorizontal) },
@@ -2322,16 +2318,11 @@ const KritzelEditor = class {
2322
2318
  {
2323
2319
  label: 'menu.group',
2324
2320
  icon: 'group',
2325
- disabled: async () => (await this.engineRef.getSelectedObjects()).length < 2,
2326
2321
  action: () => this.engineRef.group(),
2327
2322
  },
2328
2323
  {
2329
2324
  label: 'menu.ungroup',
2330
2325
  icon: 'ungroup',
2331
- disabled: async () => {
2332
- const selectedObjects = await this.engineRef.getSelectedObjects();
2333
- return !selectedObjects.some(obj => obj.__class__ === 'KritzelGroup');
2334
- },
2335
2326
  action: () => this.engineRef.ungroup(),
2336
2327
  },
2337
2328
  ],
@@ -2408,6 +2399,13 @@ const KritzelEditor = class {
2408
2399
  shortcuts = [];
2409
2400
  currentIsPublic = false;
2410
2401
  isEditorVisible = false;
2402
+ activeNotification;
2403
+ isNotificationDismissing = false;
2404
+ notificationDismissTimeout;
2405
+ notificationDismissAnimationTimeout;
2406
+ notificationDisplayDurationMs = 5000;
2407
+ notificationDismissAnimationDurationMs = 180;
2408
+ isNotificationHovered = false;
2411
2409
  onIsEngineReady(newValue) {
2412
2410
  if (newValue && this.isControlsReady) {
2413
2411
  this.checkIsReady();
@@ -2670,9 +2668,6 @@ const KritzelEditor = class {
2670
2668
  async findObjects(predicate) {
2671
2669
  return this.engineRef.findObjects(predicate);
2672
2670
  }
2673
- async getCopiedObjects() {
2674
- return this.engineRef.getCopiedObjects();
2675
- }
2676
2671
  async getObjectsInViewport() {
2677
2672
  return this.engineRef.getObjectsInViewport();
2678
2673
  }
@@ -2688,6 +2683,22 @@ const KritzelEditor = class {
2688
2683
  async getDisplayableShortcuts() {
2689
2684
  return this.engineRef.getDisplayableShortcuts();
2690
2685
  }
2686
+ /**
2687
+ * Triggers a UI notification programmatically.
2688
+ * @param notification - Notification payload. `id` and `timestamp` are optional.
2689
+ */
2690
+ async triggerNotification(notification) {
2691
+ const normalizedNotification = {
2692
+ ...notification,
2693
+ id: notification.id ?? ObjectHelper.generateUUID(),
2694
+ timestamp: notification.timestamp ?? new Date(),
2695
+ };
2696
+ this.clearNotificationDismissAnimationTimer();
2697
+ this.isNotificationDismissing = false;
2698
+ this.isNotificationHovered = false;
2699
+ this.activeNotification = normalizedNotification;
2700
+ this.scheduleNotificationDismiss();
2701
+ }
2691
2702
  engineRef;
2692
2703
  controlsRef;
2693
2704
  settingsRef;
@@ -2712,6 +2723,10 @@ const KritzelEditor = class {
2712
2723
  this.listenForMobileKeyboard();
2713
2724
  this.setOsSpecificCssVariables();
2714
2725
  }
2726
+ disconnectedCallback() {
2727
+ this.clearNotificationDismissTimer();
2728
+ this.clearNotificationDismissAnimationTimer();
2729
+ }
2715
2730
  async checkIsReady() {
2716
2731
  await customElements.whenDefined('kritzel-editor');
2717
2732
  await customElements.whenDefined('kritzel-workspace-manager');
@@ -2811,6 +2826,57 @@ const KritzelEditor = class {
2811
2826
  event.stopPropagation();
2812
2827
  this.awarenessChange.emit(event.detail);
2813
2828
  }
2829
+ handleNotificationsChange(event) {
2830
+ event.stopPropagation();
2831
+ this.clearNotificationDismissAnimationTimer();
2832
+ this.isNotificationDismissing = false;
2833
+ this.isNotificationHovered = false;
2834
+ this.activeNotification = event.detail;
2835
+ this.scheduleNotificationDismiss();
2836
+ }
2837
+ handleNotificationHoverChange(event) {
2838
+ event.stopPropagation();
2839
+ this.isNotificationHovered = event.detail;
2840
+ if (this.isNotificationHovered) {
2841
+ this.clearNotificationDismissTimer();
2842
+ return;
2843
+ }
2844
+ if (this.activeNotification && !this.isNotificationDismissing) {
2845
+ this.scheduleNotificationDismiss();
2846
+ }
2847
+ }
2848
+ scheduleNotificationDismiss() {
2849
+ this.clearNotificationDismissTimer();
2850
+ this.notificationDismissTimeout = setTimeout(() => {
2851
+ this.dismissNotification();
2852
+ }, this.notificationDisplayDurationMs);
2853
+ }
2854
+ clearNotificationDismissTimer() {
2855
+ if (this.notificationDismissTimeout) {
2856
+ clearTimeout(this.notificationDismissTimeout);
2857
+ this.notificationDismissTimeout = undefined;
2858
+ }
2859
+ }
2860
+ clearNotificationDismissAnimationTimer() {
2861
+ if (this.notificationDismissAnimationTimeout) {
2862
+ clearTimeout(this.notificationDismissAnimationTimeout);
2863
+ this.notificationDismissAnimationTimeout = undefined;
2864
+ }
2865
+ }
2866
+ dismissNotification = () => {
2867
+ this.clearNotificationDismissTimer();
2868
+ this.isNotificationHovered = false;
2869
+ if (!this.activeNotification || this.isNotificationDismissing) {
2870
+ return;
2871
+ }
2872
+ this.isNotificationDismissing = true;
2873
+ this.clearNotificationDismissAnimationTimer();
2874
+ this.notificationDismissAnimationTimeout = setTimeout(() => {
2875
+ this.activeNotification = undefined;
2876
+ this.isNotificationDismissing = false;
2877
+ this.notificationDismissAnimationTimeout = undefined;
2878
+ }, this.notificationDismissAnimationDurationMs);
2879
+ };
2814
2880
  handleSettingsChange(event) {
2815
2881
  this.scaleMin = event.detail.scaleMin;
2816
2882
  this.scaleMax = event.detail.scaleMax;
@@ -3049,31 +3115,31 @@ const KritzelEditor = class {
3049
3115
  const isLoggedIn = this.isLoggedIn;
3050
3116
  const shouldShowCurrentUser = isLoggedIn;
3051
3117
  const shouldShowLoginButton = this.isReady && !!this.loginConfig && !isLoggedIn;
3052
- return (h(Host, { key: '72238560a0f0275c506f59220277fdff7ab92c13', style: {
3118
+ return (h(Host, { key: '07fe246b722ca64cfbdd8cc3474e6288c39d1128', style: {
3053
3119
  opacity: this.isEditorVisible ? '1' : '0',
3054
3120
  visibility: this.isEditorVisible ? 'visible' : 'hidden',
3055
3121
  transition: 'opacity 0.2s ease-in-out, visibility 0.2s ease-in-out',
3056
- } }, h("div", { key: '14fd50ad857199f3b6be708fc4263aa2e69067de', class: "top-left-buttons" }, h("kritzel-workspace-manager", { key: '8788631c804770c67110c7e7906fe2034438ef9b', 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: '1840374d7353af2b050822dcd9c54be46e326278', visible: this.isBackToContentButtonVisible, text: this.resolvedTerms['backToContent.label'] ?? 'Back to content', onBackToContent: () => this.backToContent() })), h("kritzel-engine", { key: 'cdde0b65c811ee28fb4266afb96005c1fed24323', ref: el => {
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 => {
3057
3123
  if (el) {
3058
3124
  this.engineRef = el;
3059
3125
  }
3060
- }, 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) }), h("kritzel-controls", { key: '9b91d0cc3b3afba8894bd852bac911178c0ca82d', visible: this.isControlsVisible, class: { 'keyboard-open': this.isVirtualKeyboardOpen }, ref: el => {
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 => {
3061
3127
  if (el) {
3062
3128
  this.controlsRef = el;
3063
3129
  }
3064
- }, controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState ?? undefined, theme: this.theme, terms: this.resolvedTerms, onIsControlsReady: () => (this.isControlsReady = true) }), h("div", { key: '01f63c615eec68696532926b6d1efe2443e2d46a', class: "bottom-left-buttons" }, h("kritzel-zoom-panel", { key: '73a1fc5b0892b88d6c0a7c5debb42b7bb3f03a84', visible: this.isZoomPanelVisible, disabled: !this.isZoomingEnabled, zoomPercent: this.currentZoomPercent, terms: this.resolvedTerms, onZoomIn: () => this.zoomIn(), onZoomOut: () => this.zoomOut() })), h("div", { key: '94776978ba9dd68b2c5f62b141c074852585a1cd', class: "top-right-buttons" }, h("kritzel-settings", { key: '665a74511f8e511602d4d3437927e03af2c3c01c', ref: el => {
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 => {
3065
3131
  if (el) {
3066
3132
  this.settingsRef = el;
3067
3133
  }
3068
- }, 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: '6b52127d4634d92c3e7410a084f37970bd8c84e7', ref: el => {
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 => {
3069
3135
  if (el) {
3070
3136
  this.exportRef = el;
3071
3137
  }
3072
- }, 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: '002a01d361137f867efc2a0c91a6c0e9619d4359', users: this.activeUsers }), shouldShowCurrentUser && h("kritzel-current-user", { key: '6a9e0ad77997cbb10981d58afb176a7f7f4938aa', user: this.user, terms: this.resolvedTerms }), shouldShowLoginButton && (h("kritzel-button", { key: '5ba13725105d0af68c51fec455569ac7bbeaecde', onButtonClick: () => this.loginDialogRef?.open() }, this.resolvedTerms['login.dialogTitle'] ?? 'Sign in')), h("kritzel-more-menu", { key: 'f392a518fef108b395eb6d6681af9e06827e3780', items: this.moreMenuItems, visible: this.isMoreMenuVisible, terms: this.resolvedTerms }), h("kritzel-share-dialog", { key: '845a3153f5fd9b419f7d0b81be85f5e1ea0051cc', ref: el => {
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 => {
3073
3139
  if (el) {
3074
3140
  this.shareDialogRef = el;
3075
3141
  }
3076
- }, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, terms: this.resolvedTerms, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (h("kritzel-login-dialog", { key: '5a7b149d8e2c2acbda3d7818d8576d8ad249ef23', ref: el => {
3142
+ }, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, terms: this.resolvedTerms, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (h("kritzel-login-dialog", { key: '5e34b647ac4c3e18981042d765b4d443422c5db3', ref: el => {
3077
3143
  if (el) {
3078
3144
  this.loginDialogRef = el;
3079
3145
  }
@@ -21152,7 +21218,7 @@ class KritzelViewport {
21152
21218
  const NUDGE_AMOUNT = 10;
21153
21219
  /**
21154
21220
  * Handles keyboard events and manages keyboard shortcuts for the Kritzel canvas.
21155
- * Provides functionality for tool switching, clipboard operations, object manipulation,
21221
+ * Provides functionality for tool switching, object manipulation,
21156
21222
  * undo/redo, and navigation via keyboard input.
21157
21223
  */
21158
21224
  class KritzelKeyHandler extends KritzelBaseHandler {
@@ -21195,27 +21261,6 @@ class KritzelKeyHandler extends KritzelBaseHandler {
21195
21261
  { key: '7', ctrl: true, label: 'Tool 7', category: 'Tool Selection', action: () => this.switchToolByIndex(6) },
21196
21262
  { key: '8', ctrl: true, label: 'Tool 8', category: 'Tool Selection', action: () => this.switchToolByIndex(7) },
21197
21263
  { key: '9', ctrl: true, label: 'Tool 9', category: 'Tool Selection', action: () => this.switchToolByIndex(8) },
21198
- // Clipboard
21199
- {
21200
- key: 'c',
21201
- ctrl: true,
21202
- label: 'Copy',
21203
- category: 'Clipboard',
21204
- condition: c => !!c.store.selectionGroup,
21205
- action: c => {
21206
- c.copy();
21207
- c.rerender();
21208
- },
21209
- },
21210
- { key: 'x', ctrl: true, label: 'Cut', category: 'Clipboard', condition: c => !!c.store.selectionGroup, action: c => c.cut() },
21211
- {
21212
- key: 'v',
21213
- ctrl: true,
21214
- label: 'Paste',
21215
- category: 'Clipboard',
21216
- condition: c => !!c.store.state.copiedObjects && !c.store.activeText && !c.store.activeShape,
21217
- action: c => c.paste(),
21218
- },
21219
21264
  // Object layering
21220
21265
  { key: '+', ctrl: true, label: 'Bring Forward', category: 'Object Layering', condition: c => !!c.store.selectionGroup, action: c => c.bringForward() },
21221
21266
  { key: '-', ctrl: true, label: 'Send Backward', category: 'Object Layering', condition: c => !!c.store.selectionGroup, action: c => c.sendBackward() },
@@ -21725,7 +21770,6 @@ class KritzelReviver {
21725
21770
  const DEFAULT_ENGINE_CONFIG = {
21726
21771
  activeWorkspace: undefined,
21727
21772
  activeTool: null,
21728
- copiedObjects: null,
21729
21773
  objects: null,
21730
21774
  snapCandidate: null,
21731
21775
  resizeHandleType: null,
@@ -24068,6 +24112,467 @@ class KritzelCursorManager {
24068
24112
  }
24069
24113
  }
24070
24114
 
24115
+ /**
24116
+ * Manages browser permission checks and prompts for a core instance.
24117
+ *
24118
+ * Permission-specific prompt logic is routed through a handler registry so new
24119
+ * permissions can be added without changing the core control flow.
24120
+ */
24121
+ class KritzelBrowserPermissionManager {
24122
+ _isFirefox = /firefox|fxios/i.test(navigator.userAgent);
24123
+ _isSafari = /safari/i.test(navigator.userAgent) && !/chrome|chromium|crios|android|edg/i.test(navigator.userAgent);
24124
+ _requestHandlers = new Map();
24125
+ /**
24126
+ * Creates a new KritzelBrowserPermissionManager instance.
24127
+ * @param core - The KritzelCore instance this manager belongs to
24128
+ */
24129
+ constructor(_core) {
24130
+ this.registerRequestPermissionHandler('clipboard-read', async () => {
24131
+ // Firefox and Safari open native paste menus when readText() is called
24132
+ // proactively, so rely on the permission state instead of an eager read.
24133
+ if (this._isFirefox || this._isSafari) {
24134
+ return 'granted';
24135
+ }
24136
+ try {
24137
+ await navigator.clipboard.readText();
24138
+ return 'granted';
24139
+ }
24140
+ catch {
24141
+ return 'denied';
24142
+ }
24143
+ });
24144
+ }
24145
+ /**
24146
+ * Registers a custom request handler for a browser permission name.
24147
+ * Existing handlers are replaced, which makes the registry easy to extend.
24148
+ *
24149
+ * @param permissionName - The browser permission name to handle
24150
+ * @param handler - The request implementation for that permission
24151
+ */
24152
+ registerRequestPermissionHandler(permissionName, handler) {
24153
+ this._requestHandlers.set(permissionName, handler);
24154
+ }
24155
+ /**
24156
+ * Checks the current state for a browser permission via the Permissions API.
24157
+ *
24158
+ * @param permissionName - The browser permission name to inspect
24159
+ */
24160
+ async checkPermission(permissionName) {
24161
+ const status = await navigator.permissions.query({ name: permissionName });
24162
+ return status.state;
24163
+ }
24164
+ /**
24165
+ * Requests a browser permission using the registered handler for the given
24166
+ * permission name, then falls back to a permission check when no handler is
24167
+ * registered.
24168
+ *
24169
+ * @param permissionName - The browser permission name to request
24170
+ */
24171
+ async requestPermission(permissionName) {
24172
+ const handler = this._requestHandlers.get(permissionName);
24173
+ if (handler) {
24174
+ return handler();
24175
+ }
24176
+ return this.checkPermission(permissionName);
24177
+ }
24178
+ }
24179
+
24180
+ const CLIPBOARD_PAYLOAD_PREFIX = 'kritzel-clipboard/v1:';
24181
+ /**
24182
+ * Manages clipboard operations (copy/cut/paste) for a core instance.
24183
+ *
24184
+ * The system clipboard is the single source of truth: copy serializes the current
24185
+ * selection into a Kritzel payload, paste reads + revives it. No copied objects are
24186
+ * cached on the engine state.
24187
+ */
24188
+ class KritzelClipboardManager {
24189
+ _core;
24190
+ /**
24191
+ * In-memory cascade offset (world units) applied to successive pastes of the same
24192
+ * clipboard content so they don't stack exactly on top of each other. Reset on copy/cut.
24193
+ */
24194
+ _pasteOffset = 0;
24195
+ constructor(core) {
24196
+ this._core = core;
24197
+ }
24198
+ /**
24199
+ * Serializes an object for the clipboard payload. Groups additionally embed their
24200
+ * (recursively serialized) children under `__children__` so the payload is self-contained.
24201
+ */
24202
+ serializeForClipboard(object) {
24203
+ const serialized = (typeof object.serialize === 'function' ? object.serialize() : object);
24204
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelGroup')) {
24205
+ serialized.__children__ = object.children.map(child => this.serializeForClipboard(child));
24206
+ }
24207
+ return serialized;
24208
+ }
24209
+ serializeClipboardPayload(payload) {
24210
+ return `${CLIPBOARD_PAYLOAD_PREFIX}${JSON.stringify(payload)}`;
24211
+ }
24212
+ parseClipboardPayload(text) {
24213
+ if (!text || !text.startsWith(CLIPBOARD_PAYLOAD_PREFIX)) {
24214
+ return null;
24215
+ }
24216
+ try {
24217
+ const parsed = JSON.parse(text.slice(CLIPBOARD_PAYLOAD_PREFIX.length));
24218
+ if (parsed?.version !== 1 || !Array.isArray(parsed.objects)) {
24219
+ return null;
24220
+ }
24221
+ return parsed;
24222
+ }
24223
+ catch {
24224
+ return null;
24225
+ }
24226
+ }
24227
+ determineTextPasteIntent(text) {
24228
+ const payload = this.parseClipboardPayload(text);
24229
+ if (payload) {
24230
+ return { type: 'kritzel_objects', payload };
24231
+ }
24232
+ if (text && text.trim().length > 0) {
24233
+ return { type: 'plain_text', text };
24234
+ }
24235
+ return null;
24236
+ }
24237
+ async writeBrowserClipboardPayload(payload, event) {
24238
+ const serialized = this.serializeClipboardPayload(payload);
24239
+ if (event?.clipboardData) {
24240
+ event.clipboardData.setData('text/plain', serialized);
24241
+ return;
24242
+ }
24243
+ if (typeof navigator === 'undefined' || !navigator.clipboard?.writeText) {
24244
+ return;
24245
+ }
24246
+ try {
24247
+ await navigator.clipboard.writeText(serialized);
24248
+ }
24249
+ catch {
24250
+ // Ignore clipboard permission failures and keep the local clipboard working.
24251
+ }
24252
+ }
24253
+ /**
24254
+ * Revives a single serialized clipboard object (and its embedded group children).
24255
+ * IDs are NOT regenerated here; see regenerateIdsAndRemapAnchors.
24256
+ */
24257
+ reviveClipboardObject(serialized) {
24258
+ const reviver = new KritzelReviver(this._core);
24259
+ const { __children__, ...objectData } = serialized;
24260
+ const revived = reviver.revive(objectData);
24261
+ if (!revived) {
24262
+ return null;
24263
+ }
24264
+ if (__children__ && KritzelClassHelper.isInstanceOf(revived, 'KritzelGroup')) {
24265
+ const children = __children__.map(child => this.reviveClipboardObject(child)).filter((child) => child != null);
24266
+ revived._pendingChildren = children;
24267
+ revived.childIds = children.map(child => child.id);
24268
+ }
24269
+ return revived;
24270
+ }
24271
+ /**
24272
+ * Assigns fresh unique IDs to every revived object (including nested group children),
24273
+ * keeping group.childIds in sync and remapping line anchors that reference copied
24274
+ * siblings to their new IDs. Anchors pointing at objects outside the pasted set are
24275
+ * left untouched so they stay connected to the existing object.
24276
+ */
24277
+ regenerateIdsAndRemapAnchors(topLevel) {
24278
+ const all = [];
24279
+ const collect = (objects) => {
24280
+ for (const object of objects) {
24281
+ all.push(object);
24282
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelGroup')) {
24283
+ collect(object._pendingChildren);
24284
+ }
24285
+ }
24286
+ };
24287
+ collect(topLevel);
24288
+ const idMap = new Map();
24289
+ for (const object of all) {
24290
+ const newId = object.generateId();
24291
+ idMap.set(object.id, newId);
24292
+ object.id = newId;
24293
+ object.isMounted = false;
24294
+ }
24295
+ for (const object of all) {
24296
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelGroup')) {
24297
+ object.childIds = object.childIds.map(id => idMap.get(id) ?? id);
24298
+ }
24299
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelLine')) {
24300
+ if (object.startAnchor && idMap.has(object.startAnchor.objectId)) {
24301
+ object.startAnchor = { objectId: idMap.get(object.startAnchor.objectId) };
24302
+ }
24303
+ if (object.endAnchor && idMap.has(object.endAnchor.objectId)) {
24304
+ object.endAnchor = { objectId: idMap.get(object.endAnchor.objectId) };
24305
+ }
24306
+ }
24307
+ }
24308
+ }
24309
+ /**
24310
+ * Evaluates the clipboard event to determine exactly what the user is trying to paste.
24311
+ * Returns a PasteIntent covering the 3 main cases: Kritzel objects, Images, or Plain text.
24312
+ */
24313
+ async determinePasteIntent(event) {
24314
+ if (event?.clipboardData) {
24315
+ const data = event.clipboardData;
24316
+ const imageFiles = Array.from(data.files ?? []).filter(file => file.type.startsWith('image/'));
24317
+ if (imageFiles.length > 0) {
24318
+ return { type: 'image_files', files: imageFiles };
24319
+ }
24320
+ if (data.types.includes('text/plain')) {
24321
+ const textIntent = this.determineTextPasteIntent(data.getData('text/plain'));
24322
+ if (textIntent)
24323
+ return textIntent;
24324
+ }
24325
+ return { type: 'unknown' };
24326
+ }
24327
+ if (typeof navigator === 'undefined' || !navigator.clipboard) {
24328
+ return { type: 'unknown' };
24329
+ }
24330
+ const { clipboard } = navigator;
24331
+ const permissionState = await this._core.permissionManager.checkPermission('clipboard-read');
24332
+ if (permissionState !== 'granted') {
24333
+ const requestedState = await this._core.permissionManager.requestPermission('clipboard-read');
24334
+ if (requestedState !== 'granted') {
24335
+ return { type: 'unknown' };
24336
+ }
24337
+ }
24338
+ if (clipboard.read) {
24339
+ try {
24340
+ const items = await clipboard.read();
24341
+ const { imageFiles, text } = await this.processClipboardItems(items);
24342
+ if (imageFiles.length > 0) {
24343
+ return { type: 'image_files', files: imageFiles };
24344
+ }
24345
+ if (text !== null) {
24346
+ const textIntent = this.determineTextPasteIntent(text);
24347
+ if (textIntent)
24348
+ return textIntent;
24349
+ }
24350
+ }
24351
+ catch {
24352
+ // Fall back to readText below when read() is denied or unavailable at runtime.
24353
+ }
24354
+ }
24355
+ if (clipboard.readText) {
24356
+ const textIntent = this.determineTextPasteIntent(await clipboard.readText());
24357
+ if (textIntent)
24358
+ return textIntent;
24359
+ }
24360
+ return { type: 'unknown' };
24361
+ }
24362
+ /**
24363
+ * Helper to parse clipboard items in parallel
24364
+ */
24365
+ async processClipboardItems(items) {
24366
+ const imageFiles = [];
24367
+ let text = null;
24368
+ // Process items concurrently
24369
+ await Promise.all(items.map(async (item) => {
24370
+ const imageType = item.types.find(type => type.startsWith('image/'));
24371
+ if (imageType) {
24372
+ const blob = await item.getType(imageType);
24373
+ imageFiles.push(new File([blob], 'pasted-image', { type: imageType }));
24374
+ return;
24375
+ }
24376
+ if (text === null && item.types.includes('text/plain')) {
24377
+ const blob = await item.getType('text/plain');
24378
+ text = await blob.text();
24379
+ }
24380
+ }));
24381
+ return { imageFiles, text };
24382
+ }
24383
+ /**
24384
+ * Copies currently selected objects into internal clipboard state.
24385
+ */
24386
+ copy(event) {
24387
+ const selectionGroup = this._core.store.selectionGroup;
24388
+ if (!selectionGroup) {
24389
+ return;
24390
+ }
24391
+ const payload = {
24392
+ version: 1,
24393
+ sourceWorkspaceId: this._core.store.state.activeWorkspace?.id,
24394
+ objects: selectionGroup.objects.sort((a, b) => a.zIndex - b.zIndex).map(obj => this.serializeForClipboard(obj)),
24395
+ };
24396
+ // A fresh copy resets the cascade so the first paste lands at the default offset.
24397
+ this._pasteOffset = 0;
24398
+ void this.writeBrowserClipboardPayload(payload, event);
24399
+ }
24400
+ /**
24401
+ * Cuts currently selected objects (copy then delete).
24402
+ */
24403
+ cut(event) {
24404
+ const selectionGroup = this._core.store.selectionGroup;
24405
+ if (!selectionGroup) {
24406
+ return;
24407
+ }
24408
+ this.copy(event);
24409
+ this._core.delete();
24410
+ }
24411
+ materializeKritzelObjects(intent) {
24412
+ const objects = intent.payload.objects.map(object => this.reviveClipboardObject(object)).filter((object) => object != null);
24413
+ this.regenerateIdsAndRemapAnchors(objects);
24414
+ return objects;
24415
+ }
24416
+ async materializeImageFiles(intent) {
24417
+ try {
24418
+ const scale = this._core.store.state.scale;
24419
+ const offset = 25 / scale;
24420
+ const preparePromises = intent.files.map(async (file, index) => {
24421
+ // Use the default image compression size of 1200
24422
+ const compressed = await imageCompression(file, { maxWidthOrHeight: 1200 });
24423
+ return KritzelImage.fromBlob(this._core, compressed, {
24424
+ translateX: index * offset,
24425
+ translateY: index * offset,
24426
+ });
24427
+ });
24428
+ return await Promise.all(preparePromises);
24429
+ }
24430
+ catch (err) {
24431
+ console.error('Failed to import pasted image files:', err);
24432
+ return null;
24433
+ }
24434
+ }
24435
+ async materializePlainText(intent) {
24436
+ const textObject = KritzelText.create(this._core);
24437
+ const paragraphs = intent.text.split('\n').map(line => {
24438
+ if (line.length === 0) {
24439
+ return { type: 'paragraph' }; // Empty paragraph
24440
+ }
24441
+ return {
24442
+ type: 'paragraph',
24443
+ content: [{ type: 'text', text: line }],
24444
+ };
24445
+ });
24446
+ textObject.setContent({
24447
+ type: 'doc',
24448
+ content: paragraphs,
24449
+ });
24450
+ return [textObject];
24451
+ }
24452
+ /**
24453
+ * Pastes objects from clipboard with optional explicit world coordinates.
24454
+ */
24455
+ async paste(x, y, event) {
24456
+ const pasteIntent = await this.determinePasteIntent(event);
24457
+ let objects = null;
24458
+ if (pasteIntent.type === 'kritzel_objects') {
24459
+ objects = this.materializeKritzelObjects(pasteIntent);
24460
+ }
24461
+ else if (pasteIntent.type === 'image_files') {
24462
+ objects = await this.materializeImageFiles(pasteIntent);
24463
+ }
24464
+ else if (pasteIntent.type === 'plain_text') {
24465
+ objects = await this.materializePlainText(pasteIntent);
24466
+ }
24467
+ if (!objects || objects.length === 0) {
24468
+ return;
24469
+ }
24470
+ await this.insertMaterializedObjects(objects, pasteIntent, x, y);
24471
+ }
24472
+ async insertMaterializedObjects(copiedObjects, pasteIntent, x, y) {
24473
+ const activeWorkspace = this._core.store.state.activeWorkspace;
24474
+ if (!activeWorkspace) {
24475
+ return;
24476
+ }
24477
+ // Determine if we should center the objects in the viewport.
24478
+ // This happens if the user specifically pasted from a different workspace,
24479
+ // OR if we're inserting brand new external items (like images or text).
24480
+ const isDifferentWorkspace = copiedObjects.some(obj => obj.workspaceId !== activeWorkspace.id);
24481
+ const isNewExternalContent = pasteIntent.type !== 'kritzel_objects';
24482
+ const shouldCenterInViewport = isDifferentWorkspace || isNewExternalContent;
24483
+ const hasExplicitPastePosition = x !== undefined && y !== undefined;
24484
+ const minX = Math.min(...copiedObjects.map(obj => obj.translateX));
24485
+ const minY = Math.min(...copiedObjects.map(obj => obj.translateY));
24486
+ let pasteX;
24487
+ let pasteY;
24488
+ const step = 25 / this._core.store.state.scale;
24489
+ if (x !== undefined && y !== undefined) {
24490
+ pasteX = x;
24491
+ pasteY = y;
24492
+ }
24493
+ else if (shouldCenterInViewport) {
24494
+ pasteX = minX;
24495
+ pasteY = minY;
24496
+ }
24497
+ else {
24498
+ pasteX = minX + step + this._pasteOffset;
24499
+ pasteY = minY + step + this._pasteOffset;
24500
+ // Advance the cascade so the next same-content paste is offset further.
24501
+ this._pasteOffset += step;
24502
+ }
24503
+ const offsetX = pasteX - minX;
24504
+ const offsetY = pasteY - minY;
24505
+ this._core.removeSelectionGroup();
24506
+ this._core.removeSelectionBox();
24507
+ const selectionGroup = KritzelSelectionGroup.create(this._core);
24508
+ const baseZIndex = this._core.store.currentZIndex;
24509
+ this._core.store.objects.transaction(() => {
24510
+ const flushPendingChildren = (group, parentZIndex) => {
24511
+ if (group._pendingChildren.length === 0) {
24512
+ return;
24513
+ }
24514
+ const pendingChildren = group._pendingChildren;
24515
+ group._pendingChildren = [];
24516
+ pendingChildren.forEach((child, childIndex) => {
24517
+ if (child.workspaceId !== activeWorkspace.id) {
24518
+ child.workspaceId = activeWorkspace.id;
24519
+ }
24520
+ child.updatePosition(child.translateX + offsetX, child.translateY + offsetY);
24521
+ child.zIndex = parentZIndex + childIndex + 1;
24522
+ if (KritzelClassHelper.isInstanceOf(child, 'KritzelGroup')) {
24523
+ flushPendingChildren(child, child.zIndex);
24524
+ }
24525
+ this._core.addObject(child);
24526
+ });
24527
+ group.finalize();
24528
+ };
24529
+ const newlyAddedObjects = [];
24530
+ copiedObjects.forEach((obj, i) => {
24531
+ if (obj.workspaceId !== activeWorkspace.id) {
24532
+ obj.workspaceId = activeWorkspace.id;
24533
+ }
24534
+ obj.updatePosition(obj.translateX + offsetX, obj.translateY + offsetY);
24535
+ obj.zIndex = baseZIndex + i;
24536
+ if (KritzelClassHelper.isInstanceOf(obj, 'KritzelGroup')) {
24537
+ flushPendingChildren(obj, obj.zIndex);
24538
+ }
24539
+ this._core.addObject(obj);
24540
+ newlyAddedObjects.push(obj);
24541
+ selectionGroup.addOrRemove(obj);
24542
+ });
24543
+ this._core.engine.emitObjectsAdded(newlyAddedObjects);
24544
+ });
24545
+ this._core.anchorManager.rebuildIndex();
24546
+ selectionGroup.isSelected = true;
24547
+ if (copiedObjects.length === 1) {
24548
+ selectionGroup.rotation = copiedObjects[0].rotation;
24549
+ }
24550
+ if (shouldCenterInViewport && !hasExplicitPastePosition) {
24551
+ selectionGroup.centerInViewport();
24552
+ }
24553
+ this._core.addSelectionGroup(selectionGroup);
24554
+ this._core.engine.viewport?.bringIntoViewIfNeeded(selectionGroup);
24555
+ this._core.store.state.isSelecting = false;
24556
+ // Use a double requestAnimationFrame to ensure the browser has fully rendered the DOM,
24557
+ // and objects like KritzelText have executed their own internal size-adjustment rAF calls.
24558
+ await new Promise(resolve => {
24559
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
24560
+ });
24561
+ // Re-evaluate bounding box dimensions now that any DOM nodes (like ProseMirror text editors)
24562
+ // have mounted and calculated their actual bounds.
24563
+ selectionGroup.refreshObjectDimensions();
24564
+ // We should update it if the centerInViewport or width adjustments changed it significantly
24565
+ if (shouldCenterInViewport && !hasExplicitPastePosition) {
24566
+ selectionGroup.centerInViewport();
24567
+ }
24568
+ this._core.engine.emitObjectsChange();
24569
+ this._core.rerender();
24570
+ this._core.store.setState('activeTool', this._core.toolRegistry.getTool('selection'));
24571
+ this._core.engine.emitObjectsChange();
24572
+ this._core.rerender();
24573
+ }
24574
+ }
24575
+
24071
24576
  /**
24072
24577
  * Central orchestrator class for the Kritzel canvas system.
24073
24578
  * Manages workspaces, objects, selection, clipboard operations, and coordinates
@@ -24096,6 +24601,10 @@ class KritzelCore {
24096
24601
  _localizationManager;
24097
24602
  /** Manager for license validation and watermark gating */
24098
24603
  _licenseManager;
24604
+ /** Manager for browser permission checks and permission prompts */
24605
+ _permissionManager;
24606
+ /** Manager for clipboard copy/cut/paste orchestration */
24607
+ _clipboardManager;
24099
24608
  /** Per-core registry of drawing tools (one instance per editor). */
24100
24609
  _toolRegistry;
24101
24610
  /** Optional unique identifier for namespacing storage keys across multiple editor instances */
@@ -24158,6 +24667,13 @@ class KritzelCore {
24158
24667
  get licenseManager() {
24159
24668
  return this._licenseManager;
24160
24669
  }
24670
+ /**
24671
+ * Gets the browser permission manager.
24672
+ * @returns The KritzelBrowserPermissionManager for browser permission checks and requests
24673
+ */
24674
+ get permissionManager() {
24675
+ return this._permissionManager;
24676
+ }
24161
24677
  /**
24162
24678
  * Gets the tool registry scoped to this core instance.
24163
24679
  * @returns The KritzelToolRegistry owned by this core
@@ -24165,6 +24681,13 @@ class KritzelCore {
24165
24681
  get toolRegistry() {
24166
24682
  return this._toolRegistry;
24167
24683
  }
24684
+ /**
24685
+ * Gets the clipboard manager scoped to this core instance.
24686
+ * @returns The KritzelClipboardManager owned by this core
24687
+ */
24688
+ get clipboardManager() {
24689
+ return this._clipboardManager;
24690
+ }
24168
24691
  /**
24169
24692
  * Gets the editor ID used for namespacing storage keys.
24170
24693
  * @returns The editor ID or undefined if not set
@@ -24212,6 +24735,8 @@ class KritzelCore {
24212
24735
  this._themeManager = new KritzelThemeManager(this);
24213
24736
  this._localizationManager = new KritzelLocalizationManager(this);
24214
24737
  this._licenseManager = new KritzelLicenseManager(this);
24738
+ this._permissionManager = new KritzelBrowserPermissionManager(this);
24739
+ this._clipboardManager = new KritzelClipboardManager(this);
24215
24740
  this._toolRegistry = new KritzelToolRegistry(this);
24216
24741
  this._assetResolver = new KritzelAssetResolver();
24217
24742
  }
@@ -24709,216 +25234,6 @@ class KritzelCore {
24709
25234
  this.rerender();
24710
25235
  }
24711
25236
  }
24712
- /**
24713
- * Copies the currently selected objects to the clipboard.
24714
- * Creates deep copies of objects and tracks ID mappings for anchor remapping.
24715
- */
24716
- copy() {
24717
- const selectionGroup = this._store.selectionGroup;
24718
- if (selectionGroup) {
24719
- // Copy each object and store them in an array, also track the ID mapping
24720
- const idMapping = new Map();
24721
- const copiedObjects = selectionGroup.objects
24722
- .sort((a, b) => a.zIndex - b.zIndex)
24723
- .map(obj => {
24724
- const copiedObj = obj.copy();
24725
- // Map: newId -> originalId
24726
- idMapping.set(copiedObj.id, obj.id);
24727
- return copiedObj;
24728
- });
24729
- this._store.state.copiedObjects = copiedObjects;
24730
- this._store.state.copiedObjectIdMapping = idMapping;
24731
- }
24732
- }
24733
- /**
24734
- * Cuts the currently selected objects (copy then delete).
24735
- * Objects are copied to clipboard and then removed from the canvas.
24736
- */
24737
- cut() {
24738
- const selectionGroup = this._store.selectionGroup;
24739
- if (!selectionGroup) {
24740
- return;
24741
- }
24742
- // Copy first, then delete
24743
- this.copy();
24744
- this.delete();
24745
- }
24746
- /**
24747
- * Pastes objects from the clipboard to the canvas.
24748
- * Handles positioning (explicit, offset, or centered for cross-workspace paste),
24749
- * workspace assignment, z-index ordering, group children, and anchor remapping.
24750
- * Creates a selection group containing the pasted objects.
24751
- * @param x - Optional explicit X position for paste
24752
- * @param y - Optional explicit Y position for paste
24753
- */
24754
- paste(x, y) {
24755
- const copiedObjects = this._store.state.copiedObjects;
24756
- if (!copiedObjects || copiedObjects.length === 0) {
24757
- return;
24758
- }
24759
- const activeWorkspace = this._store.state.activeWorkspace;
24760
- const originalIdMapping = this._store.state.copiedObjectIdMapping;
24761
- // Check if we're pasting from a different workspace
24762
- const isDifferentWorkspace = copiedObjects.some(obj => obj.workspaceId !== activeWorkspace.id);
24763
- // Calculate the bounding box of all copied objects
24764
- const minX = Math.min(...copiedObjects.map(obj => obj.translateX));
24765
- const minY = Math.min(...copiedObjects.map(obj => obj.translateY));
24766
- // Determine the paste position
24767
- let pasteX;
24768
- let pasteY;
24769
- if (x !== undefined && y !== undefined) {
24770
- // Explicit position provided (e.g., right-click paste)
24771
- pasteX = x;
24772
- pasteY = y;
24773
- }
24774
- else if (isDifferentWorkspace) {
24775
- // Pasting to different workspace without explicit position
24776
- // Keep original position; will be centered by centerInViewport() later
24777
- pasteX = minX;
24778
- pasteY = minY;
24779
- }
24780
- else {
24781
- // Same workspace paste: offset by 25 pixels from original position
24782
- pasteX = minX + 25 / this._store.state.scale;
24783
- pasteY = minY + 25 / this._store.state.scale;
24784
- }
24785
- // Calculate the offset to apply to all objects
24786
- const offsetX = pasteX - minX;
24787
- const offsetY = pasteY - minY;
24788
- this.removeSelectionGroup();
24789
- this.removeSelectionBox();
24790
- // Create a new selection group for the pasted objects
24791
- const selectionGroup = KritzelSelectionGroup.create(this);
24792
- // Build a reverse mapping: originalId -> newCopiedObjectId
24793
- // This is used to remap anchor references from original IDs to the copied object IDs
24794
- const originalToNewIdMap = new Map();
24795
- if (originalIdMapping) {
24796
- originalIdMapping.forEach((originalId, copiedId) => {
24797
- originalToNewIdMap.set(originalId, copiedId);
24798
- });
24799
- }
24800
- // Precompute zIndex once to avoid O(N * total_objects) repeated scans
24801
- const baseZIndex = this._store.currentZIndex;
24802
- // Batch all inserts and updates in a single Y.js transaction to avoid
24803
- // N separate observer callbacks and N rerenders (fires only once at commit)
24804
- this._store.objects.transaction(() => {
24805
- // Recursively flush a group's pending children (and any nested groups'
24806
- // pending children) into the store. Without recursion, nested groups
24807
- // would have their grandchildren stranded in `_pendingChildren` and they
24808
- // would never appear on the canvas, even though the parent group's
24809
- // bounding box looks correct.
24810
- const flushPendingChildren = (group, parentZIndex) => {
24811
- if (group._pendingChildren.length === 0) {
24812
- return;
24813
- }
24814
- const pendingChildren = group._pendingChildren;
24815
- group._pendingChildren = [];
24816
- pendingChildren.forEach((child, childIndex) => {
24817
- if (child.workspaceId !== activeWorkspace.id) {
24818
- child.workspaceId = activeWorkspace.id;
24819
- }
24820
- child.updatePosition(child.translateX + offsetX, child.translateY + offsetY);
24821
- child.zIndex = parentZIndex + childIndex + 1;
24822
- // Recurse before adding so grandchildren are inserted as well
24823
- if (KritzelClassHelper.isInstanceOf(child, 'KritzelGroup')) {
24824
- flushPendingChildren(child, child.zIndex);
24825
- }
24826
- this.addObject(child);
24827
- });
24828
- group.finalize();
24829
- };
24830
- const newlyAddedObjects = [];
24831
- // First add all copied objects to the objectsMap with updated positions
24832
- copiedObjects.forEach((obj, i) => {
24833
- // Update workspace if pasting to a different workspace
24834
- if (obj.workspaceId !== activeWorkspace.id) {
24835
- obj.workspaceId = activeWorkspace.id;
24836
- }
24837
- // Update position
24838
- obj.updatePosition(obj.translateX + offsetX, obj.translateY + offsetY);
24839
- // Update z-index
24840
- obj.zIndex = baseZIndex + i;
24841
- // Handle KritzelGroup: also add pending children (and grandchildren) with offset
24842
- if (KritzelClassHelper.isInstanceOf(obj, 'KritzelGroup')) {
24843
- flushPendingChildren(obj, obj.zIndex);
24844
- }
24845
- // Add to objectsMap
24846
- this.addObject(obj);
24847
- newlyAddedObjects.push(obj);
24848
- // Add to selection group
24849
- selectionGroup.addOrRemove(obj);
24850
- });
24851
- this.engine.emitObjectsAdded(newlyAddedObjects);
24852
- // Update line anchors to point to the newly pasted objects
24853
- // Only remap if the anchor target was also part of the copied selection
24854
- copiedObjects.forEach(obj => {
24855
- if (KritzelClassHelper.isInstanceOf(obj, 'KritzelLine')) {
24856
- let updated = false;
24857
- if (obj.startAnchor && originalToNewIdMap.has(obj.startAnchor.objectId)) {
24858
- obj.startAnchor = { objectId: originalToNewIdMap.get(obj.startAnchor.objectId) };
24859
- updated = true;
24860
- }
24861
- if (obj.endAnchor && originalToNewIdMap.has(obj.endAnchor.objectId)) {
24862
- obj.endAnchor = { objectId: originalToNewIdMap.get(obj.endAnchor.objectId) };
24863
- updated = true;
24864
- }
24865
- // If anchors were updated, rebuild the anchor index and persist the change
24866
- if (updated) {
24867
- this._store.objects.update(obj);
24868
- }
24869
- }
24870
- });
24871
- });
24872
- // Rebuild anchor index after all anchor updates
24873
- this._anchorManager.rebuildIndex();
24874
- // Mark selection group as selected
24875
- selectionGroup.isSelected = true;
24876
- // Set rotation for single object
24877
- if (copiedObjects.length === 1) {
24878
- selectionGroup.rotation = copiedObjects[0].rotation;
24879
- }
24880
- // For cross-workspace paste, set the selection group's position before inserting
24881
- // it into the store. This mirrors the existing pattern where objects have
24882
- // updatePosition called before addObject, so the correct position is captured
24883
- // in the single initial insert with no separate Yjs update (and no extra undo step).
24884
- if (isDifferentWorkspace) {
24885
- if (x !== undefined && y !== undefined) {
24886
- // Explicit cursor position provided (e.g. right-click paste)
24887
- selectionGroup.updatePosition(x, y);
24888
- }
24889
- else {
24890
- // Ctrl+V paste: center objects in the current viewport
24891
- selectionGroup.centerInViewport();
24892
- }
24893
- }
24894
- // Add the selection group (inserts with the already-adjusted position)
24895
- this.addSelectionGroup(selectionGroup);
24896
- // Bring pasted objects into view with a smooth animation if they are not
24897
- // fully visible. These only mutate in-memory viewport state (scale, translateX/Y)
24898
- // and do not produce any Yjs writes, so they have no impact on the undo history.
24899
- this.engine.viewport.bringIntoViewIfNeeded(selectionGroup);
24900
- this._store.state.isSelecting = false;
24901
- // Defer creating copies for future pastes to next frame to avoid
24902
- // doubling the object creation cost in the current paste frame
24903
- requestAnimationFrame(() => {
24904
- const newSelectionGroup = this._store.selectionGroup;
24905
- if (newSelectionGroup) {
24906
- const newIdMapping = new Map();
24907
- const newCopiedObjects = newSelectionGroup.objects
24908
- .sort((a, b) => a.zIndex - b.zIndex)
24909
- .map(obj => {
24910
- const copiedObj = obj.copy();
24911
- newIdMapping.set(copiedObj.id, obj.id);
24912
- return copiedObj;
24913
- });
24914
- this._store.state.copiedObjects = newCopiedObjects;
24915
- this._store.state.copiedObjectIdMapping = newIdMapping;
24916
- }
24917
- });
24918
- this._store.setState('activeTool', this._toolRegistry.getTool('selection'));
24919
- this.engine.emitObjectsChange();
24920
- this.rerender();
24921
- }
24922
25237
  /**
24923
25238
  * Moves objects one step forward in the z-order stack.
24924
25239
  * @param object - Optional specific object to move; defaults to all selected objects
@@ -27064,6 +27379,8 @@ const KritzelEngine = class {
27064
27379
  viewportChange;
27065
27380
  /** Emitted when the awareness state changes (remote user cursors, presence). */
27066
27381
  awarenessChange;
27382
+ /** Emitted when notifications change. */
27383
+ notificationsChange;
27067
27384
  forceUpdate = 0;
27068
27385
  /** Manually triggers the `objectsSelectionChange` event. */
27069
27386
  async triggerSelectionChange() {
@@ -27178,19 +27495,56 @@ const KritzelEngine = class {
27178
27495
  this.viewport.handleResize();
27179
27496
  }
27180
27497
  handleKeyDown(ev) {
27181
- if (this.core.store.isDisabled || !this.shouldHandleKeyboardEvent(ev)) {
27498
+ if (this.core.store.isDisabled || !this.shouldHandleGlobalShortcutEvent(ev)) {
27182
27499
  return;
27183
27500
  }
27184
27501
  this.keyHandler.handleKeyDown(ev);
27185
27502
  }
27186
27503
  handleKeyUp(ev) {
27187
- if (this.core.store.isDisabled || !this.shouldHandleKeyboardEvent(ev)) {
27504
+ if (this.core.store.isDisabled || !this.shouldHandleGlobalShortcutEvent(ev)) {
27188
27505
  return;
27189
27506
  }
27190
27507
  this.keyHandler.handleKeyUp(ev);
27191
27508
  }
27509
+ handleCopy(ev) {
27510
+ if (this.core.store.isDisabled || !this.shouldHandleGlobalShortcutEvent(ev)) {
27511
+ return;
27512
+ }
27513
+ if (!this.core.store.selectionGroup || this.core.store.activeText || this.core.store.activeShape) {
27514
+ return;
27515
+ }
27516
+ if (ev.cancelable) {
27517
+ ev.preventDefault();
27518
+ }
27519
+ this.core.clipboardManager.copy(ev);
27520
+ this.core.rerender();
27521
+ }
27522
+ handleCut(ev) {
27523
+ if (this.core.store.isDisabled || !this.shouldHandleGlobalShortcutEvent(ev)) {
27524
+ return;
27525
+ }
27526
+ if (!this.core.store.selectionGroup || this.core.store.activeText || this.core.store.activeShape) {
27527
+ return;
27528
+ }
27529
+ if (ev.cancelable) {
27530
+ ev.preventDefault();
27531
+ }
27532
+ this.core.clipboardManager.cut(ev);
27533
+ }
27534
+ handlePaste(ev) {
27535
+ if (this.core.store.isDisabled || !this.shouldHandleGlobalShortcutEvent(ev)) {
27536
+ return;
27537
+ }
27538
+ if (this.core.store.activeText || this.core.store.activeShape) {
27539
+ return;
27540
+ }
27541
+ if (ev.cancelable) {
27542
+ ev.preventDefault();
27543
+ }
27544
+ void this.core.clipboardManager.paste(undefined, undefined, ev);
27545
+ }
27192
27546
  /**
27193
- * Determines if the engine should handle a keyboard event.
27547
+ * Determines if the engine should handle a keyboard/clipboard shortcut event.
27194
27548
  * Returns true only if:
27195
27549
  * - The event originates from within the engine component, OR
27196
27550
  * - The event target is not an interactive element that needs keyboard input
@@ -27198,7 +27552,7 @@ const KritzelEngine = class {
27198
27552
  * This prevents conflicts with inputs, textareas, dialogs, or other
27199
27553
  * interactive elements that need their own keyboard handling.
27200
27554
  */
27201
- shouldHandleKeyboardEvent(ev) {
27555
+ shouldHandleGlobalShortcutEvent(ev) {
27202
27556
  const path = ev.composedPath();
27203
27557
  // Check if event path includes our host - works reliably with Shadow DOM
27204
27558
  if (path.includes(this.host)) {
@@ -27294,11 +27648,11 @@ const KritzelEngine = class {
27294
27648
  }
27295
27649
  /** Copies the currently selected objects to the internal clipboard. */
27296
27650
  async copy() {
27297
- this.core.copy();
27651
+ this.core.clipboardManager.copy();
27298
27652
  }
27299
27653
  /** Cuts the currently selected objects to the internal clipboard (deletes them from the canvas). */
27300
27654
  async cut() {
27301
- this.core.cut();
27655
+ this.core.clipboardManager.cut();
27302
27656
  }
27303
27657
  /**
27304
27658
  * Pastes previously copied objects at the specified world coordinates.
@@ -27306,7 +27660,16 @@ const KritzelEngine = class {
27306
27660
  * @param y - Y position in world coordinates.
27307
27661
  */
27308
27662
  async paste(x, y) {
27309
- this.core.paste(x, y);
27663
+ const hasPermission = await this.core.permissionManager.requestPermission('clipboard-read');
27664
+ if (hasPermission !== 'granted') {
27665
+ this.notificationsChange.emit({
27666
+ type: 'error',
27667
+ message: 'Clipboard read permission is required to paste content. Please grant permission and try again.',
27668
+ id: ObjectHelper.generateUUID(),
27669
+ });
27670
+ return;
27671
+ }
27672
+ this.core.clipboardManager.paste(x, y);
27310
27673
  }
27311
27674
  /**
27312
27675
  * Moves an object one layer forward in the z-order.
@@ -27797,10 +28160,6 @@ const KritzelEngine = class {
27797
28160
  async worldToScreen(x, y) {
27798
28161
  return this.viewport.worldToScreen(x, y);
27799
28162
  }
27800
- /** Returns the objects currently stored in the internal clipboard from a prior `copy()` call. */
27801
- async getCopiedObjects() {
27802
- return this.core.store.state.copiedObjects || [];
27803
- }
27804
28163
  /** Returns all objects currently visible within the viewport bounds. */
27805
28164
  async getObjectsInViewport() {
27806
28165
  return this.core.getObjectsInViewport();
@@ -28457,6 +28816,7 @@ const KritzelEngine = class {
28457
28816
  this.objectsInViewportChange = createEvent(this, "objectsInViewportChange");
28458
28817
  this.viewportChange = createEvent(this, "viewportChange");
28459
28818
  this.awarenessChange = createEvent(this, "awarenessChange");
28819
+ this.notificationsChange = createEvent(this, "notificationsChange");
28460
28820
  this.core = new KritzelCore(this);
28461
28821
  }
28462
28822
  disconnectedCallback() {
@@ -28735,7 +29095,6 @@ const KritzelEngine = class {
28735
29095
  this.core.resetActiveShape();
28736
29096
  }
28737
29097
  this.core.store.state.skipContextMenu = false;
28738
- this.core.store.state.copiedObjects = undefined;
28739
29098
  if (activeTool) {
28740
29099
  this.activeToolChange.emit(activeTool);
28741
29100
  }
@@ -29387,7 +29746,7 @@ const KritzelExport = class {
29387
29746
  return (h("div", { class: "export-tab-content" }, h("kritzel-input", { label: this.terms['export.filename.label'] ?? 'Filename', value: this.exportFilename, placeholder: this.terms['export.filename.placeholder'] ?? 'Enter filename', suffix: ".json", onValueChange: this.handleFilenameChange })));
29388
29747
  }
29389
29748
  render() {
29390
- return (h(Host, { key: 'ff6ce7e5d3a0a7a024148529f86bf3ec7ffb0333' }, h("kritzel-dialog", { key: '9051d262450af5fbe98959092f6bb0ac25ab43ce', isOpen: this.isDialogOpen, dialogTitle: this.terms['export.dialogTitle'] ?? 'Export', closable: true, contained: true, onDialogClose: this.closeDialog }, h("div", { key: '21f404a2101d5cf1d24aeeee663d8fb1854b9574', class: "export-content" }, h("kritzel-pill-tabs", { key: '80db6cda288d1b7019387a2f1ade278475ad6e24', tabs: this.tabs, value: this.activeTab, onValueChange: this.handleTabChange }), this.activeTab === 'viewport' && this.renderViewportExport(), this.activeTab === 'workspace' && this.renderWorkspaceExport(), h("button", { key: '45809f91ddd63c74f5a4b3a2e266919f244c6109', class: "export-primary-button", onClick: this.handleExport }, this.terms['export.exportButton'] ?? 'Export')))));
29749
+ return (h(Host, { key: 'fd219fffd4d8bb528e6a90055c2b43a5f5a73060' }, h("kritzel-dialog", { key: '9f4ba5b05f5ad31084aab457386a86cbc6d4f2bc', isOpen: this.isDialogOpen, dialogTitle: this.terms['export.dialogTitle'] ?? 'Export', closable: true, contained: true, onDialogClose: this.closeDialog }, h("div", { key: '920bb8f20b42897680084bde47270b3812e2f3e4', class: "export-content" }, h("kritzel-pill-tabs", { key: '7090bd0a371457adceaa4bd7461a616839a66349', tabs: this.tabs, value: this.activeTab, onValueChange: this.handleTabChange }), this.activeTab === 'viewport' && this.renderViewportExport(), this.activeTab === 'workspace' && this.renderWorkspaceExport(), h("button", { key: 'ddf36c1e7cb81409c2cecfa6585e0f1dd7aca2c3', class: "export-primary-button", onClick: this.handleExport }, this.terms['export.exportButton'] ?? 'Export')))));
29391
29750
  }
29392
29751
  };
29393
29752
  KritzelExport.style = kritzelExportCss();
@@ -29402,7 +29761,7 @@ const KritzelFont = class {
29402
29761
  size = 24;
29403
29762
  color = '#000000';
29404
29763
  render() {
29405
- return (h(Host, { key: '49c44f7fef61bd7116e63bbe9522b0a0e22de116' }, h("div", { key: '4d130682f67e9c0d758cb6194e6a95f15bceb2ee', class: "font-preview", style: {
29764
+ return (h(Host, { key: 'e37350dd092fb7c81a5624bfdc39c3b9af66b894' }, h("div", { key: 'a962ccc23a48f62b86d49ae41b1c9c27bdc9a5b9', class: "font-preview", style: {
29406
29765
  fontFamily: this.fontFamily,
29407
29766
  fontSize: `${this.size}px`,
29408
29767
  color: this.color
@@ -29478,7 +29837,7 @@ const KritzelFontSize = class {
29478
29837
  render() {
29479
29838
  const color = 'var(--kritzel-global-text-primary)';
29480
29839
  const sizes = this.sizes ?? [];
29481
- return (h(Host, { key: '2083b05b3fdc9940c26dd90dbf9e097564ee976d' }, sizes.map(size => (h("div", { tabIndex: 0, class: {
29840
+ return (h(Host, { key: 'a314e990b56cb4e819c4d83a139016b1658606b8' }, sizes.map(size => (h("div", { tabIndex: 0, class: {
29482
29841
  'size-container': true,
29483
29842
  'selected': this.selectedSize === size,
29484
29843
  }, onClick: () => this.handleSizeClick(size), onKeyDown: event => this.handleKeyDown(event, size) }, h("kritzel-font", { fontFamily: this.fontFamily, size: size, color: color }))))));
@@ -29545,7 +29904,7 @@ const KritzelInput = class {
29545
29904
  this.valueChange.emit(input.value);
29546
29905
  };
29547
29906
  render() {
29548
- return (h(Host, { key: '8584ea0fa1a2cf0b9518ea978303837da5bfaf31' }, h("div", { key: 'e9f3ac340f17580221d1a86c8eb913fce2acdf49', class: "input-container" }, this.label && h("label", { key: 'b0d4c0250b07d83a3cd99e0ab18ed672a29de5f0', class: "input-label" }, this.label), h("div", { key: '5044bcff621d6c6850ef3d225608427401b75ebd', class: { 'input-wrapper': true, 'has-suffix': !!this.suffix } }, h("input", { key: 'f8d057840e975d900c37a891268f39edb5128d49', type: this.type, class: "text-input", value: this.inputValue, placeholder: this.placeholder, disabled: this.disabled, onInput: this.handleInput }), this.suffix && h("span", { key: '4ae299b88798767b13e7126f88d3bd4703c91938', class: "input-suffix" }, this.suffix)))));
29907
+ return (h(Host, { key: '7e16272802a5485207721f4f01ddf8e956b56dac' }, h("div", { key: 'ab60dacd08c6199db507d896c00cd0c788ecf7fa', class: "input-container" }, this.label && h("label", { key: 'e2e5769ca2954442da5a0f2a2abb07847986eac8', class: "input-label" }, this.label), h("div", { key: 'e7131f6d1a770cf71de9f7086e3a8a092296b80e', class: { 'input-wrapper': true, 'has-suffix': !!this.suffix } }, h("input", { key: '565cb30ed36818badc148475c596b468cdd2a65b', type: this.type, class: "text-input", value: this.inputValue, placeholder: this.placeholder, disabled: this.disabled, onInput: this.handleInput }), this.suffix && h("span", { key: 'ec4afb63443b6c89a829e515df7d33066f0f8def', class: "input-suffix" }, this.suffix)))));
29549
29908
  }
29550
29909
  static get watchers() { return {
29551
29910
  "value": [{
@@ -29685,7 +30044,7 @@ const KritzelLoginDialog = class {
29685
30044
  this.dialogClosed.emit();
29686
30045
  };
29687
30046
  render() {
29688
- return (h(Host, { key: '443d817d6eba7b9fcd31cab652293d7c01fc5ffa' }, h("kritzel-dialog", { key: '55214d78abb10f7e2ba789e10d98bf7f3287ff6a', dialogTitle: this.dialogTitle, isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, size: "small", contained: true }, h("div", { key: '31806a7a7c98c2210b7f60d2e0816a8ebc14aff3', class: "login-content" }, this.subtitle && (h("p", { key: '7edb2bc7d25cb3c8589594791822e62d035cd3a6', class: "login-subtitle" }, this.subtitle)), h("div", { key: '3b7731d9cfb98915ebe07b54a07991e03716ce5c', class: "login-providers" }, this.providers.map(provider => (h("button", { key: provider.name, class: {
30047
+ return (h(Host, { key: '6ef65077c95d32fee8da4b70ad2e832ad1bb78f7' }, h("kritzel-dialog", { key: '04f3d3350393475c7267d8ca90c0b3f0fce10c38', dialogTitle: this.dialogTitle, isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, size: "small", contained: true }, h("div", { key: 'c2ed4e339d86dcea23b4a0778b8966a6ae36107a', class: "login-content" }, this.subtitle && (h("p", { key: 'ae46baa70526d057b3427459903287489e645e08', class: "login-subtitle" }, this.subtitle)), h("div", { key: '1055d5861e5281be234094a3c5c86a6aaf58935f', class: "login-providers" }, this.providers.map(provider => (h("button", { key: provider.name, class: {
29689
30048
  'provider-button': true,
29690
30049
  'is-loading': this.loadingProvider === provider.name,
29691
30050
  'is-disabled': this.loadingProvider !== null && this.loadingProvider !== provider.name,
@@ -29791,15 +30150,15 @@ const KritzelMasterDetail = class {
29791
30150
  const selectedItem = this.items.find(item => item.id === this.selectedItemId);
29792
30151
  const panelId = 'master-detail-panel';
29793
30152
  const selectedTabId = selectedItem ? `tab-${selectedItem.id}` : undefined;
29794
- return (h(Host, { key: '6429c72aaf47f08ba0bdf5e64829b133339dafab' }, h("div", { key: '02e4616c67843632d2291abe347ee6ef7a660b12', class: {
30153
+ return (h(Host, { key: 'e094bc3df0e0d1598e84c9a17a033079b1db33f6' }, h("div", { key: '79509ba5d63703fe36650b07b5172f77bd6eafde', class: {
29795
30154
  'master-detail-container': true,
29796
30155
  'is-mobile-detail-visible': this.showMobileDetail,
29797
- } }, h("nav", { key: '35cf3e8f97819a313fa6c57cde0faf05677997eb', class: "master-menu", role: "tablist", "aria-orientation": "vertical", "aria-label": "Settings categories" }, this.items.map((item, index) => (h("button", { key: item.id, id: `tab-${item.id}`, ref: el => this.setTabRef(el, index), class: {
30156
+ } }, h("nav", { key: '79d200d8c2e54789a33fc88b07c48f034de140ad', class: "master-menu", role: "tablist", "aria-orientation": "vertical", "aria-label": "Settings categories" }, this.items.map((item, index) => (h("button", { key: item.id, id: `tab-${item.id}`, ref: el => this.setTabRef(el, index), class: {
29798
30157
  'menu-item': true,
29799
30158
  'is-selected': item.id === this.selectedItemId,
29800
30159
  'is-disabled': !!item.disabled,
29801
30160
  'is-focused': index === this.focusedIndex,
29802
- }, role: "tab", "aria-selected": item.id === this.selectedItemId ? 'true' : 'false', "aria-controls": panelId, "aria-disabled": item.disabled ? 'true' : undefined, tabIndex: this.getTabIndex(item, index), disabled: item.disabled, onClick: () => this.handleItemClick(item), onKeyDown: e => this.handleKeyDown(e, item, index), onFocus: () => this.handleFocus(index), onBlur: this.handleBlur }, item.icon && (h("kritzel-icon", { name: item.icon, size: 20, class: "menu-item-icon" })), h("span", { class: "menu-item-label" }, item.label), h("span", { class: "menu-item-chevron", "aria-hidden": "true" }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("path", { d: "m9 18 6-6-6-6" }))))))), h("div", { key: 'cc7c69eb7ef02096fb76dbce310740d21019e72f', id: panelId, class: "detail-panel", role: "tabpanel", "aria-labelledby": selectedTabId }, h("button", { key: 'd10898c7eb8ec6d993024c7833e1f765a7b93079', class: "mobile-back-button", onClick: this.handleBackClick, "aria-label": "Back to menu" }, h("kritzel-icon", { key: '5395af1bce2b91ade970ad68b1bb87b59e771dff', name: "chevron-left", size: 20, class: "mobile-back-icon" }), "Back"), h("slot", { key: '7a5a1ef33081c117f1b16f1da887577bd97d3681' })))));
30161
+ }, role: "tab", "aria-selected": item.id === this.selectedItemId ? 'true' : 'false', "aria-controls": panelId, "aria-disabled": item.disabled ? 'true' : undefined, tabIndex: this.getTabIndex(item, index), disabled: item.disabled, onClick: () => this.handleItemClick(item), onKeyDown: e => this.handleKeyDown(e, item, index), onFocus: () => this.handleFocus(index), onBlur: this.handleBlur }, item.icon && (h("kritzel-icon", { name: item.icon, size: 20, class: "menu-item-icon" })), h("span", { class: "menu-item-label" }, item.label), h("span", { class: "menu-item-chevron", "aria-hidden": "true" }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("path", { d: "m9 18 6-6-6-6" }))))))), h("div", { key: '642b65781509d192330bb2602dd081585d16f76d', id: panelId, class: "detail-panel", role: "tabpanel", "aria-labelledby": selectedTabId }, h("button", { key: '18a922dbaad63f05bc3ca7de176de4996b5d7fcb', class: "mobile-back-button", onClick: this.handleBackClick, "aria-label": "Back to menu" }, h("kritzel-icon", { key: 'b31732cef2ffdb78e67be7ab793aaaf60e373763', name: "chevron-left", size: 20, class: "mobile-back-icon" }), "Back"), h("slot", { key: '48fe4ae631a8c0b2db9e4a232edb6300d603562c' })))));
29803
30162
  }
29804
30163
  static get watchers() { return {
29805
30164
  "selectedItemId": [{
@@ -29872,7 +30231,7 @@ const KritzelMenu = class {
29872
30231
  this.itemCloseChildMenu.emit(event.detail);
29873
30232
  };
29874
30233
  render() {
29875
- return (h(Host, { key: '4e4e510d2b1b5834ba12739d95e7c33268c005a6', tabIndex: 0, onClick: e => e.stopPropagation() }, this.openChildMenuItem && h("div", { key: 'ee60d7f4ceefda5fcdcfb64956b20d42b703c231', class: "has-open-child-overlay", onClick: this.onOverlayClick }), this.items.map(item => (h("kritzel-menu-item", { key: item.id, "data-testid": `menu-item-${item.id}`, item: item, parent: this.parent, style: { pointerEvents: this.editingMenuItem && !item.isEditing ? 'none' : 'auto' }, onItemSelect: this.handleItemSelect, onItemSave: this.handleSave, onItemCancel: this.handleCancel, onItemToggleChildMenu: this.handleToggleChildMenu, onItemCloseChildMenu: this.handleCloseChildMenu })))));
30234
+ return (h(Host, { key: '6dfdc7928ae69aa191b2c64f9db79801e6978e92', tabIndex: 0, onClick: e => e.stopPropagation() }, this.openChildMenuItem && h("div", { key: 'ab8fa25f1b6ee0b7960cb0df84c40b1a7648c4e9', class: "has-open-child-overlay", onClick: this.onOverlayClick }), this.items.map(item => (h("kritzel-menu-item", { key: item.id, "data-testid": `menu-item-${item.id}`, item: item, parent: this.parent, style: { pointerEvents: this.editingMenuItem && !item.isEditing ? 'none' : 'auto' }, onItemSelect: this.handleItemSelect, onItemSave: this.handleSave, onItemCancel: this.handleCancel, onItemToggleChildMenu: this.handleToggleChildMenu, onItemCloseChildMenu: this.handleCloseChildMenu })))));
29876
30235
  }
29877
30236
  };
29878
30237
  KritzelMenu.style = kritzelMenuCss();
@@ -29977,12 +30336,12 @@ const KritzelMenuItem = class {
29977
30336
  ];
29978
30337
  }
29979
30338
  render() {
29980
- return (h(Host, { key: 'ac91e3534c71074634ba053adc02f924e3fb5e14', tabIndex: this.item.isDisabled ? -1 : 0, class: {
30339
+ return (h(Host, { key: 'dbdcfcee7fd36fe0b9c405dd3d1641aa62465e53', tabIndex: this.item.isDisabled ? -1 : 0, class: {
29981
30340
  'selected': this.item.isSelected,
29982
30341
  'editing': this.item.isEditing,
29983
30342
  'disabled': this.item.isDisabled,
29984
30343
  'child-open': this.item.isChildMenuOpen,
29985
- }, onClick: this.handleItemSelect }, h("div", { key: 'f49cf8a96f7394e244ce53d3933137325221d578', class: "menu-item-overlay" }), this.item.isEditing ? this.renderEditMode() : this.renderViewMode()));
30344
+ }, onClick: this.handleItemSelect }, h("div", { key: 'ef97c7cf48190c1bbdde0dd9011cc1d5674b7c94', class: "menu-item-overlay" }), this.item.isEditing ? this.renderEditMode() : this.renderViewMode()));
29986
30345
  }
29987
30346
  static get watchers() { return {
29988
30347
  "item": [{
@@ -30055,11 +30414,80 @@ const KritzelMoreMenu = class {
30055
30414
  this.closeMenu();
30056
30415
  };
30057
30416
  render() {
30058
- return (h(Host, { key: '1fe90a065ac35d1c8d2e9159d006b09e104750a3', class: { mobile: this.isTouchDevice }, style: { display: this.visible ? '' : 'none' } }, h("div", { key: '734234df7d24f712fa80c3871209142fed2b5ac2', class: { 'more-menu-wrapper': true, visible: this.visible } }, h("button", { key: 'a6a56bd5714a8780339390904f0469b395748ba9', class: "more-menu-button", "data-testid": "more-menu-button", onClick: this.toggleMenu, "aria-label": this.terms['moreMenu.ariaLabel'] ?? 'More options' }, h("kritzel-icon", { key: 'a4405ef4b8219f28fec095d4a56ac0aa027a9564', name: this.icon, size: this.iconSize })), h("kritzel-portal", { key: '99aa8a87844232ec079b48f95afab54cbfe51a0b', anchor: this.menuAnchor, offsetY: this.offsetY, onClose: this.closeMenu }, h("kritzel-menu", { key: 'b6861ae2d553ebf5f5af7448c2b053576a380e8c', items: this.visibleItems, onItemSelect: this.handleMenuItemSelect })))));
30417
+ return (h(Host, { key: '3e7179c87713cb0087d2d2053f97db8f603f7321', class: { mobile: this.isTouchDevice }, style: { display: this.visible ? '' : 'none' } }, h("div", { key: '23d6be1ec2c25cd0335c1134f18aad7d5be25c85', class: { 'more-menu-wrapper': true, visible: this.visible } }, h("button", { key: 'e3064ba3f7b4c20e8447e16327b73d0f47f3842b', class: "more-menu-button", "data-testid": "more-menu-button", onClick: this.toggleMenu, "aria-label": this.terms['moreMenu.ariaLabel'] ?? 'More options' }, h("kritzel-icon", { key: 'feed090efe4f80c62a29c8146c2d7d8041624381', name: this.icon, size: this.iconSize })), h("kritzel-portal", { key: '7b1a5db932c7bfb5ea8f4d666571c4f7fbbcd238', anchor: this.menuAnchor, offsetY: this.offsetY, onClose: this.closeMenu }, h("kritzel-menu", { key: '65b1225b15a494d49712ad1d2d108e90f09507e9', items: this.visibleItems, onItemSelect: this.handleMenuItemSelect })))));
30059
30418
  }
30060
30419
  };
30061
30420
  KritzelMoreMenu.style = kritzelMoreMenuCss();
30062
30421
 
30422
+ const kritzelNotificationCardCss = () => `:host{display:block}.card{box-sizing:border-box;width:100%;max-width:var(--kritzel-notification-card-max-width, 360px);margin-inline:auto;font-family:var(--kritzel-font-family, sans-serif);padding:var(--kritzel-notification-card-padding, 12px 14px);border:var(--kritzel-notification-card-border, 1px solid #e5e7eb);border-radius:var(--kritzel-notification-card-border-radius, 12px);background-color:var(--kritzel-notification-card-background-color, #ffffff);color:var(--kritzel-notification-card-color, #111827);box-shadow:var(--kritzel-notification-card-box-shadow, 0 8px 20px rgba(15, 23, 42, 0.08))}.card-header{display:flex;align-items:center;justify-content:space-between;gap:var(--kritzel-notification-card-header-gap, 8px);margin-bottom:var(--kritzel-notification-card-header-margin-bottom, 8px)}.header-right{display:inline-flex;align-items:center;gap:var(--kritzel-notification-card-header-right-gap, 6px)}.type-pill{display:inline-flex;align-items:center;justify-content:center;min-height:var(--kritzel-notification-card-pill-height, 22px);padding:var(--kritzel-notification-card-pill-padding, 0 8px);border-radius:var(--kritzel-notification-card-pill-border-radius, 999px);background-color:var(--kritzel-notification-card-pill-background-color, #eff6ff);color:var(--kritzel-notification-card-pill-color, #1d4ed8);font-size:var(--kritzel-notification-card-pill-font-size, 12px);font-weight:var(--kritzel-notification-card-pill-font-weight, 600);line-height:1}.card.type-info .type-pill{background-color:var(--kritzel-notification-card-info-pill-background-color, #eff6ff);color:var(--kritzel-notification-card-info-pill-color, #1d4ed8)}.card.type-warning .type-pill{background-color:var(--kritzel-notification-card-warning-pill-background-color, #fff7ed);color:var(--kritzel-notification-card-warning-pill-color, #b45309)}.card.type-error .type-pill{background-color:var(--kritzel-notification-card-error-pill-background-color, #fef2f2);color:var(--kritzel-notification-card-error-pill-color, #b91c1c)}.timestamp{color:var(--kritzel-notification-card-timestamp-color, #6b7280);font-size:var(--kritzel-notification-card-timestamp-font-size, 12px);white-space:nowrap}.dismiss-button{display:inline-flex;align-items:center;justify-content:center;width:var(--kritzel-notification-card-dismiss-button-size, 24px);height:var(--kritzel-notification-card-dismiss-button-size, 24px);padding:0;border:none;border-radius:var(--kritzel-notification-card-dismiss-button-border-radius, 6px);background:var(--kritzel-notification-card-dismiss-button-background-color, transparent);color:var(--kritzel-notification-card-dismiss-button-color, #4b5563);cursor:var(--kritzel-global-pointer-cursor, pointer);-webkit-tap-highlight-color:transparent}.dismiss-button:hover{background:var(--kritzel-notification-card-dismiss-button-hover-background-color, #f3f4f6)}.dismiss-button:active{background:var(--kritzel-notification-card-dismiss-button-active-background-color, #e5e7eb)}.dismiss-button:focus-visible{outline:revert}.message{margin:0;color:var(--kritzel-notification-card-message-color, inherit);font-size:var(--kritzel-notification-card-message-font-size, 14px);line-height:var(--kritzel-notification-card-message-line-height, 1.45);word-break:break-word}`;
30423
+
30424
+ const KritzelNotificationCard = class {
30425
+ constructor(hostRef) {
30426
+ registerInstance(this, hostRef);
30427
+ this.dismiss = createEvent(this, "dismiss");
30428
+ this.hoverChange = createEvent(this, "hoverChange");
30429
+ }
30430
+ /** Notification data rendered by this card. */
30431
+ notification;
30432
+ /** Whether a formatted timestamp should be displayed when available. */
30433
+ showTimestamp = true;
30434
+ /** Locale used for formatting the timestamp. */
30435
+ locale;
30436
+ /** Emitted when the notification should be dismissed manually. */
30437
+ dismiss;
30438
+ /** Emitted when pointer hover state changes. */
30439
+ hoverChange;
30440
+ handleDismissClick = (event) => {
30441
+ event.stopPropagation();
30442
+ this.dismiss.emit();
30443
+ };
30444
+ handleMouseEnter = () => {
30445
+ this.hoverChange.emit(true);
30446
+ };
30447
+ handleMouseLeave = () => {
30448
+ this.hoverChange.emit(false);
30449
+ };
30450
+ get type() {
30451
+ return this.notification?.type ?? 'info';
30452
+ }
30453
+ get role() {
30454
+ return this.type === 'error' ? 'alert' : 'status';
30455
+ }
30456
+ getTypeLabel(type) {
30457
+ switch (type) {
30458
+ case 'warning':
30459
+ return 'Warning';
30460
+ case 'error':
30461
+ return 'Error';
30462
+ default:
30463
+ return 'Info';
30464
+ }
30465
+ }
30466
+ formatTimestamp(timestamp) {
30467
+ if (!timestamp || !this.showTimestamp) {
30468
+ return null;
30469
+ }
30470
+ const date = timestamp instanceof Date ? timestamp : new Date(timestamp);
30471
+ if (Number.isNaN(date.getTime())) {
30472
+ return null;
30473
+ }
30474
+ return new Intl.DateTimeFormat(this.locale, {
30475
+ dateStyle: 'medium',
30476
+ timeStyle: 'short',
30477
+ }).format(date);
30478
+ }
30479
+ render() {
30480
+ const notification = this.notification;
30481
+ if (!notification) {
30482
+ return h(Host, null);
30483
+ }
30484
+ const type = this.type;
30485
+ const timestamp = this.formatTimestamp(notification.timestamp);
30486
+ return (h(Host, null, h("article", { class: { card: true, [`type-${type}`]: true }, role: this.role, "aria-live": "polite", onMouseEnter: this.handleMouseEnter, onMouseLeave: this.handleMouseLeave }, h("header", { class: "card-header" }, h("span", { class: "type-pill" }, this.getTypeLabel(type)), h("div", { class: "header-right" }, timestamp && (h("time", { class: "timestamp", dateTime: notification.timestamp?.toISOString() }, timestamp)), h("button", { class: "dismiss-button", type: "button", "aria-label": "Dismiss notification", onClick: this.handleDismissClick }, h("kritzel-icon", { name: "x", size: 16 })))), h("p", { class: "message" }, notification.message))));
30487
+ }
30488
+ };
30489
+ KritzelNotificationCard.style = kritzelNotificationCardCss();
30490
+
30063
30491
  const kritzelNumericInputCss = () => `:host{display:block}.input-container{display:flex;flex-direction:column;gap:6px}.input-label{font-size:14px;font-weight:500;color:var(--kritzel-numeric-input-label-color, #333333)}.input-wrapper{display:flex;position:relative}.numeric-input{flex:1;padding:8px 36px 8px 12px;border:1px solid var(--kritzel-numeric-input-border-color, #ebebeb);border-radius:var(--kritzel-numeric-input-border-radius, 6px);font-size:14px;color:var(--kritzel-numeric-input-text-color, #333333);background-color:var(--kritzel-numeric-input-input-background, #ffffff);outline:none;transition:border-color 150ms ease;box-sizing:border-box}.numeric-input::-webkit-outer-spin-button,.numeric-input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.numeric-input[type='number']{-moz-appearance:textfield}.numeric-input::selection{background-color:var(--kritzel-numeric-input-selection-background, #007AFF);color:var(--kritzel-numeric-input-selection-color, #ffffff)}.numeric-input:hover{border-color:var(--kritzel-numeric-input-hover-border-color, #cccccc)}.numeric-input:focus{border-color:var(--kritzel-numeric-input-focus-border-color, #0066ff);border-width:2px;padding:7px 35px 7px 11px}.spinner-buttons{position:absolute;right:1px;top:1px;bottom:1px;display:flex;flex-direction:column;border-left:1px solid var(--kritzel-numeric-input-border-color, #ebebeb);border-radius:0 var(--kritzel-numeric-input-spinner-border-radius, 5px) var(--kritzel-numeric-input-spinner-border-radius, 5px) 0;overflow:hidden}.spinner-button{flex:1;display:flex;align-items:center;justify-content:center;width:24px;padding:0;border:none;background-color:var(--kritzel-numeric-input-spinner-background, #f5f5f5);color:var(--kritzel-numeric-input-spinner-color, #666666);cursor:pointer;transition:background-color 150ms ease}.spinner-button:hover{background-color:var(--kritzel-numeric-input-spinner-hover-background, #ebebeb)}.spinner-button:active{background-color:var(--kritzel-numeric-input-spinner-active-background, #dddddd)}.spinner-up{border-bottom:1px solid var(--kritzel-numeric-input-border-color, #ebebeb)}.spinner-icon{width:10px;height:6px}`;
30064
30492
 
30065
30493
  const KritzelNumericInput = class {
@@ -30153,7 +30581,7 @@ const KritzelNumericInput = class {
30153
30581
  this.valueChange.emit(newValue);
30154
30582
  };
30155
30583
  render() {
30156
- return (h(Host, { key: '3679786121f22a0aacb1b57701764b0bd0bf02f5' }, h("div", { key: 'e88251c356332f29d93f6a3d82f5154188243e4c', class: "input-container" }, this.label && h("label", { key: 'cb4d64a5acf94794a5876bc61dc13dd8159ad055', class: "input-label" }, this.label), h("div", { key: '9faad300f263e871322b68635feefb08172bd15c', class: "input-wrapper" }, h("input", { key: 'dd58ade6fe8454a3e4f67fddbb135d279f1147a8', type: "number", class: "numeric-input", title: "", min: this.min === Number.MIN_SAFE_INTEGER ? undefined : this.min, max: this.max === Number.MAX_SAFE_INTEGER ? undefined : this.max, step: this.step, value: this.inputValue, placeholder: this.placeholder, onInput: this.handleInput, onBlur: this.handleBlur, onKeyDown: this.handleKeyDown, onInvalid: this.handleInvalid }), h("div", { key: 'a959beb441fe2143dc9c1c69347194e024362927', class: "spinner-buttons" }, h("button", { key: '6e2129f36080317e68b6fe1c05e69ea4e655fd20', type: "button", class: "spinner-button spinner-up", onClick: this.handleIncrement, tabIndex: -1, "aria-label": "Increase value" }, h("svg", { key: '47b5e178f82ef4eb6f2c5d888496ed95683c2f6d', viewBox: "0 0 10 6", class: "spinner-icon" }, h("path", { key: '6d5eb31ac5829b1e024822786beaafd347081c7e', d: "M1 5L5 1L9 5", stroke: "currentColor", "stroke-width": "1.5", fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }))), h("button", { key: '5a897f0b29ce89d5bd94327d2055753d06148c89', type: "button", class: "spinner-button spinner-down", onClick: this.handleDecrement, tabIndex: -1, "aria-label": "Decrease value" }, h("svg", { key: '19ff176c07e1c2a18f708debc7c7f3953f2219cb', viewBox: "0 0 10 6", class: "spinner-icon" }, h("path", { key: '0c1289eb009b62ea36f7397811f553a4e7e669f6', d: "M1 1L5 5L9 1", stroke: "currentColor", "stroke-width": "1.5", fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }))))))));
30584
+ return (h(Host, { key: '008f18fc5ab81adcd82364706ca7d4c39ea39d77' }, h("div", { key: '5d91b2ec6e0458d186d923c422e8f6461ac7c51a', class: "input-container" }, this.label && h("label", { key: '45c911f1e11ff8289ab876192511f506eb65c940', class: "input-label" }, this.label), h("div", { key: 'f5e0bd1610a4085a291cc1d32787b68066757c69', class: "input-wrapper" }, h("input", { key: '897b1af7725f59df86a530836dda802e8cb04038', type: "number", class: "numeric-input", title: "", min: this.min === Number.MIN_SAFE_INTEGER ? undefined : this.min, max: this.max === Number.MAX_SAFE_INTEGER ? undefined : this.max, step: this.step, value: this.inputValue, placeholder: this.placeholder, onInput: this.handleInput, onBlur: this.handleBlur, onKeyDown: this.handleKeyDown, onInvalid: this.handleInvalid }), h("div", { key: '1acab70c82ff7431f916685084a6e5dd827cdca3', class: "spinner-buttons" }, h("button", { key: 'd93c4bec338d49ead87897baf61d2e036999a035', type: "button", class: "spinner-button spinner-up", onClick: this.handleIncrement, tabIndex: -1, "aria-label": "Increase value" }, h("svg", { key: '1e52d43c1eb1e9a43025302a7cd1f3d74c038c36', viewBox: "0 0 10 6", class: "spinner-icon" }, h("path", { key: '2a96516390bd9caf5b6a3617d07f7b10e2b42451', d: "M1 5L5 1L9 5", stroke: "currentColor", "stroke-width": "1.5", fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }))), h("button", { key: '038cf61f4b8982a17bf5c279d78556872a03efea', type: "button", class: "spinner-button spinner-down", onClick: this.handleDecrement, tabIndex: -1, "aria-label": "Decrease value" }, h("svg", { key: '30110c2ad975b51049a902953c52be66c1b7b0af', viewBox: "0 0 10 6", class: "spinner-icon" }, h("path", { key: '5abe735234debfa80a983d2fb60eeeaedb1dbf9b', d: "M1 1L5 5L9 1", stroke: "currentColor", "stroke-width": "1.5", fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }))))))));
30157
30585
  }
30158
30586
  static get watchers() { return {
30159
30587
  "value": [{
@@ -30192,7 +30620,7 @@ const KritzelOpacitySlider = class {
30192
30620
  }
30193
30621
  render() {
30194
30622
  const percentage = this.getPercentage();
30195
- return (h(Host, { key: '9b580f61b9113950b7dff5339879374f7882e1bd' }, h("div", { key: '80fb336580c49d4d7026993499b9c008ac2a4594', class: "opacity-container" }, h("div", { key: 'fcdd8e523d5f2ff4758a4839f2f43cbdabba4c5a', class: "slider-wrapper" }, h("input", { key: '914473676adc7cb8974c55b82ba1abf5b2787288', type: "range", class: "opacity-slider", min: this.min, max: this.max, step: this.step, value: this.value, onInput: (e) => this.handleInput(e), style: {
30623
+ return (h(Host, { key: 'e3a18981e5193ecd1ffaae5c807bdf6fa67e9695' }, h("div", { key: 'ee5ff0bb1355ca4f7e5d727ab8861933259c1a82', class: "opacity-container" }, h("div", { key: '6362fb3dba6910c55f5fe3b2a4245bcbb33dcdbb', class: "slider-wrapper" }, h("input", { key: 'f435f00fff7cc17ccdc181f39b29e032f6c5bf8d', type: "range", class: "opacity-slider", min: this.min, max: this.max, step: this.step, value: this.value, onInput: (e) => this.handleInput(e), style: {
30196
30624
  '--slider-progress': `${percentage}%`,
30197
30625
  '--kritzel-opacity-slider-thumb-border-color': this.previewColor,
30198
30626
  } })))));
@@ -30545,7 +30973,7 @@ const KritzelPortal = class {
30545
30973
  this.portal.style.visibility = 'visible';
30546
30974
  }
30547
30975
  render() {
30548
- return (h(Host, { key: 'f4ee3156b094ff1126db417f8396ca6cd0eb9aa4', style: { display: this.anchor ? 'block' : 'none' } }, h("slot", { key: '87812f4ddd50a29c8a76938e8778500a4873a30c' })));
30976
+ return (h(Host, { key: '95fbfde070f33a7f1b59026de7c687a2f6d0119c', style: { display: this.anchor ? 'block' : 'none' } }, h("slot", { key: '14af59a35c5caf71079f7afb5ea6f4c5f13b05f8' })));
30549
30977
  }
30550
30978
  static get watchers() { return {
30551
30979
  "anchor": [{
@@ -30559,7 +30987,7 @@ const KritzelPortal = class {
30559
30987
  * This file is auto-generated by the version bump scripts.
30560
30988
  * Do not modify manually.
30561
30989
  */
30562
- const KRITZEL_VERSION = '0.3.18';
30990
+ const KRITZEL_VERSION = '0.3.20';
30563
30991
 
30564
30992
  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)}`;
30565
30993
 
@@ -30783,7 +31211,7 @@ const KritzelSettings = class {
30783
31211
  }
30784
31212
  }
30785
31213
  render() {
30786
- return (h(Host, { key: '8dc22abaa2a19a14f3bbef10e34d133b16510498' }, h("kritzel-dialog", { key: '54072bf290e2d86bb7ec40d8d3e630be46fa3e1e', isOpen: this.isDialogOpen, dialogTitle: this.t('settings.dialogTitle', 'Settings'), size: "large", contained: true, onDialogClose: this.closeDialog }, h("kritzel-master-detail", { key: '5be80411c943ab4b7b90129527c8a0d147306a5f', items: this.categories, selectedItemId: this.selectedCategoryId, onItemSelect: this.handleCategorySelect }, this.renderCategoryContent()))));
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()))));
30787
31215
  }
30788
31216
  static get watchers() { return {
30789
31217
  "settings": [{
@@ -30916,9 +31344,9 @@ const KritzelShareDialog = class {
30916
31344
  this.dialogClosed.emit();
30917
31345
  };
30918
31346
  render() {
30919
- return (h(Host, { key: 'b364f891659d54623b5472fe4cd5a9a85bc2fafa' }, h("kritzel-dialog", { key: '7059a3ede84bc67f7661efc8c84eae9b0c401d43', dialogTitle: this.terms['share.dialogTitle'] ?? 'Share Workspace', size: "small", isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, contained: true }, h("div", { key: '1b0e7a9b5483a53f91e874075c0edb116b7474d6', class: "share-content" }, h("div", { key: '1568dc99224f0f7e2a370c22327dc313515e7d0f', class: "share-section" }, h("div", { key: 'a0032f6c870a448e817b013a715aadca1c455bb3', class: "share-row" }, h("div", { key: '3bd74cbebc122133354465163f9945dd207245b5', class: "share-label-group" }, h("label", { key: '0919fc24e5637c908c53b6914983833b6600e846', class: "share-label" }, this.terms['share.linkSharing.label'] ?? 'Link sharing'), h("p", { key: 'f87c9eae7112d284d7d917fae7c9c05fa6feb0ed', class: "share-description" }, this.internalIsPublic
31347
+ return (h(Host, { key: '031cf5d1ab48f2791454d4c7348352295359d225' }, h("kritzel-dialog", { key: 'cdce63c86ca329a97d1f1787adcb2b92bbfa40f9', dialogTitle: this.terms['share.dialogTitle'] ?? 'Share Workspace', size: "small", isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, contained: true }, h("div", { key: 'dbb62b3cb175358955b11f6c2d74b0b0eecf212e', class: "share-content" }, h("div", { key: '8dc0eaea9644b60ff33812dac20def226fa14232', class: "share-section" }, h("div", { key: 'd3cfbf05c28a01681b1223edb6f0b73998ccfd08', class: "share-row" }, h("div", { key: '2400f062f92dd918341391cf71086c2676f7fc66', class: "share-label-group" }, h("label", { key: 'ab01691b9f53f59f92116a2994b6e3551d347c7b', class: "share-label" }, this.terms['share.linkSharing.label'] ?? 'Link sharing'), h("p", { key: 'fd830ad21e32e4fc5d8ba97d5cea7ef8da15fba4', class: "share-description" }, this.internalIsPublic
30920
31348
  ? (this.terms['share.linkSharing.enabledDescription'] ?? 'Anyone with the link can access this workspace.')
30921
- : (this.terms['share.linkSharing.disabledDescription'] ?? 'Link sharing is disabled. Only you can access this workspace.'))), h("kritzel-slide-toggle", { key: '81ccfb76255d40da954cfdcbc486bdbb7dc4bff4', checked: this.internalIsPublic, onCheckedChange: this.handleToggleChange, label: this.terms['share.linkSharing.toggleLabel'] ?? 'Enable link sharing' }))), this.internalIsPublic && (h("div", { key: 'c72da3550416ebfef5c24690591b212df70cb4db', class: "share-section" }, h("div", { key: 'c3dd4bce7d57e60d09989bff6dd1d828a613b425', class: "share-url-container" }, h("input", { key: '67aab8a65f54ea67392a9874e949d6d1af519e0f', type: "text", class: "share-url-input", value: this.getShareUrl(), readOnly: true, onClick: (e) => e.target.select() }), h("button", { key: 'c31d48fe67cdd12aa517e20bac54ac74c05abd04', class: { 'copy-button': true, 'copy-success': this.copySuccess }, onClick: this.handleCopyUrl, title: this.copySuccess ? (this.terms['share.copyLink.copied'] ?? 'Copied!') : (this.terms['share.copyLink.title'] ?? 'Copy link') }, h("kritzel-icon", { key: '08ed01ed081e955e28eaed577438cd0008eaacf4', name: this.copySuccess ? 'check' : 'copy', size: 18 })))))))));
31349
+ : (this.terms['share.linkSharing.disabledDescription'] ?? 'Link sharing is disabled. Only you can access this workspace.'))), h("kritzel-slide-toggle", { key: '6f1a71ba524b34753ccd5437eab00bfcbeffdd79', checked: this.internalIsPublic, onCheckedChange: this.handleToggleChange, label: this.terms['share.linkSharing.toggleLabel'] ?? 'Enable link sharing' }))), this.internalIsPublic && (h("div", { key: '298680e1afdf0094c5393aa95c4983a0a8dd5e18', class: "share-section" }, h("div", { key: '546645a748cf9a42df0c8163f2478ef31445273a', class: "share-url-container" }, h("input", { key: '9d5b93d762d1cd899f3fc8041380bae603a4919c', type: "text", class: "share-url-input", value: this.getShareUrl(), readOnly: true, onClick: (e) => e.target.select() }), h("button", { key: '747a7ca4a25827de6934b7df30cabc2db4583e1b', class: { 'copy-button': true, 'copy-success': this.copySuccess }, onClick: this.handleCopyUrl, title: this.copySuccess ? (this.terms['share.copyLink.copied'] ?? 'Copied!') : (this.terms['share.copyLink.title'] ?? 'Copy link') }, h("kritzel-icon", { key: '096ca9a0527796462769dc9d42dd81d8f97f2865', name: this.copySuccess ? 'check' : 'copy', size: 18 })))))))));
30922
31350
  }
30923
31351
  static get watchers() { return {
30924
31352
  "isPublic": [{
@@ -30956,7 +31384,7 @@ const KritzelSlideToggle = class {
30956
31384
  }
30957
31385
  };
30958
31386
  render() {
30959
- return (h(Host, { key: '92aefb30139abd512c02c99f5c532cb69daab8c3', class: { checked: this.checked, disabled: this.disabled }, tabIndex: this.disabled ? -1 : 0, role: "switch", "aria-checked": this.checked ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : 'false', "aria-label": this.label, onClick: this.handleToggle, onKeyDown: this.handleKeyDown }, h("div", { key: '53fc90f3a11055e0b16358be0e3beed2ed25bc48', class: "toggle-track" }, h("div", { key: 'e0a0cecb9ede9c8e932cbb6fa7cdc359c07be8db', class: "toggle-thumb" }))));
31387
+ return (h(Host, { key: '5850c758ccf3263b21c397219bf2301fa088087d', class: { checked: this.checked, disabled: this.disabled }, tabIndex: this.disabled ? -1 : 0, role: "switch", "aria-checked": this.checked ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : 'false', "aria-label": this.label, onClick: this.handleToggle, onKeyDown: this.handleKeyDown }, h("div", { key: 'df0adeca0447a13d5499219ac9d27b526252fdbe', class: "toggle-track" }, h("div", { key: '3938239bbdca29e62ed72229dfcf1904fcbaa983', class: "toggle-thumb" }))));
30960
31388
  }
30961
31389
  };
30962
31390
  KritzelSlideToggle.style = kritzelSlideToggleCss();
@@ -31056,7 +31484,7 @@ const KritzelSplitButton = class {
31056
31484
  this.menuScrollTop = event.target.scrollTop;
31057
31485
  };
31058
31486
  render() {
31059
- return (h(Host, { key: '39516b2486a27534273b6bf4def6b11790aa0ed5', class: { mobile: this.isTouchDevice } }, h("button", { key: '19c4a58deb3992857b0e42e4a035a15d0980da22', class: "split-main-button", tabIndex: 0, onClick: this.handleButtonClick, disabled: this.mainButtonDisabled, "aria-label": "Main action" }, this.buttonIcon && h("kritzel-icon", { key: '4c8e7e09e60717b1653251468c2a25d41c4b1921', name: this.buttonIcon })), h("div", { key: '53617db6b630dfb5320e7309dcbc9f7288ff8974', class: "split-divider" }), h("button", { key: '5134f88ed43558ff0cb11b8ef29cf2b370aad4ee', ref: el => (this.splitMenuButtonRef = el), class: "split-menu-button", tabIndex: 0, onClick: this.toggleMenu, disabled: this.menuButtonDisabled, "aria-label": "Open menu" }, h("kritzel-icon", { key: '41f39a601ae91d4faaf54c1ca39c90e27e084d31', name: this.dropdownIcon })), h("kritzel-portal", { key: 'dcfe82f72a53e9346b48d2ff6da236c1f1344f3f', anchor: this.anchorElement, offsetY: 4, onClose: this.closeMenu }, h("kritzel-menu", { key: '1208990bff47de642eafa6ca8cfaa76de46ca87c', ref: el => (this.menuRef = el), items: this.items, onItemSelect: this.handleItemSelect, onItemSave: this.handleItemSave, onItemCancel: this.handleItemCancel, onItemToggleChildMenu: this.handleItemToggleChildMenu, onItemCloseChildMenu: this.handleItemCloseChildMenu, onClose: this.closeMenu, onScroll: this.handleScroll }))));
31487
+ return (h(Host, { key: 'd6856a86b3be4d9a22c5e78df1bda19113cb988b', class: { mobile: this.isTouchDevice } }, h("button", { key: 'd26026af275a8bd5717254223d5829e3d7f02bcb', class: "split-main-button", tabIndex: 0, onClick: this.handleButtonClick, disabled: this.mainButtonDisabled, "aria-label": "Main action" }, this.buttonIcon && h("kritzel-icon", { key: '1b3e2f4c2f8245b281d0f85458fd6bcc0cc73c59', name: this.buttonIcon })), h("div", { key: 'b002b904e168d62075fa086c88df72e36cc8e8c2', class: "split-divider" }), h("button", { key: 'd39084da6775613f2c896f3fa1ae4e216505bacc', ref: el => (this.splitMenuButtonRef = el), class: "split-menu-button", tabIndex: 0, onClick: this.toggleMenu, disabled: this.menuButtonDisabled, "aria-label": "Open menu" }, h("kritzel-icon", { key: 'b1bc427b0d7cd26a283dbeb772dee0a6d2ca172c', name: this.dropdownIcon })), h("kritzel-portal", { key: 'e76300f48a23772a5923ea2cbd0824590302e7e7', anchor: this.anchorElement, offsetY: 4, onClose: this.closeMenu }, h("kritzel-menu", { key: 'f110b9315ec63dca9bb1b615136b2d3e284567ba', ref: el => (this.menuRef = el), items: this.items, onItemSelect: this.handleItemSelect, onItemSave: this.handleItemSave, onItemCancel: this.handleItemCancel, onItemToggleChildMenu: this.handleItemToggleChildMenu, onItemCloseChildMenu: this.handleItemCloseChildMenu, onClose: this.closeMenu, onScroll: this.handleScroll }))));
31060
31488
  }
31061
31489
  };
31062
31490
  KritzelSplitButton.style = kritzelSplitButtonCss();
@@ -31077,7 +31505,7 @@ const KritzelStrokeSize = class {
31077
31505
  }
31078
31506
  render() {
31079
31507
  const sizes = this.sizes ?? [];
31080
- return (h(Host, { key: '20c11daaf60055262706b8177b1090bddd2d522c' }, h("div", { key: '1729e29d836f85fe3be984a5ee78a0debc0e6218', class: "size-grid" }, sizes.map(size => (h("div", { tabIndex: 0, class: {
31508
+ return (h(Host, { key: 'bde2cc986aebbe869a3eea026654229dee0d3e0e' }, h("div", { key: 'f84c4b844b865080f4e0e245bbccc6d77ac3f049', class: "size-grid" }, sizes.map(size => (h("div", { tabIndex: 0, class: {
31081
31509
  'size-container': true,
31082
31510
  'selected': this.selectedSize === size,
31083
31511
  }, onClick: () => this.handleSizeClick(size) }, h("kritzel-color", { value: 'var(--kritzel-global-text-primary)', size: size })))))));
@@ -31458,14 +31886,14 @@ const KritzelTooltip = class {
31458
31886
  }
31459
31887
  }
31460
31888
  render() {
31461
- return (h(Host, { key: '57c0c2ddbe3f88815b975b7cf5b02c7da389b7f9', style: {
31889
+ return (h(Host, { key: 'ba4d13171aa25acac6c615ee12f35eab96d0aad6', style: {
31462
31890
  position: 'fixed',
31463
31891
  zIndex: '9999',
31464
31892
  transition: 'opacity 0.3s ease-in-out, transform 0.3s ease-in-out',
31465
31893
  visibility: this.isVisible ? 'visible' : 'hidden',
31466
31894
  left: `${this.positionX}px`,
31467
31895
  bottom: `${this.positionY}px`,
31468
- } }, h("div", { key: '3ab17ef4ba55ed39458814685574b0c00504a07d', class: "tooltip-content", onClick: event => event.stopPropagation(), onPointerDown: event => event.stopPropagation(), onMouseDown: event => event.stopPropagation() }, h("slot", { key: 'b0aa1eb2c9363644939bcc59a80d0294787f0946' }))));
31896
+ } }, h("div", { key: '923168164a018b08eac4246d88c7f8648b71d078', class: "tooltip-content", onClick: event => event.stopPropagation(), onPointerDown: event => event.stopPropagation(), onMouseDown: event => event.stopPropagation() }, h("slot", { key: 'aafad8ab026d591ebb9e2aeffa15d07718eba261' }))));
31469
31897
  }
31470
31898
  static get watchers() { return {
31471
31899
  "triggerElement": [{
@@ -31506,7 +31934,7 @@ const KritzelUtilityPanel = class {
31506
31934
  this.redo.emit();
31507
31935
  }
31508
31936
  render() {
31509
- return (h(Host, { key: '7738c225b6ec936e032770e0a7387b01718e94e0' }, h("button", { key: '8bc7346eb02e3c220ca9f9c06f1ae048e83667ee', class: "utility-button", "data-testid": "utility-undo", disabled: !this.undoState?.canUndo, onClick: event => this.handleUndo(event), "aria-label": this.terms['utility.undo'] ?? 'Undo' }, h("kritzel-icon", { key: '3c7d87edc047113cac359f4782c7feeb53e18193', name: "undo" })), h("button", { key: '960dc7783cfe558f08749b9e71c6583c8d53c167', class: "utility-button", "data-testid": "utility-redo", disabled: !this.undoState?.canRedo, onClick: event => this.handleRedo(event), "aria-label": this.terms['utility.redo'] ?? 'Redo' }, h("kritzel-icon", { key: 'b5ac0b631c51b8e6c2762b4887dcdef899327e52', name: "redo" })), h("div", { key: '4075c8eb7d8c57d5e93afb18282c7a50ec8e050e', class: "utility-separator" }), h("button", { key: '4e7c53a0933c527e84e0ad78519983446f10d23c', class: "utility-button", "data-testid": "utility-delete", onClick: () => this.delete.emit(), "aria-label": this.terms['utility.delete'] ?? 'Delete selected items' }, h("kritzel-icon", { key: 'f2d3d9817626c53e2e4c3d4c7c702705b25d7788', name: "delete" }))));
31937
+ return (h(Host, { key: '22f3826d57b4d8f0e476951a860bde3dbc6c7200' }, h("button", { key: 'c3259c5e924b3cf74920dc4676ec8a682c3273e8', class: "utility-button", "data-testid": "utility-undo", disabled: !this.undoState?.canUndo, onClick: event => this.handleUndo(event), "aria-label": this.terms['utility.undo'] ?? 'Undo' }, h("kritzel-icon", { key: 'ab4e9da6b60d9fa470fd8f5002f2acd5a758a8ba', name: "undo" })), h("button", { key: 'd17e25e78831b5ee13596cc2639cfbb5bd75a1cb', class: "utility-button", "data-testid": "utility-redo", disabled: !this.undoState?.canRedo, onClick: event => this.handleRedo(event), "aria-label": this.terms['utility.redo'] ?? 'Redo' }, h("kritzel-icon", { key: '7ae8cec39ffab1303d76315ab85ac575b886b233', name: "redo" })), h("div", { key: 'f54e0262f133ad0eed0bbf853b67edd6c2f5e87f', class: "utility-separator" }), h("button", { key: '162d6e85965338b5700e661595dc6da6131ebccf', class: "utility-button", "data-testid": "utility-delete", onClick: () => this.delete.emit(), "aria-label": this.terms['utility.delete'] ?? 'Delete selected items' }, h("kritzel-icon", { key: 'dc00247ed45f09b86ad9314395f56e70d1122a4b', name: "delete" }))));
31510
31938
  }
31511
31939
  };
31512
31940
  KritzelUtilityPanel.style = kritzelUtilityPanelCss();
@@ -31527,7 +31955,7 @@ const KritzelWatermark = class {
31527
31955
  label;
31528
31956
  render() {
31529
31957
  const label = this.label ?? this.core.localizationManager.translate('watermark.poweredBy');
31530
- return (h(Host, { key: '7ec2a304ac711a4d2a54edef317eef9f4aad1d47' }, h("a", { key: '365d588727bbc360fd47bc9a3fbd66155770d32f', class: "watermark-link", href: KRITZEL_WEBSITE_URL, target: "_blank", rel: "noopener noreferrer", part: "watermark", onPointerDown: (ev) => ev.stopPropagation(), onPointerUp: (ev) => ev.stopPropagation() }, label)));
31958
+ return (h(Host, { key: '975534180d82064e38fa0289a2955e97a4ca0a08' }, h("a", { key: 'c59723d1f5bf3089256c0b610d0574bfc385ef85', class: "watermark-link", href: KRITZEL_WEBSITE_URL, target: "_blank", rel: "noopener noreferrer", part: "watermark", onPointerDown: (ev) => ev.stopPropagation(), onPointerUp: (ev) => ev.stopPropagation() }, label)));
31531
31959
  }
31532
31960
  };
31533
31961
  KritzelWatermark.style = kritzelWatermarkCss();
@@ -31715,9 +32143,9 @@ const KritzelZoomPanel = class {
31715
32143
  return Number.isFinite(this.zoomPercent) ? Math.max(1, Math.round(this.zoomPercent)) : 100;
31716
32144
  }
31717
32145
  render() {
31718
- return (h(Host, { key: '6c88ed18f63e1a4cfffef6ba3aa1772404908d59' }, h("div", { key: '18fcdf05a6822d2b9cf319059118b4851429283d', class: { panel: true, visible: this.visible } }, h("button", { key: 'df2c90912e091e4f2ca025dd246916e9fea6f7fa', class: "zoom-button", type: "button", "aria-label": this.terms['zoom.zoomOut'] ?? 'Zoom out', disabled: this.disabled, onClick: this.handleZoomOut }, h("kritzel-icon", { key: '293847a34220520592e74f50c89d57140d7f1cbe', name: "minus" })), h("span", { key: '575119b81e6d5d41f5802a29be1b586727968919', class: "zoom-level", "aria-live": "polite" }, this.normalizedZoomPercent, "%"), h("button", { key: '8cdd7100aaeb313f2504351204ff512142414815', class: "zoom-button", type: "button", "aria-label": this.terms['zoom.zoomIn'] ?? 'Zoom in', disabled: this.disabled, onClick: this.handleZoomIn }, h("kritzel-icon", { key: 'b792164a29dd43ef601a4f469fed440ee1d72510', name: "plus" })))));
32146
+ return (h(Host, { key: '00d5c674581d4efa40da3a2ad5cbdd8295406590' }, h("div", { key: 'c7a4158f57b8062f37ee69f7b198442ec357d746', class: { panel: true, visible: this.visible } }, h("button", { key: 'e20344f5e522d481185a9dab7afdb2668cf44408', class: "zoom-button", type: "button", "aria-label": this.terms['zoom.zoomOut'] ?? 'Zoom out', disabled: this.disabled, onClick: this.handleZoomOut }, h("kritzel-icon", { key: '4fc1eeb78669894b0939a96c5e6945cea5056ac1', name: "minus" })), h("span", { key: 'ed26096e2ca76fe91b50d73ea6c9481996d01590', class: "zoom-level", "aria-live": "polite" }, this.normalizedZoomPercent, "%"), h("button", { key: '42609c6e8558b7d9e9e348b864639ead6c4cc9bb', class: "zoom-button", type: "button", "aria-label": this.terms['zoom.zoomIn'] ?? 'Zoom in', disabled: this.disabled, onClick: this.handleZoomIn }, h("kritzel-icon", { key: '3a321afb57f532ef867bae73751314f3fd355214', name: "plus" })))));
31719
32147
  }
31720
32148
  };
31721
32149
  KritzelZoomPanel.style = kritzelZoomPanelCss();
31722
32150
 
31723
- export { KritzelActiveUsers as kritzel_active_users, KritzelAvatar as kritzel_avatar, KritzelAwarenessCursors as kritzel_awareness_cursors, KritzelBackToContent as kritzel_back_to_content, KritzelButton as kritzel_button, KritzelColorComponent as kritzel_color, KritzelColorPalette as kritzel_color_palette, KritzelContextMenu as kritzel_context_menu, KritzelControls as kritzel_controls, KritzelCurrentUser as kritzel_current_user, KritzelCurrentUserDialog as kritzel_current_user_dialog, KritzelCursorTrail as kritzel_cursor_trail, KritzelDialog as kritzel_dialog, KritzelDropdown as kritzel_dropdown, KritzelEditor as kritzel_editor, KritzelEngine as kritzel_engine, KritzelExport as kritzel_export, KritzelFont as kritzel_font, KritzelFontFamily as kritzel_font_family, KritzelFontSize as kritzel_font_size, KritzelIcon as kritzel_icon, KritzelInput as kritzel_input, KritzelLineEndings as kritzel_line_endings, KritzelLoginDialog as kritzel_login_dialog, KritzelMasterDetail as kritzel_master_detail, KritzelMenu as kritzel_menu, KritzelMenuItem as kritzel_menu_item, KritzelMoreMenu as kritzel_more_menu, KritzelNumericInput as kritzel_numeric_input, KritzelOpacitySlider as kritzel_opacity_slider, KritzelPillTabs as kritzel_pill_tabs, KritzelPortal as kritzel_portal, KritzelSettings as kritzel_settings, KritzelShapeFill as kritzel_shape_fill, KritzelShareDialog as kritzel_share_dialog, KritzelSlideToggle as kritzel_slide_toggle, KritzelSplitButton as kritzel_split_button, KritzelStrokeSize as kritzel_stroke_size, KritzelToolConfig as kritzel_tool_config, KritzelTooltip as kritzel_tooltip, KritzelUtilityPanel as kritzel_utility_panel, KritzelWatermark as kritzel_watermark, KritzelWorkspaceManager as kritzel_workspace_manager, KritzelZoomPanel as kritzel_zoom_panel };
32151
+ export { KritzelActiveUsers as kritzel_active_users, KritzelAvatar as kritzel_avatar, KritzelAwarenessCursors as kritzel_awareness_cursors, KritzelBackToContent as kritzel_back_to_content, KritzelButton as kritzel_button, KritzelColorComponent as kritzel_color, KritzelColorPalette as kritzel_color_palette, KritzelContextMenu as kritzel_context_menu, KritzelControls as kritzel_controls, KritzelCurrentUser as kritzel_current_user, KritzelCurrentUserDialog as kritzel_current_user_dialog, KritzelCursorTrail as kritzel_cursor_trail, KritzelDialog as kritzel_dialog, KritzelDropdown as kritzel_dropdown, KritzelEditor as kritzel_editor, KritzelEngine as kritzel_engine, KritzelExport as kritzel_export, KritzelFont as kritzel_font, KritzelFontFamily as kritzel_font_family, KritzelFontSize as kritzel_font_size, KritzelIcon as kritzel_icon, KritzelInput as kritzel_input, KritzelLineEndings as kritzel_line_endings, KritzelLoginDialog as kritzel_login_dialog, KritzelMasterDetail as kritzel_master_detail, KritzelMenu as kritzel_menu, KritzelMenuItem as kritzel_menu_item, KritzelMoreMenu as kritzel_more_menu, KritzelNotificationCard as kritzel_notification_card, KritzelNumericInput as kritzel_numeric_input, KritzelOpacitySlider as kritzel_opacity_slider, KritzelPillTabs as kritzel_pill_tabs, KritzelPortal as kritzel_portal, KritzelSettings as kritzel_settings, KritzelShapeFill as kritzel_shape_fill, KritzelShareDialog as kritzel_share_dialog, KritzelSlideToggle as kritzel_slide_toggle, KritzelSplitButton as kritzel_split_button, KritzelStrokeSize as kritzel_stroke_size, KritzelToolConfig as kritzel_tool_config, KritzelTooltip as kritzel_tooltip, KritzelUtilityPanel as kritzel_utility_panel, KritzelWatermark as kritzel_watermark, KritzelWorkspaceManager as kritzel_workspace_manager, KritzelZoomPanel as kritzel_zoom_panel };