@noya-app/noya-multiplayer-react 0.1.17 → 0.1.19

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 CHANGED
@@ -1,8 +1,201 @@
1
1
  // src/index.ts
2
2
  export * from "@noya-app/state-manager";
3
3
 
4
+ // src/avatarStyle.ts
5
+ function hashCode(string) {
6
+ let hash = 0;
7
+ for (const char of string) {
8
+ hash = char.charCodeAt(0) + ((hash << 5) - hash);
9
+ }
10
+ return hash;
11
+ }
12
+ function elementFromHash(array, hash) {
13
+ return array[Math.abs(hash) % array.length];
14
+ }
15
+ function getAvatarInitials(name) {
16
+ return name.split(" ").filter((c) => c.length > 0).map((c) => c[0]).join("").toLocaleUpperCase();
17
+ }
18
+ function getAvatarStyle(name) {
19
+ const hash = hashCode(name);
20
+ const color = elementFromHash(Object.values(colors500), hash);
21
+ return {
22
+ color: "white",
23
+ backgroundColor: color
24
+ // primaryColors: {
25
+ // "500": color,
26
+ // "800": darkerColor,
27
+ // },
28
+ };
29
+ }
30
+ var colors500 = {
31
+ red500: "#ef4444",
32
+ orange500: "#f97316",
33
+ amber500: "#f59e0b",
34
+ yellow500: "#eab308",
35
+ lime500: "#84cc16",
36
+ green500: "#22c55e",
37
+ emerald500: "#10b981",
38
+ teal500: "#14b8a6",
39
+ cyan500: "#06b6d4",
40
+ sky500: "#0ea5e9",
41
+ blue500: "#3b82f6",
42
+ indigo500: "#6366f1",
43
+ violet500: "#8b5cf6",
44
+ purple500: "#a855f7",
45
+ fuchsia500: "#d946ef",
46
+ pink500: "#ec4899",
47
+ rose500: "#f43f5e"
48
+ };
49
+
50
+ // src/components/UserPointersOverlay.tsx
51
+ import React, { memo, useEffect, useMemo as useMemo2, useState } from "react";
52
+
53
+ // src/useObservable.ts
54
+ import { useMemo, useSyncExternalStore } from "react";
55
+ function useObservable(observable, path) {
56
+ const { get, listen } = useMemo(() => {
57
+ const listen2 = path ? (callback) => observable.listen(path, callback) : observable.listen;
58
+ const get2 = path ? () => observable.get(path) : observable.get;
59
+ return { get: get2, listen: listen2 };
60
+ }, [observable, path]);
61
+ return useSyncExternalStore(listen, get, get);
62
+ }
63
+
64
+ // src/components/UserPointersOverlay.tsx
65
+ function shouldShow(hideAfter, updatedAt) {
66
+ return !!updatedAt && Date.now() - updatedAt < hideAfter;
67
+ }
68
+ var UserPointer_ = function UserPointer({
69
+ user,
70
+ ephemeralUserData,
71
+ hideAfter = 5e3
72
+ }) {
73
+ const metadata = useObservable(ephemeralUserData.metadata$, [
74
+ user.id
75
+ ]);
76
+ const data = useObservable(ephemeralUserData.data$, [user.id]);
77
+ const avatarStyle = useMemo2(() => getAvatarStyle(user.id), [user.id]);
78
+ const [, setForceUpdate] = useState(0);
79
+ const updatedAt = metadata?.updatedAt ?? 0;
80
+ useEffect(() => {
81
+ if (!shouldShow(hideAfter, updatedAt))
82
+ return;
83
+ const timeoutId = setTimeout(() => {
84
+ setForceUpdate((prev) => prev + 1);
85
+ }, hideAfter);
86
+ return () => clearTimeout(timeoutId);
87
+ }, [hideAfter, updatedAt]);
88
+ const show = shouldShow(hideAfter, updatedAt);
89
+ if (!data || !data.pointer)
90
+ return null;
91
+ return /* @__PURE__ */ React.createElement(UserPointerContainer, { key: user.id, point: data.pointer, show }, /* @__PURE__ */ React.createElement(UserPointerIcon, { color: avatarStyle.backgroundColor }), /* @__PURE__ */ React.createElement(UserNameTag, { background: avatarStyle.backgroundColor }, user.name));
92
+ };
93
+ var UserPointer2 = memo(UserPointer_);
94
+ var UserPointerContainer = memo(function UserPointerContainer2({
95
+ point,
96
+ children,
97
+ show = true
98
+ }) {
99
+ return /* @__PURE__ */ React.createElement(
100
+ "div",
101
+ {
102
+ style: {
103
+ position: "absolute",
104
+ top: 0,
105
+ left: 0,
106
+ transform: `translate(${Math.round(point.x)}px, ${Math.round(
107
+ point.y
108
+ )}px)`,
109
+ opacity: show ? 1 : 0,
110
+ transition: "transform 0.1s, opacity 0.2s"
111
+ }
112
+ },
113
+ children
114
+ );
115
+ });
116
+ var UserNameTag = memo(function UserNameTag2({
117
+ background,
118
+ children
119
+ }) {
120
+ return /* @__PURE__ */ React.createElement(
121
+ "span",
122
+ {
123
+ style: {
124
+ position: "relative",
125
+ top: "8px",
126
+ left: "15px",
127
+ fontSize: 11,
128
+ lineHeight: "1",
129
+ fontWeight: 500,
130
+ boxShadow: "0 1px 1px rgba(0,0,0,0.1)",
131
+ padding: "2px 4px 3px",
132
+ pointerEvents: "none",
133
+ letterSpacing: "-0.3px",
134
+ outline: `1px solid rgba(255,255,255,0.3)`,
135
+ outlineOffset: "-1px",
136
+ background,
137
+ color: "white"
138
+ }
139
+ },
140
+ children
141
+ );
142
+ });
143
+ var UserPointersOverlay = memo(function UserPointers({ connectedUsers, ephemeralUserData }) {
144
+ const currentUserId = useObservable(ephemeralUserData.currentUserId$);
145
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, connectedUsers.map((user) => {
146
+ if (user.id === currentUserId)
147
+ return null;
148
+ return /* @__PURE__ */ React.createElement(
149
+ UserPointer2,
150
+ {
151
+ key: user.id,
152
+ user,
153
+ ephemeralUserData
154
+ }
155
+ );
156
+ }));
157
+ });
158
+ var UserPointerIcon = memo(function CursorIcon({
159
+ color = "black"
160
+ }) {
161
+ return /* @__PURE__ */ React.createElement(
162
+ "svg",
163
+ {
164
+ width: "15",
165
+ height: "15",
166
+ viewBox: "0 0 15 15",
167
+ fill: "none",
168
+ xmlns: "http://www.w3.org/2000/svg",
169
+ style: {
170
+ position: "absolute",
171
+ filter: "drop-shadow(0 1px 1px rgba(0,0,0,0.1))"
172
+ // filter: "drop-shadow(0 1px 1px rgba(0,0,0,0.6))",
173
+ }
174
+ },
175
+ /* @__PURE__ */ React.createElement(
176
+ "path",
177
+ {
178
+ fillRule: "evenodd",
179
+ clipRule: "evenodd",
180
+ d: "M3.29227 0.0451998C3.47033 -0.0361222 3.67946 -0.00606629 3.8274 0.122107L12.8587 7.94648C13.0134 8.08054 13.0708 8.29538 13.0035 8.48873C12.9362 8.68208 12.7578 8.81488 12.5533 8.8239L9.21887 8.97096L11.1504 13.2149C11.2648 13.4662 11.1538 13.7626 10.9026 13.877L8.75024 14.8575C8.499 14.972 8.20255 14.8611 8.08802 14.6099L6.15339 10.3665L3.86279 12.7817C3.72196 12.9302 3.50487 12.9779 3.31479 12.9021C3.1247 12.8263 3 12.6423 3 12.4376V0.500008C3 0.304264 3.11422 0.126522 3.29227 0.0451998Z",
181
+ fill: color
182
+ }
183
+ ),
184
+ /* @__PURE__ */ React.createElement(
185
+ "path",
186
+ {
187
+ fillRule: "evenodd",
188
+ clipRule: "evenodd",
189
+ d: "M3.29227 0.048984C3.47033 -0.032338 3.67946 -0.00228211 3.8274 0.125891L12.8587 7.95026C13.0134 8.08432 13.0708 8.29916 13.0035 8.49251C12.9362 8.68586 12.7578 8.81866 12.5533 8.82768L9.21887 8.97474L11.1504 13.2187C11.2648 13.47 11.1538 13.7664 10.9026 13.8808L8.75024 14.8613C8.499 14.9758 8.20255 14.8649 8.08802 14.6137L6.15339 10.3703L3.86279 12.7855C3.72196 12.934 3.50487 12.9817 3.31479 12.9059C3.1247 12.8301 3 12.6461 3 12.4414V0.503792C3 0.308048 3.11422 0.130306 3.29227 0.048984ZM4 1.59852V11.1877L5.93799 9.14425C6.05238 9.02363 6.21924 8.96776 6.38319 8.99516C6.54715 9.02256 6.68677 9.12965 6.75573 9.2809L8.79056 13.7441L10.0332 13.178L8.00195 8.71497C7.93313 8.56376 7.94391 8.38824 8.03072 8.24659C8.11753 8.10494 8.26903 8.01566 8.435 8.00834L11.2549 7.88397L4 1.59852Z",
190
+ fill: "rgba(255,255,255,0.5)"
191
+ }
192
+ )
193
+ );
194
+ });
195
+
4
196
  // src/hooks.ts
5
197
  import {
198
+ EphemeralUserData,
6
199
  MultiplayerStateManager,
7
200
  StateManager,
8
201
  stubSync
@@ -10,52 +203,24 @@ import {
10
203
  import {
11
204
  useCallback,
12
205
  useEffect as useEffect3,
13
- useMemo,
206
+ useMemo as useMemo3,
14
207
  useRef,
15
208
  useState as useState2,
16
- useSyncExternalStore
209
+ useSyncExternalStore as useSyncExternalStore2
17
210
  } from "react";
18
211
 
19
212
  // 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
- }
213
+ import { Observable } from "@noya-app/observable";
49
214
  var shouldTrackEvents$ = new Observable(false);
50
215
  var shouldTrackTasks$ = new Observable(false);
51
216
 
52
217
  // src/inspector/useStateInspector.tsx
53
- import React3, { useLayoutEffect as useLayoutEffect2 } from "react";
218
+ import React4, { useLayoutEffect as useLayoutEffect2 } from "react";
54
219
  import { createRoot } from "react-dom/client";
55
220
 
56
221
  // src/inspector/StateInspector.tsx
57
- import React2, {
58
- memo,
222
+ import React3, {
223
+ memo as memo2,
59
224
  useEffect as useEffect2,
60
225
  useLayoutEffect
61
226
  } from "react";
@@ -69,10 +234,10 @@ import {
69
234
  } from "react-inspector";
70
235
 
71
236
  // src/inspector/useLocalStorageState.tsx
72
- import React from "react";
237
+ import React2 from "react";
73
238
  var localStorage = typeof window !== "undefined" ? window.localStorage : null;
74
239
  function useLocalStorageState(key, defaultValue) {
75
- const [state, setState] = React.useState(() => {
240
+ const [state, setState] = React2.useState(() => {
76
241
  const value = localStorage?.getItem(key);
77
242
  let result = defaultValue;
78
243
  if (value) {
@@ -117,7 +282,7 @@ function ToggleButton({
117
282
  theme,
118
283
  anchor
119
284
  }) {
120
- return /* @__PURE__ */ React2.createElement(
285
+ return /* @__PURE__ */ React3.createElement(
121
286
  "span",
122
287
  {
123
288
  role: "button",
@@ -138,7 +303,7 @@ function ToggleButton({
138
303
  setShowInspector(!showInspector);
139
304
  }
140
305
  },
141
- /* @__PURE__ */ React2.createElement("span", { style: { width: "12px", height: "12px" } }, showInspector === (anchor === "right") ? /* @__PURE__ */ React2.createElement(
306
+ /* @__PURE__ */ React3.createElement("span", { style: { width: "12px", height: "12px" } }, showInspector === (anchor === "right") ? /* @__PURE__ */ React3.createElement(
142
307
  "svg",
143
308
  {
144
309
  xmlns: "http://www.w3.org/2000/svg",
@@ -148,7 +313,7 @@ function ToggleButton({
148
313
  stroke: "currentColor",
149
314
  className: "size-6"
150
315
  },
151
- /* @__PURE__ */ React2.createElement(
316
+ /* @__PURE__ */ React3.createElement(
152
317
  "path",
153
318
  {
154
319
  strokeLinecap: "round",
@@ -156,7 +321,7 @@ function ToggleButton({
156
321
  d: "m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5"
157
322
  }
158
323
  )
159
- ) : /* @__PURE__ */ React2.createElement(
324
+ ) : /* @__PURE__ */ React3.createElement(
160
325
  "svg",
161
326
  {
162
327
  xmlns: "http://www.w3.org/2000/svg",
@@ -166,7 +331,7 @@ function ToggleButton({
166
331
  stroke: "currentColor",
167
332
  className: "size-6"
168
333
  },
169
- /* @__PURE__ */ React2.createElement(
334
+ /* @__PURE__ */ React3.createElement(
170
335
  "path",
171
336
  {
172
337
  strokeLinecap: "round",
@@ -189,7 +354,7 @@ function DisclosureSection({
189
354
  }) {
190
355
  const theme = colorScheme === "light" ? lightTheme : darkTheme;
191
356
  const borderColor = colorScheme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)";
192
- return /* @__PURE__ */ React2.createElement(
357
+ return /* @__PURE__ */ React3.createElement(
193
358
  "div",
194
359
  {
195
360
  style: {
@@ -199,7 +364,7 @@ function DisclosureSection({
199
364
  ...style
200
365
  }
201
366
  },
202
- /* @__PURE__ */ React2.createElement(
367
+ /* @__PURE__ */ React3.createElement(
203
368
  "div",
204
369
  {
205
370
  onClick: () => setOpen?.(!open),
@@ -213,7 +378,7 @@ function DisclosureSection({
213
378
  ...open && { borderBottom: `1px solid ${borderColor}` }
214
379
  }
215
380
  },
216
- setOpen && /* @__PURE__ */ React2.createElement(
381
+ setOpen && /* @__PURE__ */ React3.createElement(
217
382
  Arrow,
218
383
  {
219
384
  expanded: open,
@@ -224,7 +389,7 @@ function DisclosureSection({
224
389
  }
225
390
  }
226
391
  ),
227
- /* @__PURE__ */ React2.createElement("span", { style: { flex: "1 1 0", userSelect: "none" } }, title),
392
+ /* @__PURE__ */ React3.createElement("span", { style: { flex: "1 1 0", userSelect: "none" } }, title),
228
393
  right
229
394
  ),
230
395
  open && children
@@ -238,7 +403,7 @@ function InspectorRow({
238
403
  variant
239
404
  }) {
240
405
  const solidBorderColor = colorScheme === "light" ? "rgb(223 223 223)" : "rgb(29 29 29)";
241
- return /* @__PURE__ */ React2.createElement(
406
+ return /* @__PURE__ */ React3.createElement(
242
407
  "div",
243
408
  {
244
409
  style: {
@@ -256,7 +421,7 @@ function InspectorRow({
256
421
  ...style
257
422
  }
258
423
  },
259
- /* @__PURE__ */ React2.createElement(
424
+ /* @__PURE__ */ React3.createElement(
260
425
  "span",
261
426
  {
262
427
  style: {
@@ -271,7 +436,7 @@ function InspectorRow({
271
436
  );
272
437
  }
273
438
  var HISTORY_ELEMENT_PREFIX = "noya-multiplayer-history-";
274
- var StateInspector = memo(function StateInspector2({
439
+ var StateInspector = memo2(function StateInspector2({
275
440
  state,
276
441
  connectionEvents,
277
442
  connectedUsers,
@@ -280,16 +445,17 @@ var StateInspector = memo(function StateInspector2({
280
445
  unstyled,
281
446
  colorScheme = "light",
282
447
  multiplayerStateManager,
448
+ ephemeralUserData,
283
449
  anchor = "right",
284
450
  forceInit,
285
451
  ...props
286
452
  }) {
287
- const [didMount, setDidMount] = React2.useState(false);
453
+ const [didMount, setDidMount] = React3.useState(false);
288
454
  useLayoutEffect(() => {
289
455
  setDidMount(true);
290
456
  }, []);
291
- const eventsContainerRef = React2.useRef(null);
292
- const historyContainerRef = React2.useRef(null);
457
+ const eventsContainerRef = React3.useRef(null);
458
+ const historyContainerRef = React3.useRef(null);
293
459
  const [showInspector, setShowInspector] = useLocalStorageState(
294
460
  "noya-multiplayer-react-show-inspector",
295
461
  true
@@ -314,6 +480,10 @@ var StateInspector = memo(function StateInspector2({
314
480
  "noya-multiplayer-react-show-tasks",
315
481
  false
316
482
  );
483
+ const [showEphemeral, setShowEphemeral] = useLocalStorageState(
484
+ "noya-multiplayer-react-show-ephemeral",
485
+ false
486
+ );
317
487
  useEffect2(() => {
318
488
  shouldTrackEvents$.set(showEvents);
319
489
  }, [showEvents]);
@@ -325,6 +495,7 @@ var StateInspector = memo(function StateInspector2({
325
495
  eventsContainerRef.current.scrollTop = eventsContainerRef.current.scrollHeight;
326
496
  }
327
497
  }, [connectionEvents]);
498
+ const ephemeral = useEphemeralUserData(ephemeralUserData);
328
499
  const historySnapshot = useManagedHistory(multiplayerStateManager.sm);
329
500
  useEffect2(() => {
330
501
  if (historyContainerRef.current) {
@@ -368,7 +539,7 @@ var StateInspector = memo(function StateInspector2({
368
539
  if (!didMount)
369
540
  return null;
370
541
  if (!showInspector) {
371
- return /* @__PURE__ */ React2.createElement(
542
+ return /* @__PURE__ */ React3.createElement(
372
543
  "div",
373
544
  {
374
545
  ...props,
@@ -380,7 +551,7 @@ var StateInspector = memo(function StateInspector2({
380
551
  },
381
552
  onClick: () => setShowInspector(true)
382
553
  },
383
- /* @__PURE__ */ React2.createElement(
554
+ /* @__PURE__ */ React3.createElement(
384
555
  ToggleButton,
385
556
  {
386
557
  showInspector,
@@ -391,7 +562,7 @@ var StateInspector = memo(function StateInspector2({
391
562
  )
392
563
  );
393
564
  }
394
- return /* @__PURE__ */ React2.createElement(
565
+ return /* @__PURE__ */ React3.createElement(
395
566
  "div",
396
567
  {
397
568
  ...props,
@@ -400,7 +571,7 @@ var StateInspector = memo(function StateInspector2({
400
571
  ...props.style
401
572
  }
402
573
  },
403
- /* @__PURE__ */ React2.createElement(
574
+ /* @__PURE__ */ React3.createElement(
404
575
  DisclosureSection,
405
576
  {
406
577
  isFirst: true,
@@ -412,7 +583,7 @@ var StateInspector = memo(function StateInspector2({
412
583
  flex: "0 0 auto",
413
584
  maxHeight: "200px"
414
585
  },
415
- right: /* @__PURE__ */ React2.createElement(
586
+ right: /* @__PURE__ */ React3.createElement(
416
587
  ToggleButton,
417
588
  {
418
589
  showInspector,
@@ -422,7 +593,7 @@ var StateInspector = memo(function StateInspector2({
422
593
  }
423
594
  )
424
595
  },
425
- /* @__PURE__ */ React2.createElement("div", { style: { ...styles.sectionInner, flex: "0 0 auto" } }, connectedUsers?.map((user) => /* @__PURE__ */ React2.createElement(
596
+ /* @__PURE__ */ React3.createElement("div", { style: { ...styles.sectionInner, flex: "0 0 auto" } }, connectedUsers?.map((user) => /* @__PURE__ */ React3.createElement(
426
597
  InspectorRow,
427
598
  {
428
599
  key: user.id,
@@ -433,7 +604,7 @@ var StateInspector = memo(function StateInspector2({
433
604
  " (",
434
605
  ellipsis(user.id.toString(), 8, "middle"),
435
606
  ")"
436
- )), !connectedUsers && /* @__PURE__ */ React2.createElement(
607
+ )), !connectedUsers && /* @__PURE__ */ React3.createElement(
437
608
  "div",
438
609
  {
439
610
  style: {
@@ -444,17 +615,17 @@ var StateInspector = memo(function StateInspector2({
444
615
  gap: "4px"
445
616
  }
446
617
  },
447
- /* @__PURE__ */ React2.createElement("span", null, "No connected users")
618
+ /* @__PURE__ */ React3.createElement("span", null, "No connected users")
448
619
  ))
449
620
  ),
450
- /* @__PURE__ */ React2.createElement(
621
+ /* @__PURE__ */ React3.createElement(
451
622
  DisclosureSection,
452
623
  {
453
624
  title: "Data",
454
625
  colorScheme,
455
626
  setOpen: setShowData,
456
627
  open: showData,
457
- right: /* @__PURE__ */ React2.createElement(
628
+ right: /* @__PURE__ */ React3.createElement(
458
629
  "span",
459
630
  {
460
631
  style: {
@@ -462,7 +633,7 @@ var StateInspector = memo(function StateInspector2({
462
633
  gap: "4px"
463
634
  }
464
635
  },
465
- /* @__PURE__ */ React2.createElement(
636
+ /* @__PURE__ */ React3.createElement(
466
637
  SmallButton,
467
638
  {
468
639
  theme,
@@ -474,14 +645,14 @@ var StateInspector = memo(function StateInspector2({
474
645
  )
475
646
  )
476
647
  },
477
- /* @__PURE__ */ React2.createElement("div", { style: styles.sectionInner }, /* @__PURE__ */ React2.createElement(InspectorRow, { colorScheme }, /* @__PURE__ */ React2.createElement(
648
+ /* @__PURE__ */ React3.createElement("div", { style: styles.sectionInner }, /* @__PURE__ */ React3.createElement(InspectorRow, { colorScheme }, /* @__PURE__ */ React3.createElement(
478
649
  ObjectInspector,
479
650
  {
480
651
  name: multiplayerStateManager.schema ? "state" : void 0,
481
652
  data: state,
482
653
  theme
483
654
  }
484
- )), multiplayerStateManager.schema && /* @__PURE__ */ React2.createElement(InspectorRow, { colorScheme }, /* @__PURE__ */ React2.createElement(
655
+ )), multiplayerStateManager.schema && /* @__PURE__ */ React3.createElement(InspectorRow, { colorScheme }, /* @__PURE__ */ React3.createElement(
485
656
  ObjectInspector,
486
657
  {
487
658
  name: "schema",
@@ -490,14 +661,14 @@ var StateInspector = memo(function StateInspector2({
490
661
  }
491
662
  )))
492
663
  ),
493
- /* @__PURE__ */ React2.createElement(
664
+ /* @__PURE__ */ React3.createElement(
494
665
  DisclosureSection,
495
666
  {
496
667
  open: showHistory,
497
668
  setOpen: setShowHistory,
498
669
  title: "History",
499
670
  colorScheme,
500
- right: /* @__PURE__ */ React2.createElement(
671
+ right: /* @__PURE__ */ React3.createElement(
501
672
  "span",
502
673
  {
503
674
  style: {
@@ -505,7 +676,7 @@ var StateInspector = memo(function StateInspector2({
505
676
  gap: "4px"
506
677
  }
507
678
  },
508
- /* @__PURE__ */ React2.createElement(
679
+ /* @__PURE__ */ React3.createElement(
509
680
  SmallButton,
510
681
  {
511
682
  disabled: !historySnapshot.canUndo,
@@ -514,7 +685,7 @@ var StateInspector = memo(function StateInspector2({
514
685
  multiplayerStateManager.undo();
515
686
  }
516
687
  },
517
- /* @__PURE__ */ React2.createElement("span", { style: { width: "12px", height: "12px" } }, /* @__PURE__ */ React2.createElement(
688
+ /* @__PURE__ */ React3.createElement("span", { style: { width: "12px", height: "12px" } }, /* @__PURE__ */ React3.createElement(
518
689
  "svg",
519
690
  {
520
691
  xmlns: "http://www.w3.org/2000/svg",
@@ -522,7 +693,7 @@ var StateInspector = memo(function StateInspector2({
522
693
  height: "1em",
523
694
  viewBox: "0 0 20 20"
524
695
  },
525
- /* @__PURE__ */ React2.createElement(
696
+ /* @__PURE__ */ React3.createElement(
526
697
  "path",
527
698
  {
528
699
  fill: "currentColor",
@@ -531,7 +702,7 @@ var StateInspector = memo(function StateInspector2({
531
702
  )
532
703
  ))
533
704
  ),
534
- /* @__PURE__ */ React2.createElement(
705
+ /* @__PURE__ */ React3.createElement(
535
706
  SmallButton,
536
707
  {
537
708
  disabled: !historySnapshot.canRedo,
@@ -540,7 +711,7 @@ var StateInspector = memo(function StateInspector2({
540
711
  multiplayerStateManager.redo();
541
712
  }
542
713
  },
543
- /* @__PURE__ */ React2.createElement("span", { style: { width: "12px", height: "12px" } }, /* @__PURE__ */ React2.createElement(
714
+ /* @__PURE__ */ React3.createElement("span", { style: { width: "12px", height: "12px" } }, /* @__PURE__ */ React3.createElement(
544
715
  "svg",
545
716
  {
546
717
  xmlns: "http://www.w3.org/2000/svg",
@@ -548,7 +719,7 @@ var StateInspector = memo(function StateInspector2({
548
719
  height: "1em",
549
720
  viewBox: "0 0 20 20"
550
721
  },
551
- /* @__PURE__ */ React2.createElement(
722
+ /* @__PURE__ */ React3.createElement(
552
723
  "path",
553
724
  {
554
725
  fill: "currentColor",
@@ -559,7 +730,7 @@ var StateInspector = memo(function StateInspector2({
559
730
  )
560
731
  )
561
732
  },
562
- /* @__PURE__ */ React2.createElement("div", { ref: historyContainerRef, style: styles.sectionInner }, /* @__PURE__ */ React2.createElement(
733
+ /* @__PURE__ */ React3.createElement("div", { ref: historyContainerRef, style: styles.sectionInner }, /* @__PURE__ */ React3.createElement(
563
734
  "div",
564
735
  {
565
736
  id: `${HISTORY_ELEMENT_PREFIX}0`,
@@ -573,7 +744,7 @@ var StateInspector = memo(function StateInspector2({
573
744
  background: historySnapshot.historyIndex === 0 ? "rgb(59 130 246 / 15%)" : void 0
574
745
  }
575
746
  },
576
- /* @__PURE__ */ React2.createElement(
747
+ /* @__PURE__ */ React3.createElement(
577
748
  "span",
578
749
  {
579
750
  style: {
@@ -585,7 +756,7 @@ var StateInspector = memo(function StateInspector2({
585
756
  },
586
757
  "Initial state"
587
758
  )
588
- ), historySnapshot.history.map((entry, index) => /* @__PURE__ */ React2.createElement(
759
+ ), historySnapshot.history.map((entry, index) => /* @__PURE__ */ React3.createElement(
589
760
  "div",
590
761
  {
591
762
  id: `${HISTORY_ELEMENT_PREFIX}${index + 1}`,
@@ -596,7 +767,7 @@ var StateInspector = memo(function StateInspector2({
596
767
  background: index + 1 === historySnapshot.historyIndex ? "rgb(59 130 246 / 15%)" : void 0
597
768
  }
598
769
  },
599
- entry.redoPatches?.map((patch, j) => /* @__PURE__ */ React2.createElement(
770
+ entry.redoPatches?.map((patch, j) => /* @__PURE__ */ React3.createElement(
600
771
  "pre",
601
772
  {
602
773
  key: j,
@@ -606,14 +777,14 @@ var StateInspector = memo(function StateInspector2({
606
777
  flexWrap: "wrap"
607
778
  }
608
779
  },
609
- 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 })),
610
- 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 })),
611
- 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))),
612
- patch.op === "move" && /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.from)), " -> ", /* @__PURE__ */ React2.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.path)))
780
+ patch.op === "add" && /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.path)), " = ", /* @__PURE__ */ React3.createElement(ObjectInspector, { data: patch.value, theme })),
781
+ patch.op === "replace" && /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.path)), " = ", /* @__PURE__ */ React3.createElement(ObjectInspector, { data: patch.value, theme })),
782
+ patch.op === "remove" && /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("span", { style: { color: theme.OBJECT_VALUE_STRING_COLOR } }, "delete", " "), /* @__PURE__ */ React3.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.path))),
783
+ patch.op === "move" && /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.from)), " -> ", /* @__PURE__ */ React3.createElement("span", { style: { fontStyle: "italic" } }, pathToString(patch.path)))
613
784
  ))
614
785
  )))
615
786
  ),
616
- /* @__PURE__ */ React2.createElement(
787
+ /* @__PURE__ */ React3.createElement(
617
788
  DisclosureSection,
618
789
  {
619
790
  title: "Tasks",
@@ -621,7 +792,7 @@ var StateInspector = memo(function StateInspector2({
621
792
  open: showTasks,
622
793
  setOpen: setShowTasks
623
794
  },
624
- /* @__PURE__ */ React2.createElement("div", { style: styles.sectionInner }, tasks?.map((task) => /* @__PURE__ */ React2.createElement(
795
+ /* @__PURE__ */ React3.createElement("div", { style: styles.sectionInner }, tasks?.map((task) => /* @__PURE__ */ React3.createElement(
625
796
  InspectorRow,
626
797
  {
627
798
  key: task.id,
@@ -630,7 +801,7 @@ var StateInspector = memo(function StateInspector2({
630
801
  backgroundColor: task.status === "done" ? "rgba(0,255,0,0.2)" : task.status === "error" ? "rgba(255,0,0,0.2)" : void 0
631
802
  }
632
803
  },
633
- /* @__PURE__ */ React2.createElement(
804
+ /* @__PURE__ */ React3.createElement(
634
805
  ObjectInspector,
635
806
  {
636
807
  name: task.name,
@@ -640,7 +811,25 @@ var StateInspector = memo(function StateInspector2({
640
811
  )
641
812
  )))
642
813
  ),
643
- /* @__PURE__ */ React2.createElement(
814
+ /* @__PURE__ */ React3.createElement(
815
+ DisclosureSection,
816
+ {
817
+ open: showEphemeral,
818
+ setOpen: setShowEphemeral,
819
+ title: "Ephemeral User Data",
820
+ colorScheme
821
+ },
822
+ /* @__PURE__ */ React3.createElement("div", { style: styles.sectionInner }, Object.entries(ephemeral).map(([key, value]) => /* @__PURE__ */ React3.createElement(InspectorRow, { key, colorScheme }, /* @__PURE__ */ React3.createElement(
823
+ ObjectInspector,
824
+ {
825
+ name: key,
826
+ data: value,
827
+ theme,
828
+ expandLevel: 10
829
+ }
830
+ ))))
831
+ ),
832
+ /* @__PURE__ */ React3.createElement(
644
833
  DisclosureSection,
645
834
  {
646
835
  open: showEvents,
@@ -648,8 +837,8 @@ var StateInspector = memo(function StateInspector2({
648
837
  title: "Events",
649
838
  colorScheme
650
839
  },
651
- /* @__PURE__ */ React2.createElement("div", { ref: eventsContainerRef, style: styles.sectionInner }, connectionEvents?.map(
652
- (event, index) => event.type === "stateChange" ? /* @__PURE__ */ React2.createElement(InspectorRow, { key: index, colorScheme }, "connection:", " ", /* @__PURE__ */ React2.createElement("span", { style: { fontStyle: "italic" } }, event.state.toLowerCase())) : /* @__PURE__ */ React2.createElement(
840
+ /* @__PURE__ */ React3.createElement("div", { ref: eventsContainerRef, style: styles.sectionInner }, connectionEvents?.map(
841
+ (event, index) => event.type === "stateChange" ? /* @__PURE__ */ React3.createElement(InspectorRow, { key: index, colorScheme }, "connection:", " ", /* @__PURE__ */ React3.createElement("span", { style: { fontStyle: "italic" } }, event.state.toLowerCase())) : /* @__PURE__ */ React3.createElement(
653
842
  InspectorRow,
654
843
  {
655
844
  key: index,
@@ -659,7 +848,7 @@ var StateInspector = memo(function StateInspector2({
659
848
  padding: "0px 12px 1px"
660
849
  }
661
850
  },
662
- /* @__PURE__ */ React2.createElement(
851
+ /* @__PURE__ */ React3.createElement(
663
852
  ObjectInspector,
664
853
  {
665
854
  data: event.message,
@@ -672,7 +861,7 @@ var StateInspector = memo(function StateInspector2({
672
861
  expanded
673
862
  }) => {
674
863
  const direction = event.type === "send" ? "up" : "down";
675
- return depth === 0 ? /* @__PURE__ */ React2.createElement(ObjectRootLabel, { direction, data }) : /* @__PURE__ */ React2.createElement(
864
+ return depth === 0 ? /* @__PURE__ */ React3.createElement(ObjectRootLabel, { direction, data }) : /* @__PURE__ */ React3.createElement(
676
865
  ObjectLabel,
677
866
  {
678
867
  direction,
@@ -685,7 +874,7 @@ var StateInspector = memo(function StateInspector2({
685
874
  }
686
875
  )
687
876
  )
688
- ), !connectionEvents && /* @__PURE__ */ React2.createElement(
877
+ ), !connectionEvents && /* @__PURE__ */ React3.createElement(
689
878
  "div",
690
879
  {
691
880
  style: {
@@ -696,18 +885,18 @@ var StateInspector = memo(function StateInspector2({
696
885
  gap: "4px"
697
886
  }
698
887
  },
699
- /* @__PURE__ */ React2.createElement("span", null, "No recorded events")
888
+ /* @__PURE__ */ React3.createElement("span", null, "No recorded events")
700
889
  ))
701
890
  )
702
891
  );
703
892
  });
704
893
  var ObjectRootLabel = ({ name, data, direction }) => {
705
894
  if (typeof name === "string") {
706
- return /* @__PURE__ */ React2.createElement("span", null, /* @__PURE__ */ React2.createElement(ObjectName, { name }), /* @__PURE__ */ React2.createElement("span", null, ": "), /* @__PURE__ */ React2.createElement(ObjectPreview, { data }));
895
+ return /* @__PURE__ */ React3.createElement("span", null, /* @__PURE__ */ React3.createElement(ObjectName, { name }), /* @__PURE__ */ React3.createElement("span", null, ": "), /* @__PURE__ */ React3.createElement(ObjectPreview, { data }));
707
896
  }
708
897
  if (direction === "up" || direction === "down") {
709
898
  const arrow = direction === "up" ? "\u2191" : "\u2193";
710
- return /* @__PURE__ */ React2.createElement("span", null, /* @__PURE__ */ React2.createElement(
899
+ return /* @__PURE__ */ React3.createElement("span", null, /* @__PURE__ */ React3.createElement(
711
900
  "span",
712
901
  {
713
902
  style: {
@@ -724,9 +913,9 @@ var ObjectRootLabel = ({ name, data, direction }) => {
724
913
  }
725
914
  },
726
915
  arrow
727
- ), /* @__PURE__ */ React2.createElement(ObjectPreview, { data }));
916
+ ), /* @__PURE__ */ React3.createElement(ObjectPreview, { data }));
728
917
  } else {
729
- return /* @__PURE__ */ React2.createElement(ObjectPreview, { data });
918
+ return /* @__PURE__ */ React3.createElement(ObjectPreview, { data });
730
919
  }
731
920
  };
732
921
  function Arrow({
@@ -734,7 +923,7 @@ function Arrow({
734
923
  onClick,
735
924
  style
736
925
  }) {
737
- return /* @__PURE__ */ React2.createElement(
926
+ return /* @__PURE__ */ React3.createElement(
738
927
  "span",
739
928
  {
740
929
  role: "button",
@@ -777,7 +966,7 @@ function SmallButton({
777
966
  theme,
778
967
  disabled
779
968
  }) {
780
- return /* @__PURE__ */ React2.createElement(
969
+ return /* @__PURE__ */ React3.createElement(
781
970
  "button",
782
971
  {
783
972
  type: "button",
@@ -823,9 +1012,10 @@ function useStateInspector({
823
1012
  colorScheme,
824
1013
  anchor,
825
1014
  multiplayerStateManager,
1015
+ ephemeralUserData,
826
1016
  forceInit
827
1017
  }) {
828
- const [root, setRoot] = React3.useState(null);
1018
+ const [root, setRoot] = React4.useState(null);
829
1019
  useLayoutEffect2(() => {
830
1020
  if (disabled)
831
1021
  return;
@@ -841,7 +1031,7 @@ function useStateInspector({
841
1031
  if (!root)
842
1032
  return;
843
1033
  root.render(
844
- /* @__PURE__ */ React3.createElement(
1034
+ /* @__PURE__ */ React4.createElement(
845
1035
  StateInspector,
846
1036
  {
847
1037
  state,
@@ -852,7 +1042,8 @@ function useStateInspector({
852
1042
  colorScheme,
853
1043
  anchor,
854
1044
  forceInit,
855
- multiplayerStateManager
1045
+ multiplayerStateManager,
1046
+ ephemeralUserData
856
1047
  }
857
1048
  )
858
1049
  );
@@ -866,7 +1057,8 @@ function useStateInspector({
866
1057
  multiplayerStateManager,
867
1058
  userId,
868
1059
  tasks,
869
- forceInit
1060
+ forceInit,
1061
+ ephemeralUserData
870
1062
  ]);
871
1063
  }
872
1064
 
@@ -876,7 +1068,7 @@ function useSyncStateManager(stateManager, path) {
876
1068
  () => typeof path === "string" ? stateManager.getState(path) : stateManager.getState(),
877
1069
  [stateManager, path]
878
1070
  );
879
- return useSyncExternalStore(
1071
+ return useSyncExternalStore2(
880
1072
  stateManager.addListener,
881
1073
  getSnapshot,
882
1074
  getSnapshot
@@ -923,19 +1115,26 @@ function useManagedState(createInitialState, options) {
923
1115
  return [state, setState, stateManager];
924
1116
  }
925
1117
  function useManagedHistory(stateManager) {
926
- return useSyncExternalStore(
1118
+ return useSyncExternalStore2(
927
1119
  stateManager.historyEmittor.addListener,
928
1120
  stateManager.getHistorySnapshot,
929
1121
  stateManager.getHistorySnapshot
930
1122
  );
931
1123
  }
932
1124
  function useSyncMultiplayerStateManager(multiplayerStateManager) {
933
- return useSyncExternalStore(
1125
+ return useSyncExternalStore2(
934
1126
  multiplayerStateManager.addListener,
935
1127
  multiplayerStateManager.getOptimisticState,
936
1128
  multiplayerStateManager.getOptimisticState
937
1129
  );
938
1130
  }
1131
+ function useEphemeralUserData(ephemeralUserData) {
1132
+ return useSyncExternalStore2(
1133
+ ephemeralUserData.addListener,
1134
+ ephemeralUserData.getSnapshot,
1135
+ ephemeralUserData.getSnapshot
1136
+ );
1137
+ }
939
1138
  var defaultStubSync = stubSync();
940
1139
  function useMultiplayerState(initialState, options) {
941
1140
  const { sync = defaultStubSync, ...rest } = options ?? {};
@@ -945,13 +1144,18 @@ function useMultiplayerState(initialState, options) {
945
1144
  const syncRef = useRef(sync);
946
1145
  const [connectionEvents, setConnectionEvents] = useState2();
947
1146
  const [connectedUsers, setConnectedUsers] = useState2([]);
1147
+ const [ephemeralUserData] = useState2(
1148
+ () => new EphemeralUserData()
1149
+ );
948
1150
  const [tasks, setTasks] = useState2([]);
949
1151
  const [userId, setUserId] = useState2();
950
- const extras = useMemo(() => {
1152
+ const extras = useMemo3(() => {
951
1153
  return {
952
1154
  tasks,
1155
+ userId,
953
1156
  connectionEvents,
954
- connectedUsers
1157
+ connectedUsers,
1158
+ ephemeralUserData
955
1159
  // evaluate: async (code: string) => {
956
1160
  // const payload = await rpcManager.request({ type: "eval", code });
957
1161
  // if (payload.type !== "eval") {
@@ -959,7 +1163,7 @@ function useMultiplayerState(initialState, options) {
959
1163
  // }
960
1164
  // },
961
1165
  };
962
- }, [tasks, connectionEvents, connectedUsers]);
1166
+ }, [tasks, connectionEvents, connectedUsers, ephemeralUserData, userId]);
963
1167
  const globalTrackEvents = useObservable(shouldTrackEvents$);
964
1168
  const globalTrackTasks = useObservable(shouldTrackTasks$);
965
1169
  const shouldTrackEvents = options?.trackConnectionEvents ?? globalTrackEvents;
@@ -992,12 +1196,13 @@ function useMultiplayerState(initialState, options) {
992
1196
  if (syncRef.current) {
993
1197
  return syncRef.current({
994
1198
  ms: multiplayerStateManager,
1199
+ ephemeralUserData,
995
1200
  onConnectionEvent: (e) => trackEventsCallbackRef.current?.(e),
996
1201
  onChangeConnectedUsers: (users, userId2) => trackConnectedUsersCallbackRef.current?.(users, userId2),
997
1202
  onChangeTasks: (tasks2) => trackTasksCallbackRef.current?.(tasks2)
998
1203
  });
999
1204
  }
1000
- }, [multiplayerStateManager]);
1205
+ }, [ephemeralUserData, multiplayerStateManager]);
1001
1206
  const state = useSyncMultiplayerStateManager(multiplayerStateManager);
1002
1207
  const forceInit = useCallback(() => {
1003
1208
  const state2 = typeof initialState === "function" ? initialState() : initialState;
@@ -1006,6 +1211,7 @@ function useMultiplayerState(initialState, options) {
1006
1211
  multiplayerStateManager.sendInit({ force: true, state: state2, schema });
1007
1212
  }, [initialState, multiplayerStateManager, options?.schema]);
1008
1213
  useStateInspector({
1214
+ ephemeralUserData,
1009
1215
  multiplayerStateManager,
1010
1216
  state,
1011
1217
  connectionEvents,
@@ -1025,6 +1231,14 @@ function useMultiplayerState(initialState, options) {
1025
1231
  }
1026
1232
  export {
1027
1233
  StateInspector,
1234
+ UserNameTag,
1235
+ UserPointer2 as UserPointer,
1236
+ UserPointerContainer,
1237
+ UserPointerIcon,
1238
+ UserPointersOverlay,
1239
+ getAvatarInitials,
1240
+ getAvatarStyle,
1241
+ useEphemeralUserData,
1028
1242
  useManagedHistory,
1029
1243
  useManagedState,
1030
1244
  useMultiplayerState,