kritzel-stencil 0.1.74 → 0.1.75

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 (35) hide show
  1. package/dist/cjs/index.cjs.js +131 -86
  2. package/dist/cjs/kritzel-active-users_42.cjs.entry.js +43 -14
  3. package/dist/cjs/{workspace.migrations-Dyt35LBC.js → workspace.migrations-DkmVO6dE.js} +106 -44
  4. package/dist/collection/classes/core/viewport.class.js +9 -3
  5. package/dist/collection/classes/managers/anchor.manager.js +101 -44
  6. package/dist/collection/classes/providers/broadcast-sync-provider.class.js +5 -0
  7. package/dist/collection/classes/providers/hocuspocus-sync-provider.class.js +120 -85
  8. package/dist/collection/classes/providers/indexeddb-sync-provider.class.js +5 -0
  9. package/dist/collection/classes/providers/websocket-sync-provider.class.js +5 -0
  10. package/dist/collection/classes/structures/app-state-map.structure.js +15 -4
  11. package/dist/collection/classes/structures/object-map.structure.js +17 -5
  12. package/dist/collection/constants/version.js +1 -1
  13. package/dist/components/index.js +1 -1
  14. package/dist/components/kritzel-editor.js +1 -1
  15. package/dist/components/kritzel-engine.js +1 -1
  16. package/dist/components/kritzel-settings.js +1 -1
  17. package/dist/components/{p-B4Oqnl55.js → p-D0MQFmqi.js} +1 -1
  18. package/dist/components/p-WmxufeOo.js +9 -0
  19. package/dist/esm/index.js +132 -87
  20. package/dist/esm/kritzel-active-users_42.entry.js +43 -14
  21. package/dist/esm/{workspace.migrations-B99F1MdT.js → workspace.migrations-D48_Bqvh.js} +106 -44
  22. package/dist/stencil/index.esm.js +1 -1
  23. package/dist/stencil/p-0dbd9a2f.entry.js +9 -0
  24. package/dist/stencil/{p-B99F1MdT.js → p-D48_Bqvh.js} +1 -1
  25. package/dist/stencil/stencil.esm.js +1 -1
  26. package/dist/types/classes/managers/anchor.manager.d.ts +4 -0
  27. package/dist/types/classes/providers/broadcast-sync-provider.class.d.ts +2 -0
  28. package/dist/types/classes/providers/hocuspocus-sync-provider.class.d.ts +37 -1
  29. package/dist/types/classes/providers/indexeddb-sync-provider.class.d.ts +2 -0
  30. package/dist/types/classes/providers/websocket-sync-provider.class.d.ts +2 -0
  31. package/dist/types/constants/version.d.ts +1 -1
  32. package/dist/types/interfaces/sync-provider.interface.d.ts +16 -0
  33. package/package.json +1 -1
  34. package/dist/components/p-RJWe82kG.js +0 -9
  35. package/dist/stencil/p-2a60e1bc.entry.js +0 -9
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var workspace_migrations = require('./workspace.migrations-Dyt35LBC.js');
3
+ var workspace_migrations = require('./workspace.migrations-DkmVO6dE.js');
4
4
  var Y = require('yjs');
5
5
  var yWebsocket = require('y-websocket');
6
6
  require('y-indexeddb');
@@ -360,6 +360,7 @@ const readVarUint = decoder => {
360
360
  * This is a lightweight alternative to y-webrtc for browser-tab-only sync
361
361
  */
362
362
  class BroadcastSyncProvider {
363
+ type = 'local';
363
364
  doc;
364
365
  channel;
365
366
  _synced = false;
@@ -441,6 +442,10 @@ class BroadcastSyncProvider {
441
442
  disconnect() {
442
443
  // BroadcastChannel doesn't have explicit disconnect
443
444
  }
445
+ async reconnect() {
446
+ this.disconnect();
447
+ return this.connect();
448
+ }
444
449
  destroy() {
445
450
  this.doc.off('update', this.handleDocUpdate);
446
451
  this.channel.close();
@@ -451,6 +456,7 @@ class BroadcastSyncProvider {
451
456
  * WebSocket sync provider for real-time collaboration
452
457
  */
453
458
  class WebSocketSyncProvider {
459
+ type = 'network';
454
460
  provider;
455
461
  isConnected = false;
456
462
  _quiet = false;
@@ -539,6 +545,10 @@ class WebSocketSyncProvider {
539
545
  }
540
546
  this.isConnected = false;
541
547
  }
548
+ async reconnect() {
549
+ this.disconnect();
550
+ return this.connect();
551
+ }
542
552
  destroy() {
543
553
  if (this.provider) {
544
554
  this.provider.destroy();
@@ -552,6 +562,7 @@ class WebSocketSyncProvider {
552
562
  * Supports multiplexing - multiple documents can share the same WebSocket connection
553
563
  */
554
564
  class HocuspocusSyncProvider {
565
+ type = 'network';
555
566
  provider;
556
567
  isConnected = false;
557
568
  isSynced = false;
@@ -559,16 +570,87 @@ class HocuspocusSyncProvider {
559
570
  isDestroyed = false;
560
571
  connectTimeout = null;
561
572
  pendingConnectReject = null;
573
+ connectionTimeoutMs;
574
+ _connectionStatus = 'disconnected';
575
+ visibilityHandler = null;
576
+ onlineHandler = null;
562
577
  get awareness() {
563
578
  return this.provider.awareness;
564
579
  }
580
+ get connectionStatus() {
581
+ return this._connectionStatus;
582
+ }
565
583
  // Static shared WebSocket instance for multiplexing
566
584
  static sharedWebSocketProvider = null;
567
585
  constructor(docName, doc, options) {
568
586
  const name = options?.name || docName;
569
587
  const url = options?.url || 'ws://localhost:1234';
588
+ this.connectionTimeoutMs = options?.connectionTimeout ?? 10000;
570
589
  // Use provided websocketProvider or the static shared one
571
590
  const websocketProvider = options?.websocketProvider || HocuspocusSyncProvider.sharedWebSocketProvider;
591
+ // Build reconnect config from options
592
+ const reconnectConfig = {};
593
+ if (options?.delay !== undefined)
594
+ reconnectConfig.delay = options.delay;
595
+ if (options?.factor !== undefined)
596
+ reconnectConfig.factor = options.factor;
597
+ if (options?.maxAttempts !== undefined)
598
+ reconnectConfig.maxAttempts = options.maxAttempts;
599
+ if (options?.minDelay !== undefined)
600
+ reconnectConfig.minDelay = options.minDelay;
601
+ if (options?.maxDelay !== undefined)
602
+ reconnectConfig.maxDelay = options.maxDelay;
603
+ const onConnect = () => {
604
+ if (this.isDestroyed) {
605
+ return;
606
+ }
607
+ this.isConnected = true;
608
+ this._connectionStatus = 'connected';
609
+ if (!options?.quiet) {
610
+ console.info(`Hocuspocus connected: ${name}`);
611
+ }
612
+ if (options?.onConnect) {
613
+ options.onConnect();
614
+ }
615
+ };
616
+ const onDisconnect = () => {
617
+ if (this.isDestroyed) {
618
+ return;
619
+ }
620
+ this.isConnected = false;
621
+ this.isSynced = false;
622
+ this._connectionStatus = 'disconnected';
623
+ if (!options?.quiet) {
624
+ console.info(`Hocuspocus disconnected: ${name}`);
625
+ }
626
+ if (options?.onDisconnect) {
627
+ options.onDisconnect();
628
+ }
629
+ };
630
+ const onSynced = () => {
631
+ if (this.isDestroyed) {
632
+ return;
633
+ }
634
+ this.isSynced = true;
635
+ this._connectionStatus = 'synced';
636
+ if (!options?.quiet) {
637
+ console.info(`Hocuspocus synced: ${name}`);
638
+ }
639
+ if (options?.onSynced) {
640
+ options.onSynced();
641
+ }
642
+ };
643
+ const onStatus = (data) => {
644
+ if (this.isDestroyed) {
645
+ return;
646
+ }
647
+ if (data.status === 'connecting') {
648
+ this._connectionStatus = 'connecting';
649
+ }
650
+ if (options?.onStatus) {
651
+ options.onStatus(data);
652
+ }
653
+ };
572
654
  if (websocketProvider) {
573
655
  // Multiplexing mode - use shared WebSocket connection
574
656
  this.usesSharedSocket = true;
@@ -577,48 +659,11 @@ class HocuspocusSyncProvider {
577
659
  name,
578
660
  document: doc,
579
661
  token: options?.token || null,
580
- onStatus: (data) => {
581
- if (options?.onStatus) {
582
- options.onStatus(data);
583
- }
584
- },
585
- onConnect: () => {
586
- if (this.isConnected || this.isDestroyed) {
587
- return;
588
- }
589
- this.isConnected = true;
590
- if (!options?.quiet) {
591
- console.info(`Hocuspocus connected: ${name}`);
592
- }
593
- if (options?.onConnect) {
594
- options.onConnect();
595
- }
596
- },
597
- onDisconnect: () => {
598
- if (this.isDestroyed || (!this.isConnected && !this.isSynced)) {
599
- return;
600
- }
601
- this.isConnected = false;
602
- this.isSynced = false;
603
- if (!options?.quiet) {
604
- console.info(`Hocuspocus disconnected: ${name}`);
605
- }
606
- if (options?.onDisconnect) {
607
- options.onDisconnect();
608
- }
609
- },
610
- onSynced: () => {
611
- if (this.isSynced || this.isDestroyed) {
612
- return;
613
- }
614
- this.isSynced = true;
615
- if (!options?.quiet) {
616
- console.info(`Hocuspocus synced: ${name}`);
617
- }
618
- if (options?.onSynced) {
619
- options.onSynced();
620
- }
621
- },
662
+ onStatus,
663
+ onConnect,
664
+ onDisconnect,
665
+ onSynced,
666
+ ...reconnectConfig,
622
667
  };
623
668
  // Add optional settings
624
669
  if (options?.forceSyncInterval !== undefined) {
@@ -643,48 +688,11 @@ class HocuspocusSyncProvider {
643
688
  document: doc,
644
689
  token: options?.token || null,
645
690
  autoConnect: false,
646
- onStatus: (data) => {
647
- if (options?.onStatus) {
648
- options.onStatus(data);
649
- }
650
- },
651
- onConnect: () => {
652
- if (this.isConnected || this.isDestroyed) {
653
- return;
654
- }
655
- this.isConnected = true;
656
- if (!options?.quiet) {
657
- console.info(`Hocuspocus connected: ${name}`);
658
- }
659
- if (options?.onConnect) {
660
- options.onConnect();
661
- }
662
- },
663
- onDisconnect: () => {
664
- if (this.isDestroyed || (!this.isConnected && !this.isSynced)) {
665
- return;
666
- }
667
- this.isConnected = false;
668
- this.isSynced = false;
669
- if (!options?.quiet) {
670
- console.info(`Hocuspocus disconnected: ${name}`);
671
- }
672
- if (options?.onDisconnect) {
673
- options.onDisconnect();
674
- }
675
- },
676
- onSynced: () => {
677
- if (this.isSynced || this.isDestroyed) {
678
- return;
679
- }
680
- this.isSynced = true;
681
- if (!options?.quiet) {
682
- console.info(`Hocuspocus synced: ${name}`);
683
- }
684
- if (options?.onSynced) {
685
- options.onSynced();
686
- }
687
- },
691
+ onStatus,
692
+ onConnect,
693
+ onDisconnect,
694
+ onSynced,
695
+ ...reconnectConfig,
688
696
  };
689
697
  // Add optional settings
690
698
  if (options?.forceSyncInterval !== undefined) {
@@ -701,6 +709,35 @@ class HocuspocusSyncProvider {
701
709
  console.info(`Hocuspocus Provider initialized: ${url}/${name}`);
702
710
  }
703
711
  }
712
+ this.setupBrowserEventListeners();
713
+ }
714
+ setupBrowserEventListeners() {
715
+ if (typeof document !== 'undefined') {
716
+ this.visibilityHandler = () => {
717
+ if (document.visibilityState === 'visible' && !this.isConnected && !this.isDestroyed) {
718
+ this.provider.connect();
719
+ }
720
+ };
721
+ document.addEventListener('visibilitychange', this.visibilityHandler);
722
+ }
723
+ if (typeof window !== 'undefined') {
724
+ this.onlineHandler = () => {
725
+ if (!this.isConnected && !this.isDestroyed) {
726
+ this.provider.connect();
727
+ }
728
+ };
729
+ window.addEventListener('online', this.onlineHandler);
730
+ }
731
+ }
732
+ removeBrowserEventListeners() {
733
+ if (this.visibilityHandler && typeof document !== 'undefined') {
734
+ document.removeEventListener('visibilitychange', this.visibilityHandler);
735
+ this.visibilityHandler = null;
736
+ }
737
+ if (this.onlineHandler && typeof window !== 'undefined') {
738
+ window.removeEventListener('online', this.onlineHandler);
739
+ this.onlineHandler = null;
740
+ }
704
741
  }
705
742
  /**
706
743
  * Create a shared WebSocket connection for multiplexing
@@ -763,6 +800,7 @@ class HocuspocusSyncProvider {
763
800
  if (this.isSynced || this.isDestroyed) {
764
801
  return;
765
802
  }
803
+ this._connectionStatus = 'connecting';
766
804
  return new Promise((resolve, reject) => {
767
805
  // Store reject function so we can cancel the connection if destroyed
768
806
  this.pendingConnectReject = reject;
@@ -770,7 +808,7 @@ class HocuspocusSyncProvider {
770
808
  this.pendingConnectReject = null;
771
809
  this.connectTimeout = null;
772
810
  reject(new Error('Hocuspocus connection timeout'));
773
- }, 10000); // 10 second timeout
811
+ }, this.connectionTimeoutMs);
774
812
  const syncHandler = () => {
775
813
  if (this.connectTimeout) {
776
814
  clearTimeout(this.connectTimeout);
@@ -800,6 +838,10 @@ class HocuspocusSyncProvider {
800
838
  }
801
839
  });
802
840
  }
841
+ async reconnect() {
842
+ this.disconnect();
843
+ return this.connect();
844
+ }
803
845
  disconnect() {
804
846
  // Cancel any pending connection attempt
805
847
  if (this.connectTimeout) {
@@ -820,6 +862,7 @@ class HocuspocusSyncProvider {
820
862
  }
821
863
  this.isConnected = false;
822
864
  this.isSynced = false;
865
+ this._connectionStatus = 'disconnected';
823
866
  }
824
867
  destroy() {
825
868
  // Mark as destroyed first to prevent any callbacks from doing work
@@ -832,11 +875,13 @@ class HocuspocusSyncProvider {
832
875
  if (this.pendingConnectReject) {
833
876
  this.pendingConnectReject = null; // Don't reject, just abandon the promise
834
877
  }
878
+ this.removeBrowserEventListeners();
835
879
  if (this.provider) {
836
880
  this.provider.destroy();
837
881
  }
838
882
  this.isConnected = false;
839
883
  this.isSynced = false;
884
+ this._connectionStatus = 'disconnected';
840
885
  }
841
886
  }
842
887
 
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var index = require('./index-Dc7LOVhs.js');
4
- var workspace_migrations = require('./workspace.migrations-Dyt35LBC.js');
4
+ var workspace_migrations = require('./workspace.migrations-DkmVO6dE.js');
5
5
  var Y = require('yjs');
6
6
  require('y-websocket');
7
7
  require('y-indexeddb');
@@ -19968,6 +19968,7 @@ class KritzelViewport {
19968
19968
  if (event.pointerType === 'touch' || event.pointerType === 'pen') {
19969
19969
  const activePointers = Array.from(this._core.store.state.pointers.values());
19970
19970
  if (activePointers.length === 2) {
19971
+ this._core.store.state.objects?.clearCursorPosition();
19971
19972
  const currentPath = this._core.store.currentPath;
19972
19973
  if (currentPath) {
19973
19974
  this._core.store.state.objects.remove(obj => obj.id === currentPath.id);
@@ -20028,10 +20029,15 @@ class KritzelViewport {
20028
20029
  const hostRect = this._core.store.state.host.getBoundingClientRect();
20029
20030
  const xRelativeToHost = event.clientX - hostRect.left;
20030
20031
  const yRelativeToHost = event.clientY - hostRect.top;
20031
- this._core.store.state.pointerX = (xRelativeToHost - this._core.store.state.translateX) / this._core.store.state.scale;
20032
- this._core.store.state.pointerY = (yRelativeToHost - this._core.store.state.translateY) / this._core.store.state.scale;
20033
- this._core.store.state.objects?.updateCursorPosition(this._core.store.state.pointerX, this._core.store.state.pointerY);
20034
20032
  const activePointers = Array.from(this._core.store.state.pointers.values());
20033
+ if (this._core.store.state.isScaling || activePointers.length > 1) {
20034
+ this._core.store.state.objects?.clearCursorPosition();
20035
+ }
20036
+ else {
20037
+ this._core.store.state.pointerX = (xRelativeToHost - this._core.store.state.translateX) / this._core.store.state.scale;
20038
+ this._core.store.state.pointerY = (yRelativeToHost - this._core.store.state.translateY) / this._core.store.state.scale;
20039
+ this._core.store.state.objects?.updateCursorPosition(this._core.store.state.pointerX, this._core.store.state.pointerY);
20040
+ }
20035
20041
  if (activePointers.length === 2) {
20036
20042
  const firstTouchX = activePointers[0].clientX - this._core.store.offsetX;
20037
20043
  const firstTouchY = activePointers[0].clientY - this._core.store.offsetY;
@@ -21495,16 +21501,28 @@ class KritzelObjectMap {
21495
21501
  this.handleObjectsChange(event);
21496
21502
  };
21497
21503
  this._objectsMap.observe(this._objectsObserver);
21498
- // Connect all providers in parallel (settle individually so one failure doesn't block the rest)
21499
- const results = await Promise.allSettled(this._providers.map(p => p.connect()));
21500
- results.forEach((result, i) => {
21504
+ // Separate local providers (IndexedDB, BroadcastChannel) from network providers (Hocuspocus, WebSocket)
21505
+ // Local providers are awaited so data is available immediately; network providers sync in the background
21506
+ // via Yjs observers that are already registered above.
21507
+ const localProviders = this._providers.filter(p => p.type === 'local');
21508
+ const networkProviders = this._providers.filter(p => p.type === 'network');
21509
+ // Await local providers for immediate data availability
21510
+ const localResults = await Promise.allSettled(localProviders.map(p => p.connect()));
21511
+ localResults.forEach((result, i) => {
21501
21512
  if (result.status === 'rejected') {
21502
- console.error(`[Kritzel] Sync provider "${this._providers[i]?.constructor.name}" failed to connect:`, result.reason);
21513
+ console.error(`[Kritzel] Sync provider "${localProviders[i]?.constructor.name}" failed to connect:`, result.reason);
21503
21514
  }
21504
21515
  });
21516
+ // Connect network providers in the background (remote data arrives via Yjs observers)
21517
+ for (const provider of networkProviders) {
21518
+ provider.connect().catch(err => {
21519
+ console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
21520
+ });
21521
+ }
21505
21522
  this._isReady = true;
21506
21523
  // Find the first provider that exposes awareness (network providers)
21507
- for (const provider of this._providers) {
21524
+ // Awareness is available immediately after provider construction, before connect() resolves
21525
+ for (const provider of networkProviders) {
21508
21526
  if (provider.awareness) {
21509
21527
  this._awareness = provider.awareness;
21510
21528
  break;
@@ -22451,13 +22469,24 @@ class KritzelAppStateMap {
22451
22469
  this.handleWorkspacesChange(event);
22452
22470
  };
22453
22471
  this._workspacesMap.observe(this._workspacesObserver);
22454
- // Connect all providers in parallel (settle individually so one failure doesn't block the rest)
22455
- const results = await Promise.allSettled(this._providers.map(p => p.connect()));
22456
- results.forEach((result, i) => {
22472
+ // Separate local providers (IndexedDB, BroadcastChannel) from network providers (Hocuspocus, WebSocket)
22473
+ // Local providers are awaited so data is available immediately; network providers sync in the background
22474
+ // via Yjs observers that are already registered above.
22475
+ const localProviders = this._providers.filter(p => p.type === 'local');
22476
+ const networkProviders = this._providers.filter(p => p.type === 'network');
22477
+ // Await local providers for immediate data availability
22478
+ const localResults = await Promise.allSettled(localProviders.map(p => p.connect()));
22479
+ localResults.forEach((result, i) => {
22457
22480
  if (result.status === 'rejected') {
22458
- console.error(`[Kritzel] Sync provider "${this._providers[i]?.constructor.name}" failed to connect:`, result.reason);
22481
+ console.error(`[Kritzel] Sync provider "${localProviders[i]?.constructor.name}" failed to connect:`, result.reason);
22459
22482
  }
22460
22483
  });
22484
+ // Connect network providers in the background (remote data arrives via Yjs observers)
22485
+ for (const provider of networkProviders) {
22486
+ provider.connect().catch(err => {
22487
+ console.error(`[Kritzel] Network sync provider "${provider.constructor.name}" failed to connect:`, err);
22488
+ });
22489
+ }
22461
22490
  this._isReady = true;
22462
22491
  // Run any pending schema migrations before loading data
22463
22492
  const quietMigrations = !core.store.state.debugInfo.showMigrationInfo;
@@ -28408,7 +28437,7 @@ const KritzelPortal = class {
28408
28437
  * This file is auto-generated by the version bump scripts.
28409
28438
  * Do not modify manually.
28410
28439
  */
28411
- const KRITZEL_VERSION = '0.1.74';
28440
+ const KRITZEL_VERSION = '0.1.75';
28412
28441
 
28413
28442
  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)}`;
28414
28443