kritzel-stencil 0.3.16 → 0.3.17

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 (227) hide show
  1. package/LICENSE.md +50 -0
  2. package/dist/cjs/index-Xav9JFHg.js +2 -2
  3. package/dist/cjs/index.cjs.js +7 -1
  4. package/dist/cjs/{kritzel-active-users_42.cjs.entry.js → kritzel-active-users_44.cjs.entry.js} +710 -145
  5. package/dist/cjs/loader.cjs.js +1 -1
  6. package/dist/cjs/{schema.constants-DJQTjcy7.js → schema.constants-DrHO_CYF.js} +1169 -171
  7. package/dist/cjs/stencil.cjs.js +1 -1
  8. package/dist/collection/classes/core/core.class.js +24 -0
  9. package/dist/collection/classes/handlers/context-menu.handler.js +24 -2
  10. package/dist/collection/classes/managers/license.manager.js +285 -0
  11. package/dist/collection/classes/managers/localization.manager.js +189 -0
  12. package/dist/collection/classes/objects/custom-element.class.js +2 -0
  13. package/dist/collection/classes/objects/group.class.js +7 -2
  14. package/dist/collection/classes/objects/image.class.js +10 -7
  15. package/dist/collection/classes/objects/line.class.js +3 -0
  16. package/dist/collection/classes/objects/path.class.js +13 -12
  17. package/dist/collection/classes/objects/selection-group.class.js +7 -2
  18. package/dist/collection/classes/objects/shape.class.js +3 -0
  19. package/dist/collection/classes/objects/text.class.js +4 -1
  20. package/dist/collection/classes/registries/icon-registry.class.js +1 -0
  21. package/dist/collection/classes/tools/brush-tool.class.js +1 -1
  22. package/dist/collection/collection-manifest.json +3 -1
  23. package/dist/collection/components/core/kritzel-editor/kritzel-editor.css +16 -0
  24. package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +462 -60
  25. package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +446 -16
  26. package/dist/collection/components/core/kritzel-watermark/kritzel-watermark.css +29 -0
  27. package/dist/collection/components/core/kritzel-watermark/kritzel-watermark.js +83 -0
  28. package/dist/collection/components/shared/kritzel-avatar/kritzel-avatar.js +3 -3
  29. package/dist/collection/components/shared/kritzel-button/kritzel-button.js +2 -2
  30. package/dist/collection/components/shared/kritzel-color/kritzel-color.js +2 -2
  31. package/dist/collection/components/shared/kritzel-color-palette/kritzel-color-palette.js +1 -1
  32. package/dist/collection/components/shared/kritzel-font/kritzel-font.js +1 -1
  33. package/dist/collection/components/shared/kritzel-font-size/kritzel-font-size.js +2 -1
  34. package/dist/collection/components/shared/kritzel-input/kritzel-input.js +1 -1
  35. package/dist/collection/components/shared/kritzel-master-detail/kritzel-master-detail.js +3 -3
  36. package/dist/collection/components/shared/kritzel-menu/kritzel-menu.js +1 -1
  37. package/dist/collection/components/shared/kritzel-menu-item/kritzel-menu-item.js +2 -2
  38. package/dist/collection/components/shared/kritzel-numeric-input/kritzel-numeric-input.js +1 -1
  39. package/dist/collection/components/shared/kritzel-opacity-slider/kritzel-opacity-slider.js +1 -1
  40. package/dist/collection/components/shared/kritzel-portal/kritzel-portal.js +1 -1
  41. package/dist/collection/components/shared/kritzel-slide-toggle/kritzel-slide-toggle.js +1 -1
  42. package/dist/collection/components/shared/kritzel-split-button/kritzel-split-button.js +1 -1
  43. package/dist/collection/components/shared/kritzel-stroke-size/kritzel-stroke-size.js +2 -1
  44. package/dist/collection/components/shared/kritzel-tooltip/kritzel-tooltip.js +2 -2
  45. package/dist/collection/components/ui/kritzel-back-to-content/kritzel-back-to-content.js +1 -1
  46. package/dist/collection/components/ui/kritzel-controls/kritzel-controls.js +41 -6
  47. package/dist/collection/components/ui/kritzel-current-user/kritzel-current-user.js +36 -1
  48. package/dist/collection/components/ui/kritzel-current-user-dialog/kritzel-current-user-dialog.js +36 -1
  49. package/dist/collection/components/ui/kritzel-export/kritzel-export.js +44 -7
  50. package/dist/collection/components/ui/kritzel-login-dialog/kritzel-login-dialog.js +1 -1
  51. package/dist/collection/components/ui/kritzel-more-menu/kritzel-more-menu.js +36 -1
  52. package/dist/collection/components/ui/kritzel-settings/kritzel-settings.js +108 -14
  53. package/dist/collection/components/ui/kritzel-share-dialog/kritzel-share-dialog.js +38 -3
  54. package/dist/collection/components/ui/kritzel-tool-config/kritzel-tool-config.js +38 -3
  55. package/dist/collection/components/ui/kritzel-utility-panel/kritzel-utility-panel.js +36 -1
  56. package/dist/collection/components/ui/kritzel-workspace-manager/kritzel-workspace-manager.js +38 -3
  57. package/dist/collection/components/ui/kritzel-zoom-panel/kritzel-zoom-panel.css +72 -0
  58. package/dist/collection/components/ui/kritzel-zoom-panel/kritzel-zoom-panel.js +173 -0
  59. package/dist/collection/constants/engine.constants.js +2 -0
  60. package/dist/collection/constants/license.constants.js +25 -0
  61. package/dist/collection/constants/version.js +1 -1
  62. package/dist/collection/helpers/localization.helper.js +25 -0
  63. package/dist/collection/helpers/math.helper.js +3 -0
  64. package/dist/collection/helpers/svg-export.helper.js +223 -26
  65. package/dist/collection/index.js +13 -0
  66. package/dist/collection/interfaces/localization.interface.js +1 -0
  67. package/dist/collection/locales/de-locale.js +119 -0
  68. package/dist/collection/locales/en-locale.js +120 -0
  69. package/dist/collection/locales/fr-locale.js +119 -0
  70. package/dist/collection/themes/dark-theme.js +18 -0
  71. package/dist/collection/themes/light-theme.js +18 -0
  72. package/dist/components/index.d.ts +4 -0
  73. package/dist/components/index.js +1 -1
  74. package/dist/components/kritzel-active-users.js +1 -1
  75. package/dist/components/kritzel-avatar.js +1 -1
  76. package/dist/components/kritzel-awareness-cursors.js +1 -1
  77. package/dist/components/kritzel-back-to-content.js +1 -1
  78. package/dist/components/kritzel-brush-style.js +1 -1
  79. package/dist/components/kritzel-button.js +1 -1
  80. package/dist/components/kritzel-color-palette.js +1 -1
  81. package/dist/components/kritzel-color.js +1 -1
  82. package/dist/components/kritzel-context-menu.js +1 -1
  83. package/dist/components/kritzel-controls.js +1 -1
  84. package/dist/components/kritzel-current-user-dialog.js +1 -1
  85. package/dist/components/kritzel-current-user.js +1 -1
  86. package/dist/components/kritzel-editor.js +1 -1
  87. package/dist/components/kritzel-engine.js +1 -1
  88. package/dist/components/kritzel-export.js +1 -1
  89. package/dist/components/kritzel-font-size.js +1 -1
  90. package/dist/components/kritzel-font.js +1 -1
  91. package/dist/components/kritzel-icon.js +1 -1
  92. package/dist/components/kritzel-input.js +1 -1
  93. package/dist/components/kritzel-login-dialog.js +1 -1
  94. package/dist/components/kritzel-master-detail.js +1 -1
  95. package/dist/components/kritzel-menu-item.js +1 -1
  96. package/dist/components/kritzel-menu.js +1 -1
  97. package/dist/components/kritzel-more-menu.js +1 -1
  98. package/dist/components/kritzel-numeric-input.js +1 -1
  99. package/dist/components/kritzel-opacity-slider.js +1 -1
  100. package/dist/components/kritzel-pill-tabs.js +1 -1
  101. package/dist/components/kritzel-portal.js +1 -1
  102. package/dist/components/kritzel-settings.js +1 -1
  103. package/dist/components/kritzel-share-dialog.js +1 -1
  104. package/dist/components/kritzel-slide-toggle.js +1 -1
  105. package/dist/components/kritzel-split-button.js +1 -1
  106. package/dist/components/kritzel-stroke-size.js +1 -1
  107. package/dist/components/kritzel-tool-config.js +1 -1
  108. package/dist/components/kritzel-tooltip.js +1 -1
  109. package/dist/components/kritzel-utility-panel.js +1 -1
  110. package/dist/components/kritzel-watermark.d.ts +11 -0
  111. package/dist/components/kritzel-watermark.js +1 -0
  112. package/dist/components/kritzel-workspace-manager.js +1 -1
  113. package/dist/components/kritzel-zoom-panel.d.ts +11 -0
  114. package/dist/components/kritzel-zoom-panel.js +1 -0
  115. package/dist/components/{p-B5xxfwKF.js → p-3HxnBrCM.js} +1 -1
  116. package/dist/components/p-6RjeGuvH.js +1 -0
  117. package/dist/components/p-7NsK0uHu.js +1 -0
  118. package/dist/components/{p-dcAernE1.js → p-BCNyR5Sw.js} +1 -1
  119. package/dist/components/{p-C2SX-XRr.js → p-BG6hOSrm.js} +1 -1
  120. package/dist/components/p-BKJSh8qQ.js +1 -0
  121. package/dist/components/{p-SptaSMno.js → p-BKvHg9cv.js} +1 -1
  122. package/dist/components/p-Bc55X65h.js +1 -0
  123. package/dist/components/p-BpnIvNvq.js +1 -0
  124. package/dist/components/p-BvRrA4hN.js +1 -0
  125. package/dist/components/{p-B2w8X7vn.js → p-BxpKq94F.js} +1 -1
  126. package/dist/components/{p-BFoK4W--.js → p-Bzv9Px8v.js} +1 -1
  127. package/dist/components/{p-COLHjboZ.js → p-C9HGoDHE.js} +1 -1
  128. package/dist/components/p-CEnEDaix.js +1 -0
  129. package/dist/components/p-CIcLzcfA.js +1 -0
  130. package/dist/components/p-CPtDfadX.js +1 -0
  131. package/dist/components/p-C_fKgKHu.js +9 -0
  132. package/dist/components/p-CdR76C4L.js +1 -0
  133. package/dist/components/p-Cu9KYyoq.js +1 -0
  134. package/dist/components/p-CyqRcqsO.js +1 -0
  135. package/dist/components/{p-UoPj5QjH.js → p-DDkmsPpV.js} +1 -1
  136. package/dist/components/{p-D-sRVAbQ.js → p-DI4vQRE3.js} +1 -1
  137. package/dist/components/{p-CJOhfMU5.js → p-DNdXJp8F.js} +1 -1
  138. package/dist/components/p-DX5K8xnh.js +1 -0
  139. package/dist/components/{p-DEy7zJCe.js → p-DZdgXCAx.js} +1 -1
  140. package/dist/components/p-DdH1cKED.js +1 -0
  141. package/dist/components/p-DdsSSqFY.js +1 -0
  142. package/dist/components/p-DgmtCdnL.js +1 -0
  143. package/dist/components/{p-BzYU3-MJ.js → p-DmWSRsjK.js} +1 -1
  144. package/dist/components/{p-Bj2laX89.js → p-Dz-Ti24X.js} +1 -1
  145. package/dist/components/{p-BiG1dxPS.js → p-F5_X4dZG.js} +1 -1
  146. package/dist/components/{p-x6doYeiI.js → p-IpoC5EEY.js} +1 -1
  147. package/dist/components/p-Jn6TNdfe.js +1 -0
  148. package/dist/components/{p-BfNHpqQ8.js → p-NuLP1xHe.js} +1 -1
  149. package/dist/components/{p-skWUIStn.js → p-SDZNC8GF.js} +1 -1
  150. package/dist/components/{p-BYmp9Ovv.js → p-U4oawa1x.js} +1 -1
  151. package/dist/components/{p-DM11KXUT.js → p-f8aW1ye7.js} +1 -1
  152. package/dist/components/p-v7dxxrL5.js +1 -0
  153. package/dist/components/p-vAeiXe6c.js +1 -0
  154. package/dist/esm/index-Dhio9uis.js +2 -2
  155. package/dist/esm/index.js +2 -2
  156. package/dist/esm/{kritzel-active-users_42.entry.js → kritzel-active-users_44.entry.js} +709 -146
  157. package/dist/esm/loader.js +1 -1
  158. package/dist/esm/{schema.constants-DiCnmIYK.js → schema.constants-DchTXG3V.js} +1163 -172
  159. package/dist/esm/stencil.js +1 -1
  160. package/dist/stencil/index.esm.js +1 -1
  161. package/dist/stencil/p-DchTXG3V.js +1 -0
  162. package/dist/stencil/p-c9a3807b.entry.js +9 -0
  163. package/dist/stencil/stencil.esm.js +1 -1
  164. package/dist/types/classes/core/core.class.d.ts +16 -0
  165. package/dist/types/classes/handlers/context-menu.handler.d.ts +13 -0
  166. package/dist/types/classes/managers/license.manager.d.ts +141 -0
  167. package/dist/types/classes/managers/localization.manager.d.ts +121 -0
  168. package/dist/types/classes/objects/custom-element.class.d.ts +2 -0
  169. package/dist/types/classes/objects/group.class.d.ts +6 -1
  170. package/dist/types/classes/objects/image.class.d.ts +1 -1
  171. package/dist/types/classes/objects/path.class.d.ts +3 -2
  172. package/dist/types/classes/objects/selection-group.class.d.ts +6 -1
  173. package/dist/types/classes/objects/shape.class.d.ts +2 -0
  174. package/dist/types/classes/objects/text.class.d.ts +2 -1
  175. package/dist/types/classes/tools/brush-tool.class.d.ts +1 -1
  176. package/dist/types/components/core/kritzel-editor/kritzel-editor.d.ts +53 -1
  177. package/dist/types/components/core/kritzel-engine/kritzel-engine.d.ts +55 -3
  178. package/dist/types/components/core/kritzel-watermark/kritzel-watermark.d.ts +20 -0
  179. package/dist/types/components/ui/kritzel-controls/kritzel-controls.d.ts +3 -0
  180. package/dist/types/components/ui/kritzel-current-user/kritzel-current-user.d.ts +3 -0
  181. package/dist/types/components/ui/kritzel-current-user-dialog/kritzel-current-user-dialog.d.ts +3 -0
  182. package/dist/types/components/ui/kritzel-export/kritzel-export.d.ts +4 -1
  183. package/dist/types/components/ui/kritzel-more-menu/kritzel-more-menu.d.ts +3 -0
  184. package/dist/types/components/ui/kritzel-settings/kritzel-settings.d.ts +16 -0
  185. package/dist/types/components/ui/kritzel-share-dialog/kritzel-share-dialog.d.ts +3 -0
  186. package/dist/types/components/ui/kritzel-tool-config/kritzel-tool-config.d.ts +3 -0
  187. package/dist/types/components/ui/kritzel-utility-panel/kritzel-utility-panel.d.ts +3 -0
  188. package/dist/types/components/ui/kritzel-workspace-manager/kritzel-workspace-manager.d.ts +3 -0
  189. package/dist/types/components/ui/kritzel-zoom-panel/kritzel-zoom-panel.d.ts +20 -0
  190. package/dist/types/components.d.ts +445 -26
  191. package/dist/types/constants/engine.constants.d.ts +2 -0
  192. package/dist/types/constants/license.constants.d.ts +25 -0
  193. package/dist/types/constants/version.d.ts +1 -1
  194. package/dist/types/helpers/localization.helper.d.ts +18 -0
  195. package/dist/types/helpers/math.helper.d.ts +1 -0
  196. package/dist/types/helpers/svg-export.helper.d.ts +81 -7
  197. package/dist/types/index.d.ts +13 -0
  198. package/dist/types/interfaces/context-menu-item.interface.d.ts +7 -1
  199. package/dist/types/interfaces/line-options.interface.d.ts +2 -0
  200. package/dist/types/interfaces/localization.interface.d.ts +143 -0
  201. package/dist/types/interfaces/path-options.interface.d.ts +2 -0
  202. package/dist/types/interfaces/settings.interface.d.ts +3 -0
  203. package/dist/types/interfaces/theme.interface.d.ts +27 -2
  204. package/dist/types/locales/de-locale.d.ts +5 -0
  205. package/dist/types/locales/en-locale.d.ts +6 -0
  206. package/dist/types/locales/fr-locale.d.ts +5 -0
  207. package/package.json +4 -7
  208. package/dist/components/p-2xYAGd0I.js +0 -1
  209. package/dist/components/p-B2Os1ya_.js +0 -1
  210. package/dist/components/p-BTEV1WwT.js +0 -1
  211. package/dist/components/p-BbactVA0.js +0 -1
  212. package/dist/components/p-BqwqGFQY.js +0 -1
  213. package/dist/components/p-C0TN5IAi.js +0 -1
  214. package/dist/components/p-CFgkUYoO.js +0 -1
  215. package/dist/components/p-COgo9OWy.js +0 -1
  216. package/dist/components/p-CUPYGT8c.js +0 -1
  217. package/dist/components/p-CcyIAi9S.js +0 -1
  218. package/dist/components/p-Cj78L1Kk.js +0 -1
  219. package/dist/components/p-CkAVEdDw.js +0 -9
  220. package/dist/components/p-CmuNn1Tc.js +0 -1
  221. package/dist/components/p-DDYoDSrm.js +0 -1
  222. package/dist/components/p-DbB730vO.js +0 -1
  223. package/dist/components/p-DlwYHzSj.js +0 -1
  224. package/dist/components/p-FK7b3BGt.js +0 -1
  225. package/dist/components/p-J9_SwObO.js +0 -1
  226. package/dist/stencil/p-67775031.entry.js +0 -9
  227. package/dist/stencil/p-DiCnmIYK.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-DJQTjcy7.js');
4
+ var schema_constants = require('./schema.constants-DrHO_CYF.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: '571bd5b92adc7c65b96ded37b8daf5ed79905361', style: containerStyles, class: {
157
+ return (index.h(index.Host, { key: '0d372a5443f41835c2e8e5b33b58bcb6c1292e89', 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: '1065850b4575fda4637ab61ce07c6dfc97f14a90', src: imageUrl, alt: "", class: "avatar-image", ref: (el) => {
161
+ }, role: "img", "aria-label": this.getDisplayName() || 'User avatar' }, showImage && (index.h("img", { key: 'deb5f12115dd28b4b4ab2157cb2bbc9e48bb2a3f', 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: 'a6d9c9dd2eac6e44c731a878e2460017da7fb0b7', class: "avatar-initials", style: { backgroundColor: this.getBackgroundColor() } }, initials)), showDefaultIcon && (index.h("span", { key: '9e9d33cdd213649071b76cb0875008562b30f6a1', class: "avatar-default" }, index.h("svg", { key: '5d0be5c503a8944b45de239f08e6f40378c2dc5e', viewBox: "0 0 24 24", fill: "currentColor" }, index.h("path", { key: '9264549c9b8abbdea74cd707d6f77bcfdde6459d', 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: '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" }))))));
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: 'b623a9a4e4b8fce50346771488a59c3a646c289e' }, index.h("button", { key: 'b2f6b257975639d33362f1038b61e5147963f189', class: { 'back-to-content-button': true, visible: this.visible }, onClick: this.handleClick, "aria-label": this.text }, index.h("kritzel-icon", { key: '22f34fc201a865b6d9b21775a349d0e185727d48', name: "chevrons-left" }))));
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" }))));
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: 'c915db75630392741de404f07265a391330e54ca' }, index.h("button", { key: '50248ee1ed5862c9ea72b4f7cf7d564d03b5b14e', type: this.type, class: {
503
+ return (index.h(index.Host, { key: 'aa366907b97e648ac73182a43d099baf457e0ba3' }, index.h("button", { key: 'e80875396a791606461b1a20e0f7e1c896f54073', 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: 'd595cd819c7c0a3550e468ce65d4e2c28cc02164' }))));
507
+ }, disabled: this.disabled, onClick: this.handleClick }, index.h("slot", { key: '8f4e57a73837adfbf91f678c54558fe4c737aa91' }))));
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: 'c4c1fe2559aca61557ff2e8154f4d46ce3511b30' }, index.h("div", { key: '158c243018763a9609e0a056229263864a5e4d13', class: "checkerboard-bg", style: {
555
+ return (index.h(index.Host, { key: '21ce0ecb616266ec4953468ae9bbe0b72259c2da' }, index.h("div", { key: '02c8e8684b3b0819755efd1f160bdee662428ddd', 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: 'afa21c72b17ab5fb4b16521b91dbe7e9162d05f8', class: {
561
+ } }, index.h("div", { key: '0063e9a701994869adbd3022b5ace542e85dfd95', 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: 'fc57d77d7c4cfd2aa2a02a70b8991858bb8cf61b' }, index.h("div", { key: '4fd10783609882f453ce95f5114acf799f21ec52', class: {
617
+ return (index.h(index.Host, { key: '9910a765816f45d420ad27c8fc7b02811380783b' }, index.h("div", { key: '91f3dae04338f55ab21831e4cac65f9d855c58c6', class: {
618
618
  'color-grid': true,
619
619
  'expanded': this.isExpanded,
620
620
  }, style: {
@@ -961,6 +961,8 @@ const KritzelControls = class {
961
961
  isUtilityPanelVisible = true;
962
962
  undoState = null;
963
963
  theme = 'light';
964
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
965
+ terms = {};
964
966
  isControlsReady;
965
967
  firstConfig = null;
966
968
  isTouchDevice = schema_constants.KritzelDevicesHelper.isTouchDevice();
@@ -1180,13 +1182,13 @@ const KritzelControls = class {
1180
1182
  // Separate tool controls from config control
1181
1183
  const toolControls = this.internalControls.filter(c => c.type === 'tool' || c.type === 'separator');
1182
1184
  const configControl = this.internalControls.find(c => c.type === 'config' && c.name === this.firstConfig?.name);
1183
- return (index.h(index.Host, { key: '0f40a136a6a9556080d922d346318045794421a8', style: { display: this.visible ? '' : 'none' }, class: {
1185
+ return (index.h(index.Host, { key: '7247a377d1cb75153e35f5308e81b2bd00d98578', style: { display: this.visible ? '' : 'none' }, class: {
1184
1186
  mobile: this.isTouchDevice,
1185
- } }, this.isUtilityPanelVisible && (index.h("kritzel-utility-panel", { key: 'd543e7575cb30e54d9362eddf7c7221fb8cce5f5', style: {
1187
+ } }, this.isUtilityPanelVisible && (index.h("kritzel-utility-panel", { key: '075577cd15f3e577a1a216b5b1f1874e82e0d123', style: {
1186
1188
  position: 'absolute',
1187
1189
  bottom: '56px',
1188
1190
  left: '12px',
1189
- }, undoState: this.undoState, onUndo: () => this.kritzelEngine?.undo(), onRedo: () => this.kritzelEngine?.redo(), onDelete: () => this.kritzelEngine?.delete() })), index.h("div", { key: '1083380152e9d1b51c35da335533c20ca2ca8fcc', class: "kritzel-controls" }, index.h("div", { key: '11ef0fb76c30ffda0e30f01d43229ca2142a5854', class: { 'scroll-indicator-left': true, 'visible': this.canScrollLeft } }), index.h("div", { key: '269924a8859aece37ff31202307d4feaecbbde86', 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: '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 => {
1190
1192
  // Check if this control has sub-options (split-button)
1191
1193
  if (control.subOptions?.length) {
1192
1194
  const selectedSubOption = this.getSelectedSubOption(control);
@@ -1216,10 +1218,10 @@ const KritzelControls = class {
1216
1218
  'kritzel-control': true,
1217
1219
  'selected': this.activeControl?.name === control?.name,
1218
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 })));
1219
- })), index.h("div", { key: '8b97a5bba3ac4992482e8f433d7ba6197918a914', class: { 'scroll-indicator-right': true, 'visible': this.canScrollRight && !(configControl && this.activeControl && hasConfigUI) } }), configControl && this.activeControl && (index.h("div", { class: {
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: {
1220
1222
  'kritzel-config-container': true,
1221
1223
  'visible': hasConfigUI,
1222
- }, key: configControl.name }, index.h("div", { key: 'd9adef8c2acc8d9b9d745174050ce78960b89b58', class: { 'config-gradient-left': true, 'visible': this.needsScrolling } }), index.h("kritzel-tooltip", { key: '7605bbd2b6335c89c57aa68952293f26efad6b4b', anchorElement: this.host.shadowRoot?.querySelector('.kritzel-config-container'), triggerElement: this.configTriggerRef }, index.h("kritzel-tool-config", { key: '27b0f41442215e78b692fa44bb665444a4993b89', tool: this.activeControl.tool, theme: this.theme, engine: this.kritzelEngine, onToolChange: event => this.handleToolChange?.(event), onDisplayValuesChange: this.handleDisplayValuesChange, style: { width: '100%', height: '100%' } })), index.h("div", { key: '1fb5979b1c531593acf5086861b22b7d78d03e8d', tabIndex: hasConfigUI ? 0 : -1, class: "kritzel-config", "data-testid": "tool-config", ref: el => {
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 => {
1223
1225
  if (el)
1224
1226
  this.configTriggerRef = el;
1225
1227
  }, onKeyDown: event => {
@@ -1228,7 +1230,7 @@ const KritzelControls = class {
1228
1230
  }
1229
1231
  }, style: {
1230
1232
  cursor: 'pointer',
1231
- } }, this.displayValues && (index.h("div", { key: '3713ef344630f6b4d88df2e83992018859ddb18c', class: "color-container" }, index.h("kritzel-color", { key: 'c2679f68efae77c6daeb98be9e03d5320d51a73a', value: this.displayValues.color, theme: this.theme, size: 18, style: {
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: {
1232
1234
  borderRadius: '50%',
1233
1235
  border: 'none',
1234
1236
  } })))))))));
@@ -1260,13 +1262,15 @@ const KritzelCurrentUser = class {
1260
1262
  * Avatar size in pixels
1261
1263
  */
1262
1264
  avatarSize = 40;
1265
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
1266
+ terms = {};
1263
1267
  dialogRef;
1264
1268
  handleAvatarClick = (event) => {
1265
1269
  event.stopPropagation();
1266
1270
  this.dialogRef?.open();
1267
1271
  };
1268
1272
  render() {
1269
- return (index.h(index.Host, { key: 'a735cb9f16f4898fde0b52573affa2d270a8f1de' }, index.h("kritzel-avatar", { key: 'd449a515182718ab4ef3b26b2277696bbc7ab46f', user: this.user, size: this.avatarSize, onClick: this.handleAvatarClick }), index.h("kritzel-current-user-dialog", { key: '3542f6df43c9924218e344f70bdc398c74a8eae6', ref: el => (this.dialogRef = el), user: this.user })));
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 })));
1270
1274
  }
1271
1275
  };
1272
1276
  KritzelCurrentUser.style = kritzelCurrentUserCss();
@@ -1279,6 +1283,8 @@ const KritzelCurrentUserDialog = class {
1279
1283
  }
1280
1284
  get host() { return index.getElement(this); }
1281
1285
  user;
1286
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
1287
+ terms = {};
1282
1288
  isDialogOpen = false;
1283
1289
  async open() {
1284
1290
  this.isDialogOpen = true;
@@ -1298,7 +1304,7 @@ const KritzelCurrentUserDialog = class {
1298
1304
  }
1299
1305
  render() {
1300
1306
  const displayName = this.getDisplayName();
1301
- return (index.h(index.Host, { key: '40c1a1bed0ddf02f9835199b5f7d2363e4d1902b' }, index.h("kritzel-dialog", { key: 'a83c09eac66ddf51155591a32245e3f15e34943e', dialogTitle: "Account", isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, size: "small", contained: true }, index.h("div", { key: '14f7100a881ee3c5ba6b672d509bf3a9161ccd62', class: "user-info" }, index.h("kritzel-avatar", { key: 'e3552a80db81db4c26f81c6cc699363afa6153ea', user: this.user, size: 80 }), displayName && index.h("div", { key: 'c54164be605ac2bd2fc8bac6bb4481f820119028', class: "user-name" }, displayName), this.user?.email && index.h("div", { key: 'e6af7c44e45443eb24be0777768de96b0e3d249e', class: "user-email" }, this.user.email)))));
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)))));
1302
1308
  }
1303
1309
  };
1304
1310
  KritzelCurrentUserDialog.style = kritzelCurrentUserDialogCss();
@@ -2177,6 +2183,8 @@ const DEFAULT_SHAPE_CONFIG = {
2177
2183
 
2178
2184
  const ABSOLUTE_SCALE_MAX = 1000;
2179
2185
  const ABSOLUTE_SCALE_MIN = 0.0001;
2186
+ /** Destination opened when the "Powered by Kritzel" watermark is clicked. */
2187
+ const KRITZEL_WEBSITE_URL = 'https://kritzel.io';
2180
2188
 
2181
2189
  /**
2182
2190
  * Default sync configuration - None
@@ -2185,7 +2193,7 @@ const DEFAULT_SYNC_CONFIG = {
2185
2193
  providers: [],
2186
2194
  };
2187
2195
 
2188
- const kritzelEditorCss = () => `kritzel-editor{display:flex;margin:0;position:relative;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}.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-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}`;
2189
2197
 
2190
2198
  const KritzelEditor = class {
2191
2199
  constructor(hostRef) {
@@ -2198,6 +2206,7 @@ const KritzelEditor = class {
2198
2206
  this.objectsUpdated = index.createEvent(this, "objectsUpdated");
2199
2207
  this.undoStateChange = index.createEvent(this, "undoStateChange");
2200
2208
  this.themeChange = index.createEvent(this, "themeChange");
2209
+ this.localeChange = index.createEvent(this, "localeChange");
2201
2210
  this.viewportChange = index.createEvent(this, "viewportChange");
2202
2211
  this.logout = index.createEvent(this, "logout");
2203
2212
  this.login = index.createEvent(this, "login");
@@ -2212,7 +2221,6 @@ const KritzelEditor = class {
2212
2221
  viewportBoundaryRight = Infinity;
2213
2222
  viewportBoundaryTop = -Infinity;
2214
2223
  viewportBoundaryBottom = Infinity;
2215
- wheelEnabled = true;
2216
2224
  debugInfo = {
2217
2225
  showViewportInfo: false,
2218
2226
  showObjectInfo: false,
@@ -2281,66 +2289,66 @@ const KritzelEditor = class {
2281
2289
  ];
2282
2290
  globalContextMenuItems = [
2283
2291
  {
2284
- label: 'Paste',
2292
+ label: 'menu.paste',
2285
2293
  icon: 'paste',
2286
2294
  disabled: async () => (await this.engineRef.getCopiedObjects()).length === 0,
2287
2295
  action: menu => this.engineRef.paste(menu.x, menu.y),
2288
2296
  },
2289
2297
  {
2290
- label: 'Select All',
2298
+ label: 'menu.selectAll',
2291
2299
  icon: 'select-all',
2292
2300
  disabled: async () => (await this.engineRef.getObjectsInViewport()).length === 0,
2293
2301
  action: () => this.selectAllObjectsInViewport(),
2294
2302
  },
2295
2303
  ];
2296
2304
  objectContextMenuItems = [
2297
- { label: 'Copy', icon: 'copy', group: 'clipboard', action: () => this.engineRef.copy() },
2298
- { label: 'Cut', icon: 'cut', group: 'clipboard', action: () => this.engineRef.cut() },
2305
+ { label: 'menu.copy', icon: 'copy', group: 'clipboard', action: () => this.engineRef.copy() },
2306
+ { label: 'menu.cut', icon: 'cut', group: 'clipboard', action: () => this.engineRef.cut() },
2299
2307
  {
2300
- label: 'Paste',
2308
+ label: 'menu.paste',
2301
2309
  icon: 'paste',
2302
2310
  group: 'clipboard',
2303
2311
  disabled: async () => (await this.engineRef.getCopiedObjects()).length === 0,
2304
2312
  action: (menu, _) => this.engineRef.paste(menu.x, menu.y),
2305
2313
  },
2306
2314
  {
2307
- label: 'Order',
2315
+ label: 'menu.order',
2308
2316
  icon: 'ordering',
2309
2317
  group: 'other',
2310
2318
  children: [
2311
- { label: 'Bring to Front', icon: 'bring-to-front', action: () => this.engineRef.bringToFront() },
2312
- { label: 'Send to Back', icon: 'send-to-back', action: () => this.engineRef.sendToBack() },
2313
- { label: 'Move Up', icon: 'arrow-up-from-dot', action: () => this.engineRef.bringForward() },
2314
- { label: 'Move Down', icon: 'arrow-down-from-dot', action: () => this.engineRef.sendBackward() },
2319
+ { label: 'menu.bringToFront', icon: 'bring-to-front', action: () => this.engineRef.bringToFront() },
2320
+ { label: 'menu.sendToBack', icon: 'send-to-back', action: () => this.engineRef.sendToBack() },
2321
+ { label: 'menu.moveUp', icon: 'arrow-up-from-dot', action: () => this.engineRef.bringForward() },
2322
+ { label: 'menu.moveDown', icon: 'arrow-down-from-dot', action: () => this.engineRef.sendBackward() },
2315
2323
  ],
2316
2324
  },
2317
2325
  {
2318
- label: 'Align',
2326
+ label: 'menu.align',
2319
2327
  icon: 'align',
2320
2328
  group: 'other',
2321
2329
  disabled: async () => (await this.engineRef.getSelectedObjects()).length < 2,
2322
2330
  children: [
2323
- { label: 'Align Left', icon: 'align-start-vertical', action: () => this.engineRef.alignObjects(schema_constants.KritzelAlignment.StartHorizontal) },
2324
- { label: 'Align Center Horizontally', icon: 'align-center-horizontal', action: () => this.engineRef.alignObjects(schema_constants.KritzelAlignment.CenterHorizontal) },
2325
- { label: 'Align Right', icon: 'align-end-vertical', action: () => this.engineRef.alignObjects(schema_constants.KritzelAlignment.EndHorizontal) },
2326
- { label: 'Align Top', icon: 'align-start-horizontal', action: () => this.engineRef.alignObjects(schema_constants.KritzelAlignment.StartVertical) },
2327
- { label: 'Align Center Vertically', icon: 'align-center-vertical', action: () => this.engineRef.alignObjects(schema_constants.KritzelAlignment.CenterVertical) },
2328
- { label: 'Align Bottom', icon: 'align-end-horizontal', action: () => this.engineRef.alignObjects(schema_constants.KritzelAlignment.EndVertical) },
2331
+ { label: 'menu.alignLeft', icon: 'align-start-vertical', action: () => this.engineRef.alignObjects(schema_constants.KritzelAlignment.StartHorizontal) },
2332
+ { label: 'menu.alignCenterHorizontal', icon: 'align-center-horizontal', action: () => this.engineRef.alignObjects(schema_constants.KritzelAlignment.CenterHorizontal) },
2333
+ { label: 'menu.alignRight', icon: 'align-end-vertical', action: () => this.engineRef.alignObjects(schema_constants.KritzelAlignment.EndHorizontal) },
2334
+ { label: 'menu.alignTop', icon: 'align-start-horizontal', action: () => this.engineRef.alignObjects(schema_constants.KritzelAlignment.StartVertical) },
2335
+ { label: 'menu.alignCenterVertical', icon: 'align-center-vertical', action: () => this.engineRef.alignObjects(schema_constants.KritzelAlignment.CenterVertical) },
2336
+ { label: 'menu.alignBottom', icon: 'align-end-horizontal', action: () => this.engineRef.alignObjects(schema_constants.KritzelAlignment.EndVertical) },
2329
2337
  ],
2330
2338
  },
2331
2339
  {
2332
- label: 'Group',
2340
+ label: 'menu.group',
2333
2341
  icon: 'group',
2334
2342
  group: 'other',
2335
2343
  children: [
2336
2344
  {
2337
- label: 'Group',
2345
+ label: 'menu.group',
2338
2346
  icon: 'group',
2339
2347
  disabled: async () => (await this.engineRef.getSelectedObjects()).length < 2,
2340
2348
  action: () => this.engineRef.group(),
2341
2349
  },
2342
2350
  {
2343
- label: 'Ungroup',
2351
+ label: 'menu.ungroup',
2344
2352
  icon: 'ungroup',
2345
2353
  disabled: async () => {
2346
2354
  const selectedObjects = await this.engineRef.getSelectedObjects();
@@ -2351,23 +2359,34 @@ const KritzelEditor = class {
2351
2359
  ],
2352
2360
  },
2353
2361
  {
2354
- label: 'Export',
2362
+ label: 'menu.export',
2355
2363
  icon: 'download',
2356
2364
  group: 'export',
2357
2365
  children: [
2358
- { label: 'Export as SVG', icon: 'download', action: () => this.engineRef.exportSelectedObjectsAsSvg() },
2359
- { label: 'Export as PNG', icon: 'download', action: () => this.engineRef.exportSelectedObjectsAsPng() },
2366
+ { label: 'menu.exportAsSvg', icon: 'download', action: () => this.engineRef.exportSelectedObjectsAsSvg() },
2367
+ { label: 'menu.exportAsPng', icon: 'download', action: () => this.engineRef.exportSelectedObjectsAsPng() },
2360
2368
  ],
2361
2369
  },
2362
- { label: 'Delete', icon: 'delete', group: 'edit', action: () => this.engineRef.delete() },
2370
+ { label: 'menu.delete', icon: 'delete', group: 'edit', action: () => this.engineRef.delete() },
2363
2371
  ];
2364
2372
  themes;
2365
2373
  theme = 'light';
2374
+ /** License key that, when valid, removes the "Powered by Kritzel" watermark. */
2375
+ licenseKey;
2376
+ /** The current locale (language) code applied to the editor, e.g. 'en', 'de', 'fr'. */
2377
+ locale = 'en';
2378
+ /** An array of available locale definitions (with optional partial term overrides). */
2379
+ locales;
2380
+ /** The locale used to resolve terms missing from the active locale. */
2381
+ fallbackLocale = 'en';
2366
2382
  customSvgIcons = {};
2383
+ isPanningEnabled = true;
2384
+ isZoomingEnabled = true;
2367
2385
  isControlsVisible = true;
2368
2386
  isUtilityPanelVisible = true;
2369
2387
  isWorkspaceManagerVisible = true;
2370
2388
  isMoreMenuVisible = true;
2389
+ isZoomPanelVisible = true;
2371
2390
  isObjectDistanceFadingActive = false;
2372
2391
  syncConfig = DEFAULT_SYNC_CONFIG;
2373
2392
  assetStorageConfig = schema_constants.DEFAULT_ASSET_STORAGE_CONFIG;
@@ -2389,6 +2408,7 @@ const KritzelEditor = class {
2389
2408
  objectsUpdated;
2390
2409
  undoStateChange;
2391
2410
  themeChange;
2411
+ localeChange;
2392
2412
  viewportChange;
2393
2413
  logout;
2394
2414
  login;
@@ -2402,6 +2422,11 @@ const KritzelEditor = class {
2402
2422
  isVirtualKeyboardOpen = false;
2403
2423
  undoState = null;
2404
2424
  isBackToContentButtonVisible = false;
2425
+ /** Localized strings for editor-owned UI (e.g. the more menu), resolved from the active locale. */
2426
+ resolvedTerms = {};
2427
+ /** Available locales as `{ code, label }` options for the settings language selector. */
2428
+ availableLocaleOptions = [];
2429
+ currentZoomPercent = 100;
2405
2430
  shortcuts = [];
2406
2431
  currentIsPublic = false;
2407
2432
  isEditorVisible = false;
@@ -2465,6 +2490,25 @@ const KritzelEditor = class {
2465
2490
  onThemesChange() {
2466
2491
  this.applyTheme();
2467
2492
  }
2493
+ onLocaleChange(newValue) {
2494
+ if (this.engineRef) {
2495
+ this.engineRef.setLocale(newValue);
2496
+ this.engineRef.saveSettings(this.currentSettingsConfig);
2497
+ void this.refreshLocalizedTerms();
2498
+ }
2499
+ }
2500
+ /**
2501
+ * Refreshes the editor-owned localized strings (e.g. the more menu and the
2502
+ * strings forwarded to child UI components) from the engine's localization
2503
+ * manager for the active locale.
2504
+ */
2505
+ async refreshLocalizedTerms() {
2506
+ if (!this.engineRef) {
2507
+ return;
2508
+ }
2509
+ this.resolvedTerms = await this.engineRef.getResolvedTerms();
2510
+ this.availableLocaleOptions = await this.engineRef.getAvailableLocaleOptions();
2511
+ }
2468
2512
  onTouchStart(event) {
2469
2513
  if (event.cancelable) {
2470
2514
  event.preventDefault();
@@ -2723,6 +2767,9 @@ const KritzelEditor = class {
2723
2767
  this.activeWorkspace = event.detail.activeWorkspace;
2724
2768
  this.workspaces = event.detail.workspaces;
2725
2769
  this.currentIsPublic = await this.engineRef.getIsPublic();
2770
+ await this.refreshLocalizedTerms();
2771
+ const viewport = await this.engineRef.getViewport();
2772
+ this.currentZoomPercent = this.getZoomPercentFromScale(viewport.scale);
2726
2773
  this.loadShortcuts();
2727
2774
  }
2728
2775
  handleWorkspacesChange(event) {
@@ -2773,8 +2820,15 @@ const KritzelEditor = class {
2773
2820
  }
2774
2821
  handleViewportChange(event) {
2775
2822
  event.stopPropagation();
2823
+ this.currentZoomPercent = this.getZoomPercentFromScale(event.detail.scale);
2776
2824
  this.viewportChange.emit(event.detail);
2777
2825
  }
2826
+ getZoomPercentFromScale(scale) {
2827
+ if (!Number.isFinite(scale) || scale <= 0) {
2828
+ return 100;
2829
+ }
2830
+ return Math.round(scale * 100);
2831
+ }
2778
2832
  handleAwarenessChange(event) {
2779
2833
  event.stopPropagation();
2780
2834
  this.awarenessChange.emit(event.detail);
@@ -2784,6 +2838,10 @@ const KritzelEditor = class {
2784
2838
  this.scaleMax = event.detail.scaleMax;
2785
2839
  this.lockDrawingScale = event.detail.lockDrawingScale;
2786
2840
  this.theme = event.detail.theme;
2841
+ if (typeof event.detail.locale === 'string' && event.detail.locale !== this.locale) {
2842
+ this.locale = event.detail.locale;
2843
+ this.localeChange.emit(event.detail.locale);
2844
+ }
2787
2845
  this.viewportBoundaryLeft = event.detail.viewportBoundaryLeft ?? -Infinity;
2788
2846
  this.viewportBoundaryRight = event.detail.viewportBoundaryRight ?? Infinity;
2789
2847
  this.viewportBoundaryTop = event.detail.viewportBoundaryTop ?? -Infinity;
@@ -2798,7 +2856,7 @@ const KritzelEditor = class {
2798
2856
  return [
2799
2857
  {
2800
2858
  id: 'share',
2801
- label: 'Share',
2859
+ label: this.resolvedTerms['menu.share'] ?? 'Share',
2802
2860
  icon: 'share',
2803
2861
  action: () => {
2804
2862
  if (!this.isLoggedIn && this.loginConfig) {
@@ -2810,7 +2868,7 @@ const KritzelEditor = class {
2810
2868
  },
2811
2869
  {
2812
2870
  id: 'export',
2813
- label: 'Export',
2871
+ label: this.resolvedTerms['menu.export'] ?? 'Export',
2814
2872
  icon: 'upload',
2815
2873
  action: async () => {
2816
2874
  const preview = await this.engineRef.getScreenshot('png');
@@ -2819,19 +2877,19 @@ const KritzelEditor = class {
2819
2877
  },
2820
2878
  {
2821
2879
  id: 'import',
2822
- label: 'Import',
2880
+ label: this.resolvedTerms['menu.import'] ?? 'Import',
2823
2881
  icon: 'download',
2824
2882
  action: () => this.engineRef.importFromFile(),
2825
2883
  },
2826
2884
  {
2827
2885
  id: 'settings',
2828
- label: 'Settings',
2886
+ label: this.resolvedTerms['menu.settings'] ?? 'Settings',
2829
2887
  icon: 'settings',
2830
2888
  action: () => this.settingsRef.open(),
2831
2889
  },
2832
2890
  {
2833
2891
  id: 'logout',
2834
- label: 'Logout',
2892
+ label: this.resolvedTerms['menu.logout'] ?? 'Logout',
2835
2893
  icon: 'log-out',
2836
2894
  color: '#ff3b30',
2837
2895
  isVisible: this.isLoggedIn,
@@ -2871,6 +2929,41 @@ const KritzelEditor = class {
2871
2929
  async setLoginLoading(provider) {
2872
2930
  this.loginDialogRef?.setLoading(provider);
2873
2931
  }
2932
+ /**
2933
+ * Sets the active locale (language) and re-renders the UI.
2934
+ * @param code - The locale code to activate, e.g. 'de'.
2935
+ */
2936
+ async setLocale(code) {
2937
+ this.locale = code;
2938
+ await this.engineRef?.setLocale(code);
2939
+ }
2940
+ /**
2941
+ * Gets the currently active locale code.
2942
+ */
2943
+ async getLocale() {
2944
+ return this.engineRef ? this.engineRef.getLocale() : this.locale;
2945
+ }
2946
+ /**
2947
+ * Gets the list of available locale codes (built-in and registered).
2948
+ */
2949
+ async getAvailableLocales() {
2950
+ return this.engineRef ? this.engineRef.getAvailableLocales() : [];
2951
+ }
2952
+ /**
2953
+ * Registers additional locale definitions (with optional partial term overrides).
2954
+ * @param locales - The locale definitions to register.
2955
+ */
2956
+ async registerLocales(locales) {
2957
+ await this.engineRef?.registerLocales(locales);
2958
+ }
2959
+ /**
2960
+ * Resolves a term key to its translated string for the active locale.
2961
+ * @param key - The term key to resolve.
2962
+ * @param vars - Optional values for `{placeholder}` interpolation.
2963
+ */
2964
+ async t(key, vars) {
2965
+ return this.engineRef ? this.engineRef.t(key, vars) : key;
2966
+ }
2874
2967
  getSettingsStorageKey() {
2875
2968
  return this.editorId ? `kritzel-settings-${this.editorId}` : 'kritzel-settings';
2876
2969
  }
@@ -2891,6 +2984,9 @@ const KritzelEditor = class {
2891
2984
  if (typeof parsed.theme === 'string') {
2892
2985
  this.theme = parsed.theme;
2893
2986
  }
2987
+ if (typeof parsed.locale === 'string') {
2988
+ this.locale = parsed.locale;
2989
+ }
2894
2990
  if (typeof parsed.viewportBoundaryLeft === 'number') {
2895
2991
  this.viewportBoundaryLeft = parsed.viewportBoundaryLeft;
2896
2992
  }
@@ -2921,6 +3017,7 @@ const KritzelEditor = class {
2921
3017
  scaleMax: this.scaleMax,
2922
3018
  lockDrawingScale: this.lockDrawingScale,
2923
3019
  theme: this.theme,
3020
+ locale: this.locale,
2924
3021
  viewportBoundaryLeft: this.viewportBoundaryLeft,
2925
3022
  viewportBoundaryRight: this.viewportBoundaryRight,
2926
3023
  viewportBoundaryTop: this.viewportBoundaryTop,
@@ -2974,35 +3071,35 @@ const KritzelEditor = class {
2974
3071
  const isLoggedIn = this.isLoggedIn;
2975
3072
  const shouldShowCurrentUser = isLoggedIn;
2976
3073
  const shouldShowLoginButton = this.isReady && !!this.loginConfig && !isLoggedIn;
2977
- return (index.h(index.Host, { key: 'ffacaea5d3df12a3a8b448d31db3c5949053156c', style: {
3074
+ return (index.h(index.Host, { key: '72238560a0f0275c506f59220277fdff7ab92c13', style: {
2978
3075
  opacity: this.isEditorVisible ? '1' : '0',
2979
3076
  visibility: this.isEditorVisible ? 'visible' : 'hidden',
2980
3077
  transition: 'opacity 0.2s ease-in-out, visibility 0.2s ease-in-out',
2981
- } }, index.h("div", { key: '669eafee25b4f84c39469738a1337c21ab03e388', class: "top-left-buttons" }, index.h("kritzel-workspace-manager", { key: 'd6feb4a71c3286830fbe53a533f283c8af9a0385', visible: this.isWorkspaceManagerVisible, workspaces: this.workspaces, activeWorkspace: this.activeWorkspace, onWorkspaceChange: event => (this.activeWorkspace = event.detail), onIsWorkspaceManagerReady: () => (this.isWorkspaceManagerReady = true) }), index.h("kritzel-back-to-content", { key: '5bd0e6263d51119b197292b69879c1ae437f92fc', visible: this.isBackToContentButtonVisible, onBackToContent: () => this.backToContent() })), index.h("kritzel-engine", { key: '468f17137c51c90fd61c9179d13c449b1ac8feb9', ref: el => {
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 => {
2982
3079
  if (el) {
2983
3080
  this.engineRef = el;
2984
3081
  }
2985
- }, 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, wheelEnabled: this.wheelEnabled, theme: this.theme, themes: this.themes, 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: '848c30b27fb916c8480b41745bd6ec844e0b23a2', visible: this.isControlsVisible, class: { 'keyboard-open': this.isVirtualKeyboardOpen }, ref: el => {
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 => {
2986
3083
  if (el) {
2987
3084
  this.controlsRef = el;
2988
3085
  }
2989
- }, controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState ?? undefined, theme: this.theme, onIsControlsReady: () => (this.isControlsReady = true) }), index.h("div", { key: 'e998d60679c767d15617bd7ecde5ee77e781a92f', class: "top-right-buttons" }, index.h("kritzel-settings", { key: '43b9cdb2d10de789cc03d2a9ef5df870b8ca7bfe', ref: el => {
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 => {
2990
3087
  if (el) {
2991
3088
  this.settingsRef = el;
2992
3089
  }
2993
- }, shortcuts: this.shortcuts, availableThemes: this.themes && this.themes.length > 0 ? this.themes.map(t => t.name) : ['light', 'dark'], settings: this.currentSettingsConfig, onSettingsChange: event => this.handleSettingsChange(event) }), index.h("kritzel-export", { key: '74669624a1e5177125ef00e1667c880ce47cbce4', ref: el => {
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 => {
2994
3091
  if (el) {
2995
3092
  this.exportRef = el;
2996
3093
  }
2997
- }, workspaceName: this.activeWorkspace?.name || 'workspace', onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), index.h("kritzel-active-users", { key: '18d925f32d021ff6713accb22d0594d259d70f2e', users: this.activeUsers }), shouldShowCurrentUser && index.h("kritzel-current-user", { key: 'bddd5c29c5f17cced47276c237c04cfdb711da38', user: this.user }), shouldShowLoginButton && (index.h("kritzel-button", { key: '4eb6c85459f59863d6e644ecf21c2295da71bafc', onButtonClick: () => this.loginDialogRef?.open() }, "Sign in")), index.h("kritzel-more-menu", { key: 'a5a323ec248bebc7bc07898f344e0926fac8db17', items: this.moreMenuItems, visible: this.isMoreMenuVisible }), index.h("kritzel-share-dialog", { key: '8cb3ddad95d36f5b7ad59d8c4f057df93cb2bfe3', ref: el => {
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 => {
2998
3095
  if (el) {
2999
3096
  this.shareDialogRef = el;
3000
3097
  }
3001
- }, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (index.h("kritzel-login-dialog", { key: '35395d0faadcfeb021fba685aa46e180e47d2be2', ref: el => {
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 => {
3002
3099
  if (el) {
3003
3100
  this.loginDialogRef = el;
3004
3101
  }
3005
- }, providers: this.loginConfig.providers, dialogTitle: this.loginConfig.title, subtitle: this.loginConfig.subtitle, onProviderLogin: this.handleProviderLogin })))));
3102
+ }, providers: this.loginConfig.providers, dialogTitle: this.loginConfig.title ?? this.resolvedTerms['login.dialogTitle'] ?? 'Sign in', subtitle: this.loginConfig.subtitle, onProviderLogin: this.handleProviderLogin })))));
3006
3103
  }
3007
3104
  static get watchers() { return {
3008
3105
  "isEngineReady": [{
@@ -3025,6 +3122,9 @@ const KritzelEditor = class {
3025
3122
  }],
3026
3123
  "themes": [{
3027
3124
  "onThemesChange": 0
3125
+ }],
3126
+ "locale": [{
3127
+ "onLocaleChange": 0
3028
3128
  }]
3029
3129
  }; }
3030
3130
  };
@@ -21279,6 +21379,28 @@ class KritzelContextMenuHandler extends schema_constants.KritzelBaseHandler {
21279
21379
  this.globalContextMenuItems = globalContextMenuItems;
21280
21380
  this.objectContextMenuItems = objectContextMenuItems;
21281
21381
  }
21382
+ /**
21383
+ * Resolves the active context menu items for display, translating each item's
21384
+ * `label` through the current locale. When the label is a known
21385
+ * {@link KritzelTermKey} the translation is used; any other string is passed
21386
+ * through unchanged. Runs at menu-open time so the labels reflect the locale
21387
+ * active at that moment.
21388
+ * @returns A new array of items with resolved labels.
21389
+ */
21390
+ resolveLabels(items) {
21391
+ return items.map(item => ({
21392
+ ...item,
21393
+ label: this._core.localizationManager.translate(item.label),
21394
+ children: item.children ? this.resolveLabels(item.children) : undefined,
21395
+ }));
21396
+ }
21397
+ /**
21398
+ * Gets the active context menu items (object or global) with localized labels.
21399
+ */
21400
+ getResolvedContextMenuItems() {
21401
+ const items = this._core.store.selectionGroup ? this.objectContextMenuItems : this.globalContextMenuItems;
21402
+ return this.resolveLabels(items);
21403
+ }
21282
21404
  /**
21283
21405
  * Handles the context menu event (typically triggered by right-click).
21284
21406
  * This method performs the following operations:
@@ -21322,7 +21444,7 @@ class KritzelContextMenuHandler extends schema_constants.KritzelBaseHandler {
21322
21444
  this._core.addSelectionGroup(selectionGroup);
21323
21445
  this._core.rerender();
21324
21446
  }
21325
- this._core.store.state.contextMenuItems = this._core.store.selectionGroup ? this.objectContextMenuItems : this.globalContextMenuItems;
21447
+ this._core.store.state.contextMenuItems = this.getResolvedContextMenuItems();
21326
21448
  const clickX = event.clientX - this._core.store.offsetX;
21327
21449
  const clickY = event.clientY - this._core.store.offsetY;
21328
21450
  const { translateX, translateY, scale } = this._core.store.state;
@@ -21378,7 +21500,7 @@ class KritzelContextMenuHandler extends schema_constants.KritzelBaseHandler {
21378
21500
  this._core.addSelectionGroup(selectionGroup);
21379
21501
  }
21380
21502
  }
21381
- this._core.store.state.contextMenuItems = this._core.store.selectionGroup ? this.objectContextMenuItems : this.globalContextMenuItems;
21503
+ this._core.store.state.contextMenuItems = this.getResolvedContextMenuItems();
21382
21504
  this._core.store.state.contextMenuWorldX = x;
21383
21505
  this._core.store.state.contextMenuWorldY = y;
21384
21506
  const { translateX, translateY, scale } = this._core.store.state;
@@ -21425,6 +21547,7 @@ class KritzelCustomElement extends schema_constants.KritzelBaseObject {
21425
21547
  if (config) {
21426
21548
  this.translateX = config.translateX || 0;
21427
21549
  this.translateY = config.translateY || 0;
21550
+ this.rotation = schema_constants.KritzelMathHelper.degreesToRadians(config.rotation ?? 0);
21428
21551
  this.scale = config.scale || 1;
21429
21552
  this.element = config.element;
21430
21553
  this.height = config.height || 0;
@@ -23991,6 +24114,10 @@ class KritzelCore {
23991
24114
  _cursorManager;
23992
24115
  /** Manager for theme styling */
23993
24116
  _themeManager;
24117
+ /** Manager for localization / translated UI strings */
24118
+ _localizationManager;
24119
+ /** Manager for license validation and watermark gating */
24120
+ _licenseManager;
23994
24121
  /** Per-core registry of drawing tools (one instance per editor). */
23995
24122
  _toolRegistry;
23996
24123
  /** Optional unique identifier for namespacing storage keys across multiple editor instances */
@@ -24039,6 +24166,20 @@ class KritzelCore {
24039
24166
  get themeManager() {
24040
24167
  return this._themeManager;
24041
24168
  }
24169
+ /**
24170
+ * Gets the localization manager.
24171
+ * @returns The KritzelLocalizationManager for resolving translated UI strings
24172
+ */
24173
+ get localizationManager() {
24174
+ return this._localizationManager;
24175
+ }
24176
+ /**
24177
+ * Gets the license manager.
24178
+ * @returns The KritzelLicenseManager for validating licenses and gating the watermark
24179
+ */
24180
+ get licenseManager() {
24181
+ return this._licenseManager;
24182
+ }
24042
24183
  /**
24043
24184
  * Gets the tool registry scoped to this core instance.
24044
24185
  * @returns The KritzelToolRegistry owned by this core
@@ -24091,6 +24232,8 @@ class KritzelCore {
24091
24232
  this._anchorManager = new schema_constants.KritzelAnchorManager(this);
24092
24233
  this._cursorManager = new KritzelCursorManager(this);
24093
24234
  this._themeManager = new schema_constants.KritzelThemeManager(this);
24235
+ this._localizationManager = new schema_constants.KritzelLocalizationManager(this);
24236
+ this._licenseManager = new schema_constants.KritzelLicenseManager(this);
24094
24237
  this._toolRegistry = new KritzelToolRegistry(this);
24095
24238
  this._assetResolver = new schema_constants.KritzelAssetResolver();
24096
24239
  }
@@ -24126,6 +24269,8 @@ class KritzelCore {
24126
24269
  this._editorId = editorId;
24127
24270
  // Re-create theme manager so it picks up the new storage key
24128
24271
  this._themeManager = new schema_constants.KritzelThemeManager(this);
24272
+ // Re-create localization manager so it picks up the new storage key
24273
+ this._localizationManager = new schema_constants.KritzelLocalizationManager(this);
24129
24274
  }
24130
24275
  /**
24131
24276
  * Initializes the Yjs document for collaborative editing.
@@ -25272,7 +25417,7 @@ class KritzelSvgExportHelper {
25272
25417
  * @returns SVG document string
25273
25418
  */
25274
25419
  static generateSvg(objects, options = {}) {
25275
- const { theme, padding = 0, includeXmlDeclaration = true } = options;
25420
+ const { theme, padding = 0, includeXmlDeclaration = true, rasterSafeForCanvas = false, imageDataUrls } = options;
25276
25421
  if (!objects || objects.length === 0) {
25277
25422
  return '';
25278
25423
  }
@@ -25292,7 +25437,7 @@ class KritzelSvgExportHelper {
25292
25437
  // Generate SVG elements for each object
25293
25438
  const elements = objects
25294
25439
  .sort((a, b) => a.zIndex - b.zIndex)
25295
- .map(obj => this.objectToSvgElement(obj, theme))
25440
+ .map(obj => this.objectToSvgElement(obj, theme, imageDataUrls, rasterSafeForCanvas))
25296
25441
  .filter(Boolean)
25297
25442
  .join('\n ');
25298
25443
  const xmlDecl = includeXmlDeclaration ? '<?xml version="1.0" encoding="UTF-8"?>\n' : '';
@@ -25318,6 +25463,102 @@ class KritzelSvgExportHelper {
25318
25463
  }
25319
25464
  return result;
25320
25465
  }
25466
+ /**
25467
+ * Resolves every image referenced by the given objects (including those
25468
+ * nested inside groups) to an inlined `data:` URL, keyed by object id.
25469
+ *
25470
+ * This is required for export flows that rasterize the SVG (e.g. PNG
25471
+ * export): an SVG loaded into an `<img>` element will NOT load external
25472
+ * or `blob:` resources, so a `<image href="blob:...">` renders as a
25473
+ * broken-image placeholder. Embedding the bytes as a base64 `data:` URL
25474
+ * makes the image self-contained and survive rasterization.
25475
+ *
25476
+ * Resolution is best-effort: images that fail to resolve are simply
25477
+ * omitted from the map, and the SVG falls back to whatever `href` the
25478
+ * synchronous path would have produced.
25479
+ * @param objects - Objects to inline images for
25480
+ * @param assetResolver - Resolver used to fetch asset bytes; optional
25481
+ * @returns Map of image object id -> inlined `data:` URL
25482
+ */
25483
+ static async resolveImageDataUrls(objects, assetResolver) {
25484
+ const dataUrls = new Map();
25485
+ if (!objects || objects.length === 0) {
25486
+ return dataUrls;
25487
+ }
25488
+ const images = this.flattenObjects(objects).filter((obj) => schema_constants.KritzelClassHelper.isInstanceOf(obj, 'KritzelImage'));
25489
+ await Promise.all(images.map(async (image) => {
25490
+ // Legacy inline data URL is already embeddable as-is.
25491
+ if (image.src?.startsWith('data:')) {
25492
+ dataUrls.set(image.id, image.src);
25493
+ return;
25494
+ }
25495
+ // A resolvedSrc that is itself a data URL can be embedded directly.
25496
+ if (image.resolvedSrc?.startsWith('data:')) {
25497
+ dataUrls.set(image.id, image.resolvedSrc);
25498
+ return;
25499
+ }
25500
+ if (!image.assetId || !assetResolver) {
25501
+ return;
25502
+ }
25503
+ try {
25504
+ const blob = await assetResolver.fetchBlob(image.assetId);
25505
+ dataUrls.set(image.id, await this.blobToDataUrl(blob));
25506
+ }
25507
+ catch (err) {
25508
+ console.warn(`[KritzelSvgExportHelper] Failed to inline image asset ${image.assetId} for export:`, err);
25509
+ }
25510
+ }));
25511
+ return dataUrls;
25512
+ }
25513
+ /**
25514
+ * Reads a Blob into a base64 `data:` URL.
25515
+ * @param blob - The blob to encode
25516
+ * @returns Promise resolving to the data URL string
25517
+ */
25518
+ static async blobToDataUrl(blob) {
25519
+ // Prefer FileReader in browser-like runtimes for efficiency.
25520
+ const hasFileReader = typeof FileReader !== 'undefined';
25521
+ if (hasFileReader) {
25522
+ try {
25523
+ return await new Promise((resolve, reject) => {
25524
+ const reader = new FileReader();
25525
+ reader.onload = () => {
25526
+ if (typeof reader.result === 'string') {
25527
+ resolve(reader.result);
25528
+ return;
25529
+ }
25530
+ reject(new Error('FileReader result is not a string'));
25531
+ };
25532
+ reader.onerror = () => reject(reader.error ?? new Error('Failed to read blob as data URL'));
25533
+ reader.readAsDataURL(blob);
25534
+ });
25535
+ }
25536
+ catch {
25537
+ // Fall through to the ArrayBuffer-based encoder.
25538
+ }
25539
+ }
25540
+ const bytes = new Uint8Array(await blob.arrayBuffer());
25541
+ const mimeType = blob.type || 'application/octet-stream';
25542
+ return `data:${mimeType};base64,${this.uint8ToBase64(bytes)}`;
25543
+ }
25544
+ /**
25545
+ * Encodes a byte array to base64 across browser and Node-like runtimes.
25546
+ */
25547
+ static uint8ToBase64(bytes) {
25548
+ if (typeof Buffer !== 'undefined') {
25549
+ return Buffer.from(bytes).toString('base64');
25550
+ }
25551
+ let binary = '';
25552
+ const chunkSize = 0x8000;
25553
+ for (let i = 0; i < bytes.length; i += chunkSize) {
25554
+ const chunk = bytes.subarray(i, Math.min(i + chunkSize, bytes.length));
25555
+ binary += String.fromCharCode(...chunk);
25556
+ }
25557
+ if (typeof btoa === 'function') {
25558
+ return btoa(binary);
25559
+ }
25560
+ throw new Error('No base64 encoder available in this runtime');
25561
+ }
25321
25562
  /**
25322
25563
  * Collects SVG defs (markers, patterns, etc.) needed by the objects.
25323
25564
  * @param objects - Objects to collect defs for
@@ -25362,9 +25603,10 @@ class KritzelSvgExportHelper {
25362
25603
  * Converts a Kritzel object to its SVG element representation.
25363
25604
  * @param object - The object to convert
25364
25605
  * @param theme - Theme for color resolution
25606
+ * @param imageDataUrls - Optional map of image id -> inlined data URL
25365
25607
  * @returns SVG element string, or empty string if object type is not supported
25366
25608
  */
25367
- static objectToSvgElement(object, theme) {
25609
+ static objectToSvgElement(object, theme, imageDataUrls, rasterSafeForCanvas = false) {
25368
25610
  if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelPath')) {
25369
25611
  return this.pathToSvg(object, theme);
25370
25612
  }
@@ -25375,13 +25617,13 @@ class KritzelSvgExportHelper {
25375
25617
  return this.shapeToSvg(object, theme);
25376
25618
  }
25377
25619
  if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelText')) {
25378
- return this.textToSvg(object, theme);
25620
+ return this.textToSvg(object, theme, rasterSafeForCanvas);
25379
25621
  }
25380
25622
  if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelImage')) {
25381
- return this.imageToSvg(object);
25623
+ return this.imageToSvg(object, imageDataUrls);
25382
25624
  }
25383
25625
  if (schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelGroup')) {
25384
- return this.groupToSvg(object, theme);
25626
+ return this.groupToSvg(object, theme, imageDataUrls, rasterSafeForCanvas);
25385
25627
  }
25386
25628
  return '';
25387
25629
  }
@@ -25396,9 +25638,10 @@ class KritzelSvgExportHelper {
25396
25638
  const fill = schema_constants.KritzelColorHelper.resolveThemeColor(path.fill, theme);
25397
25639
  const stroke = schema_constants.KritzelColorHelper.resolveThemeColor(path.stroke, theme);
25398
25640
  const opacity = path.opacity !== 1 ? ` opacity="${path.opacity}"` : '';
25641
+ const scale = this.getObjectScale(path);
25399
25642
  // Path needs to be wrapped in a g with translation since path.d uses local coordinates
25400
25643
  return `<g transform="${transform}"${opacity}>
25401
- <svg viewBox="${path.viewBox}" width="${path.totalWidth / path.scale}" height="${path.totalHeight / path.scale}" overflow="visible">
25644
+ <svg viewBox="${path.viewBox}" width="${path.totalWidth / scale}" height="${path.totalHeight / scale}" overflow="visible">
25402
25645
  <path d="${path.d}" fill="${fill || 'none'}" stroke="${stroke || 'none'}"${path.strokeWidth ? ` stroke-width="${path.strokeWidth}"` : ''}/>
25403
25646
  </svg>
25404
25647
  </g>`;
@@ -25413,10 +25656,11 @@ class KritzelSvgExportHelper {
25413
25656
  const transform = this.buildTransform(line);
25414
25657
  const stroke = schema_constants.KritzelColorHelper.resolveThemeColor(line.stroke, theme);
25415
25658
  const opacity = line.opacity !== 1 ? ` opacity="${line.opacity}"` : '';
25659
+ const scale = this.getObjectScale(line);
25416
25660
  const markerStart = line.hasStartArrow ? ` marker-start="url(#${line.startMarkerId})"` : '';
25417
25661
  const markerEnd = line.hasEndArrow ? ` marker-end="url(#${line.endMarkerId})"` : '';
25418
25662
  return `<g transform="${transform}"${opacity}>
25419
- <svg viewBox="${line.viewBox}" width="${line.totalWidth / line.scale}" height="${line.totalHeight / line.scale}" overflow="visible">
25663
+ <svg viewBox="${line.viewBox}" width="${line.totalWidth / scale}" height="${line.totalHeight / scale}" overflow="visible">
25420
25664
  <path d="${line.d}" fill="none" stroke="${stroke}" stroke-width="${line.strokeWidth}" stroke-linecap="round"${markerStart}${markerEnd}/>
25421
25665
  </svg>
25422
25666
  </g>`;
@@ -25432,6 +25676,7 @@ class KritzelSvgExportHelper {
25432
25676
  const fill = schema_constants.KritzelColorHelper.resolveThemeColor(shape.fillColor, theme);
25433
25677
  const stroke = schema_constants.KritzelColorHelper.resolveThemeColor(shape.strokeColor, theme);
25434
25678
  const opacity = shape.opacity !== 1 ? ` opacity="${shape.opacity}"` : '';
25679
+ const scale = this.getObjectScale(shape);
25435
25680
  // Get the SVG path for the shape
25436
25681
  const pathD = shape.getSvgPath();
25437
25682
  // Handle text content if present
@@ -25450,7 +25695,7 @@ class KritzelSvgExportHelper {
25450
25695
  }
25451
25696
  }
25452
25697
  return `<g transform="${transform}"${opacity}>
25453
- <svg viewBox="${shape.viewBox}" width="${shape.totalWidth / shape.scale}" height="${shape.totalHeight / shape.scale}" overflow="visible" preserveAspectRatio="none">
25698
+ <svg viewBox="${shape.viewBox}" width="${shape.totalWidth / scale}" height="${shape.totalHeight / scale}" overflow="visible" preserveAspectRatio="none">
25454
25699
  <path d="${pathD}" fill="${fill || 'transparent'}" stroke="${stroke}" stroke-width="${shape.strokeWidth}"/>${textContent}
25455
25700
  </svg>
25456
25701
  </g>`;
@@ -25461,45 +25706,85 @@ class KritzelSvgExportHelper {
25461
25706
  * @param theme - Theme for color resolution
25462
25707
  * @returns SVG element string
25463
25708
  */
25464
- static textToSvg(text, theme) {
25709
+ static textToSvg(text, theme, rasterSafeForCanvas = false) {
25710
+ if (rasterSafeForCanvas) {
25711
+ return this.textToSvgRasterSafe(text, theme);
25712
+ }
25465
25713
  const transform = this.buildTransform(text);
25466
25714
  const opacity = text.opacity !== 1 ? ` opacity="${text.opacity}"` : '';
25467
25715
  const fontColor = schema_constants.KritzelColorHelper.resolveThemeColor(text.fontColor, theme);
25468
25716
  const bgColor = schema_constants.KritzelColorHelper.resolveThemeColor(text.backgroundColor, theme);
25717
+ const scale = this.getObjectScale(text);
25469
25718
  // Convert ProseMirror content to HTML
25470
25719
  const htmlContent = this.prosemirrorToHtml(text.content, text, theme);
25471
25720
  // Calculate dimensions accounting for scale
25472
- const width = text.totalWidth / text.scale;
25473
- const height = text.totalHeight / text.scale;
25721
+ const width = text.totalWidth / scale;
25722
+ const height = text.totalHeight / scale;
25474
25723
  return `<g transform="${transform}"${opacity}>
25475
25724
  <foreignObject x="0" y="0" width="${width}" height="${height}">
25476
25725
  <div xmlns="http://www.w3.org/1999/xhtml" style="font-family: ${text.fontFamily}; font-size: ${text.fontSize}pt; color: ${fontColor}; background-color: ${bgColor || 'transparent'}; transform: scale(${text.scaleFactor}); transform-origin: top left; white-space: pre-wrap; word-wrap: break-word;">
25477
25726
  ${htmlContent}
25478
25727
  </div>
25479
25728
  </foreignObject>
25729
+ </g>`;
25730
+ }
25731
+ /**
25732
+ * Converts a KritzelText to SVG using pure SVG text primitives.
25733
+ * This path avoids `<foreignObject>`, which taints canvases when the
25734
+ * generated SVG is rasterized through `<img>` and `drawImage`.
25735
+ */
25736
+ static textToSvgRasterSafe(text, theme) {
25737
+ const transform = this.buildTransform(text);
25738
+ const opacity = text.opacity !== 1 ? ` opacity="${text.opacity}"` : '';
25739
+ const fontColor = schema_constants.KritzelColorHelper.resolveThemeColor(text.fontColor, theme);
25740
+ const bgColor = schema_constants.KritzelColorHelper.resolveThemeColor(text.backgroundColor, theme);
25741
+ const scale = this.getObjectScale(text);
25742
+ const width = text.totalWidth / scale;
25743
+ const height = text.totalHeight / scale;
25744
+ const lines = this.prosemirrorToPlainTextLines(text.content);
25745
+ const resolvedLines = lines.length > 0 ? lines : [''];
25746
+ const resolvedFontSize = Math.max(1, text.fontSize * (text.scaleFactor || 1));
25747
+ const lineHeight = resolvedFontSize * 1.2;
25748
+ const startY = resolvedFontSize;
25749
+ const bgRect = bgColor && bgColor !== 'transparent'
25750
+ ? `<rect x="0" y="0" width="${width}" height="${height}" fill="${bgColor}"/>`
25751
+ : '';
25752
+ const tspans = resolvedLines
25753
+ .map((line, index) => {
25754
+ const y = startY + index * lineHeight;
25755
+ return `<tspan x="0" y="${y}" xml:space="preserve">${this.escapeHtml(line)}</tspan>`;
25756
+ })
25757
+ .join('');
25758
+ return `<g transform="${transform}"${opacity}>
25759
+ ${bgRect}
25760
+ <text font-family="${this.escapeHtml(text.fontFamily)}" font-size="${resolvedFontSize}" fill="${fontColor}">${tspans}</text>
25480
25761
  </g>`;
25481
25762
  }
25482
25763
  /**
25483
25764
  * Converts a KritzelImage to SVG.
25484
25765
  *
25485
- * Uses `resolvedSrc` when available (populated by the renderer via the
25486
- * asset resolver), falling back to the legacy inline `src` field for
25766
+ * Prefers an inlined `data:` URL from `imageDataUrls` (keyed by object
25767
+ * id) when provided, so the embedded bytes survive rasterization to
25768
+ * PNG. Falls back to `resolvedSrc` (populated by the renderer via the
25769
+ * asset resolver) and finally the legacy inline `src` field for
25487
25770
  * documents persisted before the asset layer existed.
25488
25771
  *
25489
- * Note: SVG export is synchronous and cannot await asset bytes from a
25490
- * remote provider. Callers that need the fully rasterized image bytes
25491
- * embedded in the exported SVG should pre-resolve assets via
25492
- * `core.assetResolver.fetchBlob` and inline them as data URLs.
25772
+ * Note: a `blob:` `resolvedSrc` renders correctly on-canvas but becomes
25773
+ * a broken-image placeholder once the SVG is loaded into an `<img>` and
25774
+ * drawn to a canvas. Callers that rasterize the SVG must pass
25775
+ * `imageDataUrls` (see {@link KritzelSvgExportHelper.resolveImageDataUrls}).
25493
25776
  * @param image - The image object
25777
+ * @param imageDataUrls - Optional map of image id -> inlined data URL
25494
25778
  * @returns SVG element string
25495
25779
  */
25496
- static imageToSvg(image) {
25780
+ static imageToSvg(image, imageDataUrls) {
25497
25781
  const transform = this.buildTransform(image);
25498
25782
  const opacity = image.opacity !== 1 ? ` opacity="${image.opacity}"` : '';
25783
+ const scale = this.getObjectScale(image);
25499
25784
  // Calculate dimensions
25500
- const width = image.totalWidth / image.scale;
25501
- const height = image.totalHeight / image.scale;
25502
- const href = image.resolvedSrc || image.src || '';
25785
+ const width = image.totalWidth / scale;
25786
+ const height = image.totalHeight / scale;
25787
+ const href = imageDataUrls?.get(image.id) || image.resolvedSrc || image.src || '';
25503
25788
  return `<g transform="${transform}"${opacity}>
25504
25789
  <image href="${href}" x="0" y="0" width="${width}" height="${height}" preserveAspectRatio="xMidYMid meet"/>
25505
25790
  </g>`;
@@ -25508,12 +25793,13 @@ class KritzelSvgExportHelper {
25508
25793
  * Converts a KritzelGroup to SVG by recursively converting children.
25509
25794
  * @param group - The group object
25510
25795
  * @param theme - Theme for color resolution
25796
+ * @param imageDataUrls - Optional map of image id -> inlined data URL
25511
25797
  * @returns SVG element string
25512
25798
  */
25513
- static groupToSvg(group, theme) {
25799
+ static groupToSvg(group, theme, imageDataUrls, rasterSafeForCanvas = false) {
25514
25800
  const children = group.children
25515
25801
  .sort((a, b) => a.zIndex - b.zIndex)
25516
- .map(child => this.objectToSvgElement(child, theme))
25802
+ .map(child => this.objectToSvgElement(child, theme, imageDataUrls, rasterSafeForCanvas))
25517
25803
  .filter(Boolean)
25518
25804
  .join('\n ');
25519
25805
  if (!children) {
@@ -25532,17 +25818,25 @@ class KritzelSvgExportHelper {
25532
25818
  */
25533
25819
  static buildTransform(object) {
25534
25820
  const transforms = [];
25821
+ const scale = this.getObjectScale(object);
25535
25822
  // Translation
25536
25823
  transforms.push(`translate(${object.translateX}, ${object.translateY})`);
25537
25824
  // Rotation around center
25538
25825
  if (object.rotation !== 0) {
25539
- const centerX = object.totalWidth / 2 / object.scale;
25540
- const centerY = object.totalHeight / 2 / object.scale;
25826
+ const centerX = object.totalWidth / 2 / scale;
25827
+ const centerY = object.totalHeight / 2 / scale;
25541
25828
  const degrees = object.rotation * (180 / Math.PI);
25542
25829
  transforms.push(`rotate(${degrees}, ${centerX}, ${centerY})`);
25543
25830
  }
25544
25831
  return transforms.join(' ');
25545
25832
  }
25833
+ /**
25834
+ * Returns a safe non-zero scale for export geometry calculations.
25835
+ */
25836
+ static getObjectScale(object) {
25837
+ const scale = object.scale ?? 1;
25838
+ return scale === 0 ? 1 : scale;
25839
+ }
25546
25840
  /**
25547
25841
  * Converts ProseMirror JSON content to HTML string.
25548
25842
  * @param content - ProseMirror document JSON
@@ -25598,6 +25892,54 @@ class KritzelSvgExportHelper {
25598
25892
  return '';
25599
25893
  }
25600
25894
  }
25895
+ /**
25896
+ * Converts ProseMirror JSON content to plain-text lines.
25897
+ * Used by raster-safe SVG export, which cannot depend on HTML/foreignObject.
25898
+ */
25899
+ static prosemirrorToPlainTextLines(content) {
25900
+ if (!content || !Array.isArray(content.content)) {
25901
+ return [];
25902
+ }
25903
+ const plainText = content.content
25904
+ .map((node) => this.nodeToPlainText(node))
25905
+ .join('')
25906
+ .replace(/\r\n/g, '\n')
25907
+ .replace(/\n+$/, '');
25908
+ if (!plainText) {
25909
+ return [];
25910
+ }
25911
+ return plainText.split('\n');
25912
+ }
25913
+ /**
25914
+ * Converts a ProseMirror node tree into plain text.
25915
+ */
25916
+ static nodeToPlainText(node) {
25917
+ if (!node) {
25918
+ return '';
25919
+ }
25920
+ switch (node.type) {
25921
+ case 'text':
25922
+ return node.text || '';
25923
+ case 'hard_break':
25924
+ return '\n';
25925
+ case 'paragraph': {
25926
+ const paragraph = Array.isArray(node.content)
25927
+ ? node.content.map((child) => this.nodeToPlainText(child)).join('')
25928
+ : '';
25929
+ return `${paragraph}\n`;
25930
+ }
25931
+ case 'list_item': {
25932
+ const item = Array.isArray(node.content)
25933
+ ? node.content.map((child) => this.nodeToPlainText(child)).join('')
25934
+ : '';
25935
+ return `- ${item.trim()}\n`;
25936
+ }
25937
+ default:
25938
+ return Array.isArray(node.content)
25939
+ ? node.content.map((child) => this.nodeToPlainText(child)).join('')
25940
+ : '';
25941
+ }
25942
+ }
25601
25943
  /**
25602
25944
  * Applies a ProseMirror mark to text.
25603
25945
  * @param text - The text to wrap
@@ -26648,6 +26990,11 @@ const KritzelEngine = class {
26648
26990
  onThemeChange(newValue) {
26649
26991
  this.core.themeManager.setTheme(newValue);
26650
26992
  }
26993
+ /** License key that, when valid, removes the "Powered by Kritzel" watermark. */
26994
+ licenseKey;
26995
+ onLicenseKeyChange(newValue) {
26996
+ this.core.licenseManager.validate(newValue);
26997
+ }
26651
26998
  /** An array of available themes for the editor. */
26652
26999
  themes;
26653
27000
  onThemesChange(newValue) {
@@ -26656,6 +27003,25 @@ const KritzelEngine = class {
26656
27003
  this.core.themeManager.applyTheme(this.core.themeManager.currentTheme);
26657
27004
  }
26658
27005
  }
27006
+ /** The current locale (language) code to apply to the editor, e.g. 'en', 'de', 'fr'. */
27007
+ locale = 'en';
27008
+ onLocaleChange(newValue) {
27009
+ this.core.localizationManager.setLocale(newValue);
27010
+ }
27011
+ /** An array of available locale definitions (with optional partial term overrides). */
27012
+ locales;
27013
+ onLocalesChange(newValue) {
27014
+ if (newValue && newValue.length > 0) {
27015
+ this.core.localizationManager.registerLocales(newValue);
27016
+ this.core.localizationManager.setLocale(this.locale);
27017
+ }
27018
+ }
27019
+ /** The locale used to resolve terms missing from the active locale. */
27020
+ fallbackLocale = 'en';
27021
+ onFallbackLocaleChange(newValue) {
27022
+ this.core.localizationManager.setFallbackLocale(newValue);
27023
+ this.core.rerender();
27024
+ }
26659
27025
  /** Left boundary of the viewport in world coordinates. Objects beyond this X position cannot be panned to. */
26660
27026
  viewportBoundaryLeft = -Infinity;
26661
27027
  onViewportBoundaryLeftChange(newValue) {
@@ -26683,8 +27049,10 @@ const KritzelEngine = class {
26683
27049
  this.core.store.state.debugInfo = newValue;
26684
27050
  }
26685
27051
  }
26686
- /** When false, wheel events will not trigger viewport pan/zoom. The event still propagates to parent elements. */
26687
- wheelEnabled = true;
27052
+ /** When false, non-modified wheel events do not pan the viewport. */
27053
+ isPanningEnabled = true;
27054
+ /** When false, Ctrl+wheel events do not zoom the viewport. */
27055
+ isZoomingEnabled = true;
26688
27056
  /** External loading state. Combined with internal workspace-loading state to drive the overlay. */
26689
27057
  isLoading = false;
26690
27058
  onIsLoadingChange() {
@@ -26727,13 +27095,19 @@ const KritzelEngine = class {
26727
27095
  if (this.core.store.isDisabled) {
26728
27096
  return;
26729
27097
  }
26730
- if (!this.wheelEnabled) {
26731
- return;
26732
- }
26733
27098
  if (this.core.store.state.isContextMenuVisible) {
26734
27099
  this.hideContextMenu();
26735
27100
  }
26736
- this.viewport.handleWheel(ev);
27101
+ if (ev.ctrlKey) {
27102
+ if (this.isZoomingEnabled) {
27103
+ this.viewport.handleWheel(ev);
27104
+ }
27105
+ }
27106
+ else {
27107
+ if (this.isPanningEnabled) {
27108
+ this.viewport.handleWheel(ev);
27109
+ }
27110
+ }
26737
27111
  this.core.store.state?.activeTool?.handleWheel(ev);
26738
27112
  }
26739
27113
  handlePointerDown(ev) {
@@ -27569,7 +27943,7 @@ const KritzelEngine = class {
27569
27943
  /**
27570
27944
  * Generates an SVG string from the currently selected objects.
27571
27945
  * Creates clean, standards-compliant SVG markup by directly serializing object properties.
27572
- * @param options - Optional export settings (theme, padding).
27946
+ * @param options - Optional export settings (theme, padding, raster-safe mode for canvas export).
27573
27947
  * @returns SVG markup string, or null if no objects are selected.
27574
27948
  */
27575
27949
  async getSelectedObjectsAsSvgString(options) {
@@ -27578,10 +27952,16 @@ const KritzelEngine = class {
27578
27952
  return null;
27579
27953
  }
27580
27954
  const theme = options?.theme ?? this.core.themeManager.getStoredTheme();
27955
+ // Inline image bytes as data URLs so they survive both standalone SVG
27956
+ // files and rasterization to PNG (a `blob:` href is not loaded when an
27957
+ // SVG is drawn into a canvas, which otherwise yields a broken image).
27958
+ const imageDataUrls = await KritzelSvgExportHelper.resolveImageDataUrls(selectedObjects, this.core.assetResolver);
27581
27959
  return KritzelSvgExportHelper.generateSvg(selectedObjects, {
27582
27960
  theme,
27583
27961
  padding: options?.padding ?? 0,
27584
27962
  includeXmlDeclaration: true,
27963
+ rasterSafeForCanvas: options?.rasterSafeForCanvas ?? false,
27964
+ imageDataUrls,
27585
27965
  });
27586
27966
  }
27587
27967
  /**
@@ -27620,6 +28000,9 @@ const KritzelEngine = class {
27620
28000
  const svgString = await this.getSelectedObjectsAsSvgString({
27621
28001
  theme: options?.theme,
27622
28002
  padding: options?.padding,
28003
+ // Avoid foreignObject-based text serialization, which taints the
28004
+ // canvas in SVG->PNG conversion paths.
28005
+ rasterSafeForCanvas: true,
27623
28006
  });
27624
28007
  if (!svgString) {
27625
28008
  return null;
@@ -27972,6 +28355,55 @@ const KritzelEngine = class {
27972
28355
  return null;
27973
28356
  }
27974
28357
  }
28358
+ /**
28359
+ * Registers additional locale definitions (with optional partial term overrides).
28360
+ * @param locales - The locale definitions to register.
28361
+ */
28362
+ async registerLocales(locales) {
28363
+ this.core.localizationManager.registerLocales(locales);
28364
+ this.core.localizationManager.setLocale(this.core.localizationManager.currentLocale);
28365
+ }
28366
+ /**
28367
+ * Sets the active locale (language) and re-renders the UI.
28368
+ * @param code - The locale code to activate, e.g. 'de'.
28369
+ */
28370
+ async setLocale(code) {
28371
+ this.locale = code;
28372
+ this.core.localizationManager.setLocale(code);
28373
+ }
28374
+ /**
28375
+ * Gets the currently active locale code.
28376
+ */
28377
+ async getLocale() {
28378
+ return this.core.localizationManager.currentLocale;
28379
+ }
28380
+ /**
28381
+ * Gets the list of available locale codes (built-in and registered).
28382
+ */
28383
+ async getAvailableLocales() {
28384
+ return this.core.localizationManager.getAvailableLocales();
28385
+ }
28386
+ /**
28387
+ * Gets the list of available locales as `{ code, label }` options for a selector.
28388
+ */
28389
+ async getAvailableLocaleOptions() {
28390
+ return this.core.localizationManager.getAvailableLocaleOptions();
28391
+ }
28392
+ /**
28393
+ * Resolves a term key to its translated string for the active locale.
28394
+ * @param key - The term key to resolve.
28395
+ * @param vars - Optional values for `{placeholder}` interpolation.
28396
+ */
28397
+ async t(key, vars) {
28398
+ return this.core.localizationManager.translate(key, vars);
28399
+ }
28400
+ /**
28401
+ * Resolves every known term key for the active locale into a flat map.
28402
+ * Useful for UI layers that need to localize many strings at once.
28403
+ */
28404
+ async getResolvedTerms() {
28405
+ return this.core.localizationManager.getAllTerms();
28406
+ }
27975
28407
  core;
27976
28408
  viewport;
27977
28409
  contextMenuHandler;
@@ -28062,6 +28494,7 @@ const KritzelEngine = class {
28062
28494
  // Clean up managers
28063
28495
  this.core.cursorManager.cleanup();
28064
28496
  this.core.themeManager.cleanup();
28497
+ this.core.licenseManager.destroy();
28065
28498
  }
28066
28499
  componentWillLoad() {
28067
28500
  this.core.setEditorId(this.editorId);
@@ -28070,6 +28503,13 @@ const KritzelEngine = class {
28070
28503
  }
28071
28504
  const editorElement = this.host.closest('kritzel-editor');
28072
28505
  this.core.themeManager.injectThemeEarly(editorElement || this.host);
28506
+ if (this.locales && this.locales.length > 0) {
28507
+ this.core.localizationManager.registerLocales(this.locales);
28508
+ }
28509
+ this.core.localizationManager.setFallbackLocale(this.fallbackLocale);
28510
+ this.core.localizationManager.setLocale(this.locale);
28511
+ this.core.licenseManager.validate(this.licenseKey);
28512
+ this.core.licenseManager.startPeriodicValidation();
28073
28513
  this.core.setUser(this.user);
28074
28514
  this.validateScaleMax(this.scaleMax);
28075
28515
  this.validateScaleMin(this.scaleMin);
@@ -28374,7 +28814,7 @@ const KritzelEngine = class {
28374
28814
  }
28375
28815
  render() {
28376
28816
  if (!this.viewport) {
28377
- return (index.h(index.Host, null, this.core.store.state.isLoading && (index.h("div", { class: "workspace-loading-overlay" }, index.h("span", { class: "workspace-loading-spinner" }), "Loading..."))));
28817
+ return (index.h(index.Host, null, this.core.store.state.isLoading && (index.h("div", { class: "workspace-loading-overlay" }, index.h("span", { class: "workspace-loading-spinner" }), this.core.localizationManager.translate('engine.loading')))));
28378
28818
  }
28379
28819
  const currentTheme = this.core.themeManager.getStoredTheme();
28380
28820
  const computedStyle = window.getComputedStyle(this.host);
@@ -28812,7 +29252,7 @@ const KritzelEngine = class {
28812
29252
  }, this.core.store.selectionGroup?.objects || []);
28813
29253
  }
28814
29254
  this.hideContextMenu();
28815
- }, onClose: () => this.hideContextMenu() })), this.core.store.objects?.hasAwareness && index.h("kritzel-awareness-cursors", { core: this.core }), this.core.store.state?.activeTool instanceof schema_constants.KritzelEraserTool && !this.core.store.state.isScaling && index.h("kritzel-cursor-trail", { core: this.core })));
29255
+ }, onClose: () => this.hideContextMenu() })), this.core.store.objects?.hasAwareness && index.h("kritzel-awareness-cursors", { core: this.core }), this.core.store.state?.activeTool instanceof schema_constants.KritzelEraserTool && !this.core.store.state.isScaling && index.h("kritzel-cursor-trail", { core: this.core }), !this.core.licenseManager.isLicensed && (index.h("kritzel-watermark", { core: this.core, label: this.core.localizationManager.translate('watermark.poweredBy') }))));
28816
29256
  }
28817
29257
  static get watchers() { return {
28818
29258
  "workspace": [{
@@ -28854,9 +29294,21 @@ const KritzelEngine = class {
28854
29294
  "theme": [{
28855
29295
  "onThemeChange": 0
28856
29296
  }],
29297
+ "licenseKey": [{
29298
+ "onLicenseKeyChange": 0
29299
+ }],
28857
29300
  "themes": [{
28858
29301
  "onThemesChange": 0
28859
29302
  }],
29303
+ "locale": [{
29304
+ "onLocaleChange": 0
29305
+ }],
29306
+ "locales": [{
29307
+ "onLocalesChange": 0
29308
+ }],
29309
+ "fallbackLocale": [{
29310
+ "onFallbackLocaleChange": 0
29311
+ }],
28860
29312
  "viewportBoundaryLeft": [{
28861
29313
  "onViewportBoundaryLeftChange": 0
28862
29314
  }],
@@ -28892,6 +29344,8 @@ const KritzelExport = class {
28892
29344
  * The name of the current workspace, used as default filename
28893
29345
  */
28894
29346
  workspaceName = 'workspace';
29347
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
29348
+ terms = {};
28895
29349
  isDialogOpen = false;
28896
29350
  previewUrl;
28897
29351
  isLoading = false;
@@ -28901,10 +29355,12 @@ const KritzelExport = class {
28901
29355
  exportPng;
28902
29356
  exportSvg;
28903
29357
  exportJson;
28904
- tabs = [
28905
- { id: 'viewport', label: 'Export Viewport' },
28906
- { id: 'workspace', label: 'Export Workspace' },
28907
- ];
29358
+ get tabs() {
29359
+ return [
29360
+ { id: 'viewport', label: this.terms['export.tabs.viewport'] ?? 'Export Viewport' },
29361
+ { id: 'workspace', label: this.terms['export.tabs.workspace'] ?? 'Export Workspace' },
29362
+ ];
29363
+ }
28908
29364
  viewportFormatOptions = [
28909
29365
  { value: 'png', label: 'PNG' },
28910
29366
  { value: 'svg', label: 'SVG' },
@@ -28947,13 +29403,13 @@ const KritzelExport = class {
28947
29403
  this.closeDialog();
28948
29404
  };
28949
29405
  renderViewportExport() {
28950
- return (index.h("div", { class: "export-tab-content" }, this.previewUrl && (index.h("div", { class: "preview-container" }, index.h("img", { src: this.previewUrl, alt: "Viewport Preview" }))), index.h("kritzel-input", { label: "Filename", value: this.exportFilename, placeholder: "Enter filename", suffix: `.${this.viewportExportFormat}`, onValueChange: this.handleFilenameChange }), index.h("div", { class: "format-selection" }, index.h("label", null, "Format"), index.h("kritzel-dropdown", { options: this.viewportFormatOptions, value: this.viewportExportFormat, forceOpenDirection: "up", onValueChanged: this.handleViewportFormatChange }))));
29406
+ return (index.h("div", { class: "export-tab-content" }, this.previewUrl && (index.h("div", { class: "preview-container" }, index.h("img", { src: this.previewUrl, alt: "Viewport Preview" }))), index.h("kritzel-input", { label: this.terms['export.filename.label'] ?? 'Filename', value: this.exportFilename, placeholder: this.terms['export.filename.placeholder'] ?? 'Enter filename', suffix: `.${this.viewportExportFormat}`, onValueChange: this.handleFilenameChange }), index.h("div", { class: "format-selection" }, index.h("label", null, this.terms['export.format.label'] ?? 'Format'), index.h("kritzel-dropdown", { options: this.viewportFormatOptions, value: this.viewportExportFormat, forceOpenDirection: "up", onValueChanged: this.handleViewportFormatChange }))));
28951
29407
  }
28952
29408
  renderWorkspaceExport() {
28953
- return (index.h("div", { class: "export-tab-content" }, index.h("kritzel-input", { label: "Filename", value: this.exportFilename, placeholder: "Enter filename", suffix: ".json", onValueChange: this.handleFilenameChange })));
29409
+ 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 })));
28954
29410
  }
28955
29411
  render() {
28956
- return (index.h(index.Host, { key: 'efeea781325e672e3f4c1579a50da1c928dc88b5' }, index.h("kritzel-dialog", { key: '60e27233f484e70fd12bcc0f8a72b89d2f72d596', isOpen: this.isDialogOpen, dialogTitle: "Export", closable: true, contained: true, onDialogClose: this.closeDialog }, index.h("div", { key: 'e58e1d9804fdc8cb3d4c053ead641e2301b99ea5', class: "export-content" }, index.h("kritzel-pill-tabs", { key: '409f4c2d64f5477dc57c72a8e32ae0a12dfb7eda', tabs: this.tabs, value: this.activeTab, onValueChange: this.handleTabChange }), this.activeTab === 'viewport' && this.renderViewportExport(), this.activeTab === 'workspace' && this.renderWorkspaceExport(), index.h("button", { key: '7166aee26e0dbbdf6e7348428f7a740614948e5e', class: "export-primary-button", onClick: this.handleExport }, "Export")))));
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')))));
28957
29413
  }
28958
29414
  };
28959
29415
  KritzelExport.style = kritzelExportCss();
@@ -28968,7 +29424,7 @@ const KritzelFont = class {
28968
29424
  size = 24;
28969
29425
  color = '#000000';
28970
29426
  render() {
28971
- return (index.h(index.Host, { key: '6eef9e7df004469faeeaf458d9b9967f94f8536d' }, index.h("div", { key: '9926c54a57245c40d605077e191f62cebf3700b1', class: "font-preview", style: {
29427
+ return (index.h(index.Host, { key: '49c44f7fef61bd7116e63bbe9522b0a0e22de116' }, index.h("div", { key: '4d130682f67e9c0d758cb6194e6a95f15bceb2ee', class: "font-preview", style: {
28972
29428
  fontFamily: this.fontFamily,
28973
29429
  fontSize: `${this.size}px`,
28974
29430
  color: this.color
@@ -29043,7 +29499,8 @@ const KritzelFontSize = class {
29043
29499
  }
29044
29500
  render() {
29045
29501
  const color = 'var(--kritzel-global-text-primary)';
29046
- return (index.h(index.Host, { key: 'efb5ad516a0ebfea0fee5c78a1d3b4d0af4a6bc9' }, this.sizes.map(size => (index.h("div", { tabIndex: 0, class: {
29502
+ const sizes = this.sizes ?? [];
29503
+ return (index.h(index.Host, { key: '2083b05b3fdc9940c26dd90dbf9e097564ee976d' }, sizes.map(size => (index.h("div", { tabIndex: 0, class: {
29047
29504
  'size-container': true,
29048
29505
  'selected': this.selectedSize === size,
29049
29506
  }, onClick: () => this.handleSizeClick(size), onKeyDown: event => this.handleKeyDown(event, size) }, index.h("kritzel-font", { fontFamily: this.fontFamily, size: size, color: color }))))));
@@ -29110,7 +29567,7 @@ const KritzelInput = class {
29110
29567
  this.valueChange.emit(input.value);
29111
29568
  };
29112
29569
  render() {
29113
- return (index.h(index.Host, { key: '3fd1f42a3ad5edfac752c05f70558ef73bbfebc3' }, index.h("div", { key: '78468652ce95508090495fefa9381af175415be8', class: "input-container" }, this.label && index.h("label", { key: 'f368e0370df4848fa9448ed53382152ad8cc8816', class: "input-label" }, this.label), index.h("div", { key: 'c775c0c0a9f4b6c78ba83ced6237a744b7d3cf20', class: { 'input-wrapper': true, 'has-suffix': !!this.suffix } }, index.h("input", { key: 'aa5ce0bb5e3c56755bc6134a4328ce6294bda1ff', type: this.type, class: "text-input", value: this.inputValue, placeholder: this.placeholder, disabled: this.disabled, onInput: this.handleInput }), this.suffix && index.h("span", { key: '687cec4294e4cd10247db88caf157b33a3f16290', class: "input-suffix" }, this.suffix)))));
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)))));
29114
29571
  }
29115
29572
  static get watchers() { return {
29116
29573
  "value": [{
@@ -29250,7 +29707,7 @@ const KritzelLoginDialog = class {
29250
29707
  this.dialogClosed.emit();
29251
29708
  };
29252
29709
  render() {
29253
- return (index.h(index.Host, { key: '8cac83db48fef2531f1669c3f601526b1e5cdefa' }, index.h("kritzel-dialog", { key: '34e7208c8c34550292c2b7503759bf103cfb49a6', dialogTitle: this.dialogTitle, isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, size: "small", contained: true }, index.h("div", { key: 'b0a0d8e0f38adc8d9b9545a02c5fc879f64a24de', class: "login-content" }, this.subtitle && (index.h("p", { key: 'a51b5f0a8b402aaf979d4bf47c6f9c3ba7e14bfe', class: "login-subtitle" }, this.subtitle)), index.h("div", { key: 'b6d8f8748eadf1462dd4161f089130b7ded31b59', class: "login-providers" }, this.providers.map(provider => (index.h("button", { key: provider.name, class: {
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: {
29254
29711
  'provider-button': true,
29255
29712
  'is-loading': this.loadingProvider === provider.name,
29256
29713
  'is-disabled': this.loadingProvider !== null && this.loadingProvider !== provider.name,
@@ -29356,15 +29813,15 @@ const KritzelMasterDetail = class {
29356
29813
  const selectedItem = this.items.find(item => item.id === this.selectedItemId);
29357
29814
  const panelId = 'master-detail-panel';
29358
29815
  const selectedTabId = selectedItem ? `tab-${selectedItem.id}` : undefined;
29359
- return (index.h(index.Host, { key: '59479b50a3e79ee854c75d78e1a41c1cb0551dab' }, index.h("div", { key: '0fdfa2d5a28c8f5800b2eeb80c545bdfcd252f6b', class: {
29816
+ return (index.h(index.Host, { key: '6429c72aaf47f08ba0bdf5e64829b133339dafab' }, index.h("div", { key: '02e4616c67843632d2291abe347ee6ef7a660b12', class: {
29360
29817
  'master-detail-container': true,
29361
29818
  'is-mobile-detail-visible': this.showMobileDetail,
29362
- } }, index.h("nav", { key: 'f055346bdaf528e27136dffc680a5e2c6ddb95a7', 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: {
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: {
29363
29820
  'menu-item': true,
29364
29821
  'is-selected': item.id === this.selectedItemId,
29365
29822
  'is-disabled': !!item.disabled,
29366
29823
  'is-focused': index$1 === this.focusedIndex,
29367
- }, 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: '296ff692092eae9b73c673def66539aa4eb36053', id: panelId, class: "detail-panel", role: "tabpanel", "aria-labelledby": selectedTabId }, index.h("button", { key: '72c65aa2825aa373daa3b1d6fd48d8dd84ab25f1', class: "mobile-back-button", onClick: this.handleBackClick, "aria-label": "Back to menu" }, index.h("kritzel-icon", { key: '2241991dc0da3f53f77dc415e61f026a5734ad48', name: "chevron-left", size: 20, class: "mobile-back-icon" }), "Back"), index.h("slot", { key: '6ecdc24e462faf0d95e295d5536b44ea2f9181c9' })))));
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' })))));
29368
29825
  }
29369
29826
  static get watchers() { return {
29370
29827
  "selectedItemId": [{
@@ -29437,7 +29894,7 @@ const KritzelMenu = class {
29437
29894
  this.itemCloseChildMenu.emit(event.detail);
29438
29895
  };
29439
29896
  render() {
29440
- return (index.h(index.Host, { key: '2d6d46fc8135133ed3e42d65399c8549bc5f6bb5', tabIndex: 0, onClick: e => e.stopPropagation() }, this.openChildMenuItem && index.h("div", { key: 'b5b3910cc82f7cb451730792fe6e3b3a254036f3', 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 })))));
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 })))));
29441
29898
  }
29442
29899
  };
29443
29900
  KritzelMenu.style = kritzelMenuCss();
@@ -29542,12 +29999,12 @@ const KritzelMenuItem = class {
29542
29999
  ];
29543
30000
  }
29544
30001
  render() {
29545
- return (index.h(index.Host, { key: 'ae5057ce9101dc08e2365455c544914715be468e', tabIndex: this.item.isDisabled ? -1 : 0, class: {
30002
+ return (index.h(index.Host, { key: 'ac91e3534c71074634ba053adc02f924e3fb5e14', tabIndex: this.item.isDisabled ? -1 : 0, class: {
29546
30003
  'selected': this.item.isSelected,
29547
30004
  'editing': this.item.isEditing,
29548
30005
  'disabled': this.item.isDisabled,
29549
30006
  'child-open': this.item.isChildMenuOpen,
29550
- }, onClick: this.handleItemSelect }, index.h("div", { key: '973fc68404ea95c2f0459565b8a3d124d31ae090', class: "menu-item-overlay" }), this.item.isEditing ? this.renderEditMode() : this.renderViewMode()));
30007
+ }, onClick: this.handleItemSelect }, index.h("div", { key: 'f49cf8a96f7394e244ce53d3933137325221d578', class: "menu-item-overlay" }), this.item.isEditing ? this.renderEditMode() : this.renderViewMode()));
29551
30008
  }
29552
30009
  static get watchers() { return {
29553
30010
  "item": [{
@@ -29587,6 +30044,8 @@ const KritzelMoreMenu = class {
29587
30044
  * Offset Y for the portal positioning
29588
30045
  */
29589
30046
  offsetY = 4;
30047
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
30048
+ terms = {};
29590
30049
  /**
29591
30050
  * Emitted when a menu item is selected
29592
30051
  */
@@ -29618,7 +30077,7 @@ const KritzelMoreMenu = class {
29618
30077
  this.closeMenu();
29619
30078
  };
29620
30079
  render() {
29621
- return (index.h(index.Host, { key: '93bee9fc14d532a74f1b077098fb0a470655d2fe', class: { mobile: this.isTouchDevice }, style: { display: this.visible ? '' : 'none' } }, index.h("div", { key: '1783013acb533de9580698f29a7c8ae212b583fc', class: { 'more-menu-wrapper': true, visible: this.visible } }, index.h("button", { key: '8dc2f098377e78db0bf6efc05daaf02496cef527', class: "more-menu-button", "data-testid": "more-menu-button", onClick: this.toggleMenu, "aria-label": "More options" }, index.h("kritzel-icon", { key: '876a229226b0f79f1d5ef5d0b7793f362b884923', name: this.icon, size: this.iconSize })), index.h("kritzel-portal", { key: '57f7a69408b00c1bb9e5a08d22e224c6e6bcdea4', anchor: this.menuAnchor, offsetY: this.offsetY, onClose: this.closeMenu }, index.h("kritzel-menu", { key: '73a2aacd1b7c0ec79d7fa1695fbc02b1a0bde1b5', items: this.visibleItems, onItemSelect: this.handleMenuItemSelect })))));
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 })))));
29622
30081
  }
29623
30082
  };
29624
30083
  KritzelMoreMenu.style = kritzelMoreMenuCss();
@@ -29716,7 +30175,7 @@ const KritzelNumericInput = class {
29716
30175
  this.valueChange.emit(newValue);
29717
30176
  };
29718
30177
  render() {
29719
- return (index.h(index.Host, { key: '18cd66a764c334a78e2dccd444f448235b41c38c' }, index.h("div", { key: '73305950e3e840e51466d720a2ef4f834a16adc1', class: "input-container" }, this.label && index.h("label", { key: 'f9521cb0137f5f8c676ced5c76989d8d03256f68', class: "input-label" }, this.label), index.h("div", { key: '07581a242db247a833cfa879b90c6da5dd06b116', class: "input-wrapper" }, index.h("input", { key: '7faa7a9a2c8923e2b2a24c435bc47a03998ac8ad', 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: 'd3920c978a4d97032f5aec550e6ca7e74c9c2e94', class: "spinner-buttons" }, index.h("button", { key: 'ae27375f0ba66a9ca6cf53fe4a05fa219bf47711', type: "button", class: "spinner-button spinner-up", onClick: this.handleIncrement, tabIndex: -1, "aria-label": "Increase value" }, index.h("svg", { key: '8a6e40c12468bb44400c1ad015463c830d0af13e', viewBox: "0 0 10 6", class: "spinner-icon" }, index.h("path", { key: '70b8ee3cc3b00d7f83822078f73e0437e24a98aa', d: "M1 5L5 1L9 5", stroke: "currentColor", "stroke-width": "1.5", fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }))), index.h("button", { key: 'e26968c85480cbf4cd0f3bd9d6db59f1d6ade88c', type: "button", class: "spinner-button spinner-down", onClick: this.handleDecrement, tabIndex: -1, "aria-label": "Decrease value" }, index.h("svg", { key: 'a8c72dd2909b89ceb3797d0a77e531d0b5374e1d', viewBox: "0 0 10 6", class: "spinner-icon" }, index.h("path", { key: '16c95c8cbc90c9d49d081745384c3920620591fb', d: "M1 1L5 5L9 1", stroke: "currentColor", "stroke-width": "1.5", fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }))))))));
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" }))))))));
29720
30179
  }
29721
30180
  static get watchers() { return {
29722
30181
  "value": [{
@@ -29755,7 +30214,7 @@ const KritzelOpacitySlider = class {
29755
30214
  }
29756
30215
  render() {
29757
30216
  const percentage = this.getPercentage();
29758
- return (index.h(index.Host, { key: '988ad7b9ee8d3825c8577d951c6f1d00efbb45eb' }, index.h("div", { key: '4dad6a7d422333fbcaff55b2b374550b496a3adc', class: "opacity-container" }, index.h("div", { key: '7a0ceedd10f2e939dbf189410cc7263c7df498db', class: "slider-wrapper" }, index.h("input", { key: '453e8fbae27023fcbf3c453445fcb8567cb5db00', type: "range", class: "opacity-slider", min: this.min, max: this.max, step: this.step, value: this.value, onInput: (e) => this.handleInput(e), style: {
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: {
29759
30218
  '--slider-progress': `${percentage}%`,
29760
30219
  '--kritzel-opacity-slider-thumb-border-color': this.previewColor,
29761
30220
  } })))));
@@ -30108,7 +30567,7 @@ const KritzelPortal = class {
30108
30567
  this.portal.style.visibility = 'visible';
30109
30568
  }
30110
30569
  render() {
30111
- return (index.h(index.Host, { key: 'ea4b3c0bdaeb94a1aa03714537c3b68c972ad9c5', style: { display: this.anchor ? 'block' : 'none' } }, index.h("slot", { key: 'cd9060be1fbb801e3b72546d465576d5d79b3c82' })));
30570
+ return (index.h(index.Host, { key: 'f4ee3156b094ff1126db417f8396ca6cd0eb9aa4', style: { display: this.anchor ? 'block' : 'none' } }, index.h("slot", { key: '87812f4ddd50a29c8a76938e8778500a4873a30c' })));
30112
30571
  }
30113
30572
  static get watchers() { return {
30114
30573
  "anchor": [{
@@ -30122,7 +30581,7 @@ const KritzelPortal = class {
30122
30581
  * This file is auto-generated by the version bump scripts.
30123
30582
  * Do not modify manually.
30124
30583
  */
30125
- const KRITZEL_VERSION = '0.3.16';
30584
+ const KRITZEL_VERSION = '0.3.17';
30126
30585
 
30127
30586
  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)}`;
30128
30587
 
@@ -30139,13 +30598,7 @@ const DEFAULT_DEBUG_INFO = {
30139
30598
  showSyncProviderInfo: true,
30140
30599
  showMigrationInfo: true,
30141
30600
  };
30142
- const SETTINGS_CATEGORIES = [
30143
- { id: 'general', label: 'General', icon: 'settings' },
30144
- { id: 'viewport', label: 'Viewport', icon: 'viewport' },
30145
- { id: 'shortcuts', label: 'Keyboard Shortcuts', icon: 'command' },
30146
- { id: 'developer', label: 'Developer Options', icon: 'braces' },
30147
- { id: 'about', label: 'About', icon: 'info' },
30148
- ];
30601
+ const SETTINGS_CATEGORY_IDS = ['general', 'viewport', 'shortcuts', 'developer', 'about'];
30149
30602
  const KritzelSettings = class {
30150
30603
  constructor(hostRef) {
30151
30604
  index.registerInstance(this, hostRef);
@@ -30154,7 +30607,11 @@ const KritzelSettings = class {
30154
30607
  get host() { return index.getElement(this); }
30155
30608
  /** Keyboard shortcuts to display in the settings dialog */
30156
30609
  availableThemes = ['light', 'dark'];
30610
+ /** Available locales as `{ code, label }` options for the language selector. */
30611
+ availableLocales = [];
30157
30612
  shortcuts = [];
30613
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
30614
+ terms = {};
30158
30615
  /** Current settings values. Used to initialize and sync the component's internal state. */
30159
30616
  settings;
30160
30617
  onSettingsPropChange(newSettings) {
@@ -30163,11 +30620,12 @@ const KritzelSettings = class {
30163
30620
  }
30164
30621
  }
30165
30622
  isDialogOpen = false;
30166
- selectedCategoryId = SETTINGS_CATEGORIES[0].id;
30623
+ selectedCategoryId = SETTINGS_CATEGORY_IDS[0];
30167
30624
  scaleMin = DEFAULT_SCALE_MIN;
30168
30625
  scaleMax = DEFAULT_SCALE_MAX;
30169
30626
  lockDrawingScale = DEFAULT_LOCK_DRAWING_SCALE;
30170
30627
  theme = 'light';
30628
+ locale = 'en';
30171
30629
  viewportBoundaryLeft = DEFAULT_VIEWPORT_BOUNDARY_LEFT;
30172
30630
  viewportBoundaryRight = DEFAULT_VIEWPORT_BOUNDARY_RIGHT;
30173
30631
  viewportBoundaryTop = DEFAULT_VIEWPORT_BOUNDARY_TOP;
@@ -30193,6 +30651,9 @@ const KritzelSettings = class {
30193
30651
  if (typeof settings.theme === 'string') {
30194
30652
  this.theme = settings.theme;
30195
30653
  }
30654
+ if (typeof settings.locale === 'string') {
30655
+ this.locale = settings.locale;
30656
+ }
30196
30657
  if (typeof settings.viewportBoundaryLeft === 'number') {
30197
30658
  this.viewportBoundaryLeft = settings.viewportBoundaryLeft;
30198
30659
  }
@@ -30215,6 +30676,7 @@ const KritzelSettings = class {
30215
30676
  scaleMax: this.scaleMax,
30216
30677
  lockDrawingScale: this.lockDrawingScale,
30217
30678
  theme: this.theme,
30679
+ locale: this.locale,
30218
30680
  viewportBoundaryLeft: this.viewportBoundaryLeft,
30219
30681
  viewportBoundaryRight: this.viewportBoundaryRight,
30220
30682
  viewportBoundaryTop: this.viewportBoundaryTop,
@@ -30239,6 +30701,10 @@ const KritzelSettings = class {
30239
30701
  this.theme = event.detail;
30240
30702
  this.emitSettings();
30241
30703
  };
30704
+ handleLocaleChange = (event) => {
30705
+ this.locale = event.detail;
30706
+ this.emitSettings();
30707
+ };
30242
30708
  handleViewportBoundaryLeftChange = (event) => {
30243
30709
  this.viewportBoundaryLeft = event.detail ?? DEFAULT_VIEWPORT_BOUNDARY_LEFT;
30244
30710
  this.emitSettings();
@@ -30294,24 +30760,52 @@ const KritzelSettings = class {
30294
30760
  }
30295
30761
  return grouped;
30296
30762
  }
30763
+ /**
30764
+ * Resolves a localized string from the supplied {@link terms} map, falling
30765
+ * back to the provided English default when the key is missing.
30766
+ */
30767
+ t(key, fallback) {
30768
+ return this.terms[key] ?? fallback;
30769
+ }
30770
+ get categories() {
30771
+ const icons = {
30772
+ general: 'settings',
30773
+ viewport: 'viewport',
30774
+ shortcuts: 'command',
30775
+ developer: 'braces',
30776
+ about: 'info',
30777
+ };
30778
+ const labels = {
30779
+ general: { key: 'settings.categories.general', fallback: 'General' },
30780
+ viewport: { key: 'settings.categories.viewport', fallback: 'Viewport' },
30781
+ shortcuts: { key: 'settings.categories.shortcuts', fallback: 'Keyboard Shortcuts' },
30782
+ developer: { key: 'settings.categories.developer', fallback: 'Developer Options' },
30783
+ about: { key: 'settings.categories.about', fallback: 'About' },
30784
+ };
30785
+ return SETTINGS_CATEGORY_IDS.map(id => ({
30786
+ id,
30787
+ label: this.t(labels[id].key, labels[id].fallback),
30788
+ icon: icons[id],
30789
+ }));
30790
+ }
30297
30791
  renderCategoryContent() {
30298
30792
  switch (this.selectedCategoryId) {
30299
30793
  case 'general':
30300
- return (index.h("div", { class: "settings-content" }, index.h("h3", null, "General Settings"), index.h("div", { class: "settings-group" }, index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, "Theme"), index.h("p", { class: "settings-description" }, "Select a registered color theme for the editor interface."), index.h("kritzel-dropdown", { options: this.availableThemes.map(t => ({ value: t, label: t })), value: this.theme, onValueChanged: this.handleThemeChange })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, "Lock Drawing Scale"), index.h("p", { class: "settings-description" }, "When enabled, drawn objects maintain a fixed visual size regardless of the current zoom level."), index.h("kritzel-slide-toggle", { checked: this.lockDrawingScale, label: "Lock Drawing Scale", onCheckedChange: this.handleLockDrawingScaleChange })))));
30794
+ return (index.h("div", { class: "settings-content" }, index.h("h3", null, this.t('settings.general.title', 'General Settings')), index.h("div", { class: "settings-group" }, index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, this.t('settings.general.theme.label', 'Theme')), index.h("p", { class: "settings-description" }, this.t('settings.general.theme.description', 'Select a registered color theme for the editor interface.')), index.h("kritzel-dropdown", { options: this.availableThemes.map(t => ({ value: t, label: t })), value: this.theme, onValueChanged: this.handleThemeChange })), this.availableLocales.length > 0 && (index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, this.t('settings.general.language.label', 'Language')), index.h("p", { class: "settings-description" }, this.t('settings.general.language.description', 'Select the display language for the editor interface.')), index.h("kritzel-dropdown", { options: this.availableLocales.map(l => ({ value: l.code, label: l.label })), value: this.locale, onValueChanged: this.handleLocaleChange }))), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, this.t('settings.general.lockDrawingScale.label', 'Lock Drawing Scale')), index.h("p", { class: "settings-description" }, this.t('settings.general.lockDrawingScale.description', 'When enabled, drawn objects maintain a fixed visual size regardless of the current zoom level.')), index.h("kritzel-slide-toggle", { checked: this.lockDrawingScale, label: this.t('settings.general.lockDrawingScale.label', 'Lock Drawing Scale'), onCheckedChange: this.handleLockDrawingScaleChange })))));
30301
30795
  case 'viewport':
30302
- return (index.h("div", { class: "settings-content" }, index.h("h3", null, "Viewport Settings"), index.h("div", { class: "settings-group" }, index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, "Minimum Zoom Level"), index.h("p", { class: "settings-description" }, "Sets the minimum zoom level. Lower values allow zooming out further to see more of the canvas."), index.h("kritzel-numeric-input", { value: this.scaleMin, min: 0.0001, max: 1, step: 0.0001, onValueChange: this.handleScaleMinChange })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, "Maximum Zoom Level"), index.h("p", { class: "settings-description" }, "Sets the maximum zoom level. Higher values allow zooming in closer for detailed work."), index.h("kritzel-numeric-input", { value: this.scaleMax, min: 1, max: 1000, step: 1, onValueChange: this.handleScaleMaxChange })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, "Viewport Boundary Left"), index.h("p", { class: "settings-description" }, "Left boundary in world coordinates. Set to limit how far left the viewport can pan."), index.h("kritzel-numeric-input", { value: this.viewportBoundaryLeft, step: 100, placeholder: "Infinite", onValueChange: this.handleViewportBoundaryLeftChange })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, "Viewport Boundary Right"), index.h("p", { class: "settings-description" }, "Right boundary in world coordinates. Set to limit how far right the viewport can pan."), index.h("kritzel-numeric-input", { value: this.viewportBoundaryRight, step: 100, placeholder: "Infinite", onValueChange: this.handleViewportBoundaryRightChange })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, "Viewport Boundary Top"), index.h("p", { class: "settings-description" }, "Top boundary in world coordinates. Set to limit how far up the viewport can pan."), index.h("kritzel-numeric-input", { value: this.viewportBoundaryTop, step: 100, placeholder: "Infinite", onValueChange: this.handleViewportBoundaryTopChange })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, "Viewport Boundary Bottom"), index.h("p", { class: "settings-description" }, "Bottom boundary in world coordinates. Set to limit how far down the viewport can pan."), index.h("kritzel-numeric-input", { value: this.viewportBoundaryBottom, step: 100, placeholder: "Infinite", onValueChange: this.handleViewportBoundaryBottomChange })))));
30796
+ return (index.h("div", { class: "settings-content" }, index.h("h3", null, this.t('settings.viewport.title', 'Viewport Settings')), index.h("div", { class: "settings-group" }, index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, this.t('settings.viewport.minZoom.label', 'Minimum Zoom Level')), index.h("p", { class: "settings-description" }, this.t('settings.viewport.minZoom.description', 'Sets the minimum zoom level. Lower values allow zooming out further to see more of the canvas.')), index.h("kritzel-numeric-input", { value: this.scaleMin, min: 0.0001, max: 1, step: 0.0001, onValueChange: this.handleScaleMinChange })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, this.t('settings.viewport.maxZoom.label', 'Maximum Zoom Level')), index.h("p", { class: "settings-description" }, this.t('settings.viewport.maxZoom.description', 'Sets the maximum zoom level. Higher values allow zooming in closer for detailed work.')), index.h("kritzel-numeric-input", { value: this.scaleMax, min: 1, max: 1000, step: 1, onValueChange: this.handleScaleMaxChange })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, this.t('settings.viewport.boundaryLeft.label', 'Viewport Boundary Left')), index.h("p", { class: "settings-description" }, this.t('settings.viewport.boundaryLeft.description', 'Left boundary in world coordinates. Set to limit how far left the viewport can pan.')), index.h("kritzel-numeric-input", { value: this.viewportBoundaryLeft, step: 100, placeholder: this.t('settings.viewport.boundaryPlaceholder', 'Infinite'), onValueChange: this.handleViewportBoundaryLeftChange })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, this.t('settings.viewport.boundaryRight.label', 'Viewport Boundary Right')), index.h("p", { class: "settings-description" }, this.t('settings.viewport.boundaryRight.description', 'Right boundary in world coordinates. Set to limit how far right the viewport can pan.')), index.h("kritzel-numeric-input", { value: this.viewportBoundaryRight, step: 100, placeholder: this.t('settings.viewport.boundaryPlaceholder', 'Infinite'), onValueChange: this.handleViewportBoundaryRightChange })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, this.t('settings.viewport.boundaryTop.label', 'Viewport Boundary Top')), index.h("p", { class: "settings-description" }, this.t('settings.viewport.boundaryTop.description', 'Top boundary in world coordinates. Set to limit how far up the viewport can pan.')), index.h("kritzel-numeric-input", { value: this.viewportBoundaryTop, step: 100, placeholder: this.t('settings.viewport.boundaryPlaceholder', 'Infinite'), onValueChange: this.handleViewportBoundaryTopChange })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, this.t('settings.viewport.boundaryBottom.label', 'Viewport Boundary Bottom')), index.h("p", { class: "settings-description" }, this.t('settings.viewport.boundaryBottom.description', 'Bottom boundary in world coordinates. Set to limit how far down the viewport can pan.')), index.h("kritzel-numeric-input", { value: this.viewportBoundaryBottom, step: 100, placeholder: this.t('settings.viewport.boundaryPlaceholder', 'Infinite'), onValueChange: this.handleViewportBoundaryBottomChange })))));
30303
30797
  case 'shortcuts':
30304
- return (index.h("div", { class: "settings-content" }, index.h("h3", null, "Keyboard Shortcuts"), index.h("div", { class: "shortcuts-list" }, Array.from(this.groupShortcutsByCategory()).map(([category, shortcuts]) => (index.h("div", { class: "shortcuts-category", key: category }, index.h("h4", { class: "shortcuts-category-title" }, category), index.h("div", { class: "shortcuts-group" }, shortcuts.map(shortcut => (index.h("div", { class: "shortcut-item", key: shortcut.key + shortcut.label }, index.h("span", { class: "shortcut-label" }, shortcut.label), index.h("kbd", { class: "shortcut-key" }, this.formatKeyCombo(shortcut))))))))))));
30798
+ return (index.h("div", { class: "settings-content" }, index.h("h3", null, this.t('settings.shortcuts.title', 'Keyboard Shortcuts')), index.h("div", { class: "shortcuts-list" }, Array.from(this.groupShortcutsByCategory()).map(([category, shortcuts]) => (index.h("div", { class: "shortcuts-category", key: category }, index.h("h4", { class: "shortcuts-category-title" }, category), index.h("div", { class: "shortcuts-group" }, shortcuts.map(shortcut => (index.h("div", { class: "shortcut-item", key: shortcut.key + shortcut.label }, index.h("span", { class: "shortcut-label" }, shortcut.label), index.h("kbd", { class: "shortcut-key" }, this.formatKeyCombo(shortcut))))))))))));
30305
30799
  case 'developer':
30306
- return (index.h("div", { class: "settings-content" }, index.h("h3", null, "Developer Options"), index.h("div", { class: "settings-group" }, index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, "Show Viewport Info"), index.h("p", { class: "settings-description" }, "Display viewport debug information such as position, zoom level, and boundaries."), index.h("kritzel-slide-toggle", { checked: this.debugInfo.showViewportInfo, label: "Show Viewport Info", onCheckedChange: this.handleDebugInfoChange('showViewportInfo') })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, "Show Object Info"), index.h("p", { class: "settings-description" }, "Display debug information about objects on the canvas."), index.h("kritzel-slide-toggle", { checked: this.debugInfo.showObjectInfo, label: "Show Object Info", onCheckedChange: this.handleDebugInfoChange('showObjectInfo') })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, "Show Sync Provider Info"), index.h("p", { class: "settings-description" }, "Display debug information about the sync provider connection status."), index.h("kritzel-slide-toggle", { checked: this.debugInfo.showSyncProviderInfo, label: "Show Sync Provider Info", onCheckedChange: this.handleDebugInfoChange('showSyncProviderInfo') })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, "Show Migration Info"), index.h("p", { class: "settings-description" }, "Display debug information about data migrations."), index.h("kritzel-slide-toggle", { checked: this.debugInfo.showMigrationInfo, label: "Show Migration Info", onCheckedChange: this.handleDebugInfoChange('showMigrationInfo') })))));
30800
+ return (index.h("div", { class: "settings-content" }, index.h("h3", null, this.t('settings.developer.title', 'Developer Options')), index.h("div", { class: "settings-group" }, index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, this.t('settings.developer.showViewportInfo.label', 'Show Viewport Info')), index.h("p", { class: "settings-description" }, this.t('settings.developer.showViewportInfo.description', 'Display viewport debug information such as position, zoom level, and boundaries.')), index.h("kritzel-slide-toggle", { checked: this.debugInfo.showViewportInfo, label: this.t('settings.developer.showViewportInfo.label', 'Show Viewport Info'), onCheckedChange: this.handleDebugInfoChange('showViewportInfo') })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, this.t('settings.developer.showObjectInfo.label', 'Show Object Info')), index.h("p", { class: "settings-description" }, this.t('settings.developer.showObjectInfo.description', 'Display debug information about objects on the canvas.')), index.h("kritzel-slide-toggle", { checked: this.debugInfo.showObjectInfo, label: this.t('settings.developer.showObjectInfo.label', 'Show Object Info'), onCheckedChange: this.handleDebugInfoChange('showObjectInfo') })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, this.t('settings.developer.showSyncProviderInfo.label', 'Show Sync Provider Info')), index.h("p", { class: "settings-description" }, this.t('settings.developer.showSyncProviderInfo.description', 'Display debug information about the sync provider connection status.')), index.h("kritzel-slide-toggle", { checked: this.debugInfo.showSyncProviderInfo, label: this.t('settings.developer.showSyncProviderInfo.label', 'Show Sync Provider Info'), onCheckedChange: this.handleDebugInfoChange('showSyncProviderInfo') })), index.h("div", { class: "settings-item" }, index.h("label", { class: "settings-label" }, this.t('settings.developer.showMigrationInfo.label', 'Show Migration Info')), index.h("p", { class: "settings-description" }, this.t('settings.developer.showMigrationInfo.description', 'Display debug information about data migrations.')), index.h("kritzel-slide-toggle", { checked: this.debugInfo.showMigrationInfo, label: this.t('settings.developer.showMigrationInfo.label', 'Show Migration Info'), onCheckedChange: this.handleDebugInfoChange('showMigrationInfo') })))));
30307
30801
  case 'about':
30308
- return (index.h("div", { class: "settings-content" }, index.h("h3", null, "About"), index.h("p", null, "Kritzel - A drawing application"), index.h("p", { class: "version-info" }, "Version ", KRITZEL_VERSION), index.h("p", { class: "version-info" }, "App-State Schema v", schema_constants.CURRENT_APP_STATE_SCHEMA_VERSION), index.h("p", { class: "version-info" }, "Workspace Schema v", schema_constants.CURRENT_WORKSPACE_SCHEMA_VERSION)));
30802
+ return (index.h("div", { class: "settings-content" }, index.h("h3", null, this.t('settings.about.title', 'About')), index.h("p", null, this.t('settings.about.description', 'Kritzel - A drawing application')), index.h("p", { class: "version-info" }, "Version ", KRITZEL_VERSION), index.h("p", { class: "version-info" }, "App-State Schema v", schema_constants.CURRENT_APP_STATE_SCHEMA_VERSION), index.h("p", { class: "version-info" }, "Workspace Schema v", schema_constants.CURRENT_WORKSPACE_SCHEMA_VERSION)));
30309
30803
  default:
30310
30804
  return null;
30311
30805
  }
30312
30806
  }
30313
30807
  render() {
30314
- return (index.h(index.Host, { key: '46c6792ae9cdd932d3dc71526862c9281c0cefc1' }, index.h("kritzel-dialog", { key: '1cd288cdf8b26bea378665c54bfc14577597fe49', isOpen: this.isDialogOpen, dialogTitle: "Settings", size: "large", contained: true, onDialogClose: this.closeDialog }, index.h("kritzel-master-detail", { key: '4d07e94ebb09035807356bab4bc7eaca57c36c6c', items: SETTINGS_CATEGORIES, selectedItemId: this.selectedCategoryId, onItemSelect: this.handleCategorySelect }, this.renderCategoryContent()))));
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()))));
30315
30809
  }
30316
30810
  static get watchers() { return {
30317
30811
  "settings": [{
@@ -30372,6 +30866,8 @@ const KritzelShareDialog = class {
30372
30866
  * The ID of the workspace being shared. Used to build the share URL.
30373
30867
  */
30374
30868
  workspaceId = undefined;
30869
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
30870
+ terms = {};
30375
30871
  onIsPublicChange(newValue) {
30376
30872
  this.internalIsPublic = newValue;
30377
30873
  }
@@ -30442,9 +30938,9 @@ const KritzelShareDialog = class {
30442
30938
  this.dialogClosed.emit();
30443
30939
  };
30444
30940
  render() {
30445
- return (index.h(index.Host, { key: 'a104c14b2492d97f3ada98c9eaaa845d63074063' }, index.h("kritzel-dialog", { key: '1b12b27504153e54aeb0cb4e6b1030a0d43b9735', dialogTitle: "Share Workspace", size: "small", isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, contained: true }, index.h("div", { key: '652f23e37876be356beb6f93abf5930e91d82cea', class: "share-content" }, index.h("div", { key: 'aaf336f2ac86fe23cac79cef920a9d67681046e2', class: "share-section" }, index.h("div", { key: '8075a7b3fff47c4b924d3b2d92b1377641920939', class: "share-row" }, index.h("div", { key: '41e98a74a5d4aede50fd75a7de62cbef9b5a5a31', class: "share-label-group" }, index.h("label", { key: '8a4f53e13d5a81497dd31316a49971c7245d82a2', class: "share-label" }, "Link sharing"), index.h("p", { key: '907a59d50e595734f03067f70830cf96defdf8d8', class: "share-description" }, this.internalIsPublic
30446
- ? 'Anyone with the link can access this workspace.'
30447
- : 'Link sharing is disabled. Only you can access this workspace.')), index.h("kritzel-slide-toggle", { key: '0d75cfeeb63c33d20380ffe9a7e4c27148548ef9', checked: this.internalIsPublic, onCheckedChange: this.handleToggleChange, label: "Enable link sharing" }))), this.internalIsPublic && (index.h("div", { key: 'a8a10c74fd326c5097e4a5f0ee165602c3606ade', class: "share-section" }, index.h("div", { key: '6261a9fc6cb2be2a50856fb8a990b9da3fee84bf', class: "share-url-container" }, index.h("input", { key: '26ee72eebfee88d06a50c338cccc9af296c8ba4c', type: "text", class: "share-url-input", value: this.getShareUrl(), readOnly: true, onClick: (e) => e.target.select() }), index.h("button", { key: '4b1ec06fa27c95d9d0bb93f8cbb851a02fdd52cc', class: { 'copy-button': true, 'copy-success': this.copySuccess }, onClick: this.handleCopyUrl, title: this.copySuccess ? 'Copied!' : 'Copy link' }, index.h("kritzel-icon", { key: 'd9eea56b3523fcc3557d9868f3da7f28449a9447', name: this.copySuccess ? 'check' : 'copy', size: 18 })))))))));
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
30942
+ ? (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 })))))))));
30448
30944
  }
30449
30945
  static get watchers() { return {
30450
30946
  "isPublic": [{
@@ -30482,7 +30978,7 @@ const KritzelSlideToggle = class {
30482
30978
  }
30483
30979
  };
30484
30980
  render() {
30485
- return (index.h(index.Host, { key: '8a1f816240f815905cc7def7cac92eb7ddac0df2', 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: 'd5fa5091ad54032f81dad3879149c4d8ec7ea37b', class: "toggle-track" }, index.h("div", { key: 'cce0d61431ed65a26926b0a496a5c22eb4169577', class: "toggle-thumb" }))));
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" }))));
30486
30982
  }
30487
30983
  };
30488
30984
  KritzelSlideToggle.style = kritzelSlideToggleCss();
@@ -30582,7 +31078,7 @@ const KritzelSplitButton = class {
30582
31078
  this.menuScrollTop = event.target.scrollTop;
30583
31079
  };
30584
31080
  render() {
30585
- return (index.h(index.Host, { key: '794fdb5cb4d110d93b6b2cb060fe34241f29db57', class: { mobile: this.isTouchDevice } }, index.h("button", { key: '7202a40f05bf6fc256996a05db55bcfa3baba615', class: "split-main-button", tabIndex: 0, onClick: this.handleButtonClick, disabled: this.mainButtonDisabled, "aria-label": "Main action" }, this.buttonIcon && index.h("kritzel-icon", { key: '3156c6c4e757d9ebbd3f5e3719ee1bf9bf81f71b', name: this.buttonIcon })), index.h("div", { key: '4c5a3a9791ecfd00d36fc0eb885c1d227200cfc7', class: "split-divider" }), index.h("button", { key: 'fe0ee44d11ddb34f7719b986406905c47bc1f152', 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: 'b3d84e4599dc408ccc8afe17e487b501cbde89a4', name: this.dropdownIcon })), index.h("kritzel-portal", { key: '6800329ebe3c94a661e9ad852b6bf256defc291f', anchor: this.anchorElement, offsetY: 4, onClose: this.closeMenu }, index.h("kritzel-menu", { key: '0c98abe2327e0cc3182a55caf50c89316b526049', 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 }))));
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 }))));
30586
31082
  }
30587
31083
  };
30588
31084
  KritzelSplitButton.style = kritzelSplitButtonCss();
@@ -30602,7 +31098,8 @@ const KritzelStrokeSize = class {
30602
31098
  this.sizeChange.emit(size);
30603
31099
  }
30604
31100
  render() {
30605
- return (index.h(index.Host, { key: '514d87732c9b15cddd5a905407ff7ce9069c06d7' }, index.h("div", { key: '170c9a8abfe8298116d8a269338da95fbc2aac7b', class: "size-grid" }, this.sizes.map(size => (index.h("div", { tabIndex: 0, class: {
31101
+ 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: {
30606
31103
  'size-container': true,
30607
31104
  'selected': this.selectedSize === size,
30608
31105
  }, onClick: () => this.handleSizeClick(size) }, index.h("kritzel-color", { value: 'var(--kritzel-global-text-primary)', size: size })))))));
@@ -30651,6 +31148,8 @@ const KritzelToolConfig = class {
30651
31148
  isExpanded = false;
30652
31149
  theme;
30653
31150
  engine;
31151
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
31152
+ terms = {};
30654
31153
  handleSelectionChangeBound = this.handleSelectionChange.bind(this);
30655
31154
  onThemeChange() {
30656
31155
  this.emitDisplayValues();
@@ -30793,9 +31292,9 @@ const KritzelToolConfig = class {
30793
31292
  const value = this.tool[control.propertyName];
30794
31293
  switch (control.type) {
30795
31294
  case 'stroke-size':
30796
- return (index.h("kritzel-stroke-size", { key: control.type, sizes: this.sizes.length > 0 ? this.sizes : undefined, selectedSize: value, onSizeChange: this.handleSizeChange }));
31295
+ return (index.h("kritzel-stroke-size", { key: control.type, sizes: this.sizes, selectedSize: value, onSizeChange: this.handleSizeChange }));
30797
31296
  case 'font-size':
30798
- return (index.h("kritzel-font-size", { key: control.type, sizes: this.sizes.length > 0 ? this.sizes : undefined, selectedSize: value, fontFamily: this.tool.fontFamily, onSizeChange: this.handleSizeChange }));
31297
+ return (index.h("kritzel-font-size", { key: control.type, sizes: this.sizes, selectedSize: value, fontFamily: this.tool.fontFamily, onSizeChange: this.handleSizeChange }));
30799
31298
  case 'line-endings':
30800
31299
  return (index.h("kritzel-line-endings", { key: control.type, value: value, onValueChange: (event) => this.handlePropertyChange(control.propertyName, event.detail) }));
30801
31300
  case 'shape-fill':
@@ -30830,7 +31329,7 @@ const KritzelToolConfig = class {
30830
31329
  ])), shouldShowExpandButton && (index.h("div", { style: {
30831
31330
  display: 'flex',
30832
31331
  alignItems: 'flex-start',
30833
- } }, index.h("button", { class: "expand-toggle", onClick: this.handleToggleExpand, title: this.isExpanded ? 'Collapse' : 'Expand' }, index.h("kritzel-icon", { name: this.isExpanded ? 'chevron-up' : 'chevron-down' })))))));
31332
+ } }, index.h("button", { class: "expand-toggle", onClick: this.handleToggleExpand, title: this.isExpanded ? (this.terms['toolConfig.collapse'] ?? 'Collapse') : (this.terms['toolConfig.expand'] ?? 'Expand') }, index.h("kritzel-icon", { name: this.isExpanded ? 'chevron-up' : 'chevron-down' })))))));
30834
31333
  }
30835
31334
  static get watchers() { return {
30836
31335
  "tool": [{
@@ -30981,14 +31480,14 @@ const KritzelTooltip = class {
30981
31480
  }
30982
31481
  }
30983
31482
  render() {
30984
- return (index.h(index.Host, { key: '10bff4d14ff1f724d59463afc059f254e9485175', style: {
31483
+ return (index.h(index.Host, { key: '57c0c2ddbe3f88815b975b7cf5b02c7da389b7f9', style: {
30985
31484
  position: 'fixed',
30986
31485
  zIndex: '9999',
30987
31486
  transition: 'opacity 0.3s ease-in-out, transform 0.3s ease-in-out',
30988
31487
  visibility: this.isVisible ? 'visible' : 'hidden',
30989
31488
  left: `${this.positionX}px`,
30990
31489
  bottom: `${this.positionY}px`,
30991
- } }, index.h("div", { key: '6bfc8f2fe731d758c74319abeb82c4b84616f8b6', class: "tooltip-content", onClick: event => event.stopPropagation(), onPointerDown: event => event.stopPropagation(), onMouseDown: event => event.stopPropagation() }, index.h("slot", { key: '2a92ad23f4424b2d17035f86983be66a81fee81d' }))));
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' }))));
30992
31491
  }
30993
31492
  static get watchers() { return {
30994
31493
  "triggerElement": [{
@@ -31011,6 +31510,8 @@ const KritzelUtilityPanel = class {
31011
31510
  this.delete = index.createEvent(this, "delete");
31012
31511
  }
31013
31512
  undoState = null;
31513
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
31514
+ terms = {};
31014
31515
  undo;
31015
31516
  redo;
31016
31517
  delete;
@@ -31027,11 +31528,32 @@ const KritzelUtilityPanel = class {
31027
31528
  this.redo.emit();
31028
31529
  }
31029
31530
  render() {
31030
- return (index.h(index.Host, { key: 'b49f6db6c0e574dc8a5a733c749ecda6f24f9d25' }, index.h("button", { key: 'e6306e54c8f660c3e92d032527fad1ea45ca0cf8', class: "utility-button", "data-testid": "utility-undo", disabled: !this.undoState?.canUndo, onClick: event => this.handleUndo(event), "aria-label": "Undo" }, index.h("kritzel-icon", { key: '5bb1293049a1e3004504289d92ccc79958786f3f', name: "undo" })), index.h("button", { key: '8102b0403d7f328ce4bfeb79767d5bd99d879013', class: "utility-button", "data-testid": "utility-redo", disabled: !this.undoState?.canRedo, onClick: event => this.handleRedo(event), "aria-label": "Redo" }, index.h("kritzel-icon", { key: '5db3047bec5d8ab695a2dc67780a5dbecbae64d2', name: "redo" })), index.h("div", { key: 'e894d9f2aaa2cad7aa980d3b839eca05a8d9c9df', class: "utility-separator" }), index.h("button", { key: 'f0a7de5ab91f82a2e5e8df75cc1903ec647abdac', class: "utility-button", "data-testid": "utility-delete", onClick: () => this.delete.emit(), "aria-label": "Delete selected items" }, index.h("kritzel-icon", { key: '5b146375394299bae946a95545c3c42c2bf36766', name: "delete" }))));
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" }))));
31031
31532
  }
31032
31533
  };
31033
31534
  KritzelUtilityPanel.style = kritzelUtilityPanelCss();
31034
31535
 
31536
+ const kritzelWatermarkCss = () => `:host{position:absolute;bottom:var(--kritzel-watermark-offset-bottom, 12px);right:var(--kritzel-watermark-offset-right, 12px);z-index:10000;pointer-events:none}.watermark-link{pointer-events:auto;display:inline-flex;align-items:center;padding:4px 8px;border-radius:var(--kritzel-watermark-border-radius, 6px);font-family:var(--kritzel-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);font-size:var(--kritzel-watermark-font-size, 11px);line-height:1;text-decoration:none;color:var(--kritzel-watermark-color, rgba(60, 60, 60, 0.75));background-color:var(--kritzel-watermark-background, rgba(255, 255, 255, 0.6));box-shadow:var(--kritzel-watermark-box-shadow, 0 1px 2px rgba(0, 0, 0, 0.12));opacity:var(--kritzel-watermark-opacity, 0.85);transition:opacity 150ms ease;user-select:none}.watermark-link:hover{opacity:1}`;
31537
+
31538
+ const KritzelWatermark = class {
31539
+ constructor(hostRef) {
31540
+ index.registerInstance(this, hostRef);
31541
+ }
31542
+ /** The core instance used to resolve localized terms. */
31543
+ core;
31544
+ /**
31545
+ * Resolved "Powered by Kritzel" label. Passed in by the engine so the badge
31546
+ * re-renders when the active locale changes. Falls back to resolving the term
31547
+ * from the core when not provided.
31548
+ */
31549
+ label;
31550
+ render() {
31551
+ 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)));
31553
+ }
31554
+ };
31555
+ KritzelWatermark.style = kritzelWatermarkCss();
31556
+
31035
31557
  const kritzelWorkspaceManagerCss = () => `:host{display:flex;flex-direction:column;z-index:1}.manager{opacity:0;pointer-events:none;transition:opacity 0.2s ease-out}.manager.visible{opacity:1;pointer-events:auto}`;
31036
31558
 
31037
31559
  const KritzelWorkspaceManager = class {
@@ -31047,6 +31569,8 @@ const KritzelWorkspaceManager = class {
31047
31569
  visible = false;
31048
31570
  activeWorkspace;
31049
31571
  workspaces = [];
31572
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
31573
+ terms = {};
31050
31574
  isWorkspaceManagerReady;
31051
31575
  workspaceChange;
31052
31576
  childMenuAnchor = null;
@@ -31150,7 +31674,7 @@ const KritzelWorkspaceManager = class {
31150
31674
  id: ws.id,
31151
31675
  label: ws.name,
31152
31676
  icon: ws.isPublic ? 'users-round' : undefined,
31153
- iconTooltip: ws.isPublic ? 'Shared workspace' : undefined,
31677
+ iconTooltip: ws.isPublic ? (this.terms['workspace.sharedTooltip'] ?? 'Shared workspace') : undefined,
31154
31678
  value: ws,
31155
31679
  isEditing: this.editingItemId === ws.id,
31156
31680
  isSelected: this.activeWorkspace?.id === ws.id,
@@ -31160,13 +31684,13 @@ const KritzelWorkspaceManager = class {
31160
31684
  children: [
31161
31685
  {
31162
31686
  id: `${ws.id}-rename`,
31163
- label: 'Rename',
31687
+ label: this.terms['workspace.rename'] ?? 'Rename',
31164
31688
  value: 'rename',
31165
31689
  action: (_item, parent) => this.edit(parent),
31166
31690
  },
31167
31691
  {
31168
31692
  id: `${ws.id}-delete`,
31169
- label: 'Delete',
31693
+ label: this.terms['workspace.delete'] ?? 'Delete',
31170
31694
  value: 'delete',
31171
31695
  isDisabled: this.sortedWorkspaces.length <= 1,
31172
31696
  action: (_item, parent) => this.delete(parent),
@@ -31179,6 +31703,45 @@ const KritzelWorkspaceManager = class {
31179
31703
  };
31180
31704
  KritzelWorkspaceManager.style = kritzelWorkspaceManagerCss();
31181
31705
 
31706
+ const kritzelZoomPanelCss = () => `:host{display:block;z-index:1}.panel{display:flex;flex-direction:row;align-items:center;gap:var(--kritzel-zoom-panel-gap, 4px);padding:var(--kritzel-zoom-panel-padding, 4px);border:var(--kritzel-zoom-panel-border, 1px solid #ebebeb);border-radius:var(--kritzel-zoom-panel-border-radius, 12px);background-color:var(--kritzel-zoom-panel-background-color, #ffffff);box-shadow:var(--kritzel-zoom-panel-box-shadow, 0 0 3px rgba(0, 0, 0, 0.08));opacity:0;pointer-events:none;transition:opacity 0.2s ease-out}.panel.visible{opacity:1;pointer-events:auto}.zoom-level{display:inline-flex;align-items:center;justify-content:center;min-width:46px;color:var(--kritzel-zoom-panel-icon-color, #000000);font-family:var(--kritzel-font-family, sans-serif);font-size:12px;font-weight:500;line-height:1;padding:0 4px;user-select:none}.zoom-button{display:flex;align-items:center;justify-content:center;width:var(--kritzel-zoom-panel-button-size, 32px);height:var(--kritzel-zoom-panel-button-size, 32px);padding:0;border:0;border-radius:var(--kritzel-zoom-panel-button-border-radius, 8px);background:transparent;background-color:transparent;color:var(--kritzel-zoom-panel-icon-color, #000000);--kritzel-icon-color:var(--kritzel-zoom-panel-icon-color, #000000);box-shadow:none;appearance:none;cursor:var(--kritzel-global-pointer-cursor, pointer);-webkit-tap-highlight-color:transparent}.zoom-button:hover,.zoom-button:focus-visible{background-color:var(--kritzel-zoom-panel-button-hover-background-color, hsl(0, 0%, 0%, 4.3%))}.zoom-button:active{background-color:var(--kritzel-zoom-panel-button-active-background-color, hsl(0, 0%, 0%, 8.6%))}.zoom-button:disabled{opacity:0.5;cursor:not-allowed;pointer-events:none}`;
31707
+
31708
+ const KritzelZoomPanel = class {
31709
+ constructor(hostRef) {
31710
+ index.registerInstance(this, hostRef);
31711
+ this.zoomIn = index.createEvent(this, "zoomIn");
31712
+ this.zoomOut = index.createEvent(this, "zoomOut");
31713
+ }
31714
+ /** Whether the zoom panel is visible. */
31715
+ visible = true;
31716
+ /** Whether both zoom buttons are disabled. */
31717
+ disabled = false;
31718
+ /** Current zoom level in percent. */
31719
+ zoomPercent = 100;
31720
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
31721
+ terms = {};
31722
+ /** Emitted when the zoom-in button is clicked. */
31723
+ zoomIn;
31724
+ /** Emitted when the zoom-out button is clicked. */
31725
+ zoomOut;
31726
+ handleZoomIn = () => {
31727
+ if (!this.disabled) {
31728
+ this.zoomIn.emit();
31729
+ }
31730
+ };
31731
+ handleZoomOut = () => {
31732
+ if (!this.disabled) {
31733
+ this.zoomOut.emit();
31734
+ }
31735
+ };
31736
+ get normalizedZoomPercent() {
31737
+ return Number.isFinite(this.zoomPercent) ? Math.max(1, Math.round(this.zoomPercent)) : 100;
31738
+ }
31739
+ 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" })))));
31741
+ }
31742
+ };
31743
+ KritzelZoomPanel.style = kritzelZoomPanelCss();
31744
+
31182
31745
  exports.kritzel_active_users = KritzelActiveUsers;
31183
31746
  exports.kritzel_avatar = KritzelAvatar;
31184
31747
  exports.kritzel_awareness_cursors = KritzelAwarenessCursors;
@@ -31220,4 +31783,6 @@ exports.kritzel_stroke_size = KritzelStrokeSize;
31220
31783
  exports.kritzel_tool_config = KritzelToolConfig;
31221
31784
  exports.kritzel_tooltip = KritzelTooltip;
31222
31785
  exports.kritzel_utility_panel = KritzelUtilityPanel;
31786
+ exports.kritzel_watermark = KritzelWatermark;
31223
31787
  exports.kritzel_workspace_manager = KritzelWorkspaceManager;
31788
+ exports.kritzel_zoom_panel = KritzelZoomPanel;