@noya-app/noya-multiplayer-react 0.1.9

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.
package/dist/index.mjs ADDED
@@ -0,0 +1,764 @@
1
+ // src/index.ts
2
+ export * from "@noya-app/state-manager";
3
+
4
+ // src/hooks.ts
5
+ import {
6
+ MultiplayerStateManager,
7
+ StateManager,
8
+ stubSync
9
+ } from "@noya-app/state-manager";
10
+ import {
11
+ useCallback,
12
+ useEffect as useEffect3,
13
+ useMemo,
14
+ useRef,
15
+ useState as useState2,
16
+ useSyncExternalStore
17
+ } from "react";
18
+
19
+ // src/globals.ts
20
+ import { Emitter } from "@noya-app/emitter";
21
+ import { useEffect, useState } from "react";
22
+ var Observable = class {
23
+ value;
24
+ emitter = new Emitter();
25
+ constructor(value) {
26
+ this.value = value;
27
+ }
28
+ get() {
29
+ return this.value;
30
+ }
31
+ set(value) {
32
+ if (this.value === value)
33
+ return;
34
+ this.value = value;
35
+ this.emitter.emit(value);
36
+ }
37
+ listen(callback) {
38
+ callback(this.value);
39
+ return this.emitter.addListener(callback);
40
+ }
41
+ };
42
+ function useObservable(observable) {
43
+ const [value, setValue] = useState(observable.get());
44
+ useEffect(() => {
45
+ return observable.listen(setValue);
46
+ }, [observable]);
47
+ return value;
48
+ }
49
+ var trackEvents = new Observable(false);
50
+
51
+ // src/inspector/useStateInspector.tsx
52
+ import React3, { useLayoutEffect as useLayoutEffect2 } from "react";
53
+ import { createRoot } from "react-dom/client";
54
+
55
+ // src/inspector/StateInspector.tsx
56
+ import React2, {
57
+ memo,
58
+ useEffect as useEffect2,
59
+ useLayoutEffect
60
+ } from "react";
61
+ import {
62
+ ObjectInspector,
63
+ ObjectLabel,
64
+ ObjectName,
65
+ ObjectPreview,
66
+ chromeDark,
67
+ chromeLight
68
+ } from "react-inspector";
69
+
70
+ // src/inspector/useLocalStorageState.tsx
71
+ import React from "react";
72
+ var localStorage = typeof window !== "undefined" ? window.localStorage : null;
73
+ function useLocalStorageState(key, defaultValue) {
74
+ const [state, setState] = React.useState(() => {
75
+ const value = localStorage?.getItem(key);
76
+ let result = defaultValue;
77
+ if (value) {
78
+ try {
79
+ result = JSON.parse(value);
80
+ } catch (error) {
81
+ console.error("Error parsing localStorage value", error);
82
+ }
83
+ }
84
+ return result;
85
+ });
86
+ const setLocalStorageState = (value) => {
87
+ localStorage?.setItem(key, JSON.stringify(value));
88
+ setState(value);
89
+ };
90
+ return [state, setLocalStorageState];
91
+ }
92
+
93
+ // src/inspector/StateInspector.tsx
94
+ var lightTheme = {
95
+ ...chromeLight,
96
+ BASE_BACKGROUND_COLOR: "transparent",
97
+ OBJECT_NAME_COLOR: "rgba(0,0,0,0.7)"
98
+ };
99
+ var darkTheme = {
100
+ ...chromeDark,
101
+ BASE_BACKGROUND_COLOR: "transparent",
102
+ OBJECT_NAME_COLOR: "rgba(255,255,255,0.7)"
103
+ };
104
+ function ToggleButton({
105
+ showInspector,
106
+ setShowInspector,
107
+ theme
108
+ }) {
109
+ return /* @__PURE__ */ React2.createElement(
110
+ "span",
111
+ {
112
+ role: "button",
113
+ style: {
114
+ flex: "0",
115
+ appearance: "none",
116
+ color: theme.BASE_COLOR,
117
+ background: "rgba(0,0,0,0.1)",
118
+ border: "none",
119
+ fontSize: "12px",
120
+ whiteSpace: "nowrap",
121
+ display: "inline-flex",
122
+ alignItems: "center",
123
+ justifyContent: "center",
124
+ padding: "2px 0"
125
+ },
126
+ onClick: () => setShowInspector(!showInspector)
127
+ },
128
+ /* @__PURE__ */ React2.createElement("span", { style: { width: "12px", height: "12px" } }, showInspector ? /* @__PURE__ */ React2.createElement(
129
+ "svg",
130
+ {
131
+ xmlns: "http://www.w3.org/2000/svg",
132
+ fill: "none",
133
+ viewBox: "0 0 24 24",
134
+ strokeWidth: 1.5,
135
+ stroke: "currentColor",
136
+ className: "size-6"
137
+ },
138
+ /* @__PURE__ */ React2.createElement(
139
+ "path",
140
+ {
141
+ strokeLinecap: "round",
142
+ strokeLinejoin: "round",
143
+ d: "m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5"
144
+ }
145
+ )
146
+ ) : /* @__PURE__ */ React2.createElement(
147
+ "svg",
148
+ {
149
+ xmlns: "http://www.w3.org/2000/svg",
150
+ fill: "none",
151
+ viewBox: "0 0 24 24",
152
+ strokeWidth: 1.5,
153
+ stroke: "currentColor",
154
+ className: "size-6"
155
+ },
156
+ /* @__PURE__ */ React2.createElement(
157
+ "path",
158
+ {
159
+ strokeLinecap: "round",
160
+ strokeLinejoin: "round",
161
+ d: "m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5"
162
+ }
163
+ )
164
+ ))
165
+ );
166
+ }
167
+ function DisclosureSection({
168
+ open,
169
+ setOpen,
170
+ title,
171
+ right,
172
+ children,
173
+ colorScheme,
174
+ isFirst
175
+ }) {
176
+ const theme = colorScheme === "light" ? lightTheme : darkTheme;
177
+ const borderColor = colorScheme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)";
178
+ return /* @__PURE__ */ React2.createElement(
179
+ "div",
180
+ {
181
+ style: {
182
+ flex: open ? "1 1 0" : "0",
183
+ display: "flex",
184
+ flexDirection: "column"
185
+ }
186
+ },
187
+ /* @__PURE__ */ React2.createElement(
188
+ "div",
189
+ {
190
+ onClick: () => setOpen?.(!open),
191
+ style: {
192
+ cursor: "default",
193
+ fontSize: "12px",
194
+ padding: "4px 10px",
195
+ display: "flex",
196
+ ...!isFirst && { borderTop: `1px solid ${borderColor}` },
197
+ ...open && { borderBottom: `1px solid ${borderColor}` }
198
+ }
199
+ },
200
+ setOpen && /* @__PURE__ */ React2.createElement(
201
+ Arrow,
202
+ {
203
+ expanded: open,
204
+ style: {
205
+ fontSize: "11px",
206
+ // fontSize: theme.ARROW_FONT_SIZE,
207
+ marginRight: theme.ARROW_MARGIN_RIGHT,
208
+ color: theme.ARROW_COLOR
209
+ }
210
+ }
211
+ ),
212
+ /* @__PURE__ */ React2.createElement("span", { style: { flex: "1 1 0" } }, title),
213
+ right
214
+ ),
215
+ open && children
216
+ );
217
+ }
218
+ var StateInspector = memo(function StateInspector2({
219
+ state,
220
+ connectionEvents,
221
+ unstyled,
222
+ colorScheme = "light",
223
+ stateManager,
224
+ ...props
225
+ }) {
226
+ const [didMount, setDidMount] = React2.useState(false);
227
+ useLayoutEffect(() => {
228
+ setDidMount(true);
229
+ }, []);
230
+ const eventsContainerRef = React2.useRef(null);
231
+ const historyContainerRef = React2.useRef(null);
232
+ const [showInspector, setShowInspector] = useLocalStorageState(
233
+ "noya-multiplayer-react-show-inspector",
234
+ true
235
+ );
236
+ const [showEvents, setShowEvents] = useLocalStorageState(
237
+ "noya-multiplayer-react-show-events",
238
+ false
239
+ );
240
+ const [showHistory, setShowHistory] = useLocalStorageState(
241
+ "noya-multiplayer-react-show-history",
242
+ false
243
+ );
244
+ useEffect2(() => {
245
+ trackEvents.set(showEvents);
246
+ }, [showEvents]);
247
+ useEffect2(() => {
248
+ if (eventsContainerRef.current) {
249
+ eventsContainerRef.current.scrollTop = eventsContainerRef.current.scrollHeight;
250
+ }
251
+ }, [connectionEvents]);
252
+ const historySnapshot = useManagedHistory(stateManager);
253
+ useEffect2(() => {
254
+ if (historyContainerRef.current) {
255
+ historyContainerRef.current.scrollTop = historyContainerRef.current.scrollHeight;
256
+ }
257
+ }, [historySnapshot]);
258
+ useEffect2(() => {
259
+ if (!historyContainerRef.current)
260
+ return;
261
+ const historyEntry = historyContainerRef.current.querySelector(
262
+ `#history-${historySnapshot.historyIndex - 1}`
263
+ );
264
+ if (historyEntry) {
265
+ historyEntry.scrollIntoView({
266
+ block: "nearest",
267
+ inline: "nearest"
268
+ });
269
+ }
270
+ }, [historySnapshot.historyIndex]);
271
+ const theme = colorScheme === "light" ? lightTheme : darkTheme;
272
+ const borderColor = colorScheme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)";
273
+ const baseStyle = {
274
+ position: "fixed",
275
+ top: 12,
276
+ right: 12,
277
+ bottom: 12,
278
+ width: 400,
279
+ background: colorScheme === "light" ? "rgba(255,255,255,0.95)" : "rgba(0,0,0,0.95)",
280
+ backdropFilter: "blur(48px)",
281
+ border: `1px solid ${borderColor}`,
282
+ // outlineOffset: -1,
283
+ borderRadius: 4,
284
+ display: "flex",
285
+ flexDirection: "column",
286
+ // gap: 12,
287
+ color: theme.BASE_COLOR,
288
+ overflow: "hidden",
289
+ zIndex: 9999
290
+ };
291
+ if (!didMount)
292
+ return null;
293
+ if (!showInspector) {
294
+ return /* @__PURE__ */ React2.createElement(
295
+ "div",
296
+ {
297
+ ...props,
298
+ style: {
299
+ ...baseStyle,
300
+ padding: "4px 10px",
301
+ borderRadius: 8,
302
+ bottom: void 0,
303
+ width: void 0
304
+ }
305
+ },
306
+ /* @__PURE__ */ React2.createElement(
307
+ ToggleButton,
308
+ {
309
+ showInspector,
310
+ setShowInspector,
311
+ theme
312
+ }
313
+ )
314
+ );
315
+ }
316
+ return /* @__PURE__ */ React2.createElement(
317
+ "div",
318
+ {
319
+ ...props,
320
+ style: {
321
+ ...!unstyled && baseStyle,
322
+ ...props.style
323
+ }
324
+ },
325
+ /* @__PURE__ */ React2.createElement(
326
+ DisclosureSection,
327
+ {
328
+ open: true,
329
+ title: "Data",
330
+ colorScheme,
331
+ isFirst: true,
332
+ right: /* @__PURE__ */ React2.createElement(
333
+ ToggleButton,
334
+ {
335
+ showInspector,
336
+ setShowInspector,
337
+ theme
338
+ }
339
+ )
340
+ },
341
+ /* @__PURE__ */ React2.createElement(
342
+ "div",
343
+ {
344
+ style: {
345
+ flex: "1 1 0",
346
+ overflowY: "auto",
347
+ display: "flex",
348
+ flexDirection: "column",
349
+ gap: "1px",
350
+ padding: "1px 12px"
351
+ }
352
+ },
353
+ /* @__PURE__ */ React2.createElement(ObjectInspector, { data: state, theme })
354
+ )
355
+ ),
356
+ /* @__PURE__ */ React2.createElement(
357
+ DisclosureSection,
358
+ {
359
+ open: showHistory,
360
+ setOpen: setShowHistory,
361
+ title: "History",
362
+ colorScheme
363
+ },
364
+ /* @__PURE__ */ React2.createElement(
365
+ "div",
366
+ {
367
+ ref: historyContainerRef,
368
+ style: {
369
+ flex: "1 1 0",
370
+ overflowY: "auto",
371
+ overflowX: "hidden",
372
+ display: "flex",
373
+ flexDirection: "column"
374
+ }
375
+ },
376
+ historySnapshot.history.length < 50 && /* @__PURE__ */ React2.createElement(
377
+ "div",
378
+ {
379
+ id: `history-${0}`,
380
+ style: {
381
+ borderBottom: `1px solid ${borderColor}`,
382
+ fontSize: "12px",
383
+ fontFamily: "Menlo, monospace",
384
+ padding: "2px 12px 1px",
385
+ display: "flex",
386
+ alignItems: "center",
387
+ background: historySnapshot.historyIndex === 0 ? colorScheme === "light" ? "rgba(0,0,0,0.2)" : "rgb(59 130 246 / 38%)" : void 0
388
+ }
389
+ },
390
+ /* @__PURE__ */ React2.createElement(
391
+ "span",
392
+ {
393
+ style: {
394
+ fontFamily: "Menlo, monospace",
395
+ fontSize: "11px",
396
+ borderRadius: 4,
397
+ display: "inline-block"
398
+ }
399
+ },
400
+ "Initial state"
401
+ )
402
+ ),
403
+ historySnapshot.history.slice(-50).map((entry, index) => /* @__PURE__ */ React2.createElement(
404
+ "div",
405
+ {
406
+ id: `history-${index}`,
407
+ key: index,
408
+ style: {
409
+ padding: "0px 12px 1px",
410
+ borderBottom: `1px solid ${borderColor}`,
411
+ background: index === historySnapshot.historyIndex - 1 ? colorScheme === "light" ? "rgba(0,0,0,0.2)" : "rgb(59 130 246 / 15%)" : void 0
412
+ }
413
+ },
414
+ entry.redoPatches?.map((patch, j) => /* @__PURE__ */ React2.createElement(
415
+ "pre",
416
+ {
417
+ key: j,
418
+ style: {
419
+ fontSize: "11px",
420
+ display: "flex",
421
+ flexWrap: "wrap"
422
+ }
423
+ },
424
+ patch.op === "add" && /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.path)), " = ", /* @__PURE__ */ React2.createElement(ObjectInspector, { data: patch.value, theme })),
425
+ patch.op === "replace" && /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.path)), " = ", /* @__PURE__ */ React2.createElement(ObjectInspector, { data: patch.value, theme })),
426
+ patch.op === "remove" && /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("span", { style: { color: theme.OBJECT_VALUE_STRING_COLOR } }, "delete", " "), /* @__PURE__ */ React2.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.path)))
427
+ ))
428
+ ))
429
+ )
430
+ ),
431
+ /* @__PURE__ */ React2.createElement(
432
+ DisclosureSection,
433
+ {
434
+ open: showEvents,
435
+ setOpen: setShowEvents,
436
+ title: "Events",
437
+ colorScheme
438
+ },
439
+ /* @__PURE__ */ React2.createElement(
440
+ "div",
441
+ {
442
+ ref: eventsContainerRef,
443
+ style: {
444
+ flex: "1 1 0",
445
+ overflowY: "auto",
446
+ display: "flex",
447
+ flexDirection: "column"
448
+ }
449
+ },
450
+ connectionEvents?.map((event, index) => /* @__PURE__ */ React2.createElement(
451
+ "div",
452
+ {
453
+ key: index,
454
+ style: {
455
+ borderBottom: `1px solid ${borderColor}`
456
+ }
457
+ },
458
+ event.type === "stateChange" ? /* @__PURE__ */ React2.createElement(
459
+ "div",
460
+ {
461
+ style: {
462
+ padding: "2px 12px",
463
+ background: colorScheme === "light" ? "rgba(0,0,0,0.05)" : "rgba(255,255,255,0.05)",
464
+ display: "flex",
465
+ alignItems: "center"
466
+ }
467
+ },
468
+ /* @__PURE__ */ React2.createElement(
469
+ "span",
470
+ {
471
+ style: {
472
+ fontFamily: "Menlo, monospace",
473
+ fontSize: "11px",
474
+ borderRadius: 4,
475
+ display: "inline-block"
476
+ }
477
+ },
478
+ "connection:",
479
+ " ",
480
+ /* @__PURE__ */ React2.createElement("span", { style: { fontStyle: "italic" } }, event.state.toLowerCase())
481
+ )
482
+ ) : /* @__PURE__ */ React2.createElement(
483
+ "div",
484
+ {
485
+ style: {
486
+ padding: "0px 12px 1px",
487
+ background: event.type === "send" ? "rgba(0,255,0,0.1)" : "rgba(255,0,0,0.1)"
488
+ }
489
+ },
490
+ /* @__PURE__ */ React2.createElement(
491
+ ObjectInspector,
492
+ {
493
+ data: event.message,
494
+ theme,
495
+ nodeRenderer: ({
496
+ depth,
497
+ name,
498
+ data,
499
+ isNonenumerable,
500
+ expanded
501
+ }) => {
502
+ const direction = event.type === "send" ? "up" : "down";
503
+ return depth === 0 ? /* @__PURE__ */ React2.createElement(ObjectRootLabel, { direction, data }) : /* @__PURE__ */ React2.createElement(
504
+ ObjectLabel,
505
+ {
506
+ direction,
507
+ name,
508
+ data,
509
+ isNonenumerable
510
+ }
511
+ );
512
+ }
513
+ }
514
+ )
515
+ )
516
+ )),
517
+ !connectionEvents && /* @__PURE__ */ React2.createElement(
518
+ "div",
519
+ {
520
+ style: {
521
+ padding: "12px",
522
+ fontSize: "12px",
523
+ display: "flex",
524
+ flexDirection: "column",
525
+ gap: "4px"
526
+ }
527
+ },
528
+ /* @__PURE__ */ React2.createElement("span", null, "No recorded events")
529
+ )
530
+ )
531
+ )
532
+ );
533
+ });
534
+ var ObjectRootLabel = ({ name, data, direction }) => {
535
+ if (typeof name === "string") {
536
+ return /* @__PURE__ */ React2.createElement("span", null, /* @__PURE__ */ React2.createElement(ObjectName, { name }), /* @__PURE__ */ React2.createElement("span", null, ": "), /* @__PURE__ */ React2.createElement(ObjectPreview, { data }));
537
+ }
538
+ if (direction === "up" || direction === "down") {
539
+ const arrow = direction === "up" ? "\u2191" : "\u2193";
540
+ return /* @__PURE__ */ React2.createElement("span", null, /* @__PURE__ */ React2.createElement(
541
+ "span",
542
+ {
543
+ style: {
544
+ background: direction === "up" ? "rgba(0,255,0,0.2)" : "rgba(255,0,0,0.2)",
545
+ // color: "white",
546
+ width: 12,
547
+ height: 12,
548
+ borderRadius: 2,
549
+ display: "inline-flex",
550
+ justifyContent: "center",
551
+ alignItems: "center",
552
+ marginRight: 4,
553
+ lineHeight: "12px"
554
+ }
555
+ },
556
+ arrow
557
+ ), /* @__PURE__ */ React2.createElement(ObjectPreview, { data }));
558
+ } else {
559
+ return /* @__PURE__ */ React2.createElement(ObjectPreview, { data });
560
+ }
561
+ };
562
+ function Arrow({
563
+ expanded,
564
+ onClick,
565
+ style
566
+ }) {
567
+ return /* @__PURE__ */ React2.createElement(
568
+ "span",
569
+ {
570
+ role: "button",
571
+ onClick,
572
+ style: {
573
+ display: "inline-block",
574
+ textAlign: "center",
575
+ transform: expanded ? "rotate(0deg)" : "rotate(-90deg)",
576
+ ...style
577
+ }
578
+ },
579
+ "\u25BC"
580
+ );
581
+ }
582
+ function pathToString(extendedPath) {
583
+ return extendedPath.map(
584
+ (key) => typeof key === "object" ? `:${ellipsis(key.id.toString(), 8, "middle")}` : key
585
+ ).map(
586
+ (key, index) => index === 0 || typeof key === "string" && key.startsWith(":") ? key : `.${key}`
587
+ ).join("");
588
+ }
589
+ function ellipsis(str, maxLength, position = "end") {
590
+ if (str.length <= maxLength)
591
+ return str;
592
+ switch (position) {
593
+ case "start":
594
+ return `\u2026${str.slice(str.length - maxLength)}`;
595
+ case "middle":
596
+ const halfLength = Math.floor(maxLength / 2);
597
+ return `${str.slice(0, halfLength)}\u2026${str.slice(str.length - halfLength)}`;
598
+ case "end":
599
+ return `${str.slice(0, maxLength)}\u2026`;
600
+ }
601
+ }
602
+
603
+ // src/inspector/useStateInspector.tsx
604
+ function createPortalElement() {
605
+ const el = document.createElement("div");
606
+ el.id = "noya-multiplayer-react-inspector-portal";
607
+ return el;
608
+ }
609
+ function useStateInspector({
610
+ state,
611
+ connectionEvents,
612
+ disabled = false,
613
+ colorScheme,
614
+ stateManager
615
+ }) {
616
+ const [root, setRoot] = React3.useState(null);
617
+ useLayoutEffect2(() => {
618
+ if (disabled)
619
+ return;
620
+ const portalElement = createPortalElement();
621
+ const root2 = createRoot(portalElement);
622
+ document.body.appendChild(portalElement);
623
+ setRoot(root2);
624
+ return () => {
625
+ document.body.removeChild(portalElement);
626
+ };
627
+ }, [disabled]);
628
+ useLayoutEffect2(() => {
629
+ if (!root)
630
+ return;
631
+ root.render(
632
+ /* @__PURE__ */ React3.createElement(
633
+ StateInspector,
634
+ {
635
+ state,
636
+ connectionEvents,
637
+ colorScheme,
638
+ stateManager
639
+ }
640
+ )
641
+ );
642
+ }, [colorScheme, connectionEvents, root, state, stateManager]);
643
+ }
644
+
645
+ // src/hooks.ts
646
+ function useSyncStateManager(stateManager, path) {
647
+ const getSnapshot = useCallback(
648
+ () => typeof path === "string" ? stateManager.getState(path) : stateManager.getState(),
649
+ [stateManager, path]
650
+ );
651
+ return useSyncExternalStore(
652
+ stateManager.addListener,
653
+ getSnapshot,
654
+ getSnapshot
655
+ );
656
+ }
657
+ function useManagedState(createInitialState, options) {
658
+ const [stateManager] = useState2(
659
+ () => new StateManager(createInitialState(), options)
660
+ );
661
+ const state = useSyncStateManager(stateManager);
662
+ const setState = useCallback(
663
+ (...args) => {
664
+ const [metadata, action] = args.length === 1 ? [void 0, args[0]] : args;
665
+ if (metadata === void 0) {
666
+ const performActionNoMetadata = stateManager.performAction;
667
+ if (typeof action === "function") {
668
+ performActionNoMetadata({
669
+ run: action,
670
+ undo: () => state
671
+ });
672
+ } else {
673
+ performActionNoMetadata({
674
+ run: () => action,
675
+ undo: () => state
676
+ });
677
+ }
678
+ } else {
679
+ const performActionWithMetadata = stateManager.performAction;
680
+ if (typeof action === "function") {
681
+ performActionWithMetadata(metadata, {
682
+ run: action,
683
+ undo: () => state
684
+ });
685
+ } else {
686
+ performActionWithMetadata(metadata, {
687
+ run: () => action,
688
+ undo: () => state
689
+ });
690
+ }
691
+ }
692
+ },
693
+ [state, stateManager]
694
+ );
695
+ return [state, setState, stateManager];
696
+ }
697
+ function useManagedHistory(stateManager) {
698
+ return useSyncExternalStore(
699
+ stateManager.historyEmittor.addListener,
700
+ stateManager.getHistorySnapshot,
701
+ stateManager.getHistorySnapshot
702
+ );
703
+ }
704
+ function useSyncMultiplayerStateManager(multiplayerStateManager) {
705
+ return useSyncExternalStore(
706
+ multiplayerStateManager.addListener,
707
+ multiplayerStateManager.getOptimisticState,
708
+ multiplayerStateManager.getOptimisticState
709
+ );
710
+ }
711
+ var defaultStubSync = stubSync();
712
+ function useMultiplayerState(initialState, options) {
713
+ const { sync = defaultStubSync, ...rest } = options;
714
+ const [multiplayerStateManager] = useState2(
715
+ () => new MultiplayerStateManager(initialState, rest)
716
+ );
717
+ const syncRef = useRef(sync);
718
+ const [connectionEvents, setConnectionEvents] = useState2();
719
+ const extras = useMemo(() => {
720
+ return {
721
+ connectionEvents
722
+ };
723
+ }, [connectionEvents]);
724
+ const globalTrackEvents = useObservable(trackEvents);
725
+ const trackConnectionEvents = options.trackConnectionEvents ?? globalTrackEvents;
726
+ const trackEventsCallbackRef = useRef();
727
+ useEffect3(() => {
728
+ trackEventsCallbackRef.current = trackConnectionEvents ? (e) => setConnectionEvents((events) => {
729
+ const updated = events ? [...events, e] : [e];
730
+ return updated.length > 50 ? updated.slice(-50) : updated;
731
+ }) : void 0;
732
+ }, [trackConnectionEvents]);
733
+ useEffect3(() => {
734
+ if (syncRef.current) {
735
+ return syncRef.current(
736
+ multiplayerStateManager,
737
+ (e) => trackEventsCallbackRef.current?.(e)
738
+ );
739
+ }
740
+ }, [multiplayerStateManager]);
741
+ const state = useSyncMultiplayerStateManager(multiplayerStateManager);
742
+ useStateInspector({
743
+ stateManager: multiplayerStateManager.sm,
744
+ state,
745
+ connectionEvents,
746
+ disabled: !options.inspector,
747
+ colorScheme: typeof options.inspector === "object" ? options.inspector.colorScheme : void 0
748
+ });
749
+ return [
750
+ state,
751
+ multiplayerStateManager.setOptimisticState,
752
+ multiplayerStateManager,
753
+ extras
754
+ ];
755
+ }
756
+ export {
757
+ StateInspector,
758
+ useManagedHistory,
759
+ useManagedState,
760
+ useMultiplayerState,
761
+ useSyncMultiplayerStateManager,
762
+ useSyncStateManager
763
+ };
764
+ //# sourceMappingURL=index.mjs.map