kritzel-stencil 0.3.18 → 0.3.20

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 (159) hide show
  1. package/dist/cjs/index-Xav9JFHg.js +2 -2
  2. package/dist/cjs/index.cjs.js +1 -1
  3. package/dist/cjs/{kritzel-active-users_44.cjs.entry.js → kritzel-active-users_45.cjs.entry.js} +739 -310
  4. package/dist/cjs/loader.cjs.js +1 -1
  5. package/dist/cjs/{schema.constants-DrHO_CYF.js → schema.constants-IBG5VW_J.js} +116 -59
  6. package/dist/cjs/stencil.cjs.js +1 -1
  7. package/dist/collection/classes/core/core.class.js +22 -210
  8. package/dist/collection/classes/handlers/key.handler.js +1 -22
  9. package/dist/collection/classes/managers/browser-permission.manager.js +64 -0
  10. package/dist/collection/classes/managers/clipboard.manager.js +401 -0
  11. package/dist/collection/classes/objects/image.class.js +42 -0
  12. package/dist/collection/classes/tools/image-tool.class.js +1 -59
  13. package/dist/collection/collection-manifest.json +1 -0
  14. package/dist/collection/components/core/kritzel-editor/kritzel-editor.css +53 -0
  15. package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +133 -53
  16. package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +101 -43
  17. package/dist/collection/components/core/kritzel-watermark/kritzel-watermark.js +1 -1
  18. package/dist/collection/components/shared/kritzel-avatar/kritzel-avatar.js +3 -3
  19. package/dist/collection/components/shared/kritzel-button/kritzel-button.js +2 -2
  20. package/dist/collection/components/shared/kritzel-color/kritzel-color.js +2 -2
  21. package/dist/collection/components/shared/kritzel-color-palette/kritzel-color-palette.js +1 -1
  22. package/dist/collection/components/shared/kritzel-font/kritzel-font.js +1 -1
  23. package/dist/collection/components/shared/kritzel-font-size/kritzel-font-size.js +1 -1
  24. package/dist/collection/components/shared/kritzel-input/kritzel-input.js +1 -1
  25. package/dist/collection/components/shared/kritzel-master-detail/kritzel-master-detail.js +3 -3
  26. package/dist/collection/components/shared/kritzel-menu/kritzel-menu.js +1 -1
  27. package/dist/collection/components/shared/kritzel-menu-item/kritzel-menu-item.js +2 -2
  28. package/dist/collection/components/shared/kritzel-numeric-input/kritzel-numeric-input.js +1 -1
  29. package/dist/collection/components/shared/kritzel-opacity-slider/kritzel-opacity-slider.js +1 -1
  30. package/dist/collection/components/shared/kritzel-portal/kritzel-portal.js +1 -1
  31. package/dist/collection/components/shared/kritzel-slide-toggle/kritzel-slide-toggle.js +1 -1
  32. package/dist/collection/components/shared/kritzel-split-button/kritzel-split-button.js +1 -1
  33. package/dist/collection/components/shared/kritzel-stroke-size/kritzel-stroke-size.js +1 -1
  34. package/dist/collection/components/shared/kritzel-tooltip/kritzel-tooltip.js +2 -2
  35. package/dist/collection/components/ui/kritzel-back-to-content/kritzel-back-to-content.js +1 -1
  36. package/dist/collection/components/ui/kritzel-controls/kritzel-controls.js +6 -6
  37. package/dist/collection/components/ui/kritzel-current-user/kritzel-current-user.js +1 -1
  38. package/dist/collection/components/ui/kritzel-current-user-dialog/kritzel-current-user-dialog.js +1 -1
  39. package/dist/collection/components/ui/kritzel-export/kritzel-export.js +1 -1
  40. package/dist/collection/components/ui/kritzel-login-dialog/kritzel-login-dialog.js +1 -1
  41. package/dist/collection/components/ui/kritzel-more-menu/kritzel-more-menu.js +1 -1
  42. package/dist/collection/components/ui/kritzel-notification-card/kritzel-notification-card.css +101 -0
  43. package/dist/collection/components/ui/kritzel-notification-card/kritzel-notification-card.js +173 -0
  44. package/dist/collection/components/ui/kritzel-settings/kritzel-settings.js +1 -1
  45. package/dist/collection/components/ui/kritzel-share-dialog/kritzel-share-dialog.js +2 -2
  46. package/dist/collection/components/ui/kritzel-utility-panel/kritzel-utility-panel.js +1 -1
  47. package/dist/collection/components/ui/kritzel-zoom-panel/kritzel-zoom-panel.js +1 -1
  48. package/dist/collection/configs/default-engine-config.js +0 -1
  49. package/dist/collection/constants/version.js +1 -1
  50. package/dist/collection/interfaces/notification.interface.js +1 -0
  51. package/dist/collection/themes/dark-theme.js +36 -0
  52. package/dist/collection/themes/light-theme.js +36 -0
  53. package/dist/components/index.d.ts +2 -0
  54. package/dist/components/index.js +1 -1
  55. package/dist/components/kritzel-active-users.js +1 -1
  56. package/dist/components/kritzel-avatar.js +1 -1
  57. package/dist/components/kritzel-awareness-cursors.js +1 -1
  58. package/dist/components/kritzel-back-to-content.js +1 -1
  59. package/dist/components/kritzel-button.js +1 -1
  60. package/dist/components/kritzel-color-palette.js +1 -1
  61. package/dist/components/kritzel-color.js +1 -1
  62. package/dist/components/kritzel-controls.js +1 -1
  63. package/dist/components/kritzel-current-user-dialog.js +1 -1
  64. package/dist/components/kritzel-current-user.js +1 -1
  65. package/dist/components/kritzel-editor.js +1 -1
  66. package/dist/components/kritzel-engine.js +1 -1
  67. package/dist/components/kritzel-export.js +1 -1
  68. package/dist/components/kritzel-font-size.js +1 -1
  69. package/dist/components/kritzel-font.js +1 -1
  70. package/dist/components/kritzel-input.js +1 -1
  71. package/dist/components/kritzel-login-dialog.js +1 -1
  72. package/dist/components/kritzel-master-detail.js +1 -1
  73. package/dist/components/kritzel-menu-item.js +1 -1
  74. package/dist/components/kritzel-menu.js +1 -1
  75. package/dist/components/kritzel-more-menu.js +1 -1
  76. package/dist/components/kritzel-notification-card.d.ts +11 -0
  77. package/dist/components/kritzel-notification-card.js +1 -0
  78. package/dist/components/kritzel-numeric-input.js +1 -1
  79. package/dist/components/kritzel-opacity-slider.js +1 -1
  80. package/dist/components/kritzel-portal.js +1 -1
  81. package/dist/components/kritzel-settings.js +1 -1
  82. package/dist/components/kritzel-share-dialog.js +1 -1
  83. package/dist/components/kritzel-slide-toggle.js +1 -1
  84. package/dist/components/kritzel-split-button.js +1 -1
  85. package/dist/components/kritzel-stroke-size.js +1 -1
  86. package/dist/components/kritzel-tool-config.js +1 -1
  87. package/dist/components/kritzel-tooltip.js +1 -1
  88. package/dist/components/kritzel-utility-panel.js +1 -1
  89. package/dist/components/kritzel-watermark.js +1 -1
  90. package/dist/components/kritzel-workspace-manager.js +1 -1
  91. package/dist/components/kritzel-zoom-panel.js +1 -1
  92. package/dist/components/{p-BvRrA4hN.js → p-6Ca7Eolf.js} +1 -1
  93. package/dist/components/{p-BpnIvNvq.js → p-BO1CnEOT.js} +1 -1
  94. package/dist/components/{p-CyqRcqsO.js → p-BPoDE87Q.js} +1 -1
  95. package/dist/components/p-BX-nQAyH.js +1 -0
  96. package/dist/components/{p-DNdXJp8F.js → p-Bg0xIZiR.js} +1 -1
  97. package/dist/components/{p-DmWSRsjK.js → p-BhODnvMO.js} +1 -1
  98. package/dist/components/{p-mz3pUWW4.js → p-Bm0aKWPw.js} +1 -1
  99. package/dist/components/{p-3HxnBrCM.js → p-BrAeYciD.js} +1 -1
  100. package/dist/components/{p-f8aW1ye7.js → p-BwuemYET.js} +1 -1
  101. package/dist/components/{p-U4oawa1x.js → p-C-RiEw9X.js} +1 -1
  102. package/dist/components/{p-DdH1cKED.js → p-C1jg5IQi.js} +1 -1
  103. package/dist/components/{p-SDZNC8GF.js → p-C4-Som48.js} +1 -1
  104. package/dist/components/{p-C9HGoDHE.js → p-C5TZjIE1.js} +1 -1
  105. package/dist/components/{p-7NsK0uHu.js → p-CC0J_kEP.js} +1 -1
  106. package/dist/components/{p-CIcLzcfA.js → p-CJEIUs0x.js} +1 -1
  107. package/dist/components/{p-Bzv9Px8v.js → p-CO0eEzos.js} +1 -1
  108. package/dist/components/{p-DDkmsPpV.js → p-COdwK_mi.js} +1 -1
  109. package/dist/components/{p-CPtDfadX.js → p-COj8EVzL.js} +1 -1
  110. package/dist/components/{p-CEnEDaix.js → p-CVV-HzFR.js} +1 -1
  111. package/dist/components/{p-6RjeGuvH.js → p-Cb5YwXbC.js} +1 -1
  112. package/dist/components/{p-DX5K8xnh.js → p-CbMLbDou.js} +1 -1
  113. package/dist/components/{p-DZdgXCAx.js → p-CdkNxxut.js} +1 -1
  114. package/dist/components/{p-BKJSh8qQ.js → p-CpR9lc-o.js} +1 -1
  115. package/dist/components/p-CpzicliK.js +1 -0
  116. package/dist/components/{p-IpoC5EEY.js → p-D0RYktBV.js} +1 -1
  117. package/dist/components/{p-DI4vQRE3.js → p-D0v9WP5w.js} +1 -1
  118. package/dist/components/{p-DgmtCdnL.js → p-D28ZylHn.js} +1 -1
  119. package/dist/components/{p-F5_X4dZG.js → p-DEpnJBA3.js} +1 -1
  120. package/dist/components/{p-BCNyR5Sw.js → p-DRsD38Nl.js} +1 -1
  121. package/dist/components/p-DWhWFDcC.js +9 -0
  122. package/dist/components/{p-Dz-Ti24X.js → p-DeqALrzV.js} +1 -1
  123. package/dist/components/{p-BKvHg9cv.js → p-Ndib1Jb3.js} +1 -1
  124. package/dist/components/{p-vAeiXe6c.js → p-NwN6r0N0.js} +1 -1
  125. package/dist/components/{p-NuLP1xHe.js → p-W1ZwImdd.js} +1 -1
  126. package/dist/components/{p-Jn6TNdfe.js → p-YFR180v-.js} +1 -1
  127. package/dist/components/p-pagW8YF7.js +1 -0
  128. package/dist/components/{p-v7dxxrL5.js → p-vf28uSsc.js} +1 -1
  129. package/dist/esm/index-Dhio9uis.js +2 -2
  130. package/dist/esm/index.js +2 -2
  131. package/dist/esm/{kritzel-active-users_44.entry.js → kritzel-active-users_45.entry.js} +739 -311
  132. package/dist/esm/loader.js +1 -1
  133. package/dist/esm/{schema.constants-DchTXG3V.js → schema.constants-D35cwCYd.js} +116 -60
  134. package/dist/esm/stencil.js +1 -1
  135. package/dist/stencil/index.esm.js +1 -1
  136. package/dist/stencil/p-D35cwCYd.js +1 -0
  137. package/dist/stencil/p-e8e69a16.entry.js +9 -0
  138. package/dist/stencil/stencil.esm.js +1 -1
  139. package/dist/types/classes/core/core.class.d.ts +16 -19
  140. package/dist/types/classes/handlers/key.handler.d.ts +1 -1
  141. package/dist/types/classes/managers/browser-permission.manager.d.ts +41 -0
  142. package/dist/types/classes/managers/clipboard.manager.d.ts +86 -0
  143. package/dist/types/classes/objects/image.class.d.ts +10 -0
  144. package/dist/types/classes/tools/image-tool.class.d.ts +0 -14
  145. package/dist/types/components/core/kritzel-editor/kritzel-editor.d.ts +20 -1
  146. package/dist/types/components/core/kritzel-engine/kritzel-engine.d.ts +8 -4
  147. package/dist/types/components/ui/kritzel-notification-card/kritzel-notification-card.d.ts +22 -0
  148. package/dist/types/components.d.ts +83 -9
  149. package/dist/types/constants/version.d.ts +1 -1
  150. package/dist/types/interfaces/engine-state.interface.d.ts +0 -3
  151. package/dist/types/interfaces/notification.interface.d.ts +10 -0
  152. package/dist/types/interfaces/theme.interface.d.ts +43 -0
  153. package/package.json +8 -2
  154. package/readme.md +7 -111
  155. package/dist/components/p-Bc55X65h.js +0 -1
  156. package/dist/components/p-C_fKgKHu.js +0 -9
  157. package/dist/components/p-Cu9KYyoq.js +0 -1
  158. package/dist/stencil/p-86348986.entry.js +0 -9
  159. package/dist/stencil/p-DchTXG3V.js +0 -1
@@ -17,6 +17,8 @@ import { KritzelCursorManager } from "../managers/cursor.manager";
17
17
  import { KritzelThemeManager } from "../managers/theme.manager";
18
18
  import { KritzelLocalizationManager } from "../managers/localization.manager";
19
19
  import { KritzelLicenseManager } from "../managers/license.manager";
20
+ import { KritzelBrowserPermissionManager } from "../managers/browser-permission.manager";
21
+ import { KritzelClipboardManager } from "../managers/clipboard.manager";
20
22
  import { KritzelClassHelper } from "../../helpers/class.helper";
21
23
  /**
22
24
  * Central orchestrator class for the Kritzel canvas system.
@@ -46,6 +48,10 @@ export class KritzelCore {
46
48
  _localizationManager;
47
49
  /** Manager for license validation and watermark gating */
48
50
  _licenseManager;
51
+ /** Manager for browser permission checks and permission prompts */
52
+ _permissionManager;
53
+ /** Manager for clipboard copy/cut/paste orchestration */
54
+ _clipboardManager;
49
55
  /** Per-core registry of drawing tools (one instance per editor). */
50
56
  _toolRegistry;
51
57
  /** Optional unique identifier for namespacing storage keys across multiple editor instances */
@@ -108,6 +114,13 @@ export class KritzelCore {
108
114
  get licenseManager() {
109
115
  return this._licenseManager;
110
116
  }
117
+ /**
118
+ * Gets the browser permission manager.
119
+ * @returns The KritzelBrowserPermissionManager for browser permission checks and requests
120
+ */
121
+ get permissionManager() {
122
+ return this._permissionManager;
123
+ }
111
124
  /**
112
125
  * Gets the tool registry scoped to this core instance.
113
126
  * @returns The KritzelToolRegistry owned by this core
@@ -115,6 +128,13 @@ export class KritzelCore {
115
128
  get toolRegistry() {
116
129
  return this._toolRegistry;
117
130
  }
131
+ /**
132
+ * Gets the clipboard manager scoped to this core instance.
133
+ * @returns The KritzelClipboardManager owned by this core
134
+ */
135
+ get clipboardManager() {
136
+ return this._clipboardManager;
137
+ }
118
138
  /**
119
139
  * Gets the editor ID used for namespacing storage keys.
120
140
  * @returns The editor ID or undefined if not set
@@ -162,6 +182,8 @@ export class KritzelCore {
162
182
  this._themeManager = new KritzelThemeManager(this);
163
183
  this._localizationManager = new KritzelLocalizationManager(this);
164
184
  this._licenseManager = new KritzelLicenseManager(this);
185
+ this._permissionManager = new KritzelBrowserPermissionManager(this);
186
+ this._clipboardManager = new KritzelClipboardManager(this);
165
187
  this._toolRegistry = new KritzelToolRegistry(this);
166
188
  this._assetResolver = new KritzelAssetResolver();
167
189
  }
@@ -659,216 +681,6 @@ export class KritzelCore {
659
681
  this.rerender();
660
682
  }
661
683
  }
662
- /**
663
- * Copies the currently selected objects to the clipboard.
664
- * Creates deep copies of objects and tracks ID mappings for anchor remapping.
665
- */
666
- copy() {
667
- const selectionGroup = this._store.selectionGroup;
668
- if (selectionGroup) {
669
- // Copy each object and store them in an array, also track the ID mapping
670
- const idMapping = new Map();
671
- const copiedObjects = selectionGroup.objects
672
- .sort((a, b) => a.zIndex - b.zIndex)
673
- .map(obj => {
674
- const copiedObj = obj.copy();
675
- // Map: newId -> originalId
676
- idMapping.set(copiedObj.id, obj.id);
677
- return copiedObj;
678
- });
679
- this._store.state.copiedObjects = copiedObjects;
680
- this._store.state.copiedObjectIdMapping = idMapping;
681
- }
682
- }
683
- /**
684
- * Cuts the currently selected objects (copy then delete).
685
- * Objects are copied to clipboard and then removed from the canvas.
686
- */
687
- cut() {
688
- const selectionGroup = this._store.selectionGroup;
689
- if (!selectionGroup) {
690
- return;
691
- }
692
- // Copy first, then delete
693
- this.copy();
694
- this.delete();
695
- }
696
- /**
697
- * Pastes objects from the clipboard to the canvas.
698
- * Handles positioning (explicit, offset, or centered for cross-workspace paste),
699
- * workspace assignment, z-index ordering, group children, and anchor remapping.
700
- * Creates a selection group containing the pasted objects.
701
- * @param x - Optional explicit X position for paste
702
- * @param y - Optional explicit Y position for paste
703
- */
704
- paste(x, y) {
705
- const copiedObjects = this._store.state.copiedObjects;
706
- if (!copiedObjects || copiedObjects.length === 0) {
707
- return;
708
- }
709
- const activeWorkspace = this._store.state.activeWorkspace;
710
- const originalIdMapping = this._store.state.copiedObjectIdMapping;
711
- // Check if we're pasting from a different workspace
712
- const isDifferentWorkspace = copiedObjects.some(obj => obj.workspaceId !== activeWorkspace.id);
713
- // Calculate the bounding box of all copied objects
714
- const minX = Math.min(...copiedObjects.map(obj => obj.translateX));
715
- const minY = Math.min(...copiedObjects.map(obj => obj.translateY));
716
- // Determine the paste position
717
- let pasteX;
718
- let pasteY;
719
- if (x !== undefined && y !== undefined) {
720
- // Explicit position provided (e.g., right-click paste)
721
- pasteX = x;
722
- pasteY = y;
723
- }
724
- else if (isDifferentWorkspace) {
725
- // Pasting to different workspace without explicit position
726
- // Keep original position; will be centered by centerInViewport() later
727
- pasteX = minX;
728
- pasteY = minY;
729
- }
730
- else {
731
- // Same workspace paste: offset by 25 pixels from original position
732
- pasteX = minX + 25 / this._store.state.scale;
733
- pasteY = minY + 25 / this._store.state.scale;
734
- }
735
- // Calculate the offset to apply to all objects
736
- const offsetX = pasteX - minX;
737
- const offsetY = pasteY - minY;
738
- this.removeSelectionGroup();
739
- this.removeSelectionBox();
740
- // Create a new selection group for the pasted objects
741
- const selectionGroup = KritzelSelectionGroup.create(this);
742
- // Build a reverse mapping: originalId -> newCopiedObjectId
743
- // This is used to remap anchor references from original IDs to the copied object IDs
744
- const originalToNewIdMap = new Map();
745
- if (originalIdMapping) {
746
- originalIdMapping.forEach((originalId, copiedId) => {
747
- originalToNewIdMap.set(originalId, copiedId);
748
- });
749
- }
750
- // Precompute zIndex once to avoid O(N * total_objects) repeated scans
751
- const baseZIndex = this._store.currentZIndex;
752
- // Batch all inserts and updates in a single Y.js transaction to avoid
753
- // N separate observer callbacks and N rerenders (fires only once at commit)
754
- this._store.objects.transaction(() => {
755
- // Recursively flush a group's pending children (and any nested groups'
756
- // pending children) into the store. Without recursion, nested groups
757
- // would have their grandchildren stranded in `_pendingChildren` and they
758
- // would never appear on the canvas, even though the parent group's
759
- // bounding box looks correct.
760
- const flushPendingChildren = (group, parentZIndex) => {
761
- if (group._pendingChildren.length === 0) {
762
- return;
763
- }
764
- const pendingChildren = group._pendingChildren;
765
- group._pendingChildren = [];
766
- pendingChildren.forEach((child, childIndex) => {
767
- if (child.workspaceId !== activeWorkspace.id) {
768
- child.workspaceId = activeWorkspace.id;
769
- }
770
- child.updatePosition(child.translateX + offsetX, child.translateY + offsetY);
771
- child.zIndex = parentZIndex + childIndex + 1;
772
- // Recurse before adding so grandchildren are inserted as well
773
- if (KritzelClassHelper.isInstanceOf(child, 'KritzelGroup')) {
774
- flushPendingChildren(child, child.zIndex);
775
- }
776
- this.addObject(child);
777
- });
778
- group.finalize();
779
- };
780
- const newlyAddedObjects = [];
781
- // First add all copied objects to the objectsMap with updated positions
782
- copiedObjects.forEach((obj, i) => {
783
- // Update workspace if pasting to a different workspace
784
- if (obj.workspaceId !== activeWorkspace.id) {
785
- obj.workspaceId = activeWorkspace.id;
786
- }
787
- // Update position
788
- obj.updatePosition(obj.translateX + offsetX, obj.translateY + offsetY);
789
- // Update z-index
790
- obj.zIndex = baseZIndex + i;
791
- // Handle KritzelGroup: also add pending children (and grandchildren) with offset
792
- if (KritzelClassHelper.isInstanceOf(obj, 'KritzelGroup')) {
793
- flushPendingChildren(obj, obj.zIndex);
794
- }
795
- // Add to objectsMap
796
- this.addObject(obj);
797
- newlyAddedObjects.push(obj);
798
- // Add to selection group
799
- selectionGroup.addOrRemove(obj);
800
- });
801
- this.engine.emitObjectsAdded(newlyAddedObjects);
802
- // Update line anchors to point to the newly pasted objects
803
- // Only remap if the anchor target was also part of the copied selection
804
- copiedObjects.forEach(obj => {
805
- if (KritzelClassHelper.isInstanceOf(obj, 'KritzelLine')) {
806
- let updated = false;
807
- if (obj.startAnchor && originalToNewIdMap.has(obj.startAnchor.objectId)) {
808
- obj.startAnchor = { objectId: originalToNewIdMap.get(obj.startAnchor.objectId) };
809
- updated = true;
810
- }
811
- if (obj.endAnchor && originalToNewIdMap.has(obj.endAnchor.objectId)) {
812
- obj.endAnchor = { objectId: originalToNewIdMap.get(obj.endAnchor.objectId) };
813
- updated = true;
814
- }
815
- // If anchors were updated, rebuild the anchor index and persist the change
816
- if (updated) {
817
- this._store.objects.update(obj);
818
- }
819
- }
820
- });
821
- });
822
- // Rebuild anchor index after all anchor updates
823
- this._anchorManager.rebuildIndex();
824
- // Mark selection group as selected
825
- selectionGroup.isSelected = true;
826
- // Set rotation for single object
827
- if (copiedObjects.length === 1) {
828
- selectionGroup.rotation = copiedObjects[0].rotation;
829
- }
830
- // For cross-workspace paste, set the selection group's position before inserting
831
- // it into the store. This mirrors the existing pattern where objects have
832
- // updatePosition called before addObject, so the correct position is captured
833
- // in the single initial insert with no separate Yjs update (and no extra undo step).
834
- if (isDifferentWorkspace) {
835
- if (x !== undefined && y !== undefined) {
836
- // Explicit cursor position provided (e.g. right-click paste)
837
- selectionGroup.updatePosition(x, y);
838
- }
839
- else {
840
- // Ctrl+V paste: center objects in the current viewport
841
- selectionGroup.centerInViewport();
842
- }
843
- }
844
- // Add the selection group (inserts with the already-adjusted position)
845
- this.addSelectionGroup(selectionGroup);
846
- // Bring pasted objects into view with a smooth animation if they are not
847
- // fully visible. These only mutate in-memory viewport state (scale, translateX/Y)
848
- // and do not produce any Yjs writes, so they have no impact on the undo history.
849
- this.engine.viewport.bringIntoViewIfNeeded(selectionGroup);
850
- this._store.state.isSelecting = false;
851
- // Defer creating copies for future pastes to next frame to avoid
852
- // doubling the object creation cost in the current paste frame
853
- requestAnimationFrame(() => {
854
- const newSelectionGroup = this._store.selectionGroup;
855
- if (newSelectionGroup) {
856
- const newIdMapping = new Map();
857
- const newCopiedObjects = newSelectionGroup.objects
858
- .sort((a, b) => a.zIndex - b.zIndex)
859
- .map(obj => {
860
- const copiedObj = obj.copy();
861
- newIdMapping.set(copiedObj.id, obj.id);
862
- return copiedObj;
863
- });
864
- this._store.state.copiedObjects = newCopiedObjects;
865
- this._store.state.copiedObjectIdMapping = newIdMapping;
866
- }
867
- });
868
- this._store.setState('activeTool', this._toolRegistry.getTool('selection'));
869
- this.engine.emitObjectsChange();
870
- this.rerender();
871
- }
872
684
  /**
873
685
  * Moves objects one step forward in the z-order stack.
874
686
  * @param object - Optional specific object to move; defaults to all selected objects
@@ -3,7 +3,7 @@ import { KritzelBaseHandler } from "./base.handler";
3
3
  const NUDGE_AMOUNT = 10;
4
4
  /**
5
5
  * Handles keyboard events and manages keyboard shortcuts for the Kritzel canvas.
6
- * Provides functionality for tool switching, clipboard operations, object manipulation,
6
+ * Provides functionality for tool switching, object manipulation,
7
7
  * undo/redo, and navigation via keyboard input.
8
8
  */
9
9
  export class KritzelKeyHandler extends KritzelBaseHandler {
@@ -46,27 +46,6 @@ export class KritzelKeyHandler extends KritzelBaseHandler {
46
46
  { key: '7', ctrl: true, label: 'Tool 7', category: 'Tool Selection', action: () => this.switchToolByIndex(6) },
47
47
  { key: '8', ctrl: true, label: 'Tool 8', category: 'Tool Selection', action: () => this.switchToolByIndex(7) },
48
48
  { key: '9', ctrl: true, label: 'Tool 9', category: 'Tool Selection', action: () => this.switchToolByIndex(8) },
49
- // Clipboard
50
- {
51
- key: 'c',
52
- ctrl: true,
53
- label: 'Copy',
54
- category: 'Clipboard',
55
- condition: c => !!c.store.selectionGroup,
56
- action: c => {
57
- c.copy();
58
- c.rerender();
59
- },
60
- },
61
- { key: 'x', ctrl: true, label: 'Cut', category: 'Clipboard', condition: c => !!c.store.selectionGroup, action: c => c.cut() },
62
- {
63
- key: 'v',
64
- ctrl: true,
65
- label: 'Paste',
66
- category: 'Clipboard',
67
- condition: c => !!c.store.state.copiedObjects && !c.store.activeText && !c.store.activeShape,
68
- action: c => c.paste(),
69
- },
70
49
  // Object layering
71
50
  { key: '+', ctrl: true, label: 'Bring Forward', category: 'Object Layering', condition: c => !!c.store.selectionGroup, action: c => c.bringForward() },
72
51
  { key: '-', ctrl: true, label: 'Send Backward', category: 'Object Layering', condition: c => !!c.store.selectionGroup, action: c => c.sendBackward() },
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Manages browser permission checks and prompts for a core instance.
3
+ *
4
+ * Permission-specific prompt logic is routed through a handler registry so new
5
+ * permissions can be added without changing the core control flow.
6
+ */
7
+ export class KritzelBrowserPermissionManager {
8
+ _isFirefox = /firefox|fxios/i.test(navigator.userAgent);
9
+ _isSafari = /safari/i.test(navigator.userAgent) && !/chrome|chromium|crios|android|edg/i.test(navigator.userAgent);
10
+ _requestHandlers = new Map();
11
+ /**
12
+ * Creates a new KritzelBrowserPermissionManager instance.
13
+ * @param core - The KritzelCore instance this manager belongs to
14
+ */
15
+ constructor(_core) {
16
+ this.registerRequestPermissionHandler('clipboard-read', async () => {
17
+ // Firefox and Safari open native paste menus when readText() is called
18
+ // proactively, so rely on the permission state instead of an eager read.
19
+ if (this._isFirefox || this._isSafari) {
20
+ return 'granted';
21
+ }
22
+ try {
23
+ await navigator.clipboard.readText();
24
+ return 'granted';
25
+ }
26
+ catch {
27
+ return 'denied';
28
+ }
29
+ });
30
+ }
31
+ /**
32
+ * Registers a custom request handler for a browser permission name.
33
+ * Existing handlers are replaced, which makes the registry easy to extend.
34
+ *
35
+ * @param permissionName - The browser permission name to handle
36
+ * @param handler - The request implementation for that permission
37
+ */
38
+ registerRequestPermissionHandler(permissionName, handler) {
39
+ this._requestHandlers.set(permissionName, handler);
40
+ }
41
+ /**
42
+ * Checks the current state for a browser permission via the Permissions API.
43
+ *
44
+ * @param permissionName - The browser permission name to inspect
45
+ */
46
+ async checkPermission(permissionName) {
47
+ const status = await navigator.permissions.query({ name: permissionName });
48
+ return status.state;
49
+ }
50
+ /**
51
+ * Requests a browser permission using the registered handler for the given
52
+ * permission name, then falls back to a permission check when no handler is
53
+ * registered.
54
+ *
55
+ * @param permissionName - The browser permission name to request
56
+ */
57
+ async requestPermission(permissionName) {
58
+ const handler = this._requestHandlers.get(permissionName);
59
+ if (handler) {
60
+ return handler();
61
+ }
62
+ return this.checkPermission(permissionName);
63
+ }
64
+ }