kritzel-stencil 0.4.23 → 0.4.25

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/index.cjs.js +3 -46
  2. package/dist/cjs/kritzel-active-users_47.cjs.entry.js +93 -138
  3. package/dist/cjs/loader.cjs.js +1 -1
  4. package/dist/cjs/stencil.cjs.js +1 -1
  5. package/dist/collection/classes/core/core.class.js +4 -0
  6. package/dist/collection/classes/providers/sync/hocuspocus-sync-provider.class.js +1 -19
  7. package/dist/collection/classes/providers/sync/websocket-sync-provider.class.js +2 -27
  8. package/dist/collection/classes/structures/app-state-map.structure.js +1 -4
  9. package/dist/collection/classes/structures/object-map.structure.js +64 -61
  10. package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +20 -32
  11. package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +21 -73
  12. package/dist/collection/constants/version.js +1 -1
  13. package/dist/components/index.js +4 -47
  14. package/dist/components/kritzel-editor.js +20 -18
  15. package/dist/components/kritzel-engine.js +1 -1
  16. package/dist/components/kritzel-settings.js +1 -1
  17. package/dist/components/{p-Cb-xlpsJ.js → p-0fhjWRmT.js} +1 -1
  18. package/dist/components/{p-EijwLEZx.js → p-gJcH-huF.js} +75 -123
  19. package/dist/esm/index.js +3 -46
  20. package/dist/esm/kritzel-active-users_47.entry.js +93 -138
  21. package/dist/esm/loader.js +1 -1
  22. package/dist/esm/stencil.js +1 -1
  23. package/dist/stencil/index.esm.js +3 -46
  24. package/dist/stencil/{p-9e38407e.entry.js → p-dc5783ee.entry.js} +93 -138
  25. package/dist/stencil/stencil.esm.js +1 -1
  26. package/dist/types/classes/providers/sync/hocuspocus-sync-provider.class.d.ts +1 -5
  27. package/dist/types/classes/providers/sync/websocket-sync-provider.class.d.ts +1 -6
  28. package/dist/types/classes/structures/object-map.structure.d.ts +16 -26
  29. package/dist/types/components/core/kritzel-editor/kritzel-editor.d.ts +3 -4
  30. package/dist/types/components/core/kritzel-engine/kritzel-engine.d.ts +3 -12
  31. package/dist/types/components.d.ts +7 -12
  32. package/dist/types/constants/version.d.ts +1 -1
  33. package/dist/types/interfaces/sync-provider.interface.d.ts +5 -11
  34. package/package.json +1 -1
@@ -570,13 +570,10 @@ class WebSocketSyncProvider {
570
570
  provider;
571
571
  isConnected = false;
572
572
  _quiet = false;
573
- _isSynced = false;
574
- _syncedResolvers = [];
575
573
  get awareness() {
576
574
  return this.provider.awareness;
577
575
  }
578
- /** y-websocket applies remote updates with the underlying provider as the Yjs transaction origin. */
579
- get updateOrigin() {
576
+ get rawProvider() {
580
577
  return this.provider;
581
578
  }
582
579
  constructor(docName, doc, options) {
@@ -625,29 +622,11 @@ class WebSocketSyncProvider {
625
622
  }
626
623
  });
627
624
  this.provider.on('sync', (synced) => {
628
- if (!synced) {
629
- return;
630
- }
631
- this._isSynced = true;
632
- this.releaseSyncedWaiters();
633
- if (!this._quiet) {
625
+ if (synced && !this._quiet) {
634
626
  console.info('WebSocket synced');
635
627
  }
636
628
  });
637
629
  }
638
- releaseSyncedWaiters() {
639
- const resolvers = this._syncedResolvers;
640
- this._syncedResolvers = [];
641
- resolvers.forEach(resolve => resolve());
642
- }
643
- async whenSynced() {
644
- if (this._isSynced || this.provider.synced) {
645
- return;
646
- }
647
- return new Promise(resolve => {
648
- this._syncedResolvers.push(resolve);
649
- });
650
- }
651
630
  async connect() {
652
631
  if (this.isConnected) {
653
632
  return;
@@ -679,8 +658,6 @@ class WebSocketSyncProvider {
679
658
  this.provider.disconnect();
680
659
  }
681
660
  this.isConnected = false;
682
- this._isSynced = false;
683
- this.releaseSyncedWaiters();
684
661
  }
685
662
  async reconnect() {
686
663
  this.disconnect();
@@ -691,8 +668,6 @@ class WebSocketSyncProvider {
691
668
  this.provider.destroy();
692
669
  }
693
670
  this.isConnected = false;
694
- this._isSynced = false;
695
- this.releaseSyncedWaiters();
696
671
  }
697
672
  }
698
673
 
@@ -714,12 +689,10 @@ class HocuspocusSyncProvider {
714
689
  _connectionStatus = 'disconnected';
715
690
  visibilityHandler = null;
716
691
  onlineHandler = null;
717
- syncedResolvers = [];
718
692
  get awareness() {
719
693
  return this.provider.awareness;
720
694
  }
721
- /** HocuspocusProvider applies remote updates with itself as the Yjs transaction origin. */
722
- get updateOrigin() {
695
+ get rawProvider() {
723
696
  return this.provider;
724
697
  }
725
698
  get connectionStatus() {
@@ -778,7 +751,6 @@ class HocuspocusSyncProvider {
778
751
  }
779
752
  this.isSynced = true;
780
753
  this._connectionStatus = 'synced';
781
- this.releaseSyncedWaiters();
782
754
  if (!options?.quiet) {
783
755
  console.info(`Hocuspocus synced: ${name}`);
784
756
  }
@@ -989,19 +961,6 @@ class HocuspocusSyncProvider {
989
961
  this.disconnect();
990
962
  return this.connect();
991
963
  }
992
- releaseSyncedWaiters() {
993
- const resolvers = this.syncedResolvers;
994
- this.syncedResolvers = [];
995
- resolvers.forEach(resolve => resolve());
996
- }
997
- async whenSynced() {
998
- if (this.isSynced || this.isDestroyed || this.provider.isSynced) {
999
- return;
1000
- }
1001
- return new Promise(resolve => {
1002
- this.syncedResolvers.push(resolve);
1003
- });
1004
- }
1005
964
  disconnect() {
1006
965
  // Cancel any pending connection attempt
1007
966
  if (this.connectTimeout) {
@@ -1023,7 +982,6 @@ class HocuspocusSyncProvider {
1023
982
  this.isConnected = false;
1024
983
  this.isSynced = false;
1025
984
  this._connectionStatus = 'disconnected';
1026
- this.releaseSyncedWaiters();
1027
985
  }
1028
986
  destroy() {
1029
987
  // Mark as destroyed first to prevent any callbacks from doing work
@@ -1043,7 +1001,6 @@ class HocuspocusSyncProvider {
1043
1001
  this.isConnected = false;
1044
1002
  this.isSynced = false;
1045
1003
  this._connectionStatus = 'disconnected';
1046
- this.releaseSyncedWaiters();
1047
1004
  }
1048
1005
  }
1049
1006
 
@@ -2168,8 +2168,7 @@ const KritzelEditor = class {
2168
2168
  this.login = index.createEvent(this, "login");
2169
2169
  this.isPublicChange = index.createEvent(this, "isPublicChange");
2170
2170
  this.awarenessChange = index.createEvent(this, "awarenessChange");
2171
- this.syncingChange = index.createEvent(this, "syncingChange");
2172
- this.loadingChange = index.createEvent(this, "loadingChange");
2171
+ this.isSyncing = index.createEvent(this, "isSyncing");
2173
2172
  }
2174
2173
  get host() { return index.getElement(this); }
2175
2174
  scaleMax = ABSOLUTE_SCALE_MAX;
@@ -2409,9 +2408,7 @@ const KritzelEditor = class {
2409
2408
  login;
2410
2409
  isPublicChange;
2411
2410
  awarenessChange;
2412
- /** Emitted while remote changes are being applied to the active workspace. Stays silent when the remote holds nothing new. */
2413
- syncingChange;
2414
- loadingChange;
2411
+ isSyncing;
2415
2412
  isEngineReady = false;
2416
2413
  isControlsReady = false;
2417
2414
  isWorkspaceManagerReady = false;
@@ -2433,8 +2430,8 @@ const KritzelEditor = class {
2433
2430
  contextMenuState;
2434
2431
  normalizedControls = [];
2435
2432
  resolvedMoreMenuItems = [];
2436
- isSyncing = false;
2437
2433
  isLoadingOverlayVisible = false;
2434
+ isSyncingIndicatorVisible = false;
2438
2435
  notificationDismissTimeout;
2439
2436
  notificationDismissAnimationTimeout;
2440
2437
  notificationDisplayDurationMs = 5000;
@@ -2917,6 +2914,11 @@ const KritzelEditor = class {
2917
2914
  event.stopPropagation();
2918
2915
  this.awarenessChange.emit(event.detail);
2919
2916
  }
2917
+ handleIsSyncingChange(event) {
2918
+ event.stopPropagation();
2919
+ this.isSyncingIndicatorVisible = event.detail;
2920
+ this.isSyncing.emit(event.detail);
2921
+ }
2920
2922
  handleNotificationsChange(event) {
2921
2923
  event.stopPropagation();
2922
2924
  this.clearNotificationDismissAnimationTimer();
@@ -3230,40 +3232,40 @@ const KritzelEditor = class {
3230
3232
  const isLoggedIn = this.isLoggedIn;
3231
3233
  const shouldShowCurrentUser = isLoggedIn;
3232
3234
  const shouldShowLoginButton = this.isReady && !!this.loginConfig && !isLoggedIn;
3233
- return (index.h(index.Host, { key: 'ff7d505697bd4c4bd840dadcb04a934b7e243a01' }, index.h("div", { key: 'b40076c3c79ad3f879e80c58b412b79266de2ea8', class: "editor-content", style: {
3235
+ return (index.h(index.Host, { key: 'a24350a0c12d99ccb0d0ec3ab5975d0f1bf7f861' }, index.h("div", { key: 'bd7029567aeef8cba5290c2bf9937db1af113475', class: "editor-content", style: {
3234
3236
  opacity: this.isEditorVisible ? '1' : '0',
3235
3237
  visibility: this.isEditorVisible ? 'visible' : 'hidden',
3236
3238
  transition: 'opacity 0.2s ease-in-out, visibility 0.2s ease-in-out',
3237
- } }, index.h("div", { key: '1b1171eea60a36ad06da55a3da564bc356526266', class: "top-left-buttons" }, index.h("kritzel-workspace-manager", { key: '48b59f3f57ace57debc19a295d4fb642e8c3b23d', visible: this.isWorkspaceManagerVisible, workspaces: this.workspaces, activeWorkspace: this.activeWorkspace, terms: this.resolvedTerms, onWorkspaceChange: event => (this.activeWorkspace = event.detail), onIsWorkspaceManagerReady: () => (this.isWorkspaceManagerReady = true) }), index.h("kritzel-back-to-content", { key: 'c66a6faae8bf7115dcce47630ad820af9ba76eb0', visible: this.isBackToContentButtonVisible, text: this.resolvedTerms['backToContent.label'] ?? 'Back to content', onBackToContent: () => this.backToContent() })), this.activeNotification && (index.h("div", { key: '834baedc5f20328deb4ee586f693313306895a71', class: "top-center-notification-layer", role: "presentation" }, index.h("div", { key: 'f800508a2e076ee3e89820ffff4df81842b175b3', class: { 'top-center-notification-container': true, 'is-dismissing': this.isNotificationDismissing }, role: "presentation" }, index.h("kritzel-notification-card", { key: '2835ea84e8fa92c41637adaa29d9005f209b2da9', notification: this.activeNotification, locale: this.locale, onDismiss: this.dismissNotification, onHoverChange: event => this.handleNotificationHoverChange(event) })))), this.contextMenuState && this.contextMenuState.items.length > 0 && (index.h("kritzel-context-menu", { key: '6c49e899b7f933d3f06cd1f8696a590d542214a7', class: "context-menu", items: this.contextMenuState.items, objects: this.contextMenuState.objects, style: {
3239
+ } }, index.h("div", { key: 'f628dbd94d88c9bdb313a59d73ce86302c90e375', class: "top-left-buttons" }, index.h("kritzel-workspace-manager", { key: '5a82d054da31e47d3f8b5c6c403949a5fb97b9dd', visible: this.isWorkspaceManagerVisible, workspaces: this.workspaces, activeWorkspace: this.activeWorkspace, terms: this.resolvedTerms, onWorkspaceChange: event => (this.activeWorkspace = event.detail), onIsWorkspaceManagerReady: () => (this.isWorkspaceManagerReady = true) }), index.h("kritzel-back-to-content", { key: 'a5bccef36c8a49ef2cb7ec029def1c7ba71d1c31', visible: this.isBackToContentButtonVisible, text: this.resolvedTerms['backToContent.label'] ?? 'Back to content', onBackToContent: () => this.backToContent() })), this.activeNotification && (index.h("div", { key: 'c5c359b199528dd7b1b64a6a32ef349037357c5a', class: "top-center-notification-layer", role: "presentation" }, index.h("div", { key: 'a41cd6c67c56626c2b6f0eb12193af1f80d6a254', class: { 'top-center-notification-container': true, 'is-dismissing': this.isNotificationDismissing }, role: "presentation" }, index.h("kritzel-notification-card", { key: 'fb49769368ddf0fc74998a96dd26dc446ff2b386', notification: this.activeNotification, locale: this.locale, onDismiss: this.dismissNotification, onHoverChange: event => this.handleNotificationHoverChange(event) })))), this.contextMenuState && this.contextMenuState.items.length > 0 && (index.h("kritzel-context-menu", { key: '56f38790f477d08761d7542ed3794c52f1f73815', class: "context-menu", items: this.contextMenuState.items, objects: this.contextMenuState.objects, style: {
3238
3240
  position: 'absolute',
3239
3241
  left: `${this.contextMenuState.position.x}px`,
3240
3242
  top: `${this.contextMenuState.position.y}px`,
3241
3243
  zIndex: '10002',
3242
- }, onActionSelected: event => this.handleContextMenuActionSelected(event), onClose: () => this.hideContextMenu() })), index.h("kritzel-sync-indicator", { key: '9622af1634a939d6689d5a133496d536f5d7865f', visible: this.isSyncing, text: "Syncing..." }), index.h("kritzel-loading-overlay", { key: '00f142a3eb8b23fbd0bdb6ec53ae95855434516c', visible: this.isLoadingOverlayVisible, text: this.resolvedTerms['editor.loading'] ?? 'Loading...' }), index.h("kritzel-engine", { key: '39b1d7bfce0a5347fec60cd135d6db8fb4250bfa', ref: el => {
3244
+ }, onActionSelected: event => this.handleContextMenuActionSelected(event), onClose: () => this.hideContextMenu() })), index.h("kritzel-sync-indicator", { key: '4957b8bae0d7087d586c9d0c6da82cae905d7d08', visible: this.isSyncingIndicatorVisible, text: "Syncing..." }), index.h("kritzel-loading-overlay", { key: 'e2e99dc452245a2edf7a575ea5fb60aa91be20df', visible: this.isLoadingOverlayVisible, text: this.resolvedTerms['editor.loading'] ?? 'Loading...' }), index.h("kritzel-engine", { key: 'fcebe1cc1237c8649461f03679f9c76c05dde9c4', ref: el => {
3243
3245
  if (el) {
3244
3246
  this.engineRef = el;
3245
3247
  }
3246
- }, workspace: this.activeWorkspace, activeWorkspaceId: this.activeWorkspaceId, editorId: this.editorId, syncConfig: this.syncConfig, assetStorageConfig: this.assetStorageConfig, user: this.user, scaleMax: this.scaleMax, lockDrawingScale: this.lockDrawingScale, isObjectDistanceFadingActive: this.isObjectDistanceFadingActive, scaleMin: this.scaleMin, cursorTarget: this.cursorTarget, isLoading: this.isLoading, viewportBoundaryLeft: this.viewportBoundaryLeft, viewportBoundaryRight: this.viewportBoundaryRight, viewportBoundaryTop: this.viewportBoundaryTop, viewportBoundaryBottom: this.viewportBoundaryBottom, isPanningEnabled: this.isPanningEnabled, isZoomingEnabled: this.isZoomingEnabled, theme: this.theme, themes: this.themes, licenseKey: this.licenseKey, locale: this.locale, locales: this.locales, fallbackLocale: this.fallbackLocale, debugInfo: this.debugInfo, globalContextMenuItems: this.globalContextMenuItems, objectContextMenuItems: this.objectContextMenuItems, onIsEngineReady: event => this.onEngineReady(event), onWorkspacesChange: event => this.handleWorkspacesChange(event), onActiveWorkspaceChange: event => this.handleActiveWorkspaceChange(event), onObjectsChange: event => this.handleObjectsChange(event), onObjectsAdded: event => this.handleObjectsAdded(event), onObjectsRemoved: event => this.handleObjectsRemoved(event), onObjectsUpdated: event => this.handleObjectsUpdated(event), onUndoStateChange: event => this.handleUndoStateChange(event), onObjectsInViewportChange: event => this.handleObjectsInViewportChange(event), onViewportChange: event => this.handleViewportChange(event), onAwarenessChange: event => this.handleAwarenessChange(event), onNotificationsChange: event => this.handleNotificationsChange(event), onContextMenuStateChange: event => this.handleContextMenuStateChange(event), onSyncingChange: event => (this.isSyncing = event.detail), onLoadingChange: event => (this.isLoadingOverlayVisible = event.detail) }), index.h("kritzel-controls", { key: '9a708f21ab92ecc6953a35f5db8001b2403f330e', visible: this.isControlsVisible, class: { 'keyboard-open': this.isVirtualKeyboardOpen }, ref: el => {
3248
+ }, workspace: this.activeWorkspace, activeWorkspaceId: this.activeWorkspaceId, editorId: this.editorId, syncConfig: this.syncConfig, assetStorageConfig: this.assetStorageConfig, user: this.user, scaleMax: this.scaleMax, lockDrawingScale: this.lockDrawingScale, isObjectDistanceFadingActive: this.isObjectDistanceFadingActive, scaleMin: this.scaleMin, cursorTarget: this.cursorTarget, isLoading: this.isLoading, viewportBoundaryLeft: this.viewportBoundaryLeft, viewportBoundaryRight: this.viewportBoundaryRight, viewportBoundaryTop: this.viewportBoundaryTop, viewportBoundaryBottom: this.viewportBoundaryBottom, isPanningEnabled: this.isPanningEnabled, isZoomingEnabled: this.isZoomingEnabled, theme: this.theme, themes: this.themes, licenseKey: this.licenseKey, locale: this.locale, locales: this.locales, fallbackLocale: this.fallbackLocale, debugInfo: this.debugInfo, globalContextMenuItems: this.globalContextMenuItems, objectContextMenuItems: this.objectContextMenuItems, onIsEngineReady: event => this.onEngineReady(event), onWorkspacesChange: event => this.handleWorkspacesChange(event), onActiveWorkspaceChange: event => this.handleActiveWorkspaceChange(event), onObjectsChange: event => this.handleObjectsChange(event), onObjectsAdded: event => this.handleObjectsAdded(event), onObjectsRemoved: event => this.handleObjectsRemoved(event), onObjectsUpdated: event => this.handleObjectsUpdated(event), onUndoStateChange: event => this.handleUndoStateChange(event), onObjectsInViewportChange: event => this.handleObjectsInViewportChange(event), onViewportChange: event => this.handleViewportChange(event), onAwarenessChange: event => this.handleAwarenessChange(event), onIsSyncing: event => this.handleIsSyncingChange(event), onNotificationsChange: event => this.handleNotificationsChange(event), onContextMenuStateChange: event => this.handleContextMenuStateChange(event), onLoadingChange: event => (this.isLoadingOverlayVisible = event.detail) }), index.h("kritzel-controls", { key: '72b1a3f80bfedb3843d0d54159ad47793db69977', visible: this.isControlsVisible, class: { 'keyboard-open': this.isVirtualKeyboardOpen }, ref: el => {
3247
3249
  if (el) {
3248
3250
  this.controlsRef = el;
3249
3251
  }
3250
- }, controls: this.normalizedControls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState ?? undefined, theme: this.theme, terms: this.resolvedTerms, onIsControlsReady: () => (this.isControlsReady = true) }), index.h("div", { key: '6a2b1cd72099db86952ac31aad9655693d0a6a0d', class: "bottom-left-buttons" }, index.h("kritzel-zoom-panel", { key: 'b3756514bf8de6de8ac7586c626f9759da7f231d', visible: this.isZoomPanelVisible, disabled: !this.isZoomingEnabled, zoomPercent: this.currentZoomPercent, terms: this.resolvedTerms, onZoomIn: () => this.zoomIn(), onZoomOut: () => this.zoomOut() })), index.h("div", { key: 'd5e118bc4d60b867006bac287c5516baf3aa890e', class: "top-right-buttons" }, index.h("kritzel-settings", { key: 'e4065d36beb9283cdbbf3ae9800f4f8011c0b4f0', ref: el => {
3252
+ }, controls: this.normalizedControls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState ?? undefined, theme: this.theme, terms: this.resolvedTerms, onIsControlsReady: () => (this.isControlsReady = true) }), index.h("div", { key: '1684a320f1806ae9fdfcfcff115d01f5788737e7', class: "bottom-left-buttons" }, index.h("kritzel-zoom-panel", { key: '8e85652ded70df043d79c9cd73b226aaefa5d6d4', visible: this.isZoomPanelVisible, disabled: !this.isZoomingEnabled, zoomPercent: this.currentZoomPercent, terms: this.resolvedTerms, onZoomIn: () => this.zoomIn(), onZoomOut: () => this.zoomOut() })), index.h("div", { key: 'a836056bdae11324f4ce79da4b991281c1985358', class: "top-right-buttons" }, index.h("kritzel-settings", { key: 'e68f4e79bc053e03dc37c18ee153758d284b0d34', ref: el => {
3251
3253
  if (el) {
3252
3254
  this.settingsRef = el;
3253
3255
  }
3254
- }, shortcuts: this.shortcuts, availableThemes: this.themes && this.themes.length > 0 ? this.themes.map(t => t.name) : ['light', 'dark'], availableLocales: this.availableLocaleOptions, settings: this.currentSettingsConfig, terms: this.resolvedTerms, onSettingsChange: event => this.handleSettingsChange(event) }), index.h("kritzel-export", { key: '4e18d9b42c9e7fef9433577b983c508559461da2', ref: el => {
3256
+ }, shortcuts: this.shortcuts, availableThemes: this.themes && this.themes.length > 0 ? this.themes.map(t => t.name) : ['light', 'dark'], availableLocales: this.availableLocaleOptions, settings: this.currentSettingsConfig, terms: this.resolvedTerms, onSettingsChange: event => this.handleSettingsChange(event) }), index.h("kritzel-export", { key: 'f1e9ea467d11b55dbe75d8b0252fd7f5ed37a1a2', ref: el => {
3255
3257
  if (el) {
3256
3258
  this.exportRef = el;
3257
3259
  }
3258
- }, workspaceName: this.activeWorkspace?.name || 'workspace', terms: this.resolvedTerms, onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), index.h("kritzel-active-users", { key: '56e6b29c9c3dcd7b68af4a1ef7d34224d592ce34', users: this.activeUsers }), shouldShowCurrentUser && index.h("kritzel-current-user", { key: '3b90bcc7e583fcf3c6845d222ad181ccf234a423', user: this.user, terms: this.resolvedTerms, onClick: () => this.currentUserDialogRef?.open() }), shouldShowCurrentUser && (index.h("kritzel-current-user-dialog", { key: 'f87702c5a708b6afdb2bc3760895e3e3650a4cb5', ref: el => {
3260
+ }, workspaceName: this.activeWorkspace?.name || 'workspace', terms: this.resolvedTerms, onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), index.h("kritzel-active-users", { key: '01bb56bd37bc22e82be5a1175f597cdf724c4834', users: this.activeUsers }), shouldShowCurrentUser && index.h("kritzel-current-user", { key: '5470ef356a4d6332b380e8c75f02f0fccbe861cc', user: this.user, terms: this.resolvedTerms, onClick: () => this.currentUserDialogRef?.open() }), shouldShowCurrentUser && (index.h("kritzel-current-user-dialog", { key: '229551b18f0443944a96f7b8e833ff1aefb001f4', ref: el => {
3259
3261
  if (el) {
3260
3262
  this.currentUserDialogRef = el;
3261
3263
  }
3262
- }, user: this.user, terms: this.resolvedTerms, onLogoutRequest: this.handleCurrentUserLogout })), shouldShowLoginButton && index.h("kritzel-button", { key: '015bc0f4c66352136de66d35d28c20070366933f', onButtonClick: () => this.loginDialogRef?.open() }, this.resolvedTerms['login.dialogTitle'] ?? 'Sign in'), index.h("kritzel-more-menu", { key: 'b3fdf9fdb2a34508002e82dfdef9e9bdbbe57ecc', items: this.resolvedMoreMenuItems, visible: this.isMoreMenuVisible, terms: this.resolvedTerms }), index.h("kritzel-share-dialog", { key: 'bac4fbbfa04c261e2b128e766cc43c1062aec2ff', ref: el => {
3264
+ }, user: this.user, terms: this.resolvedTerms, onLogoutRequest: this.handleCurrentUserLogout })), shouldShowLoginButton && index.h("kritzel-button", { key: '3d9a8ebbe62084e94348265f1f883bc80e625d9c', onButtonClick: () => this.loginDialogRef?.open() }, this.resolvedTerms['login.dialogTitle'] ?? 'Sign in'), index.h("kritzel-more-menu", { key: '9f7f763c330b27068bc371d98eb22a80adbf359e', items: this.resolvedMoreMenuItems, visible: this.isMoreMenuVisible, terms: this.resolvedTerms }), index.h("kritzel-share-dialog", { key: 'c60b860e22394a44270285dea45f02305a05c545', ref: el => {
3263
3265
  if (el) {
3264
3266
  this.shareDialogRef = el;
3265
3267
  }
3266
- }, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, terms: this.resolvedTerms, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (index.h("kritzel-login-dialog", { key: 'a7d52bad9e752ad546adf80d257c6a46de031e76', ref: el => {
3268
+ }, isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, terms: this.resolvedTerms, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (index.h("kritzel-login-dialog", { key: '84d0fab11722a7be1d5824b3284f000502277dfc', ref: el => {
3267
3269
  if (el) {
3268
3270
  this.loginDialogRef = el;
3269
3271
  }
@@ -22306,13 +22308,17 @@ class KritzelObjectMap {
22306
22308
  _awarenessChangeHandler = null;
22307
22309
  _awarenessChangeCallbacks = [];
22308
22310
  _objectsChangeCallbacks = [];
22309
- _remoteChangesCallbacks = [];
22310
- _docUpdateHandler = null;
22311
- _remoteOrigins = new Set();
22312
- _appliedRemoteChanges = false;
22313
22311
  _lastAwarenessEmitTime = 0;
22314
22312
  _awarenessEmitTimeout = null;
22315
22313
  AWARENESS_THROTTLE_INTERVAL = 100; // milliseconds
22314
+ // Identities of the raw network-provider instances (e.g. WebsocketProvider, HocuspocusProvider)
22315
+ // that Yjs tags as transaction origin for remote updates; local providers (IndexedDB,
22316
+ // BroadcastChannel) never appear here, so their transactions never toggle isSyncing.
22317
+ _networkTransactionOrigins = new Set();
22318
+ _isSyncing = false;
22319
+ _syncingChangeCallbacks = [];
22320
+ _beforeTransactionHandler = null;
22321
+ _afterAllTransactionsHandler = null;
22316
22322
  /**
22317
22323
  * When true, update() only modifies local state (quadtree + idMap)
22318
22324
  * without writing to Yjs. Used during drag operations to avoid
@@ -22333,44 +22339,12 @@ class KritzelObjectMap {
22333
22339
  return this._isReady;
22334
22340
  }
22335
22341
  /**
22336
- * Promise that settles when network providers finish connecting and complete
22337
- * their Yjs sync handshake. Resolves immediately if no network providers.
22342
+ * Promise that settles when network providers finish connecting.
22343
+ * Resolves immediately if no network providers. Awaited by the loading overlay.
22338
22344
  */
22339
22345
  whenNetworkSynced() {
22340
22346
  return this._networkSyncPromise ?? Promise.resolve();
22341
22347
  }
22342
- /**
22343
- * Whether any configured provider synchronizes over the network.
22344
- */
22345
- get hasNetworkSyncProvider() {
22346
- return this._providers.some(provider => provider.type === 'network');
22347
- }
22348
- /**
22349
- * Whether remote-originated changes can be told apart from local writes.
22350
- * Requires every network provider to expose its Yjs update origin.
22351
- */
22352
- get supportsRemoteChangeDetection() {
22353
- const networkProviders = this._providers.filter(provider => provider.type === 'network');
22354
- return networkProviders.length > 0 && networkProviders.every(provider => provider.updateOrigin !== undefined);
22355
- }
22356
- /**
22357
- * Whether at least one update originating from a network provider has been
22358
- * applied to this document. Stays `false` when the remote held no data the
22359
- * local document was missing, because Yjs suppresses empty updates.
22360
- */
22361
- get hasAppliedRemoteChanges() {
22362
- return this._appliedRemoteChanges;
22363
- }
22364
- /**
22365
- * Registers a callback invoked whenever a network provider applies changes to this document.
22366
- * @returns A function that unregisters the callback.
22367
- */
22368
- onRemoteChangesApplied(callback) {
22369
- this._remoteChangesCallbacks.push(callback);
22370
- return () => {
22371
- this._remoteChangesCallbacks = this._remoteChangesCallbacks.filter(registered => registered !== callback);
22372
- };
22373
- }
22374
22348
  /**
22375
22349
  * Returns the Yjs Awareness instance, if a network provider is available.
22376
22350
  */
@@ -22520,6 +22494,22 @@ class KritzelObjectMap {
22520
22494
  this._objectsChangeCallbacks = this._objectsChangeCallbacks.filter(existingCallback => existingCallback !== callback);
22521
22495
  };
22522
22496
  }
22497
+ /**
22498
+ * Whether the local copy currently differs from data a remote peer is in the process of merging in.
22499
+ */
22500
+ get isSyncing() {
22501
+ return this._isSyncing;
22502
+ }
22503
+ /**
22504
+ * Registers a callback invoked when a remote sync starts (`true`) or finishes (`false`).
22505
+ * "Starts" fires from `beforeTransaction`, before the incoming remote update is merged.
22506
+ */
22507
+ onSyncingChange(callback) {
22508
+ this._syncingChangeCallbacks.push(callback);
22509
+ return () => {
22510
+ this._syncingChangeCallbacks = this._syncingChangeCallbacks.filter(existingCallback => existingCallback !== callback);
22511
+ };
22512
+ }
22523
22513
  /**
22524
22514
  * Returns the Yjs UndoManager instance for managing undo/redo operations.
22525
22515
  * @returns The UndoManager instance, or `null` if not initialized
@@ -22613,22 +22603,36 @@ class KritzelObjectMap {
22613
22603
  const providerList = this._providers.map(p => `${p.constructor.name} (${p.type})`).join(', ');
22614
22604
  console.info(`[Kritzel] Workspace sync providers initialized for ${docName}: ${providerList || 'none'}`);
22615
22605
  }
22616
- // Registered before any provider connects so no remote update can be missed.
22617
- for (const provider of this._providers) {
22618
- if (provider.type === 'network' && provider.updateOrigin !== undefined) {
22619
- this._remoteOrigins.add(provider.updateOrigin);
22606
+ // Only network providers (WebSocket, Hocuspocus) represent a genuine remote peer;
22607
+ // local providers (IndexedDB, BroadcastChannel) never appear as a transaction origin here,
22608
+ // so loading from IndexedDB or a same-device tab never toggles isSyncing.
22609
+ this._networkTransactionOrigins = new Set(this._providers
22610
+ .filter(p => p.type === 'network')
22611
+ .map(p => p.rawProvider)
22612
+ .filter((origin) => origin != null));
22613
+ // Fires before a remote-origin transaction integrates its update, so this always
22614
+ // precedes any local/remote reconciliation for that update.
22615
+ this._beforeTransactionHandler = transaction => {
22616
+ if (this._isSyncing || !this._networkTransactionOrigins.has(transaction.origin)) {
22617
+ return;
22620
22618
  }
22621
- }
22622
- this._docUpdateHandler = (_update, origin) => {
22623
- if (!this._remoteOrigins.has(origin)) {
22619
+ this._isSyncing = true;
22620
+ this._syncingChangeCallbacks.forEach(callback => callback(true));
22621
+ };
22622
+ // Fires once a batch of transactions (possibly several remote updates) has fully settled.
22623
+ this._afterAllTransactionsHandler = (_doc, transactions) => {
22624
+ if (!this._isSyncing) {
22624
22625
  return;
22625
22626
  }
22626
- this._appliedRemoteChanges = true;
22627
- for (const callback of [...this._remoteChangesCallbacks]) {
22628
- callback();
22627
+ const hadRemoteTransaction = transactions.some(transaction => this._networkTransactionOrigins.has(transaction.origin));
22628
+ if (!hadRemoteTransaction) {
22629
+ return;
22629
22630
  }
22631
+ this._isSyncing = false;
22632
+ this._syncingChangeCallbacks.forEach(callback => callback(false));
22630
22633
  };
22631
- this._ydoc.on('update', this._docUpdateHandler);
22634
+ this._ydoc.on('beforeTransaction', this._beforeTransactionHandler);
22635
+ this._ydoc.on('afterAllTransactions', this._afterAllTransactionsHandler);
22632
22636
  // captureTimeout is effectively infinite — undo-step boundaries are
22633
22637
  // marked explicitly via markUndoBoundary() in insert/remove/reset and
22634
22638
  // at stroke/gesture boundaries in the tools. This prevents a single
@@ -22682,15 +22686,12 @@ class KritzelObjectMap {
22682
22686
  if (showSyncProviderInfo && networkProviders.length > 0) {
22683
22687
  console.info(`[Kritzel] Workspace network providers connecting in background: ${networkProviders.length}`);
22684
22688
  }
22685
- const networkConnectPromises = networkProviders.map(provider => provider
22686
- .connect()
22687
- .then(() => provider.whenSynced?.())
22688
- .catch(err => {
22689
+ const networkConnectPromises = networkProviders.map(provider => provider.connect().catch(err => {
22689
22690
  if (showSyncProviderInfo) {
22690
22691
  console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
22691
22692
  }
22692
22693
  }));
22693
- // Settles once remote data has been reconciled, not merely once the transport connected
22694
+ // Network sync awaited by loading overlay to show remote workspace hydration
22694
22695
  this._networkSyncPromise = networkConnectPromises.length > 0 ? Promise.allSettled(networkConnectPromises).then(() => undefined) : null;
22695
22696
  this._isReady = true;
22696
22697
  // Find the first provider that exposes awareness (network providers)
@@ -23348,13 +23349,6 @@ class KritzelObjectMap {
23348
23349
  this._objectsMap.unobserve(this._objectsObserver);
23349
23350
  this._objectsObserver = null;
23350
23351
  }
23351
- if (this._ydoc && this._docUpdateHandler) {
23352
- this._ydoc.off('update', this._docUpdateHandler);
23353
- }
23354
- this._docUpdateHandler = null;
23355
- this._remoteOrigins.clear();
23356
- this._remoteChangesCallbacks = [];
23357
- this._appliedRemoteChanges = false;
23358
23352
  // Remove undo manager event listeners
23359
23353
  if (this._undoManager) {
23360
23354
  if (this._stackItemAddedHandler) {
@@ -23387,11 +23381,20 @@ class KritzelObjectMap {
23387
23381
  p.destroy();
23388
23382
  });
23389
23383
  this._providers = [];
23384
+ this._networkTransactionOrigins = new Set();
23390
23385
  // Clear object map references
23391
23386
  this._objectsMap = null;
23392
23387
  this._metadataMap = null;
23393
23388
  // Destroy Y.Doc
23394
23389
  if (this._ydoc) {
23390
+ if (this._beforeTransactionHandler) {
23391
+ this._ydoc.off('beforeTransaction', this._beforeTransactionHandler);
23392
+ this._beforeTransactionHandler = null;
23393
+ }
23394
+ if (this._afterAllTransactionsHandler) {
23395
+ this._ydoc.off('afterAllTransactions', this._afterAllTransactionsHandler);
23396
+ this._afterAllTransactionsHandler = null;
23397
+ }
23395
23398
  this._ydoc.destroy();
23396
23399
  this._ydoc = null;
23397
23400
  }
@@ -23401,6 +23404,8 @@ class KritzelObjectMap {
23401
23404
  this._workspaceId = null;
23402
23405
  this._isReady = false;
23403
23406
  this._temporaryItemsCount = 0;
23407
+ this._isSyncing = false;
23408
+ this._syncingChangeCallbacks = [];
23404
23409
  }
23405
23410
  }
23406
23411
 
@@ -23850,10 +23855,7 @@ class KritzelAppStateMap {
23850
23855
  if (showSyncProviderInfo && networkProviders.length > 0) {
23851
23856
  console.info(`[Kritzel] App-state network providers connecting in background: ${networkProviders.length}`);
23852
23857
  }
23853
- const networkConnectPromises = networkProviders.map(provider => provider
23854
- .connect()
23855
- .then(() => provider.whenSynced?.())
23856
- .catch(err => {
23858
+ const networkConnectPromises = networkProviders.map(provider => provider.connect().catch(err => {
23857
23859
  if (showSyncProviderInfo) {
23858
23860
  console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
23859
23861
  }
@@ -25254,6 +25256,10 @@ class KritzelCore {
25254
25256
  objectsMap.onAwarenessChange(states => {
25255
25257
  this.engine.emitAwarenessChange(states);
25256
25258
  });
25259
+ // Forward remote sync start/finish to the engine as events
25260
+ objectsMap.onSyncingChange(isSyncing => {
25261
+ this.engine.emitIsSyncing(isSyncing);
25262
+ });
25257
25263
  // Rebuild anchor index after loading objects
25258
25264
  this._anchorManager.rebuildIndex();
25259
25265
  this.engine.emitObjectsChange();
@@ -27951,16 +27957,15 @@ const KritzelEngine = class {
27951
27957
  viewportChange;
27952
27958
  /** Emitted when the awareness state changes (remote user cursors, presence). */
27953
27959
  awarenessChange;
27960
+ /** Emitted when a remote sync starts (`true`, data differs from the local copy) or finishes (`false`). */
27961
+ isSyncing;
27954
27962
  /** Emitted when notifications change. */
27955
27963
  notificationsChange;
27956
27964
  /** Emitted when context-menu visibility, items, or position changes. */
27957
27965
  contextMenuStateChange;
27958
27966
  /** Emitted when the combined loading state (external `isLoading` prop or internal workspace loading) changes. */
27959
27967
  loadingChange;
27960
- /** Emitted while remote changes are being applied to the active workspace. Stays silent when the remote holds nothing new. */
27961
- syncingChange;
27962
27968
  forceUpdate = 0;
27963
- isSyncing = false;
27964
27969
  /** Manually triggers the `objectsSelectionChange` event. */
27965
27970
  async triggerSelectionChange() {
27966
27971
  this.objectsSelectionChange.emit();
@@ -29544,7 +29549,6 @@ const KritzelEngine = class {
29544
29549
  _sceneBootstrapOpacity = 1;
29545
29550
  _sceneBootstrapTransitionEnabled = false;
29546
29551
  _sceneBootstrapTransitionTimer = null;
29547
- _remoteChangesUnsubscribe = null;
29548
29552
  _defaultUndoState = {
29549
29553
  canUndo: false,
29550
29554
  canRedo: false,
@@ -29561,17 +29565,6 @@ const KritzelEngine = class {
29561
29565
  this.core.store.state.isLoading = this._isWorkspaceLoading || this.isLoading;
29562
29566
  this.loadingChange.emit(this.core.store.state.isLoading);
29563
29567
  }
29564
- syncSyncingState(isSyncing) {
29565
- if (this.isSyncing === isSyncing) {
29566
- return;
29567
- }
29568
- this.isSyncing = isSyncing;
29569
- this.syncingChange.emit(isSyncing);
29570
- }
29571
- clearRemoteChangesSubscription() {
29572
- this._remoteChangesUnsubscribe?.();
29573
- this._remoteChangesUnsubscribe = null;
29574
- }
29575
29568
  isNetworkAvailable() {
29576
29569
  return typeof navigator !== 'undefined' && navigator.onLine;
29577
29570
  }
@@ -29674,16 +29667,15 @@ const KritzelEngine = class {
29674
29667
  this.objectsInViewportChange = index.createEvent(this, "objectsInViewportChange");
29675
29668
  this.viewportChange = index.createEvent(this, "viewportChange");
29676
29669
  this.awarenessChange = index.createEvent(this, "awarenessChange");
29670
+ this.isSyncing = index.createEvent(this, "isSyncing");
29677
29671
  this.notificationsChange = index.createEvent(this, "notificationsChange");
29678
29672
  this.contextMenuStateChange = index.createEvent(this, "contextMenuStateChange");
29679
29673
  this.loadingChange = index.createEvent(this, "loadingChange");
29680
- this.syncingChange = index.createEvent(this, "syncingChange");
29681
29674
  this.core = new KritzelCore(this);
29682
29675
  }
29683
29676
  disconnectedCallback() {
29684
29677
  this.throttledPointerMoveMulti.cancel();
29685
29678
  this._revealedImageKeys.clear();
29686
- this.clearRemoteChangesSubscription();
29687
29679
  if (this._sceneBootstrapTransitionTimer !== null) {
29688
29680
  clearTimeout(this._sceneBootstrapTransitionTimer);
29689
29681
  this._sceneBootstrapTransitionTimer = null;
@@ -29938,7 +29930,6 @@ const KritzelEngine = class {
29938
29930
  this._workspaceInitializationTargetKey = targetKey;
29939
29931
  try {
29940
29932
  await initializationPromise;
29941
- this.trackRemoteSynchronization();
29942
29933
  }
29943
29934
  finally {
29944
29935
  // Only the most recent init clears loading state to prevent race conditions
@@ -29950,45 +29941,6 @@ const KritzelEngine = class {
29950
29941
  }
29951
29942
  }
29952
29943
  }
29953
- /**
29954
- * Reports syncing only while remote data is actually being reconciled into the
29955
- * active workspace. Yjs suppresses updates that carry nothing new, so a workspace
29956
- * that already matches the remote never reports syncing at all.
29957
- */
29958
- trackRemoteSynchronization() {
29959
- this.clearRemoteChangesSubscription();
29960
- this.syncSyncingState(false);
29961
- const initializedObjects = this.core.store.objects;
29962
- if (!initializedObjects?.hasNetworkSyncProvider || !this.isNetworkAvailable()) {
29963
- return;
29964
- }
29965
- if (initializedObjects.supportsRemoteChangeDetection) {
29966
- this._remoteChangesUnsubscribe = initializedObjects.onRemoteChangesApplied(() => {
29967
- if (this.core.store.objects !== initializedObjects) {
29968
- return;
29969
- }
29970
- this.syncSyncingState(true);
29971
- });
29972
- // The network provider may have already applied remote changes between
29973
- // object-map initialization and this subscription (e.g. an already-connected
29974
- // shared socket resolves the sync handshake almost immediately). Without this
29975
- // check that update is missed entirely and the indicator never appears.
29976
- if (initializedObjects.hasAppliedRemoteChanges) {
29977
- this.syncSyncingState(true);
29978
- }
29979
- }
29980
- else {
29981
- // Custom providers without an update origin cannot be diffed, so fall back to reporting the whole handshake.
29982
- this.syncSyncingState(true);
29983
- }
29984
- void initializedObjects.whenNetworkSynced().finally(() => {
29985
- if (this.core.store.objects !== initializedObjects) {
29986
- return;
29987
- }
29988
- this.clearRemoteChangesSubscription();
29989
- this.syncSyncingState(false);
29990
- });
29991
- }
29992
29944
  emitObjectsChange() {
29993
29945
  const objectsMap = this.core.store.objects;
29994
29946
  if (!objectsMap) {
@@ -30022,6 +29974,9 @@ const KritzelEngine = class {
30022
29974
  emitAwarenessChange(states) {
30023
29975
  this.awarenessChange.emit(states);
30024
29976
  }
29977
+ emitIsSyncing(isSyncing) {
29978
+ this.isSyncing.emit(isSyncing);
29979
+ }
30025
29980
  emitObjectsInViewportChange() {
30026
29981
  const visibleObjects = this.core.store.objectsInViewport;
30027
29982
  this.objectsInViewportChange.emit(visibleObjects);
@@ -32133,7 +32088,7 @@ const KritzelPortal = class {
32133
32088
  * This file is auto-generated by the version bump scripts.
32134
32089
  * Do not modify manually.
32135
32090
  */
32136
- const KRITZEL_VERSION = '0.4.23';
32091
+ const KRITZEL_VERSION = '0.4.25';
32137
32092
 
32138
32093
  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-tab-panel{display:none}.settings-tab-panel.is-active{display:block}.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)}`;
32139
32094