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