kritzel-stencil 0.1.76 → 0.1.77

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 (40) hide show
  1. package/dist/cjs/index.cjs.js +2 -330
  2. package/dist/cjs/kritzel-active-users_42.cjs.entry.js +36 -18
  3. package/dist/cjs/{workspace.migrations-DkmVO6dE.js → workspace.migrations-OULs44dI.js} +331 -2
  4. package/dist/collection/classes/objects/selection-group.class.js +2 -0
  5. package/dist/collection/components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.css +6 -1
  6. package/dist/collection/components/core/kritzel-awareness-cursors/kritzel-awareness-cursors.js +5 -2
  7. package/dist/collection/components/core/kritzel-editor/kritzel-editor.css +2 -0
  8. package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +12 -6
  9. package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +18 -8
  10. package/dist/collection/components/ui/kritzel-utility-panel/kritzel-utility-panel.css +1 -0
  11. package/dist/collection/constants/version.js +1 -1
  12. package/dist/components/index.js +1 -1
  13. package/dist/components/kritzel-awareness-cursors.js +1 -1
  14. package/dist/components/kritzel-controls.js +1 -1
  15. package/dist/components/kritzel-editor.js +1 -1
  16. package/dist/components/kritzel-engine.js +1 -1
  17. package/dist/components/kritzel-settings.js +1 -1
  18. package/dist/components/kritzel-tool-config.js +1 -1
  19. package/dist/components/kritzel-utility-panel.js +1 -1
  20. package/dist/components/{p-DvIEvoZu.js → p-BK1hLBTd.js} +1 -1
  21. package/dist/components/{p-jdYmu4SA.js → p-BdGcOXa5.js} +2 -2
  22. package/dist/components/{p-CsoDfhD5.js → p-C_X8stam.js} +1 -1
  23. package/dist/components/p-WotNmY5q.js +1 -0
  24. package/dist/components/{p-31FVoNWR.js → p-XS5J5W5_.js} +1 -1
  25. package/dist/components/{p-CJ2eHeoV.js → p-_CqLIbO6.js} +1 -1
  26. package/dist/components/{p-2OYw6GJ7.js → p-_LbtY-TA.js} +1 -1
  27. package/dist/esm/index.js +2 -331
  28. package/dist/esm/kritzel-active-users_42.entry.js +36 -18
  29. package/dist/esm/{workspace.migrations-D48_Bqvh.js → workspace.migrations-D6whgl7G.js} +331 -1
  30. package/dist/stencil/index.esm.js +1 -1
  31. package/dist/stencil/p-8fe1ec39.entry.js +9 -0
  32. package/dist/stencil/p-D6whgl7G.js +1 -0
  33. package/dist/stencil/stencil.esm.js +1 -1
  34. package/dist/types/classes/objects/selection-group.class.d.ts +1 -0
  35. package/dist/types/components.d.ts +8 -2
  36. package/dist/types/constants/version.d.ts +1 -1
  37. package/package.json +1 -1
  38. package/dist/components/p-xNwOWoiT.js +0 -1
  39. package/dist/stencil/p-775a7246.entry.js +0 -9
  40. package/dist/stencil/p-D48_Bqvh.js +0 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var workspace_migrations = require('./workspace.migrations-DkmVO6dE.js');
3
+ var workspace_migrations = require('./workspace.migrations-OULs44dI.js');
4
4
  var Y = require('yjs');
5
5
  var yWebsocket = require('y-websocket');
6
6
  require('y-indexeddb');
@@ -557,340 +557,13 @@ class WebSocketSyncProvider {
557
557
  }
558
558
  }
559
559
 
560
- /**
561
- * Hocuspocus sync provider for real-time collaboration
562
- * Supports multiplexing - multiple documents can share the same WebSocket connection
563
- */
564
- class HocuspocusSyncProvider {
565
- type = 'network';
566
- provider;
567
- isConnected = false;
568
- isSynced = false;
569
- usesSharedSocket = false;
570
- isDestroyed = false;
571
- connectTimeout = null;
572
- pendingConnectReject = null;
573
- connectionTimeoutMs;
574
- _connectionStatus = 'disconnected';
575
- visibilityHandler = null;
576
- onlineHandler = null;
577
- get awareness() {
578
- return this.provider.awareness;
579
- }
580
- get connectionStatus() {
581
- return this._connectionStatus;
582
- }
583
- // Static shared WebSocket instance for multiplexing
584
- static sharedWebSocketProvider = null;
585
- constructor(docName, doc, options) {
586
- const name = options?.name || docName;
587
- const url = options?.url || 'ws://localhost:1234';
588
- this.connectionTimeoutMs = options?.connectionTimeout ?? 10000;
589
- // Use provided websocketProvider or the static shared one
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
- };
654
- if (websocketProvider) {
655
- // Multiplexing mode - use shared WebSocket connection
656
- this.usesSharedSocket = true;
657
- const config = {
658
- websocketProvider,
659
- name,
660
- document: doc,
661
- token: options?.token || null,
662
- onStatus,
663
- onConnect,
664
- onDisconnect,
665
- onSynced,
666
- ...reconnectConfig,
667
- };
668
- // Add optional settings
669
- if (options?.forceSyncInterval !== undefined) {
670
- config.forceSyncInterval = options.forceSyncInterval;
671
- }
672
- if (options?.onAuthenticationFailed) {
673
- config.onAuthenticationFailed = options.onAuthenticationFailed;
674
- }
675
- this.provider = new workspace_migrations.HocuspocusProvider(config);
676
- // Must call attach() explicitly when using shared socket
677
- this.provider.attach();
678
- if (!options?.quiet) {
679
- console.info(`Hocuspocus Provider initialized (multiplexed): ${name}`);
680
- }
681
- }
682
- else {
683
- // Standalone mode - create own WebSocket connection
684
- this.usesSharedSocket = false;
685
- const config = {
686
- url,
687
- name,
688
- document: doc,
689
- token: options?.token || null,
690
- autoConnect: false,
691
- onStatus,
692
- onConnect,
693
- onDisconnect,
694
- onSynced,
695
- ...reconnectConfig,
696
- };
697
- // Add optional settings
698
- if (options?.forceSyncInterval !== undefined) {
699
- config.forceSyncInterval = options.forceSyncInterval;
700
- }
701
- if (options?.onAuthenticationFailed) {
702
- config.onAuthenticationFailed = options.onAuthenticationFailed;
703
- }
704
- if (options?.WebSocketPolyfill) {
705
- config.WebSocketPolyfill = options.WebSocketPolyfill;
706
- }
707
- this.provider = new workspace_migrations.HocuspocusProvider(config);
708
- if (!options?.quiet) {
709
- console.info(`Hocuspocus Provider initialized: ${url}/${name}`);
710
- }
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
- }
741
- }
742
- /**
743
- * Create a shared WebSocket connection for multiplexing
744
- * Call this once to create a shared connection that multiple providers can use
745
- */
746
- static createSharedWebSocket(options) {
747
- if (HocuspocusSyncProvider.sharedWebSocketProvider) {
748
- console.warn('Shared WebSocket already exists. Returning existing instance.');
749
- return HocuspocusSyncProvider.sharedWebSocketProvider;
750
- }
751
- const config = {
752
- url: options.url,
753
- };
754
- if (options.WebSocketPolyfill) {
755
- config.WebSocketPolyfill = options.WebSocketPolyfill;
756
- }
757
- if (options.onConnect) {
758
- config.onConnect = options.onConnect;
759
- }
760
- if (options.onDisconnect) {
761
- config.onDisconnect = options.onDisconnect;
762
- }
763
- if (options.onStatus) {
764
- config.onStatus = options.onStatus;
765
- }
766
- HocuspocusSyncProvider.sharedWebSocketProvider = new workspace_migrations.HocuspocusProviderWebsocket(config);
767
- console.info(`Shared Hocuspocus WebSocket created: ${options.url}`);
768
- return HocuspocusSyncProvider.sharedWebSocketProvider;
769
- }
770
- /**
771
- * Destroy the shared WebSocket connection
772
- * Call this when you're done with all multiplexed providers
773
- */
774
- static destroySharedWebSocket() {
775
- if (HocuspocusSyncProvider.sharedWebSocketProvider) {
776
- HocuspocusSyncProvider.sharedWebSocketProvider.destroy();
777
- HocuspocusSyncProvider.sharedWebSocketProvider = null;
778
- console.info('Shared Hocuspocus WebSocket destroyed');
779
- }
780
- }
781
- /**
782
- * Get the shared WebSocket provider instance (if it exists)
783
- */
784
- static getSharedWebSocket() {
785
- return HocuspocusSyncProvider.sharedWebSocketProvider;
786
- }
787
- /**
788
- * Static factory method for creating HocuspocusSyncProvider with configuration options
789
- * Returns a ProviderFactory that can be used in sync configuration
790
- */
791
- static with(options) {
792
- return {
793
- create: (docName, doc, runtimeOptions) => {
794
- const mergedOptions = runtimeOptions ? { ...options, ...runtimeOptions } : options;
795
- return new HocuspocusSyncProvider(docName, doc, mergedOptions);
796
- },
797
- };
798
- }
799
- async connect() {
800
- if (this.isSynced || this.isDestroyed) {
801
- return;
802
- }
803
- this._connectionStatus = 'connecting';
804
- return new Promise((resolve, reject) => {
805
- // Store reject function so we can cancel the connection if destroyed
806
- this.pendingConnectReject = reject;
807
- this.connectTimeout = setTimeout(() => {
808
- this.pendingConnectReject = null;
809
- this.connectTimeout = null;
810
- reject(new Error('Hocuspocus connection timeout'));
811
- }, this.connectionTimeoutMs);
812
- const syncHandler = () => {
813
- if (this.connectTimeout) {
814
- clearTimeout(this.connectTimeout);
815
- this.connectTimeout = null;
816
- }
817
- this.pendingConnectReject = null;
818
- this.provider.off('synced', syncHandler);
819
- if (!this.isDestroyed) {
820
- resolve();
821
- }
822
- };
823
- this.provider.on('synced', syncHandler);
824
- // If already synced, resolve immediately
825
- if (this.provider.isSynced) {
826
- if (this.connectTimeout) {
827
- clearTimeout(this.connectTimeout);
828
- this.connectTimeout = null;
829
- }
830
- this.pendingConnectReject = null;
831
- this.provider.off('synced', syncHandler);
832
- resolve();
833
- return;
834
- }
835
- // Connect if not already connected (standalone mode only)
836
- if (!this.isConnected && !this.usesSharedSocket) {
837
- this.provider.connect();
838
- }
839
- });
840
- }
841
- async reconnect() {
842
- this.disconnect();
843
- return this.connect();
844
- }
845
- disconnect() {
846
- // Cancel any pending connection attempt
847
- if (this.connectTimeout) {
848
- clearTimeout(this.connectTimeout);
849
- this.connectTimeout = null;
850
- }
851
- if (this.pendingConnectReject) {
852
- this.pendingConnectReject = null; // Don't reject, just abandon the promise
853
- }
854
- if (this.provider) {
855
- if (this.usesSharedSocket) {
856
- // Detach from shared socket instead of disconnecting
857
- this.provider.detach();
858
- }
859
- else {
860
- this.provider.disconnect();
861
- }
862
- }
863
- this.isConnected = false;
864
- this.isSynced = false;
865
- this._connectionStatus = 'disconnected';
866
- }
867
- destroy() {
868
- // Mark as destroyed first to prevent any callbacks from doing work
869
- this.isDestroyed = true;
870
- // Cancel any pending connection attempt
871
- if (this.connectTimeout) {
872
- clearTimeout(this.connectTimeout);
873
- this.connectTimeout = null;
874
- }
875
- if (this.pendingConnectReject) {
876
- this.pendingConnectReject = null; // Don't reject, just abandon the promise
877
- }
878
- this.removeBrowserEventListeners();
879
- if (this.provider) {
880
- this.provider.destroy();
881
- }
882
- this.isConnected = false;
883
- this.isSynced = false;
884
- this._connectionStatus = 'disconnected';
885
- }
886
- }
887
-
888
560
  exports.APP_STATE_MIGRATIONS = workspace_migrations.APP_STATE_MIGRATIONS;
889
561
  exports.CURRENT_APP_STATE_SCHEMA_VERSION = workspace_migrations.CURRENT_APP_STATE_SCHEMA_VERSION;
890
562
  exports.CURRENT_WORKSPACE_SCHEMA_VERSION = workspace_migrations.CURRENT_WORKSPACE_SCHEMA_VERSION;
891
563
  exports.DEFAULT_BRUSH_CONFIG = workspace_migrations.DEFAULT_BRUSH_CONFIG;
892
564
  exports.DEFAULT_LINE_TOOL_CONFIG = workspace_migrations.DEFAULT_LINE_TOOL_CONFIG;
893
565
  exports.DEFAULT_TEXT_CONFIG = workspace_migrations.DEFAULT_TEXT_CONFIG;
566
+ exports.HocuspocusSyncProvider = workspace_migrations.HocuspocusSyncProvider;
894
567
  exports.IndexedDBSyncProvider = workspace_migrations.IndexedDBSyncProvider;
895
568
  Object.defineProperty(exports, "KritzelAlignment", {
896
569
  enumerable: true,
@@ -923,5 +596,4 @@ exports.darkTheme = workspace_migrations.darkTheme;
923
596
  exports.lightTheme = workspace_migrations.lightTheme;
924
597
  exports.runMigrations = workspace_migrations.runMigrations;
925
598
  exports.BroadcastSyncProvider = BroadcastSyncProvider;
926
- exports.HocuspocusSyncProvider = HocuspocusSyncProvider;
927
599
  exports.WebSocketSyncProvider = WebSocketSyncProvider;
@@ -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-DkmVO6dE.js');
4
+ var workspace_migrations = require('./workspace.migrations-OULs44dI.js');
5
5
  var Y = require('yjs');
6
6
  require('y-websocket');
7
7
  require('y-indexeddb');
@@ -176,7 +176,7 @@ const KritzelAvatar = class {
176
176
  };
177
177
  KritzelAvatar.style = kritzelAvatarCss();
178
178
 
179
- const kritzelAwarenessCursorsCss = () => `:host{display:block;position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:9500}.awareness-cursor{position:absolute;top:0;left:0;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, opacity 300ms ease;will-change:transform}.awareness-cursor.stale{opacity:0}.awareness-cursor.tracking-object{transition-duration:0ms}.cursor-arrow{filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.cursor-label{position:absolute;left:16px;top:16px;white-space:nowrap;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;font-size:var(--kritzel-awareness-cursor-label-font-size, 12px);color:var(--kritzel-awareness-cursor-label-text-color, #ffffff);padding:2px 8px;border-radius:4px;line-height:1.4;font-weight:500;pointer-events:none;user-select:none}.edge-indicator{position:absolute;top:-12px;left:-12px;width:24px;height:24px;display:flex;align-items:center;justify-content:center;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, opacity 300ms ease;will-change:transform;pointer-events:auto;user-select:none;cursor:pointer}.edge-indicator.stale{opacity:0}.edge-indicator.tracking-object{transition-duration:0ms}.edge-arrow{position:absolute;filter:drop-shadow(0 1px 3px rgba(0, 0, 0, 0.3))}.edge-label{position:absolute;white-space:nowrap;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;font-size:var(--kritzel-awareness-cursor-label-font-size, 12px);color:var(--kritzel-awareness-cursor-label-text-color, #ffffff);padding:2px 8px;border-radius:4px;line-height:1.4;font-weight:500;pointer-events:none;opacity:0;transform-origin:center;transition:opacity 150ms ease}.edge-indicator:hover .edge-label{opacity:1}.remote-selection-box{position:absolute;top:0;left:0;border-width:2px;border-style:solid;pointer-events:none;will-change:transform, width, height;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, width var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, height var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out}`;
179
+ const kritzelAwarenessCursorsCss = () => `:host{display:block;position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:1}.awareness-cursor{position:absolute;top:0;left:0;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, opacity 300ms ease;will-change:transform}.awareness-cursor.stale{opacity:0}.awareness-cursor.tracking-object{transition-duration:0ms}.cursor-arrow{filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.cursor-label{position:absolute;left:16px;top:16px;white-space:nowrap;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;font-size:var(--kritzel-awareness-cursor-label-font-size, 12px);color:var(--kritzel-awareness-cursor-label-text-color, #ffffff);padding:2px 8px;border-radius:4px;line-height:1.4;font-weight:500;pointer-events:none;user-select:none}.edge-indicator{position:absolute;top:-12px;left:-12px;width:24px;height:24px;display:flex;align-items:center;justify-content:center;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, opacity 300ms ease;will-change:transform;pointer-events:auto;user-select:none;cursor:pointer}.edge-indicator.stale{opacity:0}.edge-indicator.tracking-object{transition-duration:0ms}.edge-arrow{position:absolute;filter:drop-shadow(0 1px 3px rgba(0, 0, 0, 0.3));transition:opacity 300ms ease}.edge-arrow.stale{opacity:0}.edge-label{position:absolute;white-space:nowrap;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;font-size:var(--kritzel-awareness-cursor-label-font-size, 12px);color:var(--kritzel-awareness-cursor-label-text-color, #ffffff);padding:2px 8px;border-radius:4px;line-height:1.4;font-weight:500;pointer-events:none;opacity:0;transform-origin:center;transition:opacity 150ms ease}.edge-indicator:hover .edge-label{opacity:1}.remote-selection-box{position:absolute;top:0;left:0;border-width:2px;border-style:solid;pointer-events:none;will-change:transform, width, height;transition:transform var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, width var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out, height var(--kritzel-awareness-cursor-transition-duration, 100ms) ease-out}`;
180
180
 
181
181
  const STALE_THRESHOLD_MS = 10_000;
182
182
  const REMOVE_THRESHOLD_MS = 30_000;
@@ -258,6 +258,9 @@ const KritzelAwarenessCursors = class {
258
258
  updated.delete(clientId);
259
259
  changed = true;
260
260
  }
261
+ else if (!changed && now - cursor.lastCursorMove > STALE_THRESHOLD_MS) {
262
+ changed = true;
263
+ }
261
264
  }
262
265
  if (changed) {
263
266
  this.remoteCursors = updated;
@@ -353,7 +356,7 @@ const KritzelAwarenessCursors = class {
353
356
  }
354
357
  render() {
355
358
  const cursors = Array.from(this.remoteCursors.values());
356
- return (index.h(index.Host, { key: '4dd962322c7e955b9038c55cb10f8ffda1e0b246' }, cursors.map(remoteCursor => {
359
+ return (index.h(index.Host, { key: '5a28def6e024249c4309c087502cde9f219f5421' }, cursors.map(remoteCursor => {
357
360
  if (!remoteCursor.cursor)
358
361
  return null;
359
362
  // When a remote user is actively drawing, derive cursor position from
@@ -429,7 +432,7 @@ const KritzelAwarenessCursors = class {
429
432
  const displayName = this.getUserDisplayName(cursor.user);
430
433
  return (index.h("div", { key: `edge-${cursor.clientId}`, class: { 'edge-indicator': true, stale, 'tracking-object': trackingObject }, style: {
431
434
  transform: `translate(${clamped.x}px, ${clamped.y}px)`,
432
- } }, index.h("svg", { class: "edge-arrow", width: "16", height: "16", viewBox: "0 0 16 16", style: { transform: `rotate(${arrowDeg}deg)` } }, index.h("path", { d: "M8 1L14 13H2L8 1Z", fill: color, stroke: "#ffffff", "stroke-width": "1.5", "stroke-linejoin": "round" })), index.h("span", { class: "edge-label", style: {
435
+ } }, index.h("svg", { class: { 'edge-arrow': true, stale }, width: "16", height: "16", viewBox: "0 0 16 16", style: { transform: `rotate(${arrowDeg}deg)` } }, index.h("path", { d: "M8 1L14 13H2L8 1Z", fill: color, stroke: "#ffffff", "stroke-width": "1.5", "stroke-linejoin": "round" })), index.h("span", { class: "edge-label", style: {
433
436
  backgroundColor: color,
434
437
  transform: `translate(${labelX}px, ${labelY}px)`,
435
438
  } }, displayName)));
@@ -1854,7 +1857,7 @@ const DEFAULT_SHAPE_CONFIG = {
1854
1857
  const ABSOLUTE_SCALE_MAX = 1000;
1855
1858
  const ABSOLUTE_SCALE_MIN = 0.0001;
1856
1859
 
1857
- const kritzelEditorCss = () => `kritzel-editor{display:flex;margin:0;position:relative;overflow:hidden;width:100%;height:100%;align-items:center;justify-content:center;touch-action:manipulation;user-select:none;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}kritzel-controls{position:absolute;bottom:var(--kritzel-editor-controls-bottom, 14px);transition:transform var(--kritzel-editor-controls-transition-duration, 0.1s) var(--kritzel-editor-controls-transition, ease-in-out)}kritzel-controls.keyboard-open{transform:var(--kritzel-editor-controls-transform, translateY(300%))}.top-left-buttons{position:absolute;top:var(--kritzel-editor-top-left-buttons-top, 14px);left:var(--kritzel-editor-top-left-buttons-left, 14px);display:flex;align-items:flex-start;gap:8px}.top-right-buttons{position:absolute;top:var(--kritzel-editor-top-right-buttons-top, 14px);right:var(--kritzel-editor-top-right-buttons-right, 14px);display:flex;align-items:center;gap:8px}.top-right-button{display:flex;align-items:center;justify-content:center;width:50px;height:50px;padding:0;border:var(--kritzel-split-button-border, 1px solid #ebebeb);border-radius:var(--kritzel-split-button-border-radius, 12px);background-color:var(--kritzel-split-button-background-color, #ffffff);cursor:var(--kritzel-global-pointer-cursor, pointer);box-shadow:var(--kritzel-split-button-box-shadow, 0 0 3px rgba(0, 0, 0, 0.08));transition:background-color 150ms ease;-webkit-tap-highlight-color:transparent}.top-right-button:hover{background-color:#f5f5f5}.top-right-button:active{background-color:#ebebeb}`;
1860
+ const kritzelEditorCss = () => `kritzel-editor{display:flex;margin:0;position:relative;overflow:hidden;width:100%;height:100%;align-items:center;justify-content:center;touch-action:manipulation;user-select:none;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}kritzel-controls{position:absolute;bottom:var(--kritzel-editor-controls-bottom, 14px);transition:transform var(--kritzel-editor-controls-transition-duration, 0.1s) var(--kritzel-editor-controls-transition, ease-in-out)}kritzel-controls.keyboard-open{transform:var(--kritzel-editor-controls-transform, translateY(300%))}.top-left-buttons{position:absolute;top:var(--kritzel-editor-top-left-buttons-top, 14px);left:var(--kritzel-editor-top-left-buttons-left, 14px);display:flex;align-items:flex-start;gap:8px;z-index:10000}.top-right-buttons{position:absolute;top:var(--kritzel-editor-top-right-buttons-top, 14px);right:var(--kritzel-editor-top-right-buttons-right, 14px);display:flex;align-items:center;gap:8px;z-index:10000}.top-right-button{display:flex;align-items:center;justify-content:center;width:50px;height:50px;padding:0;border:var(--kritzel-split-button-border, 1px solid #ebebeb);border-radius:var(--kritzel-split-button-border-radius, 12px);background-color:var(--kritzel-split-button-background-color, #ffffff);cursor:var(--kritzel-global-pointer-cursor, pointer);box-shadow:var(--kritzel-split-button-box-shadow, 0 0 3px rgba(0, 0, 0, 0.08));transition:background-color 150ms ease;-webkit-tap-highlight-color:transparent}.top-right-button:hover{background-color:#f5f5f5}.top-right-button:active{background-color:#ebebeb}`;
1858
1861
 
1859
1862
  const KritzelEditor = class {
1860
1863
  constructor(hostRef) {
@@ -1888,7 +1891,11 @@ const KritzelEditor = class {
1888
1891
  showSyncProviderInfo: true,
1889
1892
  showMigrationInfo: true,
1890
1893
  };
1891
- user;
1894
+ user = {
1895
+ id: `guest-1`,
1896
+ displayName: 'Guest',
1897
+ isGuest: false,
1898
+ };
1892
1899
  activeUsers;
1893
1900
  controls = [
1894
1901
  {
@@ -2033,7 +2040,8 @@ const KritzelEditor = class {
2033
2040
  isControlsVisible = true;
2034
2041
  isUtilityPanelVisible = true;
2035
2042
  syncConfig = {
2036
- providers: [workspace_migrations.IndexedDBSyncProvider]
2043
+ appStateId: 'kritzel-app-test',
2044
+ providers: [workspace_migrations.HocuspocusSyncProvider, workspace_migrations.IndexedDBSyncProvider]
2037
2045
  };
2038
2046
  /** Optional login configuration. When provided, a "Sign in" button is shown that opens a login dialog with the configured providers. */
2039
2047
  loginConfig;
@@ -2550,7 +2558,7 @@ const KritzelEditor = class {
2550
2558
  const isLoggedIn = this.isLoggedIn;
2551
2559
  const shouldShowCurrentUser = isLoggedIn;
2552
2560
  const shouldShowLoginButton = !!this.loginConfig && !isLoggedIn;
2553
- return (index.h(index.Host, { key: 'd1944655652eb2939e3696ed70050baaca0871e2' }, index.h("div", { key: 'ce5dfd9b8f8d9927e9a6ace4056f9053163c7e0a', class: "top-left-buttons" }, index.h("kritzel-workspace-manager", { key: 'e127a667c6e4e853bb4f0c9ffd0edaa6d80d9dfa', workspaces: this.workspaces, activeWorkspace: this.activeWorkspace, onWorkspaceChange: event => (this.activeWorkspace = event.detail), onIsWorkspaceManagerReady: () => (this.isWorkspaceManagerReady = true) }), index.h("kritzel-back-to-content", { key: 'a9abb378f3c79f356d2164cbddf6badba10188a8', visible: this.isBackToContentButtonVisible, onBackToContent: () => this.backToContent() })), index.h("kritzel-engine", { key: 'c5f764580f3623eeb9381e54769a891a48c04620', ref: el => (this.engineRef = el), workspace: this.activeWorkspace, activeWorkspaceId: this.activeWorkspaceId, editorId: this.editorId, syncConfig: this.syncConfig, user: this.user, scaleMax: this.scaleMax, lockDrawingScale: this.lockDrawingScale, scaleMin: this.scaleMin, viewportBoundaryLeft: this.viewportBoundaryLeft, viewportBoundaryRight: this.viewportBoundaryRight, viewportBoundaryTop: this.viewportBoundaryTop, viewportBoundaryBottom: this.viewportBoundaryBottom, wheelEnabled: this.wheelEnabled, theme: this.currentTheme, 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) }), index.h("kritzel-controls", { key: '2479b6979d5b2b6c55a873bd1d8468b5fd967f72', class: { 'keyboard-open': this.isVirtualKeyboardOpen }, style: { display: this.isControlsVisible ? 'flex' : 'none' }, ref: el => (this.controlsRef = el), controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState, theme: this.currentTheme, onIsControlsReady: () => (this.isControlsReady = true) }), index.h("div", { key: 'ec6ac72ba154abe0738259464f20cae9e7dbdd9e', class: "top-right-buttons" }, index.h("kritzel-settings", { key: 'f68f791d673b30331e55f358b9c71b3e2a9a8388', ref: el => (this.settingsRef = el), shortcuts: this.shortcuts, editorId: this.editorId, onSettingsChange: event => this.handleSettingsChange(event) }), index.h("kritzel-export", { key: 'dfe5a051caccee504df4f347705303fa1f812556', ref: el => (this.exportRef = el), workspaceName: this.activeWorkspace?.name || 'workspace', onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), index.h("kritzel-active-users", { key: '48983a14d3902cd238cf0d9ed302ed561a655a9a', users: this.activeUsers }), shouldShowCurrentUser && index.h("kritzel-current-user", { key: '589bdc895741a2d17f60c4b549d14fe8fff74c17', user: this.user }), shouldShowLoginButton && index.h("kritzel-button", { key: '50414a9d7ade623678c767ce63c49f5e99336bed', onButtonClick: () => this.loginDialogRef?.open() }, "Sign in"), index.h("kritzel-more-menu", { key: 'db3069ee0b093acd238750df81c6f426139c9b87', items: this.moreMenuItems }), index.h("kritzel-share-dialog", { key: 'e16057838f00b2c3f239e794b4310bd2690d16e2', ref: el => (this.shareDialogRef = el), isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (index.h("kritzel-login-dialog", { key: 'a6d1fc06c983542c6af95e7bb4b2b2d522a03605', ref: el => (this.loginDialogRef = el), providers: this.loginConfig.providers, dialogTitle: this.loginConfig.title, subtitle: this.loginConfig.subtitle, onProviderLogin: this.handleProviderLogin })))));
2561
+ return (index.h(index.Host, { key: 'b52c23bf28392a0061034c0909aadb7abf4d86a7' }, index.h("div", { key: '7bf75101bef73a2c8481258060e3236a8ca939b4', class: "top-left-buttons" }, index.h("kritzel-workspace-manager", { key: '69adc1756e00ed56c9980e78929543ad4ecc7b0e', workspaces: this.workspaces, activeWorkspace: this.activeWorkspace, onWorkspaceChange: event => (this.activeWorkspace = event.detail), onIsWorkspaceManagerReady: () => (this.isWorkspaceManagerReady = true) }), index.h("kritzel-back-to-content", { key: 'd802fe454738919d9108a807eb379b2f31e94ab6', visible: this.isBackToContentButtonVisible, onBackToContent: () => this.backToContent() })), index.h("kritzel-engine", { key: '2d3ce8937285956d978f3947ffa86872e156b04d', ref: el => (this.engineRef = el), workspace: this.activeWorkspace, activeWorkspaceId: this.activeWorkspaceId, editorId: this.editorId, syncConfig: this.syncConfig, user: this.user, scaleMax: this.scaleMax, lockDrawingScale: this.lockDrawingScale, scaleMin: this.scaleMin, viewportBoundaryLeft: this.viewportBoundaryLeft, viewportBoundaryRight: this.viewportBoundaryRight, viewportBoundaryTop: this.viewportBoundaryTop, viewportBoundaryBottom: this.viewportBoundaryBottom, wheelEnabled: this.wheelEnabled, theme: this.currentTheme, 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) }), index.h("kritzel-controls", { key: '6d9b0640b205143c7dc21572616624ef707cfe06', class: { 'keyboard-open': this.isVirtualKeyboardOpen }, style: { display: this.isControlsVisible ? 'flex' : 'none' }, ref: el => (this.controlsRef = el), controls: this.controls, isUtilityPanelVisible: this.isUtilityPanelVisible, undoState: this.undoState, theme: this.currentTheme, onIsControlsReady: () => (this.isControlsReady = true) }), index.h("div", { key: 'c65f16c40d78d8e3eb585e870e414f496e816e82', class: "top-right-buttons" }, index.h("kritzel-settings", { key: 'a84d5de344011a32be1b4178acc3a5fff246d702', ref: el => (this.settingsRef = el), shortcuts: this.shortcuts, editorId: this.editorId, onSettingsChange: event => this.handleSettingsChange(event) }), index.h("kritzel-export", { key: '67748d0376f4e7ac39edfc61c1c2cd639664ad78', ref: el => (this.exportRef = el), workspaceName: this.activeWorkspace?.name || 'workspace', onExportPng: () => this.engineRef.exportViewportAsPng(), onExportSvg: () => this.engineRef.exportViewportAsSvg(), onExportJson: event => this.engineRef.downloadAsJson(event.detail) }), index.h("kritzel-active-users", { key: '1155b8784de2353ae208a2910844bad1e945a891', users: this.activeUsers }), shouldShowCurrentUser && index.h("kritzel-current-user", { key: 'fb8951eed7f7f5c9117bb0027e9af1aae95088ed', user: this.user }), shouldShowLoginButton && index.h("kritzel-button", { key: 'd4288b1577082c207696331de4d9fcb1e749fa2a', onButtonClick: () => this.loginDialogRef?.open() }, "Sign in"), index.h("kritzel-more-menu", { key: '66f0990868bf034a172e6e8db3162a1af0637cf8', items: this.moreMenuItems }), index.h("kritzel-share-dialog", { key: 'ce3ab2b6db71bf0a4998c16f04521ee39c1715b7', ref: el => (this.shareDialogRef = el), isPublic: this.currentIsPublic, workspaceId: this.activeWorkspace?.id, onToggleIsPublic: this.handleToggleIsPublic }), this.loginConfig && (index.h("kritzel-login-dialog", { key: 'd3ec7461c2e6a8fcdac4ce13a51f80835888cb44', ref: el => (this.loginDialogRef = el), providers: this.loginConfig.providers, dialogTitle: this.loginConfig.title, subtitle: this.loginConfig.subtitle, onProviderLogin: this.handleProviderLogin })))));
2554
2562
  }
2555
2563
  static get watchers() { return {
2556
2564
  "isEngineReady": [{
@@ -27021,18 +27029,28 @@ const KritzelEngine = class {
27021
27029
  zIndex: (object.zIndex + 2).toString(),
27022
27030
  } }, index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "Id: ", object.id), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "userId: ", object.userId), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "width: ", object.width), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "height: ", object.height), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "translateX: ", object.translateX), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "translateY: ", object.translateY), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "rotationDegrees: ", object.rotationDegrees), index.h("div", { style: { whiteSpace: 'nowrap', fontSize: '10px' } }, "zIndex: ", object.zIndex))), (this.core.displaySelectionGroupUI(object) || this.core.displaySelectionLineUI(object)) &&
27023
27031
  (() => {
27024
- const isRemoteSelection = workspace_migrations.KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup') &&
27025
- object.userId != null &&
27026
- this.core.user?.id != null &&
27027
- object.userId !== this.core.user.id;
27032
+ const isSelectionGroup = workspace_migrations.KritzelClassHelper.isInstanceOf(object, 'KritzelSelectionGroup');
27033
+ const localClientId = this.core.store.state.objects?.localClientId;
27034
+ const isRemoteSelection = isSelectionGroup && (
27035
+ // Different user
27036
+ (object.userId != null && this.core.user?.id != null && object.userId !== this.core.user.id) ||
27037
+ // Same user but different client (e.g. same account in two browser tabs)
27038
+ (object.clientId != null && localClientId != null && object.clientId !== localClientId));
27028
27039
  let remoteUserColor;
27029
27040
  if (isRemoteSelection) {
27030
27041
  const awarenessStates = this.core.store.state.objects?.awareness?.getStates();
27031
27042
  if (awarenessStates) {
27032
- for (const state of awarenessStates.values()) {
27033
- if (state.user?.id === object.userId) {
27034
- remoteUserColor = state.user.color;
27035
- break;
27043
+ // Try direct lookup by clientId first (most precise)
27044
+ if (isSelectionGroup && object.clientId != null) {
27045
+ remoteUserColor = awarenessStates.get(object.clientId)?.user?.color;
27046
+ }
27047
+ // Fall back to userId match (for selection groups without clientId)
27048
+ if (!remoteUserColor) {
27049
+ for (const state of awarenessStates.values()) {
27050
+ if (state.user?.id === object.userId) {
27051
+ remoteUserColor = state.user.color;
27052
+ break;
27053
+ }
27036
27054
  }
27037
27055
  }
27038
27056
  }
@@ -28521,7 +28539,7 @@ const KritzelPortal = class {
28521
28539
  * This file is auto-generated by the version bump scripts.
28522
28540
  * Do not modify manually.
28523
28541
  */
28524
- const KRITZEL_VERSION = '0.1.76';
28542
+ const KRITZEL_VERSION = '0.1.77';
28525
28543
 
28526
28544
  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)}`;
28527
28545
 
@@ -29366,7 +29384,7 @@ const KritzelTooltip = class {
29366
29384
  };
29367
29385
  KritzelTooltip.style = kritzelTooltipCss();
29368
29386
 
29369
- const kritzelUtilityPanelCss = () => `:host{display:flex;flex-direction:row;align-items:center;padding:4px;gap:8px;border-top-left-radius:12px;border-top-right-radius:12px;background-color:var(--kritzel-utility-panel-background-color, #e2e2e2);width:fit-content;user-select:none}.utility-button{display:flex;justify-content:center;align-items:center;width:28px;height:28px;padding:8px 4px;border:none;background:none;cursor:var(--kritzel-global-pointer-cursor, pointer);color:var(--kritzel-utility-panel-button-color, #333333);--kritzel-icon-color:var(--kritzel-utility-panel-button-color, #333333);-webkit-tap-highlight-color:transparent;border-radius:var(--kritzel-utility-panel-button-border-radius, 8px)}.utility-button:hover,.utility-button:focus-visible{background-color:var(--kritzel-utility-panel-button-hover-background-color, hsl(0, 0%, 0%, 4.3%))}.utility-button:disabled{opacity:0.4;cursor:not-allowed;pointer-events:none}.utility-separator{width:1px;height:16px;background-color:var(--kritzel-utility-panel-separator-color, hsl(0, 0%, 0%, 8%))}`;
29387
+ const kritzelUtilityPanelCss = () => `:host{display:flex;flex-direction:row;align-items:center;padding:4px;gap:8px;border-top-left-radius:12px;border-top-right-radius:12px;background-color:var(--kritzel-utility-panel-background-color, #e2e2e2);width:fit-content;user-select:none;z-index:10000}.utility-button{display:flex;justify-content:center;align-items:center;width:28px;height:28px;padding:8px 4px;border:none;background:none;cursor:var(--kritzel-global-pointer-cursor, pointer);color:var(--kritzel-utility-panel-button-color, #333333);--kritzel-icon-color:var(--kritzel-utility-panel-button-color, #333333);-webkit-tap-highlight-color:transparent;border-radius:var(--kritzel-utility-panel-button-border-radius, 8px)}.utility-button:hover,.utility-button:focus-visible{background-color:var(--kritzel-utility-panel-button-hover-background-color, hsl(0, 0%, 0%, 4.3%))}.utility-button:disabled{opacity:0.4;cursor:not-allowed;pointer-events:none}.utility-separator{width:1px;height:16px;background-color:var(--kritzel-utility-panel-separator-color, hsl(0, 0%, 0%, 8%))}`;
29370
29388
 
29371
29389
  const KritzelUtilityPanel = class {
29372
29390
  constructor(hostRef) {