use-everywhere 1.0.0 → 1.0.1
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/devtools/index.cjs +436 -436
- package/dist/devtools/index.d.cts +19 -18
- package/dist/devtools/index.d.ts +19 -18
- package/dist/devtools/index.js +434 -327
- package/dist/index.cjs +665 -621
- package/dist/index.d.cts +177 -160
- package/dist/index.d.ts +177 -160
- package/dist/index.js +449 -340
- package/dist/shared-worker.cjs +12 -30
- package/dist/shared-worker.d.cts +3 -2
- package/dist/shared-worker.d.ts +3 -2
- package/dist/shared-worker.js +2 -7
- package/dist/testing.cjs +12 -30
- package/dist/testing.d.cts +3 -1
- package/dist/testing.d.ts +3 -1
- package/dist/testing.js +1 -6
- package/dist/use-peers-DtpHGzXE.js +315 -0
- package/dist/use-peers-RhIB77TV.cjs +380 -0
- package/package.json +6 -5
- package/dist/chunk-YPQ6PLD5.js +0 -259
package/dist/devtools/index.cjs
CHANGED
|
@@ -1,408 +1,397 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
"
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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/devtools/index.ts
|
|
22
|
-
var devtools_exports = {};
|
|
23
|
-
__export(devtools_exports, {
|
|
24
|
-
Inspector: () => Inspector
|
|
25
|
-
});
|
|
26
|
-
module.exports = __toCommonJS(devtools_exports);
|
|
27
|
-
|
|
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
|
|
34
|
-
var import_react2 = require("react");
|
|
35
|
-
var import_core2 = require("@use-everywhere/core");
|
|
36
|
-
|
|
37
|
-
// src/registry.ts
|
|
38
|
-
var import_core = require("@use-everywhere/core");
|
|
39
|
-
|
|
40
|
-
// src/server-stubs.ts
|
|
41
|
-
var SERVER_CLIENT_ID = "";
|
|
42
|
-
var EMPTY = Object.freeze({});
|
|
43
|
-
var NO_PEERS = Object.freeze([]);
|
|
44
|
-
var noop = () => {
|
|
45
|
-
};
|
|
46
|
-
var unsubscribe = () => noop;
|
|
47
|
-
var INERT = Object.freeze({
|
|
48
|
-
clientId: SERVER_CLIENT_ID,
|
|
49
|
-
// store
|
|
50
|
-
//
|
|
51
|
-
// Resolved, unlike the leader's `waitForLeadership` below. A server render
|
|
52
|
-
// has nothing to restore and is documented to render defaults, so `await
|
|
53
|
-
// store.hydrated` must proceed — a never-settling promise would hang the
|
|
54
|
-
// render rather than describe it honestly.
|
|
55
|
-
hydrated: Promise.resolve(),
|
|
56
|
-
state: EMPTY,
|
|
57
|
-
getVersions: () => EMPTY,
|
|
58
|
-
set: noop,
|
|
59
|
-
subscribeKey: unsubscribe,
|
|
60
|
-
registerKey: noop,
|
|
61
|
-
// store + leader share getSnapshot; presence has getPeers
|
|
62
|
-
getSnapshot: () => EMPTY,
|
|
63
|
-
getPeers: () => NO_PEERS,
|
|
64
|
-
// presence
|
|
65
|
-
setMetadata: noop,
|
|
66
|
-
// leader
|
|
67
|
-
resign: noop,
|
|
68
|
-
setEligible: noop,
|
|
69
|
-
// common
|
|
70
|
-
subscribe: unsubscribe,
|
|
71
|
-
close: noop
|
|
72
|
-
});
|
|
73
|
-
var createServerStore = () => INERT;
|
|
74
|
-
var createServerPresence = () => INERT;
|
|
75
|
-
var NO_LEADER = Object.freeze({ leaderId: null, isLeader: false });
|
|
76
|
-
|
|
77
|
-
// src/registry.ts
|
|
78
|
-
var stores = /* @__PURE__ */ new Map();
|
|
79
|
-
var presences = /* @__PURE__ */ new Map();
|
|
80
|
-
var storeConfig = /* @__PURE__ */ new Map();
|
|
81
|
-
var isServer = () => typeof window === "undefined";
|
|
82
|
-
var scopeOptions = {
|
|
83
|
-
everywhere: {},
|
|
84
|
-
tabs: { accept: (meta) => meta.kind !== "worker" },
|
|
85
|
-
tab: { transport: () => new import_core.NoopTransport() }
|
|
86
|
-
};
|
|
87
|
-
function getSharedStore(name = import_core.DEFAULT_NAME, scope = "everywhere") {
|
|
88
|
-
return getStore(name, scope);
|
|
89
|
-
}
|
|
90
|
-
function getStore(name, scope = "everywhere") {
|
|
91
|
-
const key = `${scope} ${name}`;
|
|
92
|
-
let store = stores.get(key);
|
|
93
|
-
if (!store) {
|
|
94
|
-
store = isServer() ? createServerStore() : (0, import_core.createSharedStore)(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
|
|
95
|
-
stores.set(key, store);
|
|
96
|
-
}
|
|
97
|
-
return store;
|
|
98
|
-
}
|
|
99
|
-
function getPresence(name, includeSelf = false) {
|
|
100
|
-
const key = includeSelf ? `self ${name}` : name;
|
|
101
|
-
let presence = presences.get(key);
|
|
102
|
-
if (!presence) {
|
|
103
|
-
presence = isServer() ? createServerPresence() : (0, import_core.createPresence)(name, { includeSelf });
|
|
104
|
-
presences.set(key, presence);
|
|
105
|
-
}
|
|
106
|
-
return presence;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// src/use-peers.ts
|
|
110
|
-
var import_react = require("react");
|
|
111
|
-
var NO_PEERS2 = Object.freeze([]);
|
|
112
|
-
function usePeers(options) {
|
|
113
|
-
const presence = getPresence(options?.name ?? import_core.DEFAULT_NAME, options?.includeSelf ?? false);
|
|
114
|
-
return (0, import_react.useSyncExternalStore)(
|
|
115
|
-
(0, import_react.useCallback)((onChange) => presence.subscribe(onChange), [presence]),
|
|
116
|
-
() => presence.getPeers(),
|
|
117
|
-
() => NO_PEERS2
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// src/devtools/panel.tsx
|
|
122
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
123
|
-
var short = (id) => id.slice(0, 6);
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const require_use_peers = require("../use-peers-RhIB77TV.cjs");
|
|
4
|
+
let react = require("react");
|
|
5
|
+
let _use_everywhere_core = require("@use-everywhere/core");
|
|
6
|
+
let react_dom = require("react-dom");
|
|
7
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
+
//#region src/devtools/panel.tsx
|
|
9
|
+
const short = (id) => id.slice(0, 6);
|
|
124
10
|
function wireLabel(wire) {
|
|
125
|
-
|
|
11
|
+
return `${wire.scope}/${wire.type}`;
|
|
126
12
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
13
|
+
/** The scopes worth separating. `all` is not a scope; it is the absence of one. */
|
|
14
|
+
const SCOPES = [
|
|
15
|
+
"all",
|
|
16
|
+
"state",
|
|
17
|
+
"leader",
|
|
18
|
+
"presence",
|
|
19
|
+
"channel"
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* The panel's contents. Split out of `Inspector` because the Inspector is now
|
|
23
|
+
* the shadow host and this is what gets portalled into it — keeping them in one
|
|
24
|
+
* component would mean the hooks re-run whenever the host re-renders for
|
|
25
|
+
* reasons that have nothing to do with what is on the wire.
|
|
26
|
+
*/
|
|
27
|
+
function Panel({ name, limit, leaseMs, position, defaultOpen }) {
|
|
28
|
+
const [open, setOpen] = (0, react.useState)(defaultOpen);
|
|
29
|
+
const [wires, setWires] = (0, react.useState)([]);
|
|
30
|
+
const [crown, setCrown] = (0, react.useState)(null);
|
|
31
|
+
const [paused, setPaused] = (0, react.useState)(false);
|
|
32
|
+
const [filter, setFilter] = (0, react.useState)("");
|
|
33
|
+
const [scopeView, setScopeView] = (0, react.useState)("all");
|
|
34
|
+
const [editing, setEditing] = (0, react.useState)(null);
|
|
35
|
+
const [frames, setFrames] = (0, react.useState)([]);
|
|
36
|
+
const nextId = (0, react.useRef)(0);
|
|
37
|
+
const crownAt = (0, react.useRef)(0);
|
|
38
|
+
const pausedRef = (0, react.useRef)(false);
|
|
39
|
+
(0, react.useEffect)(() => {
|
|
40
|
+
pausedRef.current = paused;
|
|
41
|
+
}, [paused]);
|
|
42
|
+
const peers = require_use_peers.usePeers({ name });
|
|
43
|
+
const store = require_use_peers.getSharedStore(name);
|
|
44
|
+
const subscribe = (0, react.useCallback)((onChange) => store.subscribe(onChange), [store]);
|
|
45
|
+
const snapshot = (0, react.useSyncExternalStore)(subscribe, () => store.getSnapshot(), () => store.getSnapshot());
|
|
46
|
+
const versions = (0, react.useSyncExternalStore)(subscribe, () => store.getVersions(), () => store.getVersions());
|
|
47
|
+
(0, react.useEffect)(() => {
|
|
48
|
+
return (0, _use_everywhere_core.observeBus)(name, (event) => {
|
|
49
|
+
const { wire, direction } = event;
|
|
50
|
+
if (wire.scope === "leader") {
|
|
51
|
+
if (wire.type === "resign") {
|
|
52
|
+
setCrown(null);
|
|
53
|
+
crownAt.current = 0;
|
|
54
|
+
} else if (wire.type === "claim" || wire.type === "heartbeat") {
|
|
55
|
+
setCrown(wire.clientId);
|
|
56
|
+
crownAt.current = Date.now();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (pausedRef.current) return;
|
|
60
|
+
const id = nextId.current++;
|
|
61
|
+
const label = wireLabel(wire);
|
|
62
|
+
setWires((prev) => [...prev.slice(-(limit - 1)), {
|
|
63
|
+
id,
|
|
64
|
+
direction,
|
|
65
|
+
scope: wire.scope,
|
|
66
|
+
label,
|
|
67
|
+
from: short(wire.clientId)
|
|
68
|
+
}].slice(-limit));
|
|
69
|
+
if (wire.scope === "state") setFrames((prev) => [...prev, {
|
|
70
|
+
wireId: id,
|
|
71
|
+
label,
|
|
72
|
+
snapshot: { ...store.getSnapshot() }
|
|
73
|
+
}].slice(-limit));
|
|
74
|
+
});
|
|
75
|
+
}, [
|
|
76
|
+
name,
|
|
77
|
+
limit,
|
|
78
|
+
store
|
|
79
|
+
]);
|
|
80
|
+
(0, react.useEffect)(() => {
|
|
81
|
+
const timer = setInterval(() => {
|
|
82
|
+
if (crownAt.current && Date.now() - crownAt.current > leaseMs) {
|
|
83
|
+
setCrown(null);
|
|
84
|
+
crownAt.current = 0;
|
|
85
|
+
}
|
|
86
|
+
}, Math.max(50, Math.floor(leaseMs / 4)));
|
|
87
|
+
return () => clearInterval(timer);
|
|
88
|
+
}, [leaseMs]);
|
|
89
|
+
const selfId = store.clientId;
|
|
90
|
+
const entries = Object.entries(versions);
|
|
91
|
+
const needle = filter.trim().toLowerCase();
|
|
92
|
+
const shown = wires.filter((wire) => {
|
|
93
|
+
if (scopeView !== "all" && wire.scope !== scopeView) return false;
|
|
94
|
+
if (!needle) return true;
|
|
95
|
+
return wire.label.toLowerCase().includes(needle) || wire.from.toLowerCase().includes(needle);
|
|
96
|
+
});
|
|
97
|
+
const commit = (key, draft) => {
|
|
98
|
+
let value;
|
|
99
|
+
try {
|
|
100
|
+
value = JSON.parse(draft);
|
|
101
|
+
} catch {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
store.set(key, value);
|
|
105
|
+
setEditing(null);
|
|
106
|
+
};
|
|
107
|
+
const draftIsValid = (draft) => {
|
|
108
|
+
try {
|
|
109
|
+
JSON.parse(draft);
|
|
110
|
+
return true;
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Put a recorded snapshot back.
|
|
117
|
+
*
|
|
118
|
+
* Restoring is a **write**, not a rewind: every key that differs is set
|
|
119
|
+
* through the store, takes a fresh version, and goes on the wire, so peers
|
|
120
|
+
* converge on the restored value instead of quietly disagreeing with the tab
|
|
121
|
+
* that time-travelled. Nothing about history is undone — this is the honest
|
|
122
|
+
* version of the feature, and the label says "restore" rather than "undo" for
|
|
123
|
+
* that reason.
|
|
124
|
+
*
|
|
125
|
+
* Keys added after the frame was taken are left alone rather than deleted:
|
|
126
|
+
* removing a key another tab is using is a bigger claim than a devtool should
|
|
127
|
+
* make from a picture of the past.
|
|
128
|
+
*/
|
|
129
|
+
const restore = (frame) => {
|
|
130
|
+
for (const [key, value] of Object.entries(frame.snapshot)) {
|
|
131
|
+
if (Object.is(store.getSnapshot()[key], value)) continue;
|
|
132
|
+
store.set(key, value);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
136
|
+
className: `ue-ins ue-ins--${position}`,
|
|
137
|
+
"data-testid": "ue-inspector",
|
|
138
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
139
|
+
type: "button",
|
|
140
|
+
className: "ue-ins__bar",
|
|
141
|
+
onClick: () => setOpen((v) => !v),
|
|
142
|
+
"aria-expanded": open,
|
|
143
|
+
children: [
|
|
144
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "ue-ins__dot" }),
|
|
145
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
146
|
+
className: "ue-ins__title",
|
|
147
|
+
children: "use-everywhere"
|
|
148
|
+
}),
|
|
149
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
150
|
+
className: "ue-ins__muted",
|
|
151
|
+
children: name
|
|
152
|
+
}),
|
|
153
|
+
crown ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
154
|
+
className: "ue-ins__crown",
|
|
155
|
+
"data-testid": "ue-crown",
|
|
156
|
+
children: ["♔ ", crown === selfId ? "this tab" : short(crown)]
|
|
157
|
+
}) : null
|
|
158
|
+
]
|
|
159
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
160
|
+
className: "ue-ins__body",
|
|
161
|
+
children: [
|
|
162
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
163
|
+
className: "ue-ins__section",
|
|
164
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
165
|
+
className: "ue-ins__h",
|
|
166
|
+
children: "This tab"
|
|
167
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
168
|
+
className: "ue-ins__row",
|
|
169
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
170
|
+
className: "ue-ins__k",
|
|
171
|
+
children: short(selfId)
|
|
172
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
173
|
+
className: "ue-ins__v",
|
|
174
|
+
children: crown === selfId ? "leader" : crown ? "follower" : "no leader"
|
|
175
|
+
})]
|
|
176
|
+
})]
|
|
177
|
+
}),
|
|
178
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
179
|
+
className: "ue-ins__section",
|
|
180
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
181
|
+
className: "ue-ins__h",
|
|
182
|
+
children: [
|
|
183
|
+
"Peers (",
|
|
184
|
+
peers.length,
|
|
185
|
+
")"
|
|
186
|
+
]
|
|
187
|
+
}), peers.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
188
|
+
className: "ue-ins__empty",
|
|
189
|
+
children: "nobody else here"
|
|
190
|
+
}) : peers.map((peer) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
191
|
+
className: "ue-ins__row",
|
|
192
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
193
|
+
className: "ue-ins__k",
|
|
194
|
+
children: short(peer.id)
|
|
195
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
196
|
+
className: "ue-ins__v",
|
|
197
|
+
children: peer.kind
|
|
198
|
+
})]
|
|
199
|
+
}, peer.id))]
|
|
200
|
+
}),
|
|
201
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
202
|
+
className: "ue-ins__section",
|
|
203
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
204
|
+
className: "ue-ins__h",
|
|
205
|
+
children: [
|
|
206
|
+
"State (",
|
|
207
|
+
entries.length,
|
|
208
|
+
")"
|
|
209
|
+
]
|
|
210
|
+
}), entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
211
|
+
className: "ue-ins__empty",
|
|
212
|
+
children: "no keys yet"
|
|
213
|
+
}) : entries.map(([key, version]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
214
|
+
className: "ue-ins__row",
|
|
215
|
+
children: [
|
|
216
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
217
|
+
className: "ue-ins__k",
|
|
218
|
+
children: key
|
|
219
|
+
}),
|
|
220
|
+
editing?.key === key ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
221
|
+
className: `ue-ins__edit${draftIsValid(editing.draft) ? "" : " ue-ins__v--invalid"}`,
|
|
222
|
+
value: editing.draft,
|
|
223
|
+
autoFocus: true,
|
|
224
|
+
"aria-label": `Value of ${key}`,
|
|
225
|
+
onChange: (event) => setEditing({
|
|
226
|
+
key,
|
|
227
|
+
draft: event.target.value
|
|
228
|
+
}),
|
|
229
|
+
onKeyDown: (event) => {
|
|
230
|
+
if (event.key === "Enter") commit(key, editing.draft);
|
|
231
|
+
if (event.key === "Escape") setEditing(null);
|
|
232
|
+
},
|
|
233
|
+
onBlur: () => setEditing(null),
|
|
234
|
+
"data-testid": `ue-edit-${key}`
|
|
235
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
236
|
+
type: "button",
|
|
237
|
+
className: "ue-ins__v ue-ins__v--editable",
|
|
238
|
+
onClick: () => setEditing({
|
|
239
|
+
key,
|
|
240
|
+
draft: JSON.stringify(snapshot[key]) ?? ""
|
|
241
|
+
}),
|
|
242
|
+
"data-testid": `ue-value-${key}`,
|
|
243
|
+
children: JSON.stringify(snapshot[key])
|
|
244
|
+
}),
|
|
245
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
246
|
+
className: "ue-ins__ver",
|
|
247
|
+
children: [
|
|
248
|
+
version[0],
|
|
249
|
+
"·",
|
|
250
|
+
short(version[1])
|
|
251
|
+
]
|
|
252
|
+
})
|
|
253
|
+
]
|
|
254
|
+
}, key))]
|
|
255
|
+
}),
|
|
256
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
257
|
+
className: "ue-ins__section",
|
|
258
|
+
children: [
|
|
259
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
260
|
+
className: "ue-ins__h",
|
|
261
|
+
children: [
|
|
262
|
+
"Wires (",
|
|
263
|
+
shown.length,
|
|
264
|
+
shown.length === wires.length ? "" : ` of ${wires.length}`,
|
|
265
|
+
")",
|
|
266
|
+
paused ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
267
|
+
className: "ue-ins__paused",
|
|
268
|
+
children: " · paused"
|
|
269
|
+
}) : null
|
|
270
|
+
]
|
|
271
|
+
}),
|
|
272
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
273
|
+
className: "ue-ins__tools",
|
|
274
|
+
children: [
|
|
275
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
276
|
+
type: "button",
|
|
277
|
+
className: "ue-ins__btn",
|
|
278
|
+
"aria-pressed": paused,
|
|
279
|
+
onClick: () => setPaused((value) => !value),
|
|
280
|
+
"data-testid": "ue-pause",
|
|
281
|
+
children: paused ? "resume" : "pause"
|
|
282
|
+
}),
|
|
283
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
284
|
+
type: "button",
|
|
285
|
+
className: "ue-ins__btn",
|
|
286
|
+
onClick: () => {
|
|
287
|
+
setWires([]);
|
|
288
|
+
setFrames([]);
|
|
289
|
+
},
|
|
290
|
+
"data-testid": "ue-clear",
|
|
291
|
+
children: "clear"
|
|
292
|
+
}),
|
|
293
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
294
|
+
className: "ue-ins__filter",
|
|
295
|
+
value: filter,
|
|
296
|
+
placeholder: "filter",
|
|
297
|
+
"aria-label": "Filter wires",
|
|
298
|
+
onChange: (event) => setFilter(event.target.value),
|
|
299
|
+
"data-testid": "ue-filter"
|
|
300
|
+
})
|
|
301
|
+
]
|
|
302
|
+
}),
|
|
303
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
304
|
+
className: "ue-ins__tools",
|
|
305
|
+
role: "group",
|
|
306
|
+
"aria-label": "Scope",
|
|
307
|
+
children: SCOPES.map((scope) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
308
|
+
type: "button",
|
|
309
|
+
className: "ue-ins__btn",
|
|
310
|
+
"aria-pressed": scopeView === scope,
|
|
311
|
+
onClick: () => setScopeView(scope),
|
|
312
|
+
"data-testid": `ue-scope-${scope}`,
|
|
313
|
+
children: scope
|
|
314
|
+
}, scope))
|
|
315
|
+
}),
|
|
316
|
+
shown.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
317
|
+
className: "ue-ins__empty",
|
|
318
|
+
"data-testid": "ue-wires",
|
|
319
|
+
children: wires.length === 0 ? "nothing yet" : "no matches"
|
|
320
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
321
|
+
className: "ue-ins__log",
|
|
322
|
+
"data-testid": "ue-wires",
|
|
323
|
+
children: shown.map((wire) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
324
|
+
className: "ue-ins__wire",
|
|
325
|
+
children: [
|
|
326
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
327
|
+
className: `ue-ins__dir ue-ins__dir--${wire.direction}`,
|
|
328
|
+
children: wire.direction === "out" ? "→" : "←"
|
|
329
|
+
}),
|
|
330
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
331
|
+
className: "ue-ins__scope",
|
|
332
|
+
children: wire.label
|
|
333
|
+
}),
|
|
334
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
335
|
+
className: "ue-ins__from",
|
|
336
|
+
children: wire.from
|
|
337
|
+
})
|
|
338
|
+
]
|
|
339
|
+
}, wire.id))
|
|
340
|
+
})
|
|
341
|
+
]
|
|
342
|
+
}),
|
|
343
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
344
|
+
className: "ue-ins__section",
|
|
345
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
346
|
+
className: "ue-ins__h",
|
|
347
|
+
children: [
|
|
348
|
+
"Timeline (",
|
|
349
|
+
frames.length,
|
|
350
|
+
")"
|
|
351
|
+
]
|
|
352
|
+
}), frames.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
353
|
+
className: "ue-ins__empty",
|
|
354
|
+
children: "no state on the wire yet"
|
|
355
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
356
|
+
className: "ue-ins__log",
|
|
357
|
+
children: frames.map((frame) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
358
|
+
className: "ue-ins__wire",
|
|
359
|
+
children: [
|
|
360
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
361
|
+
type: "button",
|
|
362
|
+
className: "ue-ins__btn",
|
|
363
|
+
onClick: () => restore(frame),
|
|
364
|
+
"data-testid": `ue-restore-${String(frame.wireId)}`,
|
|
365
|
+
children: "restore"
|
|
366
|
+
}),
|
|
367
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
368
|
+
className: "ue-ins__scope",
|
|
369
|
+
children: frame.label
|
|
370
|
+
}),
|
|
371
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
372
|
+
className: "ue-ins__from",
|
|
373
|
+
children: [
|
|
374
|
+
Object.keys(frame.snapshot).length,
|
|
375
|
+
" key",
|
|
376
|
+
Object.keys(frame.snapshot).length === 1 ? "" : "s"
|
|
377
|
+
]
|
|
378
|
+
})
|
|
379
|
+
]
|
|
380
|
+
}, frame.wireId))
|
|
381
|
+
})]
|
|
382
|
+
})
|
|
383
|
+
]
|
|
384
|
+
}) : null]
|
|
385
|
+
});
|
|
402
386
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
387
|
+
//#endregion
|
|
388
|
+
//#region src/devtools/styles.ts
|
|
389
|
+
/**
|
|
390
|
+
* Shipped as a <style> element inside the panel rather than a CSS file: no
|
|
391
|
+
* import for consumers to wire up, no document.head mutation, and it renders
|
|
392
|
+
* fine on the server. Every rule is scoped under .ue-ins.
|
|
393
|
+
*/
|
|
394
|
+
const STYLES = `
|
|
406
395
|
.ue-ins {
|
|
407
396
|
position: fixed;
|
|
408
397
|
z-index: 2147483000;
|
|
@@ -505,39 +494,50 @@ button.ue-ins__v {
|
|
|
505
494
|
.ue-ins__v--invalid { color: #f85149; }
|
|
506
495
|
.ue-ins__paused { color: #d29922; }
|
|
507
496
|
`;
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
497
|
+
//#endregion
|
|
498
|
+
//#region src/devtools/inspector.tsx
|
|
499
|
+
/**
|
|
500
|
+
* A floating panel showing what this tab is saying and hearing on the bus:
|
|
501
|
+
* peers, the leader, store keys with their version clocks, a live wire log in
|
|
502
|
+
* both directions, and a timeline of the state each wire produced.
|
|
503
|
+
*
|
|
504
|
+
* It deliberately does **not** create a Leader. Under dynamic eligibility,
|
|
505
|
+
* mounting one with `eligible: false` would disable candidacy for the whole
|
|
506
|
+
* tab, and mounting a plain one would enrol a tab that never asked to be a
|
|
507
|
+
* candidate — a devtool must not change what it measures. Instead it reads the
|
|
508
|
+
* crown out of the wire log, which it already sees in both directions.
|
|
509
|
+
*
|
|
510
|
+
* Presence is fine to use: the bus heartbeats regardless of whether anything
|
|
511
|
+
* created a Presence, so usePeers observes rather than perturbs.
|
|
512
|
+
*
|
|
513
|
+
* **The panel lives in a shadow root.** Its styles used to be a `<style>` tag
|
|
514
|
+
* in the page, which is only ever half true: `.ue-ins` could not leak out, but
|
|
515
|
+
* everything in the host document leaked *in* — a `* { box-sizing }` reset, an
|
|
516
|
+
* app-wide `button { text-transform: uppercase }`, a Tailwind preflight, any
|
|
517
|
+
* `!important`. A devtool that renders differently depending on whose app it is
|
|
518
|
+
* mounted in is a devtool you cannot trust when it looks wrong. Inside a shadow
|
|
519
|
+
* root, page CSS does not apply and the panel's own CSS cannot escape.
|
|
520
|
+
*
|
|
521
|
+
* A consequence worth knowing when testing an app that mounts it: the panel is
|
|
522
|
+
* **not** in `document`. Reach it through `host.shadowRoot`.
|
|
523
|
+
*/
|
|
524
|
+
function Inspector({ name = _use_everywhere_core.DEFAULT_NAME, position = "bottom-right", limit = 50, defaultOpen = false, leaseMs = 3e3 } = {}) {
|
|
525
|
+
const [root, setRoot] = (0, react.useState)(null);
|
|
526
|
+
const attach = (host) => {
|
|
527
|
+
if (!host || root) return;
|
|
528
|
+
setRoot(host.shadowRoot ?? host.attachShadow({ mode: "open" }));
|
|
529
|
+
};
|
|
530
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
531
|
+
ref: attach,
|
|
532
|
+
"data-testid": "ue-inspector-host",
|
|
533
|
+
children: root ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: STYLES }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Panel, {
|
|
534
|
+
name,
|
|
535
|
+
position,
|
|
536
|
+
limit,
|
|
537
|
+
defaultOpen,
|
|
538
|
+
leaseMs
|
|
539
|
+
})] }), root) : null
|
|
540
|
+
});
|
|
539
541
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
Inspector
|
|
543
|
-
});
|
|
542
|
+
//#endregion
|
|
543
|
+
exports.Inspector = Inspector;
|