@hueest/xray 0.1.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.
- package/dist/breakpoints-CBNlh7lQ.js +47 -0
- package/dist/client.d.ts +42 -0
- package/dist/client.js +354 -0
- package/dist/core.d.ts +55 -0
- package/dist/core.js +230 -0
- package/dist/hud.d.ts +18 -0
- package/dist/hud.js +165 -0
- package/dist/index.d.ts +317 -0
- package/dist/index.js +726 -0
- package/dist/plate-BuzRMPx4.js +94 -0
- package/dist/plate-DboxMKg-.d.ts +196 -0
- package/dist/react.core-CqnDjfAJ.js +59 -0
- package/dist/react.core-fgWG_svU.d.ts +21 -0
- package/dist/react.d.ts +29 -0
- package/dist/react.dev.d.ts +19 -0
- package/dist/react.dev.js +101 -0
- package/dist/react.js +58 -0
- package/dist/serialize-Bq6yJnNV.js +1071 -0
- package/dist/serialize.d.ts +88 -0
- package/dist/serialize.js +2 -0
- package/package.json +95 -0
- package/virtual.d.ts +53 -0
package/dist/core.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { a as ROOT_ATTR, i as NODE_CLASS, o as ROOT_CLASS, r as LEAF_CLASS, s as SPEC_GATE, t as BASE_CSS, u as emptyPlate } from "./plate-BuzRMPx4.js";
|
|
2
|
+
import { i as captureRegime, o as classify, r as capture, t as CaptureTooLargeError } from "./serialize-Bq6yJnNV.js";
|
|
3
|
+
import { n as regimeFor } from "./breakpoints-CBNlh7lQ.js";
|
|
4
|
+
//#region src/chunk.ts
|
|
5
|
+
/**
|
|
6
|
+
* Serialize a merged plate's RenderNode tree into the shipped `Plate` (ADR
|
|
7
|
+
* 0008). This is the framework-neutral half of rendering: a skeleton is static,
|
|
8
|
+
* text-free DOM, so the structure is flattened to HTML strings here, at build,
|
|
9
|
+
* once — instead of every adapter walking a tree on the latency-critical first
|
|
10
|
+
* paint. The adapter only injects the strings and mounts a child plate at each
|
|
11
|
+
* stitch (see the React adapters). No escaping risk: bones carry only the
|
|
12
|
+
* content-addressed class tokens (ADR 0007), never user content.
|
|
13
|
+
*/
|
|
14
|
+
function nodeClass(node) {
|
|
15
|
+
return NODE_CLASS + (node.leaf ? ` ${LEAF_CLASS} ${LEAF_CLASS}-${node.leaf}` : "") + (node.cls ? ` ${node.cls}` : "");
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Whether this subtree holds a stitch (`ref`) anywhere — a stitched subtree
|
|
19
|
+
* can't collapse to a flat string, since a `ref` resolves to a child plate at
|
|
20
|
+
* render time and must stay a mount point.
|
|
21
|
+
*/
|
|
22
|
+
function hasStitch(node) {
|
|
23
|
+
return node.ref !== void 0 || (node.kids?.some(hasStitch) ?? false);
|
|
24
|
+
}
|
|
25
|
+
/** Serialize a stitch-free subtree to one HTML string (caller guarantees no `ref` below). */
|
|
26
|
+
function toHtml(node) {
|
|
27
|
+
const kids = node.kids ? node.kids.map(toHtml).join("") : "";
|
|
28
|
+
return `<div class="${nodeClass(node)}">${kids}</div>`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Serialize a node list into chunks: each maximal stitch-free run becomes one
|
|
32
|
+
* HTML `string`; a `ref` node becomes `{ r }`; a node that isn't itself a stitch
|
|
33
|
+
* but holds one deeper stays a real element (`{ c, k }`) and recurses. Mirrors
|
|
34
|
+
* the element shape the runtime renderer would otherwise build per node.
|
|
35
|
+
*/
|
|
36
|
+
function toChunks(nodes) {
|
|
37
|
+
const out = [];
|
|
38
|
+
let buf = "";
|
|
39
|
+
const flush = () => {
|
|
40
|
+
if (buf) {
|
|
41
|
+
out.push(buf);
|
|
42
|
+
buf = "";
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
for (const node of nodes) {
|
|
46
|
+
if (!hasStitch(node)) {
|
|
47
|
+
buf += toHtml(node);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
flush();
|
|
51
|
+
if (node.ref !== void 0) out.push({ r: node.ref });
|
|
52
|
+
else out.push({
|
|
53
|
+
c: nodeClass(node),
|
|
54
|
+
k: toChunks(node.kids ?? [])
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
flush();
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Turn a merged plate (RenderNode tree) into the shipped plate (chunks). The
|
|
62
|
+
* root's own content classes travel as `rootCls` (the adapter puts them on the
|
|
63
|
+
* root element); its children become `chunks`. A capture-less plate has null
|
|
64
|
+
* chunks and renders the fallback.
|
|
65
|
+
*/
|
|
66
|
+
function serializePlate(merged) {
|
|
67
|
+
const { v, name, tree, css } = merged;
|
|
68
|
+
if (!tree) return {
|
|
69
|
+
v,
|
|
70
|
+
name,
|
|
71
|
+
chunks: null,
|
|
72
|
+
css
|
|
73
|
+
};
|
|
74
|
+
const chunks = toChunks(tree.kids ?? []);
|
|
75
|
+
return tree.cls ? {
|
|
76
|
+
v,
|
|
77
|
+
name,
|
|
78
|
+
chunks,
|
|
79
|
+
rootCls: tree.cls,
|
|
80
|
+
css
|
|
81
|
+
} : {
|
|
82
|
+
v,
|
|
83
|
+
name,
|
|
84
|
+
chunks,
|
|
85
|
+
css
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/merge.ts
|
|
90
|
+
/**
|
|
91
|
+
* Turn the per-view captures in a plate file into the one plate a Skeleton
|
|
92
|
+
* renders.
|
|
93
|
+
*
|
|
94
|
+
* Each view is kept whole — its own subtree, its own ids, its own rules —
|
|
95
|
+
* and shown only across the viewport range it owns, via `@media display:none`
|
|
96
|
+
* gates. We do NOT merge the views' trees into one. An earlier design (ADR
|
|
97
|
+
* 0004) did, sharing common structure and forking the rest, but aligning two
|
|
98
|
+
* genuinely-different DOM trees (mobile stacked vs desktop two-column) without
|
|
99
|
+
* stable keys proved unreliable: every heuristic mis-paired some nodes and
|
|
100
|
+
* remapped one view's rules onto another's elements, corrupting both. A lone
|
|
101
|
+
* capture is pixel-faithful; keeping each view lone preserves that. The cost
|
|
102
|
+
* is a larger plate (roughly the sum of the views) — bounded by the plate
|
|
103
|
+
* size cap — in exchange for correctness.
|
|
104
|
+
*
|
|
105
|
+
* Pure data-in/data-out — runs in node (plugin load) and in tests.
|
|
106
|
+
*/
|
|
107
|
+
function mergeViews(file) {
|
|
108
|
+
const views = file.views;
|
|
109
|
+
if (views.length === 0) return emptyPlate(file.name);
|
|
110
|
+
if (views.length === 1) {
|
|
111
|
+
const [only] = views;
|
|
112
|
+
if (!only) return emptyPlate(file.name);
|
|
113
|
+
const { tree, css } = classify(only.rules, only.tree, file.name);
|
|
114
|
+
return {
|
|
115
|
+
v: 1,
|
|
116
|
+
name: file.name,
|
|
117
|
+
tree,
|
|
118
|
+
css
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const starts = views.map((r) => regimeFor(r.width, file.breakpoints).min ?? 0);
|
|
122
|
+
let nextId = 1;
|
|
123
|
+
const rootKids = [];
|
|
124
|
+
const rules = [];
|
|
125
|
+
views.forEach((view, i) => {
|
|
126
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
127
|
+
const kids = (view.tree.kids ?? []).map((kid) => cloneWithIds(kid, idMap, () => nextId++));
|
|
128
|
+
rootKids.push(...kids);
|
|
129
|
+
rules.push(...remapRules(view.rules, idMap));
|
|
130
|
+
const lo = i === 0 ? 0 : starts[i] ?? 0;
|
|
131
|
+
const hi = i === views.length - 1 ? Number.POSITIVE_INFINITY : starts[i + 1] ?? 0;
|
|
132
|
+
for (const kid of kids) {
|
|
133
|
+
if (lo > 0) rules.push(gate(kid.id, `(max-width: ${lo - .02}px)`));
|
|
134
|
+
if (hi !== Number.POSITIVE_INFINITY) rules.push(gate(kid.id, `(min-width: ${hi}px)`));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
const { tree, css } = classify(rules, {
|
|
138
|
+
id: 0,
|
|
139
|
+
kids: rootKids
|
|
140
|
+
}, file.name);
|
|
141
|
+
return {
|
|
142
|
+
v: 1,
|
|
143
|
+
name: file.name,
|
|
144
|
+
tree,
|
|
145
|
+
css
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function gate(id, condition) {
|
|
149
|
+
return {
|
|
150
|
+
ids: [id],
|
|
151
|
+
decls: ["display: none !important"],
|
|
152
|
+
media: [condition],
|
|
153
|
+
spec: SPEC_GATE,
|
|
154
|
+
order: id
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/** Deep-clone a captured subtree into fresh ids, recording the mapping. */
|
|
158
|
+
function cloneWithIds(node, idMap, allocate) {
|
|
159
|
+
const id = allocate();
|
|
160
|
+
idMap.set(node.id, id);
|
|
161
|
+
const kids = node.kids?.map((kid) => cloneWithIds(kid, idMap, allocate));
|
|
162
|
+
return {
|
|
163
|
+
id,
|
|
164
|
+
...node.leaf ? { leaf: node.leaf } : {},
|
|
165
|
+
...node.ref !== void 0 ? { ref: node.ref } : {},
|
|
166
|
+
...kids ? { kids } : {}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/** Distinct plate names referenced by the tree's stitches, in first-seen order. */
|
|
170
|
+
function collectRefs(tree) {
|
|
171
|
+
const names = [];
|
|
172
|
+
const seen = /* @__PURE__ */ new Set();
|
|
173
|
+
const visit = (node) => {
|
|
174
|
+
if (node.ref !== void 0 && !seen.has(node.ref)) {
|
|
175
|
+
seen.add(node.ref);
|
|
176
|
+
names.push(node.ref);
|
|
177
|
+
}
|
|
178
|
+
for (const kid of node.kids ?? []) visit(kid);
|
|
179
|
+
};
|
|
180
|
+
if (tree) visit(tree);
|
|
181
|
+
return names;
|
|
182
|
+
}
|
|
183
|
+
function remapRules(rules, idMap) {
|
|
184
|
+
return rules.map((rule) => ({
|
|
185
|
+
...rule,
|
|
186
|
+
ids: rule.ids.map((id) => idMap.get(id) ?? id)
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
/** Fold a fresh capture into the plate file: union breakpoints, one capture per view. */
|
|
190
|
+
function addCapture(file, name, capture, breakpoints) {
|
|
191
|
+
const allBreakpoints = [...new Set([...file?.breakpoints ?? [], ...breakpoints])].toSorted((a, b) => a - b);
|
|
192
|
+
const spanKey = (width) => JSON.stringify(regimeFor(width, allBreakpoints));
|
|
193
|
+
const views = [...file?.views ?? [], capture];
|
|
194
|
+
const byView = /* @__PURE__ */ new Map();
|
|
195
|
+
for (const view of views) byView.set(spanKey(view.width), view);
|
|
196
|
+
return {
|
|
197
|
+
v: 1,
|
|
198
|
+
name,
|
|
199
|
+
breakpoints: allBreakpoints,
|
|
200
|
+
views: [...byView.values()].toSorted((a, b) => a.width - b.width)
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/core.ts
|
|
205
|
+
/**
|
|
206
|
+
* Capture a live DOM subtree straight to a renderable Plate — `capture` followed
|
|
207
|
+
* by `serializePlate` in one call. `roots` are the top-level elements to capture
|
|
208
|
+
* (a component's rendered roots). Pass the result to a `<Skeleton plate>`.
|
|
209
|
+
*/
|
|
210
|
+
function captureElement(roots, options) {
|
|
211
|
+
return serializePlate(capture(roots, options));
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Render a stitch-free Plate to a self-contained HTML string — the
|
|
215
|
+
* framework-neutral equivalent of `<Skeleton plate loading />`, for when you
|
|
216
|
+
* don't want to pull a framework adapter (demos, plain DOM, server strings).
|
|
217
|
+
* Drop it in with `el.innerHTML = renderPlateHtml(plate)` or React's
|
|
218
|
+
* `dangerouslySetInnerHTML`. The string is self-contained: it carries BASE_CSS
|
|
219
|
+
* + the plate's scoped rules in its own `<style>`. Stitched plates (a nested
|
|
220
|
+
* `<Skeleton>`) need an adapter to mount their child plates, so they throw here.
|
|
221
|
+
*/
|
|
222
|
+
function renderPlateHtml(plate) {
|
|
223
|
+
const chunks = plate.chunks;
|
|
224
|
+
if (!chunks) return "";
|
|
225
|
+
if (chunks.length !== 1 || typeof chunks[0] !== "string") throw new Error("xray: renderPlateHtml renders stitch-free plates only — use a framework adapter for stitched plates");
|
|
226
|
+
const className = plate.rootCls ? `${ROOT_CLASS} ${plate.rootCls}` : ROOT_CLASS;
|
|
227
|
+
return `<div ${ROOT_ATTR}="${plate.name}" class="${className}" aria-hidden="true" aria-busy="true"><style>${BASE_CSS}\n${plate.css}</style>${chunks[0]}</div>`;
|
|
228
|
+
}
|
|
229
|
+
//#endregion
|
|
230
|
+
export { CaptureTooLargeError, addCapture, capture, captureElement, captureRegime, collectRefs, emptyPlate, mergeViews, renderPlateHtml, serializePlate };
|
package/dist/hud.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/hud.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The opt-in dev HUD (Astro-dev-toolbar-style): a small fixed badge. For each
|
|
4
|
+
* plate it shows every detected width band — captured ones lit, the rest dim —
|
|
5
|
+
* sourced from the committed plate files (via `__XRAY__.coverage`), not the
|
|
6
|
+
* session, so it reflects what's actually on disk.
|
|
7
|
+
*
|
|
8
|
+
* It is also the controller for capture-all-views (ADR 0010): it probes for the
|
|
9
|
+
* companion extension (which announces itself via its content script) and shows
|
|
10
|
+
* a "Capture all views" button when present, or a one-line promotion when not.
|
|
11
|
+
* The sweep itself lives in the dev client (`__XRAY__.captureAllViews`).
|
|
12
|
+
*/
|
|
13
|
+
interface HudHandle {
|
|
14
|
+
dispose: () => void;
|
|
15
|
+
}
|
|
16
|
+
declare function installXrayHud(): HudHandle;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { HudHandle, installXrayHud };
|
package/dist/hud.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
//#region src/hud.ts
|
|
2
|
+
function isExtensionHello(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && "source" in value && value.source === "xray-ext" && "kind" in value && value.kind === "hello";
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* A labelled checkbox bound to a sticky toggle store; reads the store on build
|
|
7
|
+
* so a console/URL flip stays in sync with the box at the next render.
|
|
8
|
+
*/
|
|
9
|
+
function toggleRow(store, text) {
|
|
10
|
+
const label = document.createElement("label");
|
|
11
|
+
label.className = "toggle";
|
|
12
|
+
const checkbox = document.createElement("input");
|
|
13
|
+
checkbox.type = "checkbox";
|
|
14
|
+
checkbox.checked = store.get();
|
|
15
|
+
checkbox.addEventListener("change", () => {
|
|
16
|
+
store.set(checkbox.checked);
|
|
17
|
+
});
|
|
18
|
+
label.append(checkbox, document.createTextNode(text));
|
|
19
|
+
return label;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Expand a plate's coverage into the full set of width bands, flagging which are
|
|
23
|
+
* captured. The breakpoints partition the width axis; a band is covered when a
|
|
24
|
+
* captured span matches it (spans align to band boundaries, ADR 0004).
|
|
25
|
+
*/
|
|
26
|
+
function bandsOf({ breakpoints, spans }) {
|
|
27
|
+
const sorted = [...breakpoints].toSorted((a, b) => a - b);
|
|
28
|
+
const bands = [];
|
|
29
|
+
for (let i = 0; i <= sorted.length; i++) bands.push({
|
|
30
|
+
min: i === 0 ? void 0 : sorted[i - 1],
|
|
31
|
+
max: i === sorted.length ? void 0 : sorted[i]
|
|
32
|
+
});
|
|
33
|
+
return bands.map((band) => ({
|
|
34
|
+
label: `[${band.min ?? 0},${band.max ?? "∞"})`,
|
|
35
|
+
covered: spans.some((span) => span.min === band.min && span.max === band.max)
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
function installXrayHud() {
|
|
39
|
+
const host = document.createElement("div");
|
|
40
|
+
host.setAttribute("data-xray-hud", "");
|
|
41
|
+
const shadow = host.attachShadow({ mode: "open" });
|
|
42
|
+
const style = document.createElement("style");
|
|
43
|
+
style.textContent = `
|
|
44
|
+
.hud {
|
|
45
|
+
position: fixed; bottom: 12px; right: 12px; z-index: 2147483647;
|
|
46
|
+
background: #1c1c1f; color: #e4e4e7; border: 1px solid #3f3f46;
|
|
47
|
+
border-radius: 8px; padding: 8px 10px; max-width: 340px;
|
|
48
|
+
font: 11px/1.5 ui-monospace, monospace; opacity: 0.92;
|
|
49
|
+
}
|
|
50
|
+
.title { font-weight: 700; margin-bottom: 2px; color: #a1a1aa }
|
|
51
|
+
.plate { display: flex; gap: 8px; justify-content: space-between }
|
|
52
|
+
.bands { text-align: right }
|
|
53
|
+
.band.covered { color: #4ade80 }
|
|
54
|
+
.band.missing { color: #52525b }
|
|
55
|
+
.toggle { display: flex; gap: 6px; align-items: center; cursor: pointer; user-select: none }
|
|
56
|
+
.toggle input { accent-color: #4ade80; margin: 0 }
|
|
57
|
+
.action {
|
|
58
|
+
display: block; width: 100%; margin: 6px 0 2px; padding: 5px 8px;
|
|
59
|
+
border: 0; border-radius: 5px; background: #4ade80; color: #0a0a0a;
|
|
60
|
+
font: inherit; font-weight: 700; cursor: pointer;
|
|
61
|
+
}
|
|
62
|
+
.action:disabled { opacity: 0.6; cursor: default }
|
|
63
|
+
.promo { margin: 6px 0 2px; color: #a1a1aa }
|
|
64
|
+
.promo a { color: #4ade80 }
|
|
65
|
+
.status { color: #a1a1aa }
|
|
66
|
+
`;
|
|
67
|
+
const panel = document.createElement("div");
|
|
68
|
+
panel.className = "hud";
|
|
69
|
+
shadow.append(style, panel);
|
|
70
|
+
let extensionPresent = false;
|
|
71
|
+
let sweeping = false;
|
|
72
|
+
let sweepStatus = "";
|
|
73
|
+
const render = () => {
|
|
74
|
+
panel.textContent = "";
|
|
75
|
+
const coverage = globalThis.__XRAY__?.coverage?.get();
|
|
76
|
+
const count = coverage?.size ?? 0;
|
|
77
|
+
const title = document.createElement("div");
|
|
78
|
+
title.className = "title";
|
|
79
|
+
title.textContent = count === 0 ? "xray" : `xray — ${count} plate${count > 1 ? "s" : ""}`;
|
|
80
|
+
panel.append(title);
|
|
81
|
+
const capture = globalThis.__XRAY__?.capture;
|
|
82
|
+
if (capture) panel.append(toggleRow(capture, "Capture"));
|
|
83
|
+
const lightbox = globalThis.__XRAY__?.lightbox;
|
|
84
|
+
if (lightbox) panel.append(toggleRow(lightbox, "Light Box"));
|
|
85
|
+
if (globalThis.__XRAY__?.captureAllViews) if (extensionPresent) {
|
|
86
|
+
const button = document.createElement("button");
|
|
87
|
+
button.className = "action";
|
|
88
|
+
button.type = "button";
|
|
89
|
+
button.disabled = sweeping;
|
|
90
|
+
button.textContent = sweeping ? "Capturing all views…" : "Capture all views";
|
|
91
|
+
button.addEventListener("click", () => {
|
|
92
|
+
startSweep();
|
|
93
|
+
});
|
|
94
|
+
panel.append(button);
|
|
95
|
+
if (sweepStatus) {
|
|
96
|
+
const status = document.createElement("div");
|
|
97
|
+
status.className = "status";
|
|
98
|
+
status.textContent = sweepStatus;
|
|
99
|
+
panel.append(status);
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
const promo = document.createElement("div");
|
|
103
|
+
promo.className = "promo";
|
|
104
|
+
promo.textContent = "Install the xray extension to capture all views in one click.";
|
|
105
|
+
panel.append(promo);
|
|
106
|
+
}
|
|
107
|
+
for (const [name, plateCoverage] of coverage ?? []) {
|
|
108
|
+
const row = document.createElement("div");
|
|
109
|
+
row.className = "plate";
|
|
110
|
+
const label = document.createElement("span");
|
|
111
|
+
label.textContent = name;
|
|
112
|
+
const bands = document.createElement("span");
|
|
113
|
+
bands.className = "bands";
|
|
114
|
+
for (const band of bandsOf(plateCoverage)) {
|
|
115
|
+
const chip = document.createElement("span");
|
|
116
|
+
chip.className = `band ${band.covered ? "covered" : "missing"}`;
|
|
117
|
+
chip.textContent = band.label;
|
|
118
|
+
bands.append(chip, document.createTextNode(" "));
|
|
119
|
+
}
|
|
120
|
+
row.append(label, bands);
|
|
121
|
+
panel.append(row);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
async function startSweep() {
|
|
125
|
+
if (sweeping) return;
|
|
126
|
+
sweeping = true;
|
|
127
|
+
sweepStatus = "";
|
|
128
|
+
render();
|
|
129
|
+
try {
|
|
130
|
+
await globalThis.__XRAY__?.captureAllViews?.();
|
|
131
|
+
sweepStatus = "Done — coverage updated.";
|
|
132
|
+
} catch (error) {
|
|
133
|
+
sweepStatus = `Failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
134
|
+
} finally {
|
|
135
|
+
sweeping = false;
|
|
136
|
+
render();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const onHudMessage = (event) => {
|
|
140
|
+
if (event.source !== window) return;
|
|
141
|
+
if (isExtensionHello(event.data) && !extensionPresent) {
|
|
142
|
+
extensionPresent = true;
|
|
143
|
+
render();
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
window.addEventListener("message", onHudMessage);
|
|
147
|
+
window.postMessage({
|
|
148
|
+
source: "xray",
|
|
149
|
+
kind: "whois"
|
|
150
|
+
}, "*");
|
|
151
|
+
const unsubscribeCoverage = globalThis.__XRAY__?.coverage?.subscribe(render);
|
|
152
|
+
render();
|
|
153
|
+
const mount = () => {
|
|
154
|
+
if (document.body) document.body.append(host);
|
|
155
|
+
else document.addEventListener("DOMContentLoaded", () => document.body.append(host));
|
|
156
|
+
};
|
|
157
|
+
mount();
|
|
158
|
+
return { dispose() {
|
|
159
|
+
window.removeEventListener("message", onHudMessage);
|
|
160
|
+
unsubscribeCoverage?.();
|
|
161
|
+
host.remove();
|
|
162
|
+
} };
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
165
|
+
export { installXrayHud };
|