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,5 +1,5 @@
1
1
  import { r as registerInstance, h, H as Host, c as createEvent, g as getElement } from './index-Dhio9uis.js';
2
- import { c as KritzelPath, e as KritzelLine, G as KritzelColorHelper, J as KritzelDevicesHelper, o as KritzelSelectionTool, l as KritzelTextTool, L as KritzelMouseButton, M as DEFAULT_STROKE_SIZES, N as DEFAULT_COLOR_PALETTE, S as ShapeType, D as DEFAULT_BRUSH_CONFIG, h as KritzelBrushTool, j as KritzelEraserTool, u as DEFAULT_LINE_TOOL_CONFIG, i as KritzelLineTool, m as KritzelShapeTool, t as DEFAULT_TEXT_CONFIG, k as KritzelImageTool, y as KritzelAlignment, v as DEFAULT_ASSET_STORAGE_CONFIG, T as ThemeHelper, x as darkTheme, w as lightTheme, O as KritzelSelectionGroup, P as KritzelSelectionBox, Q as KritzelIconRegistry, R as KritzelKeyboardHelper, U as KritzelBaseHandler, K as KritzelBaseObject, q as KritzelWorkspace, f as KritzelGroup, d as KritzelImage, g as KritzelShape, b as KritzelText, z as runMigrations, F as CURRENT_WORKSPACE_SCHEMA_VERSION, C as WORKSPACE_MIGRATIONS, E as CURRENT_APP_STATE_SCHEMA_VERSION, B as APP_STATE_MIGRATIONS, V as ObjectHelper, n as KritzelCursorHelper, r as KritzelAnchorManager, s as KritzelThemeManager, p as KritzelAssetResolver, X as KritzelClassHelper, Y as KritzelEventHelper, W as WORKSPACE_EXPORT_VERSION } from './schema.constants-DiCnmIYK.js';
2
+ import { c as KritzelPath, e as KritzelLine, P as KritzelColorHelper, Q as KritzelDevicesHelper, p as KritzelSelectionTool, m as KritzelTextTool, R as KritzelMouseButton, T as DEFAULT_STROKE_SIZES, U as DEFAULT_COLOR_PALETTE, S as ShapeType, D as DEFAULT_BRUSH_CONFIG, i as KritzelBrushTool, k as KritzelEraserTool, x as DEFAULT_LINE_TOOL_CONFIG, j as KritzelLineTool, n as KritzelShapeTool, w as DEFAULT_TEXT_CONFIG, l as KritzelImageTool, G as KritzelAlignment, y as DEFAULT_ASSET_STORAGE_CONFIG, V as ThemeHelper, B as darkTheme, z as lightTheme, X as KritzelSelectionGroup, Y as KritzelSelectionBox, Z as KritzelIconRegistry, _ as KritzelKeyboardHelper, $ as KritzelBaseHandler, K as KritzelBaseObject, a0 as KritzelMathHelper, r as KritzelWorkspace, f as KritzelGroup, d as KritzelImage, g as KritzelShape, b as KritzelText, J as runMigrations, O as CURRENT_WORKSPACE_SCHEMA_VERSION, M as WORKSPACE_MIGRATIONS, N as CURRENT_APP_STATE_SCHEMA_VERSION, L as APP_STATE_MIGRATIONS, a1 as ObjectHelper, o as KritzelCursorHelper, s as KritzelAnchorManager, t as KritzelThemeManager, u as KritzelLocalizationManager, v as KritzelLicenseManager, q as KritzelAssetResolver, a2 as KritzelClassHelper, a3 as KritzelEventHelper, W as WORKSPACE_EXPORT_VERSION } from './schema.constants-DchTXG3V.js';
3
3
  import * as Y from 'yjs';
4
4
  import 'y-indexeddb';
5
5
  import 'y-websocket';
@@ -132,16 +132,16 @@ const KritzelAvatar = class {
132
132
  height: `${this.size}px`,
133
133
  fontSize: `${Math.round(this.size * 0.4)}px`,
134
134
  };
135
- return (h(Host, { key: '571bd5b92adc7c65b96ded37b8daf5ed79905361', style: containerStyles, class: {
135
+ return (h(Host, { key: '0d372a5443f41835c2e8e5b33b58bcb6c1292e89', style: containerStyles, class: {
136
136
  'has-image': !!showImage,
137
137
  'has-initials': !!showInitials,
138
138
  'has-default': !!showDefaultIcon,
139
- }, role: "img", "aria-label": this.getDisplayName() || 'User avatar' }, showImage && (h("img", { key: '1065850b4575fda4637ab61ce07c6dfc97f14a90', src: imageUrl, alt: "", class: "avatar-image", ref: (el) => {
139
+ }, role: "img", "aria-label": this.getDisplayName() || 'User avatar' }, showImage && (h("img", { key: 'deb5f12115dd28b4b4ab2157cb2bbc9e48bb2a3f', src: imageUrl, alt: "", class: "avatar-image", ref: (el) => {
140
140
  if (el) {
141
141
  el.referrerPolicy = 'no-referrer';
142
142
  el.crossOrigin = 'anonymous';
143
143
  }
144
- }, onError: this.handleImageError })), showInitials && (h("span", { key: 'a6d9c9dd2eac6e44c731a878e2460017da7fb0b7', class: "avatar-initials", style: { backgroundColor: this.getBackgroundColor() } }, initials)), showDefaultIcon && (h("span", { key: '9e9d33cdd213649071b76cb0875008562b30f6a1', class: "avatar-default" }, h("svg", { key: '5d0be5c503a8944b45de239f08e6f40378c2dc5e', viewBox: "0 0 24 24", fill: "currentColor" }, 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" }))))));
144
+ }, onError: this.handleImageError })), showInitials && (h("span", { key: '6fc12f2d32923f33df5a8ec743d51434f67e6934', class: "avatar-initials", style: { backgroundColor: this.getBackgroundColor() } }, initials)), showDefaultIcon && (h("span", { key: 'd803b385f7eaa0659d7452231bfd46b1634f204d', class: "avatar-default" }, h("svg", { key: 'a3275ab33f58440abcd15c296d9c027dfab6495c', viewBox: "0 0 24 24", fill: "currentColor" }, h("path", { key: '31ac7e63fe6b1778c3d0cbd71b5c85791b79d084', d: "M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" }))))));
145
145
  }
146
146
  static get watchers() { return {
147
147
  "user": [{
@@ -451,7 +451,7 @@ const KritzelBackToContent = class {
451
451
  this.backToContent.emit();
452
452
  };
453
453
  render() {
454
- return (h(Host, { key: 'b623a9a4e4b8fce50346771488a59c3a646c289e' }, h("button", { key: 'b2f6b257975639d33362f1038b61e5147963f189', class: { 'back-to-content-button': true, visible: this.visible }, onClick: this.handleClick, "aria-label": this.text }, h("kritzel-icon", { key: '22f34fc201a865b6d9b21775a349d0e185727d48', name: "chevrons-left" }))));
454
+ return (h(Host, { key: '5ef034156f28bdd1861d99343d51a237498db46b' }, h("button", { key: 'caa1c08d04f1a3a84d694b7df5d318a686d3a1fb', class: { 'back-to-content-button': true, visible: this.visible }, onClick: this.handleClick, "aria-label": this.text }, h("kritzel-icon", { key: '0c7cf5e18294ade600be3052ca7c57c8d933a736', name: "chevrons-left" }))));
455
455
  }
456
456
  };
457
457
  KritzelBackToContent.style = kritzelBackToContentCss();
@@ -478,11 +478,11 @@ const KritzelButton = class {
478
478
  this.buttonClick.emit();
479
479
  };
480
480
  render() {
481
- return (h(Host, { key: 'c915db75630392741de404f07265a391330e54ca' }, h("button", { key: '50248ee1ed5862c9ea72b4f7cf7d564d03b5b14e', type: this.type, class: {
481
+ return (h(Host, { key: 'aa366907b97e648ac73182a43d099baf457e0ba3' }, h("button", { key: 'e80875396a791606461b1a20e0f7e1c896f54073', type: this.type, class: {
482
482
  'kritzel-button': true,
483
483
  [this.variant]: true,
484
484
  'disabled': this.disabled,
485
- }, disabled: this.disabled, onClick: this.handleClick }, h("slot", { key: 'd595cd819c7c0a3550e468ce65d4e2c28cc02164' }))));
485
+ }, disabled: this.disabled, onClick: this.handleClick }, h("slot", { key: '8f4e57a73837adfbf91f678c54558fe4c737aa91' }))));
486
486
  }
487
487
  };
488
488
  KritzelButton.style = kritzelButtonCss();
@@ -530,13 +530,13 @@ const KritzelColorComponent = class {
530
530
  render() {
531
531
  const resolvedColor = this.resolveColor();
532
532
  const isColorVeryLight = this.isLightColor(resolvedColor);
533
- return (h(Host, { key: 'c4c1fe2559aca61557ff2e8154f4d46ce3511b30' }, h("div", { key: '158c243018763a9609e0a056229263864a5e4d13', class: "checkerboard-bg", style: {
533
+ return (h(Host, { key: '21ce0ecb616266ec4953468ae9bbe0b72259c2da' }, h("div", { key: '02c8e8684b3b0819755efd1f160bdee662428ddd', class: "checkerboard-bg", style: {
534
534
  width: `${this.size}px`,
535
535
  height: `${this.size}px`,
536
536
  borderRadius: '50%',
537
537
  display: 'inline-block',
538
538
  position: 'relative',
539
- } }, h("div", { key: 'afa21c72b17ab5fb4b16521b91dbe7e9162d05f8', class: {
539
+ } }, h("div", { key: '0063e9a701994869adbd3022b5ace542e85dfd95', class: {
540
540
  'color-circle': true,
541
541
  'white': isColorVeryLight,
542
542
  }, style: {
@@ -592,7 +592,7 @@ const KritzelColorPalette = class {
592
592
  render() {
593
593
  const displayedColors = this.isExpanded ? this.colors : this.colors.slice(0, 6);
594
594
  const expandedHeight = this.isExpanded ? this.calculateHeight() : '32px';
595
- return (h(Host, { key: 'fc57d77d7c4cfd2aa2a02a70b8991858bb8cf61b' }, h("div", { key: '4fd10783609882f453ce95f5114acf799f21ec52', class: {
595
+ return (h(Host, { key: '9910a765816f45d420ad27c8fc7b02811380783b' }, h("div", { key: '91f3dae04338f55ab21831e4cac65f9d855c58c6', class: {
596
596
  'color-grid': true,
597
597
  'expanded': this.isExpanded,
598
598
  }, style: {
@@ -939,6 +939,8 @@ const KritzelControls = class {
939
939
  isUtilityPanelVisible = true;
940
940
  undoState = null;
941
941
  theme = 'light';
942
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
943
+ terms = {};
942
944
  isControlsReady;
943
945
  firstConfig = null;
944
946
  isTouchDevice = KritzelDevicesHelper.isTouchDevice();
@@ -1158,13 +1160,13 @@ const KritzelControls = class {
1158
1160
  // Separate tool controls from config control
1159
1161
  const toolControls = this.internalControls.filter(c => c.type === 'tool' || c.type === 'separator');
1160
1162
  const configControl = this.internalControls.find(c => c.type === 'config' && c.name === this.firstConfig?.name);
1161
- return (h(Host, { key: '0f40a136a6a9556080d922d346318045794421a8', style: { display: this.visible ? '' : 'none' }, class: {
1163
+ return (h(Host, { key: '7247a377d1cb75153e35f5308e81b2bd00d98578', style: { display: this.visible ? '' : 'none' }, class: {
1162
1164
  mobile: this.isTouchDevice,
1163
- } }, this.isUtilityPanelVisible && (h("kritzel-utility-panel", { key: 'd543e7575cb30e54d9362eddf7c7221fb8cce5f5', style: {
1165
+ } }, this.isUtilityPanelVisible && (h("kritzel-utility-panel", { key: '075577cd15f3e577a1a216b5b1f1874e82e0d123', style: {
1164
1166
  position: 'absolute',
1165
1167
  bottom: '56px',
1166
1168
  left: '12px',
1167
- }, undoState: this.undoState, onUndo: () => this.kritzelEngine?.undo(), onRedo: () => this.kritzelEngine?.redo(), onDelete: () => this.kritzelEngine?.delete() })), h("div", { key: '1083380152e9d1b51c35da335533c20ca2ca8fcc', class: "kritzel-controls" }, h("div", { key: '11ef0fb76c30ffda0e30f01d43229ca2142a5854', class: { 'scroll-indicator-left': true, 'visible': this.canScrollLeft } }), h("div", { key: '269924a8859aece37ff31202307d4feaecbbde86', class: "kritzel-tools-scroll", ref: el => (this.toolsScrollRef = el), onScroll: this.handleToolsScroll }, toolControls.map(control => {
1169
+ }, undoState: this.undoState, terms: this.terms, onUndo: () => this.kritzelEngine?.undo(), onRedo: () => this.kritzelEngine?.redo(), onDelete: () => this.kritzelEngine?.delete() })), h("div", { key: '7fb1041763c7b64917e337d68bbb29b36ed86b9a', class: "kritzel-controls" }, h("div", { key: '8577ed9c6f43a667aaba7dce1da519456c8ad210', class: { 'scroll-indicator-left': true, 'visible': this.canScrollLeft } }), h("div", { key: '42f8f2b85055705674f9e319056bd48856dbd197', class: "kritzel-tools-scroll", ref: el => (this.toolsScrollRef = el), onScroll: this.handleToolsScroll }, toolControls.map(control => {
1168
1170
  // Check if this control has sub-options (split-button)
1169
1171
  if (control.subOptions?.length) {
1170
1172
  const selectedSubOption = this.getSelectedSubOption(control);
@@ -1194,10 +1196,10 @@ const KritzelControls = class {
1194
1196
  'kritzel-control': true,
1195
1197
  'selected': this.activeControl?.name === control?.name,
1196
1198
  }, key: control.name, "data-testid": `tool-${control.name}`, onClick: _event => this.handleControlClick?.(control), "aria-label": control.name.charAt(0).toUpperCase() + control.name.slice(1) }, h("kritzel-icon", { name: control.icon })));
1197
- })), h("div", { key: '8b97a5bba3ac4992482e8f433d7ba6197918a914', class: { 'scroll-indicator-right': true, 'visible': this.canScrollRight && !(configControl && this.activeControl && hasConfigUI) } }), configControl && this.activeControl && (h("div", { class: {
1199
+ })), h("div", { key: '20007273e0f701193502320cf58099cbaedb834e', class: { 'scroll-indicator-right': true, 'visible': this.canScrollRight && !(configControl && this.activeControl && hasConfigUI) } }), configControl && this.activeControl && (h("div", { class: {
1198
1200
  'kritzel-config-container': true,
1199
1201
  'visible': hasConfigUI,
1200
- }, key: configControl.name }, h("div", { key: 'd9adef8c2acc8d9b9d745174050ce78960b89b58', class: { 'config-gradient-left': true, 'visible': this.needsScrolling } }), h("kritzel-tooltip", { key: '7605bbd2b6335c89c57aa68952293f26efad6b4b', anchorElement: this.host.shadowRoot?.querySelector('.kritzel-config-container'), triggerElement: this.configTriggerRef }, 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%' } })), h("div", { key: '1fb5979b1c531593acf5086861b22b7d78d03e8d', tabIndex: hasConfigUI ? 0 : -1, class: "kritzel-config", "data-testid": "tool-config", ref: el => {
1202
+ }, key: configControl.name }, h("div", { key: '2901fc2507e5b02fbd603bd2d2a4e2b07e35c970', class: { 'config-gradient-left': true, 'visible': this.needsScrolling } }), h("kritzel-tooltip", { key: '653e6a9b49146a2f297dbb5b9debe8709efd59ad', anchorElement: this.host.shadowRoot?.querySelector('.kritzel-config-container'), triggerElement: this.configTriggerRef }, h("kritzel-tool-config", { key: '6fae38444dff1978647e111643ee6e29c9cc1653', tool: this.activeControl.tool, theme: this.theme, engine: this.kritzelEngine, terms: this.terms, onToolChange: event => this.handleToolChange?.(event), onDisplayValuesChange: this.handleDisplayValuesChange, style: { width: '100%', height: '100%' } })), h("div", { key: '951a52f625c929634de5102b83a14ad0cf1abc8d', tabIndex: hasConfigUI ? 0 : -1, class: "kritzel-config", "data-testid": "tool-config", ref: el => {
1201
1203
  if (el)
1202
1204
  this.configTriggerRef = el;
1203
1205
  }, onKeyDown: event => {
@@ -1206,7 +1208,7 @@ const KritzelControls = class {
1206
1208
  }
1207
1209
  }, style: {
1208
1210
  cursor: 'pointer',
1209
- } }, this.displayValues && (h("div", { key: '3713ef344630f6b4d88df2e83992018859ddb18c', class: "color-container" }, h("kritzel-color", { key: 'c2679f68efae77c6daeb98be9e03d5320d51a73a', value: this.displayValues.color, theme: this.theme, size: 18, style: {
1211
+ } }, this.displayValues && (h("div", { key: 'b8fff73b9055e86b312a9f0798fff4177cf85970', class: "color-container" }, h("kritzel-color", { key: 'fcd474e557d887f94383b7e583ab1c95a675ce23', value: this.displayValues.color, theme: this.theme, size: 18, style: {
1210
1212
  borderRadius: '50%',
1211
1213
  border: 'none',
1212
1214
  } })))))))));
@@ -1238,13 +1240,15 @@ const KritzelCurrentUser = class {
1238
1240
  * Avatar size in pixels
1239
1241
  */
1240
1242
  avatarSize = 40;
1243
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
1244
+ terms = {};
1241
1245
  dialogRef;
1242
1246
  handleAvatarClick = (event) => {
1243
1247
  event.stopPropagation();
1244
1248
  this.dialogRef?.open();
1245
1249
  };
1246
1250
  render() {
1247
- return (h(Host, { key: 'a735cb9f16f4898fde0b52573affa2d270a8f1de' }, h("kritzel-avatar", { key: 'd449a515182718ab4ef3b26b2277696bbc7ab46f', user: this.user, size: this.avatarSize, onClick: this.handleAvatarClick }), h("kritzel-current-user-dialog", { key: '3542f6df43c9924218e344f70bdc398c74a8eae6', ref: el => (this.dialogRef = el), user: this.user })));
1251
+ return (h(Host, { key: '7e45048e532db84347e2531de2df19f5537d62ca' }, h("kritzel-avatar", { key: '5a67d1dbae041e16cdaeaf4bc678b6b00c56f442', user: this.user, size: this.avatarSize, onClick: this.handleAvatarClick }), h("kritzel-current-user-dialog", { key: '8e35219e282811b8bbafb058c02c8c0474574009', ref: el => (this.dialogRef = el), user: this.user, terms: this.terms })));
1248
1252
  }
1249
1253
  };
1250
1254
  KritzelCurrentUser.style = kritzelCurrentUserCss();
@@ -1257,6 +1261,8 @@ const KritzelCurrentUserDialog = class {
1257
1261
  }
1258
1262
  get host() { return getElement(this); }
1259
1263
  user;
1264
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
1265
+ terms = {};
1260
1266
  isDialogOpen = false;
1261
1267
  async open() {
1262
1268
  this.isDialogOpen = true;
@@ -1276,7 +1282,7 @@ const KritzelCurrentUserDialog = class {
1276
1282
  }
1277
1283
  render() {
1278
1284
  const displayName = this.getDisplayName();
1279
- return (h(Host, { key: '40c1a1bed0ddf02f9835199b5f7d2363e4d1902b' }, h("kritzel-dialog", { key: 'a83c09eac66ddf51155591a32245e3f15e34943e', dialogTitle: "Account", isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, size: "small", contained: true }, h("div", { key: '14f7100a881ee3c5ba6b672d509bf3a9161ccd62', class: "user-info" }, h("kritzel-avatar", { key: 'e3552a80db81db4c26f81c6cc699363afa6153ea', user: this.user, size: 80 }), displayName && h("div", { key: 'c54164be605ac2bd2fc8bac6bb4481f820119028', class: "user-name" }, displayName), this.user?.email && h("div", { key: 'e6af7c44e45443eb24be0777768de96b0e3d249e', class: "user-email" }, this.user.email)))));
1285
+ return (h(Host, { key: '7c9e5c19249d400e2d670c60f5d6716c742adc62' }, h("kritzel-dialog", { key: '811da6bff0ce03914f545dd289878ba04924e85c', dialogTitle: this.terms['currentUser.dialogTitle'] ?? 'Account', isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, size: "small", contained: true }, h("div", { key: '03a101b04d61882732547d91e81a2bacb3aa4df8', class: "user-info" }, h("kritzel-avatar", { key: '4033d0e2322d7a25231f01115ec33f20e4fb0d4c', user: this.user, size: 80 }), displayName && h("div", { key: 'cf3a3aecb84152736c9de119f82a79f1b2b32f6f', class: "user-name" }, displayName), this.user?.email && h("div", { key: '5725d71dd80468c64a2b0bb1d9d967232513fb63', class: "user-email" }, this.user.email)))));
1280
1286
  }
1281
1287
  };
1282
1288
  KritzelCurrentUserDialog.style = kritzelCurrentUserDialogCss();
@@ -2155,6 +2161,8 @@ const DEFAULT_SHAPE_CONFIG = {
2155
2161
 
2156
2162
  const ABSOLUTE_SCALE_MAX = 1000;
2157
2163
  const ABSOLUTE_SCALE_MIN = 0.0001;
2164
+ /** Destination opened when the "Powered by Kritzel" watermark is clicked. */
2165
+ const KRITZEL_WEBSITE_URL = 'https://kritzel.io';
2158
2166
 
2159
2167
  /**
2160
2168
  * Default sync configuration - None
@@ -2163,7 +2171,7 @@ const DEFAULT_SYNC_CONFIG = {
2163
2171
  providers: [],
2164
2172
  };
2165
2173
 
2166
- 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}`;
2174
+ const kritzelEditorCss = () => `kritzel-editor{display:flex;margin:0;position:relative;container-type:inline-size;overflow:hidden;width:100%;height:100%;align-items:center;justify-content:center;touch-action:manipulation;user-select:none;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-align:start;line-height:normal}kritzel-controls{position:absolute;left:0;right:0;margin-inline:auto;width:max-content;max-width:calc(100% - 16px);bottom:var(--kritzel-editor-controls-bottom, 14px);transition:transform var(--kritzel-editor-controls-transition-duration, 0.1s) var(--kritzel-editor-controls-transition, ease-in-out)}kritzel-controls.keyboard-open{transform:var(--kritzel-editor-controls-transform, translateY(300%))}.top-left-buttons{position:absolute;top:var(--kritzel-editor-top-left-buttons-top, 14px);left:var(--kritzel-editor-top-left-buttons-left, 14px);display:flex;align-items:flex-start;gap:8px}.top-right-buttons{position:absolute;top:var(--kritzel-editor-top-right-buttons-top, 14px);right:var(--kritzel-editor-top-right-buttons-right, 14px);display:flex;align-items:center;gap:8px}.bottom-left-buttons{position:absolute;left:var(--kritzel-editor-top-left-buttons-left, 14px);bottom:var(--kritzel-editor-controls-bottom, 14px);display:flex;align-items:flex-end}@container (max-width: 767px){.bottom-left-buttons{display:none}}.top-right-button{display:flex;align-items:center;justify-content:center;width:50px;height:50px;padding:0;border:var(--kritzel-split-button-border, 1px solid #ebebeb);border-radius:var(--kritzel-split-button-border-radius, 12px);background-color:var(--kritzel-split-button-background-color, #ffffff);cursor:var(--kritzel-global-pointer-cursor, pointer);box-shadow:var(--kritzel-split-button-box-shadow, 0 0 3px rgba(0, 0, 0, 0.08));transition:background-color 150ms ease;-webkit-tap-highlight-color:transparent}.top-right-button:hover{background-color:#f5f5f5}.top-right-button:active{background-color:#ebebeb}`;
2167
2175
 
2168
2176
  const KritzelEditor = class {
2169
2177
  constructor(hostRef) {
@@ -2176,6 +2184,7 @@ const KritzelEditor = class {
2176
2184
  this.objectsUpdated = createEvent(this, "objectsUpdated");
2177
2185
  this.undoStateChange = createEvent(this, "undoStateChange");
2178
2186
  this.themeChange = createEvent(this, "themeChange");
2187
+ this.localeChange = createEvent(this, "localeChange");
2179
2188
  this.viewportChange = createEvent(this, "viewportChange");
2180
2189
  this.logout = createEvent(this, "logout");
2181
2190
  this.login = createEvent(this, "login");
@@ -2190,7 +2199,6 @@ const KritzelEditor = class {
2190
2199
  viewportBoundaryRight = Infinity;
2191
2200
  viewportBoundaryTop = -Infinity;
2192
2201
  viewportBoundaryBottom = Infinity;
2193
- wheelEnabled = true;
2194
2202
  debugInfo = {
2195
2203
  showViewportInfo: false,
2196
2204
  showObjectInfo: false,
@@ -2259,66 +2267,66 @@ const KritzelEditor = class {
2259
2267
  ];
2260
2268
  globalContextMenuItems = [
2261
2269
  {
2262
- label: 'Paste',
2270
+ label: 'menu.paste',
2263
2271
  icon: 'paste',
2264
2272
  disabled: async () => (await this.engineRef.getCopiedObjects()).length === 0,
2265
2273
  action: menu => this.engineRef.paste(menu.x, menu.y),
2266
2274
  },
2267
2275
  {
2268
- label: 'Select All',
2276
+ label: 'menu.selectAll',
2269
2277
  icon: 'select-all',
2270
2278
  disabled: async () => (await this.engineRef.getObjectsInViewport()).length === 0,
2271
2279
  action: () => this.selectAllObjectsInViewport(),
2272
2280
  },
2273
2281
  ];
2274
2282
  objectContextMenuItems = [
2275
- { label: 'Copy', icon: 'copy', group: 'clipboard', action: () => this.engineRef.copy() },
2276
- { label: 'Cut', icon: 'cut', group: 'clipboard', action: () => this.engineRef.cut() },
2283
+ { label: 'menu.copy', icon: 'copy', group: 'clipboard', action: () => this.engineRef.copy() },
2284
+ { label: 'menu.cut', icon: 'cut', group: 'clipboard', action: () => this.engineRef.cut() },
2277
2285
  {
2278
- label: 'Paste',
2286
+ label: 'menu.paste',
2279
2287
  icon: 'paste',
2280
2288
  group: 'clipboard',
2281
2289
  disabled: async () => (await this.engineRef.getCopiedObjects()).length === 0,
2282
2290
  action: (menu, _) => this.engineRef.paste(menu.x, menu.y),
2283
2291
  },
2284
2292
  {
2285
- label: 'Order',
2293
+ label: 'menu.order',
2286
2294
  icon: 'ordering',
2287
2295
  group: 'other',
2288
2296
  children: [
2289
- { label: 'Bring to Front', icon: 'bring-to-front', action: () => this.engineRef.bringToFront() },
2290
- { label: 'Send to Back', icon: 'send-to-back', action: () => this.engineRef.sendToBack() },
2291
- { label: 'Move Up', icon: 'arrow-up-from-dot', action: () => this.engineRef.bringForward() },
2292
- { label: 'Move Down', icon: 'arrow-down-from-dot', action: () => this.engineRef.sendBackward() },
2297
+ { label: 'menu.bringToFront', icon: 'bring-to-front', action: () => this.engineRef.bringToFront() },
2298
+ { label: 'menu.sendToBack', icon: 'send-to-back', action: () => this.engineRef.sendToBack() },
2299
+ { label: 'menu.moveUp', icon: 'arrow-up-from-dot', action: () => this.engineRef.bringForward() },
2300
+ { label: 'menu.moveDown', icon: 'arrow-down-from-dot', action: () => this.engineRef.sendBackward() },
2293
2301
  ],
2294
2302
  },
2295
2303
  {
2296
- label: 'Align',
2304
+ label: 'menu.align',
2297
2305
  icon: 'align',
2298
2306
  group: 'other',
2299
2307
  disabled: async () => (await this.engineRef.getSelectedObjects()).length < 2,
2300
2308
  children: [
2301
- { label: 'Align Left', icon: 'align-start-vertical', action: () => this.engineRef.alignObjects(KritzelAlignment.StartHorizontal) },
2302
- { label: 'Align Center Horizontally', icon: 'align-center-horizontal', action: () => this.engineRef.alignObjects(KritzelAlignment.CenterHorizontal) },
2303
- { label: 'Align Right', icon: 'align-end-vertical', action: () => this.engineRef.alignObjects(KritzelAlignment.EndHorizontal) },
2304
- { label: 'Align Top', icon: 'align-start-horizontal', action: () => this.engineRef.alignObjects(KritzelAlignment.StartVertical) },
2305
- { label: 'Align Center Vertically', icon: 'align-center-vertical', action: () => this.engineRef.alignObjects(KritzelAlignment.CenterVertical) },
2306
- { label: 'Align Bottom', icon: 'align-end-horizontal', action: () => this.engineRef.alignObjects(KritzelAlignment.EndVertical) },
2309
+ { label: 'menu.alignLeft', icon: 'align-start-vertical', action: () => this.engineRef.alignObjects(KritzelAlignment.StartHorizontal) },
2310
+ { label: 'menu.alignCenterHorizontal', icon: 'align-center-horizontal', action: () => this.engineRef.alignObjects(KritzelAlignment.CenterHorizontal) },
2311
+ { label: 'menu.alignRight', icon: 'align-end-vertical', action: () => this.engineRef.alignObjects(KritzelAlignment.EndHorizontal) },
2312
+ { label: 'menu.alignTop', icon: 'align-start-horizontal', action: () => this.engineRef.alignObjects(KritzelAlignment.StartVertical) },
2313
+ { label: 'menu.alignCenterVertical', icon: 'align-center-vertical', action: () => this.engineRef.alignObjects(KritzelAlignment.CenterVertical) },
2314
+ { label: 'menu.alignBottom', icon: 'align-end-horizontal', action: () => this.engineRef.alignObjects(KritzelAlignment.EndVertical) },
2307
2315
  ],
2308
2316
  },
2309
2317
  {
2310
- label: 'Group',
2318
+ label: 'menu.group',
2311
2319
  icon: 'group',
2312
2320
  group: 'other',
2313
2321
  children: [
2314
2322
  {
2315
- label: 'Group',
2323
+ label: 'menu.group',
2316
2324
  icon: 'group',
2317
2325
  disabled: async () => (await this.engineRef.getSelectedObjects()).length < 2,
2318
2326
  action: () => this.engineRef.group(),
2319
2327
  },
2320
2328
  {
2321
- label: 'Ungroup',
2329
+ label: 'menu.ungroup',
2322
2330
  icon: 'ungroup',
2323
2331
  disabled: async () => {
2324
2332
  const selectedObjects = await this.engineRef.getSelectedObjects();
@@ -2329,23 +2337,34 @@ const KritzelEditor = class {
2329
2337
  ],
2330
2338
  },
2331
2339
  {
2332
- label: 'Export',
2340
+ label: 'menu.export',
2333
2341
  icon: 'download',
2334
2342
  group: 'export',
2335
2343
  children: [
2336
- { label: 'Export as SVG', icon: 'download', action: () => this.engineRef.exportSelectedObjectsAsSvg() },
2337
- { label: 'Export as PNG', icon: 'download', action: () => this.engineRef.exportSelectedObjectsAsPng() },
2344
+ { label: 'menu.exportAsSvg', icon: 'download', action: () => this.engineRef.exportSelectedObjectsAsSvg() },
2345
+ { label: 'menu.exportAsPng', icon: 'download', action: () => this.engineRef.exportSelectedObjectsAsPng() },
2338
2346
  ],
2339
2347
  },
2340
- { label: 'Delete', icon: 'delete', group: 'edit', action: () => this.engineRef.delete() },
2348
+ { label: 'menu.delete', icon: 'delete', group: 'edit', action: () => this.engineRef.delete() },
2341
2349
  ];
2342
2350
  themes;
2343
2351
  theme = 'light';
2352
+ /** License key that, when valid, removes the "Powered by Kritzel" watermark. */
2353
+ licenseKey;
2354
+ /** The current locale (language) code applied to the editor, e.g. 'en', 'de', 'fr'. */
2355
+ locale = 'en';
2356
+ /** An array of available locale definitions (with optional partial term overrides). */
2357
+ locales;
2358
+ /** The locale used to resolve terms missing from the active locale. */
2359
+ fallbackLocale = 'en';
2344
2360
  customSvgIcons = {};
2361
+ isPanningEnabled = true;
2362
+ isZoomingEnabled = true;
2345
2363
  isControlsVisible = true;
2346
2364
  isUtilityPanelVisible = true;
2347
2365
  isWorkspaceManagerVisible = true;
2348
2366
  isMoreMenuVisible = true;
2367
+ isZoomPanelVisible = true;
2349
2368
  isObjectDistanceFadingActive = false;
2350
2369
  syncConfig = DEFAULT_SYNC_CONFIG;
2351
2370
  assetStorageConfig = DEFAULT_ASSET_STORAGE_CONFIG;
@@ -2367,6 +2386,7 @@ const KritzelEditor = class {
2367
2386
  objectsUpdated;
2368
2387
  undoStateChange;
2369
2388
  themeChange;
2389
+ localeChange;
2370
2390
  viewportChange;
2371
2391
  logout;
2372
2392
  login;
@@ -2380,6 +2400,11 @@ const KritzelEditor = class {
2380
2400
  isVirtualKeyboardOpen = false;
2381
2401
  undoState = null;
2382
2402
  isBackToContentButtonVisible = false;
2403
+ /** Localized strings for editor-owned UI (e.g. the more menu), resolved from the active locale. */
2404
+ resolvedTerms = {};
2405
+ /** Available locales as `{ code, label }` options for the settings language selector. */
2406
+ availableLocaleOptions = [];
2407
+ currentZoomPercent = 100;
2383
2408
  shortcuts = [];
2384
2409
  currentIsPublic = false;
2385
2410
  isEditorVisible = false;
@@ -2443,6 +2468,25 @@ const KritzelEditor = class {
2443
2468
  onThemesChange() {
2444
2469
  this.applyTheme();
2445
2470
  }
2471
+ onLocaleChange(newValue) {
2472
+ if (this.engineRef) {
2473
+ this.engineRef.setLocale(newValue);
2474
+ this.engineRef.saveSettings(this.currentSettingsConfig);
2475
+ void this.refreshLocalizedTerms();
2476
+ }
2477
+ }
2478
+ /**
2479
+ * Refreshes the editor-owned localized strings (e.g. the more menu and the
2480
+ * strings forwarded to child UI components) from the engine's localization
2481
+ * manager for the active locale.
2482
+ */
2483
+ async refreshLocalizedTerms() {
2484
+ if (!this.engineRef) {
2485
+ return;
2486
+ }
2487
+ this.resolvedTerms = await this.engineRef.getResolvedTerms();
2488
+ this.availableLocaleOptions = await this.engineRef.getAvailableLocaleOptions();
2489
+ }
2446
2490
  onTouchStart(event) {
2447
2491
  if (event.cancelable) {
2448
2492
  event.preventDefault();
@@ -2701,6 +2745,9 @@ const KritzelEditor = class {
2701
2745
  this.activeWorkspace = event.detail.activeWorkspace;
2702
2746
  this.workspaces = event.detail.workspaces;
2703
2747
  this.currentIsPublic = await this.engineRef.getIsPublic();
2748
+ await this.refreshLocalizedTerms();
2749
+ const viewport = await this.engineRef.getViewport();
2750
+ this.currentZoomPercent = this.getZoomPercentFromScale(viewport.scale);
2704
2751
  this.loadShortcuts();
2705
2752
  }
2706
2753
  handleWorkspacesChange(event) {
@@ -2751,8 +2798,15 @@ const KritzelEditor = class {
2751
2798
  }
2752
2799
  handleViewportChange(event) {
2753
2800
  event.stopPropagation();
2801
+ this.currentZoomPercent = this.getZoomPercentFromScale(event.detail.scale);
2754
2802
  this.viewportChange.emit(event.detail);
2755
2803
  }
2804
+ getZoomPercentFromScale(scale) {
2805
+ if (!Number.isFinite(scale) || scale <= 0) {
2806
+ return 100;
2807
+ }
2808
+ return Math.round(scale * 100);
2809
+ }
2756
2810
  handleAwarenessChange(event) {
2757
2811
  event.stopPropagation();
2758
2812
  this.awarenessChange.emit(event.detail);
@@ -2762,6 +2816,10 @@ const KritzelEditor = class {
2762
2816
  this.scaleMax = event.detail.scaleMax;
2763
2817
  this.lockDrawingScale = event.detail.lockDrawingScale;
2764
2818
  this.theme = event.detail.theme;
2819
+ if (typeof event.detail.locale === 'string' && event.detail.locale !== this.locale) {
2820
+ this.locale = event.detail.locale;
2821
+ this.localeChange.emit(event.detail.locale);
2822
+ }
2765
2823
  this.viewportBoundaryLeft = event.detail.viewportBoundaryLeft ?? -Infinity;
2766
2824
  this.viewportBoundaryRight = event.detail.viewportBoundaryRight ?? Infinity;
2767
2825
  this.viewportBoundaryTop = event.detail.viewportBoundaryTop ?? -Infinity;
@@ -2776,7 +2834,7 @@ const KritzelEditor = class {
2776
2834
  return [
2777
2835
  {
2778
2836
  id: 'share',
2779
- label: 'Share',
2837
+ label: this.resolvedTerms['menu.share'] ?? 'Share',
2780
2838
  icon: 'share',
2781
2839
  action: () => {
2782
2840
  if (!this.isLoggedIn && this.loginConfig) {
@@ -2788,7 +2846,7 @@ const KritzelEditor = class {
2788
2846
  },
2789
2847
  {
2790
2848
  id: 'export',
2791
- label: 'Export',
2849
+ label: this.resolvedTerms['menu.export'] ?? 'Export',
2792
2850
  icon: 'upload',
2793
2851
  action: async () => {
2794
2852
  const preview = await this.engineRef.getScreenshot('png');
@@ -2797,19 +2855,19 @@ const KritzelEditor = class {
2797
2855
  },
2798
2856
  {
2799
2857
  id: 'import',
2800
- label: 'Import',
2858
+ label: this.resolvedTerms['menu.import'] ?? 'Import',
2801
2859
  icon: 'download',
2802
2860
  action: () => this.engineRef.importFromFile(),
2803
2861
  },
2804
2862
  {
2805
2863
  id: 'settings',
2806
- label: 'Settings',
2864
+ label: this.resolvedTerms['menu.settings'] ?? 'Settings',
2807
2865
  icon: 'settings',
2808
2866
  action: () => this.settingsRef.open(),
2809
2867
  },
2810
2868
  {
2811
2869
  id: 'logout',
2812
- label: 'Logout',
2870
+ label: this.resolvedTerms['menu.logout'] ?? 'Logout',
2813
2871
  icon: 'log-out',
2814
2872
  color: '#ff3b30',
2815
2873
  isVisible: this.isLoggedIn,
@@ -2849,6 +2907,41 @@ const KritzelEditor = class {
2849
2907
  async setLoginLoading(provider) {
2850
2908
  this.loginDialogRef?.setLoading(provider);
2851
2909
  }
2910
+ /**
2911
+ * Sets the active locale (language) and re-renders the UI.
2912
+ * @param code - The locale code to activate, e.g. 'de'.
2913
+ */
2914
+ async setLocale(code) {
2915
+ this.locale = code;
2916
+ await this.engineRef?.setLocale(code);
2917
+ }
2918
+ /**
2919
+ * Gets the currently active locale code.
2920
+ */
2921
+ async getLocale() {
2922
+ return this.engineRef ? this.engineRef.getLocale() : this.locale;
2923
+ }
2924
+ /**
2925
+ * Gets the list of available locale codes (built-in and registered).
2926
+ */
2927
+ async getAvailableLocales() {
2928
+ return this.engineRef ? this.engineRef.getAvailableLocales() : [];
2929
+ }
2930
+ /**
2931
+ * Registers additional locale definitions (with optional partial term overrides).
2932
+ * @param locales - The locale definitions to register.
2933
+ */
2934
+ async registerLocales(locales) {
2935
+ await this.engineRef?.registerLocales(locales);
2936
+ }
2937
+ /**
2938
+ * Resolves a term key to its translated string for the active locale.
2939
+ * @param key - The term key to resolve.
2940
+ * @param vars - Optional values for `{placeholder}` interpolation.
2941
+ */
2942
+ async t(key, vars) {
2943
+ return this.engineRef ? this.engineRef.t(key, vars) : key;
2944
+ }
2852
2945
  getSettingsStorageKey() {
2853
2946
  return this.editorId ? `kritzel-settings-${this.editorId}` : 'kritzel-settings';
2854
2947
  }
@@ -2869,6 +2962,9 @@ const KritzelEditor = class {
2869
2962
  if (typeof parsed.theme === 'string') {
2870
2963
  this.theme = parsed.theme;
2871
2964
  }
2965
+ if (typeof parsed.locale === 'string') {
2966
+ this.locale = parsed.locale;
2967
+ }
2872
2968
  if (typeof parsed.viewportBoundaryLeft === 'number') {
2873
2969
  this.viewportBoundaryLeft = parsed.viewportBoundaryLeft;
2874
2970
  }
@@ -2899,6 +2995,7 @@ const KritzelEditor = class {
2899
2995
  scaleMax: this.scaleMax,
2900
2996
  lockDrawingScale: this.lockDrawingScale,
2901
2997
  theme: this.theme,
2998
+ locale: this.locale,
2902
2999
  viewportBoundaryLeft: this.viewportBoundaryLeft,
2903
3000
  viewportBoundaryRight: this.viewportBoundaryRight,
2904
3001
  viewportBoundaryTop: this.viewportBoundaryTop,
@@ -2952,35 +3049,35 @@ const KritzelEditor = class {
2952
3049
  const isLoggedIn = this.isLoggedIn;
2953
3050
  const shouldShowCurrentUser = isLoggedIn;
2954
3051
  const shouldShowLoginButton = this.isReady && !!this.loginConfig && !isLoggedIn;
2955
- return (h(Host, { key: 'ffacaea5d3df12a3a8b448d31db3c5949053156c', style: {
3052
+ return (h(Host, { key: '72238560a0f0275c506f59220277fdff7ab92c13', style: {
2956
3053
  opacity: this.isEditorVisible ? '1' : '0',
2957
3054
  visibility: this.isEditorVisible ? 'visible' : 'hidden',
2958
3055
  transition: 'opacity 0.2s ease-in-out, visibility 0.2s ease-in-out',
2959
- } }, h("div", { key: '669eafee25b4f84c39469738a1337c21ab03e388', class: "top-left-buttons" }, 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) }), h("kritzel-back-to-content", { key: '5bd0e6263d51119b197292b69879c1ae437f92fc', visible: this.isBackToContentButtonVisible, onBackToContent: () => this.backToContent() })), h("kritzel-engine", { key: '468f17137c51c90fd61c9179d13c449b1ac8feb9', ref: el => {
3056
+ } }, h("div", { key: '14fd50ad857199f3b6be708fc4263aa2e69067de', class: "top-left-buttons" }, h("kritzel-workspace-manager", { key: '8788631c804770c67110c7e7906fe2034438ef9b', visible: this.isWorkspaceManagerVisible, workspaces: this.workspaces, activeWorkspace: this.activeWorkspace, terms: this.resolvedTerms, onWorkspaceChange: event => (this.activeWorkspace = event.detail), onIsWorkspaceManagerReady: () => (this.isWorkspaceManagerReady = true) }), h("kritzel-back-to-content", { key: '1840374d7353af2b050822dcd9c54be46e326278', visible: this.isBackToContentButtonVisible, text: this.resolvedTerms['backToContent.label'] ?? 'Back to content', onBackToContent: () => this.backToContent() })), h("kritzel-engine", { key: 'cdde0b65c811ee28fb4266afb96005c1fed24323', ref: el => {
2960
3057
  if (el) {
2961
3058
  this.engineRef = el;
2962
3059
  }
2963
- }, 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) }), h("kritzel-controls", { key: '848c30b27fb916c8480b41745bd6ec844e0b23a2', visible: this.isControlsVisible, class: { 'keyboard-open': this.isVirtualKeyboardOpen }, ref: el => {
3060
+ }, workspace: this.activeWorkspace, activeWorkspaceId: this.activeWorkspaceId, editorId: this.editorId, syncConfig: this.syncConfig, assetStorageConfig: this.assetStorageConfig, user: this.user, scaleMax: this.scaleMax, lockDrawingScale: this.lockDrawingScale, isObjectDistanceFadingActive: this.isObjectDistanceFadingActive, scaleMin: this.scaleMin, cursorTarget: this.cursorTarget, isLoading: this.isLoading, viewportBoundaryLeft: this.viewportBoundaryLeft, viewportBoundaryRight: this.viewportBoundaryRight, viewportBoundaryTop: this.viewportBoundaryTop, viewportBoundaryBottom: this.viewportBoundaryBottom, isPanningEnabled: this.isPanningEnabled, isZoomingEnabled: this.isZoomingEnabled, theme: this.theme, themes: this.themes, licenseKey: this.licenseKey, locale: this.locale, locales: this.locales, fallbackLocale: this.fallbackLocale, debugInfo: this.debugInfo, globalContextMenuItems: this.globalContextMenuItems, objectContextMenuItems: this.objectContextMenuItems, onIsEngineReady: event => this.onEngineReady(event), onWorkspacesChange: event => this.handleWorkspacesChange(event), onActiveWorkspaceChange: event => this.handleActiveWorkspaceChange(event), onObjectsChange: event => this.handleObjectsChange(event), onObjectsAdded: event => this.handleObjectsAdded(event), onObjectsRemoved: event => this.handleObjectsRemoved(event), onObjectsUpdated: event => this.handleObjectsUpdated(event), onUndoStateChange: event => this.handleUndoStateChange(event), onObjectsInViewportChange: event => this.handleObjectsInViewportChange(event), onViewportChange: event => this.handleViewportChange(event), onAwarenessChange: event => this.handleAwarenessChange(event) }), h("kritzel-controls", { key: '9b91d0cc3b3afba8894bd852bac911178c0ca82d', visible: this.isControlsVisible, class: { 'keyboard-open': this.isVirtualKeyboardOpen }, ref: el => {
2964
3061
  if (el) {
2965
3062
  this.controlsRef = el;
2966
3063
  }
2967
- }, controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState ?? undefined, theme: this.theme, onIsControlsReady: () => (this.isControlsReady = true) }), h("div", { key: 'e998d60679c767d15617bd7ecde5ee77e781a92f', class: "top-right-buttons" }, h("kritzel-settings", { key: '43b9cdb2d10de789cc03d2a9ef5df870b8ca7bfe', ref: el => {
3064
+ }, controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState ?? undefined, theme: this.theme, terms: this.resolvedTerms, onIsControlsReady: () => (this.isControlsReady = true) }), h("div", { key: '01f63c615eec68696532926b6d1efe2443e2d46a', class: "bottom-left-buttons" }, h("kritzel-zoom-panel", { key: '73a1fc5b0892b88d6c0a7c5debb42b7bb3f03a84', visible: this.isZoomPanelVisible, disabled: !this.isZoomingEnabled, zoomPercent: this.currentZoomPercent, terms: this.resolvedTerms, onZoomIn: () => this.zoomIn(), onZoomOut: () => this.zoomOut() })), h("div", { key: '94776978ba9dd68b2c5f62b141c074852585a1cd', class: "top-right-buttons" }, h("kritzel-settings", { key: '665a74511f8e511602d4d3437927e03af2c3c01c', ref: el => {
2968
3065
  if (el) {
2969
3066
  this.settingsRef = el;
2970
3067
  }
2971
- }, 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) }), h("kritzel-export", { key: '74669624a1e5177125ef00e1667c880ce47cbce4', ref: el => {
3068
+ }, shortcuts: this.shortcuts, availableThemes: this.themes && this.themes.length > 0 ? this.themes.map(t => t.name) : ['light', 'dark'], availableLocales: this.availableLocaleOptions, settings: this.currentSettingsConfig, terms: this.resolvedTerms, onSettingsChange: event => this.handleSettingsChange(event) }), h("kritzel-export", { key: '6b52127d4634d92c3e7410a084f37970bd8c84e7', ref: el => {
2972
3069
  if (el) {
2973
3070
  this.exportRef = el;
2974
3071
  }
2975
- }, workspaceName: this.activeWorkspace?.name || 'workspace', onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), h("kritzel-active-users", { key: '18d925f32d021ff6713accb22d0594d259d70f2e', users: this.activeUsers }), shouldShowCurrentUser && h("kritzel-current-user", { key: 'bddd5c29c5f17cced47276c237c04cfdb711da38', user: this.user }), shouldShowLoginButton && (h("kritzel-button", { key: '4eb6c85459f59863d6e644ecf21c2295da71bafc', onButtonClick: () => this.loginDialogRef?.open() }, "Sign in")), h("kritzel-more-menu", { key: 'a5a323ec248bebc7bc07898f344e0926fac8db17', items: this.moreMenuItems, visible: this.isMoreMenuVisible }), h("kritzel-share-dialog", { key: '8cb3ddad95d36f5b7ad59d8c4f057df93cb2bfe3', ref: el => {
3072
+ }, workspaceName: this.activeWorkspace?.name || 'workspace', terms: this.resolvedTerms, onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), h("kritzel-active-users", { key: '002a01d361137f867efc2a0c91a6c0e9619d4359', users: this.activeUsers }), shouldShowCurrentUser && h("kritzel-current-user", { key: '6a9e0ad77997cbb10981d58afb176a7f7f4938aa', user: this.user, terms: this.resolvedTerms }), shouldShowLoginButton && (h("kritzel-button", { key: '5ba13725105d0af68c51fec455569ac7bbeaecde', onButtonClick: () => this.loginDialogRef?.open() }, this.resolvedTerms['login.dialogTitle'] ?? 'Sign in')), h("kritzel-more-menu", { key: 'f392a518fef108b395eb6d6681af9e06827e3780', items: this.moreMenuItems, visible: this.isMoreMenuVisible, terms: this.resolvedTerms }), h("kritzel-share-dialog", { key: '845a3153f5fd9b419f7d0b81be85f5e1ea0051cc', ref: el => {
2976
3073
  if (el) {
2977
3074
  this.shareDialogRef = el;
2978
3075
  }
2979
- }, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (h("kritzel-login-dialog", { key: '35395d0faadcfeb021fba685aa46e180e47d2be2', ref: el => {
3076
+ }, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, terms: this.resolvedTerms, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (h("kritzel-login-dialog", { key: '5a7b149d8e2c2acbda3d7818d8576d8ad249ef23', ref: el => {
2980
3077
  if (el) {
2981
3078
  this.loginDialogRef = el;
2982
3079
  }
2983
- }, providers: this.loginConfig.providers, dialogTitle: this.loginConfig.title, subtitle: this.loginConfig.subtitle, onProviderLogin: this.handleProviderLogin })))));
3080
+ }, providers: this.loginConfig.providers, dialogTitle: this.loginConfig.title ?? this.resolvedTerms['login.dialogTitle'] ?? 'Sign in', subtitle: this.loginConfig.subtitle, onProviderLogin: this.handleProviderLogin })))));
2984
3081
  }
2985
3082
  static get watchers() { return {
2986
3083
  "isEngineReady": [{
@@ -3003,6 +3100,9 @@ const KritzelEditor = class {
3003
3100
  }],
3004
3101
  "themes": [{
3005
3102
  "onThemesChange": 0
3103
+ }],
3104
+ "locale": [{
3105
+ "onLocaleChange": 0
3006
3106
  }]
3007
3107
  }; }
3008
3108
  };
@@ -21257,6 +21357,28 @@ class KritzelContextMenuHandler extends KritzelBaseHandler {
21257
21357
  this.globalContextMenuItems = globalContextMenuItems;
21258
21358
  this.objectContextMenuItems = objectContextMenuItems;
21259
21359
  }
21360
+ /**
21361
+ * Resolves the active context menu items for display, translating each item's
21362
+ * `label` through the current locale. When the label is a known
21363
+ * {@link KritzelTermKey} the translation is used; any other string is passed
21364
+ * through unchanged. Runs at menu-open time so the labels reflect the locale
21365
+ * active at that moment.
21366
+ * @returns A new array of items with resolved labels.
21367
+ */
21368
+ resolveLabels(items) {
21369
+ return items.map(item => ({
21370
+ ...item,
21371
+ label: this._core.localizationManager.translate(item.label),
21372
+ children: item.children ? this.resolveLabels(item.children) : undefined,
21373
+ }));
21374
+ }
21375
+ /**
21376
+ * Gets the active context menu items (object or global) with localized labels.
21377
+ */
21378
+ getResolvedContextMenuItems() {
21379
+ const items = this._core.store.selectionGroup ? this.objectContextMenuItems : this.globalContextMenuItems;
21380
+ return this.resolveLabels(items);
21381
+ }
21260
21382
  /**
21261
21383
  * Handles the context menu event (typically triggered by right-click).
21262
21384
  * This method performs the following operations:
@@ -21300,7 +21422,7 @@ class KritzelContextMenuHandler extends KritzelBaseHandler {
21300
21422
  this._core.addSelectionGroup(selectionGroup);
21301
21423
  this._core.rerender();
21302
21424
  }
21303
- this._core.store.state.contextMenuItems = this._core.store.selectionGroup ? this.objectContextMenuItems : this.globalContextMenuItems;
21425
+ this._core.store.state.contextMenuItems = this.getResolvedContextMenuItems();
21304
21426
  const clickX = event.clientX - this._core.store.offsetX;
21305
21427
  const clickY = event.clientY - this._core.store.offsetY;
21306
21428
  const { translateX, translateY, scale } = this._core.store.state;
@@ -21356,7 +21478,7 @@ class KritzelContextMenuHandler extends KritzelBaseHandler {
21356
21478
  this._core.addSelectionGroup(selectionGroup);
21357
21479
  }
21358
21480
  }
21359
- this._core.store.state.contextMenuItems = this._core.store.selectionGroup ? this.objectContextMenuItems : this.globalContextMenuItems;
21481
+ this._core.store.state.contextMenuItems = this.getResolvedContextMenuItems();
21360
21482
  this._core.store.state.contextMenuWorldX = x;
21361
21483
  this._core.store.state.contextMenuWorldY = y;
21362
21484
  const { translateX, translateY, scale } = this._core.store.state;
@@ -21403,6 +21525,7 @@ class KritzelCustomElement extends KritzelBaseObject {
21403
21525
  if (config) {
21404
21526
  this.translateX = config.translateX || 0;
21405
21527
  this.translateY = config.translateY || 0;
21528
+ this.rotation = KritzelMathHelper.degreesToRadians(config.rotation ?? 0);
21406
21529
  this.scale = config.scale || 1;
21407
21530
  this.element = config.element;
21408
21531
  this.height = config.height || 0;
@@ -23969,6 +24092,10 @@ class KritzelCore {
23969
24092
  _cursorManager;
23970
24093
  /** Manager for theme styling */
23971
24094
  _themeManager;
24095
+ /** Manager for localization / translated UI strings */
24096
+ _localizationManager;
24097
+ /** Manager for license validation and watermark gating */
24098
+ _licenseManager;
23972
24099
  /** Per-core registry of drawing tools (one instance per editor). */
23973
24100
  _toolRegistry;
23974
24101
  /** Optional unique identifier for namespacing storage keys across multiple editor instances */
@@ -24017,6 +24144,20 @@ class KritzelCore {
24017
24144
  get themeManager() {
24018
24145
  return this._themeManager;
24019
24146
  }
24147
+ /**
24148
+ * Gets the localization manager.
24149
+ * @returns The KritzelLocalizationManager for resolving translated UI strings
24150
+ */
24151
+ get localizationManager() {
24152
+ return this._localizationManager;
24153
+ }
24154
+ /**
24155
+ * Gets the license manager.
24156
+ * @returns The KritzelLicenseManager for validating licenses and gating the watermark
24157
+ */
24158
+ get licenseManager() {
24159
+ return this._licenseManager;
24160
+ }
24020
24161
  /**
24021
24162
  * Gets the tool registry scoped to this core instance.
24022
24163
  * @returns The KritzelToolRegistry owned by this core
@@ -24069,6 +24210,8 @@ class KritzelCore {
24069
24210
  this._anchorManager = new KritzelAnchorManager(this);
24070
24211
  this._cursorManager = new KritzelCursorManager(this);
24071
24212
  this._themeManager = new KritzelThemeManager(this);
24213
+ this._localizationManager = new KritzelLocalizationManager(this);
24214
+ this._licenseManager = new KritzelLicenseManager(this);
24072
24215
  this._toolRegistry = new KritzelToolRegistry(this);
24073
24216
  this._assetResolver = new KritzelAssetResolver();
24074
24217
  }
@@ -24104,6 +24247,8 @@ class KritzelCore {
24104
24247
  this._editorId = editorId;
24105
24248
  // Re-create theme manager so it picks up the new storage key
24106
24249
  this._themeManager = new KritzelThemeManager(this);
24250
+ // Re-create localization manager so it picks up the new storage key
24251
+ this._localizationManager = new KritzelLocalizationManager(this);
24107
24252
  }
24108
24253
  /**
24109
24254
  * Initializes the Yjs document for collaborative editing.
@@ -25250,7 +25395,7 @@ class KritzelSvgExportHelper {
25250
25395
  * @returns SVG document string
25251
25396
  */
25252
25397
  static generateSvg(objects, options = {}) {
25253
- const { theme, padding = 0, includeXmlDeclaration = true } = options;
25398
+ const { theme, padding = 0, includeXmlDeclaration = true, rasterSafeForCanvas = false, imageDataUrls } = options;
25254
25399
  if (!objects || objects.length === 0) {
25255
25400
  return '';
25256
25401
  }
@@ -25270,7 +25415,7 @@ class KritzelSvgExportHelper {
25270
25415
  // Generate SVG elements for each object
25271
25416
  const elements = objects
25272
25417
  .sort((a, b) => a.zIndex - b.zIndex)
25273
- .map(obj => this.objectToSvgElement(obj, theme))
25418
+ .map(obj => this.objectToSvgElement(obj, theme, imageDataUrls, rasterSafeForCanvas))
25274
25419
  .filter(Boolean)
25275
25420
  .join('\n ');
25276
25421
  const xmlDecl = includeXmlDeclaration ? '<?xml version="1.0" encoding="UTF-8"?>\n' : '';
@@ -25296,6 +25441,102 @@ class KritzelSvgExportHelper {
25296
25441
  }
25297
25442
  return result;
25298
25443
  }
25444
+ /**
25445
+ * Resolves every image referenced by the given objects (including those
25446
+ * nested inside groups) to an inlined `data:` URL, keyed by object id.
25447
+ *
25448
+ * This is required for export flows that rasterize the SVG (e.g. PNG
25449
+ * export): an SVG loaded into an `<img>` element will NOT load external
25450
+ * or `blob:` resources, so a `<image href="blob:...">` renders as a
25451
+ * broken-image placeholder. Embedding the bytes as a base64 `data:` URL
25452
+ * makes the image self-contained and survive rasterization.
25453
+ *
25454
+ * Resolution is best-effort: images that fail to resolve are simply
25455
+ * omitted from the map, and the SVG falls back to whatever `href` the
25456
+ * synchronous path would have produced.
25457
+ * @param objects - Objects to inline images for
25458
+ * @param assetResolver - Resolver used to fetch asset bytes; optional
25459
+ * @returns Map of image object id -> inlined `data:` URL
25460
+ */
25461
+ static async resolveImageDataUrls(objects, assetResolver) {
25462
+ const dataUrls = new Map();
25463
+ if (!objects || objects.length === 0) {
25464
+ return dataUrls;
25465
+ }
25466
+ const images = this.flattenObjects(objects).filter((obj) => KritzelClassHelper.isInstanceOf(obj, 'KritzelImage'));
25467
+ await Promise.all(images.map(async (image) => {
25468
+ // Legacy inline data URL is already embeddable as-is.
25469
+ if (image.src?.startsWith('data:')) {
25470
+ dataUrls.set(image.id, image.src);
25471
+ return;
25472
+ }
25473
+ // A resolvedSrc that is itself a data URL can be embedded directly.
25474
+ if (image.resolvedSrc?.startsWith('data:')) {
25475
+ dataUrls.set(image.id, image.resolvedSrc);
25476
+ return;
25477
+ }
25478
+ if (!image.assetId || !assetResolver) {
25479
+ return;
25480
+ }
25481
+ try {
25482
+ const blob = await assetResolver.fetchBlob(image.assetId);
25483
+ dataUrls.set(image.id, await this.blobToDataUrl(blob));
25484
+ }
25485
+ catch (err) {
25486
+ console.warn(`[KritzelSvgExportHelper] Failed to inline image asset ${image.assetId} for export:`, err);
25487
+ }
25488
+ }));
25489
+ return dataUrls;
25490
+ }
25491
+ /**
25492
+ * Reads a Blob into a base64 `data:` URL.
25493
+ * @param blob - The blob to encode
25494
+ * @returns Promise resolving to the data URL string
25495
+ */
25496
+ static async blobToDataUrl(blob) {
25497
+ // Prefer FileReader in browser-like runtimes for efficiency.
25498
+ const hasFileReader = typeof FileReader !== 'undefined';
25499
+ if (hasFileReader) {
25500
+ try {
25501
+ return await new Promise((resolve, reject) => {
25502
+ const reader = new FileReader();
25503
+ reader.onload = () => {
25504
+ if (typeof reader.result === 'string') {
25505
+ resolve(reader.result);
25506
+ return;
25507
+ }
25508
+ reject(new Error('FileReader result is not a string'));
25509
+ };
25510
+ reader.onerror = () => reject(reader.error ?? new Error('Failed to read blob as data URL'));
25511
+ reader.readAsDataURL(blob);
25512
+ });
25513
+ }
25514
+ catch {
25515
+ // Fall through to the ArrayBuffer-based encoder.
25516
+ }
25517
+ }
25518
+ const bytes = new Uint8Array(await blob.arrayBuffer());
25519
+ const mimeType = blob.type || 'application/octet-stream';
25520
+ return `data:${mimeType};base64,${this.uint8ToBase64(bytes)}`;
25521
+ }
25522
+ /**
25523
+ * Encodes a byte array to base64 across browser and Node-like runtimes.
25524
+ */
25525
+ static uint8ToBase64(bytes) {
25526
+ if (typeof Buffer !== 'undefined') {
25527
+ return Buffer.from(bytes).toString('base64');
25528
+ }
25529
+ let binary = '';
25530
+ const chunkSize = 0x8000;
25531
+ for (let i = 0; i < bytes.length; i += chunkSize) {
25532
+ const chunk = bytes.subarray(i, Math.min(i + chunkSize, bytes.length));
25533
+ binary += String.fromCharCode(...chunk);
25534
+ }
25535
+ if (typeof btoa === 'function') {
25536
+ return btoa(binary);
25537
+ }
25538
+ throw new Error('No base64 encoder available in this runtime');
25539
+ }
25299
25540
  /**
25300
25541
  * Collects SVG defs (markers, patterns, etc.) needed by the objects.
25301
25542
  * @param objects - Objects to collect defs for
@@ -25340,9 +25581,10 @@ class KritzelSvgExportHelper {
25340
25581
  * Converts a Kritzel object to its SVG element representation.
25341
25582
  * @param object - The object to convert
25342
25583
  * @param theme - Theme for color resolution
25584
+ * @param imageDataUrls - Optional map of image id -> inlined data URL
25343
25585
  * @returns SVG element string, or empty string if object type is not supported
25344
25586
  */
25345
- static objectToSvgElement(object, theme) {
25587
+ static objectToSvgElement(object, theme, imageDataUrls, rasterSafeForCanvas = false) {
25346
25588
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelPath')) {
25347
25589
  return this.pathToSvg(object, theme);
25348
25590
  }
@@ -25353,13 +25595,13 @@ class KritzelSvgExportHelper {
25353
25595
  return this.shapeToSvg(object, theme);
25354
25596
  }
25355
25597
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelText')) {
25356
- return this.textToSvg(object, theme);
25598
+ return this.textToSvg(object, theme, rasterSafeForCanvas);
25357
25599
  }
25358
25600
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelImage')) {
25359
- return this.imageToSvg(object);
25601
+ return this.imageToSvg(object, imageDataUrls);
25360
25602
  }
25361
25603
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelGroup')) {
25362
- return this.groupToSvg(object, theme);
25604
+ return this.groupToSvg(object, theme, imageDataUrls, rasterSafeForCanvas);
25363
25605
  }
25364
25606
  return '';
25365
25607
  }
@@ -25374,9 +25616,10 @@ class KritzelSvgExportHelper {
25374
25616
  const fill = KritzelColorHelper.resolveThemeColor(path.fill, theme);
25375
25617
  const stroke = KritzelColorHelper.resolveThemeColor(path.stroke, theme);
25376
25618
  const opacity = path.opacity !== 1 ? ` opacity="${path.opacity}"` : '';
25619
+ const scale = this.getObjectScale(path);
25377
25620
  // Path needs to be wrapped in a g with translation since path.d uses local coordinates
25378
25621
  return `<g transform="${transform}"${opacity}>
25379
- <svg viewBox="${path.viewBox}" width="${path.totalWidth / path.scale}" height="${path.totalHeight / path.scale}" overflow="visible">
25622
+ <svg viewBox="${path.viewBox}" width="${path.totalWidth / scale}" height="${path.totalHeight / scale}" overflow="visible">
25380
25623
  <path d="${path.d}" fill="${fill || 'none'}" stroke="${stroke || 'none'}"${path.strokeWidth ? ` stroke-width="${path.strokeWidth}"` : ''}/>
25381
25624
  </svg>
25382
25625
  </g>`;
@@ -25391,10 +25634,11 @@ class KritzelSvgExportHelper {
25391
25634
  const transform = this.buildTransform(line);
25392
25635
  const stroke = KritzelColorHelper.resolveThemeColor(line.stroke, theme);
25393
25636
  const opacity = line.opacity !== 1 ? ` opacity="${line.opacity}"` : '';
25637
+ const scale = this.getObjectScale(line);
25394
25638
  const markerStart = line.hasStartArrow ? ` marker-start="url(#${line.startMarkerId})"` : '';
25395
25639
  const markerEnd = line.hasEndArrow ? ` marker-end="url(#${line.endMarkerId})"` : '';
25396
25640
  return `<g transform="${transform}"${opacity}>
25397
- <svg viewBox="${line.viewBox}" width="${line.totalWidth / line.scale}" height="${line.totalHeight / line.scale}" overflow="visible">
25641
+ <svg viewBox="${line.viewBox}" width="${line.totalWidth / scale}" height="${line.totalHeight / scale}" overflow="visible">
25398
25642
  <path d="${line.d}" fill="none" stroke="${stroke}" stroke-width="${line.strokeWidth}" stroke-linecap="round"${markerStart}${markerEnd}/>
25399
25643
  </svg>
25400
25644
  </g>`;
@@ -25410,6 +25654,7 @@ class KritzelSvgExportHelper {
25410
25654
  const fill = KritzelColorHelper.resolveThemeColor(shape.fillColor, theme);
25411
25655
  const stroke = KritzelColorHelper.resolveThemeColor(shape.strokeColor, theme);
25412
25656
  const opacity = shape.opacity !== 1 ? ` opacity="${shape.opacity}"` : '';
25657
+ const scale = this.getObjectScale(shape);
25413
25658
  // Get the SVG path for the shape
25414
25659
  const pathD = shape.getSvgPath();
25415
25660
  // Handle text content if present
@@ -25428,7 +25673,7 @@ class KritzelSvgExportHelper {
25428
25673
  }
25429
25674
  }
25430
25675
  return `<g transform="${transform}"${opacity}>
25431
- <svg viewBox="${shape.viewBox}" width="${shape.totalWidth / shape.scale}" height="${shape.totalHeight / shape.scale}" overflow="visible" preserveAspectRatio="none">
25676
+ <svg viewBox="${shape.viewBox}" width="${shape.totalWidth / scale}" height="${shape.totalHeight / scale}" overflow="visible" preserveAspectRatio="none">
25432
25677
  <path d="${pathD}" fill="${fill || 'transparent'}" stroke="${stroke}" stroke-width="${shape.strokeWidth}"/>${textContent}
25433
25678
  </svg>
25434
25679
  </g>`;
@@ -25439,45 +25684,85 @@ class KritzelSvgExportHelper {
25439
25684
  * @param theme - Theme for color resolution
25440
25685
  * @returns SVG element string
25441
25686
  */
25442
- static textToSvg(text, theme) {
25687
+ static textToSvg(text, theme, rasterSafeForCanvas = false) {
25688
+ if (rasterSafeForCanvas) {
25689
+ return this.textToSvgRasterSafe(text, theme);
25690
+ }
25443
25691
  const transform = this.buildTransform(text);
25444
25692
  const opacity = text.opacity !== 1 ? ` opacity="${text.opacity}"` : '';
25445
25693
  const fontColor = KritzelColorHelper.resolveThemeColor(text.fontColor, theme);
25446
25694
  const bgColor = KritzelColorHelper.resolveThemeColor(text.backgroundColor, theme);
25695
+ const scale = this.getObjectScale(text);
25447
25696
  // Convert ProseMirror content to HTML
25448
25697
  const htmlContent = this.prosemirrorToHtml(text.content, text, theme);
25449
25698
  // Calculate dimensions accounting for scale
25450
- const width = text.totalWidth / text.scale;
25451
- const height = text.totalHeight / text.scale;
25699
+ const width = text.totalWidth / scale;
25700
+ const height = text.totalHeight / scale;
25452
25701
  return `<g transform="${transform}"${opacity}>
25453
25702
  <foreignObject x="0" y="0" width="${width}" height="${height}">
25454
25703
  <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;">
25455
25704
  ${htmlContent}
25456
25705
  </div>
25457
25706
  </foreignObject>
25707
+ </g>`;
25708
+ }
25709
+ /**
25710
+ * Converts a KritzelText to SVG using pure SVG text primitives.
25711
+ * This path avoids `<foreignObject>`, which taints canvases when the
25712
+ * generated SVG is rasterized through `<img>` and `drawImage`.
25713
+ */
25714
+ static textToSvgRasterSafe(text, theme) {
25715
+ const transform = this.buildTransform(text);
25716
+ const opacity = text.opacity !== 1 ? ` opacity="${text.opacity}"` : '';
25717
+ const fontColor = KritzelColorHelper.resolveThemeColor(text.fontColor, theme);
25718
+ const bgColor = KritzelColorHelper.resolveThemeColor(text.backgroundColor, theme);
25719
+ const scale = this.getObjectScale(text);
25720
+ const width = text.totalWidth / scale;
25721
+ const height = text.totalHeight / scale;
25722
+ const lines = this.prosemirrorToPlainTextLines(text.content);
25723
+ const resolvedLines = lines.length > 0 ? lines : [''];
25724
+ const resolvedFontSize = Math.max(1, text.fontSize * (text.scaleFactor || 1));
25725
+ const lineHeight = resolvedFontSize * 1.2;
25726
+ const startY = resolvedFontSize;
25727
+ const bgRect = bgColor && bgColor !== 'transparent'
25728
+ ? `<rect x="0" y="0" width="${width}" height="${height}" fill="${bgColor}"/>`
25729
+ : '';
25730
+ const tspans = resolvedLines
25731
+ .map((line, index) => {
25732
+ const y = startY + index * lineHeight;
25733
+ return `<tspan x="0" y="${y}" xml:space="preserve">${this.escapeHtml(line)}</tspan>`;
25734
+ })
25735
+ .join('');
25736
+ return `<g transform="${transform}"${opacity}>
25737
+ ${bgRect}
25738
+ <text font-family="${this.escapeHtml(text.fontFamily)}" font-size="${resolvedFontSize}" fill="${fontColor}">${tspans}</text>
25458
25739
  </g>`;
25459
25740
  }
25460
25741
  /**
25461
25742
  * Converts a KritzelImage to SVG.
25462
25743
  *
25463
- * Uses `resolvedSrc` when available (populated by the renderer via the
25464
- * asset resolver), falling back to the legacy inline `src` field for
25744
+ * Prefers an inlined `data:` URL from `imageDataUrls` (keyed by object
25745
+ * id) when provided, so the embedded bytes survive rasterization to
25746
+ * PNG. Falls back to `resolvedSrc` (populated by the renderer via the
25747
+ * asset resolver) and finally the legacy inline `src` field for
25465
25748
  * documents persisted before the asset layer existed.
25466
25749
  *
25467
- * Note: SVG export is synchronous and cannot await asset bytes from a
25468
- * remote provider. Callers that need the fully rasterized image bytes
25469
- * embedded in the exported SVG should pre-resolve assets via
25470
- * `core.assetResolver.fetchBlob` and inline them as data URLs.
25750
+ * Note: a `blob:` `resolvedSrc` renders correctly on-canvas but becomes
25751
+ * a broken-image placeholder once the SVG is loaded into an `<img>` and
25752
+ * drawn to a canvas. Callers that rasterize the SVG must pass
25753
+ * `imageDataUrls` (see {@link KritzelSvgExportHelper.resolveImageDataUrls}).
25471
25754
  * @param image - The image object
25755
+ * @param imageDataUrls - Optional map of image id -> inlined data URL
25472
25756
  * @returns SVG element string
25473
25757
  */
25474
- static imageToSvg(image) {
25758
+ static imageToSvg(image, imageDataUrls) {
25475
25759
  const transform = this.buildTransform(image);
25476
25760
  const opacity = image.opacity !== 1 ? ` opacity="${image.opacity}"` : '';
25761
+ const scale = this.getObjectScale(image);
25477
25762
  // Calculate dimensions
25478
- const width = image.totalWidth / image.scale;
25479
- const height = image.totalHeight / image.scale;
25480
- const href = image.resolvedSrc || image.src || '';
25763
+ const width = image.totalWidth / scale;
25764
+ const height = image.totalHeight / scale;
25765
+ const href = imageDataUrls?.get(image.id) || image.resolvedSrc || image.src || '';
25481
25766
  return `<g transform="${transform}"${opacity}>
25482
25767
  <image href="${href}" x="0" y="0" width="${width}" height="${height}" preserveAspectRatio="xMidYMid meet"/>
25483
25768
  </g>`;
@@ -25486,12 +25771,13 @@ class KritzelSvgExportHelper {
25486
25771
  * Converts a KritzelGroup to SVG by recursively converting children.
25487
25772
  * @param group - The group object
25488
25773
  * @param theme - Theme for color resolution
25774
+ * @param imageDataUrls - Optional map of image id -> inlined data URL
25489
25775
  * @returns SVG element string
25490
25776
  */
25491
- static groupToSvg(group, theme) {
25777
+ static groupToSvg(group, theme, imageDataUrls, rasterSafeForCanvas = false) {
25492
25778
  const children = group.children
25493
25779
  .sort((a, b) => a.zIndex - b.zIndex)
25494
- .map(child => this.objectToSvgElement(child, theme))
25780
+ .map(child => this.objectToSvgElement(child, theme, imageDataUrls, rasterSafeForCanvas))
25495
25781
  .filter(Boolean)
25496
25782
  .join('\n ');
25497
25783
  if (!children) {
@@ -25510,17 +25796,25 @@ class KritzelSvgExportHelper {
25510
25796
  */
25511
25797
  static buildTransform(object) {
25512
25798
  const transforms = [];
25799
+ const scale = this.getObjectScale(object);
25513
25800
  // Translation
25514
25801
  transforms.push(`translate(${object.translateX}, ${object.translateY})`);
25515
25802
  // Rotation around center
25516
25803
  if (object.rotation !== 0) {
25517
- const centerX = object.totalWidth / 2 / object.scale;
25518
- const centerY = object.totalHeight / 2 / object.scale;
25804
+ const centerX = object.totalWidth / 2 / scale;
25805
+ const centerY = object.totalHeight / 2 / scale;
25519
25806
  const degrees = object.rotation * (180 / Math.PI);
25520
25807
  transforms.push(`rotate(${degrees}, ${centerX}, ${centerY})`);
25521
25808
  }
25522
25809
  return transforms.join(' ');
25523
25810
  }
25811
+ /**
25812
+ * Returns a safe non-zero scale for export geometry calculations.
25813
+ */
25814
+ static getObjectScale(object) {
25815
+ const scale = object.scale ?? 1;
25816
+ return scale === 0 ? 1 : scale;
25817
+ }
25524
25818
  /**
25525
25819
  * Converts ProseMirror JSON content to HTML string.
25526
25820
  * @param content - ProseMirror document JSON
@@ -25576,6 +25870,54 @@ class KritzelSvgExportHelper {
25576
25870
  return '';
25577
25871
  }
25578
25872
  }
25873
+ /**
25874
+ * Converts ProseMirror JSON content to plain-text lines.
25875
+ * Used by raster-safe SVG export, which cannot depend on HTML/foreignObject.
25876
+ */
25877
+ static prosemirrorToPlainTextLines(content) {
25878
+ if (!content || !Array.isArray(content.content)) {
25879
+ return [];
25880
+ }
25881
+ const plainText = content.content
25882
+ .map((node) => this.nodeToPlainText(node))
25883
+ .join('')
25884
+ .replace(/\r\n/g, '\n')
25885
+ .replace(/\n+$/, '');
25886
+ if (!plainText) {
25887
+ return [];
25888
+ }
25889
+ return plainText.split('\n');
25890
+ }
25891
+ /**
25892
+ * Converts a ProseMirror node tree into plain text.
25893
+ */
25894
+ static nodeToPlainText(node) {
25895
+ if (!node) {
25896
+ return '';
25897
+ }
25898
+ switch (node.type) {
25899
+ case 'text':
25900
+ return node.text || '';
25901
+ case 'hard_break':
25902
+ return '\n';
25903
+ case 'paragraph': {
25904
+ const paragraph = Array.isArray(node.content)
25905
+ ? node.content.map((child) => this.nodeToPlainText(child)).join('')
25906
+ : '';
25907
+ return `${paragraph}\n`;
25908
+ }
25909
+ case 'list_item': {
25910
+ const item = Array.isArray(node.content)
25911
+ ? node.content.map((child) => this.nodeToPlainText(child)).join('')
25912
+ : '';
25913
+ return `- ${item.trim()}\n`;
25914
+ }
25915
+ default:
25916
+ return Array.isArray(node.content)
25917
+ ? node.content.map((child) => this.nodeToPlainText(child)).join('')
25918
+ : '';
25919
+ }
25920
+ }
25579
25921
  /**
25580
25922
  * Applies a ProseMirror mark to text.
25581
25923
  * @param text - The text to wrap
@@ -26626,6 +26968,11 @@ const KritzelEngine = class {
26626
26968
  onThemeChange(newValue) {
26627
26969
  this.core.themeManager.setTheme(newValue);
26628
26970
  }
26971
+ /** License key that, when valid, removes the "Powered by Kritzel" watermark. */
26972
+ licenseKey;
26973
+ onLicenseKeyChange(newValue) {
26974
+ this.core.licenseManager.validate(newValue);
26975
+ }
26629
26976
  /** An array of available themes for the editor. */
26630
26977
  themes;
26631
26978
  onThemesChange(newValue) {
@@ -26634,6 +26981,25 @@ const KritzelEngine = class {
26634
26981
  this.core.themeManager.applyTheme(this.core.themeManager.currentTheme);
26635
26982
  }
26636
26983
  }
26984
+ /** The current locale (language) code to apply to the editor, e.g. 'en', 'de', 'fr'. */
26985
+ locale = 'en';
26986
+ onLocaleChange(newValue) {
26987
+ this.core.localizationManager.setLocale(newValue);
26988
+ }
26989
+ /** An array of available locale definitions (with optional partial term overrides). */
26990
+ locales;
26991
+ onLocalesChange(newValue) {
26992
+ if (newValue && newValue.length > 0) {
26993
+ this.core.localizationManager.registerLocales(newValue);
26994
+ this.core.localizationManager.setLocale(this.locale);
26995
+ }
26996
+ }
26997
+ /** The locale used to resolve terms missing from the active locale. */
26998
+ fallbackLocale = 'en';
26999
+ onFallbackLocaleChange(newValue) {
27000
+ this.core.localizationManager.setFallbackLocale(newValue);
27001
+ this.core.rerender();
27002
+ }
26637
27003
  /** Left boundary of the viewport in world coordinates. Objects beyond this X position cannot be panned to. */
26638
27004
  viewportBoundaryLeft = -Infinity;
26639
27005
  onViewportBoundaryLeftChange(newValue) {
@@ -26661,8 +27027,10 @@ const KritzelEngine = class {
26661
27027
  this.core.store.state.debugInfo = newValue;
26662
27028
  }
26663
27029
  }
26664
- /** When false, wheel events will not trigger viewport pan/zoom. The event still propagates to parent elements. */
26665
- wheelEnabled = true;
27030
+ /** When false, non-modified wheel events do not pan the viewport. */
27031
+ isPanningEnabled = true;
27032
+ /** When false, Ctrl+wheel events do not zoom the viewport. */
27033
+ isZoomingEnabled = true;
26666
27034
  /** External loading state. Combined with internal workspace-loading state to drive the overlay. */
26667
27035
  isLoading = false;
26668
27036
  onIsLoadingChange() {
@@ -26705,13 +27073,19 @@ const KritzelEngine = class {
26705
27073
  if (this.core.store.isDisabled) {
26706
27074
  return;
26707
27075
  }
26708
- if (!this.wheelEnabled) {
26709
- return;
26710
- }
26711
27076
  if (this.core.store.state.isContextMenuVisible) {
26712
27077
  this.hideContextMenu();
26713
27078
  }
26714
- this.viewport.handleWheel(ev);
27079
+ if (ev.ctrlKey) {
27080
+ if (this.isZoomingEnabled) {
27081
+ this.viewport.handleWheel(ev);
27082
+ }
27083
+ }
27084
+ else {
27085
+ if (this.isPanningEnabled) {
27086
+ this.viewport.handleWheel(ev);
27087
+ }
27088
+ }
26715
27089
  this.core.store.state?.activeTool?.handleWheel(ev);
26716
27090
  }
26717
27091
  handlePointerDown(ev) {
@@ -27547,7 +27921,7 @@ const KritzelEngine = class {
27547
27921
  /**
27548
27922
  * Generates an SVG string from the currently selected objects.
27549
27923
  * Creates clean, standards-compliant SVG markup by directly serializing object properties.
27550
- * @param options - Optional export settings (theme, padding).
27924
+ * @param options - Optional export settings (theme, padding, raster-safe mode for canvas export).
27551
27925
  * @returns SVG markup string, or null if no objects are selected.
27552
27926
  */
27553
27927
  async getSelectedObjectsAsSvgString(options) {
@@ -27556,10 +27930,16 @@ const KritzelEngine = class {
27556
27930
  return null;
27557
27931
  }
27558
27932
  const theme = options?.theme ?? this.core.themeManager.getStoredTheme();
27933
+ // Inline image bytes as data URLs so they survive both standalone SVG
27934
+ // files and rasterization to PNG (a `blob:` href is not loaded when an
27935
+ // SVG is drawn into a canvas, which otherwise yields a broken image).
27936
+ const imageDataUrls = await KritzelSvgExportHelper.resolveImageDataUrls(selectedObjects, this.core.assetResolver);
27559
27937
  return KritzelSvgExportHelper.generateSvg(selectedObjects, {
27560
27938
  theme,
27561
27939
  padding: options?.padding ?? 0,
27562
27940
  includeXmlDeclaration: true,
27941
+ rasterSafeForCanvas: options?.rasterSafeForCanvas ?? false,
27942
+ imageDataUrls,
27563
27943
  });
27564
27944
  }
27565
27945
  /**
@@ -27598,6 +27978,9 @@ const KritzelEngine = class {
27598
27978
  const svgString = await this.getSelectedObjectsAsSvgString({
27599
27979
  theme: options?.theme,
27600
27980
  padding: options?.padding,
27981
+ // Avoid foreignObject-based text serialization, which taints the
27982
+ // canvas in SVG->PNG conversion paths.
27983
+ rasterSafeForCanvas: true,
27601
27984
  });
27602
27985
  if (!svgString) {
27603
27986
  return null;
@@ -27950,6 +28333,55 @@ const KritzelEngine = class {
27950
28333
  return null;
27951
28334
  }
27952
28335
  }
28336
+ /**
28337
+ * Registers additional locale definitions (with optional partial term overrides).
28338
+ * @param locales - The locale definitions to register.
28339
+ */
28340
+ async registerLocales(locales) {
28341
+ this.core.localizationManager.registerLocales(locales);
28342
+ this.core.localizationManager.setLocale(this.core.localizationManager.currentLocale);
28343
+ }
28344
+ /**
28345
+ * Sets the active locale (language) and re-renders the UI.
28346
+ * @param code - The locale code to activate, e.g. 'de'.
28347
+ */
28348
+ async setLocale(code) {
28349
+ this.locale = code;
28350
+ this.core.localizationManager.setLocale(code);
28351
+ }
28352
+ /**
28353
+ * Gets the currently active locale code.
28354
+ */
28355
+ async getLocale() {
28356
+ return this.core.localizationManager.currentLocale;
28357
+ }
28358
+ /**
28359
+ * Gets the list of available locale codes (built-in and registered).
28360
+ */
28361
+ async getAvailableLocales() {
28362
+ return this.core.localizationManager.getAvailableLocales();
28363
+ }
28364
+ /**
28365
+ * Gets the list of available locales as `{ code, label }` options for a selector.
28366
+ */
28367
+ async getAvailableLocaleOptions() {
28368
+ return this.core.localizationManager.getAvailableLocaleOptions();
28369
+ }
28370
+ /**
28371
+ * Resolves a term key to its translated string for the active locale.
28372
+ * @param key - The term key to resolve.
28373
+ * @param vars - Optional values for `{placeholder}` interpolation.
28374
+ */
28375
+ async t(key, vars) {
28376
+ return this.core.localizationManager.translate(key, vars);
28377
+ }
28378
+ /**
28379
+ * Resolves every known term key for the active locale into a flat map.
28380
+ * Useful for UI layers that need to localize many strings at once.
28381
+ */
28382
+ async getResolvedTerms() {
28383
+ return this.core.localizationManager.getAllTerms();
28384
+ }
27953
28385
  core;
27954
28386
  viewport;
27955
28387
  contextMenuHandler;
@@ -28040,6 +28472,7 @@ const KritzelEngine = class {
28040
28472
  // Clean up managers
28041
28473
  this.core.cursorManager.cleanup();
28042
28474
  this.core.themeManager.cleanup();
28475
+ this.core.licenseManager.destroy();
28043
28476
  }
28044
28477
  componentWillLoad() {
28045
28478
  this.core.setEditorId(this.editorId);
@@ -28048,6 +28481,13 @@ const KritzelEngine = class {
28048
28481
  }
28049
28482
  const editorElement = this.host.closest('kritzel-editor');
28050
28483
  this.core.themeManager.injectThemeEarly(editorElement || this.host);
28484
+ if (this.locales && this.locales.length > 0) {
28485
+ this.core.localizationManager.registerLocales(this.locales);
28486
+ }
28487
+ this.core.localizationManager.setFallbackLocale(this.fallbackLocale);
28488
+ this.core.localizationManager.setLocale(this.locale);
28489
+ this.core.licenseManager.validate(this.licenseKey);
28490
+ this.core.licenseManager.startPeriodicValidation();
28051
28491
  this.core.setUser(this.user);
28052
28492
  this.validateScaleMax(this.scaleMax);
28053
28493
  this.validateScaleMin(this.scaleMin);
@@ -28352,7 +28792,7 @@ const KritzelEngine = class {
28352
28792
  }
28353
28793
  render() {
28354
28794
  if (!this.viewport) {
28355
- return (h(Host, null, this.core.store.state.isLoading && (h("div", { class: "workspace-loading-overlay" }, h("span", { class: "workspace-loading-spinner" }), "Loading..."))));
28795
+ return (h(Host, null, this.core.store.state.isLoading && (h("div", { class: "workspace-loading-overlay" }, h("span", { class: "workspace-loading-spinner" }), this.core.localizationManager.translate('engine.loading')))));
28356
28796
  }
28357
28797
  const currentTheme = this.core.themeManager.getStoredTheme();
28358
28798
  const computedStyle = window.getComputedStyle(this.host);
@@ -28790,7 +29230,7 @@ const KritzelEngine = class {
28790
29230
  }, this.core.store.selectionGroup?.objects || []);
28791
29231
  }
28792
29232
  this.hideContextMenu();
28793
- }, onClose: () => this.hideContextMenu() })), this.core.store.objects?.hasAwareness && h("kritzel-awareness-cursors", { core: this.core }), this.core.store.state?.activeTool instanceof KritzelEraserTool && !this.core.store.state.isScaling && h("kritzel-cursor-trail", { core: this.core })));
29233
+ }, onClose: () => this.hideContextMenu() })), this.core.store.objects?.hasAwareness && h("kritzel-awareness-cursors", { core: this.core }), this.core.store.state?.activeTool instanceof KritzelEraserTool && !this.core.store.state.isScaling && h("kritzel-cursor-trail", { core: this.core }), !this.core.licenseManager.isLicensed && (h("kritzel-watermark", { core: this.core, label: this.core.localizationManager.translate('watermark.poweredBy') }))));
28794
29234
  }
28795
29235
  static get watchers() { return {
28796
29236
  "workspace": [{
@@ -28832,9 +29272,21 @@ const KritzelEngine = class {
28832
29272
  "theme": [{
28833
29273
  "onThemeChange": 0
28834
29274
  }],
29275
+ "licenseKey": [{
29276
+ "onLicenseKeyChange": 0
29277
+ }],
28835
29278
  "themes": [{
28836
29279
  "onThemesChange": 0
28837
29280
  }],
29281
+ "locale": [{
29282
+ "onLocaleChange": 0
29283
+ }],
29284
+ "locales": [{
29285
+ "onLocalesChange": 0
29286
+ }],
29287
+ "fallbackLocale": [{
29288
+ "onFallbackLocaleChange": 0
29289
+ }],
28838
29290
  "viewportBoundaryLeft": [{
28839
29291
  "onViewportBoundaryLeftChange": 0
28840
29292
  }],
@@ -28870,6 +29322,8 @@ const KritzelExport = class {
28870
29322
  * The name of the current workspace, used as default filename
28871
29323
  */
28872
29324
  workspaceName = 'workspace';
29325
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
29326
+ terms = {};
28873
29327
  isDialogOpen = false;
28874
29328
  previewUrl;
28875
29329
  isLoading = false;
@@ -28879,10 +29333,12 @@ const KritzelExport = class {
28879
29333
  exportPng;
28880
29334
  exportSvg;
28881
29335
  exportJson;
28882
- tabs = [
28883
- { id: 'viewport', label: 'Export Viewport' },
28884
- { id: 'workspace', label: 'Export Workspace' },
28885
- ];
29336
+ get tabs() {
29337
+ return [
29338
+ { id: 'viewport', label: this.terms['export.tabs.viewport'] ?? 'Export Viewport' },
29339
+ { id: 'workspace', label: this.terms['export.tabs.workspace'] ?? 'Export Workspace' },
29340
+ ];
29341
+ }
28886
29342
  viewportFormatOptions = [
28887
29343
  { value: 'png', label: 'PNG' },
28888
29344
  { value: 'svg', label: 'SVG' },
@@ -28925,13 +29381,13 @@ const KritzelExport = class {
28925
29381
  this.closeDialog();
28926
29382
  };
28927
29383
  renderViewportExport() {
28928
- return (h("div", { class: "export-tab-content" }, this.previewUrl && (h("div", { class: "preview-container" }, h("img", { src: this.previewUrl, alt: "Viewport Preview" }))), h("kritzel-input", { label: "Filename", value: this.exportFilename, placeholder: "Enter filename", suffix: `.${this.viewportExportFormat}`, onValueChange: this.handleFilenameChange }), h("div", { class: "format-selection" }, h("label", null, "Format"), h("kritzel-dropdown", { options: this.viewportFormatOptions, value: this.viewportExportFormat, forceOpenDirection: "up", onValueChanged: this.handleViewportFormatChange }))));
29384
+ return (h("div", { class: "export-tab-content" }, this.previewUrl && (h("div", { class: "preview-container" }, h("img", { src: this.previewUrl, alt: "Viewport Preview" }))), 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 }), h("div", { class: "format-selection" }, h("label", null, this.terms['export.format.label'] ?? 'Format'), h("kritzel-dropdown", { options: this.viewportFormatOptions, value: this.viewportExportFormat, forceOpenDirection: "up", onValueChanged: this.handleViewportFormatChange }))));
28929
29385
  }
28930
29386
  renderWorkspaceExport() {
28931
- return (h("div", { class: "export-tab-content" }, h("kritzel-input", { label: "Filename", value: this.exportFilename, placeholder: "Enter filename", suffix: ".json", onValueChange: this.handleFilenameChange })));
29387
+ return (h("div", { class: "export-tab-content" }, h("kritzel-input", { label: this.terms['export.filename.label'] ?? 'Filename', value: this.exportFilename, placeholder: this.terms['export.filename.placeholder'] ?? 'Enter filename', suffix: ".json", onValueChange: this.handleFilenameChange })));
28932
29388
  }
28933
29389
  render() {
28934
- return (h(Host, { key: 'efeea781325e672e3f4c1579a50da1c928dc88b5' }, h("kritzel-dialog", { key: '60e27233f484e70fd12bcc0f8a72b89d2f72d596', isOpen: this.isDialogOpen, dialogTitle: "Export", closable: true, contained: true, onDialogClose: this.closeDialog }, h("div", { key: 'e58e1d9804fdc8cb3d4c053ead641e2301b99ea5', class: "export-content" }, 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(), h("button", { key: '7166aee26e0dbbdf6e7348428f7a740614948e5e', class: "export-primary-button", onClick: this.handleExport }, "Export")))));
29390
+ return (h(Host, { key: 'ff6ce7e5d3a0a7a024148529f86bf3ec7ffb0333' }, h("kritzel-dialog", { key: '9051d262450af5fbe98959092f6bb0ac25ab43ce', isOpen: this.isDialogOpen, dialogTitle: this.terms['export.dialogTitle'] ?? 'Export', closable: true, contained: true, onDialogClose: this.closeDialog }, h("div", { key: '21f404a2101d5cf1d24aeeee663d8fb1854b9574', class: "export-content" }, h("kritzel-pill-tabs", { key: '80db6cda288d1b7019387a2f1ade278475ad6e24', tabs: this.tabs, value: this.activeTab, onValueChange: this.handleTabChange }), this.activeTab === 'viewport' && this.renderViewportExport(), this.activeTab === 'workspace' && this.renderWorkspaceExport(), h("button", { key: '45809f91ddd63c74f5a4b3a2e266919f244c6109', class: "export-primary-button", onClick: this.handleExport }, this.terms['export.exportButton'] ?? 'Export')))));
28935
29391
  }
28936
29392
  };
28937
29393
  KritzelExport.style = kritzelExportCss();
@@ -28946,7 +29402,7 @@ const KritzelFont = class {
28946
29402
  size = 24;
28947
29403
  color = '#000000';
28948
29404
  render() {
28949
- return (h(Host, { key: '6eef9e7df004469faeeaf458d9b9967f94f8536d' }, h("div", { key: '9926c54a57245c40d605077e191f62cebf3700b1', class: "font-preview", style: {
29405
+ return (h(Host, { key: '49c44f7fef61bd7116e63bbe9522b0a0e22de116' }, h("div", { key: '4d130682f67e9c0d758cb6194e6a95f15bceb2ee', class: "font-preview", style: {
28950
29406
  fontFamily: this.fontFamily,
28951
29407
  fontSize: `${this.size}px`,
28952
29408
  color: this.color
@@ -29021,7 +29477,8 @@ const KritzelFontSize = class {
29021
29477
  }
29022
29478
  render() {
29023
29479
  const color = 'var(--kritzel-global-text-primary)';
29024
- return (h(Host, { key: 'efb5ad516a0ebfea0fee5c78a1d3b4d0af4a6bc9' }, this.sizes.map(size => (h("div", { tabIndex: 0, class: {
29480
+ const sizes = this.sizes ?? [];
29481
+ return (h(Host, { key: '2083b05b3fdc9940c26dd90dbf9e097564ee976d' }, sizes.map(size => (h("div", { tabIndex: 0, class: {
29025
29482
  'size-container': true,
29026
29483
  'selected': this.selectedSize === size,
29027
29484
  }, onClick: () => this.handleSizeClick(size), onKeyDown: event => this.handleKeyDown(event, size) }, h("kritzel-font", { fontFamily: this.fontFamily, size: size, color: color }))))));
@@ -29088,7 +29545,7 @@ const KritzelInput = class {
29088
29545
  this.valueChange.emit(input.value);
29089
29546
  };
29090
29547
  render() {
29091
- return (h(Host, { key: '3fd1f42a3ad5edfac752c05f70558ef73bbfebc3' }, h("div", { key: '78468652ce95508090495fefa9381af175415be8', class: "input-container" }, this.label && h("label", { key: 'f368e0370df4848fa9448ed53382152ad8cc8816', class: "input-label" }, this.label), h("div", { key: 'c775c0c0a9f4b6c78ba83ced6237a744b7d3cf20', class: { 'input-wrapper': true, 'has-suffix': !!this.suffix } }, h("input", { key: 'aa5ce0bb5e3c56755bc6134a4328ce6294bda1ff', type: this.type, class: "text-input", value: this.inputValue, placeholder: this.placeholder, disabled: this.disabled, onInput: this.handleInput }), this.suffix && h("span", { key: '687cec4294e4cd10247db88caf157b33a3f16290', class: "input-suffix" }, this.suffix)))));
29548
+ return (h(Host, { key: '8584ea0fa1a2cf0b9518ea978303837da5bfaf31' }, h("div", { key: 'e9f3ac340f17580221d1a86c8eb913fce2acdf49', class: "input-container" }, this.label && h("label", { key: 'b0d4c0250b07d83a3cd99e0ab18ed672a29de5f0', class: "input-label" }, this.label), h("div", { key: '5044bcff621d6c6850ef3d225608427401b75ebd', class: { 'input-wrapper': true, 'has-suffix': !!this.suffix } }, h("input", { key: 'f8d057840e975d900c37a891268f39edb5128d49', type: this.type, class: "text-input", value: this.inputValue, placeholder: this.placeholder, disabled: this.disabled, onInput: this.handleInput }), this.suffix && h("span", { key: '4ae299b88798767b13e7126f88d3bd4703c91938', class: "input-suffix" }, this.suffix)))));
29092
29549
  }
29093
29550
  static get watchers() { return {
29094
29551
  "value": [{
@@ -29228,7 +29685,7 @@ const KritzelLoginDialog = class {
29228
29685
  this.dialogClosed.emit();
29229
29686
  };
29230
29687
  render() {
29231
- return (h(Host, { key: '8cac83db48fef2531f1669c3f601526b1e5cdefa' }, h("kritzel-dialog", { key: '34e7208c8c34550292c2b7503759bf103cfb49a6', dialogTitle: this.dialogTitle, isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, size: "small", contained: true }, h("div", { key: 'b0a0d8e0f38adc8d9b9545a02c5fc879f64a24de', class: "login-content" }, this.subtitle && (h("p", { key: 'a51b5f0a8b402aaf979d4bf47c6f9c3ba7e14bfe', class: "login-subtitle" }, this.subtitle)), h("div", { key: 'b6d8f8748eadf1462dd4161f089130b7ded31b59', class: "login-providers" }, this.providers.map(provider => (h("button", { key: provider.name, class: {
29688
+ return (h(Host, { key: '443d817d6eba7b9fcd31cab652293d7c01fc5ffa' }, h("kritzel-dialog", { key: '55214d78abb10f7e2ba789e10d98bf7f3287ff6a', dialogTitle: this.dialogTitle, isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, size: "small", contained: true }, h("div", { key: '31806a7a7c98c2210b7f60d2e0816a8ebc14aff3', class: "login-content" }, this.subtitle && (h("p", { key: '7edb2bc7d25cb3c8589594791822e62d035cd3a6', class: "login-subtitle" }, this.subtitle)), h("div", { key: '3b7731d9cfb98915ebe07b54a07991e03716ce5c', class: "login-providers" }, this.providers.map(provider => (h("button", { key: provider.name, class: {
29232
29689
  'provider-button': true,
29233
29690
  'is-loading': this.loadingProvider === provider.name,
29234
29691
  'is-disabled': this.loadingProvider !== null && this.loadingProvider !== provider.name,
@@ -29334,15 +29791,15 @@ const KritzelMasterDetail = class {
29334
29791
  const selectedItem = this.items.find(item => item.id === this.selectedItemId);
29335
29792
  const panelId = 'master-detail-panel';
29336
29793
  const selectedTabId = selectedItem ? `tab-${selectedItem.id}` : undefined;
29337
- return (h(Host, { key: '59479b50a3e79ee854c75d78e1a41c1cb0551dab' }, h("div", { key: '0fdfa2d5a28c8f5800b2eeb80c545bdfcd252f6b', class: {
29794
+ return (h(Host, { key: '6429c72aaf47f08ba0bdf5e64829b133339dafab' }, h("div", { key: '02e4616c67843632d2291abe347ee6ef7a660b12', class: {
29338
29795
  'master-detail-container': true,
29339
29796
  'is-mobile-detail-visible': this.showMobileDetail,
29340
- } }, h("nav", { key: 'f055346bdaf528e27136dffc680a5e2c6ddb95a7', class: "master-menu", role: "tablist", "aria-orientation": "vertical", "aria-label": "Settings categories" }, this.items.map((item, index) => (h("button", { key: item.id, id: `tab-${item.id}`, ref: el => this.setTabRef(el, index), class: {
29797
+ } }, h("nav", { key: '35cf3e8f97819a313fa6c57cde0faf05677997eb', class: "master-menu", role: "tablist", "aria-orientation": "vertical", "aria-label": "Settings categories" }, this.items.map((item, index) => (h("button", { key: item.id, id: `tab-${item.id}`, ref: el => this.setTabRef(el, index), class: {
29341
29798
  'menu-item': true,
29342
29799
  'is-selected': item.id === this.selectedItemId,
29343
29800
  'is-disabled': !!item.disabled,
29344
29801
  'is-focused': index === this.focusedIndex,
29345
- }, role: "tab", "aria-selected": item.id === this.selectedItemId ? 'true' : 'false', "aria-controls": panelId, "aria-disabled": item.disabled ? 'true' : undefined, tabIndex: this.getTabIndex(item, index), disabled: item.disabled, onClick: () => this.handleItemClick(item), onKeyDown: e => this.handleKeyDown(e, item, index), onFocus: () => this.handleFocus(index), onBlur: this.handleBlur }, item.icon && (h("kritzel-icon", { name: item.icon, size: 20, class: "menu-item-icon" })), h("span", { class: "menu-item-label" }, item.label), h("span", { class: "menu-item-chevron", "aria-hidden": "true" }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("path", { d: "m9 18 6-6-6-6" }))))))), h("div", { key: '296ff692092eae9b73c673def66539aa4eb36053', id: panelId, class: "detail-panel", role: "tabpanel", "aria-labelledby": selectedTabId }, h("button", { key: '72c65aa2825aa373daa3b1d6fd48d8dd84ab25f1', class: "mobile-back-button", onClick: this.handleBackClick, "aria-label": "Back to menu" }, h("kritzel-icon", { key: '2241991dc0da3f53f77dc415e61f026a5734ad48', name: "chevron-left", size: 20, class: "mobile-back-icon" }), "Back"), h("slot", { key: '6ecdc24e462faf0d95e295d5536b44ea2f9181c9' })))));
29802
+ }, role: "tab", "aria-selected": item.id === this.selectedItemId ? 'true' : 'false', "aria-controls": panelId, "aria-disabled": item.disabled ? 'true' : undefined, tabIndex: this.getTabIndex(item, index), disabled: item.disabled, onClick: () => this.handleItemClick(item), onKeyDown: e => this.handleKeyDown(e, item, index), onFocus: () => this.handleFocus(index), onBlur: this.handleBlur }, item.icon && (h("kritzel-icon", { name: item.icon, size: 20, class: "menu-item-icon" })), h("span", { class: "menu-item-label" }, item.label), h("span", { class: "menu-item-chevron", "aria-hidden": "true" }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("path", { d: "m9 18 6-6-6-6" }))))))), h("div", { key: 'cc7c69eb7ef02096fb76dbce310740d21019e72f', id: panelId, class: "detail-panel", role: "tabpanel", "aria-labelledby": selectedTabId }, h("button", { key: 'd10898c7eb8ec6d993024c7833e1f765a7b93079', class: "mobile-back-button", onClick: this.handleBackClick, "aria-label": "Back to menu" }, h("kritzel-icon", { key: '5395af1bce2b91ade970ad68b1bb87b59e771dff', name: "chevron-left", size: 20, class: "mobile-back-icon" }), "Back"), h("slot", { key: '7a5a1ef33081c117f1b16f1da887577bd97d3681' })))));
29346
29803
  }
29347
29804
  static get watchers() { return {
29348
29805
  "selectedItemId": [{
@@ -29415,7 +29872,7 @@ const KritzelMenu = class {
29415
29872
  this.itemCloseChildMenu.emit(event.detail);
29416
29873
  };
29417
29874
  render() {
29418
- return (h(Host, { key: '2d6d46fc8135133ed3e42d65399c8549bc5f6bb5', tabIndex: 0, onClick: e => e.stopPropagation() }, this.openChildMenuItem && h("div", { key: 'b5b3910cc82f7cb451730792fe6e3b3a254036f3', class: "has-open-child-overlay", onClick: this.onOverlayClick }), this.items.map(item => (h("kritzel-menu-item", { key: item.id, "data-testid": `menu-item-${item.id}`, item: item, parent: this.parent, style: { pointerEvents: this.editingMenuItem && !item.isEditing ? 'none' : 'auto' }, onItemSelect: this.handleItemSelect, onItemSave: this.handleSave, onItemCancel: this.handleCancel, onItemToggleChildMenu: this.handleToggleChildMenu, onItemCloseChildMenu: this.handleCloseChildMenu })))));
29875
+ return (h(Host, { key: '4e4e510d2b1b5834ba12739d95e7c33268c005a6', tabIndex: 0, onClick: e => e.stopPropagation() }, this.openChildMenuItem && h("div", { key: 'ee60d7f4ceefda5fcdcfb64956b20d42b703c231', class: "has-open-child-overlay", onClick: this.onOverlayClick }), this.items.map(item => (h("kritzel-menu-item", { key: item.id, "data-testid": `menu-item-${item.id}`, item: item, parent: this.parent, style: { pointerEvents: this.editingMenuItem && !item.isEditing ? 'none' : 'auto' }, onItemSelect: this.handleItemSelect, onItemSave: this.handleSave, onItemCancel: this.handleCancel, onItemToggleChildMenu: this.handleToggleChildMenu, onItemCloseChildMenu: this.handleCloseChildMenu })))));
29419
29876
  }
29420
29877
  };
29421
29878
  KritzelMenu.style = kritzelMenuCss();
@@ -29520,12 +29977,12 @@ const KritzelMenuItem = class {
29520
29977
  ];
29521
29978
  }
29522
29979
  render() {
29523
- return (h(Host, { key: 'ae5057ce9101dc08e2365455c544914715be468e', tabIndex: this.item.isDisabled ? -1 : 0, class: {
29980
+ return (h(Host, { key: 'ac91e3534c71074634ba053adc02f924e3fb5e14', tabIndex: this.item.isDisabled ? -1 : 0, class: {
29524
29981
  'selected': this.item.isSelected,
29525
29982
  'editing': this.item.isEditing,
29526
29983
  'disabled': this.item.isDisabled,
29527
29984
  'child-open': this.item.isChildMenuOpen,
29528
- }, onClick: this.handleItemSelect }, h("div", { key: '973fc68404ea95c2f0459565b8a3d124d31ae090', class: "menu-item-overlay" }), this.item.isEditing ? this.renderEditMode() : this.renderViewMode()));
29985
+ }, onClick: this.handleItemSelect }, h("div", { key: 'f49cf8a96f7394e244ce53d3933137325221d578', class: "menu-item-overlay" }), this.item.isEditing ? this.renderEditMode() : this.renderViewMode()));
29529
29986
  }
29530
29987
  static get watchers() { return {
29531
29988
  "item": [{
@@ -29565,6 +30022,8 @@ const KritzelMoreMenu = class {
29565
30022
  * Offset Y for the portal positioning
29566
30023
  */
29567
30024
  offsetY = 4;
30025
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
30026
+ terms = {};
29568
30027
  /**
29569
30028
  * Emitted when a menu item is selected
29570
30029
  */
@@ -29596,7 +30055,7 @@ const KritzelMoreMenu = class {
29596
30055
  this.closeMenu();
29597
30056
  };
29598
30057
  render() {
29599
- return (h(Host, { key: '93bee9fc14d532a74f1b077098fb0a470655d2fe', class: { mobile: this.isTouchDevice }, style: { display: this.visible ? '' : 'none' } }, h("div", { key: '1783013acb533de9580698f29a7c8ae212b583fc', class: { 'more-menu-wrapper': true, visible: this.visible } }, h("button", { key: '8dc2f098377e78db0bf6efc05daaf02496cef527', class: "more-menu-button", "data-testid": "more-menu-button", onClick: this.toggleMenu, "aria-label": "More options" }, h("kritzel-icon", { key: '876a229226b0f79f1d5ef5d0b7793f362b884923', name: this.icon, size: this.iconSize })), h("kritzel-portal", { key: '57f7a69408b00c1bb9e5a08d22e224c6e6bcdea4', anchor: this.menuAnchor, offsetY: this.offsetY, onClose: this.closeMenu }, h("kritzel-menu", { key: '73a2aacd1b7c0ec79d7fa1695fbc02b1a0bde1b5', items: this.visibleItems, onItemSelect: this.handleMenuItemSelect })))));
30058
+ return (h(Host, { key: '1fe90a065ac35d1c8d2e9159d006b09e104750a3', class: { mobile: this.isTouchDevice }, style: { display: this.visible ? '' : 'none' } }, h("div", { key: '734234df7d24f712fa80c3871209142fed2b5ac2', class: { 'more-menu-wrapper': true, visible: this.visible } }, h("button", { key: 'a6a56bd5714a8780339390904f0469b395748ba9', class: "more-menu-button", "data-testid": "more-menu-button", onClick: this.toggleMenu, "aria-label": this.terms['moreMenu.ariaLabel'] ?? 'More options' }, h("kritzel-icon", { key: 'a4405ef4b8219f28fec095d4a56ac0aa027a9564', name: this.icon, size: this.iconSize })), h("kritzel-portal", { key: '99aa8a87844232ec079b48f95afab54cbfe51a0b', anchor: this.menuAnchor, offsetY: this.offsetY, onClose: this.closeMenu }, h("kritzel-menu", { key: 'b6861ae2d553ebf5f5af7448c2b053576a380e8c', items: this.visibleItems, onItemSelect: this.handleMenuItemSelect })))));
29600
30059
  }
29601
30060
  };
29602
30061
  KritzelMoreMenu.style = kritzelMoreMenuCss();
@@ -29694,7 +30153,7 @@ const KritzelNumericInput = class {
29694
30153
  this.valueChange.emit(newValue);
29695
30154
  };
29696
30155
  render() {
29697
- return (h(Host, { key: '18cd66a764c334a78e2dccd444f448235b41c38c' }, h("div", { key: '73305950e3e840e51466d720a2ef4f834a16adc1', class: "input-container" }, this.label && h("label", { key: 'f9521cb0137f5f8c676ced5c76989d8d03256f68', class: "input-label" }, this.label), h("div", { key: '07581a242db247a833cfa879b90c6da5dd06b116', class: "input-wrapper" }, 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 }), h("div", { key: 'd3920c978a4d97032f5aec550e6ca7e74c9c2e94', class: "spinner-buttons" }, h("button", { key: 'ae27375f0ba66a9ca6cf53fe4a05fa219bf47711', type: "button", class: "spinner-button spinner-up", onClick: this.handleIncrement, tabIndex: -1, "aria-label": "Increase value" }, h("svg", { key: '8a6e40c12468bb44400c1ad015463c830d0af13e', viewBox: "0 0 10 6", class: "spinner-icon" }, h("path", { key: '70b8ee3cc3b00d7f83822078f73e0437e24a98aa', d: "M1 5L5 1L9 5", stroke: "currentColor", "stroke-width": "1.5", fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }))), h("button", { key: 'e26968c85480cbf4cd0f3bd9d6db59f1d6ade88c', type: "button", class: "spinner-button spinner-down", onClick: this.handleDecrement, tabIndex: -1, "aria-label": "Decrease value" }, h("svg", { key: 'a8c72dd2909b89ceb3797d0a77e531d0b5374e1d', viewBox: "0 0 10 6", class: "spinner-icon" }, h("path", { key: '16c95c8cbc90c9d49d081745384c3920620591fb', d: "M1 1L5 5L9 1", stroke: "currentColor", "stroke-width": "1.5", fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }))))))));
30156
+ return (h(Host, { key: '3679786121f22a0aacb1b57701764b0bd0bf02f5' }, h("div", { key: 'e88251c356332f29d93f6a3d82f5154188243e4c', class: "input-container" }, this.label && h("label", { key: 'cb4d64a5acf94794a5876bc61dc13dd8159ad055', class: "input-label" }, this.label), h("div", { key: '9faad300f263e871322b68635feefb08172bd15c', class: "input-wrapper" }, h("input", { key: 'dd58ade6fe8454a3e4f67fddbb135d279f1147a8', type: "number", class: "numeric-input", title: "", min: this.min === Number.MIN_SAFE_INTEGER ? undefined : this.min, max: this.max === Number.MAX_SAFE_INTEGER ? undefined : this.max, step: this.step, value: this.inputValue, placeholder: this.placeholder, onInput: this.handleInput, onBlur: this.handleBlur, onKeyDown: this.handleKeyDown, onInvalid: this.handleInvalid }), h("div", { key: 'a959beb441fe2143dc9c1c69347194e024362927', class: "spinner-buttons" }, h("button", { key: '6e2129f36080317e68b6fe1c05e69ea4e655fd20', type: "button", class: "spinner-button spinner-up", onClick: this.handleIncrement, tabIndex: -1, "aria-label": "Increase value" }, h("svg", { key: '47b5e178f82ef4eb6f2c5d888496ed95683c2f6d', viewBox: "0 0 10 6", class: "spinner-icon" }, h("path", { key: '6d5eb31ac5829b1e024822786beaafd347081c7e', d: "M1 5L5 1L9 5", stroke: "currentColor", "stroke-width": "1.5", fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }))), h("button", { key: '5a897f0b29ce89d5bd94327d2055753d06148c89', type: "button", class: "spinner-button spinner-down", onClick: this.handleDecrement, tabIndex: -1, "aria-label": "Decrease value" }, h("svg", { key: '19ff176c07e1c2a18f708debc7c7f3953f2219cb', viewBox: "0 0 10 6", class: "spinner-icon" }, h("path", { key: '0c1289eb009b62ea36f7397811f553a4e7e669f6', d: "M1 1L5 5L9 1", stroke: "currentColor", "stroke-width": "1.5", fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round" }))))))));
29698
30157
  }
29699
30158
  static get watchers() { return {
29700
30159
  "value": [{
@@ -29733,7 +30192,7 @@ const KritzelOpacitySlider = class {
29733
30192
  }
29734
30193
  render() {
29735
30194
  const percentage = this.getPercentage();
29736
- return (h(Host, { key: '988ad7b9ee8d3825c8577d951c6f1d00efbb45eb' }, h("div", { key: '4dad6a7d422333fbcaff55b2b374550b496a3adc', class: "opacity-container" }, h("div", { key: '7a0ceedd10f2e939dbf189410cc7263c7df498db', class: "slider-wrapper" }, 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: {
30195
+ return (h(Host, { key: '9b580f61b9113950b7dff5339879374f7882e1bd' }, h("div", { key: '80fb336580c49d4d7026993499b9c008ac2a4594', class: "opacity-container" }, h("div", { key: 'fcdd8e523d5f2ff4758a4839f2f43cbdabba4c5a', class: "slider-wrapper" }, h("input", { key: '914473676adc7cb8974c55b82ba1abf5b2787288', type: "range", class: "opacity-slider", min: this.min, max: this.max, step: this.step, value: this.value, onInput: (e) => this.handleInput(e), style: {
29737
30196
  '--slider-progress': `${percentage}%`,
29738
30197
  '--kritzel-opacity-slider-thumb-border-color': this.previewColor,
29739
30198
  } })))));
@@ -30086,7 +30545,7 @@ const KritzelPortal = class {
30086
30545
  this.portal.style.visibility = 'visible';
30087
30546
  }
30088
30547
  render() {
30089
- return (h(Host, { key: 'ea4b3c0bdaeb94a1aa03714537c3b68c972ad9c5', style: { display: this.anchor ? 'block' : 'none' } }, h("slot", { key: 'cd9060be1fbb801e3b72546d465576d5d79b3c82' })));
30548
+ return (h(Host, { key: 'f4ee3156b094ff1126db417f8396ca6cd0eb9aa4', style: { display: this.anchor ? 'block' : 'none' } }, h("slot", { key: '87812f4ddd50a29c8a76938e8778500a4873a30c' })));
30090
30549
  }
30091
30550
  static get watchers() { return {
30092
30551
  "anchor": [{
@@ -30100,7 +30559,7 @@ const KritzelPortal = class {
30100
30559
  * This file is auto-generated by the version bump scripts.
30101
30560
  * Do not modify manually.
30102
30561
  */
30103
- const KRITZEL_VERSION = '0.3.16';
30562
+ const KRITZEL_VERSION = '0.3.17';
30104
30563
 
30105
30564
  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)}`;
30106
30565
 
@@ -30117,13 +30576,7 @@ const DEFAULT_DEBUG_INFO = {
30117
30576
  showSyncProviderInfo: true,
30118
30577
  showMigrationInfo: true,
30119
30578
  };
30120
- const SETTINGS_CATEGORIES = [
30121
- { id: 'general', label: 'General', icon: 'settings' },
30122
- { id: 'viewport', label: 'Viewport', icon: 'viewport' },
30123
- { id: 'shortcuts', label: 'Keyboard Shortcuts', icon: 'command' },
30124
- { id: 'developer', label: 'Developer Options', icon: 'braces' },
30125
- { id: 'about', label: 'About', icon: 'info' },
30126
- ];
30579
+ const SETTINGS_CATEGORY_IDS = ['general', 'viewport', 'shortcuts', 'developer', 'about'];
30127
30580
  const KritzelSettings = class {
30128
30581
  constructor(hostRef) {
30129
30582
  registerInstance(this, hostRef);
@@ -30132,7 +30585,11 @@ const KritzelSettings = class {
30132
30585
  get host() { return getElement(this); }
30133
30586
  /** Keyboard shortcuts to display in the settings dialog */
30134
30587
  availableThemes = ['light', 'dark'];
30588
+ /** Available locales as `{ code, label }` options for the language selector. */
30589
+ availableLocales = [];
30135
30590
  shortcuts = [];
30591
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
30592
+ terms = {};
30136
30593
  /** Current settings values. Used to initialize and sync the component's internal state. */
30137
30594
  settings;
30138
30595
  onSettingsPropChange(newSettings) {
@@ -30141,11 +30598,12 @@ const KritzelSettings = class {
30141
30598
  }
30142
30599
  }
30143
30600
  isDialogOpen = false;
30144
- selectedCategoryId = SETTINGS_CATEGORIES[0].id;
30601
+ selectedCategoryId = SETTINGS_CATEGORY_IDS[0];
30145
30602
  scaleMin = DEFAULT_SCALE_MIN;
30146
30603
  scaleMax = DEFAULT_SCALE_MAX;
30147
30604
  lockDrawingScale = DEFAULT_LOCK_DRAWING_SCALE;
30148
30605
  theme = 'light';
30606
+ locale = 'en';
30149
30607
  viewportBoundaryLeft = DEFAULT_VIEWPORT_BOUNDARY_LEFT;
30150
30608
  viewportBoundaryRight = DEFAULT_VIEWPORT_BOUNDARY_RIGHT;
30151
30609
  viewportBoundaryTop = DEFAULT_VIEWPORT_BOUNDARY_TOP;
@@ -30171,6 +30629,9 @@ const KritzelSettings = class {
30171
30629
  if (typeof settings.theme === 'string') {
30172
30630
  this.theme = settings.theme;
30173
30631
  }
30632
+ if (typeof settings.locale === 'string') {
30633
+ this.locale = settings.locale;
30634
+ }
30174
30635
  if (typeof settings.viewportBoundaryLeft === 'number') {
30175
30636
  this.viewportBoundaryLeft = settings.viewportBoundaryLeft;
30176
30637
  }
@@ -30193,6 +30654,7 @@ const KritzelSettings = class {
30193
30654
  scaleMax: this.scaleMax,
30194
30655
  lockDrawingScale: this.lockDrawingScale,
30195
30656
  theme: this.theme,
30657
+ locale: this.locale,
30196
30658
  viewportBoundaryLeft: this.viewportBoundaryLeft,
30197
30659
  viewportBoundaryRight: this.viewportBoundaryRight,
30198
30660
  viewportBoundaryTop: this.viewportBoundaryTop,
@@ -30217,6 +30679,10 @@ const KritzelSettings = class {
30217
30679
  this.theme = event.detail;
30218
30680
  this.emitSettings();
30219
30681
  };
30682
+ handleLocaleChange = (event) => {
30683
+ this.locale = event.detail;
30684
+ this.emitSettings();
30685
+ };
30220
30686
  handleViewportBoundaryLeftChange = (event) => {
30221
30687
  this.viewportBoundaryLeft = event.detail ?? DEFAULT_VIEWPORT_BOUNDARY_LEFT;
30222
30688
  this.emitSettings();
@@ -30272,24 +30738,52 @@ const KritzelSettings = class {
30272
30738
  }
30273
30739
  return grouped;
30274
30740
  }
30741
+ /**
30742
+ * Resolves a localized string from the supplied {@link terms} map, falling
30743
+ * back to the provided English default when the key is missing.
30744
+ */
30745
+ t(key, fallback) {
30746
+ return this.terms[key] ?? fallback;
30747
+ }
30748
+ get categories() {
30749
+ const icons = {
30750
+ general: 'settings',
30751
+ viewport: 'viewport',
30752
+ shortcuts: 'command',
30753
+ developer: 'braces',
30754
+ about: 'info',
30755
+ };
30756
+ const labels = {
30757
+ general: { key: 'settings.categories.general', fallback: 'General' },
30758
+ viewport: { key: 'settings.categories.viewport', fallback: 'Viewport' },
30759
+ shortcuts: { key: 'settings.categories.shortcuts', fallback: 'Keyboard Shortcuts' },
30760
+ developer: { key: 'settings.categories.developer', fallback: 'Developer Options' },
30761
+ about: { key: 'settings.categories.about', fallback: 'About' },
30762
+ };
30763
+ return SETTINGS_CATEGORY_IDS.map(id => ({
30764
+ id,
30765
+ label: this.t(labels[id].key, labels[id].fallback),
30766
+ icon: icons[id],
30767
+ }));
30768
+ }
30275
30769
  renderCategoryContent() {
30276
30770
  switch (this.selectedCategoryId) {
30277
30771
  case 'general':
30278
- return (h("div", { class: "settings-content" }, h("h3", null, "General Settings"), h("div", { class: "settings-group" }, h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, "Theme"), h("p", { class: "settings-description" }, "Select a registered color theme for the editor interface."), h("kritzel-dropdown", { options: this.availableThemes.map(t => ({ value: t, label: t })), value: this.theme, onValueChanged: this.handleThemeChange })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, "Lock Drawing Scale"), h("p", { class: "settings-description" }, "When enabled, drawn objects maintain a fixed visual size regardless of the current zoom level."), h("kritzel-slide-toggle", { checked: this.lockDrawingScale, label: "Lock Drawing Scale", onCheckedChange: this.handleLockDrawingScaleChange })))));
30772
+ return (h("div", { class: "settings-content" }, h("h3", null, this.t('settings.general.title', 'General Settings')), h("div", { class: "settings-group" }, h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, this.t('settings.general.theme.label', 'Theme')), h("p", { class: "settings-description" }, this.t('settings.general.theme.description', 'Select a registered color theme for the editor interface.')), h("kritzel-dropdown", { options: this.availableThemes.map(t => ({ value: t, label: t })), value: this.theme, onValueChanged: this.handleThemeChange })), this.availableLocales.length > 0 && (h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, this.t('settings.general.language.label', 'Language')), h("p", { class: "settings-description" }, this.t('settings.general.language.description', 'Select the display language for the editor interface.')), h("kritzel-dropdown", { options: this.availableLocales.map(l => ({ value: l.code, label: l.label })), value: this.locale, onValueChanged: this.handleLocaleChange }))), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, this.t('settings.general.lockDrawingScale.label', 'Lock Drawing Scale')), 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.')), h("kritzel-slide-toggle", { checked: this.lockDrawingScale, label: this.t('settings.general.lockDrawingScale.label', 'Lock Drawing Scale'), onCheckedChange: this.handleLockDrawingScaleChange })))));
30279
30773
  case 'viewport':
30280
- return (h("div", { class: "settings-content" }, h("h3", null, "Viewport Settings"), h("div", { class: "settings-group" }, h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, "Minimum Zoom Level"), h("p", { class: "settings-description" }, "Sets the minimum zoom level. Lower values allow zooming out further to see more of the canvas."), h("kritzel-numeric-input", { value: this.scaleMin, min: 0.0001, max: 1, step: 0.0001, onValueChange: this.handleScaleMinChange })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, "Maximum Zoom Level"), h("p", { class: "settings-description" }, "Sets the maximum zoom level. Higher values allow zooming in closer for detailed work."), h("kritzel-numeric-input", { value: this.scaleMax, min: 1, max: 1000, step: 1, onValueChange: this.handleScaleMaxChange })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, "Viewport Boundary Left"), h("p", { class: "settings-description" }, "Left boundary in world coordinates. Set to limit how far left the viewport can pan."), h("kritzel-numeric-input", { value: this.viewportBoundaryLeft, step: 100, placeholder: "Infinite", onValueChange: this.handleViewportBoundaryLeftChange })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, "Viewport Boundary Right"), h("p", { class: "settings-description" }, "Right boundary in world coordinates. Set to limit how far right the viewport can pan."), h("kritzel-numeric-input", { value: this.viewportBoundaryRight, step: 100, placeholder: "Infinite", onValueChange: this.handleViewportBoundaryRightChange })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, "Viewport Boundary Top"), h("p", { class: "settings-description" }, "Top boundary in world coordinates. Set to limit how far up the viewport can pan."), h("kritzel-numeric-input", { value: this.viewportBoundaryTop, step: 100, placeholder: "Infinite", onValueChange: this.handleViewportBoundaryTopChange })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, "Viewport Boundary Bottom"), h("p", { class: "settings-description" }, "Bottom boundary in world coordinates. Set to limit how far down the viewport can pan."), h("kritzel-numeric-input", { value: this.viewportBoundaryBottom, step: 100, placeholder: "Infinite", onValueChange: this.handleViewportBoundaryBottomChange })))));
30774
+ return (h("div", { class: "settings-content" }, h("h3", null, this.t('settings.viewport.title', 'Viewport Settings')), h("div", { class: "settings-group" }, h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, this.t('settings.viewport.minZoom.label', 'Minimum Zoom Level')), 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.')), h("kritzel-numeric-input", { value: this.scaleMin, min: 0.0001, max: 1, step: 0.0001, onValueChange: this.handleScaleMinChange })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, this.t('settings.viewport.maxZoom.label', 'Maximum Zoom Level')), 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.')), h("kritzel-numeric-input", { value: this.scaleMax, min: 1, max: 1000, step: 1, onValueChange: this.handleScaleMaxChange })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, this.t('settings.viewport.boundaryLeft.label', 'Viewport Boundary Left')), 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.')), h("kritzel-numeric-input", { value: this.viewportBoundaryLeft, step: 100, placeholder: this.t('settings.viewport.boundaryPlaceholder', 'Infinite'), onValueChange: this.handleViewportBoundaryLeftChange })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, this.t('settings.viewport.boundaryRight.label', 'Viewport Boundary Right')), 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.')), h("kritzel-numeric-input", { value: this.viewportBoundaryRight, step: 100, placeholder: this.t('settings.viewport.boundaryPlaceholder', 'Infinite'), onValueChange: this.handleViewportBoundaryRightChange })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, this.t('settings.viewport.boundaryTop.label', 'Viewport Boundary Top')), 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.')), h("kritzel-numeric-input", { value: this.viewportBoundaryTop, step: 100, placeholder: this.t('settings.viewport.boundaryPlaceholder', 'Infinite'), onValueChange: this.handleViewportBoundaryTopChange })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, this.t('settings.viewport.boundaryBottom.label', 'Viewport Boundary Bottom')), 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.')), h("kritzel-numeric-input", { value: this.viewportBoundaryBottom, step: 100, placeholder: this.t('settings.viewport.boundaryPlaceholder', 'Infinite'), onValueChange: this.handleViewportBoundaryBottomChange })))));
30281
30775
  case 'shortcuts':
30282
- return (h("div", { class: "settings-content" }, h("h3", null, "Keyboard Shortcuts"), h("div", { class: "shortcuts-list" }, Array.from(this.groupShortcutsByCategory()).map(([category, shortcuts]) => (h("div", { class: "shortcuts-category", key: category }, h("h4", { class: "shortcuts-category-title" }, category), h("div", { class: "shortcuts-group" }, shortcuts.map(shortcut => (h("div", { class: "shortcut-item", key: shortcut.key + shortcut.label }, h("span", { class: "shortcut-label" }, shortcut.label), h("kbd", { class: "shortcut-key" }, this.formatKeyCombo(shortcut))))))))))));
30776
+ return (h("div", { class: "settings-content" }, h("h3", null, this.t('settings.shortcuts.title', 'Keyboard Shortcuts')), h("div", { class: "shortcuts-list" }, Array.from(this.groupShortcutsByCategory()).map(([category, shortcuts]) => (h("div", { class: "shortcuts-category", key: category }, h("h4", { class: "shortcuts-category-title" }, category), h("div", { class: "shortcuts-group" }, shortcuts.map(shortcut => (h("div", { class: "shortcut-item", key: shortcut.key + shortcut.label }, h("span", { class: "shortcut-label" }, shortcut.label), h("kbd", { class: "shortcut-key" }, this.formatKeyCombo(shortcut))))))))))));
30283
30777
  case 'developer':
30284
- return (h("div", { class: "settings-content" }, h("h3", null, "Developer Options"), h("div", { class: "settings-group" }, h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, "Show Viewport Info"), h("p", { class: "settings-description" }, "Display viewport debug information such as position, zoom level, and boundaries."), h("kritzel-slide-toggle", { checked: this.debugInfo.showViewportInfo, label: "Show Viewport Info", onCheckedChange: this.handleDebugInfoChange('showViewportInfo') })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, "Show Object Info"), h("p", { class: "settings-description" }, "Display debug information about objects on the canvas."), h("kritzel-slide-toggle", { checked: this.debugInfo.showObjectInfo, label: "Show Object Info", onCheckedChange: this.handleDebugInfoChange('showObjectInfo') })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, "Show Sync Provider Info"), h("p", { class: "settings-description" }, "Display debug information about the sync provider connection status."), h("kritzel-slide-toggle", { checked: this.debugInfo.showSyncProviderInfo, label: "Show Sync Provider Info", onCheckedChange: this.handleDebugInfoChange('showSyncProviderInfo') })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, "Show Migration Info"), h("p", { class: "settings-description" }, "Display debug information about data migrations."), h("kritzel-slide-toggle", { checked: this.debugInfo.showMigrationInfo, label: "Show Migration Info", onCheckedChange: this.handleDebugInfoChange('showMigrationInfo') })))));
30778
+ return (h("div", { class: "settings-content" }, h("h3", null, this.t('settings.developer.title', 'Developer Options')), h("div", { class: "settings-group" }, h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, this.t('settings.developer.showViewportInfo.label', 'Show Viewport Info')), h("p", { class: "settings-description" }, this.t('settings.developer.showViewportInfo.description', 'Display viewport debug information such as position, zoom level, and boundaries.')), h("kritzel-slide-toggle", { checked: this.debugInfo.showViewportInfo, label: this.t('settings.developer.showViewportInfo.label', 'Show Viewport Info'), onCheckedChange: this.handleDebugInfoChange('showViewportInfo') })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, this.t('settings.developer.showObjectInfo.label', 'Show Object Info')), h("p", { class: "settings-description" }, this.t('settings.developer.showObjectInfo.description', 'Display debug information about objects on the canvas.')), h("kritzel-slide-toggle", { checked: this.debugInfo.showObjectInfo, label: this.t('settings.developer.showObjectInfo.label', 'Show Object Info'), onCheckedChange: this.handleDebugInfoChange('showObjectInfo') })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, this.t('settings.developer.showSyncProviderInfo.label', 'Show Sync Provider Info')), h("p", { class: "settings-description" }, this.t('settings.developer.showSyncProviderInfo.description', 'Display debug information about the sync provider connection status.')), h("kritzel-slide-toggle", { checked: this.debugInfo.showSyncProviderInfo, label: this.t('settings.developer.showSyncProviderInfo.label', 'Show Sync Provider Info'), onCheckedChange: this.handleDebugInfoChange('showSyncProviderInfo') })), h("div", { class: "settings-item" }, h("label", { class: "settings-label" }, this.t('settings.developer.showMigrationInfo.label', 'Show Migration Info')), h("p", { class: "settings-description" }, this.t('settings.developer.showMigrationInfo.description', 'Display debug information about data migrations.')), h("kritzel-slide-toggle", { checked: this.debugInfo.showMigrationInfo, label: this.t('settings.developer.showMigrationInfo.label', 'Show Migration Info'), onCheckedChange: this.handleDebugInfoChange('showMigrationInfo') })))));
30285
30779
  case 'about':
30286
- return (h("div", { class: "settings-content" }, h("h3", null, "About"), h("p", null, "Kritzel - A drawing application"), h("p", { class: "version-info" }, "Version ", KRITZEL_VERSION), h("p", { class: "version-info" }, "App-State Schema v", CURRENT_APP_STATE_SCHEMA_VERSION), h("p", { class: "version-info" }, "Workspace Schema v", CURRENT_WORKSPACE_SCHEMA_VERSION)));
30780
+ return (h("div", { class: "settings-content" }, h("h3", null, this.t('settings.about.title', 'About')), h("p", null, this.t('settings.about.description', 'Kritzel - A drawing application')), h("p", { class: "version-info" }, "Version ", KRITZEL_VERSION), h("p", { class: "version-info" }, "App-State Schema v", CURRENT_APP_STATE_SCHEMA_VERSION), h("p", { class: "version-info" }, "Workspace Schema v", CURRENT_WORKSPACE_SCHEMA_VERSION)));
30287
30781
  default:
30288
30782
  return null;
30289
30783
  }
30290
30784
  }
30291
30785
  render() {
30292
- return (h(Host, { key: '46c6792ae9cdd932d3dc71526862c9281c0cefc1' }, h("kritzel-dialog", { key: '1cd288cdf8b26bea378665c54bfc14577597fe49', isOpen: this.isDialogOpen, dialogTitle: "Settings", size: "large", contained: true, onDialogClose: this.closeDialog }, h("kritzel-master-detail", { key: '4d07e94ebb09035807356bab4bc7eaca57c36c6c', items: SETTINGS_CATEGORIES, selectedItemId: this.selectedCategoryId, onItemSelect: this.handleCategorySelect }, this.renderCategoryContent()))));
30786
+ return (h(Host, { key: '8dc22abaa2a19a14f3bbef10e34d133b16510498' }, h("kritzel-dialog", { key: '54072bf290e2d86bb7ec40d8d3e630be46fa3e1e', isOpen: this.isDialogOpen, dialogTitle: this.t('settings.dialogTitle', 'Settings'), size: "large", contained: true, onDialogClose: this.closeDialog }, h("kritzel-master-detail", { key: '5be80411c943ab4b7b90129527c8a0d147306a5f', items: this.categories, selectedItemId: this.selectedCategoryId, onItemSelect: this.handleCategorySelect }, this.renderCategoryContent()))));
30293
30787
  }
30294
30788
  static get watchers() { return {
30295
30789
  "settings": [{
@@ -30350,6 +30844,8 @@ const KritzelShareDialog = class {
30350
30844
  * The ID of the workspace being shared. Used to build the share URL.
30351
30845
  */
30352
30846
  workspaceId = undefined;
30847
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
30848
+ terms = {};
30353
30849
  onIsPublicChange(newValue) {
30354
30850
  this.internalIsPublic = newValue;
30355
30851
  }
@@ -30420,9 +30916,9 @@ const KritzelShareDialog = class {
30420
30916
  this.dialogClosed.emit();
30421
30917
  };
30422
30918
  render() {
30423
- return (h(Host, { key: 'a104c14b2492d97f3ada98c9eaaa845d63074063' }, h("kritzel-dialog", { key: '1b12b27504153e54aeb0cb4e6b1030a0d43b9735', dialogTitle: "Share Workspace", size: "small", isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, contained: true }, h("div", { key: '652f23e37876be356beb6f93abf5930e91d82cea', class: "share-content" }, h("div", { key: 'aaf336f2ac86fe23cac79cef920a9d67681046e2', class: "share-section" }, h("div", { key: '8075a7b3fff47c4b924d3b2d92b1377641920939', class: "share-row" }, h("div", { key: '41e98a74a5d4aede50fd75a7de62cbef9b5a5a31', class: "share-label-group" }, h("label", { key: '8a4f53e13d5a81497dd31316a49971c7245d82a2', class: "share-label" }, "Link sharing"), h("p", { key: '907a59d50e595734f03067f70830cf96defdf8d8', class: "share-description" }, this.internalIsPublic
30424
- ? 'Anyone with the link can access this workspace.'
30425
- : 'Link sharing is disabled. Only you can access this workspace.')), h("kritzel-slide-toggle", { key: '0d75cfeeb63c33d20380ffe9a7e4c27148548ef9', checked: this.internalIsPublic, onCheckedChange: this.handleToggleChange, label: "Enable link sharing" }))), this.internalIsPublic && (h("div", { key: 'a8a10c74fd326c5097e4a5f0ee165602c3606ade', class: "share-section" }, h("div", { key: '6261a9fc6cb2be2a50856fb8a990b9da3fee84bf', class: "share-url-container" }, h("input", { key: '26ee72eebfee88d06a50c338cccc9af296c8ba4c', type: "text", class: "share-url-input", value: this.getShareUrl(), readOnly: true, onClick: (e) => e.target.select() }), h("button", { key: '4b1ec06fa27c95d9d0bb93f8cbb851a02fdd52cc', class: { 'copy-button': true, 'copy-success': this.copySuccess }, onClick: this.handleCopyUrl, title: this.copySuccess ? 'Copied!' : 'Copy link' }, h("kritzel-icon", { key: 'd9eea56b3523fcc3557d9868f3da7f28449a9447', name: this.copySuccess ? 'check' : 'copy', size: 18 })))))))));
30919
+ return (h(Host, { key: 'b364f891659d54623b5472fe4cd5a9a85bc2fafa' }, h("kritzel-dialog", { key: '7059a3ede84bc67f7661efc8c84eae9b0c401d43', dialogTitle: this.terms['share.dialogTitle'] ?? 'Share Workspace', size: "small", isOpen: this.isDialogOpen, onDialogClose: this.closeDialog, contained: true }, h("div", { key: '1b0e7a9b5483a53f91e874075c0edb116b7474d6', class: "share-content" }, h("div", { key: '1568dc99224f0f7e2a370c22327dc313515e7d0f', class: "share-section" }, h("div", { key: 'a0032f6c870a448e817b013a715aadca1c455bb3', class: "share-row" }, h("div", { key: '3bd74cbebc122133354465163f9945dd207245b5', class: "share-label-group" }, h("label", { key: '0919fc24e5637c908c53b6914983833b6600e846', class: "share-label" }, this.terms['share.linkSharing.label'] ?? 'Link sharing'), h("p", { key: 'f87c9eae7112d284d7d917fae7c9c05fa6feb0ed', class: "share-description" }, this.internalIsPublic
30920
+ ? (this.terms['share.linkSharing.enabledDescription'] ?? 'Anyone with the link can access this workspace.')
30921
+ : (this.terms['share.linkSharing.disabledDescription'] ?? 'Link sharing is disabled. Only you can access this workspace.'))), h("kritzel-slide-toggle", { key: '81ccfb76255d40da954cfdcbc486bdbb7dc4bff4', checked: this.internalIsPublic, onCheckedChange: this.handleToggleChange, label: this.terms['share.linkSharing.toggleLabel'] ?? 'Enable link sharing' }))), this.internalIsPublic && (h("div", { key: 'c72da3550416ebfef5c24690591b212df70cb4db', class: "share-section" }, h("div", { key: 'c3dd4bce7d57e60d09989bff6dd1d828a613b425', class: "share-url-container" }, h("input", { key: '67aab8a65f54ea67392a9874e949d6d1af519e0f', type: "text", class: "share-url-input", value: this.getShareUrl(), readOnly: true, onClick: (e) => e.target.select() }), h("button", { key: 'c31d48fe67cdd12aa517e20bac54ac74c05abd04', class: { 'copy-button': true, 'copy-success': this.copySuccess }, onClick: this.handleCopyUrl, title: this.copySuccess ? (this.terms['share.copyLink.copied'] ?? 'Copied!') : (this.terms['share.copyLink.title'] ?? 'Copy link') }, h("kritzel-icon", { key: '08ed01ed081e955e28eaed577438cd0008eaacf4', name: this.copySuccess ? 'check' : 'copy', size: 18 })))))))));
30426
30922
  }
30427
30923
  static get watchers() { return {
30428
30924
  "isPublic": [{
@@ -30460,7 +30956,7 @@ const KritzelSlideToggle = class {
30460
30956
  }
30461
30957
  };
30462
30958
  render() {
30463
- return (h(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 }, h("div", { key: 'd5fa5091ad54032f81dad3879149c4d8ec7ea37b', class: "toggle-track" }, h("div", { key: 'cce0d61431ed65a26926b0a496a5c22eb4169577', class: "toggle-thumb" }))));
30959
+ return (h(Host, { key: '92aefb30139abd512c02c99f5c532cb69daab8c3', class: { checked: this.checked, disabled: this.disabled }, tabIndex: this.disabled ? -1 : 0, role: "switch", "aria-checked": this.checked ? 'true' : 'false', "aria-disabled": this.disabled ? 'true' : 'false', "aria-label": this.label, onClick: this.handleToggle, onKeyDown: this.handleKeyDown }, h("div", { key: '53fc90f3a11055e0b16358be0e3beed2ed25bc48', class: "toggle-track" }, h("div", { key: 'e0a0cecb9ede9c8e932cbb6fa7cdc359c07be8db', class: "toggle-thumb" }))));
30464
30960
  }
30465
30961
  };
30466
30962
  KritzelSlideToggle.style = kritzelSlideToggleCss();
@@ -30560,7 +31056,7 @@ const KritzelSplitButton = class {
30560
31056
  this.menuScrollTop = event.target.scrollTop;
30561
31057
  };
30562
31058
  render() {
30563
- return (h(Host, { key: '794fdb5cb4d110d93b6b2cb060fe34241f29db57', class: { mobile: this.isTouchDevice } }, h("button", { key: '7202a40f05bf6fc256996a05db55bcfa3baba615', class: "split-main-button", tabIndex: 0, onClick: this.handleButtonClick, disabled: this.mainButtonDisabled, "aria-label": "Main action" }, this.buttonIcon && h("kritzel-icon", { key: '3156c6c4e757d9ebbd3f5e3719ee1bf9bf81f71b', name: this.buttonIcon })), h("div", { key: '4c5a3a9791ecfd00d36fc0eb885c1d227200cfc7', class: "split-divider" }), 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" }, h("kritzel-icon", { key: 'b3d84e4599dc408ccc8afe17e487b501cbde89a4', name: this.dropdownIcon })), h("kritzel-portal", { key: '6800329ebe3c94a661e9ad852b6bf256defc291f', anchor: this.anchorElement, offsetY: 4, onClose: this.closeMenu }, 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 }))));
31059
+ return (h(Host, { key: '39516b2486a27534273b6bf4def6b11790aa0ed5', class: { mobile: this.isTouchDevice } }, h("button", { key: '19c4a58deb3992857b0e42e4a035a15d0980da22', class: "split-main-button", tabIndex: 0, onClick: this.handleButtonClick, disabled: this.mainButtonDisabled, "aria-label": "Main action" }, this.buttonIcon && h("kritzel-icon", { key: '4c8e7e09e60717b1653251468c2a25d41c4b1921', name: this.buttonIcon })), h("div", { key: '53617db6b630dfb5320e7309dcbc9f7288ff8974', class: "split-divider" }), h("button", { key: '5134f88ed43558ff0cb11b8ef29cf2b370aad4ee', ref: el => (this.splitMenuButtonRef = el), class: "split-menu-button", tabIndex: 0, onClick: this.toggleMenu, disabled: this.menuButtonDisabled, "aria-label": "Open menu" }, h("kritzel-icon", { key: '41f39a601ae91d4faaf54c1ca39c90e27e084d31', name: this.dropdownIcon })), h("kritzel-portal", { key: 'dcfe82f72a53e9346b48d2ff6da236c1f1344f3f', anchor: this.anchorElement, offsetY: 4, onClose: this.closeMenu }, h("kritzel-menu", { key: '1208990bff47de642eafa6ca8cfaa76de46ca87c', ref: el => (this.menuRef = el), items: this.items, onItemSelect: this.handleItemSelect, onItemSave: this.handleItemSave, onItemCancel: this.handleItemCancel, onItemToggleChildMenu: this.handleItemToggleChildMenu, onItemCloseChildMenu: this.handleItemCloseChildMenu, onClose: this.closeMenu, onScroll: this.handleScroll }))));
30564
31060
  }
30565
31061
  };
30566
31062
  KritzelSplitButton.style = kritzelSplitButtonCss();
@@ -30580,7 +31076,8 @@ const KritzelStrokeSize = class {
30580
31076
  this.sizeChange.emit(size);
30581
31077
  }
30582
31078
  render() {
30583
- return (h(Host, { key: '514d87732c9b15cddd5a905407ff7ce9069c06d7' }, h("div", { key: '170c9a8abfe8298116d8a269338da95fbc2aac7b', class: "size-grid" }, this.sizes.map(size => (h("div", { tabIndex: 0, class: {
31079
+ const sizes = this.sizes ?? [];
31080
+ return (h(Host, { key: '20c11daaf60055262706b8177b1090bddd2d522c' }, h("div", { key: '1729e29d836f85fe3be984a5ee78a0debc0e6218', class: "size-grid" }, sizes.map(size => (h("div", { tabIndex: 0, class: {
30584
31081
  'size-container': true,
30585
31082
  'selected': this.selectedSize === size,
30586
31083
  }, onClick: () => this.handleSizeClick(size) }, h("kritzel-color", { value: 'var(--kritzel-global-text-primary)', size: size })))))));
@@ -30629,6 +31126,8 @@ const KritzelToolConfig = class {
30629
31126
  isExpanded = false;
30630
31127
  theme;
30631
31128
  engine;
31129
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
31130
+ terms = {};
30632
31131
  handleSelectionChangeBound = this.handleSelectionChange.bind(this);
30633
31132
  onThemeChange() {
30634
31133
  this.emitDisplayValues();
@@ -30771,9 +31270,9 @@ const KritzelToolConfig = class {
30771
31270
  const value = this.tool[control.propertyName];
30772
31271
  switch (control.type) {
30773
31272
  case 'stroke-size':
30774
- return (h("kritzel-stroke-size", { key: control.type, sizes: this.sizes.length > 0 ? this.sizes : undefined, selectedSize: value, onSizeChange: this.handleSizeChange }));
31273
+ return (h("kritzel-stroke-size", { key: control.type, sizes: this.sizes, selectedSize: value, onSizeChange: this.handleSizeChange }));
30775
31274
  case 'font-size':
30776
- return (h("kritzel-font-size", { key: control.type, sizes: this.sizes.length > 0 ? this.sizes : undefined, selectedSize: value, fontFamily: this.tool.fontFamily, onSizeChange: this.handleSizeChange }));
31275
+ return (h("kritzel-font-size", { key: control.type, sizes: this.sizes, selectedSize: value, fontFamily: this.tool.fontFamily, onSizeChange: this.handleSizeChange }));
30777
31276
  case 'line-endings':
30778
31277
  return (h("kritzel-line-endings", { key: control.type, value: value, onValueChange: (event) => this.handlePropertyChange(control.propertyName, event.detail) }));
30779
31278
  case 'shape-fill':
@@ -30808,7 +31307,7 @@ const KritzelToolConfig = class {
30808
31307
  ])), shouldShowExpandButton && (h("div", { style: {
30809
31308
  display: 'flex',
30810
31309
  alignItems: 'flex-start',
30811
- } }, h("button", { class: "expand-toggle", onClick: this.handleToggleExpand, title: this.isExpanded ? 'Collapse' : 'Expand' }, h("kritzel-icon", { name: this.isExpanded ? 'chevron-up' : 'chevron-down' })))))));
31310
+ } }, h("button", { class: "expand-toggle", onClick: this.handleToggleExpand, title: this.isExpanded ? (this.terms['toolConfig.collapse'] ?? 'Collapse') : (this.terms['toolConfig.expand'] ?? 'Expand') }, h("kritzel-icon", { name: this.isExpanded ? 'chevron-up' : 'chevron-down' })))))));
30812
31311
  }
30813
31312
  static get watchers() { return {
30814
31313
  "tool": [{
@@ -30959,14 +31458,14 @@ const KritzelTooltip = class {
30959
31458
  }
30960
31459
  }
30961
31460
  render() {
30962
- return (h(Host, { key: '10bff4d14ff1f724d59463afc059f254e9485175', style: {
31461
+ return (h(Host, { key: '57c0c2ddbe3f88815b975b7cf5b02c7da389b7f9', style: {
30963
31462
  position: 'fixed',
30964
31463
  zIndex: '9999',
30965
31464
  transition: 'opacity 0.3s ease-in-out, transform 0.3s ease-in-out',
30966
31465
  visibility: this.isVisible ? 'visible' : 'hidden',
30967
31466
  left: `${this.positionX}px`,
30968
31467
  bottom: `${this.positionY}px`,
30969
- } }, h("div", { key: '6bfc8f2fe731d758c74319abeb82c4b84616f8b6', class: "tooltip-content", onClick: event => event.stopPropagation(), onPointerDown: event => event.stopPropagation(), onMouseDown: event => event.stopPropagation() }, h("slot", { key: '2a92ad23f4424b2d17035f86983be66a81fee81d' }))));
31468
+ } }, h("div", { key: '3ab17ef4ba55ed39458814685574b0c00504a07d', class: "tooltip-content", onClick: event => event.stopPropagation(), onPointerDown: event => event.stopPropagation(), onMouseDown: event => event.stopPropagation() }, h("slot", { key: 'b0aa1eb2c9363644939bcc59a80d0294787f0946' }))));
30970
31469
  }
30971
31470
  static get watchers() { return {
30972
31471
  "triggerElement": [{
@@ -30989,6 +31488,8 @@ const KritzelUtilityPanel = class {
30989
31488
  this.delete = createEvent(this, "delete");
30990
31489
  }
30991
31490
  undoState = null;
31491
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
31492
+ terms = {};
30992
31493
  undo;
30993
31494
  redo;
30994
31495
  delete;
@@ -31005,11 +31506,32 @@ const KritzelUtilityPanel = class {
31005
31506
  this.redo.emit();
31006
31507
  }
31007
31508
  render() {
31008
- return (h(Host, { key: 'b49f6db6c0e574dc8a5a733c749ecda6f24f9d25' }, h("button", { key: 'e6306e54c8f660c3e92d032527fad1ea45ca0cf8', class: "utility-button", "data-testid": "utility-undo", disabled: !this.undoState?.canUndo, onClick: event => this.handleUndo(event), "aria-label": "Undo" }, h("kritzel-icon", { key: '5bb1293049a1e3004504289d92ccc79958786f3f', name: "undo" })), h("button", { key: '8102b0403d7f328ce4bfeb79767d5bd99d879013', class: "utility-button", "data-testid": "utility-redo", disabled: !this.undoState?.canRedo, onClick: event => this.handleRedo(event), "aria-label": "Redo" }, h("kritzel-icon", { key: '5db3047bec5d8ab695a2dc67780a5dbecbae64d2', name: "redo" })), h("div", { key: 'e894d9f2aaa2cad7aa980d3b839eca05a8d9c9df', class: "utility-separator" }), h("button", { key: 'f0a7de5ab91f82a2e5e8df75cc1903ec647abdac', class: "utility-button", "data-testid": "utility-delete", onClick: () => this.delete.emit(), "aria-label": "Delete selected items" }, h("kritzel-icon", { key: '5b146375394299bae946a95545c3c42c2bf36766', name: "delete" }))));
31509
+ return (h(Host, { key: '7738c225b6ec936e032770e0a7387b01718e94e0' }, h("button", { key: '8bc7346eb02e3c220ca9f9c06f1ae048e83667ee', class: "utility-button", "data-testid": "utility-undo", disabled: !this.undoState?.canUndo, onClick: event => this.handleUndo(event), "aria-label": this.terms['utility.undo'] ?? 'Undo' }, h("kritzel-icon", { key: '3c7d87edc047113cac359f4782c7feeb53e18193', name: "undo" })), h("button", { key: '960dc7783cfe558f08749b9e71c6583c8d53c167', class: "utility-button", "data-testid": "utility-redo", disabled: !this.undoState?.canRedo, onClick: event => this.handleRedo(event), "aria-label": this.terms['utility.redo'] ?? 'Redo' }, h("kritzel-icon", { key: 'b5ac0b631c51b8e6c2762b4887dcdef899327e52', name: "redo" })), h("div", { key: '4075c8eb7d8c57d5e93afb18282c7a50ec8e050e', class: "utility-separator" }), h("button", { key: '4e7c53a0933c527e84e0ad78519983446f10d23c', class: "utility-button", "data-testid": "utility-delete", onClick: () => this.delete.emit(), "aria-label": this.terms['utility.delete'] ?? 'Delete selected items' }, h("kritzel-icon", { key: 'f2d3d9817626c53e2e4c3d4c7c702705b25d7788', name: "delete" }))));
31009
31510
  }
31010
31511
  };
31011
31512
  KritzelUtilityPanel.style = kritzelUtilityPanelCss();
31012
31513
 
31514
+ 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}`;
31515
+
31516
+ const KritzelWatermark = class {
31517
+ constructor(hostRef) {
31518
+ registerInstance(this, hostRef);
31519
+ }
31520
+ /** The core instance used to resolve localized terms. */
31521
+ core;
31522
+ /**
31523
+ * Resolved "Powered by Kritzel" label. Passed in by the engine so the badge
31524
+ * re-renders when the active locale changes. Falls back to resolving the term
31525
+ * from the core when not provided.
31526
+ */
31527
+ label;
31528
+ render() {
31529
+ const label = this.label ?? this.core.localizationManager.translate('watermark.poweredBy');
31530
+ return (h(Host, { key: '7ec2a304ac711a4d2a54edef317eef9f4aad1d47' }, h("a", { key: '365d588727bbc360fd47bc9a3fbd66155770d32f', class: "watermark-link", href: KRITZEL_WEBSITE_URL, target: "_blank", rel: "noopener noreferrer", part: "watermark", onPointerDown: (ev) => ev.stopPropagation(), onPointerUp: (ev) => ev.stopPropagation() }, label)));
31531
+ }
31532
+ };
31533
+ KritzelWatermark.style = kritzelWatermarkCss();
31534
+
31013
31535
  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}`;
31014
31536
 
31015
31537
  const KritzelWorkspaceManager = class {
@@ -31025,6 +31547,8 @@ const KritzelWorkspaceManager = class {
31025
31547
  visible = false;
31026
31548
  activeWorkspace;
31027
31549
  workspaces = [];
31550
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
31551
+ terms = {};
31028
31552
  isWorkspaceManagerReady;
31029
31553
  workspaceChange;
31030
31554
  childMenuAnchor = null;
@@ -31128,7 +31652,7 @@ const KritzelWorkspaceManager = class {
31128
31652
  id: ws.id,
31129
31653
  label: ws.name,
31130
31654
  icon: ws.isPublic ? 'users-round' : undefined,
31131
- iconTooltip: ws.isPublic ? 'Shared workspace' : undefined,
31655
+ iconTooltip: ws.isPublic ? (this.terms['workspace.sharedTooltip'] ?? 'Shared workspace') : undefined,
31132
31656
  value: ws,
31133
31657
  isEditing: this.editingItemId === ws.id,
31134
31658
  isSelected: this.activeWorkspace?.id === ws.id,
@@ -31138,13 +31662,13 @@ const KritzelWorkspaceManager = class {
31138
31662
  children: [
31139
31663
  {
31140
31664
  id: `${ws.id}-rename`,
31141
- label: 'Rename',
31665
+ label: this.terms['workspace.rename'] ?? 'Rename',
31142
31666
  value: 'rename',
31143
31667
  action: (_item, parent) => this.edit(parent),
31144
31668
  },
31145
31669
  {
31146
31670
  id: `${ws.id}-delete`,
31147
- label: 'Delete',
31671
+ label: this.terms['workspace.delete'] ?? 'Delete',
31148
31672
  value: 'delete',
31149
31673
  isDisabled: this.sortedWorkspaces.length <= 1,
31150
31674
  action: (_item, parent) => this.delete(parent),
@@ -31157,4 +31681,43 @@ const KritzelWorkspaceManager = class {
31157
31681
  };
31158
31682
  KritzelWorkspaceManager.style = kritzelWorkspaceManagerCss();
31159
31683
 
31160
- export { KritzelActiveUsers as kritzel_active_users, KritzelAvatar as kritzel_avatar, KritzelAwarenessCursors as kritzel_awareness_cursors, KritzelBackToContent as kritzel_back_to_content, KritzelButton as kritzel_button, KritzelColorComponent as kritzel_color, KritzelColorPalette as kritzel_color_palette, KritzelContextMenu as kritzel_context_menu, KritzelControls as kritzel_controls, KritzelCurrentUser as kritzel_current_user, KritzelCurrentUserDialog as kritzel_current_user_dialog, KritzelCursorTrail as kritzel_cursor_trail, KritzelDialog as kritzel_dialog, KritzelDropdown as kritzel_dropdown, KritzelEditor as kritzel_editor, KritzelEngine as kritzel_engine, KritzelExport as kritzel_export, KritzelFont as kritzel_font, KritzelFontFamily as kritzel_font_family, KritzelFontSize as kritzel_font_size, KritzelIcon as kritzel_icon, KritzelInput as kritzel_input, KritzelLineEndings as kritzel_line_endings, KritzelLoginDialog as kritzel_login_dialog, KritzelMasterDetail as kritzel_master_detail, KritzelMenu as kritzel_menu, KritzelMenuItem as kritzel_menu_item, KritzelMoreMenu as kritzel_more_menu, KritzelNumericInput as kritzel_numeric_input, KritzelOpacitySlider as kritzel_opacity_slider, KritzelPillTabs as kritzel_pill_tabs, KritzelPortal as kritzel_portal, KritzelSettings as kritzel_settings, KritzelShapeFill as kritzel_shape_fill, KritzelShareDialog as kritzel_share_dialog, KritzelSlideToggle as kritzel_slide_toggle, KritzelSplitButton as kritzel_split_button, KritzelStrokeSize as kritzel_stroke_size, KritzelToolConfig as kritzel_tool_config, KritzelTooltip as kritzel_tooltip, KritzelUtilityPanel as kritzel_utility_panel, KritzelWorkspaceManager as kritzel_workspace_manager };
31684
+ 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}`;
31685
+
31686
+ const KritzelZoomPanel = class {
31687
+ constructor(hostRef) {
31688
+ registerInstance(this, hostRef);
31689
+ this.zoomIn = createEvent(this, "zoomIn");
31690
+ this.zoomOut = createEvent(this, "zoomOut");
31691
+ }
31692
+ /** Whether the zoom panel is visible. */
31693
+ visible = true;
31694
+ /** Whether both zoom buttons are disabled. */
31695
+ disabled = false;
31696
+ /** Current zoom level in percent. */
31697
+ zoomPercent = 100;
31698
+ /** Resolved localized strings keyed by term key, supplied by the editor. */
31699
+ terms = {};
31700
+ /** Emitted when the zoom-in button is clicked. */
31701
+ zoomIn;
31702
+ /** Emitted when the zoom-out button is clicked. */
31703
+ zoomOut;
31704
+ handleZoomIn = () => {
31705
+ if (!this.disabled) {
31706
+ this.zoomIn.emit();
31707
+ }
31708
+ };
31709
+ handleZoomOut = () => {
31710
+ if (!this.disabled) {
31711
+ this.zoomOut.emit();
31712
+ }
31713
+ };
31714
+ get normalizedZoomPercent() {
31715
+ return Number.isFinite(this.zoomPercent) ? Math.max(1, Math.round(this.zoomPercent)) : 100;
31716
+ }
31717
+ render() {
31718
+ return (h(Host, { key: '6c88ed18f63e1a4cfffef6ba3aa1772404908d59' }, h("div", { key: '18fcdf05a6822d2b9cf319059118b4851429283d', class: { panel: true, visible: this.visible } }, h("button", { key: 'df2c90912e091e4f2ca025dd246916e9fea6f7fa', class: "zoom-button", type: "button", "aria-label": this.terms['zoom.zoomOut'] ?? 'Zoom out', disabled: this.disabled, onClick: this.handleZoomOut }, h("kritzel-icon", { key: '293847a34220520592e74f50c89d57140d7f1cbe', name: "minus" })), h("span", { key: '575119b81e6d5d41f5802a29be1b586727968919', class: "zoom-level", "aria-live": "polite" }, this.normalizedZoomPercent, "%"), h("button", { key: '8cdd7100aaeb313f2504351204ff512142414815', class: "zoom-button", type: "button", "aria-label": this.terms['zoom.zoomIn'] ?? 'Zoom in', disabled: this.disabled, onClick: this.handleZoomIn }, h("kritzel-icon", { key: 'b792164a29dd43ef601a4f469fed440ee1d72510', name: "plus" })))));
31719
+ }
31720
+ };
31721
+ KritzelZoomPanel.style = kritzelZoomPanelCss();
31722
+
31723
+ export { KritzelActiveUsers as kritzel_active_users, KritzelAvatar as kritzel_avatar, KritzelAwarenessCursors as kritzel_awareness_cursors, KritzelBackToContent as kritzel_back_to_content, KritzelButton as kritzel_button, KritzelColorComponent as kritzel_color, KritzelColorPalette as kritzel_color_palette, KritzelContextMenu as kritzel_context_menu, KritzelControls as kritzel_controls, KritzelCurrentUser as kritzel_current_user, KritzelCurrentUserDialog as kritzel_current_user_dialog, KritzelCursorTrail as kritzel_cursor_trail, KritzelDialog as kritzel_dialog, KritzelDropdown as kritzel_dropdown, KritzelEditor as kritzel_editor, KritzelEngine as kritzel_engine, KritzelExport as kritzel_export, KritzelFont as kritzel_font, KritzelFontFamily as kritzel_font_family, KritzelFontSize as kritzel_font_size, KritzelIcon as kritzel_icon, KritzelInput as kritzel_input, KritzelLineEndings as kritzel_line_endings, KritzelLoginDialog as kritzel_login_dialog, KritzelMasterDetail as kritzel_master_detail, KritzelMenu as kritzel_menu, KritzelMenuItem as kritzel_menu_item, KritzelMoreMenu as kritzel_more_menu, KritzelNumericInput as kritzel_numeric_input, KritzelOpacitySlider as kritzel_opacity_slider, KritzelPillTabs as kritzel_pill_tabs, KritzelPortal as kritzel_portal, KritzelSettings as kritzel_settings, KritzelShapeFill as kritzel_shape_fill, KritzelShareDialog as kritzel_share_dialog, KritzelSlideToggle as kritzel_slide_toggle, KritzelSplitButton as kritzel_split_button, KritzelStrokeSize as kritzel_stroke_size, KritzelToolConfig as kritzel_tool_config, KritzelTooltip as kritzel_tooltip, KritzelUtilityPanel as kritzel_utility_panel, KritzelWatermark as kritzel_watermark, KritzelWorkspaceManager as kritzel_workspace_manager, KritzelZoomPanel as kritzel_zoom_panel };