use-everywhere 0.9.0 → 0.10.0

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.
@@ -26,6 +26,11 @@ __export(devtools_exports, {
26
26
  module.exports = __toCommonJS(devtools_exports);
27
27
 
28
28
  // src/devtools/inspector.tsx
29
+ var import_react3 = require("react");
30
+ var import_react_dom = require("react-dom");
31
+ var import_core3 = require("@use-everywhere/core");
32
+
33
+ // src/devtools/panel.tsx
29
34
  var import_react2 = require("react");
30
35
  var import_core2 = require("@use-everywhere/core");
31
36
 
@@ -113,130 +118,28 @@ function usePeers(options) {
113
118
  );
114
119
  }
115
120
 
116
- // src/devtools/styles.ts
117
- var STYLES = `
118
- .ue-ins {
119
- position: fixed;
120
- z-index: 2147483000;
121
- font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;
122
- color: #e6edf3;
123
- background: #0d1117;
124
- border: 1px solid #30363d;
125
- border-radius: 8px;
126
- box-shadow: 0 8px 32px rgb(0 0 0 / 0.4);
127
- max-width: min(420px, calc(100vw - 24px));
128
- overflow: hidden;
129
- }
130
- .ue-ins--bottom-right { bottom: 12px; right: 12px; }
131
- .ue-ins--bottom-left { bottom: 12px; left: 12px; }
132
- .ue-ins--top-right { top: 12px; right: 12px; }
133
- .ue-ins--top-left { top: 12px; left: 12px; }
134
-
135
- .ue-ins__bar {
136
- display: flex;
137
- align-items: center;
138
- gap: 8px;
139
- width: 100%;
140
- padding: 7px 10px;
141
- background: #161b22;
142
- border: 0;
143
- color: inherit;
144
- font: inherit;
145
- cursor: pointer;
146
- text-align: left;
147
- }
148
- .ue-ins__dot { width: 7px; height: 7px; border-radius: 50%; background: #3fb950; flex: none; }
149
- .ue-ins__title { font-weight: 600; }
150
- .ue-ins__muted { color: #8b949e; }
151
- .ue-ins__crown { margin-left: auto; color: #d29922; }
152
-
153
- .ue-ins__body { max-height: 60vh; overflow-y: auto; }
154
- .ue-ins__section { border-top: 1px solid #21262d; padding: 8px 10px; }
155
- .ue-ins__h {
156
- color: #8b949e;
157
- text-transform: uppercase;
158
- letter-spacing: 0.06em;
159
- font-size: 10px;
160
- margin-bottom: 5px;
161
- }
162
- .ue-ins__row { display: flex; gap: 8px; padding: 1px 0; }
163
- .ue-ins__k { color: #79c0ff; flex: none; }
164
- .ue-ins__v { color: #e6edf3; overflow-wrap: anywhere; }
165
- button.ue-ins__v {
166
- background: none;
167
- border: 0;
168
- padding: 0;
169
- font: inherit;
170
- text-align: left;
171
- }
172
- .ue-ins__ver { color: #6e7681; margin-left: auto; flex: none; }
173
- .ue-ins__empty { color: #6e7681; }
174
-
175
- .ue-ins__log { display: flex; flex-direction: column-reverse; max-height: 190px; overflow-y: auto; }
176
- .ue-ins__wire { display: flex; gap: 7px; padding: 1px 0; white-space: nowrap; }
177
- .ue-ins__dir { flex: none; width: 9px; }
178
- .ue-ins__dir--out { color: #d29922; }
179
- .ue-ins__dir--in { color: #3fb950; }
180
- .ue-ins__scope { color: #e6edf3; }
181
- .ue-ins__from { color: #6e7681; margin-left: auto; }
182
-
183
- .ue-ins__tools { display: flex; gap: 6px; align-items: center; margin-bottom: 5px; }
184
- .ue-ins__btn {
185
- background: #21262d;
186
- border: 1px solid #30363d;
187
- border-radius: 5px;
188
- color: #c9d1d9;
189
- font: inherit;
190
- font-size: 10px;
191
- padding: 1px 7px;
192
- cursor: pointer;
193
- }
194
- .ue-ins__btn[aria-pressed='true'] { background: #1f6feb; border-color: #1f6feb; color: #fff; }
195
- .ue-ins__filter {
196
- flex: 1;
197
- min-width: 0;
198
- background: #0d1117;
199
- border: 1px solid #30363d;
200
- border-radius: 5px;
201
- color: #c9d1d9;
202
- font: inherit;
203
- font-size: 10px;
204
- padding: 1px 6px;
205
- }
206
- .ue-ins__edit {
207
- flex: 1;
208
- min-width: 0;
209
- background: #0d1117;
210
- border: 1px solid #1f6feb;
211
- border-radius: 4px;
212
- color: #e6edf3;
213
- font: inherit;
214
- padding: 0 4px;
215
- }
216
- .ue-ins__v--editable { cursor: text; text-decoration: underline dotted #30363d; }
217
- .ue-ins__v--invalid { color: #f85149; }
218
- .ue-ins__paused { color: #d29922; }
219
- `;
220
-
221
- // src/devtools/inspector.tsx
121
+ // src/devtools/panel.tsx
222
122
  var import_jsx_runtime = require("react/jsx-runtime");
223
123
  var short = (id) => id.slice(0, 6);
224
124
  function wireLabel(wire) {
225
125
  return `${wire.scope}/${wire.type}`;
226
126
  }
227
- function Inspector({
228
- name = import_core2.DEFAULT_NAME,
229
- position = "bottom-right",
230
- limit = 50,
231
- defaultOpen = false,
232
- leaseMs = 3e3
233
- } = {}) {
127
+ var SCOPES = ["all", "state", "leader", "presence", "channel"];
128
+ function Panel({
129
+ name,
130
+ limit,
131
+ leaseMs,
132
+ position,
133
+ defaultOpen
134
+ }) {
234
135
  const [open, setOpen] = (0, import_react2.useState)(defaultOpen);
235
136
  const [wires, setWires] = (0, import_react2.useState)([]);
236
137
  const [crown, setCrown] = (0, import_react2.useState)(null);
237
138
  const [paused, setPaused] = (0, import_react2.useState)(false);
238
139
  const [filter, setFilter] = (0, import_react2.useState)("");
140
+ const [scopeView, setScopeView] = (0, import_react2.useState)("all");
239
141
  const [editing, setEditing] = (0, import_react2.useState)(null);
142
+ const [frames, setFrames] = (0, import_react2.useState)([]);
240
143
  const nextId = (0, import_react2.useRef)(0);
241
144
  const crownAt = (0, import_react2.useRef)(0);
242
145
  const pausedRef = (0, import_react2.useRef)(false);
@@ -269,20 +172,21 @@ function Inspector({
269
172
  }
270
173
  }
271
174
  if (pausedRef.current) return;
175
+ const id = nextId.current++;
176
+ const label = wireLabel(wire);
272
177
  setWires(
273
178
  (prev) => [
274
179
  ...prev.slice(-(limit - 1)),
275
- {
276
- id: nextId.current++,
277
- direction,
278
- scope: wire.scope,
279
- label: wireLabel(wire),
280
- from: short(wire.clientId)
281
- }
180
+ { id, direction, scope: wire.scope, label, from: short(wire.clientId) }
282
181
  ].slice(-limit)
283
182
  );
183
+ if (wire.scope === "state") {
184
+ setFrames(
185
+ (prev) => [...prev, { wireId: id, label, snapshot: { ...store.getSnapshot() } }].slice(-limit)
186
+ );
187
+ }
284
188
  });
285
- }, [name, limit]);
189
+ }, [name, limit, store]);
286
190
  (0, import_react2.useEffect)(() => {
287
191
  const timer = setInterval(
288
192
  () => {
@@ -291,9 +195,6 @@ function Inspector({
291
195
  crownAt.current = 0;
292
196
  }
293
197
  },
294
- // Quarter of the lease, so a vacated crown clears well within it. The
295
- // floor is low enough that a short lease still works rather than being
296
- // silently rounded up to something coarser than the lease itself.
297
198
  Math.max(50, Math.floor(leaseMs / 4))
298
199
  );
299
200
  return () => clearInterval(timer);
@@ -301,9 +202,11 @@ function Inspector({
301
202
  const selfId = store.clientId;
302
203
  const entries = Object.entries(versions);
303
204
  const needle = filter.trim().toLowerCase();
304
- const shown = needle ? wires.filter(
305
- (wire) => wire.label.toLowerCase().includes(needle) || wire.from.toLowerCase().includes(needle)
306
- ) : wires;
205
+ const shown = wires.filter((wire) => {
206
+ if (scopeView !== "all" && wire.scope !== scopeView) return false;
207
+ if (!needle) return true;
208
+ return wire.label.toLowerCase().includes(needle) || wire.from.toLowerCase().includes(needle);
209
+ });
307
210
  const commit = (key, draft) => {
308
211
  let value;
309
212
  try {
@@ -322,8 +225,13 @@ function Inspector({
322
225
  return false;
323
226
  }
324
227
  };
228
+ const restore = (frame) => {
229
+ for (const [key, value] of Object.entries(frame.snapshot)) {
230
+ if (Object.is(store.getSnapshot()[key], value)) continue;
231
+ store.set(key, value);
232
+ }
233
+ };
325
234
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `ue-ins ue-ins--${position}`, "data-testid": "ue-inspector", children: [
326
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: STYLES }),
327
235
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
328
236
  "button",
329
237
  {
@@ -426,7 +334,10 @@ function Inspector({
426
334
  {
427
335
  type: "button",
428
336
  className: "ue-ins__btn",
429
- onClick: () => setWires([]),
337
+ onClick: () => {
338
+ setWires([]);
339
+ setFrames([]);
340
+ },
430
341
  "data-testid": "ue-clear",
431
342
  children: "clear"
432
343
  }
@@ -443,15 +354,189 @@ function Inspector({
443
354
  }
444
355
  )
445
356
  ] }),
446
- shown.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ue-ins__empty", children: wires.length === 0 ? "nothing yet" : "no matches" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ue-ins__log", children: shown.map((wire) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__wire", children: [
357
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ue-ins__tools", role: "group", "aria-label": "Scope", children: SCOPES.map((scope) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
358
+ "button",
359
+ {
360
+ type: "button",
361
+ className: "ue-ins__btn",
362
+ "aria-pressed": scopeView === scope,
363
+ onClick: () => setScopeView(scope),
364
+ "data-testid": `ue-scope-${scope}`,
365
+ children: scope
366
+ },
367
+ scope
368
+ )) }),
369
+ shown.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ue-ins__empty", "data-testid": "ue-wires", children: wires.length === 0 ? "nothing yet" : "no matches" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ue-ins__log", "data-testid": "ue-wires", children: shown.map((wire) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__wire", children: [
447
370
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `ue-ins__dir ue-ins__dir--${wire.direction}`, children: wire.direction === "out" ? "\u2192" : "\u2190" }),
448
371
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ue-ins__scope", children: wire.label }),
449
372
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ue-ins__from", children: wire.from })
450
373
  ] }, wire.id)) })
374
+ ] }),
375
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__section", children: [
376
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__h", children: [
377
+ "Timeline (",
378
+ frames.length,
379
+ ")"
380
+ ] }),
381
+ frames.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ue-ins__empty", children: "no state on the wire yet" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ue-ins__log", children: frames.map((frame) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__wire", children: [
382
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
383
+ "button",
384
+ {
385
+ type: "button",
386
+ className: "ue-ins__btn",
387
+ onClick: () => restore(frame),
388
+ "data-testid": `ue-restore-${String(frame.wireId)}`,
389
+ children: "restore"
390
+ }
391
+ ),
392
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ue-ins__scope", children: frame.label }),
393
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "ue-ins__from", children: [
394
+ Object.keys(frame.snapshot).length,
395
+ " key",
396
+ Object.keys(frame.snapshot).length === 1 ? "" : "s"
397
+ ] })
398
+ ] }, frame.wireId)) })
451
399
  ] })
452
400
  ] }) : null
453
401
  ] });
454
402
  }
403
+
404
+ // src/devtools/styles.ts
405
+ var STYLES = `
406
+ .ue-ins {
407
+ position: fixed;
408
+ z-index: 2147483000;
409
+ font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;
410
+ color: #e6edf3;
411
+ background: #0d1117;
412
+ border: 1px solid #30363d;
413
+ border-radius: 8px;
414
+ box-shadow: 0 8px 32px rgb(0 0 0 / 0.4);
415
+ max-width: min(420px, calc(100vw - 24px));
416
+ overflow: hidden;
417
+ }
418
+ .ue-ins--bottom-right { bottom: 12px; right: 12px; }
419
+ .ue-ins--bottom-left { bottom: 12px; left: 12px; }
420
+ .ue-ins--top-right { top: 12px; right: 12px; }
421
+ .ue-ins--top-left { top: 12px; left: 12px; }
422
+
423
+ .ue-ins__bar {
424
+ display: flex;
425
+ align-items: center;
426
+ gap: 8px;
427
+ width: 100%;
428
+ padding: 7px 10px;
429
+ background: #161b22;
430
+ border: 0;
431
+ color: inherit;
432
+ font: inherit;
433
+ cursor: pointer;
434
+ text-align: left;
435
+ }
436
+ .ue-ins__dot { width: 7px; height: 7px; border-radius: 50%; background: #3fb950; flex: none; }
437
+ .ue-ins__title { font-weight: 600; }
438
+ .ue-ins__muted { color: #8b949e; }
439
+ .ue-ins__crown { margin-left: auto; color: #d29922; }
440
+
441
+ .ue-ins__body { max-height: 60vh; overflow-y: auto; }
442
+ .ue-ins__section { border-top: 1px solid #21262d; padding: 8px 10px; }
443
+ .ue-ins__h {
444
+ color: #8b949e;
445
+ text-transform: uppercase;
446
+ letter-spacing: 0.06em;
447
+ font-size: 10px;
448
+ margin-bottom: 5px;
449
+ }
450
+ .ue-ins__row { display: flex; gap: 8px; padding: 1px 0; }
451
+ .ue-ins__k { color: #79c0ff; flex: none; }
452
+ .ue-ins__v { color: #e6edf3; overflow-wrap: anywhere; }
453
+ button.ue-ins__v {
454
+ background: none;
455
+ border: 0;
456
+ padding: 0;
457
+ font: inherit;
458
+ text-align: left;
459
+ }
460
+ .ue-ins__ver { color: #6e7681; margin-left: auto; flex: none; }
461
+ .ue-ins__empty { color: #6e7681; }
462
+
463
+ .ue-ins__log { display: flex; flex-direction: column-reverse; max-height: 190px; overflow-y: auto; }
464
+ .ue-ins__wire { display: flex; gap: 7px; padding: 1px 0; white-space: nowrap; }
465
+ .ue-ins__dir { flex: none; width: 9px; }
466
+ .ue-ins__dir--out { color: #d29922; }
467
+ .ue-ins__dir--in { color: #3fb950; }
468
+ .ue-ins__scope { color: #e6edf3; }
469
+ .ue-ins__from { color: #6e7681; margin-left: auto; }
470
+
471
+ .ue-ins__tools { display: flex; gap: 6px; align-items: center; margin-bottom: 5px; }
472
+ .ue-ins__btn {
473
+ background: #21262d;
474
+ border: 1px solid #30363d;
475
+ border-radius: 5px;
476
+ color: #c9d1d9;
477
+ font: inherit;
478
+ font-size: 10px;
479
+ padding: 1px 7px;
480
+ cursor: pointer;
481
+ }
482
+ .ue-ins__btn[aria-pressed='true'] { background: #1f6feb; border-color: #1f6feb; color: #fff; }
483
+ .ue-ins__filter {
484
+ flex: 1;
485
+ min-width: 0;
486
+ background: #0d1117;
487
+ border: 1px solid #30363d;
488
+ border-radius: 5px;
489
+ color: #c9d1d9;
490
+ font: inherit;
491
+ font-size: 10px;
492
+ padding: 1px 6px;
493
+ }
494
+ .ue-ins__edit {
495
+ flex: 1;
496
+ min-width: 0;
497
+ background: #0d1117;
498
+ border: 1px solid #1f6feb;
499
+ border-radius: 4px;
500
+ color: #e6edf3;
501
+ font: inherit;
502
+ padding: 0 4px;
503
+ }
504
+ .ue-ins__v--editable { cursor: text; text-decoration: underline dotted #30363d; }
505
+ .ue-ins__v--invalid { color: #f85149; }
506
+ .ue-ins__paused { color: #d29922; }
507
+ `;
508
+
509
+ // src/devtools/inspector.tsx
510
+ var import_jsx_runtime2 = require("react/jsx-runtime");
511
+ function Inspector({
512
+ name = import_core3.DEFAULT_NAME,
513
+ position = "bottom-right",
514
+ limit = 50,
515
+ defaultOpen = false,
516
+ leaseMs = 3e3
517
+ } = {}) {
518
+ const [root, setRoot] = (0, import_react3.useState)(null);
519
+ const attach = (host) => {
520
+ if (!host || root) return;
521
+ setRoot(host.shadowRoot ?? host.attachShadow({ mode: "open" }));
522
+ };
523
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: attach, "data-testid": "ue-inspector-host", children: root ? (0, import_react_dom.createPortal)(
524
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
525
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: STYLES }),
526
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
527
+ Panel,
528
+ {
529
+ name,
530
+ position,
531
+ limit,
532
+ defaultOpen,
533
+ leaseMs
534
+ }
535
+ )
536
+ ] }),
537
+ root
538
+ ) : null });
539
+ }
455
540
  // Annotate the CommonJS export names for ESM import in node:
456
541
  0 && (module.exports = {
457
542
  Inspector
@@ -18,8 +18,8 @@ interface InspectorProps {
18
18
 
19
19
  /**
20
20
  * A floating panel showing what this tab is saying and hearing on the bus:
21
- * peers, the leader, store keys with their version clocks, and a live wire log
22
- * in both directions.
21
+ * peers, the leader, store keys with their version clocks, a live wire log in
22
+ * both directions, and a timeline of the state each wire produced.
23
23
  *
24
24
  * It deliberately does **not** create a Leader. Under dynamic eligibility,
25
25
  * mounting one with `eligible: false` would disable candidacy for the whole
@@ -29,6 +29,17 @@ interface InspectorProps {
29
29
  *
30
30
  * Presence is fine to use: the bus heartbeats regardless of whether anything
31
31
  * created a Presence, so usePeers observes rather than perturbs.
32
+ *
33
+ * **The panel lives in a shadow root.** Its styles used to be a `<style>` tag
34
+ * in the page, which is only ever half true: `.ue-ins` could not leak out, but
35
+ * everything in the host document leaked *in* — a `* { box-sizing }` reset, an
36
+ * app-wide `button { text-transform: uppercase }`, a Tailwind preflight, any
37
+ * `!important`. A devtool that renders differently depending on whose app it is
38
+ * mounted in is a devtool you cannot trust when it looks wrong. Inside a shadow
39
+ * root, page CSS does not apply and the panel's own CSS cannot escape.
40
+ *
41
+ * A consequence worth knowing when testing an app that mounts it: the panel is
42
+ * **not** in `document`. Reach it through `host.shadowRoot`.
32
43
  */
33
44
  declare function Inspector({ name, position, limit, defaultOpen, leaseMs, }?: InspectorProps): react.JSX.Element;
34
45
 
@@ -18,8 +18,8 @@ interface InspectorProps {
18
18
 
19
19
  /**
20
20
  * A floating panel showing what this tab is saying and hearing on the bus:
21
- * peers, the leader, store keys with their version clocks, and a live wire log
22
- * in both directions.
21
+ * peers, the leader, store keys with their version clocks, a live wire log in
22
+ * both directions, and a timeline of the state each wire produced.
23
23
  *
24
24
  * It deliberately does **not** create a Leader. Under dynamic eligibility,
25
25
  * mounting one with `eligible: false` would disable candidacy for the whole
@@ -29,6 +29,17 @@ interface InspectorProps {
29
29
  *
30
30
  * Presence is fine to use: the bus heartbeats regardless of whether anything
31
31
  * created a Presence, so usePeers observes rather than perturbs.
32
+ *
33
+ * **The panel lives in a shadow root.** Its styles used to be a `<style>` tag
34
+ * in the page, which is only ever half true: `.ue-ins` could not leak out, but
35
+ * everything in the host document leaked *in* — a `* { box-sizing }` reset, an
36
+ * app-wide `button { text-transform: uppercase }`, a Tailwind preflight, any
37
+ * `!important`. A devtool that renders differently depending on whose app it is
38
+ * mounted in is a devtool you cannot trust when it looks wrong. Inside a shadow
39
+ * root, page CSS does not apply and the panel's own CSS cannot escape.
40
+ *
41
+ * A consequence worth knowing when testing an app that mounts it: the panel is
42
+ * **not** in `document`. Reach it through `host.shadowRoot`.
32
43
  */
33
44
  declare function Inspector({ name, position, limit, defaultOpen, leaseMs, }?: InspectorProps): react.JSX.Element;
34
45
 
@@ -5,133 +5,34 @@ import {
5
5
  } from "../chunk-R7PBPLF7.js";
6
6
 
7
7
  // src/devtools/inspector.tsx
8
- import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
9
- import { DEFAULT_NAME, observeBus } from "@use-everywhere/core";
10
-
11
- // src/devtools/styles.ts
12
- var STYLES = `
13
- .ue-ins {
14
- position: fixed;
15
- z-index: 2147483000;
16
- font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;
17
- color: #e6edf3;
18
- background: #0d1117;
19
- border: 1px solid #30363d;
20
- border-radius: 8px;
21
- box-shadow: 0 8px 32px rgb(0 0 0 / 0.4);
22
- max-width: min(420px, calc(100vw - 24px));
23
- overflow: hidden;
24
- }
25
- .ue-ins--bottom-right { bottom: 12px; right: 12px; }
26
- .ue-ins--bottom-left { bottom: 12px; left: 12px; }
27
- .ue-ins--top-right { top: 12px; right: 12px; }
28
- .ue-ins--top-left { top: 12px; left: 12px; }
29
-
30
- .ue-ins__bar {
31
- display: flex;
32
- align-items: center;
33
- gap: 8px;
34
- width: 100%;
35
- padding: 7px 10px;
36
- background: #161b22;
37
- border: 0;
38
- color: inherit;
39
- font: inherit;
40
- cursor: pointer;
41
- text-align: left;
42
- }
43
- .ue-ins__dot { width: 7px; height: 7px; border-radius: 50%; background: #3fb950; flex: none; }
44
- .ue-ins__title { font-weight: 600; }
45
- .ue-ins__muted { color: #8b949e; }
46
- .ue-ins__crown { margin-left: auto; color: #d29922; }
47
-
48
- .ue-ins__body { max-height: 60vh; overflow-y: auto; }
49
- .ue-ins__section { border-top: 1px solid #21262d; padding: 8px 10px; }
50
- .ue-ins__h {
51
- color: #8b949e;
52
- text-transform: uppercase;
53
- letter-spacing: 0.06em;
54
- font-size: 10px;
55
- margin-bottom: 5px;
56
- }
57
- .ue-ins__row { display: flex; gap: 8px; padding: 1px 0; }
58
- .ue-ins__k { color: #79c0ff; flex: none; }
59
- .ue-ins__v { color: #e6edf3; overflow-wrap: anywhere; }
60
- button.ue-ins__v {
61
- background: none;
62
- border: 0;
63
- padding: 0;
64
- font: inherit;
65
- text-align: left;
66
- }
67
- .ue-ins__ver { color: #6e7681; margin-left: auto; flex: none; }
68
- .ue-ins__empty { color: #6e7681; }
69
-
70
- .ue-ins__log { display: flex; flex-direction: column-reverse; max-height: 190px; overflow-y: auto; }
71
- .ue-ins__wire { display: flex; gap: 7px; padding: 1px 0; white-space: nowrap; }
72
- .ue-ins__dir { flex: none; width: 9px; }
73
- .ue-ins__dir--out { color: #d29922; }
74
- .ue-ins__dir--in { color: #3fb950; }
75
- .ue-ins__scope { color: #e6edf3; }
76
- .ue-ins__from { color: #6e7681; margin-left: auto; }
8
+ import { useState as useState2 } from "react";
9
+ import { createPortal } from "react-dom";
10
+ import { DEFAULT_NAME } from "@use-everywhere/core";
77
11
 
78
- .ue-ins__tools { display: flex; gap: 6px; align-items: center; margin-bottom: 5px; }
79
- .ue-ins__btn {
80
- background: #21262d;
81
- border: 1px solid #30363d;
82
- border-radius: 5px;
83
- color: #c9d1d9;
84
- font: inherit;
85
- font-size: 10px;
86
- padding: 1px 7px;
87
- cursor: pointer;
88
- }
89
- .ue-ins__btn[aria-pressed='true'] { background: #1f6feb; border-color: #1f6feb; color: #fff; }
90
- .ue-ins__filter {
91
- flex: 1;
92
- min-width: 0;
93
- background: #0d1117;
94
- border: 1px solid #30363d;
95
- border-radius: 5px;
96
- color: #c9d1d9;
97
- font: inherit;
98
- font-size: 10px;
99
- padding: 1px 6px;
100
- }
101
- .ue-ins__edit {
102
- flex: 1;
103
- min-width: 0;
104
- background: #0d1117;
105
- border: 1px solid #1f6feb;
106
- border-radius: 4px;
107
- color: #e6edf3;
108
- font: inherit;
109
- padding: 0 4px;
110
- }
111
- .ue-ins__v--editable { cursor: text; text-decoration: underline dotted #30363d; }
112
- .ue-ins__v--invalid { color: #f85149; }
113
- .ue-ins__paused { color: #d29922; }
114
- `;
115
-
116
- // src/devtools/inspector.tsx
12
+ // src/devtools/panel.tsx
13
+ import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
14
+ import { observeBus } from "@use-everywhere/core";
117
15
  import { jsx, jsxs } from "react/jsx-runtime";
118
16
  var short = (id) => id.slice(0, 6);
119
17
  function wireLabel(wire) {
120
18
  return `${wire.scope}/${wire.type}`;
121
19
  }
122
- function Inspector({
123
- name = DEFAULT_NAME,
124
- position = "bottom-right",
125
- limit = 50,
126
- defaultOpen = false,
127
- leaseMs = 3e3
128
- } = {}) {
20
+ var SCOPES = ["all", "state", "leader", "presence", "channel"];
21
+ function Panel({
22
+ name,
23
+ limit,
24
+ leaseMs,
25
+ position,
26
+ defaultOpen
27
+ }) {
129
28
  const [open, setOpen] = useState(defaultOpen);
130
29
  const [wires, setWires] = useState([]);
131
30
  const [crown, setCrown] = useState(null);
132
31
  const [paused, setPaused] = useState(false);
133
32
  const [filter, setFilter] = useState("");
33
+ const [scopeView, setScopeView] = useState("all");
134
34
  const [editing, setEditing] = useState(null);
35
+ const [frames, setFrames] = useState([]);
135
36
  const nextId = useRef(0);
136
37
  const crownAt = useRef(0);
137
38
  const pausedRef = useRef(false);
@@ -164,20 +65,21 @@ function Inspector({
164
65
  }
165
66
  }
166
67
  if (pausedRef.current) return;
68
+ const id = nextId.current++;
69
+ const label = wireLabel(wire);
167
70
  setWires(
168
71
  (prev) => [
169
72
  ...prev.slice(-(limit - 1)),
170
- {
171
- id: nextId.current++,
172
- direction,
173
- scope: wire.scope,
174
- label: wireLabel(wire),
175
- from: short(wire.clientId)
176
- }
73
+ { id, direction, scope: wire.scope, label, from: short(wire.clientId) }
177
74
  ].slice(-limit)
178
75
  );
76
+ if (wire.scope === "state") {
77
+ setFrames(
78
+ (prev) => [...prev, { wireId: id, label, snapshot: { ...store.getSnapshot() } }].slice(-limit)
79
+ );
80
+ }
179
81
  });
180
- }, [name, limit]);
82
+ }, [name, limit, store]);
181
83
  useEffect(() => {
182
84
  const timer = setInterval(
183
85
  () => {
@@ -186,9 +88,6 @@ function Inspector({
186
88
  crownAt.current = 0;
187
89
  }
188
90
  },
189
- // Quarter of the lease, so a vacated crown clears well within it. The
190
- // floor is low enough that a short lease still works rather than being
191
- // silently rounded up to something coarser than the lease itself.
192
91
  Math.max(50, Math.floor(leaseMs / 4))
193
92
  );
194
93
  return () => clearInterval(timer);
@@ -196,9 +95,11 @@ function Inspector({
196
95
  const selfId = store.clientId;
197
96
  const entries = Object.entries(versions);
198
97
  const needle = filter.trim().toLowerCase();
199
- const shown = needle ? wires.filter(
200
- (wire) => wire.label.toLowerCase().includes(needle) || wire.from.toLowerCase().includes(needle)
201
- ) : wires;
98
+ const shown = wires.filter((wire) => {
99
+ if (scopeView !== "all" && wire.scope !== scopeView) return false;
100
+ if (!needle) return true;
101
+ return wire.label.toLowerCase().includes(needle) || wire.from.toLowerCase().includes(needle);
102
+ });
202
103
  const commit = (key, draft) => {
203
104
  let value;
204
105
  try {
@@ -217,8 +118,13 @@ function Inspector({
217
118
  return false;
218
119
  }
219
120
  };
121
+ const restore = (frame) => {
122
+ for (const [key, value] of Object.entries(frame.snapshot)) {
123
+ if (Object.is(store.getSnapshot()[key], value)) continue;
124
+ store.set(key, value);
125
+ }
126
+ };
220
127
  return /* @__PURE__ */ jsxs("div", { className: `ue-ins ue-ins--${position}`, "data-testid": "ue-inspector", children: [
221
- /* @__PURE__ */ jsx("style", { children: STYLES }),
222
128
  /* @__PURE__ */ jsxs(
223
129
  "button",
224
130
  {
@@ -321,7 +227,10 @@ function Inspector({
321
227
  {
322
228
  type: "button",
323
229
  className: "ue-ins__btn",
324
- onClick: () => setWires([]),
230
+ onClick: () => {
231
+ setWires([]);
232
+ setFrames([]);
233
+ },
325
234
  "data-testid": "ue-clear",
326
235
  children: "clear"
327
236
  }
@@ -338,15 +247,189 @@ function Inspector({
338
247
  }
339
248
  )
340
249
  ] }),
341
- shown.length === 0 ? /* @__PURE__ */ jsx("div", { className: "ue-ins__empty", children: wires.length === 0 ? "nothing yet" : "no matches" }) : /* @__PURE__ */ jsx("div", { className: "ue-ins__log", children: shown.map((wire) => /* @__PURE__ */ jsxs("div", { className: "ue-ins__wire", children: [
250
+ /* @__PURE__ */ jsx("div", { className: "ue-ins__tools", role: "group", "aria-label": "Scope", children: SCOPES.map((scope) => /* @__PURE__ */ jsx(
251
+ "button",
252
+ {
253
+ type: "button",
254
+ className: "ue-ins__btn",
255
+ "aria-pressed": scopeView === scope,
256
+ onClick: () => setScopeView(scope),
257
+ "data-testid": `ue-scope-${scope}`,
258
+ children: scope
259
+ },
260
+ scope
261
+ )) }),
262
+ shown.length === 0 ? /* @__PURE__ */ jsx("div", { className: "ue-ins__empty", "data-testid": "ue-wires", children: wires.length === 0 ? "nothing yet" : "no matches" }) : /* @__PURE__ */ jsx("div", { className: "ue-ins__log", "data-testid": "ue-wires", children: shown.map((wire) => /* @__PURE__ */ jsxs("div", { className: "ue-ins__wire", children: [
342
263
  /* @__PURE__ */ jsx("span", { className: `ue-ins__dir ue-ins__dir--${wire.direction}`, children: wire.direction === "out" ? "\u2192" : "\u2190" }),
343
264
  /* @__PURE__ */ jsx("span", { className: "ue-ins__scope", children: wire.label }),
344
265
  /* @__PURE__ */ jsx("span", { className: "ue-ins__from", children: wire.from })
345
266
  ] }, wire.id)) })
267
+ ] }),
268
+ /* @__PURE__ */ jsxs("div", { className: "ue-ins__section", children: [
269
+ /* @__PURE__ */ jsxs("div", { className: "ue-ins__h", children: [
270
+ "Timeline (",
271
+ frames.length,
272
+ ")"
273
+ ] }),
274
+ frames.length === 0 ? /* @__PURE__ */ jsx("div", { className: "ue-ins__empty", children: "no state on the wire yet" }) : /* @__PURE__ */ jsx("div", { className: "ue-ins__log", children: frames.map((frame) => /* @__PURE__ */ jsxs("div", { className: "ue-ins__wire", children: [
275
+ /* @__PURE__ */ jsx(
276
+ "button",
277
+ {
278
+ type: "button",
279
+ className: "ue-ins__btn",
280
+ onClick: () => restore(frame),
281
+ "data-testid": `ue-restore-${String(frame.wireId)}`,
282
+ children: "restore"
283
+ }
284
+ ),
285
+ /* @__PURE__ */ jsx("span", { className: "ue-ins__scope", children: frame.label }),
286
+ /* @__PURE__ */ jsxs("span", { className: "ue-ins__from", children: [
287
+ Object.keys(frame.snapshot).length,
288
+ " key",
289
+ Object.keys(frame.snapshot).length === 1 ? "" : "s"
290
+ ] })
291
+ ] }, frame.wireId)) })
346
292
  ] })
347
293
  ] }) : null
348
294
  ] });
349
295
  }
296
+
297
+ // src/devtools/styles.ts
298
+ var STYLES = `
299
+ .ue-ins {
300
+ position: fixed;
301
+ z-index: 2147483000;
302
+ font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;
303
+ color: #e6edf3;
304
+ background: #0d1117;
305
+ border: 1px solid #30363d;
306
+ border-radius: 8px;
307
+ box-shadow: 0 8px 32px rgb(0 0 0 / 0.4);
308
+ max-width: min(420px, calc(100vw - 24px));
309
+ overflow: hidden;
310
+ }
311
+ .ue-ins--bottom-right { bottom: 12px; right: 12px; }
312
+ .ue-ins--bottom-left { bottom: 12px; left: 12px; }
313
+ .ue-ins--top-right { top: 12px; right: 12px; }
314
+ .ue-ins--top-left { top: 12px; left: 12px; }
315
+
316
+ .ue-ins__bar {
317
+ display: flex;
318
+ align-items: center;
319
+ gap: 8px;
320
+ width: 100%;
321
+ padding: 7px 10px;
322
+ background: #161b22;
323
+ border: 0;
324
+ color: inherit;
325
+ font: inherit;
326
+ cursor: pointer;
327
+ text-align: left;
328
+ }
329
+ .ue-ins__dot { width: 7px; height: 7px; border-radius: 50%; background: #3fb950; flex: none; }
330
+ .ue-ins__title { font-weight: 600; }
331
+ .ue-ins__muted { color: #8b949e; }
332
+ .ue-ins__crown { margin-left: auto; color: #d29922; }
333
+
334
+ .ue-ins__body { max-height: 60vh; overflow-y: auto; }
335
+ .ue-ins__section { border-top: 1px solid #21262d; padding: 8px 10px; }
336
+ .ue-ins__h {
337
+ color: #8b949e;
338
+ text-transform: uppercase;
339
+ letter-spacing: 0.06em;
340
+ font-size: 10px;
341
+ margin-bottom: 5px;
342
+ }
343
+ .ue-ins__row { display: flex; gap: 8px; padding: 1px 0; }
344
+ .ue-ins__k { color: #79c0ff; flex: none; }
345
+ .ue-ins__v { color: #e6edf3; overflow-wrap: anywhere; }
346
+ button.ue-ins__v {
347
+ background: none;
348
+ border: 0;
349
+ padding: 0;
350
+ font: inherit;
351
+ text-align: left;
352
+ }
353
+ .ue-ins__ver { color: #6e7681; margin-left: auto; flex: none; }
354
+ .ue-ins__empty { color: #6e7681; }
355
+
356
+ .ue-ins__log { display: flex; flex-direction: column-reverse; max-height: 190px; overflow-y: auto; }
357
+ .ue-ins__wire { display: flex; gap: 7px; padding: 1px 0; white-space: nowrap; }
358
+ .ue-ins__dir { flex: none; width: 9px; }
359
+ .ue-ins__dir--out { color: #d29922; }
360
+ .ue-ins__dir--in { color: #3fb950; }
361
+ .ue-ins__scope { color: #e6edf3; }
362
+ .ue-ins__from { color: #6e7681; margin-left: auto; }
363
+
364
+ .ue-ins__tools { display: flex; gap: 6px; align-items: center; margin-bottom: 5px; }
365
+ .ue-ins__btn {
366
+ background: #21262d;
367
+ border: 1px solid #30363d;
368
+ border-radius: 5px;
369
+ color: #c9d1d9;
370
+ font: inherit;
371
+ font-size: 10px;
372
+ padding: 1px 7px;
373
+ cursor: pointer;
374
+ }
375
+ .ue-ins__btn[aria-pressed='true'] { background: #1f6feb; border-color: #1f6feb; color: #fff; }
376
+ .ue-ins__filter {
377
+ flex: 1;
378
+ min-width: 0;
379
+ background: #0d1117;
380
+ border: 1px solid #30363d;
381
+ border-radius: 5px;
382
+ color: #c9d1d9;
383
+ font: inherit;
384
+ font-size: 10px;
385
+ padding: 1px 6px;
386
+ }
387
+ .ue-ins__edit {
388
+ flex: 1;
389
+ min-width: 0;
390
+ background: #0d1117;
391
+ border: 1px solid #1f6feb;
392
+ border-radius: 4px;
393
+ color: #e6edf3;
394
+ font: inherit;
395
+ padding: 0 4px;
396
+ }
397
+ .ue-ins__v--editable { cursor: text; text-decoration: underline dotted #30363d; }
398
+ .ue-ins__v--invalid { color: #f85149; }
399
+ .ue-ins__paused { color: #d29922; }
400
+ `;
401
+
402
+ // src/devtools/inspector.tsx
403
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
404
+ function Inspector({
405
+ name = DEFAULT_NAME,
406
+ position = "bottom-right",
407
+ limit = 50,
408
+ defaultOpen = false,
409
+ leaseMs = 3e3
410
+ } = {}) {
411
+ const [root, setRoot] = useState2(null);
412
+ const attach = (host) => {
413
+ if (!host || root) return;
414
+ setRoot(host.shadowRoot ?? host.attachShadow({ mode: "open" }));
415
+ };
416
+ return /* @__PURE__ */ jsx2("div", { ref: attach, "data-testid": "ue-inspector-host", children: root ? createPortal(
417
+ /* @__PURE__ */ jsxs2(Fragment, { children: [
418
+ /* @__PURE__ */ jsx2("style", { children: STYLES }),
419
+ /* @__PURE__ */ jsx2(
420
+ Panel,
421
+ {
422
+ name,
423
+ position,
424
+ limit,
425
+ defaultOpen,
426
+ leaseMs
427
+ }
428
+ )
429
+ ] }),
430
+ root
431
+ ) : null });
432
+ }
350
433
  export {
351
434
  Inspector
352
435
  };
package/dist/index.cjs CHANGED
@@ -26,6 +26,7 @@ __export(src_exports, {
26
26
  DEFAULT_NAME: () => import_core.DEFAULT_NAME,
27
27
  HandshakeTimeoutError: () => import_core4.HandshakeTimeoutError,
28
28
  NoopTransport: () => import_core4.NoopTransport,
29
+ SharedWorkerTransport: () => import_core4.SharedWorkerTransport,
29
30
  StorageTransport: () => import_core4.StorageTransport,
30
31
  WIRE_VERSION: () => import_core4.WIRE_VERSION,
31
32
  WindowClosedError: () => import_core4.WindowClosedError,
@@ -47,6 +48,7 @@ __export(src_exports, {
47
48
  getWireSkew: () => import_core4.getWireSkew,
48
49
  indexedDbAdapter: () => import_core4.indexedDbAdapter,
49
50
  isBroadcastChannelAvailable: () => import_core4.isBroadcastChannelAvailable,
51
+ isSharedWorkerAvailable: () => import_core4.isSharedWorkerAvailable,
50
52
  isStorageEventAvailable: () => import_core4.isStorageEventAvailable,
51
53
  jsonSerializer: () => import_core4.jsonSerializer,
52
54
  localStorageAdapter: () => import_core4.localStorageAdapter,
@@ -591,6 +593,7 @@ var import_core4 = require("@use-everywhere/core");
591
593
  DEFAULT_NAME,
592
594
  HandshakeTimeoutError,
593
595
  NoopTransport,
596
+ SharedWorkerTransport,
594
597
  StorageTransport,
595
598
  WIRE_VERSION,
596
599
  WindowClosedError,
@@ -612,6 +615,7 @@ var import_core4 = require("@use-everywhere/core");
612
615
  getWireSkew,
613
616
  indexedDbAdapter,
614
617
  isBroadcastChannelAvailable,
618
+ isSharedWorkerAvailable,
615
619
  isStorageEventAvailable,
616
620
  jsonSerializer,
617
621
  localStorageAdapter,
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { SharedStore, LeaderOptions, Leader, MessageMap, ReplyMap, Channel, MessageMeta, ChannelOptions, PersistAdapter, RestoreError, Peer, LeaderSnapshot, Namespace, OpenedWindow, WindowClosedError } from '@use-everywhere/core';
2
- export { AskOptions, BroadcastChannelTransport, BusEvent, BusObserver, BusWire, CID_PARAM, Channel, ChannelOptions, CommonOptions, ConnectToOpenerOptions, DEFAULT_NAME, DebugOptions, HandshakeTimeoutError, IndexedDbAdapterOptions, InvalidPayload, Leader, LeaderOptions, LeaderSnapshot, LeaderStrategy, MessageEventLike, MessageMap, MessageMeta, Namespace, NoopTransport, OnInvalid, OnOptions, OpenWindowOptions, OpenedWindow, OpenerConnection, Peer, PeerKind, PersistAdapter, PersistOptions, Persisted, PostOptions, Presence, PresenceOptions, ReplyMap, RestoreError, SchemaMap, SchemaOptions, Serializer, SharedReducer, SharedReducerOptions, SharedStore, SharedStoreOptions, StandardSchemaV1, StorageLike, StorageTransport, Transport, TransportKind, Version, WIRE_VERSION, WebStorageAdapterOptions, WindowClosedError, WindowEventTarget, WindowLike, connectToOpener, createChannel, createLeader, createPresence, createSharedReducer, createSharedStore, defaultTransport, enableDebug, getBusNames, getTransportKind, getWireSkew, indexedDbAdapter, isBroadcastChannelAvailable, isStorageEventAvailable, jsonSerializer, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter } from '@use-everywhere/core';
2
+ export { AskOptions, BroadcastChannelTransport, BusEvent, BusObserver, BusWire, CID_PARAM, Channel, ChannelOptions, CommonOptions, ConnectToOpenerOptions, DEFAULT_NAME, DebugOptions, HandshakeTimeoutError, IndexedDbAdapterOptions, InvalidPayload, Leader, LeaderOptions, LeaderSnapshot, LeaderStrategy, MessageEventLike, MessageMap, MessageMeta, Namespace, NoopTransport, OnInvalid, OnOptions, OpenWindowOptions, OpenedWindow, OpenerConnection, Peer, PeerKind, PersistAdapter, PersistOptions, Persisted, PostOptions, Presence, PresenceOptions, ReplyMap, RestoreError, SchemaMap, SchemaOptions, Serializer, SharedReducer, SharedReducerOptions, SharedStore, SharedStoreOptions, SharedWorkerTransport, SharedWorkerTransportOptions, StandardSchemaV1, StorageLike, StorageTransport, Transport, TransportKind, Version, WIRE_VERSION, WebStorageAdapterOptions, WindowClosedError, WindowEventTarget, WindowLike, connectToOpener, createChannel, createLeader, createPresence, createSharedReducer, createSharedStore, defaultTransport, enableDebug, getBusNames, getTransportKind, getWireSkew, indexedDbAdapter, isBroadcastChannelAvailable, isSharedWorkerAvailable, isStorageEventAvailable, jsonSerializer, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter } from '@use-everywhere/core';
3
3
 
4
4
  /**
5
5
  * How far a shared value travels:
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { SharedStore, LeaderOptions, Leader, MessageMap, ReplyMap, Channel, MessageMeta, ChannelOptions, PersistAdapter, RestoreError, Peer, LeaderSnapshot, Namespace, OpenedWindow, WindowClosedError } from '@use-everywhere/core';
2
- export { AskOptions, BroadcastChannelTransport, BusEvent, BusObserver, BusWire, CID_PARAM, Channel, ChannelOptions, CommonOptions, ConnectToOpenerOptions, DEFAULT_NAME, DebugOptions, HandshakeTimeoutError, IndexedDbAdapterOptions, InvalidPayload, Leader, LeaderOptions, LeaderSnapshot, LeaderStrategy, MessageEventLike, MessageMap, MessageMeta, Namespace, NoopTransport, OnInvalid, OnOptions, OpenWindowOptions, OpenedWindow, OpenerConnection, Peer, PeerKind, PersistAdapter, PersistOptions, Persisted, PostOptions, Presence, PresenceOptions, ReplyMap, RestoreError, SchemaMap, SchemaOptions, Serializer, SharedReducer, SharedReducerOptions, SharedStore, SharedStoreOptions, StandardSchemaV1, StorageLike, StorageTransport, Transport, TransportKind, Version, WIRE_VERSION, WebStorageAdapterOptions, WindowClosedError, WindowEventTarget, WindowLike, connectToOpener, createChannel, createLeader, createPresence, createSharedReducer, createSharedStore, defaultTransport, enableDebug, getBusNames, getTransportKind, getWireSkew, indexedDbAdapter, isBroadcastChannelAvailable, isStorageEventAvailable, jsonSerializer, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter } from '@use-everywhere/core';
2
+ export { AskOptions, BroadcastChannelTransport, BusEvent, BusObserver, BusWire, CID_PARAM, Channel, ChannelOptions, CommonOptions, ConnectToOpenerOptions, DEFAULT_NAME, DebugOptions, HandshakeTimeoutError, IndexedDbAdapterOptions, InvalidPayload, Leader, LeaderOptions, LeaderSnapshot, LeaderStrategy, MessageEventLike, MessageMap, MessageMeta, Namespace, NoopTransport, OnInvalid, OnOptions, OpenWindowOptions, OpenedWindow, OpenerConnection, Peer, PeerKind, PersistAdapter, PersistOptions, Persisted, PostOptions, Presence, PresenceOptions, ReplyMap, RestoreError, SchemaMap, SchemaOptions, Serializer, SharedReducer, SharedReducerOptions, SharedStore, SharedStoreOptions, SharedWorkerTransport, SharedWorkerTransportOptions, StandardSchemaV1, StorageLike, StorageTransport, Transport, TransportKind, Version, WIRE_VERSION, WebStorageAdapterOptions, WindowClosedError, WindowEventTarget, WindowLike, connectToOpener, createChannel, createLeader, createPresence, createSharedReducer, createSharedStore, defaultTransport, enableDebug, getBusNames, getTransportKind, getWireSkew, indexedDbAdapter, isBroadcastChannelAvailable, isSharedWorkerAvailable, isStorageEventAvailable, jsonSerializer, localStorageAdapter, newer, observeBus, openWindow, sessionStorageAdapter, webStorageAdapter } from '@use-everywhere/core';
3
3
 
4
4
  /**
5
5
  * How far a shared value travels:
package/dist/index.js CHANGED
@@ -313,9 +313,11 @@ import {
313
313
  BroadcastChannelTransport,
314
314
  NoopTransport,
315
315
  StorageTransport,
316
+ SharedWorkerTransport,
316
317
  defaultTransport,
317
318
  isBroadcastChannelAvailable,
318
- isStorageEventAvailable
319
+ isStorageEventAvailable,
320
+ isSharedWorkerAvailable
319
321
  } from "@use-everywhere/core";
320
322
  export {
321
323
  BroadcastChannelTransport,
@@ -323,6 +325,7 @@ export {
323
325
  DEFAULT_NAME,
324
326
  HandshakeTimeoutError,
325
327
  NoopTransport,
328
+ SharedWorkerTransport,
326
329
  StorageTransport,
327
330
  WIRE_VERSION,
328
331
  WindowClosedError2 as WindowClosedError,
@@ -344,6 +347,7 @@ export {
344
347
  getWireSkew,
345
348
  indexedDbAdapter,
346
349
  isBroadcastChannelAvailable,
350
+ isSharedWorkerAvailable,
347
351
  isStorageEventAvailable,
348
352
  jsonSerializer,
349
353
  localStorageAdapter,
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/shared-worker.ts
22
+ var shared_worker_exports = {};
23
+ __export(shared_worker_exports, {
24
+ startRelay: () => import_shared_worker.startRelay
25
+ });
26
+ module.exports = __toCommonJS(shared_worker_exports);
27
+ var import_shared_worker = require("@use-everywhere/core/shared-worker");
28
+ // Annotate the CommonJS export names for ESM import in node:
29
+ 0 && (module.exports = {
30
+ startRelay
31
+ });
@@ -0,0 +1,2 @@
1
+ export { RelayPort, RelayScope, startRelay } from '@use-everywhere/core/shared-worker';
2
+ import '@use-everywhere/core/testing';
@@ -0,0 +1,2 @@
1
+ export { RelayPort, RelayScope, startRelay } from '@use-everywhere/core/shared-worker';
2
+ import '@use-everywhere/core/testing';
@@ -0,0 +1,7 @@
1
+ 'use client';
2
+
3
+ // src/shared-worker.ts
4
+ import { startRelay } from "@use-everywhere/core/shared-worker";
5
+ export {
6
+ startRelay
7
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-everywhere",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "React hooks for state and messages shared across tabs, windows, and workers",
5
5
  "license": "MIT",
6
6
  "author": "Jonatan Kruszewski <jonakrusze@gmail.com>",
@@ -25,7 +25,10 @@
25
25
  "access": "public"
26
26
  },
27
27
  "type": "module",
28
- "sideEffects": false,
28
+ "sideEffects": [
29
+ "./dist/shared-worker.js",
30
+ "./dist/shared-worker.cjs"
31
+ ],
29
32
  "main": "./dist/index.cjs",
30
33
  "module": "./dist/index.js",
31
34
  "types": "./dist/index.d.ts",
@@ -50,6 +53,16 @@
50
53
  "default": "./dist/devtools/index.cjs"
51
54
  }
52
55
  },
56
+ "./shared-worker": {
57
+ "import": {
58
+ "types": "./dist/shared-worker.d.ts",
59
+ "default": "./dist/shared-worker.js"
60
+ },
61
+ "require": {
62
+ "types": "./dist/shared-worker.d.cts",
63
+ "default": "./dist/shared-worker.cjs"
64
+ }
65
+ },
53
66
  "./testing": {
54
67
  "import": {
55
68
  "types": "./dist/testing.d.ts",
@@ -123,7 +136,13 @@
123
136
  "name": "Inspector (devtools subpath)",
124
137
  "path": "dist/devtools/index.js",
125
138
  "import": "{ Inspector }",
126
- "limit": "6.70 kB"
139
+ "limit": "7.20 kB"
140
+ },
141
+ {
142
+ "name": "SharedWorker relay (shared-worker subpath)",
143
+ "path": "dist/shared-worker.js",
144
+ "import": "*",
145
+ "limit": "280 B"
127
146
  },
128
147
  {
129
148
  "name": "MemoryHub (testing subpath)",
@@ -133,10 +152,16 @@
133
152
  }
134
153
  ],
135
154
  "dependencies": {
136
- "@use-everywhere/core": "0.9.0"
155
+ "@use-everywhere/core": "0.10.0"
137
156
  },
138
157
  "peerDependencies": {
139
- "react": ">=18"
158
+ "react": ">=18",
159
+ "react-dom": ">=18"
160
+ },
161
+ "peerDependenciesMeta": {
162
+ "react-dom": {
163
+ "optional": true
164
+ }
140
165
  },
141
166
  "devDependencies": {
142
167
  "@arethetypeswrong/cli": "^0.18.5",
@@ -159,6 +184,9 @@
159
184
  "devtools": [
160
185
  "./dist/devtools/index.d.ts"
161
186
  ],
187
+ "shared-worker": [
188
+ "./dist/shared-worker.d.ts"
189
+ ],
162
190
  "testing": [
163
191
  "./dist/testing.d.ts"
164
192
  ]