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,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var index = require('./index-Xav9JFHg.js');
4
- var schema_constants = require('./schema.constants-DrHO_CYF.js');
4
+ var schema_constants = require('./schema.constants-IBG5VW_J.js');
5
5
  var Y = require('yjs');
6
6
  require('y-indexeddb');
7
7
  require('y-websocket');
@@ -154,16 +154,16 @@ const KritzelAvatar = class {
154
154
  height: `${this.size}px`,
155
155
  fontSize: `${Math.round(this.size * 0.4)}px`,
156
156
  };
157
- return (index.h(index.Host, { key: '0d372a5443f41835c2e8e5b33b58bcb6c1292e89', style: containerStyles, class: {
157
+ return (index.h(index.Host, { key: '4bdbe2c1d5d3a1708189166498ae4e36c19c46ff', style: containerStyles, class: {
158
158
  'has-image': !!showImage,
159
159
  'has-initials': !!showInitials,
160
160
  'has-default': !!showDefaultIcon,
161
- }, role: "img", "aria-label": this.getDisplayName() || 'User avatar' }, showImage && (index.h("img", { key: 'deb5f12115dd28b4b4ab2157cb2bbc9e48bb2a3f', src: imageUrl, alt: "", class: "avatar-image", ref: (el) => {
161
+ }, role: "img", "aria-label": this.getDisplayName() || 'User avatar' }, showImage && (index.h("img", { key: '55c3a13c9d514ea700c6b1a9339cd0dff1828b2b', src: imageUrl, alt: "", class: "avatar-image", ref: (el) => {
162
162
  if (el) {
163
163
  el.referrerPolicy = 'no-referrer';
164
164
  el.crossOrigin = 'anonymous';
165
165
  }
166
- }, onError: this.handleImageError })), showInitials && (index.h("span", { key: '6fc12f2d32923f33df5a8ec743d51434f67e6934', class: "avatar-initials", style: { backgroundColor: this.getBackgroundColor() } }, initials)), showDefaultIcon && (index.h("span", { key: 'd803b385f7eaa0659d7452231bfd46b1634f204d', class: "avatar-default" }, index.h("svg", { key: 'a3275ab33f58440abcd15c296d9c027dfab6495c', viewBox: "0 0 24 24", fill: "currentColor" }, index.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" }))))));
166
+ }, onError: this.handleImageError })), showInitials && (index.h("span", { key: 'b60cbe2b94ea1d2cd2a09d10c928290fc1469a4a', class: "avatar-initials", style: { backgroundColor: this.getBackgroundColor() } }, initials)), showDefaultIcon && (index.h("span", { key: 'e8a0f49952b5b7e5e4a1fe3d67e68a56081d5e4c', class: "avatar-default" }, index.h("svg", { key: '1fdc256fd53aa5c866bb0e81fdd40393c8a7d69d', viewBox: "0 0 24 24", fill: "currentColor" }, index.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" }))))));
167
167
  }
168
168
  static get watchers() { return {
169
169
  "user": [{
@@ -473,7 +473,7 @@ const KritzelBackToContent = class {
473
473
  this.backToContent.emit();
474
474
  };
475
475
  render() {
476
- return (index.h(index.Host, { key: '5ef034156f28bdd1861d99343d51a237498db46b' }, index.h("button", { key: 'caa1c08d04f1a3a84d694b7df5d318a686d3a1fb', class: { 'back-to-content-button': true, visible: this.visible }, onClick: this.handleClick, "aria-label": this.text }, index.h("kritzel-icon", { key: '0c7cf5e18294ade600be3052ca7c57c8d933a736', name: "chevrons-left" }))));
476
+ return (index.h(index.Host, { key: 'a7d52616642f45a0c0e7b76deb3cc5994184b968' }, index.h("button", { key: '9e7acae2b62f7f66ee0127545ee8ab115f9d340e', class: { 'back-to-content-button': true, visible: this.visible }, onClick: this.handleClick, "aria-label": this.text }, index.h("kritzel-icon", { key: '6c404376643554cdb86c33957c066dd4580f19f1', name: "chevrons-left" }))));
477
477
  }
478
478
  };
479
479
  KritzelBackToContent.style = kritzelBackToContentCss();
@@ -500,11 +500,11 @@ const KritzelButton = class {
500
500
  this.buttonClick.emit();
501
501
  };
502
502
  render() {
503
- return (index.h(index.Host, { key: 'aa366907b97e648ac73182a43d099baf457e0ba3' }, index.h("button", { key: 'e80875396a791606461b1a20e0f7e1c896f54073', type: this.type, class: {
503
+ return (index.h(index.Host, { key: '0de2b5dcf75de9f5b7de0c6b876bd18733bf21b9' }, index.h("button", { key: '5ab9d80167c6b90add7779d4b1c3ebb80fafaed5', type: this.type, class: {
504
504
  'kritzel-button': true,
505
505
  [this.variant]: true,
506
506
  'disabled': this.disabled,
507
- }, disabled: this.disabled, onClick: this.handleClick }, index.h("slot", { key: '8f4e57a73837adfbf91f678c54558fe4c737aa91' }))));
507
+ }, disabled: this.disabled, onClick: this.handleClick }, index.h("slot", { key: 'dc105f7d1f87aa55c314d942a1cc6522b272526a' }))));
508
508
  }
509
509
  };
510
510
  KritzelButton.style = kritzelButtonCss();
@@ -552,13 +552,13 @@ const KritzelColorComponent = class {
552
552
  render() {
553
553
  const resolvedColor = this.resolveColor();
554
554
  const isColorVeryLight = this.isLightColor(resolvedColor);
555
- return (index.h(index.Host, { key: '21ce0ecb616266ec4953468ae9bbe0b72259c2da' }, index.h("div", { key: '02c8e8684b3b0819755efd1f160bdee662428ddd', class: "checkerboard-bg", style: {
555
+ return (index.h(index.Host, { key: '0d56c7ad01eddd523ebcf11a32e68367c96090bd' }, index.h("div", { key: '0bef4cb73804b98e6eea6b3cf006c19c8786bdb9', class: "checkerboard-bg", style: {
556
556
  width: `${this.size}px`,
557
557
  height: `${this.size}px`,
558
558
  borderRadius: '50%',
559
559
  display: 'inline-block',
560
560
  position: 'relative',
561
- } }, index.h("div", { key: '0063e9a701994869adbd3022b5ace542e85dfd95', class: {
561
+ } }, index.h("div", { key: '5be9714c3ddd56ae77c2870333ff97a59b58bd45', class: {
562
562
  'color-circle': true,
563
563
  'white': isColorVeryLight,
564
564
  }, style: {
@@ -614,7 +614,7 @@ const KritzelColorPalette = class {
614
614
  render() {
615
615
  const displayedColors = this.isExpanded ? this.colors : this.colors.slice(0, 6);
616
616
  const expandedHeight = this.isExpanded ? this.calculateHeight() : '32px';
617
- return (index.h(index.Host, { key: '9910a765816f45d420ad27c8fc7b02811380783b' }, index.h("div", { key: '91f3dae04338f55ab21831e4cac65f9d855c58c6', class: {
617
+ return (index.h(index.Host, { key: '6d003ec5b92f586532782144c25ca5a733741f20' }, index.h("div", { key: '031838b45da4916aa91d87c066ac950cfe3a1ddc', class: {
618
618
  'color-grid': true,
619
619
  'expanded': this.isExpanded,
620
620
  }, style: {
@@ -1182,13 +1182,13 @@ const KritzelControls = class {
1182
1182
  // Separate tool controls from config control
1183
1183
  const toolControls = this.internalControls.filter(c => c.type === 'tool' || c.type === 'separator');
1184
1184
  const configControl = this.internalControls.find(c => c.type === 'config' && c.name === this.firstConfig?.name);
1185
- return (index.h(index.Host, { key: '7247a377d1cb75153e35f5308e81b2bd00d98578', style: { display: this.visible ? '' : 'none' }, class: {
1185
+ return (index.h(index.Host, { key: '266e38cdeffb8c22a7f778e865e5fab6b461f5b6', style: { display: this.visible ? '' : 'none' }, class: {
1186
1186
  mobile: this.isTouchDevice,
1187
- } }, this.isUtilityPanelVisible && (index.h("kritzel-utility-panel", { key: '075577cd15f3e577a1a216b5b1f1874e82e0d123', style: {
1187
+ } }, this.isUtilityPanelVisible && (index.h("kritzel-utility-panel", { key: '5e75ece000ac02675d3d226461b7f522dcf5ead9', style: {
1188
1188
  position: 'absolute',
1189
1189
  bottom: '56px',
1190
1190
  left: '12px',
1191
- }, undoState: this.undoState, terms: this.terms, onUndo: () => this.kritzelEngine?.undo(), onRedo: () => this.kritzelEngine?.redo(), onDelete: () => this.kritzelEngine?.delete() })), index.h("div", { key: '7fb1041763c7b64917e337d68bbb29b36ed86b9a', class: "kritzel-controls" }, index.h("div", { key: '8577ed9c6f43a667aaba7dce1da519456c8ad210', class: { 'scroll-indicator-left': true, 'visible': this.canScrollLeft } }), index.h("div", { key: '42f8f2b85055705674f9e319056bd48856dbd197', class: "kritzel-tools-scroll", ref: el => (this.toolsScrollRef = el), onScroll: this.handleToolsScroll }, toolControls.map(control => {
1191
+ }, undoState: this.undoState, terms: this.terms, onUndo: () => this.kritzelEngine?.undo(), onRedo: () => this.kritzelEngine?.redo(), onDelete: () => this.kritzelEngine?.delete() })), index.h("div", { key: 'fc98b1675afe1abb6a8d391c4ace34c7f0e46677', class: "kritzel-controls" }, index.h("div", { key: 'b17bc9e9a65646735c56f78a326a281229fa9790', class: { 'scroll-indicator-left': true, 'visible': this.canScrollLeft } }), index.h("div", { key: '9ab9d5c44e515551d75f27c31875de72e9d3407c', class: "kritzel-tools-scroll", ref: el => (this.toolsScrollRef = el), onScroll: this.handleToolsScroll }, toolControls.map(control => {
1192
1192
  // Check if this control has sub-options (split-button)
1193
1193
  if (control.subOptions?.length) {
1194
1194
  const selectedSubOption = this.getSelectedSubOption(control);
@@ -1218,10 +1218,10 @@ const KritzelControls = class {
1218
1218
  'kritzel-control': true,
1219
1219
  'selected': this.activeControl?.name === control?.name,
1220
1220
  }, key: control.name, "data-testid": `tool-${control.name}`, onClick: _event => this.handleControlClick?.(control), "aria-label": control.name.charAt(0).toUpperCase() + control.name.slice(1) }, index.h("kritzel-icon", { name: control.icon })));
1221
- })), index.h("div", { key: '20007273e0f701193502320cf58099cbaedb834e', class: { 'scroll-indicator-right': true, 'visible': this.canScrollRight && !(configControl && this.activeControl && hasConfigUI) } }), configControl && this.activeControl && (index.h("div", { class: {
1221
+ })), index.h("div", { key: 'fad7512f5d1aa68905a56b8f22d498380f166f9f', class: { 'scroll-indicator-right': true, 'visible': this.canScrollRight && !(configControl && this.activeControl && hasConfigUI) } }), configControl && this.activeControl && (index.h("div", { class: {
1222
1222
  'kritzel-config-container': true,
1223
1223
  'visible': hasConfigUI,
1224
- }, key: configControl.name }, index.h("div", { key: '2901fc2507e5b02fbd603bd2d2a4e2b07e35c970', class: { 'config-gradient-left': true, 'visible': this.needsScrolling } }), index.h("kritzel-tooltip", { key: '653e6a9b49146a2f297dbb5b9debe8709efd59ad', anchorElement: this.host.shadowRoot?.querySelector('.kritzel-config-container'), triggerElement: this.configTriggerRef }, index.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%' } })), index.h("div", { key: '951a52f625c929634de5102b83a14ad0cf1abc8d', tabIndex: hasConfigUI ? 0 : -1, class: "kritzel-config", "data-testid": "tool-config", ref: el => {
1224
+ }, key: configControl.name }, index.h("div", { key: '2ff7cb3ad18608102f3d8188b5936cf00b00451f', class: { 'config-gradient-left': true, 'visible': this.needsScrolling } }), index.h("kritzel-tooltip", { key: '10f15f717c8573cbad6ea53b412ad56254099ad1', anchorElement: this.host.shadowRoot?.querySelector('.kritzel-config-container'), triggerElement: this.configTriggerRef }, index.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%' } })), index.h("div", { key: '7ac8214a3dbad5323a38cad3ff7324b57f9dbfb5', tabIndex: hasConfigUI ? 0 : -1, class: "kritzel-config", "data-testid": "tool-config", ref: el => {
1225
1225
  if (el)
1226
1226
  this.configTriggerRef = el;
1227
1227
  }, onKeyDown: event => {
@@ -1230,7 +1230,7 @@ const KritzelControls = class {
1230
1230
  }
1231
1231
  }, style: {
1232
1232
  cursor: 'pointer',
1233
- } }, this.displayValues && (index.h("div", { key: 'b8fff73b9055e86b312a9f0798fff4177cf85970', class: "color-container" }, index.h("kritzel-color", { key: 'fcd474e557d887f94383b7e583ab1c95a675ce23', value: this.displayValues.color, theme: this.theme, size: 18, style: {
1233
+ } }, this.displayValues && (index.h("div", { key: '7e64f79859f7f2b03d72bb7a93fb295e7d2b2ac0', class: "color-container" }, index.h("kritzel-color", { key: 'b87d9861949379d84954ef1ecb554e13317b3287', value: this.displayValues.color, theme: this.theme, size: 18, style: {
1234
1234
  borderRadius: '50%',
1235
1235
  border: 'none',
1236
1236
  } })))))))));
@@ -1270,7 +1270,7 @@ const KritzelCurrentUser = class {
1270
1270
  this.dialogRef?.open();
1271
1271
  };
1272
1272
  render() {
1273
- return (index.h(index.Host, { key: '7e45048e532db84347e2531de2df19f5537d62ca' }, index.h("kritzel-avatar", { key: '5a67d1dbae041e16cdaeaf4bc678b6b00c56f442', user: this.user, size: this.avatarSize, onClick: this.handleAvatarClick }), index.h("kritzel-current-user-dialog", { key: '8e35219e282811b8bbafb058c02c8c0474574009', ref: el => (this.dialogRef = el), user: this.user, terms: this.terms })));
1273
+ return (index.h(index.Host, { key: 'bdee6b71c08d6ea22aec40e88ba56077882b301d' }, index.h("kritzel-avatar", { key: '97bcaaa6b683c0d26d547ac5cbd9df7814f0cd80', user: this.user, size: this.avatarSize, onClick: this.handleAvatarClick }), index.h("kritzel-current-user-dialog", { key: 'c984ea1803bcf2b06569fd66d12e4e7054235225', ref: el => (this.dialogRef = el), user: this.user, terms: this.terms })));
1274
1274
  }
1275
1275
  };
1276
1276
  KritzelCurrentUser.style = kritzelCurrentUserCss();
@@ -1304,7 +1304,7 @@ const KritzelCurrentUserDialog = class {
1304
1304
  }
1305
1305
  render() {
1306
1306
  const displayName = this.getDisplayName();
1307
- return (index.h(index.Host, { key: '7c9e5c19249d400e2d670c60f5d6716c742adc62' }, index.h("kritzel-dialog", { key: '811da6bff0ce03914f545dd289878ba04924e85c', dialogTitle: this.terms['currentUser.dialogTitle'] ?? 'Account', isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, size: "small", contained: true }, index.h("div", { key: '03a101b04d61882732547d91e81a2bacb3aa4df8', class: "user-info" }, index.h("kritzel-avatar", { key: '4033d0e2322d7a25231f01115ec33f20e4fb0d4c', user: this.user, size: 80 }), displayName && index.h("div", { key: 'cf3a3aecb84152736c9de119f82a79f1b2b32f6f', class: "user-name" }, displayName), this.user?.email && index.h("div", { key: '5725d71dd80468c64a2b0bb1d9d967232513fb63', class: "user-email" }, this.user.email)))));
1307
+ return (index.h(index.Host, { key: '042455d68a432e0e218d5b8750fb1d22b0d202fc' }, index.h("kritzel-dialog", { key: '058abbc8e1c38c647cc02e85c272e89693cab22a', dialogTitle: this.terms['currentUser.dialogTitle'] ?? 'Account', isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, size: "small", contained: true }, index.h("div", { key: 'ee141b2e91f093527e2d8f6649bade8e1a14f130', class: "user-info" }, index.h("kritzel-avatar", { key: '2734bb0d3c69df14d26c204eeaf726174afaabc0', user: this.user, size: 80 }), displayName && index.h("div", { key: '56cbad7a51264ebada0cb029e6455aabb9cb4ae9', class: "user-name" }, displayName), this.user?.email && index.h("div", { key: 'ef9075dfdeb52ce6f90bb41d1348fbdf7dbb93ee', class: "user-email" }, this.user.email)))));
1308
1308
  }
1309
1309
  };
1310
1310
  KritzelCurrentUserDialog.style = kritzelCurrentUserDialogCss();
@@ -2193,7 +2193,7 @@ const DEFAULT_SYNC_CONFIG = {
2193
2193
  providers: [],
2194
2194
  };
2195
2195
 
2196
- 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}`;
2196
+ 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}`;
2197
2197
 
2198
2198
  const KritzelEditor = class {
2199
2199
  constructor(hostRef) {
@@ -2291,13 +2291,11 @@ const KritzelEditor = class {
2291
2291
  {
2292
2292
  label: 'menu.paste',
2293
2293
  icon: 'paste',
2294
- disabled: async () => (await this.engineRef.getCopiedObjects()).length === 0,
2295
2294
  action: menu => this.engineRef.paste(menu.x, menu.y),
2296
2295
  },
2297
2296
  {
2298
2297
  label: 'menu.selectAll',
2299
2298
  icon: 'select-all',
2300
- disabled: async () => (await this.engineRef.getObjectsInViewport()).length === 0,
2301
2299
  action: () => this.selectAllObjectsInViewport(),
2302
2300
  },
2303
2301
  ];
@@ -2308,7 +2306,6 @@ const KritzelEditor = class {
2308
2306
  label: 'menu.paste',
2309
2307
  icon: 'paste',
2310
2308
  group: 'clipboard',
2311
- disabled: async () => (await this.engineRef.getCopiedObjects()).length === 0,
2312
2309
  action: (menu, _) => this.engineRef.paste(menu.x, menu.y),
2313
2310
  },
2314
2311
  {
@@ -2326,7 +2323,6 @@ const KritzelEditor = class {
2326
2323
  label: 'menu.align',
2327
2324
  icon: 'align',
2328
2325
  group: 'other',
2329
- disabled: async () => (await this.engineRef.getSelectedObjects()).length < 2,
2330
2326
  children: [
2331
2327
  { label: 'menu.alignLeft', icon: 'align-start-vertical', action: () => this.engineRef.alignObjects(schema_constants.KritzelAlignment.StartHorizontal) },
2332
2328
  { label: 'menu.alignCenterHorizontal', icon: 'align-center-horizontal', action: () => this.engineRef.alignObjects(schema_constants.KritzelAlignment.CenterHorizontal) },
@@ -2344,16 +2340,11 @@ const KritzelEditor = class {
2344
2340
  {
2345
2341
  label: 'menu.group',
2346
2342
  icon: 'group',
2347
- disabled: async () => (await this.engineRef.getSelectedObjects()).length < 2,
2348
2343
  action: () => this.engineRef.group(),
2349
2344
  },
2350
2345
  {
2351
2346
  label: 'menu.ungroup',
2352
2347
  icon: 'ungroup',
2353
- disabled: async () => {
2354
- const selectedObjects = await this.engineRef.getSelectedObjects();
2355
- return !selectedObjects.some(obj => obj.__class__ === 'KritzelGroup');
2356
- },
2357
2348
  action: () => this.engineRef.ungroup(),
2358
2349
  },
2359
2350
  ],
@@ -2430,6 +2421,13 @@ const KritzelEditor = class {
2430
2421
  shortcuts = [];
2431
2422
  currentIsPublic = false;
2432
2423
  isEditorVisible = false;
2424
+ activeNotification;
2425
+ isNotificationDismissing = false;
2426
+ notificationDismissTimeout;
2427
+ notificationDismissAnimationTimeout;
2428
+ notificationDisplayDurationMs = 5000;
2429
+ notificationDismissAnimationDurationMs = 180;
2430
+ isNotificationHovered = false;
2433
2431
  onIsEngineReady(newValue) {
2434
2432
  if (newValue && this.isControlsReady) {
2435
2433
  this.checkIsReady();
@@ -2692,9 +2690,6 @@ const KritzelEditor = class {
2692
2690
  async findObjects(predicate) {
2693
2691
  return this.engineRef.findObjects(predicate);
2694
2692
  }
2695
- async getCopiedObjects() {
2696
- return this.engineRef.getCopiedObjects();
2697
- }
2698
2693
  async getObjectsInViewport() {
2699
2694
  return this.engineRef.getObjectsInViewport();
2700
2695
  }
@@ -2710,6 +2705,22 @@ const KritzelEditor = class {
2710
2705
  async getDisplayableShortcuts() {
2711
2706
  return this.engineRef.getDisplayableShortcuts();
2712
2707
  }
2708
+ /**
2709
+ * Triggers a UI notification programmatically.
2710
+ * @param notification - Notification payload. `id` and `timestamp` are optional.
2711
+ */
2712
+ async triggerNotification(notification) {
2713
+ const normalizedNotification = {
2714
+ ...notification,
2715
+ id: notification.id ?? schema_constants.ObjectHelper.generateUUID(),
2716
+ timestamp: notification.timestamp ?? new Date(),
2717
+ };
2718
+ this.clearNotificationDismissAnimationTimer();
2719
+ this.isNotificationDismissing = false;
2720
+ this.isNotificationHovered = false;
2721
+ this.activeNotification = normalizedNotification;
2722
+ this.scheduleNotificationDismiss();
2723
+ }
2713
2724
  engineRef;
2714
2725
  controlsRef;
2715
2726
  settingsRef;
@@ -2734,6 +2745,10 @@ const KritzelEditor = class {
2734
2745
  this.listenForMobileKeyboard();
2735
2746
  this.setOsSpecificCssVariables();
2736
2747
  }
2748
+ disconnectedCallback() {
2749
+ this.clearNotificationDismissTimer();
2750
+ this.clearNotificationDismissAnimationTimer();
2751
+ }
2737
2752
  async checkIsReady() {
2738
2753
  await customElements.whenDefined('kritzel-editor');
2739
2754
  await customElements.whenDefined('kritzel-workspace-manager');
@@ -2833,6 +2848,57 @@ const KritzelEditor = class {
2833
2848
  event.stopPropagation();
2834
2849
  this.awarenessChange.emit(event.detail);
2835
2850
  }
2851
+ handleNotificationsChange(event) {
2852
+ event.stopPropagation();
2853
+ this.clearNotificationDismissAnimationTimer();
2854
+ this.isNotificationDismissing = false;
2855
+ this.isNotificationHovered = false;
2856
+ this.activeNotification = event.detail;
2857
+ this.scheduleNotificationDismiss();
2858
+ }
2859
+ handleNotificationHoverChange(event) {
2860
+ event.stopPropagation();
2861
+ this.isNotificationHovered = event.detail;
2862
+ if (this.isNotificationHovered) {
2863
+ this.clearNotificationDismissTimer();
2864
+ return;
2865
+ }
2866
+ if (this.activeNotification && !this.isNotificationDismissing) {
2867
+ this.scheduleNotificationDismiss();
2868
+ }
2869
+ }
2870
+ scheduleNotificationDismiss() {
2871
+ this.clearNotificationDismissTimer();
2872
+ this.notificationDismissTimeout = setTimeout(() => {
2873
+ this.dismissNotification();
2874
+ }, this.notificationDisplayDurationMs);
2875
+ }
2876
+ clearNotificationDismissTimer() {
2877
+ if (this.notificationDismissTimeout) {
2878
+ clearTimeout(this.notificationDismissTimeout);
2879
+ this.notificationDismissTimeout = undefined;
2880
+ }
2881
+ }
2882
+ clearNotificationDismissAnimationTimer() {
2883
+ if (this.notificationDismissAnimationTimeout) {
2884
+ clearTimeout(this.notificationDismissAnimationTimeout);
2885
+ this.notificationDismissAnimationTimeout = undefined;
2886
+ }
2887
+ }
2888
+ dismissNotification = () => {
2889
+ this.clearNotificationDismissTimer();
2890
+ this.isNotificationHovered = false;
2891
+ if (!this.activeNotification || this.isNotificationDismissing) {
2892
+ return;
2893
+ }
2894
+ this.isNotificationDismissing = true;
2895
+ this.clearNotificationDismissAnimationTimer();
2896
+ this.notificationDismissAnimationTimeout = setTimeout(() => {
2897
+ this.activeNotification = undefined;
2898
+ this.isNotificationDismissing = false;
2899
+ this.notificationDismissAnimationTimeout = undefined;
2900
+ }, this.notificationDismissAnimationDurationMs);
2901
+ };
2836
2902
  handleSettingsChange(event) {
2837
2903
  this.scaleMin = event.detail.scaleMin;
2838
2904
  this.scaleMax = event.detail.scaleMax;
@@ -3071,31 +3137,31 @@ const KritzelEditor = class {
3071
3137
  const isLoggedIn = this.isLoggedIn;
3072
3138
  const shouldShowCurrentUser = isLoggedIn;
3073
3139
  const shouldShowLoginButton = this.isReady && !!this.loginConfig && !isLoggedIn;
3074
- return (index.h(index.Host, { key: '72238560a0f0275c506f59220277fdff7ab92c13', style: {
3140
+ return (index.h(index.Host, { key: '07fe246b722ca64cfbdd8cc3474e6288c39d1128', style: {
3075
3141
  opacity: this.isEditorVisible ? '1' : '0',
3076
3142
  visibility: this.isEditorVisible ? 'visible' : 'hidden',
3077
3143
  transition: 'opacity 0.2s ease-in-out, visibility 0.2s ease-in-out',
3078
- } }, index.h("div", { key: '14fd50ad857199f3b6be708fc4263aa2e69067de', class: "top-left-buttons" }, index.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) }), index.h("kritzel-back-to-content", { key: '1840374d7353af2b050822dcd9c54be46e326278', visible: this.isBackToContentButtonVisible, text: this.resolvedTerms['backToContent.label'] ?? 'Back to content', onBackToContent: () => this.backToContent() })), index.h("kritzel-engine", { key: 'cdde0b65c811ee28fb4266afb96005c1fed24323', ref: el => {
3144
+ } }, index.h("div", { key: '2d0db1e78161edb061779b5875114e901f8e79f8', class: "top-left-buttons" }, index.h("kritzel-workspace-manager", { key: '8fee1d57b0b868b673c41015453fd39a19f8cb43', visible: this.isWorkspaceManagerVisible, workspaces: this.workspaces, activeWorkspace: this.activeWorkspace, terms: this.resolvedTerms, onWorkspaceChange: event => (this.activeWorkspace = event.detail), onIsWorkspaceManagerReady: () => (this.isWorkspaceManagerReady = true) }), index.h("kritzel-back-to-content", { key: 'f2a0305bf73bd14d7522974b7956a289a0a44258', visible: this.isBackToContentButtonVisible, text: this.resolvedTerms['backToContent.label'] ?? 'Back to content', onBackToContent: () => this.backToContent() })), this.activeNotification && (index.h("div", { key: '918fe80193999ec072042500cd1eaf3670f5ed3c', class: "top-center-notification-layer", role: "presentation" }, index.h("div", { key: '0a5609b2b32f9f04bac03618803c14d9a891742c', class: { 'top-center-notification-container': true, 'is-dismissing': this.isNotificationDismissing }, role: "presentation" }, index.h("kritzel-notification-card", { key: 'eb1ff7aeaee3a237baf3ba2d96a10ba03a8bf7fb', notification: this.activeNotification, locale: this.locale, onDismiss: this.dismissNotification, onHoverChange: event => this.handleNotificationHoverChange(event) })))), index.h("kritzel-engine", { key: '8783a87101f308029482ced926a8d2163aae1428', ref: el => {
3079
3145
  if (el) {
3080
3146
  this.engineRef = el;
3081
3147
  }
3082
- }, 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) }), index.h("kritzel-controls", { key: '9b91d0cc3b3afba8894bd852bac911178c0ca82d', visible: this.isControlsVisible, class: { 'keyboard-open': this.isVirtualKeyboardOpen }, ref: el => {
3148
+ }, workspace: this.activeWorkspace, activeWorkspaceId: this.activeWorkspaceId, editorId: this.editorId, syncConfig: this.syncConfig, assetStorageConfig: this.assetStorageConfig, user: this.user, scaleMax: this.scaleMax, lockDrawingScale: this.lockDrawingScale, isObjectDistanceFadingActive: this.isObjectDistanceFadingActive, scaleMin: this.scaleMin, cursorTarget: this.cursorTarget, isLoading: this.isLoading, viewportBoundaryLeft: this.viewportBoundaryLeft, viewportBoundaryRight: this.viewportBoundaryRight, viewportBoundaryTop: this.viewportBoundaryTop, viewportBoundaryBottom: this.viewportBoundaryBottom, isPanningEnabled: this.isPanningEnabled, isZoomingEnabled: this.isZoomingEnabled, theme: this.theme, themes: this.themes, licenseKey: this.licenseKey, locale: this.locale, locales: this.locales, fallbackLocale: this.fallbackLocale, debugInfo: this.debugInfo, globalContextMenuItems: this.globalContextMenuItems, objectContextMenuItems: this.objectContextMenuItems, onIsEngineReady: event => this.onEngineReady(event), onWorkspacesChange: event => this.handleWorkspacesChange(event), onActiveWorkspaceChange: event => this.handleActiveWorkspaceChange(event), onObjectsChange: event => this.handleObjectsChange(event), onObjectsAdded: event => this.handleObjectsAdded(event), onObjectsRemoved: event => this.handleObjectsRemoved(event), onObjectsUpdated: event => this.handleObjectsUpdated(event), onUndoStateChange: event => this.handleUndoStateChange(event), onObjectsInViewportChange: event => this.handleObjectsInViewportChange(event), onViewportChange: event => this.handleViewportChange(event), onAwarenessChange: event => this.handleAwarenessChange(event), onNotificationsChange: event => this.handleNotificationsChange(event) }), index.h("kritzel-controls", { key: '73a94f6baef4e0e26be4540ecd44d24588f5761b', visible: this.isControlsVisible, class: { 'keyboard-open': this.isVirtualKeyboardOpen }, ref: el => {
3083
3149
  if (el) {
3084
3150
  this.controlsRef = el;
3085
3151
  }
3086
- }, controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState ?? undefined, theme: this.theme, terms: this.resolvedTerms, onIsControlsReady: () => (this.isControlsReady = true) }), index.h("div", { key: '01f63c615eec68696532926b6d1efe2443e2d46a', class: "bottom-left-buttons" }, index.h("kritzel-zoom-panel", { key: '73a1fc5b0892b88d6c0a7c5debb42b7bb3f03a84', visible: this.isZoomPanelVisible, disabled: !this.isZoomingEnabled, zoomPercent: this.currentZoomPercent, terms: this.resolvedTerms, onZoomIn: () => this.zoomIn(), onZoomOut: () => this.zoomOut() })), index.h("div", { key: '94776978ba9dd68b2c5f62b141c074852585a1cd', class: "top-right-buttons" }, index.h("kritzel-settings", { key: '665a74511f8e511602d4d3437927e03af2c3c01c', ref: el => {
3152
+ }, controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState ?? undefined, theme: this.theme, terms: this.resolvedTerms, onIsControlsReady: () => (this.isControlsReady = true) }), index.h("div", { key: 'b0653098e5e15b81f453a7f3c2b27f137eb38f2e', class: "bottom-left-buttons" }, index.h("kritzel-zoom-panel", { key: '33384aeb6bd32d17fb428bd9ed48e5d588ac60c0', visible: this.isZoomPanelVisible, disabled: !this.isZoomingEnabled, zoomPercent: this.currentZoomPercent, terms: this.resolvedTerms, onZoomIn: () => this.zoomIn(), onZoomOut: () => this.zoomOut() })), index.h("div", { key: '37bed04c062b63a99c930362fb8ce30b58ed8872', class: "top-right-buttons" }, index.h("kritzel-settings", { key: '4e339573e3d8d48bc9803b9dd40a549bd056d147', ref: el => {
3087
3153
  if (el) {
3088
3154
  this.settingsRef = el;
3089
3155
  }
3090
- }, shortcuts: this.shortcuts, availableThemes: this.themes && this.themes.length > 0 ? this.themes.map(t => t.name) : ['light', 'dark'], availableLocales: this.availableLocaleOptions, settings: this.currentSettingsConfig, terms: this.resolvedTerms, onSettingsChange: event => this.handleSettingsChange(event) }), index.h("kritzel-export", { key: '6b52127d4634d92c3e7410a084f37970bd8c84e7', ref: el => {
3156
+ }, shortcuts: this.shortcuts, availableThemes: this.themes && this.themes.length > 0 ? this.themes.map(t => t.name) : ['light', 'dark'], availableLocales: this.availableLocaleOptions, settings: this.currentSettingsConfig, terms: this.resolvedTerms, onSettingsChange: event => this.handleSettingsChange(event) }), index.h("kritzel-export", { key: '2a726ea5b56cd36ecf92bbab17d69d04baf73009', ref: el => {
3091
3157
  if (el) {
3092
3158
  this.exportRef = el;
3093
3159
  }
3094
- }, workspaceName: this.activeWorkspace?.name || 'workspace', terms: this.resolvedTerms, onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), index.h("kritzel-active-users", { key: '002a01d361137f867efc2a0c91a6c0e9619d4359', users: this.activeUsers }), shouldShowCurrentUser && index.h("kritzel-current-user", { key: '6a9e0ad77997cbb10981d58afb176a7f7f4938aa', user: this.user, terms: this.resolvedTerms }), shouldShowLoginButton && (index.h("kritzel-button", { key: '5ba13725105d0af68c51fec455569ac7bbeaecde', onButtonClick: () => this.loginDialogRef?.open() }, this.resolvedTerms['login.dialogTitle'] ?? 'Sign in')), index.h("kritzel-more-menu", { key: 'f392a518fef108b395eb6d6681af9e06827e3780', items: this.moreMenuItems, visible: this.isMoreMenuVisible, terms: this.resolvedTerms }), index.h("kritzel-share-dialog", { key: '845a3153f5fd9b419f7d0b81be85f5e1ea0051cc', ref: el => {
3160
+ }, workspaceName: this.activeWorkspace?.name || 'workspace', terms: this.resolvedTerms, onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), index.h("kritzel-active-users", { key: '26d9bbf922b1d4158a0875ba42e7b598011973d5', users: this.activeUsers }), shouldShowCurrentUser && index.h("kritzel-current-user", { key: '8179cc3e940c9fd8d97978af2bf52543f661a0fd', user: this.user, terms: this.resolvedTerms }), shouldShowLoginButton && (index.h("kritzel-button", { key: 'd4aa5bc108043eb103754d14f1b5df42ab73f8c4', onButtonClick: () => this.loginDialogRef?.open() }, this.resolvedTerms['login.dialogTitle'] ?? 'Sign in')), index.h("kritzel-more-menu", { key: '17ac617bde379d7484dbeed39ac7168349b634a1', items: this.moreMenuItems, visible: this.isMoreMenuVisible, terms: this.resolvedTerms }), index.h("kritzel-share-dialog", { key: '881729b36d34ef3ac3d87fc51ad828fc20fa36cd', ref: el => {
3095
3161
  if (el) {
3096
3162
  this.shareDialogRef = el;
3097
3163
  }
3098
- }, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, terms: this.resolvedTerms, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (index.h("kritzel-login-dialog", { key: '5a7b149d8e2c2acbda3d7818d8576d8ad249ef23', ref: el => {
3164
+ }, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, terms: this.resolvedTerms, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (index.h("kritzel-login-dialog", { key: '5e34b647ac4c3e18981042d765b4d443422c5db3', ref: el => {
3099
3165
  if (el) {
3100
3166
  this.loginDialogRef = el;
3101
3167
  }
@@ -21174,7 +21240,7 @@ class KritzelViewport {
21174
21240
  const NUDGE_AMOUNT = 10;
21175
21241
  /**
21176
21242
  * Handles keyboard events and manages keyboard shortcuts for the Kritzel canvas.
21177
- * Provides functionality for tool switching, clipboard operations, object manipulation,
21243
+ * Provides functionality for tool switching, object manipulation,
21178
21244
  * undo/redo, and navigation via keyboard input.
21179
21245
  */
21180
21246
  class KritzelKeyHandler extends schema_constants.KritzelBaseHandler {
@@ -21217,27 +21283,6 @@ class KritzelKeyHandler extends schema_constants.KritzelBaseHandler {
21217
21283
  { key: '7', ctrl: true, label: 'Tool 7', category: 'Tool Selection', action: () => this.switchToolByIndex(6) },
21218
21284
  { key: '8', ctrl: true, label: 'Tool 8', category: 'Tool Selection', action: () => this.switchToolByIndex(7) },
21219
21285
  { key: '9', ctrl: true, label: 'Tool 9', category: 'Tool Selection', action: () => this.switchToolByIndex(8) },
21220
- // Clipboard
21221
- {
21222
- key: 'c',
21223
- ctrl: true,
21224
- label: 'Copy',
21225
- category: 'Clipboard',
21226
- condition: c => !!c.store.selectionGroup,
21227
- action: c => {
21228
- c.copy();
21229
- c.rerender();
21230
- },
21231
- },
21232
- { key: 'x', ctrl: true, label: 'Cut', category: 'Clipboard', condition: c => !!c.store.selectionGroup, action: c => c.cut() },
21233
- {
21234
- key: 'v',
21235
- ctrl: true,
21236
- label: 'Paste',
21237
- category: 'Clipboard',
21238
- condition: c => !!c.store.state.copiedObjects && !c.store.activeText && !c.store.activeShape,
21239
- action: c => c.paste(),
21240
- },
21241
21286
  // Object layering
21242
21287
  { key: '+', ctrl: true, label: 'Bring Forward', category: 'Object Layering', condition: c => !!c.store.selectionGroup, action: c => c.bringForward() },
21243
21288
  { key: '-', ctrl: true, label: 'Send Backward', category: 'Object Layering', condition: c => !!c.store.selectionGroup, action: c => c.sendBackward() },
@@ -21747,7 +21792,6 @@ class KritzelReviver {
21747
21792
  const DEFAULT_ENGINE_CONFIG = {
21748
21793
  activeWorkspace: undefined,
21749
21794
  activeTool: null,
21750
- copiedObjects: null,
21751
21795
  objects: null,
21752
21796
  snapCandidate: null,
21753
21797
  resizeHandleType: null,
@@ -24090,6 +24134,467 @@ class KritzelCursorManager {
24090
24134
  }
24091
24135
  }
24092
24136
 
24137
+ /**
24138
+ * Manages browser permission checks and prompts for a core instance.
24139
+ *
24140
+ * Permission-specific prompt logic is routed through a handler registry so new
24141
+ * permissions can be added without changing the core control flow.
24142
+ */
24143
+ class KritzelBrowserPermissionManager {
24144
+ _isFirefox = /firefox|fxios/i.test(navigator.userAgent);
24145
+ _isSafari = /safari/i.test(navigator.userAgent) && !/chrome|chromium|crios|android|edg/i.test(navigator.userAgent);
24146
+ _requestHandlers = new Map();
24147
+ /**
24148
+ * Creates a new KritzelBrowserPermissionManager instance.
24149
+ * @param core - The KritzelCore instance this manager belongs to
24150
+ */
24151
+ constructor(_core) {
24152
+ this.registerRequestPermissionHandler('clipboard-read', async () => {
24153
+ // Firefox and Safari open native paste menus when readText() is called
24154
+ // proactively, so rely on the permission state instead of an eager read.
24155
+ if (this._isFirefox || this._isSafari) {
24156
+ return 'granted';
24157
+ }
24158
+ try {
24159
+ await navigator.clipboard.readText();
24160
+ return 'granted';
24161
+ }
24162
+ catch {
24163
+ return 'denied';
24164
+ }
24165
+ });
24166
+ }
24167
+ /**
24168
+ * Registers a custom request handler for a browser permission name.
24169
+ * Existing handlers are replaced, which makes the registry easy to extend.
24170
+ *
24171
+ * @param permissionName - The browser permission name to handle
24172
+ * @param handler - The request implementation for that permission
24173
+ */
24174
+ registerRequestPermissionHandler(permissionName, handler) {
24175
+ this._requestHandlers.set(permissionName, handler);
24176
+ }
24177
+ /**
24178
+ * Checks the current state for a browser permission via the Permissions API.
24179
+ *
24180
+ * @param permissionName - The browser permission name to inspect
24181
+ */
24182
+ async checkPermission(permissionName) {
24183
+ const status = await navigator.permissions.query({ name: permissionName });
24184
+ return status.state;
24185
+ }
24186
+ /**
24187
+ * Requests a browser permission using the registered handler for the given
24188
+ * permission name, then falls back to a permission check when no handler is
24189
+ * registered.
24190
+ *
24191
+ * @param permissionName - The browser permission name to request
24192
+ */
24193
+ async requestPermission(permissionName) {
24194
+ const handler = this._requestHandlers.get(permissionName);
24195
+ if (handler) {
24196
+ return handler();
24197
+ }
24198
+ return this.checkPermission(permissionName);
24199
+ }
24200
+ }
24201
+
24202
+ const CLIPBOARD_PAYLOAD_PREFIX = 'kritzel-clipboard/v1:';
24203
+ /**
24204
+ * Manages clipboard operations (copy/cut/paste) for a core instance.
24205
+ *
24206
+ * The system clipboard is the single source of truth: copy serializes the current
24207
+ * selection into a Kritzel payload, paste reads + revives it. No copied objects are
24208
+ * cached on the engine state.
24209
+ */
24210
+ class KritzelClipboardManager {
24211
+ _core;
24212
+ /**
24213
+ * In-memory cascade offset (world units) applied to successive pastes of the same
24214
+ * clipboard content so they don't stack exactly on top of each other. Reset on copy/cut.
24215
+ */
24216
+ _pasteOffset = 0;
24217
+ constructor(core) {
24218
+ this._core = core;
24219
+ }
24220
+ /**
24221
+ * Serializes an object for the clipboard payload. Groups additionally embed their
24222
+ * (recursively serialized) children under `__children__` so the payload is self-contained.
24223
+ */
24224
+ serializeForClipboard(object) {
24225
+ const serialized = (typeof object.serialize === 'function' ? object.serialize() : object);
24226
+ if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelGroup')) {
24227
+ serialized.__children__ = object.children.map(child => this.serializeForClipboard(child));
24228
+ }
24229
+ return serialized;
24230
+ }
24231
+ serializeClipboardPayload(payload) {
24232
+ return `${CLIPBOARD_PAYLOAD_PREFIX}${JSON.stringify(payload)}`;
24233
+ }
24234
+ parseClipboardPayload(text) {
24235
+ if (!text || !text.startsWith(CLIPBOARD_PAYLOAD_PREFIX)) {
24236
+ return null;
24237
+ }
24238
+ try {
24239
+ const parsed = JSON.parse(text.slice(CLIPBOARD_PAYLOAD_PREFIX.length));
24240
+ if (parsed?.version !== 1 || !Array.isArray(parsed.objects)) {
24241
+ return null;
24242
+ }
24243
+ return parsed;
24244
+ }
24245
+ catch {
24246
+ return null;
24247
+ }
24248
+ }
24249
+ determineTextPasteIntent(text) {
24250
+ const payload = this.parseClipboardPayload(text);
24251
+ if (payload) {
24252
+ return { type: 'kritzel_objects', payload };
24253
+ }
24254
+ if (text && text.trim().length > 0) {
24255
+ return { type: 'plain_text', text };
24256
+ }
24257
+ return null;
24258
+ }
24259
+ async writeBrowserClipboardPayload(payload, event) {
24260
+ const serialized = this.serializeClipboardPayload(payload);
24261
+ if (event?.clipboardData) {
24262
+ event.clipboardData.setData('text/plain', serialized);
24263
+ return;
24264
+ }
24265
+ if (typeof navigator === 'undefined' || !navigator.clipboard?.writeText) {
24266
+ return;
24267
+ }
24268
+ try {
24269
+ await navigator.clipboard.writeText(serialized);
24270
+ }
24271
+ catch {
24272
+ // Ignore clipboard permission failures and keep the local clipboard working.
24273
+ }
24274
+ }
24275
+ /**
24276
+ * Revives a single serialized clipboard object (and its embedded group children).
24277
+ * IDs are NOT regenerated here; see regenerateIdsAndRemapAnchors.
24278
+ */
24279
+ reviveClipboardObject(serialized) {
24280
+ const reviver = new KritzelReviver(this._core);
24281
+ const { __children__, ...objectData } = serialized;
24282
+ const revived = reviver.revive(objectData);
24283
+ if (!revived) {
24284
+ return null;
24285
+ }
24286
+ if (__children__ && schema_constants.KritzelClassHelper.isInstanceOf(revived, 'KritzelGroup')) {
24287
+ const children = __children__.map(child => this.reviveClipboardObject(child)).filter((child) => child != null);
24288
+ revived._pendingChildren = children;
24289
+ revived.childIds = children.map(child => child.id);
24290
+ }
24291
+ return revived;
24292
+ }
24293
+ /**
24294
+ * Assigns fresh unique IDs to every revived object (including nested group children),
24295
+ * keeping group.childIds in sync and remapping line anchors that reference copied
24296
+ * siblings to their new IDs. Anchors pointing at objects outside the pasted set are
24297
+ * left untouched so they stay connected to the existing object.
24298
+ */
24299
+ regenerateIdsAndRemapAnchors(topLevel) {
24300
+ const all = [];
24301
+ const collect = (objects) => {
24302
+ for (const object of objects) {
24303
+ all.push(object);
24304
+ if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelGroup')) {
24305
+ collect(object._pendingChildren);
24306
+ }
24307
+ }
24308
+ };
24309
+ collect(topLevel);
24310
+ const idMap = new Map();
24311
+ for (const object of all) {
24312
+ const newId = object.generateId();
24313
+ idMap.set(object.id, newId);
24314
+ object.id = newId;
24315
+ object.isMounted = false;
24316
+ }
24317
+ for (const object of all) {
24318
+ if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelGroup')) {
24319
+ object.childIds = object.childIds.map(id => idMap.get(id) ?? id);
24320
+ }
24321
+ if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelLine')) {
24322
+ if (object.startAnchor && idMap.has(object.startAnchor.objectId)) {
24323
+ object.startAnchor = { objectId: idMap.get(object.startAnchor.objectId) };
24324
+ }
24325
+ if (object.endAnchor && idMap.has(object.endAnchor.objectId)) {
24326
+ object.endAnchor = { objectId: idMap.get(object.endAnchor.objectId) };
24327
+ }
24328
+ }
24329
+ }
24330
+ }
24331
+ /**
24332
+ * Evaluates the clipboard event to determine exactly what the user is trying to paste.
24333
+ * Returns a PasteIntent covering the 3 main cases: Kritzel objects, Images, or Plain text.
24334
+ */
24335
+ async determinePasteIntent(event) {
24336
+ if (event?.clipboardData) {
24337
+ const data = event.clipboardData;
24338
+ const imageFiles = Array.from(data.files ?? []).filter(file => file.type.startsWith('image/'));
24339
+ if (imageFiles.length > 0) {
24340
+ return { type: 'image_files', files: imageFiles };
24341
+ }
24342
+ if (data.types.includes('text/plain')) {
24343
+ const textIntent = this.determineTextPasteIntent(data.getData('text/plain'));
24344
+ if (textIntent)
24345
+ return textIntent;
24346
+ }
24347
+ return { type: 'unknown' };
24348
+ }
24349
+ if (typeof navigator === 'undefined' || !navigator.clipboard) {
24350
+ return { type: 'unknown' };
24351
+ }
24352
+ const { clipboard } = navigator;
24353
+ const permissionState = await this._core.permissionManager.checkPermission('clipboard-read');
24354
+ if (permissionState !== 'granted') {
24355
+ const requestedState = await this._core.permissionManager.requestPermission('clipboard-read');
24356
+ if (requestedState !== 'granted') {
24357
+ return { type: 'unknown' };
24358
+ }
24359
+ }
24360
+ if (clipboard.read) {
24361
+ try {
24362
+ const items = await clipboard.read();
24363
+ const { imageFiles, text } = await this.processClipboardItems(items);
24364
+ if (imageFiles.length > 0) {
24365
+ return { type: 'image_files', files: imageFiles };
24366
+ }
24367
+ if (text !== null) {
24368
+ const textIntent = this.determineTextPasteIntent(text);
24369
+ if (textIntent)
24370
+ return textIntent;
24371
+ }
24372
+ }
24373
+ catch {
24374
+ // Fall back to readText below when read() is denied or unavailable at runtime.
24375
+ }
24376
+ }
24377
+ if (clipboard.readText) {
24378
+ const textIntent = this.determineTextPasteIntent(await clipboard.readText());
24379
+ if (textIntent)
24380
+ return textIntent;
24381
+ }
24382
+ return { type: 'unknown' };
24383
+ }
24384
+ /**
24385
+ * Helper to parse clipboard items in parallel
24386
+ */
24387
+ async processClipboardItems(items) {
24388
+ const imageFiles = [];
24389
+ let text = null;
24390
+ // Process items concurrently
24391
+ await Promise.all(items.map(async (item) => {
24392
+ const imageType = item.types.find(type => type.startsWith('image/'));
24393
+ if (imageType) {
24394
+ const blob = await item.getType(imageType);
24395
+ imageFiles.push(new File([blob], 'pasted-image', { type: imageType }));
24396
+ return;
24397
+ }
24398
+ if (text === null && item.types.includes('text/plain')) {
24399
+ const blob = await item.getType('text/plain');
24400
+ text = await blob.text();
24401
+ }
24402
+ }));
24403
+ return { imageFiles, text };
24404
+ }
24405
+ /**
24406
+ * Copies currently selected objects into internal clipboard state.
24407
+ */
24408
+ copy(event) {
24409
+ const selectionGroup = this._core.store.selectionGroup;
24410
+ if (!selectionGroup) {
24411
+ return;
24412
+ }
24413
+ const payload = {
24414
+ version: 1,
24415
+ sourceWorkspaceId: this._core.store.state.activeWorkspace?.id,
24416
+ objects: selectionGroup.objects.sort((a, b) => a.zIndex - b.zIndex).map(obj => this.serializeForClipboard(obj)),
24417
+ };
24418
+ // A fresh copy resets the cascade so the first paste lands at the default offset.
24419
+ this._pasteOffset = 0;
24420
+ void this.writeBrowserClipboardPayload(payload, event);
24421
+ }
24422
+ /**
24423
+ * Cuts currently selected objects (copy then delete).
24424
+ */
24425
+ cut(event) {
24426
+ const selectionGroup = this._core.store.selectionGroup;
24427
+ if (!selectionGroup) {
24428
+ return;
24429
+ }
24430
+ this.copy(event);
24431
+ this._core.delete();
24432
+ }
24433
+ materializeKritzelObjects(intent) {
24434
+ const objects = intent.payload.objects.map(object => this.reviveClipboardObject(object)).filter((object) => object != null);
24435
+ this.regenerateIdsAndRemapAnchors(objects);
24436
+ return objects;
24437
+ }
24438
+ async materializeImageFiles(intent) {
24439
+ try {
24440
+ const scale = this._core.store.state.scale;
24441
+ const offset = 25 / scale;
24442
+ const preparePromises = intent.files.map(async (file, index) => {
24443
+ // Use the default image compression size of 1200
24444
+ const compressed = await schema_constants.imageCompression(file, { maxWidthOrHeight: 1200 });
24445
+ return schema_constants.KritzelImage.fromBlob(this._core, compressed, {
24446
+ translateX: index * offset,
24447
+ translateY: index * offset,
24448
+ });
24449
+ });
24450
+ return await Promise.all(preparePromises);
24451
+ }
24452
+ catch (err) {
24453
+ console.error('Failed to import pasted image files:', err);
24454
+ return null;
24455
+ }
24456
+ }
24457
+ async materializePlainText(intent) {
24458
+ const textObject = schema_constants.KritzelText.create(this._core);
24459
+ const paragraphs = intent.text.split('\n').map(line => {
24460
+ if (line.length === 0) {
24461
+ return { type: 'paragraph' }; // Empty paragraph
24462
+ }
24463
+ return {
24464
+ type: 'paragraph',
24465
+ content: [{ type: 'text', text: line }],
24466
+ };
24467
+ });
24468
+ textObject.setContent({
24469
+ type: 'doc',
24470
+ content: paragraphs,
24471
+ });
24472
+ return [textObject];
24473
+ }
24474
+ /**
24475
+ * Pastes objects from clipboard with optional explicit world coordinates.
24476
+ */
24477
+ async paste(x, y, event) {
24478
+ const pasteIntent = await this.determinePasteIntent(event);
24479
+ let objects = null;
24480
+ if (pasteIntent.type === 'kritzel_objects') {
24481
+ objects = this.materializeKritzelObjects(pasteIntent);
24482
+ }
24483
+ else if (pasteIntent.type === 'image_files') {
24484
+ objects = await this.materializeImageFiles(pasteIntent);
24485
+ }
24486
+ else if (pasteIntent.type === 'plain_text') {
24487
+ objects = await this.materializePlainText(pasteIntent);
24488
+ }
24489
+ if (!objects || objects.length === 0) {
24490
+ return;
24491
+ }
24492
+ await this.insertMaterializedObjects(objects, pasteIntent, x, y);
24493
+ }
24494
+ async insertMaterializedObjects(copiedObjects, pasteIntent, x, y) {
24495
+ const activeWorkspace = this._core.store.state.activeWorkspace;
24496
+ if (!activeWorkspace) {
24497
+ return;
24498
+ }
24499
+ // Determine if we should center the objects in the viewport.
24500
+ // This happens if the user specifically pasted from a different workspace,
24501
+ // OR if we're inserting brand new external items (like images or text).
24502
+ const isDifferentWorkspace = copiedObjects.some(obj => obj.workspaceId !== activeWorkspace.id);
24503
+ const isNewExternalContent = pasteIntent.type !== 'kritzel_objects';
24504
+ const shouldCenterInViewport = isDifferentWorkspace || isNewExternalContent;
24505
+ const hasExplicitPastePosition = x !== undefined && y !== undefined;
24506
+ const minX = Math.min(...copiedObjects.map(obj => obj.translateX));
24507
+ const minY = Math.min(...copiedObjects.map(obj => obj.translateY));
24508
+ let pasteX;
24509
+ let pasteY;
24510
+ const step = 25 / this._core.store.state.scale;
24511
+ if (x !== undefined && y !== undefined) {
24512
+ pasteX = x;
24513
+ pasteY = y;
24514
+ }
24515
+ else if (shouldCenterInViewport) {
24516
+ pasteX = minX;
24517
+ pasteY = minY;
24518
+ }
24519
+ else {
24520
+ pasteX = minX + step + this._pasteOffset;
24521
+ pasteY = minY + step + this._pasteOffset;
24522
+ // Advance the cascade so the next same-content paste is offset further.
24523
+ this._pasteOffset += step;
24524
+ }
24525
+ const offsetX = pasteX - minX;
24526
+ const offsetY = pasteY - minY;
24527
+ this._core.removeSelectionGroup();
24528
+ this._core.removeSelectionBox();
24529
+ const selectionGroup = schema_constants.KritzelSelectionGroup.create(this._core);
24530
+ const baseZIndex = this._core.store.currentZIndex;
24531
+ this._core.store.objects.transaction(() => {
24532
+ const flushPendingChildren = (group, parentZIndex) => {
24533
+ if (group._pendingChildren.length === 0) {
24534
+ return;
24535
+ }
24536
+ const pendingChildren = group._pendingChildren;
24537
+ group._pendingChildren = [];
24538
+ pendingChildren.forEach((child, childIndex) => {
24539
+ if (child.workspaceId !== activeWorkspace.id) {
24540
+ child.workspaceId = activeWorkspace.id;
24541
+ }
24542
+ child.updatePosition(child.translateX + offsetX, child.translateY + offsetY);
24543
+ child.zIndex = parentZIndex + childIndex + 1;
24544
+ if (schema_constants.KritzelClassHelper.isInstanceOf(child, 'KritzelGroup')) {
24545
+ flushPendingChildren(child, child.zIndex);
24546
+ }
24547
+ this._core.addObject(child);
24548
+ });
24549
+ group.finalize();
24550
+ };
24551
+ const newlyAddedObjects = [];
24552
+ copiedObjects.forEach((obj, i) => {
24553
+ if (obj.workspaceId !== activeWorkspace.id) {
24554
+ obj.workspaceId = activeWorkspace.id;
24555
+ }
24556
+ obj.updatePosition(obj.translateX + offsetX, obj.translateY + offsetY);
24557
+ obj.zIndex = baseZIndex + i;
24558
+ if (schema_constants.KritzelClassHelper.isInstanceOf(obj, 'KritzelGroup')) {
24559
+ flushPendingChildren(obj, obj.zIndex);
24560
+ }
24561
+ this._core.addObject(obj);
24562
+ newlyAddedObjects.push(obj);
24563
+ selectionGroup.addOrRemove(obj);
24564
+ });
24565
+ this._core.engine.emitObjectsAdded(newlyAddedObjects);
24566
+ });
24567
+ this._core.anchorManager.rebuildIndex();
24568
+ selectionGroup.isSelected = true;
24569
+ if (copiedObjects.length === 1) {
24570
+ selectionGroup.rotation = copiedObjects[0].rotation;
24571
+ }
24572
+ if (shouldCenterInViewport && !hasExplicitPastePosition) {
24573
+ selectionGroup.centerInViewport();
24574
+ }
24575
+ this._core.addSelectionGroup(selectionGroup);
24576
+ this._core.engine.viewport?.bringIntoViewIfNeeded(selectionGroup);
24577
+ this._core.store.state.isSelecting = false;
24578
+ // Use a double requestAnimationFrame to ensure the browser has fully rendered the DOM,
24579
+ // and objects like KritzelText have executed their own internal size-adjustment rAF calls.
24580
+ await new Promise(resolve => {
24581
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
24582
+ });
24583
+ // Re-evaluate bounding box dimensions now that any DOM nodes (like ProseMirror text editors)
24584
+ // have mounted and calculated their actual bounds.
24585
+ selectionGroup.refreshObjectDimensions();
24586
+ // We should update it if the centerInViewport or width adjustments changed it significantly
24587
+ if (shouldCenterInViewport && !hasExplicitPastePosition) {
24588
+ selectionGroup.centerInViewport();
24589
+ }
24590
+ this._core.engine.emitObjectsChange();
24591
+ this._core.rerender();
24592
+ this._core.store.setState('activeTool', this._core.toolRegistry.getTool('selection'));
24593
+ this._core.engine.emitObjectsChange();
24594
+ this._core.rerender();
24595
+ }
24596
+ }
24597
+
24093
24598
  /**
24094
24599
  * Central orchestrator class for the Kritzel canvas system.
24095
24600
  * Manages workspaces, objects, selection, clipboard operations, and coordinates
@@ -24118,6 +24623,10 @@ class KritzelCore {
24118
24623
  _localizationManager;
24119
24624
  /** Manager for license validation and watermark gating */
24120
24625
  _licenseManager;
24626
+ /** Manager for browser permission checks and permission prompts */
24627
+ _permissionManager;
24628
+ /** Manager for clipboard copy/cut/paste orchestration */
24629
+ _clipboardManager;
24121
24630
  /** Per-core registry of drawing tools (one instance per editor). */
24122
24631
  _toolRegistry;
24123
24632
  /** Optional unique identifier for namespacing storage keys across multiple editor instances */
@@ -24180,6 +24689,13 @@ class KritzelCore {
24180
24689
  get licenseManager() {
24181
24690
  return this._licenseManager;
24182
24691
  }
24692
+ /**
24693
+ * Gets the browser permission manager.
24694
+ * @returns The KritzelBrowserPermissionManager for browser permission checks and requests
24695
+ */
24696
+ get permissionManager() {
24697
+ return this._permissionManager;
24698
+ }
24183
24699
  /**
24184
24700
  * Gets the tool registry scoped to this core instance.
24185
24701
  * @returns The KritzelToolRegistry owned by this core
@@ -24187,6 +24703,13 @@ class KritzelCore {
24187
24703
  get toolRegistry() {
24188
24704
  return this._toolRegistry;
24189
24705
  }
24706
+ /**
24707
+ * Gets the clipboard manager scoped to this core instance.
24708
+ * @returns The KritzelClipboardManager owned by this core
24709
+ */
24710
+ get clipboardManager() {
24711
+ return this._clipboardManager;
24712
+ }
24190
24713
  /**
24191
24714
  * Gets the editor ID used for namespacing storage keys.
24192
24715
  * @returns The editor ID or undefined if not set
@@ -24234,6 +24757,8 @@ class KritzelCore {
24234
24757
  this._themeManager = new schema_constants.KritzelThemeManager(this);
24235
24758
  this._localizationManager = new schema_constants.KritzelLocalizationManager(this);
24236
24759
  this._licenseManager = new schema_constants.KritzelLicenseManager(this);
24760
+ this._permissionManager = new KritzelBrowserPermissionManager(this);
24761
+ this._clipboardManager = new KritzelClipboardManager(this);
24237
24762
  this._toolRegistry = new KritzelToolRegistry(this);
24238
24763
  this._assetResolver = new schema_constants.KritzelAssetResolver();
24239
24764
  }
@@ -24731,216 +25256,6 @@ class KritzelCore {
24731
25256
  this.rerender();
24732
25257
  }
24733
25258
  }
24734
- /**
24735
- * Copies the currently selected objects to the clipboard.
24736
- * Creates deep copies of objects and tracks ID mappings for anchor remapping.
24737
- */
24738
- copy() {
24739
- const selectionGroup = this._store.selectionGroup;
24740
- if (selectionGroup) {
24741
- // Copy each object and store them in an array, also track the ID mapping
24742
- const idMapping = new Map();
24743
- const copiedObjects = selectionGroup.objects
24744
- .sort((a, b) => a.zIndex - b.zIndex)
24745
- .map(obj => {
24746
- const copiedObj = obj.copy();
24747
- // Map: newId -> originalId
24748
- idMapping.set(copiedObj.id, obj.id);
24749
- return copiedObj;
24750
- });
24751
- this._store.state.copiedObjects = copiedObjects;
24752
- this._store.state.copiedObjectIdMapping = idMapping;
24753
- }
24754
- }
24755
- /**
24756
- * Cuts the currently selected objects (copy then delete).
24757
- * Objects are copied to clipboard and then removed from the canvas.
24758
- */
24759
- cut() {
24760
- const selectionGroup = this._store.selectionGroup;
24761
- if (!selectionGroup) {
24762
- return;
24763
- }
24764
- // Copy first, then delete
24765
- this.copy();
24766
- this.delete();
24767
- }
24768
- /**
24769
- * Pastes objects from the clipboard to the canvas.
24770
- * Handles positioning (explicit, offset, or centered for cross-workspace paste),
24771
- * workspace assignment, z-index ordering, group children, and anchor remapping.
24772
- * Creates a selection group containing the pasted objects.
24773
- * @param x - Optional explicit X position for paste
24774
- * @param y - Optional explicit Y position for paste
24775
- */
24776
- paste(x, y) {
24777
- const copiedObjects = this._store.state.copiedObjects;
24778
- if (!copiedObjects || copiedObjects.length === 0) {
24779
- return;
24780
- }
24781
- const activeWorkspace = this._store.state.activeWorkspace;
24782
- const originalIdMapping = this._store.state.copiedObjectIdMapping;
24783
- // Check if we're pasting from a different workspace
24784
- const isDifferentWorkspace = copiedObjects.some(obj => obj.workspaceId !== activeWorkspace.id);
24785
- // Calculate the bounding box of all copied objects
24786
- const minX = Math.min(...copiedObjects.map(obj => obj.translateX));
24787
- const minY = Math.min(...copiedObjects.map(obj => obj.translateY));
24788
- // Determine the paste position
24789
- let pasteX;
24790
- let pasteY;
24791
- if (x !== undefined && y !== undefined) {
24792
- // Explicit position provided (e.g., right-click paste)
24793
- pasteX = x;
24794
- pasteY = y;
24795
- }
24796
- else if (isDifferentWorkspace) {
24797
- // Pasting to different workspace without explicit position
24798
- // Keep original position; will be centered by centerInViewport() later
24799
- pasteX = minX;
24800
- pasteY = minY;
24801
- }
24802
- else {
24803
- // Same workspace paste: offset by 25 pixels from original position
24804
- pasteX = minX + 25 / this._store.state.scale;
24805
- pasteY = minY + 25 / this._store.state.scale;
24806
- }
24807
- // Calculate the offset to apply to all objects
24808
- const offsetX = pasteX - minX;
24809
- const offsetY = pasteY - minY;
24810
- this.removeSelectionGroup();
24811
- this.removeSelectionBox();
24812
- // Create a new selection group for the pasted objects
24813
- const selectionGroup = schema_constants.KritzelSelectionGroup.create(this);
24814
- // Build a reverse mapping: originalId -> newCopiedObjectId
24815
- // This is used to remap anchor references from original IDs to the copied object IDs
24816
- const originalToNewIdMap = new Map();
24817
- if (originalIdMapping) {
24818
- originalIdMapping.forEach((originalId, copiedId) => {
24819
- originalToNewIdMap.set(originalId, copiedId);
24820
- });
24821
- }
24822
- // Precompute zIndex once to avoid O(N * total_objects) repeated scans
24823
- const baseZIndex = this._store.currentZIndex;
24824
- // Batch all inserts and updates in a single Y.js transaction to avoid
24825
- // N separate observer callbacks and N rerenders (fires only once at commit)
24826
- this._store.objects.transaction(() => {
24827
- // Recursively flush a group's pending children (and any nested groups'
24828
- // pending children) into the store. Without recursion, nested groups
24829
- // would have their grandchildren stranded in `_pendingChildren` and they
24830
- // would never appear on the canvas, even though the parent group's
24831
- // bounding box looks correct.
24832
- const flushPendingChildren = (group, parentZIndex) => {
24833
- if (group._pendingChildren.length === 0) {
24834
- return;
24835
- }
24836
- const pendingChildren = group._pendingChildren;
24837
- group._pendingChildren = [];
24838
- pendingChildren.forEach((child, childIndex) => {
24839
- if (child.workspaceId !== activeWorkspace.id) {
24840
- child.workspaceId = activeWorkspace.id;
24841
- }
24842
- child.updatePosition(child.translateX + offsetX, child.translateY + offsetY);
24843
- child.zIndex = parentZIndex + childIndex + 1;
24844
- // Recurse before adding so grandchildren are inserted as well
24845
- if (schema_constants.KritzelClassHelper.isInstanceOf(child, 'KritzelGroup')) {
24846
- flushPendingChildren(child, child.zIndex);
24847
- }
24848
- this.addObject(child);
24849
- });
24850
- group.finalize();
24851
- };
24852
- const newlyAddedObjects = [];
24853
- // First add all copied objects to the objectsMap with updated positions
24854
- copiedObjects.forEach((obj, i) => {
24855
- // Update workspace if pasting to a different workspace
24856
- if (obj.workspaceId !== activeWorkspace.id) {
24857
- obj.workspaceId = activeWorkspace.id;
24858
- }
24859
- // Update position
24860
- obj.updatePosition(obj.translateX + offsetX, obj.translateY + offsetY);
24861
- // Update z-index
24862
- obj.zIndex = baseZIndex + i;
24863
- // Handle KritzelGroup: also add pending children (and grandchildren) with offset
24864
- if (schema_constants.KritzelClassHelper.isInstanceOf(obj, 'KritzelGroup')) {
24865
- flushPendingChildren(obj, obj.zIndex);
24866
- }
24867
- // Add to objectsMap
24868
- this.addObject(obj);
24869
- newlyAddedObjects.push(obj);
24870
- // Add to selection group
24871
- selectionGroup.addOrRemove(obj);
24872
- });
24873
- this.engine.emitObjectsAdded(newlyAddedObjects);
24874
- // Update line anchors to point to the newly pasted objects
24875
- // Only remap if the anchor target was also part of the copied selection
24876
- copiedObjects.forEach(obj => {
24877
- if (schema_constants.KritzelClassHelper.isInstanceOf(obj, 'KritzelLine')) {
24878
- let updated = false;
24879
- if (obj.startAnchor && originalToNewIdMap.has(obj.startAnchor.objectId)) {
24880
- obj.startAnchor = { objectId: originalToNewIdMap.get(obj.startAnchor.objectId) };
24881
- updated = true;
24882
- }
24883
- if (obj.endAnchor && originalToNewIdMap.has(obj.endAnchor.objectId)) {
24884
- obj.endAnchor = { objectId: originalToNewIdMap.get(obj.endAnchor.objectId) };
24885
- updated = true;
24886
- }
24887
- // If anchors were updated, rebuild the anchor index and persist the change
24888
- if (updated) {
24889
- this._store.objects.update(obj);
24890
- }
24891
- }
24892
- });
24893
- });
24894
- // Rebuild anchor index after all anchor updates
24895
- this._anchorManager.rebuildIndex();
24896
- // Mark selection group as selected
24897
- selectionGroup.isSelected = true;
24898
- // Set rotation for single object
24899
- if (copiedObjects.length === 1) {
24900
- selectionGroup.rotation = copiedObjects[0].rotation;
24901
- }
24902
- // For cross-workspace paste, set the selection group's position before inserting
24903
- // it into the store. This mirrors the existing pattern where objects have
24904
- // updatePosition called before addObject, so the correct position is captured
24905
- // in the single initial insert with no separate Yjs update (and no extra undo step).
24906
- if (isDifferentWorkspace) {
24907
- if (x !== undefined && y !== undefined) {
24908
- // Explicit cursor position provided (e.g. right-click paste)
24909
- selectionGroup.updatePosition(x, y);
24910
- }
24911
- else {
24912
- // Ctrl+V paste: center objects in the current viewport
24913
- selectionGroup.centerInViewport();
24914
- }
24915
- }
24916
- // Add the selection group (inserts with the already-adjusted position)
24917
- this.addSelectionGroup(selectionGroup);
24918
- // Bring pasted objects into view with a smooth animation if they are not
24919
- // fully visible. These only mutate in-memory viewport state (scale, translateX/Y)
24920
- // and do not produce any Yjs writes, so they have no impact on the undo history.
24921
- this.engine.viewport.bringIntoViewIfNeeded(selectionGroup);
24922
- this._store.state.isSelecting = false;
24923
- // Defer creating copies for future pastes to next frame to avoid
24924
- // doubling the object creation cost in the current paste frame
24925
- requestAnimationFrame(() => {
24926
- const newSelectionGroup = this._store.selectionGroup;
24927
- if (newSelectionGroup) {
24928
- const newIdMapping = new Map();
24929
- const newCopiedObjects = newSelectionGroup.objects
24930
- .sort((a, b) => a.zIndex - b.zIndex)
24931
- .map(obj => {
24932
- const copiedObj = obj.copy();
24933
- newIdMapping.set(copiedObj.id, obj.id);
24934
- return copiedObj;
24935
- });
24936
- this._store.state.copiedObjects = newCopiedObjects;
24937
- this._store.state.copiedObjectIdMapping = newIdMapping;
24938
- }
24939
- });
24940
- this._store.setState('activeTool', this._toolRegistry.getTool('selection'));
24941
- this.engine.emitObjectsChange();
24942
- this.rerender();
24943
- }
24944
25259
  /**
24945
25260
  * Moves objects one step forward in the z-order stack.
24946
25261
  * @param object - Optional specific object to move; defaults to all selected objects
@@ -27086,6 +27401,8 @@ const KritzelEngine = class {
27086
27401
  viewportChange;
27087
27402
  /** Emitted when the awareness state changes (remote user cursors, presence). */
27088
27403
  awarenessChange;
27404
+ /** Emitted when notifications change. */
27405
+ notificationsChange;
27089
27406
  forceUpdate = 0;
27090
27407
  /** Manually triggers the `objectsSelectionChange` event. */
27091
27408
  async triggerSelectionChange() {
@@ -27200,19 +27517,56 @@ const KritzelEngine = class {
27200
27517
  this.viewport.handleResize();
27201
27518
  }
27202
27519
  handleKeyDown(ev) {
27203
- if (this.core.store.isDisabled || !this.shouldHandleKeyboardEvent(ev)) {
27520
+ if (this.core.store.isDisabled || !this.shouldHandleGlobalShortcutEvent(ev)) {
27204
27521
  return;
27205
27522
  }
27206
27523
  this.keyHandler.handleKeyDown(ev);
27207
27524
  }
27208
27525
  handleKeyUp(ev) {
27209
- if (this.core.store.isDisabled || !this.shouldHandleKeyboardEvent(ev)) {
27526
+ if (this.core.store.isDisabled || !this.shouldHandleGlobalShortcutEvent(ev)) {
27210
27527
  return;
27211
27528
  }
27212
27529
  this.keyHandler.handleKeyUp(ev);
27213
27530
  }
27531
+ handleCopy(ev) {
27532
+ if (this.core.store.isDisabled || !this.shouldHandleGlobalShortcutEvent(ev)) {
27533
+ return;
27534
+ }
27535
+ if (!this.core.store.selectionGroup || this.core.store.activeText || this.core.store.activeShape) {
27536
+ return;
27537
+ }
27538
+ if (ev.cancelable) {
27539
+ ev.preventDefault();
27540
+ }
27541
+ this.core.clipboardManager.copy(ev);
27542
+ this.core.rerender();
27543
+ }
27544
+ handleCut(ev) {
27545
+ if (this.core.store.isDisabled || !this.shouldHandleGlobalShortcutEvent(ev)) {
27546
+ return;
27547
+ }
27548
+ if (!this.core.store.selectionGroup || this.core.store.activeText || this.core.store.activeShape) {
27549
+ return;
27550
+ }
27551
+ if (ev.cancelable) {
27552
+ ev.preventDefault();
27553
+ }
27554
+ this.core.clipboardManager.cut(ev);
27555
+ }
27556
+ handlePaste(ev) {
27557
+ if (this.core.store.isDisabled || !this.shouldHandleGlobalShortcutEvent(ev)) {
27558
+ return;
27559
+ }
27560
+ if (this.core.store.activeText || this.core.store.activeShape) {
27561
+ return;
27562
+ }
27563
+ if (ev.cancelable) {
27564
+ ev.preventDefault();
27565
+ }
27566
+ void this.core.clipboardManager.paste(undefined, undefined, ev);
27567
+ }
27214
27568
  /**
27215
- * Determines if the engine should handle a keyboard event.
27569
+ * Determines if the engine should handle a keyboard/clipboard shortcut event.
27216
27570
  * Returns true only if:
27217
27571
  * - The event originates from within the engine component, OR
27218
27572
  * - The event target is not an interactive element that needs keyboard input
@@ -27220,7 +27574,7 @@ const KritzelEngine = class {
27220
27574
  * This prevents conflicts with inputs, textareas, dialogs, or other
27221
27575
  * interactive elements that need their own keyboard handling.
27222
27576
  */
27223
- shouldHandleKeyboardEvent(ev) {
27577
+ shouldHandleGlobalShortcutEvent(ev) {
27224
27578
  const path = ev.composedPath();
27225
27579
  // Check if event path includes our host - works reliably with Shadow DOM
27226
27580
  if (path.includes(this.host)) {
@@ -27316,11 +27670,11 @@ const KritzelEngine = class {
27316
27670
  }
27317
27671
  /** Copies the currently selected objects to the internal clipboard. */
27318
27672
  async copy() {
27319
- this.core.copy();
27673
+ this.core.clipboardManager.copy();
27320
27674
  }
27321
27675
  /** Cuts the currently selected objects to the internal clipboard (deletes them from the canvas). */
27322
27676
  async cut() {
27323
- this.core.cut();
27677
+ this.core.clipboardManager.cut();
27324
27678
  }
27325
27679
  /**
27326
27680
  * Pastes previously copied objects at the specified world coordinates.
@@ -27328,7 +27682,16 @@ const KritzelEngine = class {
27328
27682
  * @param y - Y position in world coordinates.
27329
27683
  */
27330
27684
  async paste(x, y) {
27331
- this.core.paste(x, y);
27685
+ const hasPermission = await this.core.permissionManager.requestPermission('clipboard-read');
27686
+ if (hasPermission !== 'granted') {
27687
+ this.notificationsChange.emit({
27688
+ type: 'error',
27689
+ message: 'Clipboard read permission is required to paste content. Please grant permission and try again.',
27690
+ id: schema_constants.ObjectHelper.generateUUID(),
27691
+ });
27692
+ return;
27693
+ }
27694
+ this.core.clipboardManager.paste(x, y);
27332
27695
  }
27333
27696
  /**
27334
27697
  * Moves an object one layer forward in the z-order.
@@ -27819,10 +28182,6 @@ const KritzelEngine = class {
27819
28182
  async worldToScreen(x, y) {
27820
28183
  return this.viewport.worldToScreen(x, y);
27821
28184
  }
27822
- /** Returns the objects currently stored in the internal clipboard from a prior `copy()` call. */
27823
- async getCopiedObjects() {
27824
- return this.core.store.state.copiedObjects || [];
27825
- }
27826
28185
  /** Returns all objects currently visible within the viewport bounds. */
27827
28186
  async getObjectsInViewport() {
27828
28187
  return this.core.getObjectsInViewport();
@@ -28479,6 +28838,7 @@ const KritzelEngine = class {
28479
28838
  this.objectsInViewportChange = index.createEvent(this, "objectsInViewportChange");
28480
28839
  this.viewportChange = index.createEvent(this, "viewportChange");
28481
28840
  this.awarenessChange = index.createEvent(this, "awarenessChange");
28841
+ this.notificationsChange = index.createEvent(this, "notificationsChange");
28482
28842
  this.core = new KritzelCore(this);
28483
28843
  }
28484
28844
  disconnectedCallback() {
@@ -28757,7 +29117,6 @@ const KritzelEngine = class {
28757
29117
  this.core.resetActiveShape();
28758
29118
  }
28759
29119
  this.core.store.state.skipContextMenu = false;
28760
- this.core.store.state.copiedObjects = undefined;
28761
29120
  if (activeTool) {
28762
29121
  this.activeToolChange.emit(activeTool);
28763
29122
  }
@@ -29409,7 +29768,7 @@ const KritzelExport = class {
29409
29768
  return (index.h("div", { class: "export-tab-content" }, index.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 })));
29410
29769
  }
29411
29770
  render() {
29412
- return (index.h(index.Host, { key: 'ff6ce7e5d3a0a7a024148529f86bf3ec7ffb0333' }, index.h("kritzel-dialog", { key: '9051d262450af5fbe98959092f6bb0ac25ab43ce', isOpen: this.isDialogOpen, dialogTitle: this.terms['export.dialogTitle'] ?? 'Export', closable: true, contained: true, onDialogClose: this.closeDialog }, index.h("div", { key: '21f404a2101d5cf1d24aeeee663d8fb1854b9574', class: "export-content" }, index.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(), index.h("button", { key: '45809f91ddd63c74f5a4b3a2e266919f244c6109', class: "export-primary-button", onClick: this.handleExport }, this.terms['export.exportButton'] ?? 'Export')))));
29771
+ return (index.h(index.Host, { key: 'fd219fffd4d8bb528e6a90055c2b43a5f5a73060' }, index.h("kritzel-dialog", { key: '9f4ba5b05f5ad31084aab457386a86cbc6d4f2bc', isOpen: this.isDialogOpen, dialogTitle: this.terms['export.dialogTitle'] ?? 'Export', closable: true, contained: true, onDialogClose: this.closeDialog }, index.h("div", { key: '920bb8f20b42897680084bde47270b3812e2f3e4', class: "export-content" }, index.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(), index.h("button", { key: 'ddf36c1e7cb81409c2cecfa6585e0f1dd7aca2c3', class: "export-primary-button", onClick: this.handleExport }, this.terms['export.exportButton'] ?? 'Export')))));
29413
29772
  }
29414
29773
  };
29415
29774
  KritzelExport.style = kritzelExportCss();
@@ -29424,7 +29783,7 @@ const KritzelFont = class {
29424
29783
  size = 24;
29425
29784
  color = '#000000';
29426
29785
  render() {
29427
- return (index.h(index.Host, { key: '49c44f7fef61bd7116e63bbe9522b0a0e22de116' }, index.h("div", { key: '4d130682f67e9c0d758cb6194e6a95f15bceb2ee', class: "font-preview", style: {
29786
+ return (index.h(index.Host, { key: 'e37350dd092fb7c81a5624bfdc39c3b9af66b894' }, index.h("div", { key: 'a962ccc23a48f62b86d49ae41b1c9c27bdc9a5b9', class: "font-preview", style: {
29428
29787
  fontFamily: this.fontFamily,
29429
29788
  fontSize: `${this.size}px`,
29430
29789
  color: this.color
@@ -29500,7 +29859,7 @@ const KritzelFontSize = class {
29500
29859
  render() {
29501
29860
  const color = 'var(--kritzel-global-text-primary)';
29502
29861
  const sizes = this.sizes ?? [];
29503
- return (index.h(index.Host, { key: '2083b05b3fdc9940c26dd90dbf9e097564ee976d' }, sizes.map(size => (index.h("div", { tabIndex: 0, class: {
29862
+ return (index.h(index.Host, { key: 'a314e990b56cb4e819c4d83a139016b1658606b8' }, sizes.map(size => (index.h("div", { tabIndex: 0, class: {
29504
29863
  'size-container': true,
29505
29864
  'selected': this.selectedSize === size,
29506
29865
  }, onClick: () => this.handleSizeClick(size), onKeyDown: event => this.handleKeyDown(event, size) }, index.h("kritzel-font", { fontFamily: this.fontFamily, size: size, color: color }))))));
@@ -29567,7 +29926,7 @@ const KritzelInput = class {
29567
29926
  this.valueChange.emit(input.value);
29568
29927
  };
29569
29928
  render() {
29570
- return (index.h(index.Host, { key: '8584ea0fa1a2cf0b9518ea978303837da5bfaf31' }, index.h("div", { key: 'e9f3ac340f17580221d1a86c8eb913fce2acdf49', class: "input-container" }, this.label && index.h("label", { key: 'b0d4c0250b07d83a3cd99e0ab18ed672a29de5f0', class: "input-label" }, this.label), index.h("div", { key: '5044bcff621d6c6850ef3d225608427401b75ebd', class: { 'input-wrapper': true, 'has-suffix': !!this.suffix } }, index.h("input", { key: 'f8d057840e975d900c37a891268f39edb5128d49', type: this.type, class: "text-input", value: this.inputValue, placeholder: this.placeholder, disabled: this.disabled, onInput: this.handleInput }), this.suffix && index.h("span", { key: '4ae299b88798767b13e7126f88d3bd4703c91938', class: "input-suffix" }, this.suffix)))));
29929
+ return (index.h(index.Host, { key: '7e16272802a5485207721f4f01ddf8e956b56dac' }, index.h("div", { key: 'ab60dacd08c6199db507d896c00cd0c788ecf7fa', class: "input-container" }, this.label && index.h("label", { key: 'e2e5769ca2954442da5a0f2a2abb07847986eac8', class: "input-label" }, this.label), index.h("div", { key: 'e7131f6d1a770cf71de9f7086e3a8a092296b80e', class: { 'input-wrapper': true, 'has-suffix': !!this.suffix } }, index.h("input", { key: '565cb30ed36818badc148475c596b468cdd2a65b', type: this.type, class: "text-input", value: this.inputValue, placeholder: this.placeholder, disabled: this.disabled, onInput: this.handleInput }), this.suffix && index.h("span", { key: 'ec4afb63443b6c89a829e515df7d33066f0f8def', class: "input-suffix" }, this.suffix)))));
29571
29930
  }
29572
29931
  static get watchers() { return {
29573
29932
  "value": [{
@@ -29707,7 +30066,7 @@ const KritzelLoginDialog = class {
29707
30066
  this.dialogClosed.emit();
29708
30067
  };
29709
30068
  render() {
29710
- return (index.h(index.Host, { key: '443d817d6eba7b9fcd31cab652293d7c01fc5ffa' }, index.h("kritzel-dialog", { key: '55214d78abb10f7e2ba789e10d98bf7f3287ff6a', dialogTitle: this.dialogTitle, isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, size: "small", contained: true }, index.h("div", { key: '31806a7a7c98c2210b7f60d2e0816a8ebc14aff3', class: "login-content" }, this.subtitle && (index.h("p", { key: '7edb2bc7d25cb3c8589594791822e62d035cd3a6', class: "login-subtitle" }, this.subtitle)), index.h("div", { key: '3b7731d9cfb98915ebe07b54a07991e03716ce5c', class: "login-providers" }, this.providers.map(provider => (index.h("button", { key: provider.name, class: {
30069
+ return (index.h(index.Host, { key: '6ef65077c95d32fee8da4b70ad2e832ad1bb78f7' }, index.h("kritzel-dialog", { key: '04f3d3350393475c7267d8ca90c0b3f0fce10c38', dialogTitle: this.dialogTitle, isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, size: "small", contained: true }, index.h("div", { key: 'c2ed4e339d86dcea23b4a0778b8966a6ae36107a', class: "login-content" }, this.subtitle && (index.h("p", { key: 'ae46baa70526d057b3427459903287489e645e08', class: "login-subtitle" }, this.subtitle)), index.h("div", { key: '1055d5861e5281be234094a3c5c86a6aaf58935f', class: "login-providers" }, this.providers.map(provider => (index.h("button", { key: provider.name, class: {
29711
30070
  'provider-button': true,
29712
30071
  'is-loading': this.loadingProvider === provider.name,
29713
30072
  'is-disabled': this.loadingProvider !== null && this.loadingProvider !== provider.name,
@@ -29813,15 +30172,15 @@ const KritzelMasterDetail = class {
29813
30172
  const selectedItem = this.items.find(item => item.id === this.selectedItemId);
29814
30173
  const panelId = 'master-detail-panel';
29815
30174
  const selectedTabId = selectedItem ? `tab-${selectedItem.id}` : undefined;
29816
- return (index.h(index.Host, { key: '6429c72aaf47f08ba0bdf5e64829b133339dafab' }, index.h("div", { key: '02e4616c67843632d2291abe347ee6ef7a660b12', class: {
30175
+ return (index.h(index.Host, { key: 'e094bc3df0e0d1598e84c9a17a033079b1db33f6' }, index.h("div", { key: '79509ba5d63703fe36650b07b5172f77bd6eafde', class: {
29817
30176
  'master-detail-container': true,
29818
30177
  'is-mobile-detail-visible': this.showMobileDetail,
29819
- } }, index.h("nav", { key: '35cf3e8f97819a313fa6c57cde0faf05677997eb', class: "master-menu", role: "tablist", "aria-orientation": "vertical", "aria-label": "Settings categories" }, this.items.map((item, index$1) => (index.h("button", { key: item.id, id: `tab-${item.id}`, ref: el => this.setTabRef(el, index$1), class: {
30178
+ } }, index.h("nav", { key: '79d200d8c2e54789a33fc88b07c48f034de140ad', class: "master-menu", role: "tablist", "aria-orientation": "vertical", "aria-label": "Settings categories" }, this.items.map((item, index$1) => (index.h("button", { key: item.id, id: `tab-${item.id}`, ref: el => this.setTabRef(el, index$1), class: {
29820
30179
  'menu-item': true,
29821
30180
  'is-selected': item.id === this.selectedItemId,
29822
30181
  'is-disabled': !!item.disabled,
29823
30182
  'is-focused': index$1 === this.focusedIndex,
29824
- }, role: "tab", "aria-selected": item.id === this.selectedItemId ? 'true' : 'false', "aria-controls": panelId, "aria-disabled": item.disabled ? 'true' : undefined, tabIndex: this.getTabIndex(item, index$1), disabled: item.disabled, onClick: () => this.handleItemClick(item), onKeyDown: e => this.handleKeyDown(e, item, index$1), onFocus: () => this.handleFocus(index$1), onBlur: this.handleBlur }, item.icon && (index.h("kritzel-icon", { name: item.icon, size: 20, class: "menu-item-icon" })), index.h("span", { class: "menu-item-label" }, item.label), index.h("span", { class: "menu-item-chevron", "aria-hidden": "true" }, index.h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("path", { d: "m9 18 6-6-6-6" }))))))), index.h("div", { key: 'cc7c69eb7ef02096fb76dbce310740d21019e72f', id: panelId, class: "detail-panel", role: "tabpanel", "aria-labelledby": selectedTabId }, index.h("button", { key: 'd10898c7eb8ec6d993024c7833e1f765a7b93079', class: "mobile-back-button", onClick: this.handleBackClick, "aria-label": "Back to menu" }, index.h("kritzel-icon", { key: '5395af1bce2b91ade970ad68b1bb87b59e771dff', name: "chevron-left", size: 20, class: "mobile-back-icon" }), "Back"), index.h("slot", { key: '7a5a1ef33081c117f1b16f1da887577bd97d3681' })))));
30183
+ }, role: "tab", "aria-selected": item.id === this.selectedItemId ? 'true' : 'false', "aria-controls": panelId, "aria-disabled": item.disabled ? 'true' : undefined, tabIndex: this.getTabIndex(item, index$1), disabled: item.disabled, onClick: () => this.handleItemClick(item), onKeyDown: e => this.handleKeyDown(e, item, index$1), onFocus: () => this.handleFocus(index$1), onBlur: this.handleBlur }, item.icon && (index.h("kritzel-icon", { name: item.icon, size: 20, class: "menu-item-icon" })), index.h("span", { class: "menu-item-label" }, item.label), index.h("span", { class: "menu-item-chevron", "aria-hidden": "true" }, index.h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("path", { d: "m9 18 6-6-6-6" }))))))), index.h("div", { key: '642b65781509d192330bb2602dd081585d16f76d', id: panelId, class: "detail-panel", role: "tabpanel", "aria-labelledby": selectedTabId }, index.h("button", { key: '18a922dbaad63f05bc3ca7de176de4996b5d7fcb', class: "mobile-back-button", onClick: this.handleBackClick, "aria-label": "Back to menu" }, index.h("kritzel-icon", { key: 'b31732cef2ffdb78e67be7ab793aaaf60e373763', name: "chevron-left", size: 20, class: "mobile-back-icon" }), "Back"), index.h("slot", { key: '48fe4ae631a8c0b2db9e4a232edb6300d603562c' })))));
29825
30184
  }
29826
30185
  static get watchers() { return {
29827
30186
  "selectedItemId": [{
@@ -29894,7 +30253,7 @@ const KritzelMenu = class {
29894
30253
  this.itemCloseChildMenu.emit(event.detail);
29895
30254
  };
29896
30255
  render() {
29897
- return (index.h(index.Host, { key: '4e4e510d2b1b5834ba12739d95e7c33268c005a6', tabIndex: 0, onClick: e => e.stopPropagation() }, this.openChildMenuItem && index.h("div", { key: 'ee60d7f4ceefda5fcdcfb64956b20d42b703c231', class: "has-open-child-overlay", onClick: this.onOverlayClick }), this.items.map(item => (index.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 })))));
30256
+ return (index.h(index.Host, { key: '6dfdc7928ae69aa191b2c64f9db79801e6978e92', tabIndex: 0, onClick: e => e.stopPropagation() }, this.openChildMenuItem && index.h("div", { key: 'ab8fa25f1b6ee0b7960cb0df84c40b1a7648c4e9', class: "has-open-child-overlay", onClick: this.onOverlayClick }), this.items.map(item => (index.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 })))));
29898
30257
  }
29899
30258
  };
29900
30259
  KritzelMenu.style = kritzelMenuCss();
@@ -29999,12 +30358,12 @@ const KritzelMenuItem = class {
29999
30358
  ];
30000
30359
  }
30001
30360
  render() {
30002
- return (index.h(index.Host, { key: 'ac91e3534c71074634ba053adc02f924e3fb5e14', tabIndex: this.item.isDisabled ? -1 : 0, class: {
30361
+ return (index.h(index.Host, { key: 'dbdcfcee7fd36fe0b9c405dd3d1641aa62465e53', tabIndex: this.item.isDisabled ? -1 : 0, class: {
30003
30362
  'selected': this.item.isSelected,
30004
30363
  'editing': this.item.isEditing,
30005
30364
  'disabled': this.item.isDisabled,
30006
30365
  'child-open': this.item.isChildMenuOpen,
30007
- }, onClick: this.handleItemSelect }, index.h("div", { key: 'f49cf8a96f7394e244ce53d3933137325221d578', class: "menu-item-overlay" }), this.item.isEditing ? this.renderEditMode() : this.renderViewMode()));
30366
+ }, onClick: this.handleItemSelect }, index.h("div", { key: 'ef97c7cf48190c1bbdde0dd9011cc1d5674b7c94', class: "menu-item-overlay" }), this.item.isEditing ? this.renderEditMode() : this.renderViewMode()));
30008
30367
  }
30009
30368
  static get watchers() { return {
30010
30369
  "item": [{
@@ -30077,11 +30436,80 @@ const KritzelMoreMenu = class {
30077
30436
  this.closeMenu();
30078
30437
  };
30079
30438
  render() {
30080
- return (index.h(index.Host, { key: '1fe90a065ac35d1c8d2e9159d006b09e104750a3', class: { mobile: this.isTouchDevice }, style: { display: this.visible ? '' : 'none' } }, index.h("div", { key: '734234df7d24f712fa80c3871209142fed2b5ac2', class: { 'more-menu-wrapper': true, visible: this.visible } }, index.h("button", { key: 'a6a56bd5714a8780339390904f0469b395748ba9', class: "more-menu-button", "data-testid": "more-menu-button", onClick: this.toggleMenu, "aria-label": this.terms['moreMenu.ariaLabel'] ?? 'More options' }, index.h("kritzel-icon", { key: 'a4405ef4b8219f28fec095d4a56ac0aa027a9564', name: this.icon, size: this.iconSize })), index.h("kritzel-portal", { key: '99aa8a87844232ec079b48f95afab54cbfe51a0b', anchor: this.menuAnchor, offsetY: this.offsetY, onClose: this.closeMenu }, index.h("kritzel-menu", { key: 'b6861ae2d553ebf5f5af7448c2b053576a380e8c', items: this.visibleItems, onItemSelect: this.handleMenuItemSelect })))));
30439
+ return (index.h(index.Host, { key: '3e7179c87713cb0087d2d2053f97db8f603f7321', class: { mobile: this.isTouchDevice }, style: { display: this.visible ? '' : 'none' } }, index.h("div", { key: '23d6be1ec2c25cd0335c1134f18aad7d5be25c85', class: { 'more-menu-wrapper': true, visible: this.visible } }, index.h("button", { key: 'e3064ba3f7b4c20e8447e16327b73d0f47f3842b', class: "more-menu-button", "data-testid": "more-menu-button", onClick: this.toggleMenu, "aria-label": this.terms['moreMenu.ariaLabel'] ?? 'More options' }, index.h("kritzel-icon", { key: 'feed090efe4f80c62a29c8146c2d7d8041624381', name: this.icon, size: this.iconSize })), index.h("kritzel-portal", { key: '7b1a5db932c7bfb5ea8f4d666571c4f7fbbcd238', anchor: this.menuAnchor, offsetY: this.offsetY, onClose: this.closeMenu }, index.h("kritzel-menu", { key: '65b1225b15a494d49712ad1d2d108e90f09507e9', items: this.visibleItems, onItemSelect: this.handleMenuItemSelect })))));
30081
30440
  }
30082
30441
  };
30083
30442
  KritzelMoreMenu.style = kritzelMoreMenuCss();
30084
30443
 
30444
+ 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}`;
30445
+
30446
+ const KritzelNotificationCard = class {
30447
+ constructor(hostRef) {
30448
+ index.registerInstance(this, hostRef);
30449
+ this.dismiss = index.createEvent(this, "dismiss");
30450
+ this.hoverChange = index.createEvent(this, "hoverChange");
30451
+ }
30452
+ /** Notification data rendered by this card. */
30453
+ notification;
30454
+ /** Whether a formatted timestamp should be displayed when available. */
30455
+ showTimestamp = true;
30456
+ /** Locale used for formatting the timestamp. */
30457
+ locale;
30458
+ /** Emitted when the notification should be dismissed manually. */
30459
+ dismiss;
30460
+ /** Emitted when pointer hover state changes. */
30461
+ hoverChange;
30462
+ handleDismissClick = (event) => {
30463
+ event.stopPropagation();
30464
+ this.dismiss.emit();
30465
+ };
30466
+ handleMouseEnter = () => {
30467
+ this.hoverChange.emit(true);
30468
+ };
30469
+ handleMouseLeave = () => {
30470
+ this.hoverChange.emit(false);
30471
+ };
30472
+ get type() {
30473
+ return this.notification?.type ?? 'info';
30474
+ }
30475
+ get role() {
30476
+ return this.type === 'error' ? 'alert' : 'status';
30477
+ }
30478
+ getTypeLabel(type) {
30479
+ switch (type) {
30480
+ case 'warning':
30481
+ return 'Warning';
30482
+ case 'error':
30483
+ return 'Error';
30484
+ default:
30485
+ return 'Info';
30486
+ }
30487
+ }
30488
+ formatTimestamp(timestamp) {
30489
+ if (!timestamp || !this.showTimestamp) {
30490
+ return null;
30491
+ }
30492
+ const date = timestamp instanceof Date ? timestamp : new Date(timestamp);
30493
+ if (Number.isNaN(date.getTime())) {
30494
+ return null;
30495
+ }
30496
+ return new Intl.DateTimeFormat(this.locale, {
30497
+ dateStyle: 'medium',
30498
+ timeStyle: 'short',
30499
+ }).format(date);
30500
+ }
30501
+ render() {
30502
+ const notification = this.notification;
30503
+ if (!notification) {
30504
+ return index.h(index.Host, null);
30505
+ }
30506
+ const type = this.type;
30507
+ const timestamp = this.formatTimestamp(notification.timestamp);
30508
+ return (index.h(index.Host, null, index.h("article", { class: { card: true, [`type-${type}`]: true }, role: this.role, "aria-live": "polite", onMouseEnter: this.handleMouseEnter, onMouseLeave: this.handleMouseLeave }, index.h("header", { class: "card-header" }, index.h("span", { class: "type-pill" }, this.getTypeLabel(type)), index.h("div", { class: "header-right" }, timestamp && (index.h("time", { class: "timestamp", dateTime: notification.timestamp?.toISOString() }, timestamp)), index.h("button", { class: "dismiss-button", type: "button", "aria-label": "Dismiss notification", onClick: this.handleDismissClick }, index.h("kritzel-icon", { name: "x", size: 16 })))), index.h("p", { class: "message" }, notification.message))));
30509
+ }
30510
+ };
30511
+ KritzelNotificationCard.style = kritzelNotificationCardCss();
30512
+
30085
30513
  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}`;
30086
30514
 
30087
30515
  const KritzelNumericInput = class {
@@ -30175,7 +30603,7 @@ const KritzelNumericInput = class {
30175
30603
  this.valueChange.emit(newValue);
30176
30604
  };
30177
30605
  render() {
30178
- return (index.h(index.Host, { key: '3679786121f22a0aacb1b57701764b0bd0bf02f5' }, index.h("div", { key: 'e88251c356332f29d93f6a3d82f5154188243e4c', class: "input-container" }, this.label && index.h("label", { key: 'cb4d64a5acf94794a5876bc61dc13dd8159ad055', class: "input-label" }, this.label), index.h("div", { key: '9faad300f263e871322b68635feefb08172bd15c', class: "input-wrapper" }, index.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 }), index.h("div", { key: 'a959beb441fe2143dc9c1c69347194e024362927', class: "spinner-buttons" }, index.h("button", { key: '6e2129f36080317e68b6fe1c05e69ea4e655fd20', type: "button", class: "spinner-button spinner-up", onClick: this.handleIncrement, tabIndex: -1, "aria-label": "Increase value" }, index.h("svg", { key: '47b5e178f82ef4eb6f2c5d888496ed95683c2f6d', viewBox: "0 0 10 6", class: "spinner-icon" }, index.h("path", { key: '6d5eb31ac5829b1e024822786beaafd347081c7e', d: "M1 5L5 1L9 5", stroke: "currentColor", "stroke-width": "1.5", fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }))), index.h("button", { key: '5a897f0b29ce89d5bd94327d2055753d06148c89', type: "button", class: "spinner-button spinner-down", onClick: this.handleDecrement, tabIndex: -1, "aria-label": "Decrease value" }, index.h("svg", { key: '19ff176c07e1c2a18f708debc7c7f3953f2219cb', viewBox: "0 0 10 6", class: "spinner-icon" }, index.h("path", { key: '0c1289eb009b62ea36f7397811f553a4e7e669f6', d: "M1 1L5 5L9 1", stroke: "currentColor", "stroke-width": "1.5", fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }))))))));
30606
+ return (index.h(index.Host, { key: '008f18fc5ab81adcd82364706ca7d4c39ea39d77' }, index.h("div", { key: '5d91b2ec6e0458d186d923c422e8f6461ac7c51a', class: "input-container" }, this.label && index.h("label", { key: '45c911f1e11ff8289ab876192511f506eb65c940', class: "input-label" }, this.label), index.h("div", { key: 'f5e0bd1610a4085a291cc1d32787b68066757c69', class: "input-wrapper" }, index.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 }), index.h("div", { key: '1acab70c82ff7431f916685084a6e5dd827cdca3', class: "spinner-buttons" }, index.h("button", { key: 'd93c4bec338d49ead87897baf61d2e036999a035', type: "button", class: "spinner-button spinner-up", onClick: this.handleIncrement, tabIndex: -1, "aria-label": "Increase value" }, index.h("svg", { key: '1e52d43c1eb1e9a43025302a7cd1f3d74c038c36', viewBox: "0 0 10 6", class: "spinner-icon" }, index.h("path", { key: '2a96516390bd9caf5b6a3617d07f7b10e2b42451', d: "M1 5L5 1L9 5", stroke: "currentColor", "stroke-width": "1.5", fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }))), index.h("button", { key: '038cf61f4b8982a17bf5c279d78556872a03efea', type: "button", class: "spinner-button spinner-down", onClick: this.handleDecrement, tabIndex: -1, "aria-label": "Decrease value" }, index.h("svg", { key: '30110c2ad975b51049a902953c52be66c1b7b0af', viewBox: "0 0 10 6", class: "spinner-icon" }, index.h("path", { key: '5abe735234debfa80a983d2fb60eeeaedb1dbf9b', d: "M1 1L5 5L9 1", stroke: "currentColor", "stroke-width": "1.5", fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }))))))));
30179
30607
  }
30180
30608
  static get watchers() { return {
30181
30609
  "value": [{
@@ -30214,7 +30642,7 @@ const KritzelOpacitySlider = class {
30214
30642
  }
30215
30643
  render() {
30216
30644
  const percentage = this.getPercentage();
30217
- return (index.h(index.Host, { key: '9b580f61b9113950b7dff5339879374f7882e1bd' }, index.h("div", { key: '80fb336580c49d4d7026993499b9c008ac2a4594', class: "opacity-container" }, index.h("div", { key: 'fcdd8e523d5f2ff4758a4839f2f43cbdabba4c5a', class: "slider-wrapper" }, index.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: {
30645
+ return (index.h(index.Host, { key: 'e3a18981e5193ecd1ffaae5c807bdf6fa67e9695' }, index.h("div", { key: 'ee5ff0bb1355ca4f7e5d727ab8861933259c1a82', class: "opacity-container" }, index.h("div", { key: '6362fb3dba6910c55f5fe3b2a4245bcbb33dcdbb', class: "slider-wrapper" }, index.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: {
30218
30646
  '--slider-progress': `${percentage}%`,
30219
30647
  '--kritzel-opacity-slider-thumb-border-color': this.previewColor,
30220
30648
  } })))));
@@ -30567,7 +30995,7 @@ const KritzelPortal = class {
30567
30995
  this.portal.style.visibility = 'visible';
30568
30996
  }
30569
30997
  render() {
30570
- return (index.h(index.Host, { key: 'f4ee3156b094ff1126db417f8396ca6cd0eb9aa4', style: { display: this.anchor ? 'block' : 'none' } }, index.h("slot", { key: '87812f4ddd50a29c8a76938e8778500a4873a30c' })));
30998
+ return (index.h(index.Host, { key: '95fbfde070f33a7f1b59026de7c687a2f6d0119c', style: { display: this.anchor ? 'block' : 'none' } }, index.h("slot", { key: '14af59a35c5caf71079f7afb5ea6f4c5f13b05f8' })));
30571
30999
  }
30572
31000
  static get watchers() { return {
30573
31001
  "anchor": [{
@@ -30581,7 +31009,7 @@ const KritzelPortal = class {
30581
31009
  * This file is auto-generated by the version bump scripts.
30582
31010
  * Do not modify manually.
30583
31011
  */
30584
- const KRITZEL_VERSION = '0.3.18';
31012
+ const KRITZEL_VERSION = '0.3.20';
30585
31013
 
30586
31014
  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)}`;
30587
31015
 
@@ -30805,7 +31233,7 @@ const KritzelSettings = class {
30805
31233
  }
30806
31234
  }
30807
31235
  render() {
30808
- return (index.h(index.Host, { key: '8dc22abaa2a19a14f3bbef10e34d133b16510498' }, index.h("kritzel-dialog", { key: '54072bf290e2d86bb7ec40d8d3e630be46fa3e1e', isOpen: this.isDialogOpen, dialogTitle: this.t('settings.dialogTitle', 'Settings'), size: "large", contained: true, onDialogClose: this.closeDialog }, index.h("kritzel-master-detail", { key: '5be80411c943ab4b7b90129527c8a0d147306a5f', items: this.categories, selectedItemId: this.selectedCategoryId, onItemSelect: this.handleCategorySelect }, this.renderCategoryContent()))));
31236
+ return (index.h(index.Host, { key: '6f26bd23bf7bb51653f09b96bafef221c51c2bfa' }, index.h("kritzel-dialog", { key: '49ffda0a422ae1c2c9c8e6e608bce532e7d4099e', isOpen: this.isDialogOpen, dialogTitle: this.t('settings.dialogTitle', 'Settings'), size: "large", contained: true, onDialogClose: this.closeDialog }, index.h("kritzel-master-detail", { key: 'ab5dc7952e555cae6f841ef4de2ab50d7039279b', items: this.categories, selectedItemId: this.selectedCategoryId, onItemSelect: this.handleCategorySelect }, this.renderCategoryContent()))));
30809
31237
  }
30810
31238
  static get watchers() { return {
30811
31239
  "settings": [{
@@ -30938,9 +31366,9 @@ const KritzelShareDialog = class {
30938
31366
  this.dialogClosed.emit();
30939
31367
  };
30940
31368
  render() {
30941
- return (index.h(index.Host, { key: 'b364f891659d54623b5472fe4cd5a9a85bc2fafa' }, index.h("kritzel-dialog", { key: '7059a3ede84bc67f7661efc8c84eae9b0c401d43', dialogTitle: this.terms['share.dialogTitle'] ?? 'Share Workspace', size: "small", isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, contained: true }, index.h("div", { key: '1b0e7a9b5483a53f91e874075c0edb116b7474d6', class: "share-content" }, index.h("div", { key: '1568dc99224f0f7e2a370c22327dc313515e7d0f', class: "share-section" }, index.h("div", { key: 'a0032f6c870a448e817b013a715aadca1c455bb3', class: "share-row" }, index.h("div", { key: '3bd74cbebc122133354465163f9945dd207245b5', class: "share-label-group" }, index.h("label", { key: '0919fc24e5637c908c53b6914983833b6600e846', class: "share-label" }, this.terms['share.linkSharing.label'] ?? 'Link sharing'), index.h("p", { key: 'f87c9eae7112d284d7d917fae7c9c05fa6feb0ed', class: "share-description" }, this.internalIsPublic
31369
+ return (index.h(index.Host, { key: '031cf5d1ab48f2791454d4c7348352295359d225' }, index.h("kritzel-dialog", { key: 'cdce63c86ca329a97d1f1787adcb2b92bbfa40f9', dialogTitle: this.terms['share.dialogTitle'] ?? 'Share Workspace', size: "small", isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, contained: true }, index.h("div", { key: 'dbb62b3cb175358955b11f6c2d74b0b0eecf212e', class: "share-content" }, index.h("div", { key: '8dc0eaea9644b60ff33812dac20def226fa14232', class: "share-section" }, index.h("div", { key: 'd3cfbf05c28a01681b1223edb6f0b73998ccfd08', class: "share-row" }, index.h("div", { key: '2400f062f92dd918341391cf71086c2676f7fc66', class: "share-label-group" }, index.h("label", { key: 'ab01691b9f53f59f92116a2994b6e3551d347c7b', class: "share-label" }, this.terms['share.linkSharing.label'] ?? 'Link sharing'), index.h("p", { key: 'fd830ad21e32e4fc5d8ba97d5cea7ef8da15fba4', class: "share-description" }, this.internalIsPublic
30942
31370
  ? (this.terms['share.linkSharing.enabledDescription'] ?? 'Anyone with the link can access this workspace.')
30943
- : (this.terms['share.linkSharing.disabledDescription'] ?? 'Link sharing is disabled. Only you can access this workspace.'))), index.h("kritzel-slide-toggle", { key: '81ccfb76255d40da954cfdcbc486bdbb7dc4bff4', checked: this.internalIsPublic, onCheckedChange: this.handleToggleChange, label: this.terms['share.linkSharing.toggleLabel'] ?? 'Enable link sharing' }))), this.internalIsPublic && (index.h("div", { key: 'c72da3550416ebfef5c24690591b212df70cb4db', class: "share-section" }, index.h("div", { key: 'c3dd4bce7d57e60d09989bff6dd1d828a613b425', class: "share-url-container" }, index.h("input", { key: '67aab8a65f54ea67392a9874e949d6d1af519e0f', type: "text", class: "share-url-input", value: this.getShareUrl(), readOnly: true, onClick: (e) => e.target.select() }), index.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') }, index.h("kritzel-icon", { key: '08ed01ed081e955e28eaed577438cd0008eaacf4', name: this.copySuccess ? 'check' : 'copy', size: 18 })))))))));
31371
+ : (this.terms['share.linkSharing.disabledDescription'] ?? 'Link sharing is disabled. Only you can access this workspace.'))), index.h("kritzel-slide-toggle", { key: '6f1a71ba524b34753ccd5437eab00bfcbeffdd79', checked: this.internalIsPublic, onCheckedChange: this.handleToggleChange, label: this.terms['share.linkSharing.toggleLabel'] ?? 'Enable link sharing' }))), this.internalIsPublic && (index.h("div", { key: '298680e1afdf0094c5393aa95c4983a0a8dd5e18', class: "share-section" }, index.h("div", { key: '546645a748cf9a42df0c8163f2478ef31445273a', class: "share-url-container" }, index.h("input", { key: '9d5b93d762d1cd899f3fc8041380bae603a4919c', type: "text", class: "share-url-input", value: this.getShareUrl(), readOnly: true, onClick: (e) => e.target.select() }), index.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') }, index.h("kritzel-icon", { key: '096ca9a0527796462769dc9d42dd81d8f97f2865', name: this.copySuccess ? 'check' : 'copy', size: 18 })))))))));
30944
31372
  }
30945
31373
  static get watchers() { return {
30946
31374
  "isPublic": [{
@@ -30978,7 +31406,7 @@ const KritzelSlideToggle = class {
30978
31406
  }
30979
31407
  };
30980
31408
  render() {
30981
- return (index.h(index.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 }, index.h("div", { key: '53fc90f3a11055e0b16358be0e3beed2ed25bc48', class: "toggle-track" }, index.h("div", { key: 'e0a0cecb9ede9c8e932cbb6fa7cdc359c07be8db', class: "toggle-thumb" }))));
31409
+ return (index.h(index.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 }, index.h("div", { key: 'df0adeca0447a13d5499219ac9d27b526252fdbe', class: "toggle-track" }, index.h("div", { key: '3938239bbdca29e62ed72229dfcf1904fcbaa983', class: "toggle-thumb" }))));
30982
31410
  }
30983
31411
  };
30984
31412
  KritzelSlideToggle.style = kritzelSlideToggleCss();
@@ -31078,7 +31506,7 @@ const KritzelSplitButton = class {
31078
31506
  this.menuScrollTop = event.target.scrollTop;
31079
31507
  };
31080
31508
  render() {
31081
- return (index.h(index.Host, { key: '39516b2486a27534273b6bf4def6b11790aa0ed5', class: { mobile: this.isTouchDevice } }, index.h("button", { key: '19c4a58deb3992857b0e42e4a035a15d0980da22', class: "split-main-button", tabIndex: 0, onClick: this.handleButtonClick, disabled: this.mainButtonDisabled, "aria-label": "Main action" }, this.buttonIcon && index.h("kritzel-icon", { key: '4c8e7e09e60717b1653251468c2a25d41c4b1921', name: this.buttonIcon })), index.h("div", { key: '53617db6b630dfb5320e7309dcbc9f7288ff8974', class: "split-divider" }), index.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" }, index.h("kritzel-icon", { key: '41f39a601ae91d4faaf54c1ca39c90e27e084d31', name: this.dropdownIcon })), index.h("kritzel-portal", { key: 'dcfe82f72a53e9346b48d2ff6da236c1f1344f3f', anchor: this.anchorElement, offsetY: 4, onClose: this.closeMenu }, index.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 }))));
31509
+ return (index.h(index.Host, { key: 'd6856a86b3be4d9a22c5e78df1bda19113cb988b', class: { mobile: this.isTouchDevice } }, index.h("button", { key: 'd26026af275a8bd5717254223d5829e3d7f02bcb', class: "split-main-button", tabIndex: 0, onClick: this.handleButtonClick, disabled: this.mainButtonDisabled, "aria-label": "Main action" }, this.buttonIcon && index.h("kritzel-icon", { key: '1b3e2f4c2f8245b281d0f85458fd6bcc0cc73c59', name: this.buttonIcon })), index.h("div", { key: 'b002b904e168d62075fa086c88df72e36cc8e8c2', class: "split-divider" }), index.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" }, index.h("kritzel-icon", { key: 'b1bc427b0d7cd26a283dbeb772dee0a6d2ca172c', name: this.dropdownIcon })), index.h("kritzel-portal", { key: 'e76300f48a23772a5923ea2cbd0824590302e7e7', anchor: this.anchorElement, offsetY: 4, onClose: this.closeMenu }, index.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 }))));
31082
31510
  }
31083
31511
  };
31084
31512
  KritzelSplitButton.style = kritzelSplitButtonCss();
@@ -31099,7 +31527,7 @@ const KritzelStrokeSize = class {
31099
31527
  }
31100
31528
  render() {
31101
31529
  const sizes = this.sizes ?? [];
31102
- return (index.h(index.Host, { key: '20c11daaf60055262706b8177b1090bddd2d522c' }, index.h("div", { key: '1729e29d836f85fe3be984a5ee78a0debc0e6218', class: "size-grid" }, sizes.map(size => (index.h("div", { tabIndex: 0, class: {
31530
+ return (index.h(index.Host, { key: 'bde2cc986aebbe869a3eea026654229dee0d3e0e' }, index.h("div", { key: 'f84c4b844b865080f4e0e245bbccc6d77ac3f049', class: "size-grid" }, sizes.map(size => (index.h("div", { tabIndex: 0, class: {
31103
31531
  'size-container': true,
31104
31532
  'selected': this.selectedSize === size,
31105
31533
  }, onClick: () => this.handleSizeClick(size) }, index.h("kritzel-color", { value: 'var(--kritzel-global-text-primary)', size: size })))))));
@@ -31480,14 +31908,14 @@ const KritzelTooltip = class {
31480
31908
  }
31481
31909
  }
31482
31910
  render() {
31483
- return (index.h(index.Host, { key: '57c0c2ddbe3f88815b975b7cf5b02c7da389b7f9', style: {
31911
+ return (index.h(index.Host, { key: 'ba4d13171aa25acac6c615ee12f35eab96d0aad6', style: {
31484
31912
  position: 'fixed',
31485
31913
  zIndex: '9999',
31486
31914
  transition: 'opacity 0.3s ease-in-out, transform 0.3s ease-in-out',
31487
31915
  visibility: this.isVisible ? 'visible' : 'hidden',
31488
31916
  left: `${this.positionX}px`,
31489
31917
  bottom: `${this.positionY}px`,
31490
- } }, index.h("div", { key: '3ab17ef4ba55ed39458814685574b0c00504a07d', class: "tooltip-content", onClick: event => event.stopPropagation(), onPointerDown: event => event.stopPropagation(), onMouseDown: event => event.stopPropagation() }, index.h("slot", { key: 'b0aa1eb2c9363644939bcc59a80d0294787f0946' }))));
31918
+ } }, index.h("div", { key: '923168164a018b08eac4246d88c7f8648b71d078', class: "tooltip-content", onClick: event => event.stopPropagation(), onPointerDown: event => event.stopPropagation(), onMouseDown: event => event.stopPropagation() }, index.h("slot", { key: 'aafad8ab026d591ebb9e2aeffa15d07718eba261' }))));
31491
31919
  }
31492
31920
  static get watchers() { return {
31493
31921
  "triggerElement": [{
@@ -31528,7 +31956,7 @@ const KritzelUtilityPanel = class {
31528
31956
  this.redo.emit();
31529
31957
  }
31530
31958
  render() {
31531
- return (index.h(index.Host, { key: '7738c225b6ec936e032770e0a7387b01718e94e0' }, index.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' }, index.h("kritzel-icon", { key: '3c7d87edc047113cac359f4782c7feeb53e18193', name: "undo" })), index.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' }, index.h("kritzel-icon", { key: 'b5ac0b631c51b8e6c2762b4887dcdef899327e52', name: "redo" })), index.h("div", { key: '4075c8eb7d8c57d5e93afb18282c7a50ec8e050e', class: "utility-separator" }), index.h("button", { key: '4e7c53a0933c527e84e0ad78519983446f10d23c', class: "utility-button", "data-testid": "utility-delete", onClick: () => this.delete.emit(), "aria-label": this.terms['utility.delete'] ?? 'Delete selected items' }, index.h("kritzel-icon", { key: 'f2d3d9817626c53e2e4c3d4c7c702705b25d7788', name: "delete" }))));
31959
+ return (index.h(index.Host, { key: '22f3826d57b4d8f0e476951a860bde3dbc6c7200' }, index.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' }, index.h("kritzel-icon", { key: 'ab4e9da6b60d9fa470fd8f5002f2acd5a758a8ba', name: "undo" })), index.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' }, index.h("kritzel-icon", { key: '7ae8cec39ffab1303d76315ab85ac575b886b233', name: "redo" })), index.h("div", { key: 'f54e0262f133ad0eed0bbf853b67edd6c2f5e87f', class: "utility-separator" }), index.h("button", { key: '162d6e85965338b5700e661595dc6da6131ebccf', class: "utility-button", "data-testid": "utility-delete", onClick: () => this.delete.emit(), "aria-label": this.terms['utility.delete'] ?? 'Delete selected items' }, index.h("kritzel-icon", { key: 'dc00247ed45f09b86ad9314395f56e70d1122a4b', name: "delete" }))));
31532
31960
  }
31533
31961
  };
31534
31962
  KritzelUtilityPanel.style = kritzelUtilityPanelCss();
@@ -31549,7 +31977,7 @@ const KritzelWatermark = class {
31549
31977
  label;
31550
31978
  render() {
31551
31979
  const label = this.label ?? this.core.localizationManager.translate('watermark.poweredBy');
31552
- return (index.h(index.Host, { key: '7ec2a304ac711a4d2a54edef317eef9f4aad1d47' }, index.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)));
31980
+ return (index.h(index.Host, { key: '975534180d82064e38fa0289a2955e97a4ca0a08' }, index.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)));
31553
31981
  }
31554
31982
  };
31555
31983
  KritzelWatermark.style = kritzelWatermarkCss();
@@ -31737,7 +32165,7 @@ const KritzelZoomPanel = class {
31737
32165
  return Number.isFinite(this.zoomPercent) ? Math.max(1, Math.round(this.zoomPercent)) : 100;
31738
32166
  }
31739
32167
  render() {
31740
- return (index.h(index.Host, { key: '6c88ed18f63e1a4cfffef6ba3aa1772404908d59' }, index.h("div", { key: '18fcdf05a6822d2b9cf319059118b4851429283d', class: { panel: true, visible: this.visible } }, index.h("button", { key: 'df2c90912e091e4f2ca025dd246916e9fea6f7fa', class: "zoom-button", type: "button", "aria-label": this.terms['zoom.zoomOut'] ?? 'Zoom out', disabled: this.disabled, onClick: this.handleZoomOut }, index.h("kritzel-icon", { key: '293847a34220520592e74f50c89d57140d7f1cbe', name: "minus" })), index.h("span", { key: '575119b81e6d5d41f5802a29be1b586727968919', class: "zoom-level", "aria-live": "polite" }, this.normalizedZoomPercent, "%"), index.h("button", { key: '8cdd7100aaeb313f2504351204ff512142414815', class: "zoom-button", type: "button", "aria-label": this.terms['zoom.zoomIn'] ?? 'Zoom in', disabled: this.disabled, onClick: this.handleZoomIn }, index.h("kritzel-icon", { key: 'b792164a29dd43ef601a4f469fed440ee1d72510', name: "plus" })))));
32168
+ return (index.h(index.Host, { key: '00d5c674581d4efa40da3a2ad5cbdd8295406590' }, index.h("div", { key: 'c7a4158f57b8062f37ee69f7b198442ec357d746', class: { panel: true, visible: this.visible } }, index.h("button", { key: 'e20344f5e522d481185a9dab7afdb2668cf44408', class: "zoom-button", type: "button", "aria-label": this.terms['zoom.zoomOut'] ?? 'Zoom out', disabled: this.disabled, onClick: this.handleZoomOut }, index.h("kritzel-icon", { key: '4fc1eeb78669894b0939a96c5e6945cea5056ac1', name: "minus" })), index.h("span", { key: 'ed26096e2ca76fe91b50d73ea6c9481996d01590', class: "zoom-level", "aria-live": "polite" }, this.normalizedZoomPercent, "%"), index.h("button", { key: '42609c6e8558b7d9e9e348b864639ead6c4cc9bb', class: "zoom-button", type: "button", "aria-label": this.terms['zoom.zoomIn'] ?? 'Zoom in', disabled: this.disabled, onClick: this.handleZoomIn }, index.h("kritzel-icon", { key: '3a321afb57f532ef867bae73751314f3fd355214', name: "plus" })))));
31741
32169
  }
31742
32170
  };
31743
32171
  KritzelZoomPanel.style = kritzelZoomPanelCss();
@@ -31770,6 +32198,7 @@ exports.kritzel_master_detail = KritzelMasterDetail;
31770
32198
  exports.kritzel_menu = KritzelMenu;
31771
32199
  exports.kritzel_menu_item = KritzelMenuItem;
31772
32200
  exports.kritzel_more_menu = KritzelMoreMenu;
32201
+ exports.kritzel_notification_card = KritzelNotificationCard;
31773
32202
  exports.kritzel_numeric_input = KritzelNumericInput;
31774
32203
  exports.kritzel_opacity_slider = KritzelOpacitySlider;
31775
32204
  exports.kritzel_pill_tabs = KritzelPillTabs;