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