kritzel-stencil 0.4.0 → 0.4.2

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 (86) hide show
  1. package/dist/cjs/index.cjs.js +2 -1
  2. package/dist/cjs/kritzel-active-users_47.cjs.entry.js +365 -80
  3. package/dist/cjs/loader.cjs.js +1 -1
  4. package/dist/cjs/{schema.constants-fuAsi56H.js → schema.constants-C8hqt--r.js} +274 -33
  5. package/dist/cjs/stencil.cjs.js +1 -1
  6. package/dist/collection/classes/core/workspace.class.js +1 -1
  7. package/dist/collection/classes/handlers/context-menu.handler.js +2 -3
  8. package/dist/collection/classes/managers/clipboard.manager.js +9 -3
  9. package/dist/collection/classes/objects/base-object.class.js +0 -1
  10. package/dist/collection/classes/objects/text.class.js +2 -1
  11. package/dist/collection/classes/registries/font-registry.class.js +161 -0
  12. package/dist/collection/classes/structures/object-map.structure.js +9 -0
  13. package/dist/collection/classes/tools/image-tool.class.js +19 -3
  14. package/dist/collection/classes/tools/text-tool.class.js +2 -0
  15. package/dist/collection/components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.js +22 -7
  16. package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +138 -9
  17. package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +186 -25
  18. package/dist/collection/components/shared/kritzel-font-family/kritzel-font-family.js +31 -22
  19. package/dist/collection/components/ui/kritzel-controls/kritzel-controls.js +10 -11
  20. package/dist/collection/components/ui/kritzel-tool-config/kritzel-tool-config.js +40 -5
  21. package/dist/collection/configs/default-text-tool.config.js +2 -0
  22. package/dist/collection/constants/text-tool-fonts.constants.js +18 -0
  23. package/dist/collection/constants/version.js +1 -1
  24. package/dist/collection/helpers/image-file.helper.js +6 -0
  25. package/dist/collection/index.js +2 -0
  26. package/dist/collection/interfaces/font.interface.js +1 -0
  27. package/dist/components/index.js +1 -1
  28. package/dist/components/kritzel-awareness-cursors.js +1 -1
  29. package/dist/components/kritzel-controls.js +1 -1
  30. package/dist/components/kritzel-cursor-trail.js +1 -1
  31. package/dist/components/kritzel-editor.js +1 -1
  32. package/dist/components/kritzel-engine.js +1 -1
  33. package/dist/components/kritzel-font-family.js +1 -1
  34. package/dist/components/kritzel-settings.js +1 -1
  35. package/dist/components/kritzel-tool-config.js +1 -1
  36. package/dist/components/kritzel-workspace-manager.js +1 -1
  37. package/dist/components/p--Oazz-BP.js +9 -0
  38. package/dist/components/p-C38P-Vd0.js +1 -0
  39. package/dist/components/{p-zhjb97WN.js → p-C3UQ-Zrg.js} +1 -1
  40. package/dist/components/{p-9692OaI_.js → p-ClFprFvz.js} +1 -1
  41. package/dist/components/p-Cm33td2O.js +1 -0
  42. package/dist/components/{p-DhMlShij.js → p-DAwPPDMS.js} +1 -1
  43. package/dist/components/p-DJkKlUyZ.js +1 -0
  44. package/dist/components/p-JYj3Facr.js +1 -0
  45. package/dist/components/p-Ka7OWbnk.js +1 -0
  46. package/dist/components/p-djJSnjAX.js +1 -0
  47. package/dist/esm/index.js +2 -2
  48. package/dist/esm/kritzel-active-users_47.entry.js +365 -80
  49. package/dist/esm/loader.js +1 -1
  50. package/dist/esm/{schema.constants-Cv5-yM39.js → schema.constants-uC7_X6yr.js} +272 -34
  51. package/dist/esm/stencil.js +1 -1
  52. package/dist/stencil/index.esm.js +1 -1
  53. package/dist/stencil/p-b64c3584.entry.js +9 -0
  54. package/dist/stencil/p-uC7_X6yr.js +1 -0
  55. package/dist/stencil/stencil.esm.js +1 -1
  56. package/dist/types/classes/core/workspace.class.d.ts +1 -1
  57. package/dist/types/classes/managers/clipboard.manager.d.ts +1 -0
  58. package/dist/types/classes/objects/base-object.class.d.ts +0 -1
  59. package/dist/types/classes/registries/font-registry.class.d.ts +19 -0
  60. package/dist/types/classes/structures/object-map.structure.d.ts +2 -2
  61. package/dist/types/classes/tools/image-tool.class.d.ts +9 -2
  62. package/dist/types/classes/tools/text-tool.class.d.ts +3 -0
  63. package/dist/types/components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.d.ts +2 -0
  64. package/dist/types/components/core/kritzel-editor/kritzel-editor.d.ts +22 -0
  65. package/dist/types/components/core/kritzel-engine/kritzel-engine.d.ts +17 -0
  66. package/dist/types/components/shared/kritzel-font-family/kritzel-font-family.d.ts +4 -0
  67. package/dist/types/components/ui/kritzel-tool-config/kritzel-tool-config.d.ts +1 -0
  68. package/dist/types/components.d.ts +20 -2
  69. package/dist/types/constants/text-tool-fonts.constants.d.ts +3 -0
  70. package/dist/types/constants/version.d.ts +1 -1
  71. package/dist/types/helpers/image-file.helper.d.ts +3 -0
  72. package/dist/types/index.d.ts +2 -0
  73. package/dist/types/interfaces/font.interface.d.ts +15 -0
  74. package/dist/types/interfaces/toolbar-control.interface.d.ts +2 -0
  75. package/package.json +1 -1
  76. package/dist/components/p-B4b6TDxp.js +0 -1
  77. package/dist/components/p-C2ocjA6D.js +0 -1
  78. package/dist/components/p-CwbZCXKh.js +0 -1
  79. package/dist/components/p-D2q_wTyG.js +0 -1
  80. package/dist/components/p-D8NvNMcU.js +0 -1
  81. package/dist/components/p-D8W6LE-c.js +0 -1
  82. package/dist/components/p-G9G9y5VE.js +0 -9
  83. package/dist/components/p-JhOYwUOj.js +0 -1
  84. package/dist/components/p-UEf2EN23.js +0 -1
  85. package/dist/stencil/p-2c489187.entry.js +0 -9
  86. package/dist/stencil/p-Cv5-yM39.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, R as KritzelColorHelper, T as KritzelDevicesHelper, q as KritzelSelectionTool, n as KritzelTextTool, U as KritzelMouseButton, V as KritzelHTMLHelper, X as DEFAULT_STROKE_SIZES, Y as DEFAULT_COLOR_PALETTE, S as ShapeType, D as DEFAULT_BRUSH_CONFIG, j as KritzelBrushTool, l as KritzelEraserTool, z as DEFAULT_LINE_TOOL_CONFIG, k as KritzelLineTool, o as KritzelShapeTool, y as DEFAULT_TEXT_CONFIG, m as KritzelImageTool, L as KritzelAlignment, B as DEFAULT_ASSET_STORAGE_CONFIG, Z as ObjectHelper, _ as ThemeHelper, E as darkTheme, C as lightTheme, $ as KritzelSelectionGroup, a0 as KritzelSelectionBox, a1 as KritzelIconRegistry, a2 as KritzelKeyboardHelper, a3 as KritzelBaseHandler, s as KritzelWorkspace, f as KritzelGroup, h as KritzelCustomElement, d as KritzelImage, g as KritzelShape, b as KritzelText, M as runMigrations, Q as CURRENT_WORKSPACE_SCHEMA_VERSION, O as WORKSPACE_MIGRATIONS, a4 as KritzelClassHelper, P as CURRENT_APP_STATE_SCHEMA_VERSION, N as APP_STATE_MIGRATIONS, p as KritzelCursorHelper, a5 as imageCompression, t as KritzelAnchorManager, u as KritzelThemeManager, v as KritzelLocalizationManager, w as KritzelLicenseManager, r as KritzelAssetResolver, a6 as KritzelEventHelper, W as WORKSPACE_EXPORT_VERSION } from './schema.constants-Cv5-yM39.js';
2
+ import { c as KritzelPath, e as KritzelLine, T as KritzelColorHelper, U as KritzelDevicesHelper, V as KritzelMouseButton, X as KritzelHTMLHelper, Y as DEFAULT_STROKE_SIZES, Z as DEFAULT_COLOR_PALETTE, S as ShapeType, q as KritzelSelectionTool, D as DEFAULT_BRUSH_CONFIG, j as KritzelBrushTool, l as KritzelEraserTool, B as DEFAULT_LINE_TOOL_CONFIG, k as KritzelLineTool, o as KritzelShapeTool, z as DEFAULT_TEXT_CONFIG, n as KritzelTextTool, m as KritzelImageTool, M as KritzelAlignment, C as DEFAULT_ASSET_STORAGE_CONFIG, _ as ObjectHelper, $ as resolveTextToolAvailableFonts, a0 as ThemeHelper, F as darkTheme, E as lightTheme, a1 as KritzelSelectionGroup, a2 as KritzelSelectionBox, a3 as KritzelIconRegistry, y as KritzelFontRegistry, a4 as KritzelKeyboardHelper, a5 as KritzelBaseHandler, s as KritzelWorkspace, f as KritzelGroup, h as KritzelCustomElement, d as KritzelImage, g as KritzelShape, b as KritzelText, N as runMigrations, R as CURRENT_WORKSPACE_SCHEMA_VERSION, P as WORKSPACE_MIGRATIONS, a6 as KritzelClassHelper, Q as CURRENT_APP_STATE_SCHEMA_VERSION, O as APP_STATE_MIGRATIONS, p as KritzelCursorHelper, a7 as KritzelImageFileHelper, a8 as imageCompression, t as KritzelAnchorManager, u as KritzelThemeManager, v as KritzelLocalizationManager, w as KritzelLicenseManager, r as KritzelAssetResolver, a9 as KritzelEventHelper, W as WORKSPACE_EXPORT_VERSION, i as KritzelBaseTool } from './schema.constants-uC7_X6yr.js';
3
3
  import * as Y from 'yjs';
4
4
  import 'y-indexeddb';
5
5
  import 'y-websocket';
@@ -174,23 +174,38 @@ const KritzelAwarenessCursors = class {
174
174
  objectVersion = 0;
175
175
  cleanupIntervalId;
176
176
  objectChangeRafId = null;
177
+ removeAwarenessChangeListener;
178
+ removeObjectsChangeListener;
177
179
  componentDidLoad() {
178
- this.core.store.objects?.onAwarenessChange(states => {
180
+ this.removeAwarenessChangeListener = this.core.store.objects?.onAwarenessChange(states => {
179
181
  this.handleAwarenessChange(states);
180
182
  });
181
- this.core.store.objects?.onObjectsChange(() => {
183
+ this.removeObjectsChangeListener = this.core.store.objects?.onObjectsChange(() => {
182
184
  this.handleRemoteObjectChange();
183
185
  });
184
186
  this.cleanupIntervalId = setInterval(() => {
185
187
  this.cleanupStaleCursors();
186
188
  }, CLEANUP_INTERVAL_MS);
189
+ if (this.cleanupIntervalId && typeof this.cleanupIntervalId.unref === 'function') {
190
+ this.cleanupIntervalId.unref();
191
+ }
187
192
  }
188
193
  disconnectedCallback() {
189
- if (this.cleanupIntervalId) {
190
- clearInterval(this.cleanupIntervalId);
194
+ if (this.removeAwarenessChangeListener) {
195
+ this.removeAwarenessChangeListener();
196
+ this.removeAwarenessChangeListener = undefined;
197
+ }
198
+ if (this.removeObjectsChangeListener) {
199
+ this.removeObjectsChangeListener();
200
+ this.removeObjectsChangeListener = undefined;
191
201
  }
192
- if (this.objectChangeRafId !== null) {
193
- cancelAnimationFrame(this.objectChangeRafId);
202
+ if (this.cleanupIntervalId && typeof globalThis.clearInterval === 'function') {
203
+ globalThis.clearInterval(this.cleanupIntervalId);
204
+ this.cleanupIntervalId = undefined;
205
+ }
206
+ if (this.objectChangeRafId !== null && typeof globalThis.cancelAnimationFrame === 'function') {
207
+ globalThis.cancelAnimationFrame(this.objectChangeRafId);
208
+ this.objectChangeRafId = null;
194
209
  }
195
210
  }
196
211
  handleAwarenessChange(states) {
@@ -348,7 +363,7 @@ const KritzelAwarenessCursors = class {
348
363
  }
349
364
  render() {
350
365
  const cursors = Array.from(this.remoteCursors.values());
351
- return (h(Host, { key: '5c695e3c5a012767b31bb1b756ef03e2a174f46e' }, cursors.map(remoteCursor => {
366
+ return (h(Host, { key: '9be6694250bafe011be27301d1c7a53fc7957657' }, cursors.map(remoteCursor => {
352
367
  if (!remoteCursor.cursor)
353
368
  return null;
354
369
  // When a remote user is actively drawing, derive cursor position from
@@ -971,8 +986,9 @@ const KritzelControls = class {
971
986
  this.closeTooltip();
972
987
  }
973
988
  handleSelectionChange() {
974
- if (this.activeControl?.tool instanceof KritzelSelectionTool) {
975
- this.updateDisplayValues(this.activeControl.tool);
989
+ const tool = this.activeControl?.tool;
990
+ if (tool && typeof tool !== 'function' && tool.toolType === 'selection') {
991
+ this.updateDisplayValues(tool);
976
992
  }
977
993
  }
978
994
  async onControlsChange() {
@@ -1023,7 +1039,7 @@ const KritzelControls = class {
1023
1039
  color: KritzelColorHelper.applyOpacity(color, opacity, this.theme),
1024
1040
  size,
1025
1041
  };
1026
- if (tool instanceof KritzelTextTool) {
1042
+ if (tool.toolType === 'text') {
1027
1043
  displayValues.fontFamily = tool.fontFamily;
1028
1044
  }
1029
1045
  // Check for equality implementation to prevent unnecessary re-renders
@@ -1164,13 +1180,13 @@ const KritzelControls = class {
1164
1180
  // Separate tool controls from config control
1165
1181
  const toolControls = this.internalControls.filter(c => c.type === 'tool' || c.type === 'separator');
1166
1182
  const configControl = this.internalControls.find(c => c.type === 'config' && c.name === this.firstConfig?.name);
1167
- return (h(Host, { key: '5a23a0a1baa407097b18b9bc43f41186c0c333b2', style: { display: this.visible ? '' : 'none' }, class: {
1183
+ return (h(Host, { key: 'a526bd337a6b75933ec4be89c3edded0a9c39e7c', style: { display: this.visible ? '' : 'none' }, class: {
1168
1184
  mobile: this.isTouchDevice,
1169
- } }, this.isUtilityPanelVisible && (h("kritzel-utility-panel", { key: '6d3878904bd13266be79cef0e0d1ee55d760b3da', style: {
1185
+ } }, this.isUtilityPanelVisible && (h("kritzel-utility-panel", { key: '5314f067c5a22425690e3b6f26c836962ac528a2', style: {
1170
1186
  position: 'absolute',
1171
1187
  bottom: '56px',
1172
1188
  left: '12px',
1173
- }, undoState: this.undoState, terms: this.terms, onUndo: () => this.kritzelEngine?.undo(), onRedo: () => this.kritzelEngine?.redo(), onDelete: () => this.kritzelEngine?.delete() })), h("div", { key: '5e066964eba04ad9b0d1abf36f61b93464254f48', class: "kritzel-controls" }, h("div", { key: '76e49fd459e0a312837ec0b5e552355395b8814e', class: { 'scroll-indicator-left': true, 'visible': this.canScrollLeft } }), h("div", { key: 'd4cffe56418dae5c569a47fe2ffc3f810f221455', class: "kritzel-tools-scroll", ref: el => (this.toolsScrollRef = el), onScroll: this.handleToolsScroll }, toolControls.map(control => {
1189
+ }, undoState: this.undoState, terms: this.terms, onUndo: () => this.kritzelEngine?.undo(), onRedo: () => this.kritzelEngine?.redo(), onDelete: () => this.kritzelEngine?.delete() })), h("div", { key: '7cf0cd164d723b5901f54a6671395f63dd8bad29', class: "kritzel-controls" }, h("div", { key: 'b7169122686a0a67c37d96751291fc43f4c45e4a', class: { 'scroll-indicator-left': true, 'visible': this.canScrollLeft } }), h("div", { key: '6e3c4f30900e4722abdfaf3cfdc68cf1a07b9292', class: "kritzel-tools-scroll", ref: el => (this.toolsScrollRef = el), onScroll: this.handleToolsScroll }, toolControls.map(control => {
1174
1190
  // Check if this control has sub-options (split-button)
1175
1191
  if (control.subOptions?.length) {
1176
1192
  const selectedSubOption = this.getSelectedSubOption(control);
@@ -1200,10 +1216,10 @@ const KritzelControls = class {
1200
1216
  'kritzel-control': true,
1201
1217
  'selected': this.activeControl?.name === control?.name,
1202
1218
  }, 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 })));
1203
- })), h("div", { key: '49806e8e06b8756a097eeec53203a55586214a4a', class: { 'scroll-indicator-right': true, 'visible': this.canScrollRight && !(configControl && this.activeControl && hasConfigUI) } }), configControl && this.activeControl && (h("div", { class: {
1219
+ })), h("div", { key: 'bc8d79e0512f30fe150c6d2ac7f81206fc47c1a5', class: { 'scroll-indicator-right': true, 'visible': this.canScrollRight && !(configControl && this.activeControl && hasConfigUI) } }), configControl && this.activeControl && (h("div", { class: {
1204
1220
  'kritzel-config-container': true,
1205
1221
  'visible': hasConfigUI,
1206
- }, key: configControl.name }, h("div", { key: 'da0ec9cf776dee0872bbb7f0369a6a5a56ebb955', class: { 'config-gradient-left': true, 'visible': this.needsScrolling } }), h("kritzel-tooltip", { key: 'c848a63392d4ed9a30f467512421fbea3096d876', anchorElement: this.host.shadowRoot?.querySelector('.kritzel-config-container'), triggerElement: this.configTriggerRef }, h("kritzel-tool-config", { key: 'c7ed50fbdfbdab1847fb8c5c6e73d0e743b45706', 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: 'de6c9801d3e9e89674b60b1c4e270b8520014aae', tabIndex: hasConfigUI ? 0 : -1, class: "kritzel-config", "data-testid": "tool-config", ref: el => {
1222
+ }, key: configControl.name }, h("div", { key: '671431a9c388080fb917d4e8c596d17e92eaba08', class: { 'config-gradient-left': true, 'visible': this.needsScrolling } }), h("kritzel-tooltip", { key: '394e8e2c07b191b3fb93645d68d7eae67761167a', anchorElement: this.host.shadowRoot?.querySelector('.kritzel-config-container'), triggerElement: this.configTriggerRef }, h("kritzel-tool-config", { key: 'b8479fba2afdb51fe0ef965e18b1ae45ba8a7da6', 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: '8fac81d682dbdec6b5247154fd20d553dfe6f1a7', tabIndex: hasConfigUI ? 0 : -1, class: "kritzel-config", "data-testid": "tool-config", ref: el => {
1207
1223
  if (el)
1208
1224
  this.configTriggerRef = el;
1209
1225
  }, onKeyDown: event => {
@@ -1212,7 +1228,7 @@ const KritzelControls = class {
1212
1228
  }
1213
1229
  }, style: {
1214
1230
  cursor: 'pointer',
1215
- } }, this.displayValues && (h("div", { key: '140bbf567b0bbd2a2fe878536e93d8646a58b209', class: "color-container" }, h("kritzel-color", { key: 'eeb1d94fa41bbc177d39b4158ae3c4c1d692c0ec', value: this.displayValues.color, theme: this.theme, size: 18, style: {
1231
+ } }, this.displayValues && (h("div", { key: '6aa1daed9425d069c7d0405a04e65b4c4e88291f', class: "color-container" }, h("kritzel-color", { key: '7b54461769bf4aec3e39d1fbe21c015b7d7a85bb', value: this.displayValues.color, theme: this.theme, size: 18, style: {
1216
1232
  borderRadius: '50%',
1217
1233
  border: 'none',
1218
1234
  } })))))))));
@@ -2290,6 +2306,11 @@ const KritzelEditor = class {
2290
2306
  /** The locale used to resolve terms missing from the active locale. */
2291
2307
  fallbackLocale = 'en';
2292
2308
  customSvgIcons = {};
2309
+ /**
2310
+ * Registers fonts for runtime usage (for example custom object rendering).
2311
+ * Text-tool config tooltip options are controlled via controls[].config.availableFonts.
2312
+ */
2313
+ customFonts = {};
2293
2314
  isPanningEnabled = true;
2294
2315
  isZoomingEnabled = true;
2295
2316
  isControlsVisible = true;
@@ -2342,11 +2363,14 @@ const KritzelEditor = class {
2342
2363
  isEditorVisible = false;
2343
2364
  activeNotification;
2344
2365
  isNotificationDismissing = false;
2366
+ normalizedControls = [];
2345
2367
  notificationDismissTimeout;
2346
2368
  notificationDismissAnimationTimeout;
2347
2369
  notificationDisplayDurationMs = 5000;
2348
2370
  notificationDismissAnimationDurationMs = 180;
2349
2371
  isNotificationHovered = false;
2372
+ customFontsLoadVersion = 0;
2373
+ customFontsReady = Promise.resolve();
2350
2374
  onIsEngineReady(newValue) {
2351
2375
  if (newValue && this.isControlsReady) {
2352
2376
  this.checkIsReady();
@@ -2376,6 +2400,12 @@ const KritzelEditor = class {
2376
2400
  onActiveWorkspaceChange() {
2377
2401
  this.updateCurrentIsPublic();
2378
2402
  }
2403
+ onCustomFontsChange(newValue) {
2404
+ void this.registerCustomFonts(newValue);
2405
+ }
2406
+ onControlsChange(newValue) {
2407
+ this.normalizedControls = this.normalizeControls(newValue);
2408
+ }
2379
2409
  onActiveWorkspaceIdChange(newId) {
2380
2410
  // If no ID is provided, do nothing (allows other logic to control activeWorkspace)
2381
2411
  if (!newId) {
@@ -2649,9 +2679,27 @@ const KritzelEditor = class {
2649
2679
  loginDialogRef;
2650
2680
  currentUserDialogRef;
2651
2681
  componentWillLoad() {
2682
+ this.normalizedControls = this.normalizeControls(this.controls);
2652
2683
  this.loadSettingsFromStorage();
2653
2684
  this.applyTheme();
2654
2685
  }
2686
+ isTextToolConfig(config) {
2687
+ return !!config && 'fontFamily' in config && 'size' in config && 'color' in config;
2688
+ }
2689
+ normalizeControls(controls) {
2690
+ return controls.map(control => {
2691
+ if (!this.isTextToolConfig(control.config)) {
2692
+ return control;
2693
+ }
2694
+ return {
2695
+ ...control,
2696
+ config: {
2697
+ ...control.config,
2698
+ availableFonts: resolveTextToolAvailableFonts(control.config.availableFonts),
2699
+ },
2700
+ };
2701
+ });
2702
+ }
2655
2703
  applyTheme() {
2656
2704
  const themeObj = this.resolveThemeObject();
2657
2705
  ThemeHelper.applyThemeToElement(this.host, themeObj);
@@ -2662,6 +2710,7 @@ const KritzelEditor = class {
2662
2710
  }
2663
2711
  componentDidLoad() {
2664
2712
  this.registerCustomSvgIcons();
2713
+ this.registerCustomFonts();
2665
2714
  this.listenForMobileKeyboard();
2666
2715
  this.setOsSpecificCssVariables();
2667
2716
  }
@@ -2674,6 +2723,7 @@ const KritzelEditor = class {
2674
2723
  await customElements.whenDefined('kritzel-workspace-manager');
2675
2724
  await customElements.whenDefined('kritzel-controls');
2676
2725
  await customElements.whenDefined('kritzel-engine');
2726
+ await this.waitForCustomFontsReady();
2677
2727
  if (!this.isEngineReady || !this.isControlsReady || !this.isWorkspaceManagerReady || !this.activeWorkspace) {
2678
2728
  return;
2679
2729
  }
@@ -2937,6 +2987,15 @@ const KritzelEditor = class {
2937
2987
  async registerLocales(locales) {
2938
2988
  await this.engineRef?.registerLocales(locales);
2939
2989
  }
2990
+ /**
2991
+ * Registers custom fonts for the editor runtime.
2992
+ * @param fonts - Mapping from font keys to family names or font definitions.
2993
+ * @remarks This no longer controls the text-tool config tooltip font options.
2994
+ * Configure tooltip fonts via controls[].config.availableFonts.
2995
+ */
2996
+ async registerFonts(fonts) {
2997
+ await this.registerCustomFonts(fonts);
2998
+ }
2940
2999
  /**
2941
3000
  * Resolves a term key to its translated string for the active locale.
2942
3001
  * @param key - The term key to resolve.
@@ -3014,6 +3073,25 @@ const KritzelEditor = class {
3014
3073
  KritzelIconRegistry.register(name, svg);
3015
3074
  }
3016
3075
  }
3076
+ async registerCustomFonts(fonts = this.customFonts) {
3077
+ KritzelFontRegistry.registerFonts(fonts);
3078
+ const currentVersion = ++this.customFontsLoadVersion;
3079
+ const loadPromise = KritzelFontRegistry.waitForFonts(fonts);
3080
+ this.customFontsReady = loadPromise;
3081
+ await loadPromise;
3082
+ if (currentVersion !== this.customFontsLoadVersion) {
3083
+ await this.waitForCustomFontsReady();
3084
+ }
3085
+ }
3086
+ async waitForCustomFontsReady() {
3087
+ while (true) {
3088
+ const version = this.customFontsLoadVersion;
3089
+ await this.customFontsReady;
3090
+ if (version === this.customFontsLoadVersion) {
3091
+ return;
3092
+ }
3093
+ }
3094
+ }
3017
3095
  listenForMobileKeyboard() {
3018
3096
  KritzelKeyboardHelper.onKeyboardVisibleChanged(isOpen => {
3019
3097
  this.isVirtualKeyboardOpen = isOpen;
@@ -3052,36 +3130,36 @@ const KritzelEditor = class {
3052
3130
  const isLoggedIn = this.isLoggedIn;
3053
3131
  const shouldShowCurrentUser = isLoggedIn;
3054
3132
  const shouldShowLoginButton = this.isReady && !!this.loginConfig && !isLoggedIn;
3055
- return (h(Host, { key: '3128b38a04cd636abc1f2466f1ac1aa3dcea2262' }, h("div", { key: 'b1fbb051cb1144fd84f28cfea4ad824f92189dbd', class: "editor-content", style: {
3133
+ return (h(Host, { key: '00d652c74feac7b4917772146368125da6deeb1c' }, h("div", { key: 'f8fb86b45b0cdabb5e3ad52de6d6ef31a71a09e4', class: "editor-content", style: {
3056
3134
  opacity: this.isEditorVisible ? '1' : '0',
3057
3135
  visibility: this.isEditorVisible ? 'visible' : 'hidden',
3058
3136
  transition: 'opacity 0.2s ease-in-out, visibility 0.2s ease-in-out',
3059
- } }, h("div", { key: '2b896fa8bd00a7c68b74df7a9846e8c3e0f6efc4', class: "top-left-buttons" }, h("kritzel-workspace-manager", { key: '13244251aa8999d78d275eefe5583d592f3506fc', 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: '7d3015918e4af7ee435aee8da37ddfa206cdfd66', visible: this.isBackToContentButtonVisible, text: this.resolvedTerms['backToContent.label'] ?? 'Back to content', onBackToContent: () => this.backToContent() })), this.activeNotification && (h("div", { key: '49f13f23698db9b804d139e0f35696be06569346', class: "top-center-notification-layer", role: "presentation" }, h("div", { key: '765aa6995398aca3c7738758bedb7d9a7c2f099a', class: { 'top-center-notification-container': true, 'is-dismissing': this.isNotificationDismissing }, role: "presentation" }, h("kritzel-notification-card", { key: 'fe0dfa30aa498a608f9167b01e0a2309dfa67162', notification: this.activeNotification, locale: this.locale, onDismiss: this.dismissNotification, onHoverChange: event => this.handleNotificationHoverChange(event) })))), h("kritzel-engine", { key: '5aeb7979faa8779653ff6a2930d288fd601cb0e9', ref: el => {
3137
+ } }, h("div", { key: '2378c776d8eecf2614567bed11c6e93fb338e302', class: "top-left-buttons" }, h("kritzel-workspace-manager", { key: 'ce41192540169694482d6f8cca60701bc4427438', 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: 'ec269d3e9f589362d8301370778f0c5e4252eead', visible: this.isBackToContentButtonVisible, text: this.resolvedTerms['backToContent.label'] ?? 'Back to content', onBackToContent: () => this.backToContent() })), this.activeNotification && (h("div", { key: '847ed8149d556c934090424ff54b17b6721ccc76', class: "top-center-notification-layer", role: "presentation" }, h("div", { key: '61d8b8e91cda100ffa6620db26ba6dfb2232946a', class: { 'top-center-notification-container': true, 'is-dismissing': this.isNotificationDismissing }, role: "presentation" }, h("kritzel-notification-card", { key: 'b7e132773225ace38e447756110dd39e4661f5fc', notification: this.activeNotification, locale: this.locale, onDismiss: this.dismissNotification, onHoverChange: event => this.handleNotificationHoverChange(event) })))), h("kritzel-engine", { key: '3e4a6d0dbc015ec675fb21a59990c87e9eaa805f', ref: el => {
3060
3138
  if (el) {
3061
3139
  this.engineRef = el;
3062
3140
  }
3063
- }, workspace: this.activeWorkspace, activeWorkspaceId: this.activeWorkspaceId, editorId: this.editorId, syncConfig: this.syncConfig, assetStorageConfig: this.assetStorageConfig, user: this.user, scaleMax: this.scaleMax, lockDrawingScale: this.lockDrawingScale, isObjectDistanceFadingActive: this.isObjectDistanceFadingActive, scaleMin: this.scaleMin, cursorTarget: this.cursorTarget, isLoading: this.isLoading, viewportBoundaryLeft: this.viewportBoundaryLeft, viewportBoundaryRight: this.viewportBoundaryRight, viewportBoundaryTop: this.viewportBoundaryTop, viewportBoundaryBottom: this.viewportBoundaryBottom, isPanningEnabled: this.isPanningEnabled, isZoomingEnabled: this.isZoomingEnabled, theme: this.theme, themes: this.themes, licenseKey: this.licenseKey, locale: this.locale, locales: this.locales, fallbackLocale: this.fallbackLocale, debugInfo: this.debugInfo, globalContextMenuItems: this.globalContextMenuItems, objectContextMenuItems: this.objectContextMenuItems, onIsEngineReady: event => this.onEngineReady(event), onWorkspacesChange: event => this.handleWorkspacesChange(event), onActiveWorkspaceChange: event => this.handleActiveWorkspaceChange(event), onObjectsChange: event => this.handleObjectsChange(event), onObjectsAdded: event => this.handleObjectsAdded(event), onObjectsRemoved: event => this.handleObjectsRemoved(event), onObjectsUpdated: event => this.handleObjectsUpdated(event), onUndoStateChange: event => this.handleUndoStateChange(event), onObjectsInViewportChange: event => this.handleObjectsInViewportChange(event), onViewportChange: event => this.handleViewportChange(event), onAwarenessChange: event => this.handleAwarenessChange(event), onNotificationsChange: event => this.handleNotificationsChange(event) }), h("kritzel-controls", { key: 'eda4dc93fe663c8472638190216bea5bf64d7153', visible: this.isControlsVisible, class: { 'keyboard-open': this.isVirtualKeyboardOpen }, ref: el => {
3141
+ }, workspace: this.activeWorkspace, activeWorkspaceId: this.activeWorkspaceId, editorId: this.editorId, syncConfig: this.syncConfig, assetStorageConfig: this.assetStorageConfig, user: this.user, scaleMax: this.scaleMax, lockDrawingScale: this.lockDrawingScale, isObjectDistanceFadingActive: this.isObjectDistanceFadingActive, scaleMin: this.scaleMin, cursorTarget: this.cursorTarget, isLoading: this.isLoading, viewportBoundaryLeft: this.viewportBoundaryLeft, viewportBoundaryRight: this.viewportBoundaryRight, viewportBoundaryTop: this.viewportBoundaryTop, viewportBoundaryBottom: this.viewportBoundaryBottom, isPanningEnabled: this.isPanningEnabled, isZoomingEnabled: this.isZoomingEnabled, theme: this.theme, themes: this.themes, licenseKey: this.licenseKey, locale: this.locale, locales: this.locales, fallbackLocale: this.fallbackLocale, debugInfo: this.debugInfo, globalContextMenuItems: this.globalContextMenuItems, objectContextMenuItems: this.objectContextMenuItems, onIsEngineReady: event => this.onEngineReady(event), onWorkspacesChange: event => this.handleWorkspacesChange(event), onActiveWorkspaceChange: event => this.handleActiveWorkspaceChange(event), onObjectsChange: event => this.handleObjectsChange(event), onObjectsAdded: event => this.handleObjectsAdded(event), onObjectsRemoved: event => this.handleObjectsRemoved(event), onObjectsUpdated: event => this.handleObjectsUpdated(event), onUndoStateChange: event => this.handleUndoStateChange(event), onObjectsInViewportChange: event => this.handleObjectsInViewportChange(event), onViewportChange: event => this.handleViewportChange(event), onAwarenessChange: event => this.handleAwarenessChange(event), onNotificationsChange: event => this.handleNotificationsChange(event) }), h("kritzel-controls", { key: 'f84607a0a4d0762c1249400f85b7b8454d672c1e', visible: this.isControlsVisible, class: { 'keyboard-open': this.isVirtualKeyboardOpen }, ref: el => {
3064
3142
  if (el) {
3065
3143
  this.controlsRef = el;
3066
3144
  }
3067
- }, controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState ?? undefined, theme: this.theme, terms: this.resolvedTerms, onIsControlsReady: () => (this.isControlsReady = true) }), h("div", { key: 'ffdb548da8aac3498b1b3e9bfdc9a41f8c448675', class: "bottom-left-buttons" }, h("kritzel-zoom-panel", { key: 'af6f6a86212195fd3ee13bc33c16574e0a5236a6', visible: this.isZoomPanelVisible, disabled: !this.isZoomingEnabled, zoomPercent: this.currentZoomPercent, terms: this.resolvedTerms, onZoomIn: () => this.zoomIn(), onZoomOut: () => this.zoomOut() })), h("div", { key: '0870c0e47838fc99b4041718a431535827e199cf', class: "top-right-buttons" }, h("kritzel-settings", { key: 'b95beb43dbe1a9c99454fc4ff74f0630aca7ad83', ref: el => {
3145
+ }, controls: this.normalizedControls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState ?? undefined, theme: this.theme, terms: this.resolvedTerms, onIsControlsReady: () => (this.isControlsReady = true) }), h("div", { key: 'e2691e7c730195e197511cb1e4d0fe707677b5ff', class: "bottom-left-buttons" }, h("kritzel-zoom-panel", { key: 'd0ac6624eab49af78d4761c3ad91842834717aad', visible: this.isZoomPanelVisible, disabled: !this.isZoomingEnabled, zoomPercent: this.currentZoomPercent, terms: this.resolvedTerms, onZoomIn: () => this.zoomIn(), onZoomOut: () => this.zoomOut() })), h("div", { key: 'ad3d386ab7e8ad9fdfbb12754db005bc85fa28ce', class: "top-right-buttons" }, h("kritzel-settings", { key: '566024ac6f3d8f7f20238cc1f83882b39dbf698e', ref: el => {
3068
3146
  if (el) {
3069
3147
  this.settingsRef = el;
3070
3148
  }
3071
- }, 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: 'ef6fea2715ff99226fde937185a693efc2a7dd75', ref: el => {
3149
+ }, 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: '4a23ea0c6e070d424a90e9fc87de04e11cff7ade', ref: el => {
3072
3150
  if (el) {
3073
3151
  this.exportRef = el;
3074
3152
  }
3075
- }, 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: '872b7fa12d49410d1c370baed3386a34f89d3e2a', users: this.activeUsers }), shouldShowCurrentUser && h("kritzel-current-user", { key: 'da19825b4fba6a516803f8330cdfdfac6b6c9db1', user: this.user, terms: this.resolvedTerms, onClick: () => this.currentUserDialogRef?.open() }), shouldShowCurrentUser && (h("kritzel-current-user-dialog", { key: '9336ce611286d3ecf9318317ab4bedb8c3f79f05', ref: el => {
3153
+ }, 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: 'feef074b52e552fa2ef7e8258002571aca4c5abd', users: this.activeUsers }), shouldShowCurrentUser && h("kritzel-current-user", { key: '844c722545363eaf7eec69037a835eae943afeb9', user: this.user, terms: this.resolvedTerms, onClick: () => this.currentUserDialogRef?.open() }), shouldShowCurrentUser && (h("kritzel-current-user-dialog", { key: '8363c1d3230d111cf1ace0ccf805669e592bb533', ref: el => {
3076
3154
  if (el) {
3077
3155
  this.currentUserDialogRef = el;
3078
3156
  this.currentUserDialogRef.addEventListener('logoutRequest', this.handleCurrentUserLogout);
3079
3157
  }
3080
- }, user: this.user, terms: this.resolvedTerms })), shouldShowLoginButton && (h("kritzel-button", { key: 'a9e38ad3e13c785f6a4223ebde95f60451c6515f', onButtonClick: () => this.loginDialogRef?.open() }, this.resolvedTerms['login.dialogTitle'] ?? 'Sign in')), h("kritzel-more-menu", { key: 'b8f63a63ae4e75a70d0069410170af902f2a362b', items: this.moreMenuItems, visible: this.isMoreMenuVisible, terms: this.resolvedTerms }), h("kritzel-share-dialog", { key: '7a923fd51e8d2bd1e2833e0941622efadd55f881', ref: el => {
3158
+ }, user: this.user, terms: this.resolvedTerms })), shouldShowLoginButton && (h("kritzel-button", { key: '68ddf8c3aabc7634b4c1d88f3d2d584fa1f1b150', onButtonClick: () => this.loginDialogRef?.open() }, this.resolvedTerms['login.dialogTitle'] ?? 'Sign in')), h("kritzel-more-menu", { key: '39b938f5792f7a36bbf21ed88a1a5c7717a785a6', items: this.moreMenuItems, visible: this.isMoreMenuVisible, terms: this.resolvedTerms }), h("kritzel-share-dialog", { key: '61ed918499760d874d5d2ec140d3cf59ce94520b', ref: el => {
3081
3159
  if (el) {
3082
3160
  this.shareDialogRef = el;
3083
3161
  }
3084
- }, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, terms: this.resolvedTerms, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (h("kritzel-login-dialog", { key: 'ac54c699a94bb2657a6817c37d6220b75d931cd3', ref: el => {
3162
+ }, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, terms: this.resolvedTerms, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (h("kritzel-login-dialog", { key: '5f6528a57884bd1494e45755609e710f69041a4d', ref: el => {
3085
3163
  if (el) {
3086
3164
  this.loginDialogRef = el;
3087
3165
  }
@@ -3100,6 +3178,12 @@ const KritzelEditor = class {
3100
3178
  "activeWorkspace": [{
3101
3179
  "onActiveWorkspaceChange": 0
3102
3180
  }],
3181
+ "customFonts": [{
3182
+ "onCustomFontsChange": 0
3183
+ }],
3184
+ "controls": [{
3185
+ "onControlsChange": 0
3186
+ }],
3103
3187
  "activeWorkspaceId": [{
3104
3188
  "onActiveWorkspaceIdChange": 0
3105
3189
  }],
@@ -21381,7 +21465,7 @@ class KritzelContextMenuHandler extends KritzelBaseHandler {
21381
21465
  * @returns void - This method does not return a value; it updates state and triggers a rerender.
21382
21466
  */
21383
21467
  handleContextMenu(event) {
21384
- if (!(this._core.store.state.activeTool instanceof KritzelSelectionTool)) {
21468
+ if (this._core.store.state.activeTool?.toolType !== 'selection') {
21385
21469
  return;
21386
21470
  }
21387
21471
  if (this._core.store.state.skipContextMenu) {
@@ -21441,7 +21525,7 @@ class KritzelContextMenuHandler extends KritzelBaseHandler {
21441
21525
  */
21442
21526
  open(options) {
21443
21527
  const { x, y, objectId } = options;
21444
- if (this._core.store.state.activeTool instanceof KritzelSelectionTool) {
21528
+ if (this._core.store.state.activeTool?.toolType === 'selection') {
21445
21529
  const selectionTool = this._core.store.state.activeTool;
21446
21530
  selectionTool?.moveHandler?.cancelPendingDrag();
21447
21531
  }
@@ -22256,6 +22340,9 @@ class KritzelObjectMap {
22256
22340
  */
22257
22341
  onAwarenessChange(callback) {
22258
22342
  this._awarenessChangeCallbacks.push(callback);
22343
+ return () => {
22344
+ this._awarenessChangeCallbacks = this._awarenessChangeCallbacks.filter(existingCallback => existingCallback !== callback);
22345
+ };
22259
22346
  }
22260
22347
  /**
22261
22348
  * Registers a callback to be invoked when remote object changes are received.
@@ -22264,6 +22351,9 @@ class KritzelObjectMap {
22264
22351
  */
22265
22352
  onObjectsChange(callback) {
22266
22353
  this._objectsChangeCallbacks.push(callback);
22354
+ return () => {
22355
+ this._objectsChangeCallbacks = this._objectsChangeCallbacks.filter(existingCallback => existingCallback !== callback);
22356
+ };
22267
22357
  }
22268
22358
  /**
22269
22359
  * Returns the Yjs UndoManager instance for managing undo/redo operations.
@@ -22460,6 +22550,9 @@ class KritzelObjectMap {
22460
22550
  callback(states);
22461
22551
  }
22462
22552
  }, delayMs);
22553
+ if (typeof this._awarenessEmitTimeout.unref === 'function') {
22554
+ this._awarenessEmitTimeout.unref();
22555
+ }
22463
22556
  }
22464
22557
  };
22465
22558
  this._awareness.on('change', this._awarenessChangeHandler);
@@ -24450,9 +24543,8 @@ class KritzelClipboardManager {
24450
24543
  const scale = this._core.store.state.scale;
24451
24544
  const offset = 25 / scale;
24452
24545
  const preparePromises = intent.files.map(async (file, index) => {
24453
- // Use the default image compression size of 1200
24454
- const compressed = await imageCompression(file, { maxWidthOrHeight: 1200 });
24455
- return KritzelImage.fromBlob(this._core, compressed, {
24546
+ const preparedFile = await this.prepareImageFile(file);
24547
+ return KritzelImage.fromBlob(this._core, preparedFile, {
24456
24548
  translateX: index * offset,
24457
24549
  translateY: index * offset,
24458
24550
  });
@@ -24464,6 +24556,12 @@ class KritzelClipboardManager {
24464
24556
  return null;
24465
24557
  }
24466
24558
  }
24559
+ async prepareImageFile(file) {
24560
+ if (KritzelImageFileHelper.isGifFile(file)) {
24561
+ return file;
24562
+ }
24563
+ return imageCompression(file, { maxWidthOrHeight: 1200 });
24564
+ }
24467
24565
  async materializePlainText(intent) {
24468
24566
  const textObject = KritzelText.create(this._core);
24469
24567
  const paragraphs = intent.text.split('\n').map(line => {
@@ -27931,7 +28029,7 @@ const KritzelEngine = class {
27931
28029
  }
27932
28030
  return existingTool;
27933
28031
  }
27934
- const registeredTool = this.core.toolRegistry.registerTool(toolName, toolClass);
28032
+ const registeredTool = this.core.toolRegistry.registerTool(toolName, this.resolveToolClass(toolClass));
27935
28033
  if (toolConfig) {
27936
28034
  this.applyToolConfig(registeredTool, toolConfig);
27937
28035
  }
@@ -28733,8 +28831,25 @@ const KritzelEngine = class {
28733
28831
  if (!workspace) {
28734
28832
  throw new Error('Cannot export workspace: no active workspace is loaded');
28735
28833
  }
28834
+ await this.core.initializeAssetStorage();
28736
28835
  const serialized = workspace.serialize({ includeObjects: true });
28737
- return JSON.stringify(serialized, null, 2);
28836
+ const exportMode = this.getWorkspaceExportMode();
28837
+ const { objects, ...workspaceWithoutObjects } = serialized;
28838
+ const payload = {
28839
+ ...workspaceWithoutObjects,
28840
+ exportMode,
28841
+ ...(objects !== undefined ? { objects } : {}),
28842
+ };
28843
+ if (exportMode === 'portable') {
28844
+ const portableAssets = await this.collectPortableAssets(payload.objects);
28845
+ if (Object.keys(portableAssets).length > 0) {
28846
+ payload.assets = portableAssets;
28847
+ }
28848
+ }
28849
+ else {
28850
+ delete payload.assets;
28851
+ }
28852
+ return JSON.stringify(payload, null, 2);
28738
28853
  }
28739
28854
  /**
28740
28855
  * Imports a workspace from a JSON string into a new workspace.
@@ -28748,22 +28863,15 @@ const KritzelEngine = class {
28748
28863
  if (!data.__class__ || data.__class__ !== 'KritzelWorkspace') {
28749
28864
  throw new Error('Invalid workspace data: missing or incorrect __class__ identifier');
28750
28865
  }
28866
+ await this.core.initializeAssetStorage();
28867
+ await this.restorePortableAssets(data);
28751
28868
  if (data.version && data.version !== WORKSPACE_EXPORT_VERSION) {
28752
28869
  console.warn(`Workspace version mismatch: expected ${WORKSPACE_EXPORT_VERSION}, got ${data.version}`);
28753
28870
  }
28754
28871
  // Create a new workspace with a fresh ID but use the imported name
28755
28872
  const newWorkspaceId = ObjectHelper.generateUUID();
28756
- const importedName = data.name || 'Imported Workspace';
28757
- let importedViewport = data.viewport || { centerWorldX: 0, centerWorldY: 0, scale: 1 };
28758
- // Backward compatibility: convert legacy translateX/translateY to centerWorldX/centerWorldY
28759
- if ('translateX' in importedViewport && !('centerWorldX' in importedViewport)) {
28760
- const scale = importedViewport.scale ?? 1;
28761
- importedViewport = {
28762
- centerWorldX: -(importedViewport.translateX ?? 0) / scale,
28763
- centerWorldY: -(importedViewport.translateY ?? 0) / scale,
28764
- scale,
28765
- };
28766
- }
28873
+ const importedName = this.getImportedWorkspaceName(data.name);
28874
+ const importedViewport = this.getImportedWorkspaceViewport(data.viewport);
28767
28875
  const newWorkspace = new KritzelWorkspace(newWorkspaceId, importedName, importedViewport);
28768
28876
  // Save the new workspace
28769
28877
  this.core.createWorkspace(newWorkspace);
@@ -28802,6 +28910,8 @@ const KritzelEngine = class {
28802
28910
  if (!data.__class__ || data.__class__ !== 'KritzelWorkspace') {
28803
28911
  throw new Error('Invalid workspace data: missing or incorrect __class__ identifier');
28804
28912
  }
28913
+ await this.core.initializeAssetStorage();
28914
+ await this.restorePortableAssets(data);
28805
28915
  if (data.version && data.version !== WORKSPACE_EXPORT_VERSION) {
28806
28916
  console.warn(`Workspace version mismatch: expected ${WORKSPACE_EXPORT_VERSION}, got ${data.version}`);
28807
28917
  }
@@ -28828,6 +28938,98 @@ const KritzelEngine = class {
28828
28938
  this.core.rerender();
28829
28939
  return loadedCount;
28830
28940
  }
28941
+ getWorkspaceExportMode() {
28942
+ return this.core.assetResolver.hasRemoteProvider ? 'lightweight' : 'portable';
28943
+ }
28944
+ async collectPortableAssets(serializedObjects) {
28945
+ if (!Array.isArray(serializedObjects) || serializedObjects.length === 0) {
28946
+ return {};
28947
+ }
28948
+ const assetIds = new Set();
28949
+ for (const serializedObject of serializedObjects) {
28950
+ if (!serializedObject || typeof serializedObject !== 'object') {
28951
+ continue;
28952
+ }
28953
+ const maybeImage = serializedObject;
28954
+ if (maybeImage.__class__ === 'KritzelImage' && typeof maybeImage.assetId === 'string' && maybeImage.assetId.length > 0) {
28955
+ assetIds.add(maybeImage.assetId);
28956
+ }
28957
+ }
28958
+ const assets = {};
28959
+ for (const assetId of assetIds) {
28960
+ try {
28961
+ const blob = await this.core.assetResolver.fetchBlob(assetId);
28962
+ assets[assetId] = await this.blobToDataUrl(blob);
28963
+ }
28964
+ catch (error) {
28965
+ console.warn(`[Kritzel] Failed to embed asset ${assetId} in portable workspace export:`, error);
28966
+ }
28967
+ }
28968
+ return assets;
28969
+ }
28970
+ async restorePortableAssets(data) {
28971
+ const assets = data.assets;
28972
+ if (!assets || typeof assets !== 'object') {
28973
+ return;
28974
+ }
28975
+ for (const [assetId, dataUrl] of Object.entries(assets)) {
28976
+ if (!assetId || typeof dataUrl !== 'string' || !dataUrl.startsWith('data:')) {
28977
+ continue;
28978
+ }
28979
+ try {
28980
+ const blob = await this.dataUrlToBlob(dataUrl);
28981
+ await this.core.assetResolver.put(blob, {
28982
+ id: assetId,
28983
+ mimeType: blob.type || 'application/octet-stream',
28984
+ kind: 'image',
28985
+ });
28986
+ }
28987
+ catch (error) {
28988
+ console.warn(`[Kritzel] Failed to restore embedded asset ${assetId} from portable workspace import:`, error);
28989
+ }
28990
+ }
28991
+ }
28992
+ blobToDataUrl(blob) {
28993
+ return new Promise((resolve, reject) => {
28994
+ const reader = new FileReader();
28995
+ reader.onload = () => resolve(reader.result);
28996
+ reader.onerror = () => reject(reader.error ?? new Error('Failed to read blob as data URL'));
28997
+ reader.readAsDataURL(blob);
28998
+ });
28999
+ }
29000
+ async dataUrlToBlob(dataUrl) {
29001
+ const response = await fetch(dataUrl);
29002
+ return response.blob();
29003
+ }
29004
+ getImportedWorkspaceName(rawName) {
29005
+ if (typeof rawName === 'string' && rawName.trim().length > 0) {
29006
+ return rawName;
29007
+ }
29008
+ return 'Imported Workspace';
29009
+ }
29010
+ getImportedWorkspaceViewport(rawViewport) {
29011
+ if (!rawViewport || typeof rawViewport !== 'object') {
29012
+ return { centerWorldX: 0, centerWorldY: 0, scale: 1 };
29013
+ }
29014
+ const viewport = rawViewport;
29015
+ if (typeof viewport.centerWorldX === 'number' && typeof viewport.centerWorldY === 'number') {
29016
+ const scale = typeof viewport.scale === 'number' && viewport.scale > 0 ? viewport.scale : 1;
29017
+ return {
29018
+ centerWorldX: viewport.centerWorldX,
29019
+ centerWorldY: viewport.centerWorldY,
29020
+ scale,
29021
+ };
29022
+ }
29023
+ if (typeof viewport.translateX === 'number' || typeof viewport.translateY === 'number') {
29024
+ const scale = typeof viewport.scale === 'number' && viewport.scale > 0 ? viewport.scale : 1;
29025
+ return {
29026
+ centerWorldX: -(typeof viewport.translateX === 'number' ? viewport.translateX : 0) / scale,
29027
+ centerWorldY: -(typeof viewport.translateY === 'number' ? viewport.translateY : 0) / scale,
29028
+ scale,
29029
+ };
29030
+ }
29031
+ return { centerWorldX: 0, centerWorldY: 0, scale: 1 };
29032
+ }
28831
29033
  /**
28832
29034
  * Exports the current workspace as a JSON file and triggers a browser download.
28833
29035
  * @param filename - Optional custom filename (without extension). Defaults to workspace name with timestamp.
@@ -29112,31 +29314,71 @@ const KritzelEngine = class {
29112
29314
  this.syncingChange.emit(isSyncing);
29113
29315
  }
29114
29316
  applyToolConfig(tool, toolConfig) {
29115
- Object.entries(toolConfig).forEach(([key, value]) => {
29317
+ const normalizedToolConfig = this.normalizeToolConfig(toolConfig);
29318
+ Object.entries(normalizedToolConfig).forEach(([key, value]) => {
29116
29319
  tool[key] = value;
29117
29320
  });
29118
29321
  // Resolve palettes map into the flat palette array the UI reads
29119
- if ('palettes' in toolConfig && 'type' in toolConfig) {
29120
- const brushConfig = toolConfig;
29322
+ if ('palettes' in normalizedToolConfig && 'type' in normalizedToolConfig) {
29323
+ const brushConfig = normalizedToolConfig;
29121
29324
  const resolvedPalette = brushConfig.palettes[brushConfig.type];
29122
29325
  if (resolvedPalette) {
29123
29326
  tool['palette'] = resolvedPalette;
29124
29327
  }
29125
29328
  }
29126
29329
  // Resolve sizes map into the flat sizes array the UI reads (brush tool uses a map keyed by type)
29127
- if ('sizes' in toolConfig && toolConfig.sizes != null && 'type' in toolConfig) {
29128
- const brushConfig = toolConfig;
29330
+ if ('sizes' in normalizedToolConfig && normalizedToolConfig.sizes != null && 'type' in normalizedToolConfig) {
29331
+ const brushConfig = normalizedToolConfig;
29129
29332
  const resolvedSizes = brushConfig.sizes?.[brushConfig.type];
29130
29333
  if (resolvedSizes) {
29131
29334
  tool['sizes'] = resolvedSizes;
29132
29335
  }
29133
29336
  }
29134
29337
  }
29338
+ normalizeToolConfig(toolConfig) {
29339
+ if (!this.isTextToolConfig(toolConfig)) {
29340
+ return toolConfig;
29341
+ }
29342
+ return {
29343
+ ...toolConfig,
29344
+ availableFonts: resolveTextToolAvailableFonts(toolConfig.availableFonts),
29345
+ };
29346
+ }
29347
+ isTextToolConfig(toolConfig) {
29348
+ return 'fontFamily' in toolConfig && 'size' in toolConfig && 'color' in toolConfig;
29349
+ }
29350
+ /** Built-in tool classes of this bundle keyed by their tool type. */
29351
+ static BUILT_IN_TOOL_CLASSES = {
29352
+ selection: KritzelSelectionTool,
29353
+ brush: KritzelBrushTool,
29354
+ eraser: KritzelEraserTool,
29355
+ line: KritzelLineTool,
29356
+ shape: KritzelShapeTool,
29357
+ text: KritzelTextTool,
29358
+ image: KritzelImageTool,
29359
+ };
29360
+ /**
29361
+ * Resolves a tool class to the internal built-in class when a foreign copy is passed.
29362
+ * Classes from another compiled copy of the library (dual-package hazard, e.g. built-ins
29363
+ * re-exported by a framework wrapper) would otherwise break `instanceof` checks.
29364
+ */
29365
+ resolveToolClass(toolClass) {
29366
+ if (toolClass.prototype instanceof KritzelBaseTool) {
29367
+ return toolClass;
29368
+ }
29369
+ try {
29370
+ const probe = new toolClass(this.core);
29371
+ return KritzelEngine.BUILT_IN_TOOL_CLASSES[probe.toolType] ?? toolClass;
29372
+ }
29373
+ catch {
29374
+ return toolClass;
29375
+ }
29376
+ }
29135
29377
  get isSelecting() {
29136
- return this.core.store.state.activeTool instanceof KritzelSelectionTool && this.core.store.state.isSelecting;
29378
+ return this.core.store.state.activeTool?.toolType === 'selection' && this.core.store.state.isSelecting;
29137
29379
  }
29138
29380
  get isSelectionActive() {
29139
- return this.core.store.state.activeTool instanceof KritzelSelectionTool && this.core.store.selectionGroup !== null;
29381
+ return this.core.store.state.activeTool?.toolType === 'selection' && this.core.store.selectionGroup !== null;
29140
29382
  }
29141
29383
  constructor(hostRef) {
29142
29384
  registerInstance(this, hostRef);
@@ -29453,7 +29695,7 @@ const KritzelEngine = class {
29453
29695
  this.core.store.onStateChange('activeTool', this._handleActiveToolChange.bind(this));
29454
29696
  }
29455
29697
  _handleActiveToolChange(activeTool) {
29456
- if (!(activeTool instanceof KritzelSelectionTool)) {
29698
+ if (activeTool?.toolType !== 'selection') {
29457
29699
  this.core.clearSelection();
29458
29700
  this.core.store.objects?.remove(o => o instanceof KritzelSelectionBox);
29459
29701
  this.core.store.setSelectionBox(null);
@@ -29462,10 +29704,10 @@ const KritzelEngine = class {
29462
29704
  this.core.store.state.isResizeHandleSelected = false;
29463
29705
  this.core.store.state.isRotationHandleSelected = false;
29464
29706
  }
29465
- if (!(activeTool instanceof KritzelTextTool)) {
29707
+ if (activeTool?.toolType !== 'text') {
29466
29708
  this.core.resetActiveText();
29467
29709
  }
29468
- if (!(activeTool instanceof KritzelShapeTool)) {
29710
+ if (activeTool?.toolType !== 'shape') {
29469
29711
  this.core.resetActiveShape();
29470
29712
  }
29471
29713
  this.core.store.state.skipContextMenu = false;
@@ -29699,7 +29941,7 @@ const KritzelEngine = class {
29699
29941
  borderWidth: object.borderWidth + 'px',
29700
29942
  borderStyle: 'solid',
29701
29943
  padding: object.padding + 'px',
29702
- overflow: 'hidden',
29944
+ overflow: 'visible',
29703
29945
  display: 'block',
29704
29946
  } })), KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup') && !this.core.displaySelectionLineUI(object) && (h("div", { ref: el => el && object.mount(el), style: {
29705
29947
  position: 'absolute',
@@ -30049,7 +30291,7 @@ const KritzelEngine = class {
30049
30291
  }, this.core.store.selectionGroup?.objects || []);
30050
30292
  }
30051
30293
  this.hideContextMenu();
30052
- }, 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') })));
30294
+ }, onClose: () => this.hideContextMenu() })), this.core.store.objects?.hasAwareness && h("kritzel-awareness-cursors", { core: this.core }), this.core.store.state?.activeTool?.toolType === 'eraser' && !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') })));
30053
30295
  }
30054
30296
  static get watchers() { return {
30055
30297
  "workspace": [{
@@ -30237,39 +30479,47 @@ const KritzelFontFamily = class {
30237
30479
  registerInstance(this, hostRef);
30238
30480
  this.fontFamilyChange = createEvent(this, "fontFamilyChange");
30239
30481
  }
30240
- fontOptions = [
30241
- { value: 'arial', label: 'Arial' },
30242
- { value: 'verdana', label: 'Verdana' },
30243
- { value: 'helvetica', label: 'Helvetica' },
30244
- { value: 'tahoma', label: 'Tahoma' },
30245
- { value: 'trebuchet ms', label: 'Trebuchet MS' },
30246
- { value: 'times new roman', label: 'Times New Roman' },
30247
- { value: 'georgia', label: 'Georgia' },
30248
- { value: 'garamond', label: 'Garamond' },
30249
- { value: 'courier new', label: 'Courier New' },
30250
- { value: 'brush script mt', label: 'Brush Script MT' },
30251
- ];
30482
+ fontOptions = [];
30252
30483
  selectedFontFamily;
30253
30484
  fontFamilyChange;
30485
+ handleFontOptionsChange() {
30486
+ this.ensureValidSelectedFont(this.resolveFontOptions());
30487
+ }
30254
30488
  componentWillLoad() {
30255
- if (this.fontOptions && this.fontOptions.length > 0) {
30256
- const isValidCurrentFont = this.fontOptions.some(opt => opt.value === this.selectedFontFamily);
30257
- if (!this.selectedFontFamily || !isValidCurrentFont) {
30258
- this.selectedFontFamily = this.fontOptions[0].value;
30259
- }
30260
- }
30489
+ this.ensureValidSelectedFont(this.resolveFontOptions());
30261
30490
  }
30262
30491
  handleDropdownValueChange = (event) => {
30263
30492
  this.fontFamilyChange.emit(event.detail);
30264
30493
  };
30494
+ ensureValidSelectedFont(options) {
30495
+ if (options.length === 0) {
30496
+ return;
30497
+ }
30498
+ const normalizedSelected = this.selectedFontFamily?.trim().toLocaleLowerCase();
30499
+ const isValidCurrentFont = options.some(opt => opt.value.trim().toLocaleLowerCase() === normalizedSelected);
30500
+ if (!normalizedSelected || !isValidCurrentFont) {
30501
+ this.selectedFontFamily = options[0].value;
30502
+ }
30503
+ }
30504
+ resolveFontOptions() {
30505
+ return this.fontOptions;
30506
+ }
30265
30507
  render() {
30266
- const dropdownOptions = this.fontOptions.map(option => ({
30508
+ const fontOptions = this.resolveFontOptions();
30509
+ const selectedOption = fontOptions.find(option => option.value === this.selectedFontFamily);
30510
+ const selectedFontFamily = selectedOption?.cssFontFamily ?? this.selectedFontFamily;
30511
+ const dropdownOptions = fontOptions.map(option => ({
30267
30512
  value: option.value,
30268
30513
  label: option.label,
30269
- style: { fontFamily: option.value },
30514
+ style: { fontFamily: option.cssFontFamily ?? option.value },
30270
30515
  }));
30271
- return (h(Host, { key: '0123dbdff9b7051d2ebc9dcf8d1f9c7c4c161dc4' }, h("kritzel-dropdown", { key: '5d4e289161502ecc60cef3d0c6d546a64817a12a', options: dropdownOptions, value: this.selectedFontFamily, onValueChanged: this.handleDropdownValueChange, selectStyles: { fontFamily: this.selectedFontFamily } })));
30516
+ return (h(Host, { key: '059350f34370f7a291f47a42721ca5d9bb82df49' }, h("kritzel-dropdown", { key: 'fbd8cf1ae463c7e9b76d0ac754a41de54397058e', options: dropdownOptions, value: this.selectedFontFamily, onValueChanged: this.handleDropdownValueChange, selectStyles: { fontFamily: selectedFontFamily } })));
30272
30517
  }
30518
+ static get watchers() { return {
30519
+ "fontOptions": [{
30520
+ "handleFontOptionsChange": 0
30521
+ }]
30522
+ }; }
30273
30523
  };
30274
30524
  KritzelFontFamily.style = kritzelFontFamilyCss();
30275
30525
 
@@ -31467,7 +31717,7 @@ const KritzelPortal = class {
31467
31717
  * This file is auto-generated by the version bump scripts.
31468
31718
  * Do not modify manually.
31469
31719
  */
31470
- const KRITZEL_VERSION = '0.4.0';
31720
+ const KRITZEL_VERSION = '0.4.2';
31471
31721
 
31472
31722
  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)}`;
31473
31723
 
@@ -35182,8 +35432,43 @@ const KritzelToolConfig = class {
35182
35432
  sizes = [];
35183
35433
  currentOpacity = 1;
35184
35434
  updateTrigger = 0;
35435
+ getFontOptionsFromTool() {
35436
+ const configuredFonts = resolveTextToolAvailableFonts(this.tool.availableFonts).map(font => {
35437
+ const registeredFont = typeof font === 'string'
35438
+ ? {
35439
+ family: font,
35440
+ label: font,
35441
+ cssFontFamily: font,
35442
+ }
35443
+ : {
35444
+ family: font.family,
35445
+ label: font.label ?? font.family,
35446
+ cssFontFamily: font.cssFontFamily ?? font.family,
35447
+ };
35448
+ return {
35449
+ value: registeredFont.family,
35450
+ label: registeredFont.label,
35451
+ cssFontFamily: registeredFont.cssFontFamily,
35452
+ };
35453
+ });
35454
+ const mergedFonts = [...configuredFonts];
35455
+ const seenFonts = new Set(mergedFonts.map(font => font.value.trim().toLocaleLowerCase()));
35456
+ for (const font of KritzelFontRegistry.list()) {
35457
+ const normalizedValue = font.family.trim().toLocaleLowerCase();
35458
+ if (seenFonts.has(normalizedValue)) {
35459
+ continue;
35460
+ }
35461
+ seenFonts.add(normalizedValue);
35462
+ mergedFonts.push({
35463
+ value: font.family,
35464
+ label: font.label,
35465
+ cssFontFamily: font.cssFontFamily,
35466
+ });
35467
+ }
35468
+ return mergedFonts;
35469
+ }
35185
35470
  handleSelectionChange() {
35186
- if (this.tool instanceof KritzelSelectionTool) {
35471
+ if (this.tool?.toolType === 'selection') {
35187
35472
  this.config = KritzelToolConfigHelper.getToolConfig(this.tool);
35188
35473
  if (this.config) {
35189
35474
  this.updatePalette();
@@ -35220,7 +35505,7 @@ const KritzelToolConfig = class {
35220
35505
  color: KritzelColorHelper.applyOpacity(color, opacity, this.theme),
35221
35506
  size,
35222
35507
  };
35223
- if (this.tool instanceof KritzelTextTool) {
35508
+ if (this.tool.toolType === 'text') {
35224
35509
  displayValues.fontFamily = this.tool.fontFamily;
35225
35510
  }
35226
35511
  this.displayValuesChange.emit(displayValues);
@@ -35313,7 +35598,7 @@ const KritzelToolConfig = class {
35313
35598
  case 'shape-fill':
35314
35599
  return (h("kritzel-shape-fill", { key: control.type, value: this.getShapeFillValue(), onValueChange: (event) => this.handlePropertyChange(control.propertyName, event.detail) }));
35315
35600
  case 'font-family':
35316
- return (h("kritzel-font-family", { key: control.type, selectedFontFamily: value, onFontFamilyChange: (event) => this.handlePropertyChange(control.propertyName, event.detail) }));
35601
+ return (h("kritzel-font-family", { key: control.type, fontOptions: this.getFontOptionsFromTool(), selectedFontFamily: value, onFontFamilyChange: (event) => this.handlePropertyChange(control.propertyName, event.detail) }));
35317
35602
  default:
35318
35603
  return null;
35319
35604
  }