kritzel-stencil 0.2.11 → 0.2.13

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 (34) hide show
  1. package/dist/cjs/kritzel-active-users_42.cjs.entry.js +94 -21
  2. package/dist/collection/classes/core/core.class.js +12 -7
  3. package/dist/collection/classes/core/viewport.class.js +65 -0
  4. package/dist/collection/classes/handlers/context-menu.handler.js +7 -6
  5. package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +4 -4
  6. package/dist/collection/components/shared/kritzel-menu/kritzel-menu.js +1 -1
  7. package/dist/collection/components/ui/kritzel-more-menu/kritzel-more-menu.js +1 -1
  8. package/dist/collection/components/ui/kritzel-workspace-manager/kritzel-workspace-manager.js +1 -1
  9. package/dist/collection/configs/default-engine-config.js +2 -0
  10. package/dist/collection/constants/version.js +1 -1
  11. package/dist/components/index.js +1 -1
  12. package/dist/components/kritzel-editor.js +1 -1
  13. package/dist/components/kritzel-engine.js +1 -1
  14. package/dist/components/kritzel-menu-item.js +1 -1
  15. package/dist/components/kritzel-menu.js +1 -1
  16. package/dist/components/kritzel-more-menu.js +1 -1
  17. package/dist/components/kritzel-settings.js +1 -1
  18. package/dist/components/kritzel-split-button.js +1 -1
  19. package/dist/components/kritzel-workspace-manager.js +1 -1
  20. package/dist/components/{p-B_fA1LTU.js → p-BWB2UGxj.js} +1 -1
  21. package/dist/components/p-CYMDh3Vm.js +1 -0
  22. package/dist/components/{p-DQ1fRE9J.js → p-CvCTQQcJ.js} +1 -1
  23. package/dist/components/{p-BpRNLd4z.js → p-D-ggocUt.js} +1 -1
  24. package/dist/components/{p-CRsnBR8O.js → p-D9BJCr8V.js} +1 -1
  25. package/dist/components/{p-DqJ9KC24.js → p-DItqr_lo.js} +1 -1
  26. package/dist/esm/kritzel-active-users_42.entry.js +94 -21
  27. package/dist/stencil/p-fa1d2546.entry.js +9 -0
  28. package/dist/stencil/stencil.esm.js +1 -1
  29. package/dist/types/classes/core/viewport.class.d.ts +22 -0
  30. package/dist/types/constants/version.d.ts +1 -1
  31. package/dist/types/interfaces/engine-state.interface.d.ts +2 -0
  32. package/package.json +1 -1
  33. package/dist/components/p-xHh03blG.js +0 -1
  34. package/dist/stencil/p-836d973c.entry.js +0 -9
@@ -20569,6 +20569,71 @@ class KritzelViewport {
20569
20569
  this._core.store.state.scale = clampedScale;
20570
20570
  this.centerInViewport(object);
20571
20571
  }
20572
+ /**
20573
+ * Centers the viewport on a given object and zooms out only if the object
20574
+ * does not fit at the current scale. Never zooms in — if the object already
20575
+ * fits, the scale is left unchanged.
20576
+ * @param object - The object to fit in the viewport
20577
+ */
20578
+ centerFitInViewportIfNeeded(object) {
20579
+ const scaleX = this._core.store.state.viewportWidth / (object.rotatedBoundingBox.width * 1.1);
20580
+ const scaleY = this._core.store.state.viewportHeight / (object.rotatedBoundingBox.height * 1.1);
20581
+ const newScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
20582
+ const clampedScale = Math.max(newScale, this.getEffectiveMinScale());
20583
+ if (clampedScale < this._core.store.state.scale) {
20584
+ this._core.store.state.scale = clampedScale;
20585
+ }
20586
+ this.centerInViewport(object);
20587
+ }
20588
+ /**
20589
+ * Zooms out and centers the viewport on a given object only if the object
20590
+ * does not fit at the current scale. If the object already fits, the
20591
+ * viewport is left completely unchanged.
20592
+ * @param object - The object to check and fit in the viewport
20593
+ */
20594
+ fitInViewportIfNeeded(object) {
20595
+ const scaleX = this._core.store.state.viewportWidth / (object.rotatedBoundingBox.width * 1.1);
20596
+ const scaleY = this._core.store.state.viewportHeight / (object.rotatedBoundingBox.height * 1.1);
20597
+ const newScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
20598
+ const clampedScale = Math.max(newScale, this.getEffectiveMinScale());
20599
+ if (clampedScale < this._core.store.state.scale) {
20600
+ this._core.store.state.scale = clampedScale;
20601
+ this.centerInViewport(object);
20602
+ }
20603
+ }
20604
+ /**
20605
+ * Smoothly brings a given object into view if it is not fully visible within
20606
+ * the current viewport. Zooms out only if the object is too large to fit at
20607
+ * the current scale; never zooms in. If the object is already fully visible,
20608
+ * the viewport is left unchanged.
20609
+ * @param object - The object to bring into view
20610
+ */
20611
+ bringIntoViewIfNeeded(object) {
20612
+ const { scale, translateX, translateY, viewportWidth, viewportHeight } = this._core.store.state;
20613
+ const bounds = object.rotatedBoundingBox;
20614
+ // Compute the object's bounding box in screen-space coordinates
20615
+ const screenLeft = bounds.x * scale + translateX;
20616
+ const screenTop = bounds.y * scale + translateY;
20617
+ const screenRight = (bounds.x + bounds.width) * scale + translateX;
20618
+ const screenBottom = (bounds.y + bounds.height) * scale + translateY;
20619
+ // If fully visible, no adjustment needed
20620
+ if (screenLeft >= 0 && screenTop >= 0 && screenRight <= viewportWidth && screenBottom <= viewportHeight) {
20621
+ return;
20622
+ }
20623
+ // Calculate the scale needed to fit the object with padding; never zoom in
20624
+ const scaleX = viewportWidth / (bounds.width * 1.1);
20625
+ const scaleY = viewportHeight / (bounds.height * 1.1);
20626
+ const fitScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
20627
+ const clampedFitScale = Math.max(fitScale, this.getEffectiveMinScale());
20628
+ const targetScale = Math.min(clampedFitScale, scale);
20629
+ // Animate to center the object at the target scale
20630
+ const objectCenterX = bounds.x + bounds.width / 2;
20631
+ const objectCenterY = bounds.y + bounds.height / 2;
20632
+ const targetTranslateX = viewportWidth / 2 - objectCenterX * targetScale;
20633
+ const targetTranslateY = viewportHeight / 2 - objectCenterY * targetScale;
20634
+ const clamped = this.clampTranslate(targetTranslateX, targetTranslateY);
20635
+ this.animateViewportTo(clamped.translateX, clamped.translateY, targetScale);
20636
+ }
20572
20637
  /**
20573
20638
  * Handles zoom operations triggered by wheel events with Ctrl key.
20574
20639
  * Zooms around the cursor position, respecting scale limits.
@@ -21019,17 +21084,18 @@ class KritzelContextMenuHandler extends workspace_migrations.KritzelBaseHandler
21019
21084
  this._core.rerender();
21020
21085
  }
21021
21086
  this._core.store.state.contextMenuItems = this._core.store.selectionGroup ? this.objectContextMenuItems : this.globalContextMenuItems;
21022
- let x = event.clientX - this._core.store.offsetX;
21023
- let y = event.clientY - this._core.store.offsetY;
21087
+ const clickX = event.clientX - this._core.store.offsetX;
21088
+ const clickY = event.clientY - this._core.store.offsetY;
21089
+ const { translateX, translateY, scale } = this._core.store.state;
21090
+ this._core.store.state.contextMenuWorldX = (clickX - translateX) / scale;
21091
+ this._core.store.state.contextMenuWorldY = (clickY - translateY) / scale;
21092
+ let x = clickX;
21093
+ let y = clickY;
21024
21094
  const menuWidthEstimate = 150;
21025
- const menuHeightEstimate = 200;
21026
21095
  const margin = 10;
21027
21096
  if (x + menuWidthEstimate > window.innerWidth - margin) {
21028
21097
  x = window.innerWidth - menuWidthEstimate - margin;
21029
21098
  }
21030
- if (y + menuHeightEstimate > window.innerHeight - margin) {
21031
- y = window.innerHeight - menuHeightEstimate - margin;
21032
- }
21033
21099
  x = Math.max(margin, x);
21034
21100
  y = Math.max(margin, y);
21035
21101
  this._core.store.state.contextMenuX = x;
@@ -21295,6 +21361,8 @@ const DEFAULT_ENGINE_CONFIG = {
21295
21361
  contextMenuItems: [],
21296
21362
  contextMenuX: 0,
21297
21363
  contextMenuY: 0,
21364
+ contextMenuWorldX: 0,
21365
+ contextMenuWorldY: 0,
21298
21366
  skipContextMenu: false,
21299
21367
  debugInfo: {
21300
21368
  showObjectInfo: false,
@@ -24302,21 +24370,26 @@ class KritzelCore {
24302
24370
  if (copiedObjects.length === 1) {
24303
24371
  selectionGroup.rotation = copiedObjects[0].rotation;
24304
24372
  }
24305
- // Add the selection group
24306
- this.addSelectionGroup(selectionGroup);
24307
- // Handle cross-workspace paste: center objects in viewport
24373
+ // For cross-workspace paste, set the selection group's position before inserting
24374
+ // it into the store. This mirrors the existing pattern where objects have
24375
+ // updatePosition called before addObject, so the correct position is captured
24376
+ // in the single initial insert with no separate Yjs update (and no extra undo step).
24308
24377
  if (isDifferentWorkspace) {
24309
24378
  if (x !== undefined && y !== undefined) {
24310
- // Position was explicitly provided, use it
24379
+ // Explicit cursor position provided (e.g. right-click paste)
24311
24380
  selectionGroup.updatePosition(x, y);
24312
24381
  }
24313
24382
  else {
24314
- // Center the selection group in the viewport
24383
+ // Ctrl+V paste: center objects in the current viewport
24315
24384
  selectionGroup.centerInViewport();
24316
24385
  }
24317
- // Fit the viewport to show the pasted objects
24318
- this.engine.viewport.centerFitInViewport(selectionGroup);
24319
24386
  }
24387
+ // Add the selection group (inserts with the already-adjusted position)
24388
+ this.addSelectionGroup(selectionGroup);
24389
+ // Bring pasted objects into view with a smooth animation if they are not
24390
+ // fully visible. These only mutate in-memory viewport state (scale, translateX/Y)
24391
+ // and do not produce any Yjs writes, so they have no impact on the undo history.
24392
+ this.engine.viewport.bringIntoViewIfNeeded(selectionGroup);
24320
24393
  this._store.state.isSelecting = false;
24321
24394
  // Defer creating copies for future pastes to next frame to avoid
24322
24395
  // doubling the object creation cost in the current paste frame
@@ -26381,9 +26454,9 @@ const KritzelEngine = class {
26381
26454
  if (isInsideDialog) {
26382
26455
  return false;
26383
26456
  }
26384
- const target = ev.target;
26457
+ const target = path[0];
26385
26458
  // Don't handle if target is an interactive element that needs keyboard input
26386
- if (target.matches?.('input, textarea, select, [contenteditable="true"], [contenteditable=""]')) {
26459
+ if (target instanceof HTMLElement && target.matches?.('input, textarea, select, [contenteditable="true"], [contenteditable=""]')) {
26387
26460
  return false;
26388
26461
  }
26389
26462
  // Handle global shortcuts when no interactive element has focus
@@ -28061,8 +28134,8 @@ const KritzelEngine = class {
28061
28134
  }, onActionSelected: event => {
28062
28135
  if (event.detail.action) {
28063
28136
  event.detail.action({
28064
- x: (-this.core.store.state.translateX + this.core.store.state.contextMenuX) / this.core.store.state.scale,
28065
- y: (-this.core.store.state.translateY + this.core.store.state.contextMenuY) / this.core.store.state.scale,
28137
+ x: this.core.store.state.contextMenuWorldX,
28138
+ y: this.core.store.state.contextMenuWorldY,
28066
28139
  }, this.core.store.selectionGroup?.objects || []);
28067
28140
  }
28068
28141
  this.hideContextMenu();
@@ -28679,7 +28752,7 @@ const KritzelMenu = class {
28679
28752
  this.itemCloseChildMenu.emit(event.detail);
28680
28753
  };
28681
28754
  render() {
28682
- return (index.h(index.Host, { key: 'a81ea8a1fe2dc6cb8d9f395cafbcadec3eb4aa45', tabIndex: 0, onClick: e => e.stopPropagation() }, this.openChildMenuItem && index.h("div", { key: 'bb27d7a923431d79567e79283e505ea4ae02ef36', class: "has-open-child-overlay", onClick: this.onOverlayClick }), this.items.map(item => (index.h("kritzel-menu-item", { key: item.id, item: item, parent: this.parent, style: { pointerEvents: this.editingMenuItem && !item.isEditing ? 'none' : 'auto' }, onItemSelect: this.handleItemSelect, onItemSave: this.handleSave, onItemCancel: this.handleCancel, onItemToggleChildMenu: this.handleToggleChildMenu, onItemCloseChildMenu: this.handleCloseChildMenu })))));
28755
+ return (index.h(index.Host, { key: 'a81ea8a1fe2dc6cb8d9f395cafbcadec3eb4aa45', tabIndex: 0, onClick: e => e.stopPropagation() }, this.openChildMenuItem && index.h("div", { key: 'bb27d7a923431d79567e79283e505ea4ae02ef36', class: "has-open-child-overlay", onClick: this.onOverlayClick }), this.items.map(item => (index.h("kritzel-menu-item", { key: item.id, "data-testid": `menu-item-${item.id}`, item: item, parent: this.parent, style: { pointerEvents: this.editingMenuItem && !item.isEditing ? 'none' : 'auto' }, onItemSelect: this.handleItemSelect, onItemSave: this.handleSave, onItemCancel: this.handleCancel, onItemToggleChildMenu: this.handleToggleChildMenu, onItemCloseChildMenu: this.handleCloseChildMenu })))));
28683
28756
  }
28684
28757
  };
28685
28758
  KritzelMenu.style = kritzelMenuCss();
@@ -28860,7 +28933,7 @@ const KritzelMoreMenu = class {
28860
28933
  this.closeMenu();
28861
28934
  };
28862
28935
  render() {
28863
- return (index.h(index.Host, { key: '0e12ffc8c72566ec92080e6a19bd1d929795bef9', class: { mobile: this.isTouchDevice } }, index.h("div", { key: 'cc73b51c5aa39522a7ab7ec23d5c0a2732ed7acc', class: { 'more-menu-wrapper': true, visible: this.visible } }, index.h("button", { key: 'c35b8c7aa56e5e0e2773fed9fbbbead0b6b01a71', class: "more-menu-button", onClick: this.toggleMenu }, index.h("kritzel-icon", { key: '8b3261da5a10371a17b2562b71fde48dd0ba8ccd', name: this.icon, size: this.iconSize })), index.h("kritzel-portal", { key: 'be4a42061f27bbca3d435dec8e4dd25fc78febb0', anchor: this.menuAnchor, offsetY: this.offsetY, onClose: this.closeMenu }, index.h("kritzel-menu", { key: '763043f4d02819097396ce1baa85f398695e38b5', items: this.visibleItems, onItemSelect: this.handleMenuItemSelect })))));
28936
+ return (index.h(index.Host, { key: '33d85e3b5ad51effdf2f61c8742dbe829ef43f15', class: { mobile: this.isTouchDevice }, style: { display: this.visible ? '' : 'none' } }, index.h("div", { key: '917ca25a14294f44a0428431a3ec08a84db2aff0', class: { 'more-menu-wrapper': true, visible: this.visible } }, index.h("button", { key: '6977d9efac27011c579d5452ebc14c99644c313b', class: "more-menu-button", "data-testid": "more-menu-button", onClick: this.toggleMenu }, index.h("kritzel-icon", { key: 'ff8af735c75d298063119d454331744985587f9c', name: this.icon, size: this.iconSize })), index.h("kritzel-portal", { key: '329670fe1700f3ae9359972e69882bf770f2f706', anchor: this.menuAnchor, offsetY: this.offsetY, onClose: this.closeMenu }, index.h("kritzel-menu", { key: 'ec6cc8df73c3c0239eb1b2c5ca8c6d79e51d124c', items: this.visibleItems, onItemSelect: this.handleMenuItemSelect })))));
28864
28937
  }
28865
28938
  };
28866
28939
  KritzelMoreMenu.style = kritzelMoreMenuCss();
@@ -29363,7 +29436,7 @@ const KritzelPortal = class {
29363
29436
  * This file is auto-generated by the version bump scripts.
29364
29437
  * Do not modify manually.
29365
29438
  */
29366
- const KRITZEL_VERSION = '0.2.11';
29439
+ const KRITZEL_VERSION = '0.2.13';
29367
29440
 
29368
29441
  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)}`;
29369
29442
 
@@ -30379,7 +30452,7 @@ const KritzelWorkspaceManager = class {
30379
30452
  ],
30380
30453
  };
30381
30454
  });
30382
- return (index.h(index.Host, null, index.h("div", { class: { manager: true, visible: this.visible } }, index.h("kritzel-split-button", { ref: el => (this.splitButtonRef = el), items: menuItems, mainButtonDisabled: this.editingItemId != null, onMainButtonClick: () => this.add(), onItemSelect: event => this.select(event.detail.item, event.detail.parent), onItemToggleChildMenu: event => this.toggleChildMenu(event.detail.item, event.detail.childMenuAnchor), onItemSave: event => this.save(event.detail), onItemCancel: () => this.cancel(), onItemCloseChildMenu: () => this.closeChildMenu(), onMenuOpen: () => this.handleMenuOpen(), onMenuClose: () => this.handleMenuClose() }))));
30455
+ return (index.h(index.Host, { style: { display: this.visible ? '' : 'none' } }, index.h("div", { class: { manager: true, visible: this.visible } }, index.h("kritzel-split-button", { ref: el => (this.splitButtonRef = el), items: menuItems, mainButtonDisabled: this.editingItemId != null, onMainButtonClick: () => this.add(), onItemSelect: event => this.select(event.detail.item, event.detail.parent), onItemToggleChildMenu: event => this.toggleChildMenu(event.detail.item, event.detail.childMenuAnchor), onItemSave: event => this.save(event.detail), onItemCancel: () => this.cancel(), onItemCloseChildMenu: () => this.closeChildMenu(), onMenuOpen: () => this.handleMenuOpen(), onMenuClose: () => this.handleMenuClose() }))));
30383
30456
  }
30384
30457
  };
30385
30458
  KritzelWorkspaceManager.style = kritzelWorkspaceManagerCss();
@@ -789,21 +789,26 @@ export class KritzelCore {
789
789
  if (copiedObjects.length === 1) {
790
790
  selectionGroup.rotation = copiedObjects[0].rotation;
791
791
  }
792
- // Add the selection group
793
- this.addSelectionGroup(selectionGroup);
794
- // Handle cross-workspace paste: center objects in viewport
792
+ // For cross-workspace paste, set the selection group's position before inserting
793
+ // it into the store. This mirrors the existing pattern where objects have
794
+ // updatePosition called before addObject, so the correct position is captured
795
+ // in the single initial insert with no separate Yjs update (and no extra undo step).
795
796
  if (isDifferentWorkspace) {
796
797
  if (x !== undefined && y !== undefined) {
797
- // Position was explicitly provided, use it
798
+ // Explicit cursor position provided (e.g. right-click paste)
798
799
  selectionGroup.updatePosition(x, y);
799
800
  }
800
801
  else {
801
- // Center the selection group in the viewport
802
+ // Ctrl+V paste: center objects in the current viewport
802
803
  selectionGroup.centerInViewport();
803
804
  }
804
- // Fit the viewport to show the pasted objects
805
- this.engine.viewport.centerFitInViewport(selectionGroup);
806
805
  }
806
+ // Add the selection group (inserts with the already-adjusted position)
807
+ this.addSelectionGroup(selectionGroup);
808
+ // Bring pasted objects into view with a smooth animation if they are not
809
+ // fully visible. These only mutate in-memory viewport state (scale, translateX/Y)
810
+ // and do not produce any Yjs writes, so they have no impact on the undo history.
811
+ this.engine.viewport.bringIntoViewIfNeeded(selectionGroup);
807
812
  this._store.state.isSelecting = false;
808
813
  // Defer creating copies for future pastes to next frame to avoid
809
814
  // doubling the object creation cost in the current paste frame
@@ -429,6 +429,71 @@ export class KritzelViewport {
429
429
  this._core.store.state.scale = clampedScale;
430
430
  this.centerInViewport(object);
431
431
  }
432
+ /**
433
+ * Centers the viewport on a given object and zooms out only if the object
434
+ * does not fit at the current scale. Never zooms in — if the object already
435
+ * fits, the scale is left unchanged.
436
+ * @param object - The object to fit in the viewport
437
+ */
438
+ centerFitInViewportIfNeeded(object) {
439
+ const scaleX = this._core.store.state.viewportWidth / (object.rotatedBoundingBox.width * 1.1);
440
+ const scaleY = this._core.store.state.viewportHeight / (object.rotatedBoundingBox.height * 1.1);
441
+ const newScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
442
+ const clampedScale = Math.max(newScale, this.getEffectiveMinScale());
443
+ if (clampedScale < this._core.store.state.scale) {
444
+ this._core.store.state.scale = clampedScale;
445
+ }
446
+ this.centerInViewport(object);
447
+ }
448
+ /**
449
+ * Zooms out and centers the viewport on a given object only if the object
450
+ * does not fit at the current scale. If the object already fits, the
451
+ * viewport is left completely unchanged.
452
+ * @param object - The object to check and fit in the viewport
453
+ */
454
+ fitInViewportIfNeeded(object) {
455
+ const scaleX = this._core.store.state.viewportWidth / (object.rotatedBoundingBox.width * 1.1);
456
+ const scaleY = this._core.store.state.viewportHeight / (object.rotatedBoundingBox.height * 1.1);
457
+ const newScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
458
+ const clampedScale = Math.max(newScale, this.getEffectiveMinScale());
459
+ if (clampedScale < this._core.store.state.scale) {
460
+ this._core.store.state.scale = clampedScale;
461
+ this.centerInViewport(object);
462
+ }
463
+ }
464
+ /**
465
+ * Smoothly brings a given object into view if it is not fully visible within
466
+ * the current viewport. Zooms out only if the object is too large to fit at
467
+ * the current scale; never zooms in. If the object is already fully visible,
468
+ * the viewport is left unchanged.
469
+ * @param object - The object to bring into view
470
+ */
471
+ bringIntoViewIfNeeded(object) {
472
+ const { scale, translateX, translateY, viewportWidth, viewportHeight } = this._core.store.state;
473
+ const bounds = object.rotatedBoundingBox;
474
+ // Compute the object's bounding box in screen-space coordinates
475
+ const screenLeft = bounds.x * scale + translateX;
476
+ const screenTop = bounds.y * scale + translateY;
477
+ const screenRight = (bounds.x + bounds.width) * scale + translateX;
478
+ const screenBottom = (bounds.y + bounds.height) * scale + translateY;
479
+ // If fully visible, no adjustment needed
480
+ if (screenLeft >= 0 && screenTop >= 0 && screenRight <= viewportWidth && screenBottom <= viewportHeight) {
481
+ return;
482
+ }
483
+ // Calculate the scale needed to fit the object with padding; never zoom in
484
+ const scaleX = viewportWidth / (bounds.width * 1.1);
485
+ const scaleY = viewportHeight / (bounds.height * 1.1);
486
+ const fitScale = Math.min(scaleX, scaleY, this._core.store.state.scaleMax);
487
+ const clampedFitScale = Math.max(fitScale, this.getEffectiveMinScale());
488
+ const targetScale = Math.min(clampedFitScale, scale);
489
+ // Animate to center the object at the target scale
490
+ const objectCenterX = bounds.x + bounds.width / 2;
491
+ const objectCenterY = bounds.y + bounds.height / 2;
492
+ const targetTranslateX = viewportWidth / 2 - objectCenterX * targetScale;
493
+ const targetTranslateY = viewportHeight / 2 - objectCenterY * targetScale;
494
+ const clamped = this.clampTranslate(targetTranslateX, targetTranslateY);
495
+ this.animateViewportTo(clamped.translateX, clamped.translateY, targetScale);
496
+ }
432
497
  /**
433
498
  * Handles zoom operations triggered by wheel events with Ctrl key.
434
499
  * Zooms around the cursor position, respecting scale limits.
@@ -72,17 +72,18 @@ export class KritzelContextMenuHandler extends KritzelBaseHandler {
72
72
  this._core.rerender();
73
73
  }
74
74
  this._core.store.state.contextMenuItems = this._core.store.selectionGroup ? this.objectContextMenuItems : this.globalContextMenuItems;
75
- let x = event.clientX - this._core.store.offsetX;
76
- let y = event.clientY - this._core.store.offsetY;
75
+ const clickX = event.clientX - this._core.store.offsetX;
76
+ const clickY = event.clientY - this._core.store.offsetY;
77
+ const { translateX, translateY, scale } = this._core.store.state;
78
+ this._core.store.state.contextMenuWorldX = (clickX - translateX) / scale;
79
+ this._core.store.state.contextMenuWorldY = (clickY - translateY) / scale;
80
+ let x = clickX;
81
+ let y = clickY;
77
82
  const menuWidthEstimate = 150;
78
- const menuHeightEstimate = 200;
79
83
  const margin = 10;
80
84
  if (x + menuWidthEstimate > window.innerWidth - margin) {
81
85
  x = window.innerWidth - menuWidthEstimate - margin;
82
86
  }
83
- if (y + menuHeightEstimate > window.innerHeight - margin) {
84
- y = window.innerHeight - menuHeightEstimate - margin;
85
- }
86
87
  x = Math.max(margin, x);
87
88
  y = Math.max(margin, y);
88
89
  this._core.store.state.contextMenuX = x;
@@ -341,9 +341,9 @@ export class KritzelEngine {
341
341
  if (isInsideDialog) {
342
342
  return false;
343
343
  }
344
- const target = ev.target;
344
+ const target = path[0];
345
345
  // Don't handle if target is an interactive element that needs keyboard input
346
- if (target.matches?.('input, textarea, select, [contenteditable="true"], [contenteditable=""]')) {
346
+ if (target instanceof HTMLElement && target.matches?.('input, textarea, select, [contenteditable="true"], [contenteditable=""]')) {
347
347
  return false;
348
348
  }
349
349
  // Handle global shortcuts when no interactive element has focus
@@ -2006,8 +2006,8 @@ export class KritzelEngine {
2006
2006
  }, onActionSelected: event => {
2007
2007
  if (event.detail.action) {
2008
2008
  event.detail.action({
2009
- x: (-this.core.store.state.translateX + this.core.store.state.contextMenuX) / this.core.store.state.scale,
2010
- y: (-this.core.store.state.translateY + this.core.store.state.contextMenuY) / this.core.store.state.scale,
2009
+ x: this.core.store.state.contextMenuWorldX,
2010
+ y: this.core.store.state.contextMenuWorldY,
2011
2011
  }, this.core.store.selectionGroup?.objects || []);
2012
2012
  }
2013
2013
  this.hideContextMenu();
@@ -51,7 +51,7 @@ export class KritzelMenu {
51
51
  this.itemCloseChildMenu.emit(event.detail);
52
52
  };
53
53
  render() {
54
- return (h(Host, { key: 'a81ea8a1fe2dc6cb8d9f395cafbcadec3eb4aa45', tabIndex: 0, onClick: e => e.stopPropagation() }, this.openChildMenuItem && h("div", { key: 'bb27d7a923431d79567e79283e505ea4ae02ef36', class: "has-open-child-overlay", onClick: this.onOverlayClick }), this.items.map(item => (h("kritzel-menu-item", { key: item.id, item: item, parent: this.parent, style: { pointerEvents: this.editingMenuItem && !item.isEditing ? 'none' : 'auto' }, onItemSelect: this.handleItemSelect, onItemSave: this.handleSave, onItemCancel: this.handleCancel, onItemToggleChildMenu: this.handleToggleChildMenu, onItemCloseChildMenu: this.handleCloseChildMenu })))));
54
+ return (h(Host, { key: 'a81ea8a1fe2dc6cb8d9f395cafbcadec3eb4aa45', tabIndex: 0, onClick: e => e.stopPropagation() }, this.openChildMenuItem && h("div", { key: 'bb27d7a923431d79567e79283e505ea4ae02ef36', class: "has-open-child-overlay", onClick: this.onOverlayClick }), this.items.map(item => (h("kritzel-menu-item", { key: item.id, "data-testid": `menu-item-${item.id}`, item: item, parent: this.parent, style: { pointerEvents: this.editingMenuItem && !item.isEditing ? 'none' : 'auto' }, onItemSelect: this.handleItemSelect, onItemSave: this.handleSave, onItemCancel: this.handleCancel, onItemToggleChildMenu: this.handleToggleChildMenu, onItemCloseChildMenu: this.handleCloseChildMenu })))));
55
55
  }
56
56
  static get is() { return "kritzel-menu"; }
57
57
  static get encapsulation() { return "shadow"; }
@@ -55,7 +55,7 @@ export class KritzelMoreMenu {
55
55
  this.closeMenu();
56
56
  };
57
57
  render() {
58
- return (h(Host, { key: '0e12ffc8c72566ec92080e6a19bd1d929795bef9', class: { mobile: this.isTouchDevice } }, h("div", { key: 'cc73b51c5aa39522a7ab7ec23d5c0a2732ed7acc', class: { 'more-menu-wrapper': true, visible: this.visible } }, h("button", { key: 'c35b8c7aa56e5e0e2773fed9fbbbead0b6b01a71', class: "more-menu-button", onClick: this.toggleMenu }, h("kritzel-icon", { key: '8b3261da5a10371a17b2562b71fde48dd0ba8ccd', name: this.icon, size: this.iconSize })), h("kritzel-portal", { key: 'be4a42061f27bbca3d435dec8e4dd25fc78febb0', anchor: this.menuAnchor, offsetY: this.offsetY, onClose: this.closeMenu }, h("kritzel-menu", { key: '763043f4d02819097396ce1baa85f398695e38b5', items: this.visibleItems, onItemSelect: this.handleMenuItemSelect })))));
58
+ return (h(Host, { key: '33d85e3b5ad51effdf2f61c8742dbe829ef43f15', class: { mobile: this.isTouchDevice }, style: { display: this.visible ? '' : 'none' } }, h("div", { key: '917ca25a14294f44a0428431a3ec08a84db2aff0', class: { 'more-menu-wrapper': true, visible: this.visible } }, h("button", { key: '6977d9efac27011c579d5452ebc14c99644c313b', class: "more-menu-button", "data-testid": "more-menu-button", onClick: this.toggleMenu }, h("kritzel-icon", { key: 'ff8af735c75d298063119d454331744985587f9c', name: this.icon, size: this.iconSize })), h("kritzel-portal", { key: '329670fe1700f3ae9359972e69882bf770f2f706', anchor: this.menuAnchor, offsetY: this.offsetY, onClose: this.closeMenu }, h("kritzel-menu", { key: 'ec6cc8df73c3c0239eb1b2c5ca8c6d79e51d124c', items: this.visibleItems, onItemSelect: this.handleMenuItemSelect })))));
59
59
  }
60
60
  static get is() { return "kritzel-more-menu"; }
61
61
  static get encapsulation() { return "shadow"; }
@@ -136,7 +136,7 @@ export class KritzelWorkspaceManager {
136
136
  ],
137
137
  };
138
138
  });
139
- return (h(Host, null, h("div", { class: { manager: true, visible: this.visible } }, h("kritzel-split-button", { ref: el => (this.splitButtonRef = el), items: menuItems, mainButtonDisabled: this.editingItemId != null, onMainButtonClick: () => this.add(), onItemSelect: event => this.select(event.detail.item, event.detail.parent), onItemToggleChildMenu: event => this.toggleChildMenu(event.detail.item, event.detail.childMenuAnchor), onItemSave: event => this.save(event.detail), onItemCancel: () => this.cancel(), onItemCloseChildMenu: () => this.closeChildMenu(), onMenuOpen: () => this.handleMenuOpen(), onMenuClose: () => this.handleMenuClose() }))));
139
+ return (h(Host, { style: { display: this.visible ? '' : 'none' } }, h("div", { class: { manager: true, visible: this.visible } }, h("kritzel-split-button", { ref: el => (this.splitButtonRef = el), items: menuItems, mainButtonDisabled: this.editingItemId != null, onMainButtonClick: () => this.add(), onItemSelect: event => this.select(event.detail.item, event.detail.parent), onItemToggleChildMenu: event => this.toggleChildMenu(event.detail.item, event.detail.childMenuAnchor), onItemSave: event => this.save(event.detail), onItemCancel: () => this.cancel(), onItemCloseChildMenu: () => this.closeChildMenu(), onMenuOpen: () => this.handleMenuOpen(), onMenuClose: () => this.handleMenuClose() }))));
140
140
  }
141
141
  static get is() { return "kritzel-workspace-manager"; }
142
142
  static get encapsulation() { return "shadow"; }
@@ -31,6 +31,8 @@ export const DEFAULT_ENGINE_CONFIG = {
31
31
  contextMenuItems: [],
32
32
  contextMenuX: 0,
33
33
  contextMenuY: 0,
34
+ contextMenuWorldX: 0,
35
+ contextMenuWorldY: 0,
34
36
  skipContextMenu: false,
35
37
  debugInfo: {
36
38
  showObjectInfo: false,
@@ -3,4 +3,4 @@
3
3
  * This file is auto-generated by the version bump scripts.
4
4
  * Do not modify manually.
5
5
  */
6
- export const KRITZEL_VERSION = '0.2.11';
6
+ export const KRITZEL_VERSION = '0.2.13';
@@ -1 +1 @@
1
- export{g as getAssetPath,r as render,s as setAssetPath,a as setNonce,b as setPlatformOptions}from"./p-BWj1eE2b.js";export{A as AssetNotFoundError,I as IndexedDBAssetProvider,i as KritzelAssetResolver,d as KritzelBrushTool,b as KritzelGroup,a as KritzelImage,e as KritzelLineTool,h as KritzelSelectionTool,c as KritzelShape,g as KritzelShapeTool,K as KritzelText,f as KritzelTextTool,S as ShapeType}from"./p-D9-C4GfD.js";export{a as KritzelLine,K as KritzelPath}from"./p-C4bAtxyk.js";export{A as APP_STATE_MIGRATIONS,I as IndexedDBSyncProvider,d as KritzelAlignment,c as KritzelAnchorManager,b as KritzelCursorHelper,K as KritzelEraserTool,a as KritzelImageTool,W as WORKSPACE_MIGRATIONS,r as runMigrations}from"./p-B_fA1LTU.js";import*as t from"yjs";import{WebsocketProvider as o}from"y-websocket";import{H as n,a as m}from"./kritzel-editor.js";export{d as DEFAULT_ASSET_STORAGE_CONFIG,D as DEFAULT_BRUSH_CONFIG,c as DEFAULT_LINE_TOOL_CONFIG,b as DEFAULT_TEXT_CONFIG,KritzelEditor,defineCustomElement as defineCustomElementKritzelEditor}from"./kritzel-editor.js";export{K as KritzelWorkspace,W as WORKSPACE_EXPORT_VERSION}from"./p-DhMlShij.js";export{K as KritzelThemeManager,d as darkTheme,l as lightTheme}from"./p-DjAiIBXv.js";export{C as CURRENT_APP_STATE_SCHEMA_VERSION,a as CURRENT_WORKSPACE_SCHEMA_VERSION}from"./p-CW-VyJgK.js";export{KritzelActiveUsers,defineCustomElement as defineCustomElementKritzelActiveUsers}from"./kritzel-active-users.js";export{KritzelAvatar,defineCustomElement as defineCustomElementKritzelAvatar}from"./kritzel-avatar.js";export{KritzelAwarenessCursors,defineCustomElement as defineCustomElementKritzelAwarenessCursors}from"./kritzel-awareness-cursors.js";export{KritzelBackToContent,defineCustomElement as defineCustomElementKritzelBackToContent}from"./kritzel-back-to-content.js";export{KritzelBrushStyle,defineCustomElement as defineCustomElementKritzelBrushStyle}from"./kritzel-brush-style.js";export{KritzelButton,defineCustomElement as defineCustomElementKritzelButton}from"./kritzel-button.js";export{KritzelColor,defineCustomElement as defineCustomElementKritzelColor}from"./kritzel-color.js";export{KritzelColorPalette,defineCustomElement as defineCustomElementKritzelColorPalette}from"./kritzel-color-palette.js";export{KritzelContextMenu,defineCustomElement as defineCustomElementKritzelContextMenu}from"./kritzel-context-menu.js";export{KritzelControls,defineCustomElement as defineCustomElementKritzelControls}from"./kritzel-controls.js";export{KritzelCurrentUser,defineCustomElement as defineCustomElementKritzelCurrentUser}from"./kritzel-current-user.js";export{KritzelCurrentUserDialog,defineCustomElement as defineCustomElementKritzelCurrentUserDialog}from"./kritzel-current-user-dialog.js";export{KritzelCursorTrail,defineCustomElement as defineCustomElementKritzelCursorTrail}from"./kritzel-cursor-trail.js";export{KritzelDialog,defineCustomElement as defineCustomElementKritzelDialog}from"./kritzel-dialog.js";export{KritzelDropdown,defineCustomElement as defineCustomElementKritzelDropdown}from"./kritzel-dropdown.js";export{KritzelEngine,defineCustomElement as defineCustomElementKritzelEngine}from"./kritzel-engine.js";export{KritzelExport,defineCustomElement as defineCustomElementKritzelExport}from"./kritzel-export.js";export{KritzelFont,defineCustomElement as defineCustomElementKritzelFont}from"./kritzel-font.js";export{KritzelFontFamily,defineCustomElement as defineCustomElementKritzelFontFamily}from"./kritzel-font-family.js";export{KritzelFontSize,defineCustomElement as defineCustomElementKritzelFontSize}from"./kritzel-font-size.js";export{KritzelIcon,defineCustomElement as defineCustomElementKritzelIcon}from"./kritzel-icon.js";export{KritzelInput,defineCustomElement as defineCustomElementKritzelInput}from"./kritzel-input.js";export{KritzelLineEndings,defineCustomElement as defineCustomElementKritzelLineEndings}from"./kritzel-line-endings.js";export{KritzelLoginDialog,defineCustomElement as defineCustomElementKritzelLoginDialog}from"./kritzel-login-dialog.js";export{KritzelMasterDetail,defineCustomElement as defineCustomElementKritzelMasterDetail}from"./kritzel-master-detail.js";export{KritzelMenu,defineCustomElement as defineCustomElementKritzelMenu}from"./kritzel-menu.js";export{KritzelMenuItem,defineCustomElement as defineCustomElementKritzelMenuItem}from"./kritzel-menu-item.js";export{KritzelMoreMenu,defineCustomElement as defineCustomElementKritzelMoreMenu}from"./kritzel-more-menu.js";export{KritzelNumericInput,defineCustomElement as defineCustomElementKritzelNumericInput}from"./kritzel-numeric-input.js";export{KritzelOpacitySlider,defineCustomElement as defineCustomElementKritzelOpacitySlider}from"./kritzel-opacity-slider.js";export{KritzelPillTabs,defineCustomElement as defineCustomElementKritzelPillTabs}from"./kritzel-pill-tabs.js";export{KritzelPortal,defineCustomElement as defineCustomElementKritzelPortal}from"./kritzel-portal.js";export{KritzelSettings,defineCustomElement as defineCustomElementKritzelSettings}from"./kritzel-settings.js";export{KritzelShapeFill,defineCustomElement as defineCustomElementKritzelShapeFill}from"./kritzel-shape-fill.js";export{KritzelShareDialog,defineCustomElement as defineCustomElementKritzelShareDialog}from"./kritzel-share-dialog.js";export{KritzelSlideToggle,defineCustomElement as defineCustomElementKritzelSlideToggle}from"./kritzel-slide-toggle.js";export{KritzelSplitButton,defineCustomElement as defineCustomElementKritzelSplitButton}from"./kritzel-split-button.js";export{KritzelStrokeSize,defineCustomElement as defineCustomElementKritzelStrokeSize}from"./kritzel-stroke-size.js";export{KritzelToolConfig,defineCustomElement as defineCustomElementKritzelToolConfig}from"./kritzel-tool-config.js";export{KritzelTooltip,defineCustomElement as defineCustomElementKritzelTooltip}from"./kritzel-tooltip.js";export{KritzelUtilityPanel,defineCustomElement as defineCustomElementKritzelUtilityPanel}from"./kritzel-utility-panel.js";export{KritzelWorkspaceManager,defineCustomElement as defineCustomElementKritzelWorkspaceManager}from"./kritzel-workspace-manager.js";const u=Math.floor,z=127,p=Number.MAX_SAFE_INTEGER;class E{constructor(){this.cpos=0,this.cbuf=new Uint8Array(100),this.bufs=[]}}const y=()=>new E,k=e=>{const t=new Uint8Array((e=>{let t=e.cpos;for(let s=0;s<e.bufs.length;s++)t+=e.bufs[s].length;return t})(e));let s=0;for(let i=0;i<e.bufs.length;i++){const o=e.bufs[i];t.set(o,s),s+=o.length}return t.set(new Uint8Array(e.cbuf.buffer,0,e.cpos),s),t},w=(e,t)=>{const s=e.cbuf.length;e.cpos===s&&(e.bufs.push(e.cbuf),e.cbuf=new Uint8Array(2*s),e.cpos=0),e.cbuf[e.cpos++]=t},x=(e,t)=>{for(;t>z;)w(e,128|z&t),t=u(t/128);w(e,z&t)},T=(e,t)=>{x(e,t.byteLength),((e,t)=>{const s=e.cbuf.length,i=e.cpos,o=((e,t)=>e<t?e:t)(s-i,t.length),n=t.length-o;e.cbuf.set(t.subarray(0,o),i),e.cpos+=o,n>0&&(e.bufs.push(e.cbuf),e.cbuf=new Uint8Array(((e,t)=>e>t?e:t)(2*s,n)),e.cbuf.set(t.subarray(o)),e.cpos=n)})(e,t)},j=e=>Error(e),P=j("Unexpected end of array"),v=j("Integer out of Range");class U{constructor(e){this.arr=e,this.pos=0}}const M=e=>((e,t)=>{const s=new Uint8Array(e.arr.buffer,e.pos+e.arr.byteOffset,t);return e.pos+=t,s})(e,_(e)),_=e=>{let t=0,s=1;const i=e.arr.length;for(;e.pos<i;){const i=e.arr[e.pos++];if(t+=(i&z)*s,s*=128,i<128)return t;if(t>p)throw v}throw P};class F{type="local";doc;channel;_synced=!1;constructor(e,t,s){this.doc=t,this.channel=new BroadcastChannel(e),this.channel.onmessage=e=>{this.handleMessage(e.data)},this.doc.on("update",this.handleDocUpdate),this.broadcastSync(),setTimeout((()=>{this._synced=!0}),100),s?.quiet||console.info("BroadcastChannel Provider initialized: "+e)}handleDocUpdate=(e,t)=>{if(t!==this){const t=y();x(t,0),T(t,e),this.channel.postMessage(k(t))}};handleMessage(e){const s=(e=>new U(e))(new Uint8Array(e));switch(_(s)){case 0:const e=M(s);t.applyUpdate(this.doc,e,this);break;case 1:this.broadcastSync();break;case 2:const i=M(s),o=t.encodeStateAsUpdate(this.doc,i);if(o.length>0){const e=y();x(e,0),T(e,o),this.channel.postMessage(k(e))}}}broadcastSync(){const e=y();x(e,2),T(e,t.encodeStateVector(this.doc)),this.channel.postMessage(k(e))}async connect(){if(!this._synced)return new Promise((e=>{const t=()=>{this._synced?e():setTimeout(t,50)};t()}))}disconnect(){}async reconnect(){return this.disconnect(),this.connect()}destroy(){this.doc.off("update",this.handleDocUpdate),this.channel.close()}}class ${type="network";provider;isConnected=!1;_quiet=!1;get awareness(){return this.provider.awareness}constructor(e,t,s){const i=s?.url||"ws://localhost:1234",n=s?.roomName||e;this.provider=new o(i,n,t,{params:s?.params,protocols:s?.protocols,WebSocketPolyfill:s?.WebSocketPolyfill,awareness:s?.awareness,maxBackoffTime:s?.maxBackoffTime,disableBc:!0}),this._quiet=s?.quiet??!1,this.setupEventListeners(),this._quiet||console.info(`WebSocket Provider initialized: ${i}/${n}`)}static with(e){return{create:(t,s,i)=>{const o=i?{...e,...i}:e;return new $(t,s,o)}}}setupEventListeners(){this.provider.on("status",(({status:e})=>{"connected"===e?(this.isConnected=!0,this._quiet||console.info("WebSocket connected")):"disconnected"===e&&(this.isConnected=!1,this._quiet||console.info("WebSocket disconnected"))})),this.provider.on("sync",(e=>{e&&!this._quiet&&console.info("WebSocket synced")}))}async connect(){if(!this.isConnected)return new Promise(((e,t)=>{const s=setTimeout((()=>{t(Error("WebSocket connection timeout"))}),1e4),i=({status:t})=>{"connected"===t&&(clearTimeout(s),this.provider.off("status",i),this.isConnected=!0,e())};this.provider.on("status",i),this.provider.wsconnected&&(clearTimeout(s),this.provider.off("status",i),this.isConnected=!0,e())}))}disconnect(){this.provider&&this.provider.disconnect(),this.isConnected=!1}async reconnect(){return this.disconnect(),this.connect()}destroy(){this.provider&&this.provider.destroy(),this.isConnected=!1}}class O{type="network";provider;isConnected=!1;isSynced=!1;usesSharedSocket=!1;isDestroyed=!1;connectTimeout=null;pendingConnectReject=null;connectionTimeoutMs;_connectionStatus="disconnected";visibilityHandler=null;onlineHandler=null;get awareness(){return this.provider.awareness}get connectionStatus(){return this._connectionStatus}static sharedWebSocketProvider=null;constructor(e,t,s){const i=s?.name||e,o=s?.url||"ws://localhost:1234";this.connectionTimeoutMs=s?.connectionTimeout??1e4;const r=s?.websocketProvider||O.sharedWebSocketProvider,l={};void 0!==s?.delay&&(l.delay=s.delay),void 0!==s?.factor&&(l.factor=s.factor),void 0!==s?.maxAttempts&&(l.maxAttempts=s.maxAttempts),void 0!==s?.minDelay&&(l.minDelay=s.minDelay),void 0!==s?.maxDelay&&(l.maxDelay=s.maxDelay);const a=()=>{this.isDestroyed||(this.isConnected=!0,this._connectionStatus="connected",s?.quiet||console.info("Hocuspocus connected: "+i),s?.onConnect&&s.onConnect())},c=()=>{this.isDestroyed||(this.isConnected=!1,this.isSynced=!1,this._connectionStatus="disconnected",s?.quiet||console.info("Hocuspocus disconnected: "+i),s?.onDisconnect&&s.onDisconnect())},m=()=>{this.isDestroyed||(this.isSynced=!0,this._connectionStatus="synced",s?.quiet||console.info("Hocuspocus synced: "+i),s?.onSynced&&s.onSynced())},d=e=>{this.isDestroyed||("connecting"===e.status&&(this._connectionStatus="connecting"),s?.onStatus&&s.onStatus(e))};if(r){this.usesSharedSocket=!0;const e={websocketProvider:r,name:i,document:t,token:s?.token||null,onStatus:d,onConnect:a,onDisconnect:c,onSynced:m,...l};void 0!==s?.forceSyncInterval&&(e.forceSyncInterval=s.forceSyncInterval),s?.onAuthenticationFailed&&(e.onAuthenticationFailed=s.onAuthenticationFailed),this.provider=new n(e),this.provider.attach(),s?.quiet||console.info("Hocuspocus Provider initialized (multiplexed): "+i)}else{this.usesSharedSocket=!1;const e={url:o,name:i,document:t,token:s?.token||null,autoConnect:!1,onStatus:d,onConnect:a,onDisconnect:c,onSynced:m,...l};void 0!==s?.forceSyncInterval&&(e.forceSyncInterval=s.forceSyncInterval),s?.onAuthenticationFailed&&(e.onAuthenticationFailed=s.onAuthenticationFailed),s?.WebSocketPolyfill&&(e.WebSocketPolyfill=s.WebSocketPolyfill),this.provider=new n(e),s?.quiet||console.info(`Hocuspocus Provider initialized: ${o}/${i}`)}this.setupBrowserEventListeners()}setupBrowserEventListeners(){"undefined"!=typeof document&&(this.visibilityHandler=()=>{"visible"!==document.visibilityState||this.isConnected||this.isDestroyed||this.provider.connect()},document.addEventListener("visibilitychange",this.visibilityHandler)),"undefined"!=typeof window&&(this.onlineHandler=()=>{this.isConnected||this.isDestroyed||this.provider.connect()},window.addEventListener("online",this.onlineHandler))}removeBrowserEventListeners(){this.visibilityHandler&&"undefined"!=typeof document&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),this.onlineHandler&&"undefined"!=typeof window&&(window.removeEventListener("online",this.onlineHandler),this.onlineHandler=null)}static createSharedWebSocket(e){if(O.sharedWebSocketProvider)return console.warn("Shared WebSocket already exists. Returning existing instance."),O.sharedWebSocketProvider;const t={url:e.url};return e.WebSocketPolyfill&&(t.WebSocketPolyfill=e.WebSocketPolyfill),e.onConnect&&(t.onConnect=e.onConnect),e.onDisconnect&&(t.onDisconnect=e.onDisconnect),e.onStatus&&(t.onStatus=e.onStatus),O.sharedWebSocketProvider=new m(t),console.info("Shared Hocuspocus WebSocket created: "+e.url),O.sharedWebSocketProvider}static destroySharedWebSocket(){O.sharedWebSocketProvider&&(O.sharedWebSocketProvider.destroy(),O.sharedWebSocketProvider=null,console.info("Shared Hocuspocus WebSocket destroyed"))}static getSharedWebSocket(){return O.sharedWebSocketProvider}static with(e){return{create:(t,s,i)=>{const o=i?{...e,...i}:e;return new O(t,s,o)}}}async connect(){if(!this.isSynced&&!this.isDestroyed)return this._connectionStatus="connecting",new Promise(((e,t)=>{this.pendingConnectReject=t,this.connectTimeout=setTimeout((()=>{this.pendingConnectReject=null,this.connectTimeout=null,t(Error("Hocuspocus connection timeout"))}),this.connectionTimeoutMs);const s=()=>{this.connectTimeout&&(clearTimeout(this.connectTimeout),this.connectTimeout=null),this.pendingConnectReject=null,this.provider.off("synced",s),this.isDestroyed||e()};if(this.provider.on("synced",s),this.provider.isSynced)return this.connectTimeout&&(clearTimeout(this.connectTimeout),this.connectTimeout=null),this.pendingConnectReject=null,this.provider.off("synced",s),void e();this.isConnected||this.usesSharedSocket||this.provider.connect()}))}async reconnect(){return this.disconnect(),this.connect()}disconnect(){this.connectTimeout&&(clearTimeout(this.connectTimeout),this.connectTimeout=null),this.pendingConnectReject&&(this.pendingConnectReject=null),this.provider&&(this.usesSharedSocket?this.provider.detach():this.provider.disconnect()),this.isConnected=!1,this.isSynced=!1,this._connectionStatus="disconnected"}destroy(){this.isDestroyed=!0,this.connectTimeout&&(clearTimeout(this.connectTimeout),this.connectTimeout=null),this.pendingConnectReject&&(this.pendingConnectReject=null),this.removeBrowserEventListeners(),this.provider&&this.provider.destroy(),this.isConnected=!1,this.isSynced=!1,this._connectionStatus="disconnected"}}class B{type="remote";name="HttpAssetProvider";_options;constructor(e){this._options=e}static with(e){return{create:()=>new B(e)}}async init(){this._options.quiet||console.info("HttpAssetProvider initialized")}destroy(){}canResolve(e){return!0}async put(e,t){const s=t.id??this.generateUuid(),i={...t,id:s},o=await(this._options.headers?.())??{},n=await this._options.uploadUrl(i),r=this._options.upload??this.defaultUpload;return{id:(await r(n,e,i,o)).id??s,kind:t.kind??"file",mimeType:t.mimeType,size:e.size,createdAt:Date.now(),width:t.width,height:t.height,durationMs:t.durationMs,originalFilename:t.originalFilename}}async resolve(e){return this._options.resolveUrl(e)}async fetch(e){const t=await this._options.resolveUrl(e),s=await(this._options.headers?.())??{},i=await fetch(t,{headers:s});if(!i.ok)throw Error(`[HttpAssetProvider] Failed to fetch asset ${e}: ${i.status} ${i.statusText}`);return i.blob()}async delete(e){if(!this._options.deleteUrl)return;const t=await this._options.deleteUrl(e),s=await(this._options.headers?.())??{},i=await fetch(t,{method:"DELETE",headers:s});if(!i.ok)throw Error(`[HttpAssetProvider] Failed to delete asset ${e}: ${i.status} ${i.statusText}`);this._options.quiet||console.info("HttpAssetProvider: deleted asset "+e)}defaultUpload=async(e,t,s,i)=>{const o=new FormData;o.append("metadata",JSON.stringify(s)),o.append("file",t,s.originalFilename||`${s.id}.${this.extensionFromMime(s.mimeType)}`);const n=await fetch(e,{method:"POST",headers:i,body:o});if(!n.ok)throw Error(`[HttpAssetProvider] Upload failed: ${n.status} ${n.statusText}`);return(n.headers.get("content-type")||"").includes("application/json")?await n.json():{}};extensionFromMime(e){const t=e.indexOf("/");return t>=0?e.slice(t+1):"bin"}generateUuid(){if("undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID)return crypto.randomUUID();const e=crypto.getRandomValues(new Uint8Array(16));e[6]=15&e[6]|64,e[8]=63&e[8]|128;const t=Array.from(e,(e=>e.toString(16).padStart(2,"0"))).join("");return`${t.slice(0,8)}-${t.slice(8,12)}-${t.slice(12,16)}-${t.slice(16,20)}-${t.slice(20)}`}}class H{type="remote";name="PresignedAssetProvider";_options;constructor(e){this._options=e}static with(e){return{create:()=>new H(e)}}async init(){this._options.quiet||console.info("PresignedAssetProvider initialized")}destroy(){}canResolve(e){return!0}async put(e,t){const s=await this._options.getUploadDescriptor(t),i=s.method??"PUT",o={...s.headers??{}};let n;if(o["Content-Type"]||o["content-type"]||(o["Content-Type"]=t.mimeType),"POST"===i&&s.fields){const t=new FormData;for(const[e,i]of Object.entries(s.fields))t.append(e,i);t.append("file",e),n=t,delete o["Content-Type"],delete o["content-type"]}else n=e;const r=await fetch(s.url,{method:i,headers:o,body:n});if(!r.ok)throw Error(`[PresignedAssetProvider] Upload failed: ${r.status} ${r.statusText}`);return{id:s.id,kind:t.kind??"file",mimeType:t.mimeType,size:e.size,createdAt:Date.now(),width:t.width,height:t.height,durationMs:t.durationMs,originalFilename:t.originalFilename}}async resolve(e){return this._options.getDownloadUrl(e)}async fetch(e){const t=await this._options.getDownloadUrl(e),s=await fetch(t);if(!s.ok)throw Error(`[PresignedAssetProvider] Failed to fetch asset ${e}: ${s.status} ${s.statusText}`);return s.blob()}async delete(e){this._options.deleteAsset&&(await this._options.deleteAsset(e),this._options.quiet||console.info("PresignedAssetProvider: deleted asset "+e))}}export{F as BroadcastSyncProvider,O as HocuspocusSyncProvider,B as HttpAssetProvider,H as PresignedAssetProvider,$ as WebSocketSyncProvider}
1
+ export{g as getAssetPath,r as render,s as setAssetPath,a as setNonce,b as setPlatformOptions}from"./p-BWj1eE2b.js";export{A as AssetNotFoundError,I as IndexedDBAssetProvider,i as KritzelAssetResolver,d as KritzelBrushTool,b as KritzelGroup,a as KritzelImage,e as KritzelLineTool,h as KritzelSelectionTool,c as KritzelShape,g as KritzelShapeTool,K as KritzelText,f as KritzelTextTool,S as ShapeType}from"./p-D9-C4GfD.js";export{a as KritzelLine,K as KritzelPath}from"./p-C4bAtxyk.js";export{A as APP_STATE_MIGRATIONS,I as IndexedDBSyncProvider,d as KritzelAlignment,c as KritzelAnchorManager,b as KritzelCursorHelper,K as KritzelEraserTool,a as KritzelImageTool,W as WORKSPACE_MIGRATIONS,r as runMigrations}from"./p-BWB2UGxj.js";import*as t from"yjs";import{WebsocketProvider as o}from"y-websocket";import{H as n,a as m}from"./kritzel-editor.js";export{d as DEFAULT_ASSET_STORAGE_CONFIG,D as DEFAULT_BRUSH_CONFIG,c as DEFAULT_LINE_TOOL_CONFIG,b as DEFAULT_TEXT_CONFIG,KritzelEditor,defineCustomElement as defineCustomElementKritzelEditor}from"./kritzel-editor.js";export{K as KritzelWorkspace,W as WORKSPACE_EXPORT_VERSION}from"./p-DhMlShij.js";export{K as KritzelThemeManager,d as darkTheme,l as lightTheme}from"./p-DjAiIBXv.js";export{C as CURRENT_APP_STATE_SCHEMA_VERSION,a as CURRENT_WORKSPACE_SCHEMA_VERSION}from"./p-CW-VyJgK.js";export{KritzelActiveUsers,defineCustomElement as defineCustomElementKritzelActiveUsers}from"./kritzel-active-users.js";export{KritzelAvatar,defineCustomElement as defineCustomElementKritzelAvatar}from"./kritzel-avatar.js";export{KritzelAwarenessCursors,defineCustomElement as defineCustomElementKritzelAwarenessCursors}from"./kritzel-awareness-cursors.js";export{KritzelBackToContent,defineCustomElement as defineCustomElementKritzelBackToContent}from"./kritzel-back-to-content.js";export{KritzelBrushStyle,defineCustomElement as defineCustomElementKritzelBrushStyle}from"./kritzel-brush-style.js";export{KritzelButton,defineCustomElement as defineCustomElementKritzelButton}from"./kritzel-button.js";export{KritzelColor,defineCustomElement as defineCustomElementKritzelColor}from"./kritzel-color.js";export{KritzelColorPalette,defineCustomElement as defineCustomElementKritzelColorPalette}from"./kritzel-color-palette.js";export{KritzelContextMenu,defineCustomElement as defineCustomElementKritzelContextMenu}from"./kritzel-context-menu.js";export{KritzelControls,defineCustomElement as defineCustomElementKritzelControls}from"./kritzel-controls.js";export{KritzelCurrentUser,defineCustomElement as defineCustomElementKritzelCurrentUser}from"./kritzel-current-user.js";export{KritzelCurrentUserDialog,defineCustomElement as defineCustomElementKritzelCurrentUserDialog}from"./kritzel-current-user-dialog.js";export{KritzelCursorTrail,defineCustomElement as defineCustomElementKritzelCursorTrail}from"./kritzel-cursor-trail.js";export{KritzelDialog,defineCustomElement as defineCustomElementKritzelDialog}from"./kritzel-dialog.js";export{KritzelDropdown,defineCustomElement as defineCustomElementKritzelDropdown}from"./kritzel-dropdown.js";export{KritzelEngine,defineCustomElement as defineCustomElementKritzelEngine}from"./kritzel-engine.js";export{KritzelExport,defineCustomElement as defineCustomElementKritzelExport}from"./kritzel-export.js";export{KritzelFont,defineCustomElement as defineCustomElementKritzelFont}from"./kritzel-font.js";export{KritzelFontFamily,defineCustomElement as defineCustomElementKritzelFontFamily}from"./kritzel-font-family.js";export{KritzelFontSize,defineCustomElement as defineCustomElementKritzelFontSize}from"./kritzel-font-size.js";export{KritzelIcon,defineCustomElement as defineCustomElementKritzelIcon}from"./kritzel-icon.js";export{KritzelInput,defineCustomElement as defineCustomElementKritzelInput}from"./kritzel-input.js";export{KritzelLineEndings,defineCustomElement as defineCustomElementKritzelLineEndings}from"./kritzel-line-endings.js";export{KritzelLoginDialog,defineCustomElement as defineCustomElementKritzelLoginDialog}from"./kritzel-login-dialog.js";export{KritzelMasterDetail,defineCustomElement as defineCustomElementKritzelMasterDetail}from"./kritzel-master-detail.js";export{KritzelMenu,defineCustomElement as defineCustomElementKritzelMenu}from"./kritzel-menu.js";export{KritzelMenuItem,defineCustomElement as defineCustomElementKritzelMenuItem}from"./kritzel-menu-item.js";export{KritzelMoreMenu,defineCustomElement as defineCustomElementKritzelMoreMenu}from"./kritzel-more-menu.js";export{KritzelNumericInput,defineCustomElement as defineCustomElementKritzelNumericInput}from"./kritzel-numeric-input.js";export{KritzelOpacitySlider,defineCustomElement as defineCustomElementKritzelOpacitySlider}from"./kritzel-opacity-slider.js";export{KritzelPillTabs,defineCustomElement as defineCustomElementKritzelPillTabs}from"./kritzel-pill-tabs.js";export{KritzelPortal,defineCustomElement as defineCustomElementKritzelPortal}from"./kritzel-portal.js";export{KritzelSettings,defineCustomElement as defineCustomElementKritzelSettings}from"./kritzel-settings.js";export{KritzelShapeFill,defineCustomElement as defineCustomElementKritzelShapeFill}from"./kritzel-shape-fill.js";export{KritzelShareDialog,defineCustomElement as defineCustomElementKritzelShareDialog}from"./kritzel-share-dialog.js";export{KritzelSlideToggle,defineCustomElement as defineCustomElementKritzelSlideToggle}from"./kritzel-slide-toggle.js";export{KritzelSplitButton,defineCustomElement as defineCustomElementKritzelSplitButton}from"./kritzel-split-button.js";export{KritzelStrokeSize,defineCustomElement as defineCustomElementKritzelStrokeSize}from"./kritzel-stroke-size.js";export{KritzelToolConfig,defineCustomElement as defineCustomElementKritzelToolConfig}from"./kritzel-tool-config.js";export{KritzelTooltip,defineCustomElement as defineCustomElementKritzelTooltip}from"./kritzel-tooltip.js";export{KritzelUtilityPanel,defineCustomElement as defineCustomElementKritzelUtilityPanel}from"./kritzel-utility-panel.js";export{KritzelWorkspaceManager,defineCustomElement as defineCustomElementKritzelWorkspaceManager}from"./kritzel-workspace-manager.js";const u=Math.floor,z=127,p=Number.MAX_SAFE_INTEGER;class E{constructor(){this.cpos=0,this.cbuf=new Uint8Array(100),this.bufs=[]}}const y=()=>new E,k=e=>{const t=new Uint8Array((e=>{let t=e.cpos;for(let s=0;s<e.bufs.length;s++)t+=e.bufs[s].length;return t})(e));let s=0;for(let i=0;i<e.bufs.length;i++){const o=e.bufs[i];t.set(o,s),s+=o.length}return t.set(new Uint8Array(e.cbuf.buffer,0,e.cpos),s),t},w=(e,t)=>{const s=e.cbuf.length;e.cpos===s&&(e.bufs.push(e.cbuf),e.cbuf=new Uint8Array(2*s),e.cpos=0),e.cbuf[e.cpos++]=t},x=(e,t)=>{for(;t>z;)w(e,128|z&t),t=u(t/128);w(e,z&t)},T=(e,t)=>{x(e,t.byteLength),((e,t)=>{const s=e.cbuf.length,i=e.cpos,o=((e,t)=>e<t?e:t)(s-i,t.length),n=t.length-o;e.cbuf.set(t.subarray(0,o),i),e.cpos+=o,n>0&&(e.bufs.push(e.cbuf),e.cbuf=new Uint8Array(((e,t)=>e>t?e:t)(2*s,n)),e.cbuf.set(t.subarray(o)),e.cpos=n)})(e,t)},j=e=>Error(e),P=j("Unexpected end of array"),v=j("Integer out of Range");class U{constructor(e){this.arr=e,this.pos=0}}const M=e=>((e,t)=>{const s=new Uint8Array(e.arr.buffer,e.pos+e.arr.byteOffset,t);return e.pos+=t,s})(e,F(e)),F=e=>{let t=0,s=1;const i=e.arr.length;for(;e.pos<i;){const i=e.arr[e.pos++];if(t+=(i&z)*s,s*=128,i<128)return t;if(t>p)throw v}throw P};class _{type="local";doc;channel;_synced=!1;constructor(e,t,s){this.doc=t,this.channel=new BroadcastChannel(e),this.channel.onmessage=e=>{this.handleMessage(e.data)},this.doc.on("update",this.handleDocUpdate),this.broadcastSync(),setTimeout((()=>{this._synced=!0}),100),s?.quiet||console.info("BroadcastChannel Provider initialized: "+e)}handleDocUpdate=(e,t)=>{if(t!==this){const t=y();x(t,0),T(t,e),this.channel.postMessage(k(t))}};handleMessage(e){const s=(e=>new U(e))(new Uint8Array(e));switch(F(s)){case 0:const e=M(s);t.applyUpdate(this.doc,e,this);break;case 1:this.broadcastSync();break;case 2:const i=M(s),o=t.encodeStateAsUpdate(this.doc,i);if(o.length>0){const e=y();x(e,0),T(e,o),this.channel.postMessage(k(e))}}}broadcastSync(){const e=y();x(e,2),T(e,t.encodeStateVector(this.doc)),this.channel.postMessage(k(e))}async connect(){if(!this._synced)return new Promise((e=>{const t=()=>{this._synced?e():setTimeout(t,50)};t()}))}disconnect(){}async reconnect(){return this.disconnect(),this.connect()}destroy(){this.doc.off("update",this.handleDocUpdate),this.channel.close()}}class ${type="network";provider;isConnected=!1;_quiet=!1;get awareness(){return this.provider.awareness}constructor(e,t,s){const i=s?.url||"ws://localhost:1234",n=s?.roomName||e;this.provider=new o(i,n,t,{params:s?.params,protocols:s?.protocols,WebSocketPolyfill:s?.WebSocketPolyfill,awareness:s?.awareness,maxBackoffTime:s?.maxBackoffTime,disableBc:!0}),this._quiet=s?.quiet??!1,this.setupEventListeners(),this._quiet||console.info(`WebSocket Provider initialized: ${i}/${n}`)}static with(e){return{create:(t,s,i)=>{const o=i?{...e,...i}:e;return new $(t,s,o)}}}setupEventListeners(){this.provider.on("status",(({status:e})=>{"connected"===e?(this.isConnected=!0,this._quiet||console.info("WebSocket connected")):"disconnected"===e&&(this.isConnected=!1,this._quiet||console.info("WebSocket disconnected"))})),this.provider.on("sync",(e=>{e&&!this._quiet&&console.info("WebSocket synced")}))}async connect(){if(!this.isConnected)return new Promise(((e,t)=>{const s=setTimeout((()=>{t(Error("WebSocket connection timeout"))}),1e4),i=({status:t})=>{"connected"===t&&(clearTimeout(s),this.provider.off("status",i),this.isConnected=!0,e())};this.provider.on("status",i),this.provider.wsconnected&&(clearTimeout(s),this.provider.off("status",i),this.isConnected=!0,e())}))}disconnect(){this.provider&&this.provider.disconnect(),this.isConnected=!1}async reconnect(){return this.disconnect(),this.connect()}destroy(){this.provider&&this.provider.destroy(),this.isConnected=!1}}class O{type="network";provider;isConnected=!1;isSynced=!1;usesSharedSocket=!1;isDestroyed=!1;connectTimeout=null;pendingConnectReject=null;connectionTimeoutMs;_connectionStatus="disconnected";visibilityHandler=null;onlineHandler=null;get awareness(){return this.provider.awareness}get connectionStatus(){return this._connectionStatus}static sharedWebSocketProvider=null;constructor(e,t,s){const i=s?.name||e,o=s?.url||"ws://localhost:1234";this.connectionTimeoutMs=s?.connectionTimeout??1e4;const r=s?.websocketProvider||O.sharedWebSocketProvider,l={};void 0!==s?.delay&&(l.delay=s.delay),void 0!==s?.factor&&(l.factor=s.factor),void 0!==s?.maxAttempts&&(l.maxAttempts=s.maxAttempts),void 0!==s?.minDelay&&(l.minDelay=s.minDelay),void 0!==s?.maxDelay&&(l.maxDelay=s.maxDelay);const a=()=>{this.isDestroyed||(this.isConnected=!0,this._connectionStatus="connected",s?.quiet||console.info("Hocuspocus connected: "+i),s?.onConnect&&s.onConnect())},c=()=>{this.isDestroyed||(this.isConnected=!1,this.isSynced=!1,this._connectionStatus="disconnected",s?.quiet||console.info("Hocuspocus disconnected: "+i),s?.onDisconnect&&s.onDisconnect())},m=()=>{this.isDestroyed||(this.isSynced=!0,this._connectionStatus="synced",s?.quiet||console.info("Hocuspocus synced: "+i),s?.onSynced&&s.onSynced())},d=e=>{this.isDestroyed||("connecting"===e.status&&(this._connectionStatus="connecting"),s?.onStatus&&s.onStatus(e))};if(r){this.usesSharedSocket=!0;const e={websocketProvider:r,name:i,document:t,token:s?.token||null,onStatus:d,onConnect:a,onDisconnect:c,onSynced:m,...l};void 0!==s?.forceSyncInterval&&(e.forceSyncInterval=s.forceSyncInterval),s?.onAuthenticationFailed&&(e.onAuthenticationFailed=s.onAuthenticationFailed),this.provider=new n(e),this.provider.attach(),s?.quiet||console.info("Hocuspocus Provider initialized (multiplexed): "+i)}else{this.usesSharedSocket=!1;const e={url:o,name:i,document:t,token:s?.token||null,autoConnect:!1,onStatus:d,onConnect:a,onDisconnect:c,onSynced:m,...l};void 0!==s?.forceSyncInterval&&(e.forceSyncInterval=s.forceSyncInterval),s?.onAuthenticationFailed&&(e.onAuthenticationFailed=s.onAuthenticationFailed),s?.WebSocketPolyfill&&(e.WebSocketPolyfill=s.WebSocketPolyfill),this.provider=new n(e),s?.quiet||console.info(`Hocuspocus Provider initialized: ${o}/${i}`)}this.setupBrowserEventListeners()}setupBrowserEventListeners(){"undefined"!=typeof document&&(this.visibilityHandler=()=>{"visible"!==document.visibilityState||this.isConnected||this.isDestroyed||this.provider.connect()},document.addEventListener("visibilitychange",this.visibilityHandler)),"undefined"!=typeof window&&(this.onlineHandler=()=>{this.isConnected||this.isDestroyed||this.provider.connect()},window.addEventListener("online",this.onlineHandler))}removeBrowserEventListeners(){this.visibilityHandler&&"undefined"!=typeof document&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),this.onlineHandler&&"undefined"!=typeof window&&(window.removeEventListener("online",this.onlineHandler),this.onlineHandler=null)}static createSharedWebSocket(e){if(O.sharedWebSocketProvider)return console.warn("Shared WebSocket already exists. Returning existing instance."),O.sharedWebSocketProvider;const t={url:e.url};return e.WebSocketPolyfill&&(t.WebSocketPolyfill=e.WebSocketPolyfill),e.onConnect&&(t.onConnect=e.onConnect),e.onDisconnect&&(t.onDisconnect=e.onDisconnect),e.onStatus&&(t.onStatus=e.onStatus),O.sharedWebSocketProvider=new m(t),console.info("Shared Hocuspocus WebSocket created: "+e.url),O.sharedWebSocketProvider}static destroySharedWebSocket(){O.sharedWebSocketProvider&&(O.sharedWebSocketProvider.destroy(),O.sharedWebSocketProvider=null,console.info("Shared Hocuspocus WebSocket destroyed"))}static getSharedWebSocket(){return O.sharedWebSocketProvider}static with(e){return{create:(t,s,i)=>{const o=i?{...e,...i}:e;return new O(t,s,o)}}}async connect(){if(!this.isSynced&&!this.isDestroyed)return this._connectionStatus="connecting",new Promise(((e,t)=>{this.pendingConnectReject=t,this.connectTimeout=setTimeout((()=>{this.pendingConnectReject=null,this.connectTimeout=null,t(Error("Hocuspocus connection timeout"))}),this.connectionTimeoutMs);const s=()=>{this.connectTimeout&&(clearTimeout(this.connectTimeout),this.connectTimeout=null),this.pendingConnectReject=null,this.provider.off("synced",s),this.isDestroyed||e()};if(this.provider.on("synced",s),this.provider.isSynced)return this.connectTimeout&&(clearTimeout(this.connectTimeout),this.connectTimeout=null),this.pendingConnectReject=null,this.provider.off("synced",s),void e();this.isConnected||this.usesSharedSocket||this.provider.connect()}))}async reconnect(){return this.disconnect(),this.connect()}disconnect(){this.connectTimeout&&(clearTimeout(this.connectTimeout),this.connectTimeout=null),this.pendingConnectReject&&(this.pendingConnectReject=null),this.provider&&(this.usesSharedSocket?this.provider.detach():this.provider.disconnect()),this.isConnected=!1,this.isSynced=!1,this._connectionStatus="disconnected"}destroy(){this.isDestroyed=!0,this.connectTimeout&&(clearTimeout(this.connectTimeout),this.connectTimeout=null),this.pendingConnectReject&&(this.pendingConnectReject=null),this.removeBrowserEventListeners(),this.provider&&this.provider.destroy(),this.isConnected=!1,this.isSynced=!1,this._connectionStatus="disconnected"}}class B{type="remote";name="HttpAssetProvider";_options;constructor(e){this._options=e}static with(e){return{create:()=>new B(e)}}async init(){this._options.quiet||console.info("HttpAssetProvider initialized")}destroy(){}canResolve(e){return!0}async put(e,t){const s=t.id??this.generateUuid(),i={...t,id:s},o=await(this._options.headers?.())??{},n=await this._options.uploadUrl(i),r=this._options.upload??this.defaultUpload;return{id:(await r(n,e,i,o)).id??s,kind:t.kind??"file",mimeType:t.mimeType,size:e.size,createdAt:Date.now(),width:t.width,height:t.height,durationMs:t.durationMs,originalFilename:t.originalFilename}}async resolve(e){return this._options.resolveUrl(e)}async fetch(e){const t=await this._options.resolveUrl(e),s=await(this._options.headers?.())??{},i=await fetch(t,{headers:s});if(!i.ok)throw Error(`[HttpAssetProvider] Failed to fetch asset ${e}: ${i.status} ${i.statusText}`);return i.blob()}async delete(e){if(!this._options.deleteUrl)return;const t=await this._options.deleteUrl(e),s=await(this._options.headers?.())??{},i=await fetch(t,{method:"DELETE",headers:s});if(!i.ok)throw Error(`[HttpAssetProvider] Failed to delete asset ${e}: ${i.status} ${i.statusText}`);this._options.quiet||console.info("HttpAssetProvider: deleted asset "+e)}defaultUpload=async(e,t,s,i)=>{const o=new FormData;o.append("metadata",JSON.stringify(s)),o.append("file",t,s.originalFilename||`${s.id}.${this.extensionFromMime(s.mimeType)}`);const n=await fetch(e,{method:"POST",headers:i,body:o});if(!n.ok)throw Error(`[HttpAssetProvider] Upload failed: ${n.status} ${n.statusText}`);return(n.headers.get("content-type")||"").includes("application/json")?await n.json():{}};extensionFromMime(e){const t=e.indexOf("/");return t>=0?e.slice(t+1):"bin"}generateUuid(){if("undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID)return crypto.randomUUID();const e=crypto.getRandomValues(new Uint8Array(16));e[6]=15&e[6]|64,e[8]=63&e[8]|128;const t=Array.from(e,(e=>e.toString(16).padStart(2,"0"))).join("");return`${t.slice(0,8)}-${t.slice(8,12)}-${t.slice(12,16)}-${t.slice(16,20)}-${t.slice(20)}`}}class H{type="remote";name="PresignedAssetProvider";_options;constructor(e){this._options=e}static with(e){return{create:()=>new H(e)}}async init(){this._options.quiet||console.info("PresignedAssetProvider initialized")}destroy(){}canResolve(e){return!0}async put(e,t){const s=await this._options.getUploadDescriptor(t),i=s.method??"PUT",o={...s.headers??{}};let n;if(o["Content-Type"]||o["content-type"]||(o["Content-Type"]=t.mimeType),"POST"===i&&s.fields){const t=new FormData;for(const[e,i]of Object.entries(s.fields))t.append(e,i);t.append("file",e),n=t,delete o["Content-Type"],delete o["content-type"]}else n=e;const r=await fetch(s.url,{method:i,headers:o,body:n});if(!r.ok)throw Error(`[PresignedAssetProvider] Upload failed: ${r.status} ${r.statusText}`);return{id:s.id,kind:t.kind??"file",mimeType:t.mimeType,size:e.size,createdAt:Date.now(),width:t.width,height:t.height,durationMs:t.durationMs,originalFilename:t.originalFilename}}async resolve(e){return this._options.getDownloadUrl(e)}async fetch(e){const t=await this._options.getDownloadUrl(e),s=await fetch(t);if(!s.ok)throw Error(`[PresignedAssetProvider] Failed to fetch asset ${e}: ${s.status} ${s.statusText}`);return s.blob()}async delete(e){this._options.deleteAsset&&(await this._options.deleteAsset(e),this._options.quiet||console.info("PresignedAssetProvider: deleted asset "+e))}}export{_ as BroadcastSyncProvider,O as HocuspocusSyncProvider,B as HttpAssetProvider,H as PresignedAssetProvider,$ as WebSocketSyncProvider}