kritzel-stencil 0.3.22 → 0.3.23

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 (104) hide show
  1. package/dist/cjs/index.cjs.js +3 -1
  2. package/dist/cjs/kritzel-active-users_45.cjs.entry.js +507 -288
  3. package/dist/cjs/{schema.constants-IBG5VW_J.js → schema.constants-Lr_zNkox.js} +713 -47
  4. package/dist/collection/classes/core/core.class.js +83 -5
  5. package/dist/collection/classes/core/store.class.js +9 -0
  6. package/dist/collection/classes/handlers/key.handler.js +1 -1
  7. package/dist/collection/classes/handlers/selection.handler.js +13 -9
  8. package/dist/collection/classes/objects/base-object.class.js +20 -8
  9. package/dist/collection/classes/objects/custom-element.class.js +330 -8
  10. package/dist/collection/classes/objects/image.class.js +0 -1
  11. package/dist/collection/classes/objects/selection-group.class.js +12 -0
  12. package/dist/collection/classes/providers/assets/asset-resolver.class.js +24 -14
  13. package/dist/collection/classes/providers/assets/indexeddb-asset-provider.class.js +0 -8
  14. package/dist/collection/classes/registries/custom-element-renderer.registry.js +52 -0
  15. package/dist/collection/classes/structures/app-state-map.structure.js +17 -3
  16. package/dist/collection/classes/structures/object-map.structure.js +17 -3
  17. package/dist/collection/classes/tools/selection-tool.class.js +34 -6
  18. package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +9 -8
  19. package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +211 -78
  20. package/dist/collection/components/ui/kritzel-settings/kritzel-settings.js +2 -1
  21. package/dist/collection/configs/default-engine-config.js +3 -2
  22. package/dist/collection/constants/version.js +1 -1
  23. package/dist/collection/helpers/html.helper.js +52 -0
  24. package/dist/collection/helpers/object.helper.js +3 -0
  25. package/dist/collection/helpers/svg-export.helper.js +35 -0
  26. package/dist/collection/helpers/wheel.helper.js +113 -0
  27. package/dist/collection/index.js +3 -0
  28. package/dist/collection/interfaces/custom-element-renderer.interface.js +1 -0
  29. package/dist/components/index.js +1 -1
  30. package/dist/components/kritzel-awareness-cursors.js +1 -1
  31. package/dist/components/kritzel-controls.js +1 -1
  32. package/dist/components/kritzel-current-user-dialog.js +1 -1
  33. package/dist/components/kritzel-current-user.js +1 -1
  34. package/dist/components/kritzel-dialog.js +1 -1
  35. package/dist/components/kritzel-editor.js +1 -1
  36. package/dist/components/kritzel-engine.js +1 -1
  37. package/dist/components/kritzel-export.js +1 -1
  38. package/dist/components/kritzel-login-dialog.js +1 -1
  39. package/dist/components/kritzel-menu-item.js +1 -1
  40. package/dist/components/kritzel-menu.js +1 -1
  41. package/dist/components/kritzel-more-menu.js +1 -1
  42. package/dist/components/kritzel-portal.js +1 -1
  43. package/dist/components/kritzel-settings.js +1 -1
  44. package/dist/components/kritzel-share-dialog.js +1 -1
  45. package/dist/components/kritzel-split-button.js +1 -1
  46. package/dist/components/kritzel-tool-config.js +1 -1
  47. package/dist/components/kritzel-tooltip.js +1 -1
  48. package/dist/components/kritzel-workspace-manager.js +1 -1
  49. package/dist/components/{p-BwuemYET.js → p-B4WfPDj0.js} +1 -1
  50. package/dist/components/p-BNu3Akp5.js +1 -0
  51. package/dist/components/p-BYveczcb.js +1 -0
  52. package/dist/components/{p-D28ZylHn.js → p-BixEMVtW.js} +1 -1
  53. package/dist/components/{p-Ndib1Jb3.js → p-BntMEYss.js} +1 -1
  54. package/dist/components/p-C48bv_0f.js +9 -0
  55. package/dist/components/{p-BO1CnEOT.js → p-CPMpf9ku.js} +1 -1
  56. package/dist/components/{p-BhODnvMO.js → p-CZQz_mul.js} +1 -1
  57. package/dist/components/{p-COj8EVzL.js → p-Ckpa5tHF.js} +1 -1
  58. package/dist/components/{p-BPoDE87Q.js → p-DJ3KI725.js} +1 -1
  59. package/dist/components/{p-DRsD38Nl.js → p-DPlEzPKO.js} +1 -1
  60. package/dist/components/{p-C-RiEw9X.js → p-DSd5xTAH.js} +1 -1
  61. package/dist/components/{p-CbMLbDou.js → p-DTqMF94b.js} +1 -1
  62. package/dist/components/{p-CHrSFOSI.js → p-DX2nriaq.js} +1 -1
  63. package/dist/components/{p-BAN5dnHX.js → p-Dd-pMvlr.js} +1 -1
  64. package/dist/components/{p-C5TZjIE1.js → p-M9m9-OEY.js} +1 -1
  65. package/dist/components/p-WUdQqYnu.js +1 -0
  66. package/dist/components/{p-YFR180v-.js → p-XDNjFYRB.js} +1 -1
  67. package/dist/components/{p-CJEIUs0x.js → p-X_pyPCe8.js} +1 -1
  68. package/dist/components/{p-CpR9lc-o.js → p-meY80XnF.js} +1 -1
  69. package/dist/components/{p-6Ca7Eolf.js → p-y5T0_Wvi.js} +1 -1
  70. package/dist/esm/index.js +2 -2
  71. package/dist/esm/kritzel-active-users_45.entry.js +500 -281
  72. package/dist/esm/{schema.constants-D35cwCYd.js → schema.constants-DMPBDrZh.js} +711 -47
  73. package/dist/stencil/index.esm.js +1 -1
  74. package/dist/stencil/p-27d41f6f.entry.js +9 -0
  75. package/dist/stencil/p-DMPBDrZh.js +1 -0
  76. package/dist/stencil/stencil.esm.js +1 -1
  77. package/dist/types/classes/core/core.class.d.ts +15 -0
  78. package/dist/types/classes/core/store.class.d.ts +6 -0
  79. package/dist/types/classes/objects/base-object.class.d.ts +3 -2
  80. package/dist/types/classes/objects/custom-element.class.d.ts +87 -21
  81. package/dist/types/classes/objects/image.class.d.ts +0 -1
  82. package/dist/types/classes/objects/selection-group.class.d.ts +1 -0
  83. package/dist/types/classes/providers/assets/asset-resolver.class.d.ts +3 -0
  84. package/dist/types/classes/providers/assets/indexeddb-asset-provider.class.d.ts +0 -1
  85. package/dist/types/classes/registries/custom-element-renderer.registry.d.ts +32 -0
  86. package/dist/types/components/core/kritzel-engine/kritzel-engine.d.ts +7 -1
  87. package/dist/types/components.d.ts +2 -2
  88. package/dist/types/constants/version.d.ts +1 -1
  89. package/dist/types/helpers/html.helper.d.ts +2 -0
  90. package/dist/types/helpers/object.helper.d.ts +1 -0
  91. package/dist/types/helpers/svg-export.helper.d.ts +9 -0
  92. package/dist/types/helpers/wheel.helper.d.ts +9 -0
  93. package/dist/types/index.d.ts +3 -0
  94. package/dist/types/interfaces/asset-storage-config.interface.d.ts +0 -4
  95. package/dist/types/interfaces/custom-element-renderer.interface.d.ts +40 -0
  96. package/dist/types/interfaces/debug-info.interface.d.ts +1 -0
  97. package/dist/types/interfaces/object.interface.d.ts +0 -1
  98. package/package.json +1 -1
  99. package/dist/components/p-BML28BJR.js +0 -1
  100. package/dist/components/p-CVV-HzFR.js +0 -1
  101. package/dist/components/p-DWhWFDcC.js +0 -9
  102. package/dist/components/p-Dd_EODkk.js +0 -1
  103. package/dist/stencil/p-D35cwCYd.js +0 -1
  104. package/dist/stencil/p-fcbf7965.entry.js +0 -9
@@ -186,6 +186,7 @@ export class KritzelCore {
186
186
  this._clipboardManager = new KritzelClipboardManager(this);
187
187
  this._toolRegistry = new KritzelToolRegistry(this);
188
188
  this._assetResolver = new KritzelAssetResolver();
189
+ this._assetResolver.setShowInfo(this._store.state.debugInfo?.showAssetResolverInfo ?? false);
189
190
  }
190
191
  /**
191
192
  * Gets the asset resolver responsible for storing and resolving
@@ -557,6 +558,10 @@ export class KritzelCore {
557
558
  this.removeObject(child);
558
559
  });
559
560
  }
561
+ // Unmount custom elements before removal
562
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
563
+ object.unmount();
564
+ }
560
565
  // Handle anchor cleanup
561
566
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelLine')) {
562
567
  // If removing a line, clean up its anchor index entries
@@ -597,21 +602,84 @@ export class KritzelCore {
597
602
  * @param selectionGroup - The selection group to add
598
603
  */
599
604
  addSelectionGroup(selectionGroup) {
600
- this.removeSelectionGroup();
605
+ this.removeLocalSelectionGroups();
601
606
  this.removeSelectionBox();
607
+ for (const object of selectionGroup.objects ?? []) {
608
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
609
+ const customElementObject = object;
610
+ if (typeof customElementObject.setIsInteractive === 'function') {
611
+ customElementObject.setIsInteractive(true);
612
+ }
613
+ else {
614
+ customElementObject.isInteractive = true;
615
+ }
616
+ }
617
+ }
602
618
  this._store.objects.insert(selectionGroup);
603
619
  this._store.setSelectionGroup(selectionGroup);
604
620
  this._kritzelEngine.triggerSelectionChange();
605
621
  }
622
+ /**
623
+ * Returns whether the provided selection group belongs to the local user context.
624
+ * Local groups are either explicitly owned by the current user or have no user ID.
625
+ */
626
+ isLocalSelectionGroup(selectionGroup) {
627
+ const localUserId = this._user?.id;
628
+ return localUserId == null || selectionGroup.userId === localUserId || selectionGroup.userId == null;
629
+ }
630
+ /**
631
+ * Applies deselection teardown side effects to a selection group before removal.
632
+ */
633
+ teardownSelectionGroup(selectionGroup) {
634
+ selectionGroup.deselectAllChildren?.();
635
+ for (const object of selectionGroup.objects ?? []) {
636
+ if (KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement')) {
637
+ const customElementObject = object;
638
+ if (typeof customElementObject.setIsInteractive === 'function') {
639
+ customElementObject.setIsInteractive(false);
640
+ }
641
+ else {
642
+ customElementObject.isInteractive = false;
643
+ }
644
+ }
645
+ }
646
+ }
647
+ /**
648
+ * Removes all local selection groups from the store, including orphaned groups
649
+ * that are no longer referenced by store.selectionGroup.
650
+ * @returns True when at least one local selection group was removed
651
+ */
652
+ removeLocalSelectionGroups() {
653
+ const allSelectionGroupsInStore = this._store.objects
654
+ .allObjects()
655
+ .filter((object) => {
656
+ return object instanceof KritzelSelectionGroup || KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup');
657
+ });
658
+ const allLocalSelectionGroups = allSelectionGroupsInStore.filter(selectionGroup => this.isLocalSelectionGroup(selectionGroup));
659
+ const currentSelectionGroup = this._store.selectionGroup;
660
+ if (currentSelectionGroup &&
661
+ this.isLocalSelectionGroup(currentSelectionGroup) &&
662
+ !allLocalSelectionGroups.some(group => group.id === currentSelectionGroup.id)) {
663
+ allLocalSelectionGroups.push(currentSelectionGroup);
664
+ }
665
+ if (allLocalSelectionGroups.length === 0) {
666
+ return false;
667
+ }
668
+ for (const selectionGroup of allLocalSelectionGroups) {
669
+ this.teardownSelectionGroup(selectionGroup);
670
+ }
671
+ const localSelectionGroupIds = new Set(allLocalSelectionGroups.map(group => group.id));
672
+ this._store.objects.remove(object => localSelectionGroupIds.has(object.id));
673
+ this._store.setSelectionGroup(null);
674
+ return true;
675
+ }
606
676
  /**
607
677
  * Removes the current selection group from the canvas.
608
678
  * Clears the cached reference and triggers the selection change event.
609
679
  */
610
680
  removeSelectionGroup() {
611
- const selectionGroup = this._store.selectionGroup;
612
- if (selectionGroup) {
613
- this._store.objects.remove(object => object.id === selectionGroup.id);
614
- this._store.setSelectionGroup(null);
681
+ const removedAnySelectionGroup = this.removeLocalSelectionGroups();
682
+ if (removedAnySelectionGroup) {
615
683
  this._kritzelEngine.triggerSelectionChange();
616
684
  }
617
685
  }
@@ -915,6 +983,11 @@ export class KritzelCore {
915
983
  (o instanceof KritzelSelectionGroup && (localUserId == null || o.userId === localUserId || o.userId == null)));
916
984
  this._store.setSelectionBox(null);
917
985
  this._store.setSelectionGroup(null);
986
+ for (const object of this._store.allNonSelectionObjects) {
987
+ if (object.isSelected) {
988
+ object.isSelected = false;
989
+ }
990
+ }
918
991
  this._store.state.isSelecting = false;
919
992
  this._store.state.isResizeHandleSelected = false;
920
993
  this._store.state.isRotationHandleSelected = false;
@@ -1077,6 +1150,11 @@ export class KritzelCore {
1077
1150
  // During selection phase (no group yet), hide UI for KritzelLine objects
1078
1151
  return !(object instanceof KritzelLine);
1079
1152
  }
1153
+ // Once a local selection group exists, only the selection-group object
1154
+ // should render group UI (children remain selected but don't render handles).
1155
+ if (!(object instanceof KritzelSelectionGroup) || object.id !== selectionGroup.id) {
1156
+ return false;
1157
+ }
1080
1158
  // Show UI if selection contains more than one object
1081
1159
  if (selectionGroup.objects.length > 1) {
1082
1160
  return true;
@@ -4,6 +4,7 @@ import { KritzelText } from "../objects/text.class";
4
4
  import { KritzelShape } from "../objects/shape.class";
5
5
  import { KritzelPath } from "../objects/path.class";
6
6
  import { KritzelLine } from "../objects/line.class";
7
+ import { KritzelClassHelper } from "../../helpers/class.helper";
7
8
  import { KritzelObjectMap } from "../structures/object-map.structure";
8
9
  /**
9
10
  * Central state management store for the Kritzel engine.
@@ -222,6 +223,14 @@ export class KritzelStore {
222
223
  const activeShapes = this.objects.filter(o => o instanceof KritzelShape && o.isEditing);
223
224
  return activeShapes.length > 0 ? activeShapes[0] : null;
224
225
  }
226
+ /**
227
+ * Gets the currently selected custom element whose focused descendant is an input-like element.
228
+ * @returns The interactive custom element with focused input-like content, or null if none matches
229
+ */
230
+ get activeInteractiveCustomElement() {
231
+ const interactiveCustomElement = this.selectedObjects.find(object => KritzelClassHelper.isInstanceOf(object, 'KritzelCustomElement') && object.hasFocusedInputElement());
232
+ return interactiveCustomElement ?? null;
233
+ }
225
234
  /**
226
235
  * Gets the path currently being drawn (not yet completed).
227
236
  * @returns The incomplete path object, or null if no path is being drawn
@@ -129,7 +129,7 @@ export class KritzelKeyHandler extends KritzelBaseHandler {
129
129
  // of running global canvas shortcuts. Escape is the one exception — it
130
130
  // still runs through the shortcut system so it can collapse the text
131
131
  // selection (handled inside the shortcut's action).
132
- if ((this._core.store.activeText || this._core.store.activeShape) && event.key !== 'Escape') {
132
+ if ((this._core.store.activeText || this._core.store.activeShape || this._core.store.activeInteractiveCustomElement) && event.key !== 'Escape') {
133
133
  return;
134
134
  }
135
135
  const shortcut = this.shortcuts.find(s => s.key === event.key && !!s.ctrl === event.ctrlKey && !!s.shift === event.shiftKey && (!s.condition || s.condition(this._core)));
@@ -178,10 +178,9 @@ export class KritzelSelectionHandler extends KritzelBaseHandler {
178
178
  selectionBox.translateX = this.startX;
179
179
  selectionBox.translateY = this.startY;
180
180
  this._core.store.state.isSelecting = true;
181
- this._core.store.objects.remove(o => o instanceof KritzelSelectionBox || (!isAdditive && o instanceof KritzelSelectionGroup && (o.userId == null || o.userId === this._core.user?.id)));
182
- this._core.store.setSelectionBox(null);
181
+ this._core.removeSelectionBox();
183
182
  if (!isAdditive) {
184
- this._core.store.setSelectionGroup(null);
183
+ this._core.removeSelectionGroup();
185
184
  }
186
185
  this._core.store.objects.insert(selectionBox);
187
186
  this._core.store.setSelectionBox(selectionBox);
@@ -210,10 +209,9 @@ export class KritzelSelectionHandler extends KritzelBaseHandler {
210
209
  selectionBox.translateX = this.startX;
211
210
  selectionBox.translateY = this.startY;
212
211
  this._core.store.state.isSelecting = true;
213
- this._core.store.objects.remove(o => o instanceof KritzelSelectionBox || (!isAdditive && o instanceof KritzelSelectionGroup && (o.userId == null || o.userId === this._core.user?.id)));
214
- this._core.store.setSelectionBox(null);
212
+ this._core.removeSelectionBox();
215
213
  if (!isAdditive) {
216
- this._core.store.setSelectionGroup(null);
214
+ this._core.removeSelectionGroup();
217
215
  }
218
216
  this._core.store.objects.insert(selectionBox);
219
217
  this._core.store.setSelectionBox(selectionBox);
@@ -383,8 +381,11 @@ export class KritzelSelectionHandler extends KritzelBaseHandler {
383
381
  */
384
382
  clearSelectionPreview() {
385
383
  // Only clear previously tracked selected objects - O(k) instead of O(n)
384
+ const selectedGroupIds = new Set(this._core.store.selectionGroup?.objectIds ?? []);
386
385
  for (const obj of this._previouslySelectedObjects) {
387
- obj.isSelected = false;
386
+ if (!selectedGroupIds.has(obj.id)) {
387
+ obj.isSelected = false;
388
+ }
388
389
  }
389
390
  this._previouslySelectedObjects.clear();
390
391
  }
@@ -403,7 +404,6 @@ export class KritzelSelectionHandler extends KritzelBaseHandler {
403
404
  // Resolve child objects to their parent groups
404
405
  const parentGroup = KritzelGroup.findParentGroup(this._core, object.id);
405
406
  const objectToSelect = parentGroup || object;
406
- objectToSelect.isSelected = false;
407
407
  const existingGroup = this._core.store.selectionGroup;
408
408
  if (shiftKey && existingGroup) {
409
409
  existingGroup.addOrRemove(objectToSelect);
@@ -450,7 +450,6 @@ export class KritzelSelectionHandler extends KritzelBaseHandler {
450
450
  const parentGroup = childToParentMap.get(obj.id);
451
451
  const toSelect = parentGroup || obj;
452
452
  resolvedObjects.set(toSelect.id, toSelect);
453
- obj.isSelected = false;
454
453
  }
455
454
  const unrolledObjects = Array.from(resolvedObjects.values());
456
455
  const filteredObjects = this.filterDescendantsOfSelectedGroups(unrolledObjects);
@@ -458,12 +457,16 @@ export class KritzelSelectionHandler extends KritzelBaseHandler {
458
457
  if (shiftKey && existingGroup) {
459
458
  existingGroup.addObjects(filteredObjects);
460
459
  this.removeDescendantsOfSelectedGroups(existingGroup);
460
+ this._previouslySelectedObjects.clear();
461
461
  this._core.rerender();
462
462
  }
463
463
  else {
464
464
  // Create selection group and set all IDs at once (no per-item refresh)
465
465
  const selectionGroup = KritzelSelectionGroup.create(this._core);
466
466
  selectionGroup.objectIds = filteredObjects.map(o => o.id);
467
+ filteredObjects.forEach(obj => {
468
+ obj.isSelected = true;
469
+ });
467
470
  // Only refresh dimensions and capture snapshots ONCE at the end - O(n) instead of O(n²)
468
471
  if (selectionGroup.length === 1) {
469
472
  selectionGroup.rotation = selectionGroup.objects[0].rotation;
@@ -472,6 +475,7 @@ export class KritzelSelectionHandler extends KritzelBaseHandler {
472
475
  selectionGroup.refreshObjectDimensions();
473
476
  selectionGroup.isSelected = true;
474
477
  this._core.addSelectionGroup(selectionGroup);
478
+ this._previouslySelectedObjects.clear();
475
479
  this._core.rerender();
476
480
  }
477
481
  }
@@ -36,7 +36,8 @@ export class KritzelBaseObject {
36
36
  isMounted = false;
37
37
  isEditable = false;
38
38
  isInteractive = false;
39
- isDebugInfoVisible = false;
39
+ isResizable = true;
40
+ isRotatable = true;
40
41
  /**
41
42
  * Gets the total width of the object including padding.
42
43
  * @returns The width plus double the padding value
@@ -344,21 +345,24 @@ export class KritzelBaseObject {
344
345
  Object.assign(copiedObject, this);
345
346
  copiedObject.id = this.generateId();
346
347
  copiedObject.isMounted = false;
348
+ copiedObject.isSelected = false;
349
+ copiedObject.isHovered = false;
350
+ copiedObject.isInteractive = false;
351
+ copiedObject.resizing = false;
352
+ copiedObject.markedForRemoval = false;
353
+ copiedObject.isVisible = true;
347
354
  return copiedObject;
348
355
  }
349
356
  /**
350
357
  * Serializes the object to a plain JavaScript object for persistence.
351
358
  * Excludes internal references (_core, _elementRef) and computed properties.
352
- * DOM elements are cloned if present.
359
+ * Subclasses should override to handle domain-specific serialization (e.g., HTML elements).
353
360
  * @returns A serializable object containing all persistent properties
354
361
  */
355
362
  serialize() {
356
- const { _core, _elementRef, element, totalWidth, totalHeight, ...remainingProps } = this;
357
- const clonedProps = structuredClone(remainingProps);
358
- if (element && typeof element === 'object' && 'nodeType' in element && element.nodeType === 1) {
359
- clonedProps.element = element.cloneNode(true);
360
- }
361
- return clonedProps;
363
+ const { _core, _elementRef, element, totalWidth, totalHeight, isSelected, isHovered, isMounted, isInteractive, resizing, markedForRemoval, isVisible, ...remainingProps } = this;
364
+ // No longer clone raw elements—let subclasses handle serializable payloads
365
+ return structuredClone(remainingProps);
362
366
  }
363
367
  /**
364
368
  * Restores the object's state from a serialized plain object.
@@ -369,6 +373,14 @@ export class KritzelBaseObject {
369
373
  */
370
374
  deserialize(object) {
371
375
  Object.assign(this, object);
376
+ // Selection/interactivity state is transient UI state and must not survive revival.
377
+ this.isSelected = false;
378
+ this.isHovered = false;
379
+ this.isMounted = false;
380
+ this.isInteractive = false;
381
+ this.resizing = false;
382
+ this.markedForRemoval = false;
383
+ this.isVisible = true;
372
384
  return this;
373
385
  }
374
386
  /**