kritzel-stencil 0.3.2 → 0.3.7

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 (66) hide show
  1. package/dist/cjs/index.cjs.js +70 -1
  2. package/dist/cjs/kritzel-active-users_42.cjs.entry.js +167 -41
  3. package/dist/cjs/loader.cjs.js +1 -1
  4. package/dist/cjs/stencil.cjs.js +1 -1
  5. package/dist/cjs/{workspace.migrations-C1ZoE9o5.js → workspace.migrations-BlC8KRoQ.js} +12 -71
  6. package/dist/collection/classes/core/core.class.js +16 -5
  7. package/dist/collection/classes/core/store.class.js +8 -1
  8. package/dist/collection/classes/handlers/key.handler.js +1 -2
  9. package/dist/collection/classes/objects/shape.class.js +2 -3
  10. package/dist/collection/classes/objects/text.class.js +2 -3
  11. package/dist/collection/classes/providers/sync/in-memory-sync-provider.class.js +68 -0
  12. package/dist/collection/classes/registries/tool.registry.js +21 -9
  13. package/dist/collection/classes/tools/image-tool.class.js +2 -3
  14. package/dist/collection/classes/tools/line-tool.class.js +1 -2
  15. package/dist/collection/classes/tools/shape-tool.class.js +3 -4
  16. package/dist/collection/classes/tools/text-tool.class.js +2 -3
  17. package/dist/collection/components/core/kritzel-editor/kritzel-editor.css +0 -2
  18. package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +5 -6
  19. package/dist/collection/components/ui/kritzel-controls/kritzel-controls.css +2 -1
  20. package/dist/collection/components/ui/kritzel-controls/kritzel-controls.js +36 -34
  21. package/dist/collection/components/ui/kritzel-more-menu/kritzel-more-menu.css +1 -0
  22. package/dist/collection/components/ui/kritzel-tool-config/kritzel-tool-config.js +43 -9
  23. package/dist/collection/components/ui/kritzel-utility-panel/kritzel-utility-panel.css +1 -1
  24. package/dist/collection/constants/version.js +1 -1
  25. package/dist/collection/helpers/tool-config.helper.js +6 -5
  26. package/dist/collection/index.js +1 -0
  27. package/dist/components/index.js +1 -1
  28. package/dist/components/kritzel-controls.js +1 -1
  29. package/dist/components/kritzel-editor.js +1 -1
  30. package/dist/components/kritzel-engine.js +1 -1
  31. package/dist/components/kritzel-more-menu.js +1 -1
  32. package/dist/components/kritzel-settings.js +1 -1
  33. package/dist/components/kritzel-tool-config.js +1 -1
  34. package/dist/components/kritzel-utility-panel.js +1 -1
  35. package/dist/components/p-8b9zAWnS.js +1 -0
  36. package/dist/components/{p-Cx6YyDdF.js → p-BV3EJRtU.js} +1 -1
  37. package/dist/components/p-CSODtZrV.js +1 -0
  38. package/dist/components/p-CoJdbj3f.js +1 -0
  39. package/dist/components/p-Ctd1w9-z.js +1 -0
  40. package/dist/components/{p-CTvfEvp0.js → p-D0ctIEh_.js} +1 -1
  41. package/dist/components/p-P0p4WBpa.js +9 -0
  42. package/dist/esm/index.js +71 -3
  43. package/dist/esm/kritzel-active-users_42.entry.js +167 -41
  44. package/dist/esm/loader.js +1 -1
  45. package/dist/esm/stencil.js +1 -1
  46. package/dist/esm/{workspace.migrations-DAPZgVVd.js → workspace.migrations-BLXBI--a.js} +13 -71
  47. package/dist/stencil/index.esm.js +1 -1
  48. package/dist/stencil/p-3058e485.entry.js +9 -0
  49. package/dist/stencil/p-BLXBI--a.js +1 -0
  50. package/dist/stencil/stencil.esm.js +1 -1
  51. package/dist/types/classes/core/core.class.d.ts +8 -0
  52. package/dist/types/classes/providers/sync/in-memory-sync-provider.class.d.ts +38 -0
  53. package/dist/types/classes/registries/tool.registry.d.ts +18 -8
  54. package/dist/types/components/ui/kritzel-controls/kritzel-controls.d.ts +4 -0
  55. package/dist/types/components/ui/kritzel-tool-config/kritzel-tool-config.d.ts +4 -0
  56. package/dist/types/components.d.ts +2 -0
  57. package/dist/types/constants/version.d.ts +1 -1
  58. package/dist/types/index.d.ts +1 -0
  59. package/package.json +1 -1
  60. package/dist/components/p-BlkUdg5N.js +0 -1
  61. package/dist/components/p-CJfHfT5E.js +0 -1
  62. package/dist/components/p-CQVYExZL.js +0 -9
  63. package/dist/components/p-Clo5Ydi1.js +0 -1
  64. package/dist/components/p-S3uDfF_D.js +0 -1
  65. package/dist/stencil/p-DAPZgVVd.js +0 -1
  66. package/dist/stencil/p-ef426a72.entry.js +0 -9
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { H as HocuspocusProvider, a as HocuspocusProviderWebsocket } from './workspace.migrations-DAPZgVVd.js';
2
- export { E as APP_STATE_MIGRATIONS, A as AssetNotFoundError, C as CURRENT_APP_STATE_SCHEMA_VERSION, z as CURRENT_WORKSPACE_SCHEMA_VERSION, v as DEFAULT_ASSET_STORAGE_CONFIG, D as DEFAULT_BRUSH_CONFIG, u as DEFAULT_LINE_TOOL_CONFIG, t as DEFAULT_TEXT_CONFIG, p as IndexedDBAssetProvider, I as IndexedDBSyncProvider, y as KritzelAlignment, r as KritzelAnchorManager, o as KritzelAssetResolver, g as KritzelBrushTool, m as KritzelCursorHelper, i as KritzelEraserTool, e as KritzelGroup, c as KritzelImage, j as KritzelImageTool, d as KritzelLine, h as KritzelLineTool, b as KritzelPath, n as KritzelSelectionTool, f as KritzelShape, l as KritzelShapeTool, K as KritzelText, k as KritzelTextTool, s as KritzelThemeManager, q as KritzelWorkspace, S as ShapeType, W as WORKSPACE_EXPORT_VERSION, F as WORKSPACE_MIGRATIONS, x as darkTheme, w as lightTheme, B as runMigrations } from './workspace.migrations-DAPZgVVd.js';
1
+ import { H as HocuspocusProvider, a as HocuspocusProviderWebsocket } from './workspace.migrations-BLXBI--a.js';
2
+ export { E as APP_STATE_MIGRATIONS, A as AssetNotFoundError, C as CURRENT_APP_STATE_SCHEMA_VERSION, z as CURRENT_WORKSPACE_SCHEMA_VERSION, v as DEFAULT_ASSET_STORAGE_CONFIG, D as DEFAULT_BRUSH_CONFIG, u as DEFAULT_LINE_TOOL_CONFIG, t as DEFAULT_TEXT_CONFIG, p as IndexedDBAssetProvider, I as IndexedDBSyncProvider, y as KritzelAlignment, r as KritzelAnchorManager, o as KritzelAssetResolver, g as KritzelBrushTool, m as KritzelCursorHelper, i as KritzelEraserTool, e as KritzelGroup, c as KritzelImage, j as KritzelImageTool, d as KritzelLine, h as KritzelLineTool, b as KritzelPath, n as KritzelSelectionTool, f as KritzelShape, l as KritzelShapeTool, K as KritzelText, k as KritzelTextTool, s as KritzelThemeManager, q as KritzelWorkspace, S as ShapeType, W as WORKSPACE_EXPORT_VERSION, F as WORKSPACE_MIGRATIONS, x as darkTheme, w as lightTheme, B as runMigrations } from './workspace.migrations-BLXBI--a.js';
3
3
  import * as Y from 'yjs';
4
4
  import { WebsocketProvider } from 'y-websocket';
5
5
  import 'y-indexeddb';
@@ -431,6 +431,74 @@ class BroadcastSyncProvider {
431
431
  }
432
432
  }
433
433
 
434
+ /**
435
+ * Module-scoped cache that survives component removal and remounting
436
+ * within the same JS runtime. Cleared on page reload.
437
+ */
438
+ const sessionCache = new Map();
439
+ /**
440
+ * In-memory sync provider for session-scoped persistence.
441
+ *
442
+ * State survives the component being removed from the DOM and later
443
+ * re-added within the same page session, but is lost on page reload.
444
+ * Intended for documentation demos where durable IndexedDB persistence
445
+ * is not desired.
446
+ */
447
+ class InMemorySyncProvider {
448
+ type = 'local';
449
+ doc;
450
+ cacheKey;
451
+ isConnected = false;
452
+ constructor(docName, doc, options) {
453
+ this.doc = doc;
454
+ this.cacheKey = options?.name ?? docName;
455
+ }
456
+ handleUpdate = (_update, origin) => {
457
+ // Skip updates that originated from connect() rehydration to avoid a
458
+ // redundant re-encode of the state we just applied.
459
+ if (origin === this) {
460
+ return;
461
+ }
462
+ sessionCache.set(this.cacheKey, Y.encodeStateAsUpdate(this.doc));
463
+ };
464
+ async connect() {
465
+ if (this.isConnected) {
466
+ return;
467
+ }
468
+ const cached = sessionCache.get(this.cacheKey);
469
+ if (cached) {
470
+ Y.applyUpdate(this.doc, cached, this);
471
+ }
472
+ this.doc.on('update', this.handleUpdate);
473
+ this.isConnected = true;
474
+ }
475
+ disconnect() {
476
+ this.isConnected = false;
477
+ }
478
+ async reconnect() {
479
+ this.disconnect();
480
+ return this.connect();
481
+ }
482
+ destroy() {
483
+ this.doc.off('update', this.handleUpdate);
484
+ this.isConnected = false;
485
+ }
486
+ /**
487
+ * Clears the in-memory cache.
488
+ *
489
+ * @param name - If provided, only the entry with this cache key is removed.
490
+ * If omitted, all entries are cleared.
491
+ */
492
+ static clear(name) {
493
+ if (name !== undefined) {
494
+ sessionCache.delete(name);
495
+ }
496
+ else {
497
+ sessionCache.clear();
498
+ }
499
+ }
500
+ }
501
+
434
502
  /**
435
503
  * WebSocket sync provider for real-time collaboration
436
504
  */
@@ -1063,4 +1131,4 @@ class PresignedAssetProvider {
1063
1131
  }
1064
1132
  }
1065
1133
 
1066
- export { BroadcastSyncProvider, HocuspocusSyncProvider, HttpAssetProvider, PresignedAssetProvider, WebSocketSyncProvider };
1134
+ export { BroadcastSyncProvider, HocuspocusSyncProvider, HttpAssetProvider, InMemorySyncProvider, PresignedAssetProvider, WebSocketSyncProvider };
@@ -1,5 +1,5 @@
1
1
  import { r as registerInstance, h, H as Host, c as createEvent, g as getElement } from './index-D9HaikfQ.js';
2
- import { b as KritzelPath, d as KritzelLine, G as KritzelColorHelper, n as KritzelSelectionTool, g as KritzelBrushTool, h as KritzelLineTool, l as KritzelShapeTool, k as KritzelTextTool, J as KritzelDevicesHelper, L as KritzelMouseButton, M as DEFAULT_COLOR_PALETTE, S as ShapeType, I as IndexedDBSyncProvider, D as DEFAULT_BRUSH_CONFIG, i as KritzelEraserTool, u as DEFAULT_LINE_TOOL_CONFIG, t as DEFAULT_TEXT_CONFIG, j as KritzelImageTool, y as KritzelAlignment, v as DEFAULT_ASSET_STORAGE_CONFIG, N as KritzelSelectionGroup, O as KritzelSelectionBox, P as KritzelIconRegistry, Q as KritzelKeyboardHelper, R as KritzelBaseHandler, T as KritzelToolRegistry, U as KritzelBaseObject, q as KritzelWorkspace, e as KritzelGroup, c as KritzelImage, f as KritzelShape, K as KritzelText, B as runMigrations, z as CURRENT_WORKSPACE_SCHEMA_VERSION, F as WORKSPACE_MIGRATIONS, C as CURRENT_APP_STATE_SCHEMA_VERSION, E as APP_STATE_MIGRATIONS, V as ObjectHelper, m as KritzelCursorHelper, r as KritzelAnchorManager, s as KritzelThemeManager, o as KritzelAssetResolver, X as KritzelClassHelper, Y as KritzelEventHelper, W as WORKSPACE_EXPORT_VERSION } from './workspace.migrations-DAPZgVVd.js';
2
+ import { b as KritzelPath, d as KritzelLine, G as KritzelColorHelper, n as KritzelSelectionTool, g as KritzelBrushTool, h as KritzelLineTool, l as KritzelShapeTool, k as KritzelTextTool, J as KritzelDevicesHelper, L as KritzelMouseButton, M as DEFAULT_COLOR_PALETTE, S as ShapeType, I as IndexedDBSyncProvider, D as DEFAULT_BRUSH_CONFIG, i as KritzelEraserTool, u as DEFAULT_LINE_TOOL_CONFIG, t as DEFAULT_TEXT_CONFIG, j as KritzelImageTool, y as KritzelAlignment, v as DEFAULT_ASSET_STORAGE_CONFIG, N as KritzelSelectionGroup, O as KritzelSelectionBox, P as KritzelIconRegistry, Q as KritzelKeyboardHelper, R as KritzelBaseHandler, T as KritzelBaseObject, q as KritzelWorkspace, e as KritzelGroup, c as KritzelImage, f as KritzelShape, K as KritzelText, B as runMigrations, z as CURRENT_WORKSPACE_SCHEMA_VERSION, F as WORKSPACE_MIGRATIONS, C as CURRENT_APP_STATE_SCHEMA_VERSION, E as APP_STATE_MIGRATIONS, U as ObjectHelper, m as KritzelCursorHelper, r as KritzelAnchorManager, s as KritzelThemeManager, o as KritzelAssetResolver, V as KritzelClassHelper, X as KritzelEventHelper, W as WORKSPACE_EXPORT_VERSION } from './workspace.migrations-BLXBI--a.js';
3
3
  import * as Y from 'yjs';
4
4
  import 'y-indexeddb';
5
5
  import 'y-websocket';
@@ -811,10 +811,11 @@ KritzelContextMenu.style = kritzelContextMenuCss();
811
811
 
812
812
  class KritzelToolConfigHelper {
813
813
  static getToolConfig(tool) {
814
- if (tool instanceof KritzelSelectionTool) {
814
+ const toolConstructorName = tool?.constructor?.name;
815
+ if (tool instanceof KritzelSelectionTool || toolConstructorName === 'KritzelSelectionTool') {
815
816
  return tool.getToolConfig();
816
817
  }
817
- if (tool instanceof KritzelBrushTool) {
818
+ if (tool instanceof KritzelBrushTool || toolConstructorName === 'KritzelBrushTool') {
818
819
  return {
819
820
  type: 'brush',
820
821
  colorProperty: 'color',
@@ -826,7 +827,7 @@ class KritzelToolConfigHelper {
826
827
  ],
827
828
  };
828
829
  }
829
- if (tool instanceof KritzelLineTool) {
830
+ if (tool instanceof KritzelLineTool || toolConstructorName === 'KritzelLineTool') {
830
831
  return {
831
832
  type: 'line',
832
833
  colorProperty: 'color',
@@ -839,7 +840,7 @@ class KritzelToolConfigHelper {
839
840
  ],
840
841
  };
841
842
  }
842
- if (tool instanceof KritzelShapeTool) {
843
+ if (tool instanceof KritzelShapeTool || toolConstructorName === 'KritzelShapeTool') {
843
844
  return {
844
845
  type: 'shape',
845
846
  colorProperty: 'strokeColor',
@@ -852,7 +853,7 @@ class KritzelToolConfigHelper {
852
853
  ],
853
854
  };
854
855
  }
855
- if (tool instanceof KritzelTextTool) {
856
+ if (tool instanceof KritzelTextTool || toolConstructorName === 'KritzelTextTool') {
856
857
  return {
857
858
  type: 'text',
858
859
  colorProperty: 'fontColor',
@@ -870,7 +871,7 @@ class KritzelToolConfigHelper {
870
871
  }
871
872
  }
872
873
 
873
- const kritzelControlsCss = () => `:host{display:flex;flex-direction:column;user-select:none;max-width:100%}:host(.mobile){--kritzel-controls-control-hover-background-color:transparent;--kritzel-controls-control-active-background-color:transparent}.kritzel-controls{display:flex;flex-direction:row;align-items:center;justify-content:flex-start;gap:var(--kritzel-controls-gap, 8px);height:100%;padding:var(--kritzel-controls-padding, 8px);background-color:var(--kritzel-controls-background-color, #ffffff);border-radius:var(--kritzel-controls-border-radius, 16px);box-shadow:var(--kritzel-controls-box-shadow, 0 0 3px rgba(0, 0, 0, 0.08));border:var(--kritzel-controls-border, 1px solid #ebebeb);z-index:10000;position:relative;max-width:100%;overflow:hidden}.kritzel-tools-scroll{display:flex;flex-direction:row;align-items:center;gap:var(--kritzel-controls-gap, 8px);overflow-x:auto;overflow-y:hidden;flex:1 1 auto;min-width:0;padding:4px;margin:-4px;scrollbar-width:none;-ms-overflow-style:none}.kritzel-tools-scroll::-webkit-scrollbar{display:none}.scroll-indicator-left,.scroll-indicator-right{position:absolute;top:0;bottom:0;width:32px;pointer-events:none;opacity:0;transition:opacity 0.2s ease-out;z-index:1}.scroll-indicator-left{left:0;background:linear-gradient(to right, var(--kritzel-controls-background-color, #ffffff), transparent);border-radius:var(--kritzel-controls-border-radius, 16px) 0 0 var(--kritzel-controls-border-radius, 16px)}.scroll-indicator-right{right:0;background:linear-gradient(to left, var(--kritzel-controls-background-color, #ffffff), transparent);border-radius:0 var(--kritzel-controls-border-radius, 16px) var(--kritzel-controls-border-radius, 16px) 0}.scroll-indicator-left.visible,.scroll-indicator-right.visible{opacity:1}.kritzel-control{display:flex;justify-content:center;align-items:center;color:var(--kritzel-controls-control-color, #000000);border-radius:var(--kritzel-controls-control-border-radius, 12px);padding:var(--kritzel-controls-control-padding, 8px);border:none;background:none;cursor:var(--kritzel-global-pointer-cursor, pointer);-webkit-tap-highlight-color:transparent;font-weight:bold}.kritzel-control:focus,.kritzel-control:hover{background-color:var(--kritzel-controls-control-hover-background-color, hsl(0, 0%, 0%, 4.3%))}.kritzel-control:active{background-color:var(--kritzel-controls-control-active-background-color, hsl(0, 0%, 0%, 8.6%))}.kritzel-control.selected,.kritzel-control.selected:hover,.kritzel-control.selected:active{background-color:var(--kritzel-controls-control-selected-background-color, #007AFF) !important;color:var(--kritzel-controls-control-selected-color, #ffffff) !important}.kritzel-control.selected:focus{background-color:var(--kritzel-controls-control-selected-background-color, #007bffe3) !important}.kritzel-control-separator{width:1px;height:24px;background-color:var(--kritzel-controls-border, #ebebeb);margin:0 4px}.kritzel-control-split{position:relative;display:flex;align-items:center;border-radius:var(--kritzel-controls-control-border-radius, 12px);color:var(--kritzel-controls-control-color, #000000)}.kritzel-control-split .kritzel-control-main{display:flex;justify-content:center;align-items:center;padding:var(--kritzel-controls-control-padding, 8px);border:none;background:none;cursor:var(--kritzel-global-pointer-cursor, pointer);-webkit-tap-highlight-color:transparent;border-radius:var(--kritzel-controls-control-border-radius, 12px);color:inherit}.kritzel-control-split.selected .kritzel-control-main{border-radius:var(--kritzel-controls-control-border-radius, 12px) 0 0 var(--kritzel-controls-control-border-radius, 12px)}.kritzel-control-split .kritzel-control-dropdown{display:flex;justify-content:center;align-items:center;align-self:stretch;border:none;background:none;cursor:var(--kritzel-global-pointer-cursor, pointer);-webkit-tap-highlight-color:transparent;border-radius:0 var(--kritzel-controls-control-border-radius, 12px) var(--kritzel-controls-control-border-radius, 12px) 0;color:inherit;width:0;padding:0;opacity:0;overflow:hidden;pointer-events:none;transition:width 0.15s ease-out, padding 0.15s ease-out, opacity 0.15s ease-out}.kritzel-control-split .kritzel-control-dropdown.visible{width:auto;padding:0 6px;opacity:1;pointer-events:auto}.kritzel-control-split .kritzel-control-main:focus,.kritzel-control-split .kritzel-control-main:hover,.kritzel-control-split .kritzel-control-dropdown:focus,.kritzel-control-split .kritzel-control-dropdown:hover{background-color:var(--kritzel-controls-control-hover-background-color, hsl(0, 0%, 0%, 4.3%))}.kritzel-control-split .kritzel-control-main:active,.kritzel-control-split .kritzel-control-dropdown:active{background-color:var(--kritzel-controls-control-active-background-color, hsl(0, 0%, 0%, 8.6%))}.kritzel-control-split.selected{background-color:var(--kritzel-controls-control-selected-background-color, #007AFF) !important;color:var(--kritzel-controls-control-selected-color, #ffffff) !important}.kritzel-control-split.selected .kritzel-control-main:hover,.kritzel-control-split.selected .kritzel-control-dropdown:hover{background-color:rgba(255, 255, 255, 0.15)}.kritzel-submenu-content{display:flex;flex-direction:column;gap:var(--kritzel-submenu-gap, 4px);min-width:140px}.kritzel-submenu-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border:none;background:none;cursor:var(--kritzel-global-pointer-cursor, pointer);border-radius:8px;color:var(--kritzel-controls-control-color, #000000);font-size:14px;text-align:left;white-space:nowrap;-webkit-tap-highlight-color:transparent}.kritzel-submenu-item:hover{background-color:var(--kritzel-controls-control-hover-background-color, hsl(0, 0%, 0%, 4.3%))}.kritzel-submenu-item.active{background-color:var(--kritzel-controls-control-selected-background-color, #007AFF);color:var(--kritzel-controls-control-selected-color, #ffffff)}.kritzel-submenu-item.active:hover{background-color:var(--kritzel-controls-control-selected-background-color, #007AFF)}.kritzel-config-container{position:relative;display:flex;justify-content:center;align-items:center;height:40px;box-sizing:border-box;-webkit-tap-highlight-color:transparent;flex-shrink:0;width:0;opacity:0;overflow:hidden;pointer-events:none;margin-left:calc(-1 * var(--kritzel-controls-gap, 8px));transition:width 0.2s ease-out, opacity 0.2s ease-out, margin-left 0.2s ease-out}.kritzel-config-container.visible{width:40px;opacity:1;pointer-events:auto;margin-left:0;overflow:visible}.config-gradient-left{position:absolute;top:0;bottom:0;left:-32px;width:32px;background:linear-gradient(to right, transparent, var(--kritzel-controls-background-color, #ffffff));pointer-events:none;z-index:1;opacity:0;transition:opacity 0.2s ease-out}.config-gradient-left.visible{opacity:1}.kritzel-config{display:flex;justify-content:center;align-items:center;cursor:var(--kritzel-global-pointer-cursor, pointer);border-radius:50%}.color-container{display:flex;justify-content:center;align-items:center;width:32px;height:32px;border-radius:50%;cursor:var(--kritzel-global-pointer-cursor, pointer);border:2px solid transparent;box-sizing:border-box;background-color:var(--kritzel-color-palette-hover-background-color, #ebebeb)}.font-container{display:flex;justify-content:center;align-items:center;width:32px;height:32px;border-radius:50%;cursor:var(--kritzel-global-pointer-cursor, pointer);border:2px solid transparent;box-sizing:border-box;background-color:var(--kritzel-color-palette-hover-background-color, #ebebeb)}.no-config{height:24px;width:24px;border-radius:50%;border:1px dashed gray}kritzel-tooltip{z-index:10001}`;
874
+ const kritzelControlsCss = () => `:host{display:flex;flex-direction:column;user-select:none;max-width:100%;z-index:1}:host(.mobile){--kritzel-controls-control-hover-background-color:transparent;--kritzel-controls-control-active-background-color:transparent}.kritzel-controls{display:flex;flex-direction:row;align-items:center;justify-content:flex-start;gap:var(--kritzel-controls-gap, 8px);height:100%;padding:var(--kritzel-controls-padding, 8px);background-color:var(--kritzel-controls-background-color, #ffffff);border-radius:var(--kritzel-controls-border-radius, 16px);box-shadow:var(--kritzel-controls-box-shadow, 0 0 3px rgba(0, 0, 0, 0.08));border:var(--kritzel-controls-border, 1px solid #ebebeb);z-index:1;position:relative;max-width:100%;overflow:hidden}.kritzel-tools-scroll{display:flex;flex-direction:row;align-items:center;gap:var(--kritzel-controls-gap, 8px);overflow-x:auto;overflow-y:hidden;flex:1 1 auto;min-width:0;padding:4px;margin:-4px;scrollbar-width:none;-ms-overflow-style:none}.kritzel-tools-scroll::-webkit-scrollbar{display:none}.scroll-indicator-left,.scroll-indicator-right{position:absolute;top:0;bottom:0;width:32px;pointer-events:none;opacity:0;transition:opacity 0.2s ease-out;z-index:1}.scroll-indicator-left{left:0;background:linear-gradient(to right, var(--kritzel-controls-background-color, #ffffff), transparent);border-radius:var(--kritzel-controls-border-radius, 16px) 0 0 var(--kritzel-controls-border-radius, 16px)}.scroll-indicator-right{right:0;background:linear-gradient(to left, var(--kritzel-controls-background-color, #ffffff), transparent);border-radius:0 var(--kritzel-controls-border-radius, 16px) var(--kritzel-controls-border-radius, 16px) 0}.scroll-indicator-left.visible,.scroll-indicator-right.visible{opacity:1}.kritzel-control{display:flex;justify-content:center;align-items:center;color:var(--kritzel-controls-control-color, #000000);border-radius:var(--kritzel-controls-control-border-radius, 12px);padding:var(--kritzel-controls-control-padding, 8px);border:none;background:none;cursor:var(--kritzel-global-pointer-cursor, pointer);-webkit-tap-highlight-color:transparent;font-weight:bold}.kritzel-control:focus,.kritzel-control:hover{background-color:var(--kritzel-controls-control-hover-background-color, hsl(0, 0%, 0%, 4.3%))}.kritzel-control:active{background-color:var(--kritzel-controls-control-active-background-color, hsl(0, 0%, 0%, 8.6%))}.kritzel-control.selected,.kritzel-control.selected:hover,.kritzel-control.selected:active{background-color:var(--kritzel-controls-control-selected-background-color, #007AFF) !important;color:var(--kritzel-controls-control-selected-color, #ffffff) !important}.kritzel-control.selected:focus{background-color:var(--kritzel-controls-control-selected-background-color, #007bffe3) !important}.kritzel-control-separator{width:1px;height:24px;background-color:var(--kritzel-controls-border, #ebebeb);margin:0 4px}.kritzel-control-split{position:relative;display:flex;align-items:center;border-radius:var(--kritzel-controls-control-border-radius, 12px);color:var(--kritzel-controls-control-color, #000000)}.kritzel-control-split .kritzel-control-main{display:flex;justify-content:center;align-items:center;padding:var(--kritzel-controls-control-padding, 8px);border:none;background:none;cursor:var(--kritzel-global-pointer-cursor, pointer);-webkit-tap-highlight-color:transparent;border-radius:var(--kritzel-controls-control-border-radius, 12px);color:inherit}.kritzel-control-split.selected .kritzel-control-main{border-radius:var(--kritzel-controls-control-border-radius, 12px) 0 0 var(--kritzel-controls-control-border-radius, 12px)}.kritzel-control-split .kritzel-control-dropdown{display:flex;justify-content:center;align-items:center;align-self:stretch;border:none;background:none;cursor:var(--kritzel-global-pointer-cursor, pointer);-webkit-tap-highlight-color:transparent;border-radius:0 var(--kritzel-controls-control-border-radius, 12px) var(--kritzel-controls-control-border-radius, 12px) 0;color:inherit;width:0;padding:0;opacity:0;overflow:hidden;pointer-events:none;transition:width 0.15s ease-out, padding 0.15s ease-out, opacity 0.15s ease-out}.kritzel-control-split .kritzel-control-dropdown.visible{width:auto;padding:0 6px;opacity:1;pointer-events:auto}.kritzel-control-split .kritzel-control-main:focus,.kritzel-control-split .kritzel-control-main:hover,.kritzel-control-split .kritzel-control-dropdown:focus,.kritzel-control-split .kritzel-control-dropdown:hover{background-color:var(--kritzel-controls-control-hover-background-color, hsl(0, 0%, 0%, 4.3%))}.kritzel-control-split .kritzel-control-main:active,.kritzel-control-split .kritzel-control-dropdown:active{background-color:var(--kritzel-controls-control-active-background-color, hsl(0, 0%, 0%, 8.6%))}.kritzel-control-split.selected{background-color:var(--kritzel-controls-control-selected-background-color, #007AFF) !important;color:var(--kritzel-controls-control-selected-color, #ffffff) !important}.kritzel-control-split.selected .kritzel-control-main:hover,.kritzel-control-split.selected .kritzel-control-dropdown:hover{background-color:rgba(255, 255, 255, 0.15)}.kritzel-submenu-content{display:flex;flex-direction:column;gap:var(--kritzel-submenu-gap, 4px);min-width:140px}.kritzel-submenu-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border:none;background:none;cursor:var(--kritzel-global-pointer-cursor, pointer);border-radius:8px;color:var(--kritzel-controls-control-color, #000000);font-size:14px;text-align:left;white-space:nowrap;-webkit-tap-highlight-color:transparent}.kritzel-submenu-item:hover{background-color:var(--kritzel-controls-control-hover-background-color, hsl(0, 0%, 0%, 4.3%))}.kritzel-submenu-item.active{background-color:var(--kritzel-controls-control-selected-background-color, #007AFF);color:var(--kritzel-controls-control-selected-color, #ffffff)}.kritzel-submenu-item.active:hover{background-color:var(--kritzel-controls-control-selected-background-color, #007AFF)}.kritzel-config-container{position:relative;display:flex;justify-content:center;align-items:center;height:40px;box-sizing:border-box;-webkit-tap-highlight-color:transparent;flex-shrink:0;width:0;opacity:0;overflow:hidden;pointer-events:none;margin-left:calc(-1 * var(--kritzel-controls-gap, 8px));transition:width 0.2s ease-out, opacity 0.2s ease-out, margin-left 0.2s ease-out}.kritzel-config-container.visible{width:40px;opacity:1;pointer-events:auto;margin-left:0;overflow:visible}.config-gradient-left{position:absolute;top:0;bottom:0;left:-32px;width:32px;background:linear-gradient(to right, transparent, var(--kritzel-controls-background-color, #ffffff));pointer-events:none;z-index:1;opacity:0;transition:opacity 0.2s ease-out}.config-gradient-left.visible{opacity:1}.kritzel-config{display:flex;justify-content:center;align-items:center;cursor:var(--kritzel-global-pointer-cursor, pointer);border-radius:50%}.color-container{display:flex;justify-content:center;align-items:center;width:32px;height:32px;border-radius:50%;cursor:var(--kritzel-global-pointer-cursor, pointer);border:2px solid transparent;box-sizing:border-box;background-color:var(--kritzel-color-palette-hover-background-color, #ebebeb)}.font-container{display:flex;justify-content:center;align-items:center;width:32px;height:32px;border-radius:50%;cursor:var(--kritzel-global-pointer-cursor, pointer);border:2px solid transparent;box-sizing:border-box;background-color:var(--kritzel-color-palette-hover-background-color, #ebebeb)}.no-config{height:24px;width:24px;border-radius:50%;border:1px dashed gray}kritzel-tooltip{z-index:10001}`;
874
875
 
875
876
  const KritzelControls = class {
876
877
  constructor(hostRef) {
@@ -891,6 +892,9 @@ const KritzelControls = class {
891
892
  canScrollRight = false;
892
893
  needsScrolling = false;
893
894
  displayValues = null;
895
+ internalControls = [];
896
+ handleActiveToolChangeBound = this.handleActiveToolChange.bind(this);
897
+ handleSelectionChangeBound = this.handleSelectionChange.bind(this);
894
898
  handleKeyDown(event) {
895
899
  if (event.key === 'Escape') {
896
900
  event.preventDefault();
@@ -899,7 +903,7 @@ const KritzelControls = class {
899
903
  }
900
904
  }
901
905
  async handleActiveToolChange(event) {
902
- this.activeControl = this.controls.find(control => control.tool === event.detail) || null;
906
+ this.activeControl = this.internalControls.find(control => control.tool === event.detail) || null;
903
907
  if (this.activeControl?.tool) {
904
908
  this.updateDisplayValues(this.activeControl.tool);
905
909
  }
@@ -981,6 +985,12 @@ const KritzelControls = class {
981
985
  componentDidRender() {
982
986
  this.updateScrollIndicators();
983
987
  }
988
+ disconnectedCallback() {
989
+ if (this.kritzelEngine) {
990
+ this.kritzelEngine.removeEventListener('activeToolChange', this.handleActiveToolChangeBound);
991
+ this.kritzelEngine.removeEventListener('objectsSelectionChange', this.handleSelectionChangeBound);
992
+ }
993
+ }
984
994
  updateScrollIndicators() {
985
995
  if (!this.toolsScrollRef)
986
996
  return;
@@ -1005,12 +1015,19 @@ const KritzelControls = class {
1005
1015
  if (!this.kritzelEngine) {
1006
1016
  throw new Error('kritzel-engine not found in parent element.');
1007
1017
  }
1018
+ this.kritzelEngine.addEventListener('activeToolChange', this.handleActiveToolChangeBound);
1019
+ this.kritzelEngine.addEventListener('objectsSelectionChange', this.handleSelectionChangeBound);
1008
1020
  }
1009
1021
  async initializeTools() {
1010
1022
  let hasDefault = false;
1011
- for (const c of this.controls) {
1012
- if (c.type === 'tool' && c.tool && typeof c.tool === 'function') {
1013
- const registered = await this.kritzelEngine.registerTool(c.name, c.tool, c.config);
1023
+ const newControls = this.controls.map(c => ({ ...c }));
1024
+ for (const c of newControls) {
1025
+ if (c.type === 'tool' && c.tool) {
1026
+ let toolConstructor = c.tool;
1027
+ if (typeof toolConstructor !== 'function') {
1028
+ toolConstructor = toolConstructor.constructor;
1029
+ }
1030
+ const registered = await this.kritzelEngine.registerTool(c.name, toolConstructor, c.config);
1014
1031
  if (registered) {
1015
1032
  c.tool = registered;
1016
1033
  }
@@ -1030,9 +1047,10 @@ const KritzelControls = class {
1030
1047
  }
1031
1048
  }
1032
1049
  }
1050
+ this.internalControls = newControls;
1033
1051
  // If no tool is marked as default, activate the first tool control
1034
1052
  if (!hasDefault) {
1035
- const firstTool = this.controls.find(c => c.type === 'tool' && c.tool);
1053
+ const firstTool = this.internalControls.find(c => c.type === 'tool' && c.tool);
1036
1054
  if (firstTool) {
1037
1055
  await this.kritzelEngine.changeActiveTool(firstTool.tool);
1038
1056
  this.activeControl = firstTool;
@@ -1078,21 +1096,20 @@ const KritzelControls = class {
1078
1096
  await this.handleControlClick(control);
1079
1097
  }
1080
1098
  render() {
1081
- const hasConfigUI = this.activeControl?.tool instanceof KritzelBrushTool ||
1082
- this.activeControl?.tool instanceof KritzelTextTool ||
1083
- this.activeControl?.tool instanceof KritzelLineTool ||
1084
- this.activeControl?.tool instanceof KritzelShapeTool ||
1085
- (this.activeControl?.tool instanceof KritzelSelectionTool && this.activeControl.tool.hasSelection());
1099
+ const activeToolConfig = this.activeControl?.tool
1100
+ ? KritzelToolConfigHelper.getToolConfig(this.activeControl.tool)
1101
+ : null;
1102
+ const hasConfigUI = activeToolConfig !== null;
1086
1103
  // Separate tool controls from config control
1087
- const toolControls = this.controls.filter(c => c.type === 'tool' || c.type === 'separator');
1088
- const configControl = this.controls.find(c => c.type === 'config' && c.name === this.firstConfig?.name);
1089
- return (h(Host, { key: '1e1e558582af482f2bffe065005584b760eca0bb', class: {
1104
+ const toolControls = this.internalControls.filter(c => c.type === 'tool' || c.type === 'separator');
1105
+ const configControl = this.internalControls.find(c => c.type === 'config' && c.name === this.firstConfig?.name);
1106
+ return (h(Host, { key: '7f2a5fed45ac89b34a86b87552ffaa1b94f44d8b', class: {
1090
1107
  mobile: this.isTouchDevice,
1091
- } }, this.isUtilityPanelVisible && (h("kritzel-utility-panel", { key: 'd4f3a0d30be09d7bdbf6ece5bc1255c2e15fa5c2', style: {
1108
+ } }, this.isUtilityPanelVisible && (h("kritzel-utility-panel", { key: '2dcbbe498bce0b59fbd225d103f2e322d3b7ff85', style: {
1092
1109
  position: 'absolute',
1093
1110
  bottom: '56px',
1094
1111
  left: '12px',
1095
- }, undoState: this.undoState, onUndo: () => this.kritzelEngine?.undo(), onRedo: () => this.kritzelEngine?.redo(), onDelete: () => this.kritzelEngine?.delete() })), h("div", { key: 'cf7cdb4a097ff53b3788e05d76ea4b33eb58969b', class: "kritzel-controls" }, h("div", { key: '0d5561f50ddd3d1b0601855f8e46f47c0ceb0b75', class: { 'scroll-indicator-left': true, 'visible': this.canScrollLeft } }), h("div", { key: '0e072381f68873156cd4482fab2ede6e8e994dc3', class: "kritzel-tools-scroll", ref: el => (this.toolsScrollRef = el), onScroll: this.handleToolsScroll }, toolControls.map(control => {
1112
+ }, undoState: this.undoState, onUndo: () => this.kritzelEngine?.undo(), onRedo: () => this.kritzelEngine?.redo(), onDelete: () => this.kritzelEngine?.delete() })), h("div", { key: '6431079040502a78f021c612a7e953b1349f319a', class: "kritzel-controls" }, h("div", { key: '6e0d6dce107cf6b54fb904809fd525ce3b2ae4f0', class: { 'scroll-indicator-left': true, 'visible': this.canScrollLeft } }), h("div", { key: '2686304ad10deac2d022e1deba19195587659d64', class: "kritzel-tools-scroll", ref: el => (this.toolsScrollRef = el), onScroll: this.handleToolsScroll }, toolControls.map(control => {
1096
1113
  // Check if this control has sub-options (split-button)
1097
1114
  if (control.subOptions?.length) {
1098
1115
  const selectedSubOption = this.getSelectedSubOption(control);
@@ -1122,10 +1139,10 @@ const KritzelControls = class {
1122
1139
  'kritzel-control': true,
1123
1140
  'selected': this.activeControl?.name === control?.name,
1124
1141
  }, 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 })));
1125
- })), h("div", { key: 'fa36d29f6609b34d06beecf10402a48ee92a115a', class: { 'scroll-indicator-right': true, 'visible': this.canScrollRight && !(configControl && this.activeControl && hasConfigUI) } }), configControl && this.activeControl && (h("div", { class: {
1142
+ })), h("div", { key: 'f4bcbc856f6e027cdb579356faf54288b43aa080', class: { 'scroll-indicator-right': true, 'visible': this.canScrollRight && !(configControl && this.activeControl && hasConfigUI) } }), configControl && this.activeControl && (h("div", { class: {
1126
1143
  'kritzel-config-container': true,
1127
1144
  'visible': hasConfigUI,
1128
- }, key: configControl.name }, h("div", { key: '7ae1a911cc15561efe9fb6f60f4046e0b443959e', class: { 'config-gradient-left': true, 'visible': this.needsScrolling } }), h("kritzel-tooltip", { key: '6e27b488e1fffefafc85f0bc981f49695e317f99', anchorElement: this.host.shadowRoot?.querySelector('.kritzel-config-container'), triggerElement: this.configTriggerRef }, h("kritzel-tool-config", { key: '235d66bab4c80e77b04e8cefd9f4f3db17928d60', tool: this.activeControl.tool, theme: this.theme, onToolChange: event => this.handleToolChange?.(event), onDisplayValuesChange: this.handleDisplayValuesChange, style: { width: '100%', height: '100%' } })), h("div", { key: 'e1752b2de626ecd36ded0fcbc45333bf6847b155', tabIndex: hasConfigUI ? 0 : -1, class: "kritzel-config", "data-testid": "tool-config", ref: el => {
1145
+ }, key: configControl.name }, h("div", { key: '0646c98b32047f51841f75dd5fb57813ebb153f5', class: { 'config-gradient-left': true, 'visible': this.needsScrolling } }), h("kritzel-tooltip", { key: '0816b8d7ca55add3c6a81de788be3c61a8a814f2', anchorElement: this.host.shadowRoot?.querySelector('.kritzel-config-container'), triggerElement: this.configTriggerRef }, h("kritzel-tool-config", { key: '8f299593454e7855d83ad5d79b528940b2dcb202', 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: '8b2dc1dcc61df93a92cea8ba2aeb776e57965ab3', tabIndex: hasConfigUI ? 0 : -1, class: "kritzel-config", "data-testid": "tool-config", ref: el => {
1129
1146
  if (el)
1130
1147
  this.configTriggerRef = el;
1131
1148
  }, onKeyDown: event => {
@@ -1134,7 +1151,7 @@ const KritzelControls = class {
1134
1151
  }
1135
1152
  }, style: {
1136
1153
  cursor: 'pointer',
1137
- } }, this.displayValues && (h("div", { key: 'dd1bd3067a91a9b6bda71addce88ea2f0ad73e79', class: "color-container" }, h("kritzel-color", { key: 'b2882d7910f4df1f39672eacf540ecfe0bf4413c', value: this.displayValues.color, theme: this.theme, size: 18, style: {
1154
+ } }, this.displayValues && (h("div", { key: '1074fc05048aed92ec9d62e347822df0a57a80e4', class: "color-container" }, h("kritzel-color", { key: '222efa6883ccbc6c97d10360039a1315de7d6273', value: this.displayValues.color, theme: this.theme, size: 18, style: {
1138
1155
  borderRadius: '50%',
1139
1156
  border: 'none',
1140
1157
  } })))))))));
@@ -2091,7 +2108,7 @@ const DEFAULT_SYNC_CONFIG = {
2091
2108
  ],
2092
2109
  };
2093
2110
 
2094
- 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;z-index:10000}.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;z-index:10000}.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}`;
2111
+ 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}`;
2095
2112
 
2096
2113
  const KritzelEditor = class {
2097
2114
  constructor(hostRef) {
@@ -20962,7 +20979,7 @@ class KritzelKeyHandler extends KritzelBaseHandler {
20962
20979
  * @param index - The zero-based index of the tool to activate from the tool registry.
20963
20980
  */
20964
20981
  switchToolByIndex(index) {
20965
- const toolInstance = KritzelToolRegistry.getToolByIndex(index);
20982
+ const toolInstance = this._core.toolRegistry.getToolByIndex(index);
20966
20983
  if (toolInstance) {
20967
20984
  this._core.store.setState('activeTool', toolInstance);
20968
20985
  this._core.deselectAllObjects();
@@ -21474,6 +21491,76 @@ class AlignmentHelper {
21474
21491
  }
21475
21492
  }
21476
21493
 
21494
+ /**
21495
+ * A per-engine registry for managing drawing tools in the Kritzel library.
21496
+ * Each `KritzelCore` owns its own `KritzelToolRegistry` instance so that
21497
+ * multiple editors on the same page do not share tool instances or configs.
21498
+ *
21499
+ * Tools are instantiated and stored by name, allowing retrieval by name or index.
21500
+ */
21501
+ class KritzelToolRegistry {
21502
+ core;
21503
+ /** Internal storage for registered tools, mapping tool names to their instances. */
21504
+ registry = {};
21505
+ /**
21506
+ * Creates a new tool registry bound to a specific `KritzelCore` instance.
21507
+ * The bound core is used as the constructor argument when registering new tools.
21508
+ *
21509
+ * @param core - The `KritzelCore` instance this registry is bound to.
21510
+ */
21511
+ constructor(core) {
21512
+ this.core = core;
21513
+ }
21514
+ /**
21515
+ * Registers a new tool by instantiating it with the registry's bound core.
21516
+ * The tool is stored in the registry under the specified name.
21517
+ *
21518
+ * @param toolName - The unique identifier for the tool.
21519
+ * @param constructor - The tool class constructor that extends `KritzelBaseTool`.
21520
+ * @returns The newly created and registered tool instance.
21521
+ */
21522
+ registerTool(toolName, constructor) {
21523
+ const toolInstance = new constructor(this.core);
21524
+ toolInstance.name = toolName;
21525
+ this.registry[toolName] = toolInstance;
21526
+ return toolInstance;
21527
+ }
21528
+ /**
21529
+ * Checks if a tool with the given name is registered.
21530
+ *
21531
+ * @param toolName - The unique identifier of the tool to check.
21532
+ * @returns `true` if the tool is registered, `false` otherwise.
21533
+ */
21534
+ hasTool(toolName) {
21535
+ return toolName in this.registry;
21536
+ }
21537
+ /**
21538
+ * Retrieves a registered tool by its name.
21539
+ * Logs a warning if the tool is not found.
21540
+ *
21541
+ * @param toolName - The unique identifier of the tool to retrieve.
21542
+ * @returns The tool instance if found, or `null` if not registered.
21543
+ */
21544
+ getTool(toolName) {
21545
+ const toolInstance = this.registry[toolName];
21546
+ if (!toolInstance) {
21547
+ console.warn(`Unknown tool: ${toolName}`);
21548
+ return null;
21549
+ }
21550
+ return toolInstance;
21551
+ }
21552
+ /**
21553
+ * Retrieves a registered tool by its index in the registry.
21554
+ * The index is based on the order tools were registered.
21555
+ *
21556
+ * @param index - The zero-based index of the tool in the registry.
21557
+ * @returns The tool instance at the specified index, or `null` if the index is out of bounds.
21558
+ */
21559
+ getToolByIndex(index) {
21560
+ return Object.values(this.registry)[index] ?? null;
21561
+ }
21562
+ }
21563
+
21477
21564
  /**
21478
21565
  * A quadtree data structure for efficient spatial indexing of objects.
21479
21566
  * Quadtrees recursively subdivide 2D space into four quadrants to enable
@@ -22982,7 +23069,14 @@ class KritzelStore {
22982
23069
  * @param state - The initial engine state configuration
22983
23070
  */
22984
23071
  constructor(state) {
22985
- this._state = state;
23072
+ this._state = {
23073
+ ...state,
23074
+ contextMenuItems: [...state.contextMenuItems],
23075
+ debugInfo: { ...state.debugInfo },
23076
+ pointers: new Map(state.pointers),
23077
+ workspaces: [...state.workspaces],
23078
+ cursor: { ...state.cursor },
23079
+ };
22986
23080
  this._state.objects = new KritzelObjectMap();
22987
23081
  }
22988
23082
  /**
@@ -23621,6 +23715,8 @@ class KritzelCore {
23621
23715
  _cursorManager;
23622
23716
  /** Manager for theme styling */
23623
23717
  _themeManager;
23718
+ /** Per-core registry of drawing tools (one instance per editor). */
23719
+ _toolRegistry;
23624
23720
  /** Optional unique identifier for namespacing storage keys across multiple editor instances */
23625
23721
  _editorId;
23626
23722
  /** Current user for awareness broadcasting */
@@ -23667,6 +23763,13 @@ class KritzelCore {
23667
23763
  get themeManager() {
23668
23764
  return this._themeManager;
23669
23765
  }
23766
+ /**
23767
+ * Gets the tool registry scoped to this core instance.
23768
+ * @returns The KritzelToolRegistry owned by this core
23769
+ */
23770
+ get toolRegistry() {
23771
+ return this._toolRegistry;
23772
+ }
23670
23773
  /**
23671
23774
  * Gets the editor ID used for namespacing storage keys.
23672
23775
  * @returns The editor ID or undefined if not set
@@ -23712,6 +23815,7 @@ class KritzelCore {
23712
23815
  this._anchorManager = new KritzelAnchorManager(this);
23713
23816
  this._cursorManager = new KritzelCursorManager(this);
23714
23817
  this._themeManager = new KritzelThemeManager(this);
23818
+ this._toolRegistry = new KritzelToolRegistry(this);
23715
23819
  this._assetResolver = new KritzelAssetResolver();
23716
23820
  }
23717
23821
  /**
@@ -23818,6 +23922,7 @@ class KritzelCore {
23818
23922
  async initializeWorkspace(workspace, options) {
23819
23923
  // Load all workspaces from app state map
23820
23924
  const workspaces = this.loadWorkspacesFromAppState();
23925
+ const shouldCreateEditorScopedFallback = !workspace && !!this._editorId;
23821
23926
  // Find newest created workspace (tie-break by most recently updated)
23822
23927
  const newestCreatedWorkspace = workspaces.length > 0
23823
23928
  ? [...workspaces].sort((a, b) => {
@@ -23843,7 +23948,7 @@ class KritzelCore {
23843
23948
  // Use last active workspace from localStorage
23844
23949
  activeWorkspace = lastActiveWorkspace;
23845
23950
  }
23846
- else if (newestCreatedWorkspace) {
23951
+ else if (newestCreatedWorkspace && !shouldCreateEditorScopedFallback) {
23847
23952
  // Use newest created workspace
23848
23953
  activeWorkspace = newestCreatedWorkspace;
23849
23954
  }
@@ -24408,7 +24513,7 @@ class KritzelCore {
24408
24513
  this._store.state.copiedObjectIdMapping = newIdMapping;
24409
24514
  }
24410
24515
  });
24411
- this._store.setState('activeTool', KritzelToolRegistry.getTool('selection'));
24516
+ this._store.setState('activeTool', this._toolRegistry.getTool('selection'));
24412
24517
  this.engine.emitObjectsChange();
24413
24518
  this.rerender();
24414
24519
  }
@@ -24597,7 +24702,7 @@ class KritzelCore {
24597
24702
  selectionGroup.rotation = selectionGroup.objects[0].rotation;
24598
24703
  }
24599
24704
  this.addSelectionGroup(selectionGroup);
24600
- this._store.setState('activeTool', KritzelToolRegistry.getTool('selection'));
24705
+ this._store.setState('activeTool', this._toolRegistry.getTool('selection'));
24601
24706
  this.rerender();
24602
24707
  }
24603
24708
  }
@@ -24631,7 +24736,7 @@ class KritzelCore {
24631
24736
  selectionGroup.rotation = selectionGroup.objects[0].rotation;
24632
24737
  }
24633
24738
  this.addSelectionGroup(selectionGroup);
24634
- this._store.setState('activeTool', KritzelToolRegistry.getTool('selection'));
24739
+ this._store.setState('activeTool', this._toolRegistry.getTool('selection'));
24635
24740
  this.rerender();
24636
24741
  }
24637
24742
  }
@@ -24787,7 +24892,7 @@ class KritzelCore {
24787
24892
  }
24788
24893
  this.resetActiveText();
24789
24894
  this.clearSelection();
24790
- this._store.setState('activeTool', KritzelToolRegistry.getTool('selection'));
24895
+ this._store.setState('activeTool', this._toolRegistry.getTool('selection'));
24791
24896
  }
24792
24897
  /**
24793
24898
  * Determines whether to display the selection group UI for an object.
@@ -26481,8 +26586,8 @@ const KritzelEngine = class {
26481
26586
  }
26482
26587
  // If a tool with this name is already registered, return the existing instance
26483
26588
  // This avoids dual-package issues when tool classes are passed from external bundles
26484
- if (KritzelToolRegistry.hasTool(toolName)) {
26485
- const existingTool = KritzelToolRegistry.getTool(toolName);
26589
+ if (this.core.toolRegistry.hasTool(toolName)) {
26590
+ const existingTool = this.core.toolRegistry.getTool(toolName);
26486
26591
  if (toolConfig) {
26487
26592
  Object.entries(toolConfig).forEach(([key, value]) => {
26488
26593
  existingTool[key] = value;
@@ -26490,7 +26595,7 @@ const KritzelEngine = class {
26490
26595
  }
26491
26596
  return existingTool;
26492
26597
  }
26493
- const registeredTool = KritzelToolRegistry.registerTool(toolName, toolClass, this.core);
26598
+ const registeredTool = this.core.toolRegistry.registerTool(toolName, toolClass);
26494
26599
  if (toolConfig) {
26495
26600
  Object.entries(toolConfig).forEach(([key, value]) => {
26496
26601
  registeredTool[key] = value;
@@ -26768,7 +26873,7 @@ const KritzelEngine = class {
26768
26873
  * @param objects - The objects to select.
26769
26874
  */
26770
26875
  async selectObjects(objects) {
26771
- const selectionTool = KritzelToolRegistry.getTool('selection');
26876
+ const selectionTool = this.core.toolRegistry.getTool('selection');
26772
26877
  if (!selectionTool) {
26773
26878
  return;
26774
26879
  }
@@ -26779,7 +26884,7 @@ const KritzelEngine = class {
26779
26884
  }
26780
26885
  /** Selects all objects currently visible in the viewport. Switches to the selection tool automatically. */
26781
26886
  async selectAllObjectsInViewport() {
26782
- const selectionTool = KritzelToolRegistry.getTool('selection');
26887
+ const selectionTool = this.core.toolRegistry.getTool('selection');
26783
26888
  if (!selectionTool) {
26784
26889
  return;
26785
26890
  }
@@ -28883,7 +28988,7 @@ const KritzelMenuItem = class {
28883
28988
  };
28884
28989
  KritzelMenuItem.style = kritzelMenuItemCss();
28885
28990
 
28886
- const kritzelMoreMenuCss = () => `:host{display:inline-flex}.more-menu-wrapper{display:inline-flex;padding:var(--kritzel-more-menu-padding, 4px);background-color:var(--kritzel-more-menu-background-color, #ffffff);border-radius:var(--kritzel-more-menu-border-radius, 12px);box-shadow:var(--kritzel-more-menu-box-shadow, 0 0 3px rgba(0, 0, 0, 0.08));border:var(--kritzel-more-menu-border, 1px solid #ebebeb);opacity:0;pointer-events:none;transition:opacity 0.2s ease-out}.more-menu-wrapper.visible{opacity:1;pointer-events:auto}:host(.mobile){--kritzel-more-menu-button-hover-background-color:transparent;--kritzel-more-menu-button-active-background-color:transparent}.more-menu-button{display:flex;align-items:center;justify-content:center;width:var(--kritzel-more-menu-button-width, 40px);height:var(--kritzel-more-menu-button-height, 40px);padding:0;border:none;border-radius:var(--kritzel-more-menu-inner-border-radius, 12px);background-color:transparent;cursor:var(--kritzel-global-pointer-cursor, pointer);box-shadow:none;transition:background-color 150ms ease;-webkit-tap-highlight-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none;color:var(--kritzel-more-menu-button-color, currentColor)}.more-menu-button:hover{background-color:var(--kritzel-more-menu-button-hover-background-color, hsl(0, 0%, 0%, 4.3%))}.more-menu-button:focus-visible{background-color:var(--kritzel-more-menu-button-hover-background-color, hsl(0, 0%, 0%, 4.3%))}.more-menu-button:active{background-color:var(--kritzel-more-menu-button-active-background-color, hsl(0, 0%, 0%, 4.3%))}`;
28991
+ const kritzelMoreMenuCss = () => `:host{display:inline-flex;z-index:1}.more-menu-wrapper{display:inline-flex;padding:var(--kritzel-more-menu-padding, 4px);background-color:var(--kritzel-more-menu-background-color, #ffffff);border-radius:var(--kritzel-more-menu-border-radius, 12px);box-shadow:var(--kritzel-more-menu-box-shadow, 0 0 3px rgba(0, 0, 0, 0.08));border:var(--kritzel-more-menu-border, 1px solid #ebebeb);opacity:0;pointer-events:none;transition:opacity 0.2s ease-out}.more-menu-wrapper.visible{opacity:1;pointer-events:auto}:host(.mobile){--kritzel-more-menu-button-hover-background-color:transparent;--kritzel-more-menu-button-active-background-color:transparent}.more-menu-button{display:flex;align-items:center;justify-content:center;width:var(--kritzel-more-menu-button-width, 40px);height:var(--kritzel-more-menu-button-height, 40px);padding:0;border:none;border-radius:var(--kritzel-more-menu-inner-border-radius, 12px);background-color:transparent;cursor:var(--kritzel-global-pointer-cursor, pointer);box-shadow:none;transition:background-color 150ms ease;-webkit-tap-highlight-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none;color:var(--kritzel-more-menu-button-color, currentColor)}.more-menu-button:hover{background-color:var(--kritzel-more-menu-button-hover-background-color, hsl(0, 0%, 0%, 4.3%))}.more-menu-button:focus-visible{background-color:var(--kritzel-more-menu-button-hover-background-color, hsl(0, 0%, 0%, 4.3%))}.more-menu-button:active{background-color:var(--kritzel-more-menu-button-active-background-color, hsl(0, 0%, 0%, 4.3%))}`;
28887
28992
 
28888
28993
  const KritzelMoreMenu = class {
28889
28994
  constructor(hostRef) {
@@ -29447,7 +29552,7 @@ const KritzelPortal = class {
29447
29552
  * This file is auto-generated by the version bump scripts.
29448
29553
  * Do not modify manually.
29449
29554
  */
29450
- const KRITZEL_VERSION = '0.3.2';
29555
+ const KRITZEL_VERSION = '0.3.7';
29451
29556
 
29452
29557
  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)}`;
29453
29558
 
@@ -29973,9 +30078,19 @@ const KritzelToolConfig = class {
29973
30078
  }
29974
30079
  isExpanded = false;
29975
30080
  theme;
30081
+ engine;
30082
+ handleSelectionChangeBound = this.handleSelectionChange.bind(this);
29976
30083
  onThemeChange() {
29977
30084
  this.emitDisplayValues();
29978
30085
  }
30086
+ handleEngineChange(newEngine, oldEngine) {
30087
+ if (oldEngine) {
30088
+ oldEngine.removeEventListener('objectsSelectionChange', this.handleSelectionChangeBound);
30089
+ }
30090
+ if (newEngine) {
30091
+ newEngine.addEventListener('objectsSelectionChange', this.handleSelectionChangeBound);
30092
+ }
30093
+ }
29979
30094
  toolChange;
29980
30095
  displayValuesChange;
29981
30096
  config;
@@ -29992,6 +30107,11 @@ const KritzelToolConfig = class {
29992
30107
  }
29993
30108
  }
29994
30109
  }
30110
+ disconnectedCallback() {
30111
+ if (this.engine) {
30112
+ this.engine.removeEventListener('objectsSelectionChange', this.handleSelectionChangeBound);
30113
+ }
30114
+ }
29995
30115
  componentWillLoad() {
29996
30116
  this.config = KritzelToolConfigHelper.getToolConfig(this.tool);
29997
30117
  if (this.config) {
@@ -29999,6 +30119,9 @@ const KritzelToolConfig = class {
29999
30119
  this.currentOpacity = this.tool[this.config.opacityProperty] ?? 1;
30000
30120
  this.emitDisplayValues();
30001
30121
  }
30122
+ if (this.engine) {
30123
+ this.engine.addEventListener('objectsSelectionChange', this.handleSelectionChangeBound);
30124
+ }
30002
30125
  }
30003
30126
  emitDisplayValues() {
30004
30127
  if (!this.config)
@@ -30130,6 +30253,9 @@ const KritzelToolConfig = class {
30130
30253
  }],
30131
30254
  "theme": [{
30132
30255
  "onThemeChange": 0
30256
+ }],
30257
+ "engine": [{
30258
+ "handleEngineChange": 0
30133
30259
  }]
30134
30260
  }; }
30135
30261
  };
@@ -30290,7 +30416,7 @@ const KritzelTooltip = class {
30290
30416
  };
30291
30417
  KritzelTooltip.style = kritzelTooltipCss();
30292
30418
 
30293
- const kritzelUtilityPanelCss = () => `:host{display:flex;flex-direction:row;align-items:center;padding:4px;gap:8px;border-top-left-radius:12px;border-top-right-radius:12px;background-color:var(--kritzel-utility-panel-background-color, #e2e2e2);width:fit-content;user-select:none;z-index:10000}.utility-button{display:flex;justify-content:center;align-items:center;width:28px;height:28px;padding:8px 4px;border:none;background:none;cursor:var(--kritzel-global-pointer-cursor, pointer);color:var(--kritzel-utility-panel-button-color, #333333);--kritzel-icon-color:var(--kritzel-utility-panel-button-color, #333333);-webkit-tap-highlight-color:transparent;border-radius:var(--kritzel-utility-panel-button-border-radius, 8px)}.utility-button:hover,.utility-button:focus-visible{background-color:var(--kritzel-utility-panel-button-hover-background-color, hsl(0, 0%, 0%, 4.3%))}.utility-button:disabled{opacity:0.4;cursor:not-allowed;pointer-events:none}.utility-separator{width:1px;height:16px;background-color:var(--kritzel-utility-panel-separator-color, hsl(0, 0%, 0%, 8%))}`;
30419
+ const kritzelUtilityPanelCss = () => `:host{display:flex;flex-direction:row;align-items:center;padding:4px;gap:8px;border-top-left-radius:12px;border-top-right-radius:12px;background-color:var(--kritzel-utility-panel-background-color, #e2e2e2);width:fit-content;user-select:none;z-index:1}.utility-button{display:flex;justify-content:center;align-items:center;width:28px;height:28px;padding:8px 4px;border:none;background:none;cursor:var(--kritzel-global-pointer-cursor, pointer);color:var(--kritzel-utility-panel-button-color, #333333);--kritzel-icon-color:var(--kritzel-utility-panel-button-color, #333333);-webkit-tap-highlight-color:transparent;border-radius:var(--kritzel-utility-panel-button-border-radius, 8px)}.utility-button:hover,.utility-button:focus-visible{background-color:var(--kritzel-utility-panel-button-hover-background-color, hsl(0, 0%, 0%, 4.3%))}.utility-button:disabled{opacity:0.4;cursor:not-allowed;pointer-events:none}.utility-separator{width:1px;height:16px;background-color:var(--kritzel-utility-panel-separator-color, hsl(0, 0%, 0%, 8%))}`;
30294
30420
 
30295
30421
  const KritzelUtilityPanel = class {
30296
30422
  constructor(hostRef) {