@hueest/xray 0.1.0 → 0.3.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/client.d.ts +64 -16
- package/dist/client.js +903 -72
- package/dist/core.d.ts +21 -27
- package/dist/core.js +39 -210
- package/dist/css-escape-N7bOusGW.js +52 -0
- package/dist/hud.js +46 -0
- package/dist/index.d.ts +297 -48
- package/dist/index.js +674 -353
- package/dist/plate-BIr62u7l.d.ts +363 -0
- package/dist/plate-BRR6d8Se.js +156 -0
- package/dist/project-DORoPAo7.js +10224 -0
- package/dist/react.core-BUj8ziwh.d.ts +79 -0
- package/dist/react.core-DMIhXHZF.js +272 -0
- package/dist/react.d.ts +2 -8
- package/dist/react.dev.d.ts +2 -9
- package/dist/react.dev.js +153 -23
- package/dist/react.js +24 -12
- package/dist/serialize-Cs7hUxmK.d.ts +107 -0
- package/package.json +8 -7
- package/virtual.d.ts +6 -0
- package/dist/breakpoints-CBNlh7lQ.js +0 -47
- package/dist/plate-BuzRMPx4.js +0 -94
- package/dist/plate-DboxMKg-.d.ts +0 -196
- package/dist/react.core-CqnDjfAJ.js +0 -59
- package/dist/react.core-fgWG_svU.d.ts +0 -21
- package/dist/serialize-Bq6yJnNV.js +0 -1071
- package/dist/serialize.d.ts +0 -88
- package/dist/serialize.js +0 -2
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { t as LeafKind } from "./plate-BIr62u7l.js";
|
|
2
|
+
|
|
3
|
+
//#region src/serialize.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Browser-side capture: walk a live rendered subtree into a Plate — one node
|
|
6
|
+
* tree plus one self-contained, scoped CSS string (ADR 0003). The CSS is
|
|
7
|
+
* lifted from the author rules that matched each node, rewritten to
|
|
8
|
+
* plate-local selectors — it carries `@media` for free and keeps layout live
|
|
9
|
+
* (the M1 bake-off winner; ADR 0005).
|
|
10
|
+
*/
|
|
11
|
+
interface CaptureOptions {
|
|
12
|
+
/** Plate name; comes from the virtual-module specifier. */
|
|
13
|
+
name: string;
|
|
14
|
+
/**
|
|
15
|
+
* Optional programmable capture walker (ADR 0018). The declarative `data-xr-*`
|
|
16
|
+
* annotations cover the 90%; this is the escape hatch for the 10% a markup
|
|
17
|
+
* attribute cannot express. Capture-only: it shapes the resulting `PlateNode`
|
|
18
|
+
* tree but is never persisted into the Plate, and the production adapter never
|
|
19
|
+
* captures so it ignores this entirely.
|
|
20
|
+
*/
|
|
21
|
+
walker?: XrayCaptureWalker;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the top-level capture roots ARE this plate's own boundary, so the
|
|
24
|
+
* stitch guard must be SKIPPED for them (ADR 0006/0014, finding #6). True ONLY
|
|
25
|
+
* for a MANUAL root: there `site.el` is the consumer's own element and its
|
|
26
|
+
* `data-xr-boundary` marks the boundary of THIS plate, so the root must be
|
|
27
|
+
* serialized as a real Entry (its box/classes/padding/border) rather than a
|
|
28
|
+
* stitch-to-itself. Defaults to `false` — the auto path, where the top-level
|
|
29
|
+
* roots are the wrapper's CHILDREN and any boundary marker on a direct-child
|
|
30
|
+
* root is a NESTED `<Skeleton>` that MUST stitch (the capture-leak guard).
|
|
31
|
+
*/
|
|
32
|
+
captureRootIsBoundary?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* What a capture walker decides for one element (ADR 0018; ADR 0022 §5). The
|
|
36
|
+
* `applyWalker` PASS reads the decision and applies it to the measured IR node —
|
|
37
|
+
* a walker NEVER hand-builds a `PlateNode`, so xray keeps ownership of the Plate
|
|
38
|
+
* format. `ignore` drops the element + its subtree; `bone` collapses it to one
|
|
39
|
+
* Bone leaf (inferring the kind when omitted); `keep` defers to the default
|
|
40
|
+
* classifier (`markInk`).
|
|
41
|
+
*/
|
|
42
|
+
type WalkerDecision = {
|
|
43
|
+
type: 'ignore';
|
|
44
|
+
} | {
|
|
45
|
+
type: 'bone';
|
|
46
|
+
kind?: LeafKind;
|
|
47
|
+
} | {
|
|
48
|
+
type: 'keep';
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* The context handed to a capture walker's `element` hook (ADR 0018). The
|
|
52
|
+
* helpers are PURE — each returns a `WalkerDecision` the `applyWalker` pass
|
|
53
|
+
* applies; they do NOT mutate. `el` is the element under consideration.
|
|
54
|
+
*/
|
|
55
|
+
interface CaptureWalkerContext {
|
|
56
|
+
/** The element currently being classified. */
|
|
57
|
+
el: Element;
|
|
58
|
+
/** Drop this element AND its whole subtree (no bone). */
|
|
59
|
+
ignore(): WalkerDecision;
|
|
60
|
+
/**
|
|
61
|
+
* Collapse this element's subtree into ONE Bone leaf. `kind` omitted shares
|
|
62
|
+
* the default classifier's kind-inference heuristic (`inferLeafKind`; ADR 0018,
|
|
63
|
+
* Q4 — ONE code path).
|
|
64
|
+
*/
|
|
65
|
+
bone(opts?: {
|
|
66
|
+
kind?: LeafKind;
|
|
67
|
+
}): WalkerDecision;
|
|
68
|
+
/** Defer to the default classifier (`markInk`) — recurse into the subtree. */
|
|
69
|
+
keep(): WalkerDecision;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The programmable capture walker (ADR 0018; ADR 0022 §5). `element` is offered
|
|
73
|
+
* each non-boundary element TOP-DOWN over the measured IR (the `applyWalker`
|
|
74
|
+
* pass, NOT the DOM walk) and returns a `WalkerDecision` the pass applies. The
|
|
75
|
+
* declarative `data-xr-*` annotations cover the 90%; this is the 10% escape
|
|
76
|
+
* hatch for what a markup attribute cannot express.
|
|
77
|
+
*/
|
|
78
|
+
interface XrayCaptureWalker {
|
|
79
|
+
/**
|
|
80
|
+
* Called for every non-boundary element node (a stitch carries no element and
|
|
81
|
+
* is skipped — ADR 0006 — so a boundary never reaches here). Return one of
|
|
82
|
+
* `ctx.ignore()`, `ctx.bone()`, or `ctx.keep()`. An `ignore`/`bone` decision
|
|
83
|
+
* stops the descent (the subtree lowers); `keep` recurses.
|
|
84
|
+
*/
|
|
85
|
+
element?(ctx: CaptureWalkerContext): WalkerDecision;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Typed identity wrapper for authoring a capture walker (ADR 0018) — the
|
|
89
|
+
* `defineConfig` pattern. It returns its argument unchanged at runtime; its only
|
|
90
|
+
* job is to infer and check the `XrayCaptureWalker` shape at the call site so an
|
|
91
|
+
* author gets completion and a typo in a hook name is caught.
|
|
92
|
+
*/
|
|
93
|
+
declare function defineXrayCaptureWalker(walker: XrayCaptureWalker): XrayCaptureWalker;
|
|
94
|
+
/**
|
|
95
|
+
* Thrown when a subtree has more nodes than the capture limit — almost always
|
|
96
|
+
* a `<Skeleton>` sitting too high in the tree. Raised after the cheap walk and
|
|
97
|
+
* before the O(rules × nodes) CSS extraction, so an oversized capture is
|
|
98
|
+
* refused without freezing the main thread on it.
|
|
99
|
+
*/
|
|
100
|
+
declare class CaptureTooLargeError extends Error {
|
|
101
|
+
readonly nodeCount: number;
|
|
102
|
+
/** Folds the oversized-capture warning into the shared diagnostics vocabulary (diagnostics.ts). */
|
|
103
|
+
readonly code: "too-large";
|
|
104
|
+
constructor(nodeCount: number);
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { XrayCaptureWalker as a, WalkerDecision as i, CaptureTooLargeError as n, defineXrayCaptureWalker as o, CaptureWalkerContext as r, CaptureOptions as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hueest/xray",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Auto-captured skeleton loading screens for Vite — capture the structure, ship the plate.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"loading",
|
|
@@ -42,15 +42,11 @@
|
|
|
42
42
|
"types": "./dist/react.dev.d.ts",
|
|
43
43
|
"import": "./dist/react.dev.js"
|
|
44
44
|
},
|
|
45
|
-
"./
|
|
46
|
-
"types": "./dist/serialize.d.ts",
|
|
47
|
-
"import": "./dist/serialize.js"
|
|
48
|
-
},
|
|
49
|
-
"./client": {
|
|
45
|
+
"./internal/client": {
|
|
50
46
|
"types": "./dist/client.d.ts",
|
|
51
47
|
"import": "./dist/client.js"
|
|
52
48
|
},
|
|
53
|
-
"./hud": {
|
|
49
|
+
"./internal/hud": {
|
|
54
50
|
"types": "./dist/hud.d.ts",
|
|
55
51
|
"import": "./dist/hud.js"
|
|
56
52
|
},
|
|
@@ -61,6 +57,11 @@
|
|
|
61
57
|
"publishConfig": {
|
|
62
58
|
"access": "public"
|
|
63
59
|
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@chenglou/pretext": "0.0.8",
|
|
62
|
+
"devalue": "5.8.1",
|
|
63
|
+
"valibot": "1.4.1"
|
|
64
|
+
},
|
|
64
65
|
"devDependencies": {
|
|
65
66
|
"@types/node": "25.9.3",
|
|
66
67
|
"@types/react": "19.2.17",
|
package/virtual.d.ts
CHANGED
|
@@ -16,6 +16,12 @@
|
|
|
16
16
|
* imported Plate. Pass the imported Plate to `<Skeleton>`; do not repeat the
|
|
17
17
|
* name in props.
|
|
18
18
|
*
|
|
19
|
+
* A name is one or more `/`-separated path segments, each `\w+(-\w+)*` — word
|
|
20
|
+
* chars with single interior hyphens. A segment may not contain `--` or a
|
|
21
|
+
* leading/trailing hyphen: the generated class prefix renders `/` as `--` to
|
|
22
|
+
* keep nested names readable, and banning those forms keeps the encoding
|
|
23
|
+
* collision-free (it is the isolation boundary across stitched Plates).
|
|
24
|
+
*
|
|
19
25
|
* If the file does not exist yet, the Vite plugin resolves an empty Plate with
|
|
20
26
|
* the same name so `<Skeleton fallback={...}>` can render until the first
|
|
21
27
|
* Capture writes the file. Nested `<Skeleton>` boundaries become Stitches; the
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
//#region src/breakpoints.ts
|
|
2
|
-
/**
|
|
3
|
-
* Per-plate breakpoint derivation (ADR 0004): the width thresholds that could
|
|
4
|
-
* change this plate come from the `@media` conditions on its own copied rules
|
|
5
|
-
* plus the queries the app evaluated through `matchMedia` during capture.
|
|
6
|
-
* Thresholds partition the width axis into views; identical captures in
|
|
7
|
-
* adjacent views collapse at merge time.
|
|
8
|
-
*/
|
|
9
|
-
const FONT_SIZE_PX = 16;
|
|
10
|
-
/** A breakpoint is the first integer px width of the view ABOVE the boundary. */
|
|
11
|
-
function thresholdsOf(condition) {
|
|
12
|
-
const out = [];
|
|
13
|
-
const toPx = (value, unit) => Number.parseFloat(value) * (unit === "px" ? 1 : FONT_SIZE_PX);
|
|
14
|
-
const push = (raw, exclusiveBelow) => {
|
|
15
|
-
const bp = exclusiveBelow ? Number.isInteger(raw) ? raw + 1 : Math.ceil(raw) : Math.ceil(raw);
|
|
16
|
-
if (Number.isFinite(bp) && bp > 0) out.push(bp);
|
|
17
|
-
};
|
|
18
|
-
for (const m of condition.matchAll(/min-width:\s*([\d.]+)(px|em|rem)/g)) push(toPx(m[1] ?? "", m[2] ?? "px"), false);
|
|
19
|
-
for (const m of condition.matchAll(/max-width:\s*([\d.]+)(px|em|rem)/g)) push(toPx(m[1] ?? "", m[2] ?? "px"), true);
|
|
20
|
-
for (const m of condition.matchAll(/width\s*>=?\s*([\d.]+)(px|em|rem)/g)) push(toPx(m[1] ?? "", m[2] ?? "px"), false);
|
|
21
|
-
for (const m of condition.matchAll(/width\s*<=?\s*([\d.]+)(px|em|rem)/g)) push(toPx(m[1] ?? "", m[2] ?? "px"), true);
|
|
22
|
-
for (const m of condition.matchAll(/([\d.]+)(px|em|rem)\s*<=?\s*width/g)) push(toPx(m[1] ?? "", m[2] ?? "px"), false);
|
|
23
|
-
return out;
|
|
24
|
-
}
|
|
25
|
-
/** Width thresholds for this plate: its rules' media conditions + the app's matchMedia queries. */
|
|
26
|
-
function deriveBreakpoints(rules, matchMediaQueries = []) {
|
|
27
|
-
const set = /* @__PURE__ */ new Set();
|
|
28
|
-
for (const rule of rules) for (const condition of rule.media ?? []) for (const bp of thresholdsOf(condition)) set.add(bp);
|
|
29
|
-
for (const query of matchMediaQueries) for (const bp of thresholdsOf(query)) set.add(bp);
|
|
30
|
-
return [...set].toSorted((a, b) => a - b);
|
|
31
|
-
}
|
|
32
|
-
/** The view interval `[min, max)` a given viewport width falls into. */
|
|
33
|
-
function regimeFor(width, breakpoints) {
|
|
34
|
-
let min;
|
|
35
|
-
let max;
|
|
36
|
-
for (const bp of breakpoints) if (bp <= width) min = bp;
|
|
37
|
-
else {
|
|
38
|
-
max = bp;
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
return {
|
|
42
|
-
...min === void 0 ? {} : { min },
|
|
43
|
-
...max === void 0 ? {} : { max }
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
//#endregion
|
|
47
|
-
export { regimeFor as n, deriveBreakpoints as t };
|
package/dist/plate-BuzRMPx4.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/** Marks the rendered skeleton root; carries the plate name. Also a dev capture marker. */
|
|
2
|
-
const ROOT_ATTR = "data-xr-root";
|
|
3
|
-
/**
|
|
4
|
-
* Marks a `<Content>` capture boundary in dev; carries the plate name. A parent
|
|
5
|
-
* capture treats it as a stitch — it references the child plate by name instead of
|
|
6
|
-
* re-serializing the child's subtree (ADR 0006).
|
|
7
|
-
*/
|
|
8
|
-
const BOUNDARY_ATTR = "data-xr-boundary";
|
|
9
|
-
const SPEC_LEAF = 9e6;
|
|
10
|
-
const SPEC_INLINE = 1e7;
|
|
11
|
-
const SPEC_GATE = 2e7;
|
|
12
|
-
/** Fixed renderer-owned classes every rendered node carries. */
|
|
13
|
-
const ROOT_CLASS = "xr-root";
|
|
14
|
-
const NODE_CLASS = "xr-node";
|
|
15
|
-
const LEAF_CLASS = "xr-leaf";
|
|
16
|
-
/**
|
|
17
|
-
* Renderer-owned base styles, emitted once by the outermost view (ADR 0007),
|
|
18
|
-
* before a plate's node rules so a node's captured class wins by source order.
|
|
19
|
-
* Everything visual is driven by inheritable custom properties, so consumers
|
|
20
|
-
* theme by setting `--xr-*` in `:root` or on any ancestor — and per-plate via
|
|
21
|
-
* `[data-xr-root="name"]` (the root carries its plate name). See ADR 0011 and
|
|
22
|
-
* docs/architecture.md. Class-based and unlayered — `@layer` was rejected
|
|
23
|
-
* (unlayered app CSS would beat layered skeleton rules, and it inverts
|
|
24
|
-
* `!important`).
|
|
25
|
-
*
|
|
26
|
-
* Progressive enhancement (ADR 0011): the plain bone fill + opacity pulse use
|
|
27
|
-
* only universally-supported CSS and are the floor. `light-dark()` is guarded
|
|
28
|
-
* by `@supports` because an unsupported VALUE drops the whole declaration (an
|
|
29
|
-
* invisible bone), and it is not Baseline-widely-available until 2026-11-13 —
|
|
30
|
-
* drop the guard then. Features that merely no-op when absent (`@property`,
|
|
31
|
-
* `prefers-contrast`, relative-color-as-fallback) carry no hard guard.
|
|
32
|
-
*
|
|
33
|
-
* Tokens: `--xr-bone` (fill), `--xr-bone-highlight` (sheen), `--xr-radius`
|
|
34
|
-
* (+ `--xr-radius-text` / `--xr-radius-media`), `--xr-duration`, the pulse range
|
|
35
|
-
* `--xr-pulse-min`/`--xr-pulse-max`, `--xr-anim` (mode: `xr-pulse` | `none` | a
|
|
36
|
-
* custom keyframe), and `--xr-fill` (override the leaf paint, e.g. a sheen
|
|
37
|
-
* gradient built from the two bone tokens).
|
|
38
|
-
*/
|
|
39
|
-
const BASE_CSS = `
|
|
40
|
-
/* One timeline per skeleton, not per leaf: the root animates one inherited
|
|
41
|
-
opacity property and every leaf reads it. Hundreds of independent infinite
|
|
42
|
-
opacity animations would pin the compositor and cook the CPU. */
|
|
43
|
-
@property --xr-o { syntax: "<number>"; inherits: true; initial-value: 1 }
|
|
44
|
-
.${ROOT_CLASS} {
|
|
45
|
-
--xr-bone-highlight: #f4f4f5;
|
|
46
|
-
display: contents;
|
|
47
|
-
animation: var(--xr-anim, xr-pulse) var(--xr-duration, 1.2s) ease-in-out infinite alternate;
|
|
48
|
-
}
|
|
49
|
-
/* No pointer-events:none — it blocks inspecting bones in devtools and buys
|
|
50
|
-
nothing (no real content to click). border-color: a node may capture
|
|
51
|
-
border-width for layout; its paint stays out. list-style: suppress a ::marker
|
|
52
|
-
a captured display:list-item would otherwise paint. */
|
|
53
|
-
.${NODE_CLASS} { border-color: transparent; list-style: none }
|
|
54
|
-
.${LEAF_CLASS} {
|
|
55
|
-
background: var(--xr-fill, var(--xr-bone, #e4e4e7));
|
|
56
|
-
border-radius: var(--xr-radius, 4px);
|
|
57
|
-
opacity: var(--xr-o);
|
|
58
|
-
}
|
|
59
|
-
/* Per-kind defaults: text reads as rounded bars, media as blocks. Re-theme any
|
|
60
|
-
kind via [data-xr-root] .${LEAF_CLASS}-text { ... }. */
|
|
61
|
-
.${LEAF_CLASS}-text { border-radius: var(--xr-radius-text, 999px) }
|
|
62
|
-
.${LEAF_CLASS}-media { border-radius: var(--xr-radius-media, var(--xr-radius, 4px)) }
|
|
63
|
-
@keyframes xr-pulse {
|
|
64
|
-
from { --xr-o: var(--xr-pulse-max, 1) }
|
|
65
|
-
to { --xr-o: var(--xr-pulse-min, 0.5) }
|
|
66
|
-
}
|
|
67
|
-
@media (prefers-reduced-motion: reduce) { .${ROOT_CLASS} { animation: none } }
|
|
68
|
-
/* Dark + high-contrast bone defaults. Guarded: light-dark() is an invalid value
|
|
69
|
-
without support and would drop the declaration — see the note above; remove
|
|
70
|
-
this @supports once widely available (2026-11-13). */
|
|
71
|
-
@supports (color: light-dark(#000, #fff)) {
|
|
72
|
-
.${ROOT_CLASS} { --xr-bone-highlight: light-dark(#f4f4f5, #52525b) }
|
|
73
|
-
.${LEAF_CLASS} { background: var(--xr-fill, var(--xr-bone, light-dark(#e4e4e7, #3f3f46))) }
|
|
74
|
-
@media (prefers-contrast: more) {
|
|
75
|
-
.${LEAF_CLASS} { background: var(--xr-fill, var(--xr-bone, light-dark(#d4d4d8, #52525b))) }
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
/* Derive the sheen highlight from --xr-bone so re-theming one token updates both.
|
|
79
|
-
Relative color isn't widely available yet; the static highlight above is the
|
|
80
|
-
fallback (sheen just looks flatter), so no hard guard is needed. */
|
|
81
|
-
@supports (color: oklch(from red l c h)) {
|
|
82
|
-
.${ROOT_CLASS} { --xr-bone-highlight: oklch(from var(--xr-bone, #e4e4e7) calc(l + 0.08) c h) }
|
|
83
|
-
}`.trim();
|
|
84
|
-
/** The (unserialized) plate an import resolves to before anything has been captured. */
|
|
85
|
-
function emptyPlate(name) {
|
|
86
|
-
return {
|
|
87
|
-
v: 1,
|
|
88
|
-
name,
|
|
89
|
-
tree: null,
|
|
90
|
-
css: ""
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
//#endregion
|
|
94
|
-
export { ROOT_ATTR as a, SPEC_INLINE as c, NODE_CLASS as i, SPEC_LEAF as l, BOUNDARY_ATTR as n, ROOT_CLASS as o, LEAF_CLASS as r, SPEC_GATE as s, BASE_CSS as t, emptyPlate as u };
|
package/dist/plate-DboxMKg-.d.ts
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
//#region src/plate.d.ts
|
|
2
|
-
type LeafKind = 'text' | 'media' | 'box';
|
|
3
|
-
/**
|
|
4
|
-
* The shipped structural capture of a component's rendered DOM — the data a
|
|
5
|
-
* Skeleton renders from (see CONTEXT.md). Self-contained: pre-serialized chunks
|
|
6
|
-
* plus one scoped CSS string; depends on nothing in the consumer's build (ADR
|
|
7
|
-
* 0003). Built from the RenderNode tree at plugin load (see chunk.ts), so the
|
|
8
|
-
* adapter injects strings instead of walking a tree on the latency-critical
|
|
9
|
-
* first paint — and no per-framework serializer ships to the client (ADR 0008).
|
|
10
|
-
*/
|
|
11
|
-
interface Plate {
|
|
12
|
-
v: number;
|
|
13
|
-
/** Capture name; comes from the virtual-module specifier, never typed at a call site. */
|
|
14
|
-
name: string;
|
|
15
|
-
/**
|
|
16
|
-
* The root's children, pre-serialized (ADR 0008): a stitch-free run is one
|
|
17
|
-
* HTML `string`; a stitch is `{ r: name }`; a stitch-bearing element is
|
|
18
|
-
* `{ c: className, k: chunk[] }`. Null until first capture.
|
|
19
|
-
*/
|
|
20
|
-
chunks: Chunk[] | null;
|
|
21
|
-
/** The root node's own content classes (its inherited font context), if any. */
|
|
22
|
-
rootCls?: string;
|
|
23
|
-
/** Layout-relevant CSS (incl. `@media`) scoped under the plate root, paint stripped. */
|
|
24
|
-
css: string;
|
|
25
|
-
/**
|
|
26
|
-
* Child plates named by the chunks' `{ r }` stitches, resolved through the
|
|
27
|
-
* import graph (ADR 0006). Populated by the loader's generated imports, never
|
|
28
|
-
* serialized into the plates JSON; absent in production when there are no
|
|
29
|
-
* stitches. In dev the live store takes precedence, so this need not be present.
|
|
30
|
-
*/
|
|
31
|
-
refs?: Record<string, Plate>;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* One serialized child of a plate root (or of a stitch-bearing node): a
|
|
35
|
-
* stitch-free HTML run (`string`), a stitch to a child plate (`{ r: name }`),
|
|
36
|
-
* or a stitch-bearing element kept real so its inner stitches can mount
|
|
37
|
-
* (`{ c: className, k: chunk[] }`). The framework-neutral render contract: an
|
|
38
|
-
* adapter injects the strings and mounts a child plate at each stitch (ADR 0008).
|
|
39
|
-
*/
|
|
40
|
-
type Chunk = string | {
|
|
41
|
-
r: string;
|
|
42
|
-
} | {
|
|
43
|
-
c: string;
|
|
44
|
-
k: Chunk[];
|
|
45
|
-
};
|
|
46
|
-
/**
|
|
47
|
-
* A plate after view-merge + classify but before chunk serialization: the
|
|
48
|
-
* build-time RenderNode tree plus its scoped CSS. Produced by `mergeViews` and
|
|
49
|
-
* `capture`, then serialized into the shipped `Plate` (chunks) at load
|
|
50
|
-
* (`serializePlate`). Never shipped to the client.
|
|
51
|
-
*/
|
|
52
|
-
interface MergedPlate {
|
|
53
|
-
v: number;
|
|
54
|
-
name: string;
|
|
55
|
-
tree: RenderNode | null;
|
|
56
|
-
css: string;
|
|
57
|
-
}
|
|
58
|
-
interface PlateNode {
|
|
59
|
-
/** Plate-local id; rules reference it pre-classify. Stripped from the shipped tree. */
|
|
60
|
-
id: number;
|
|
61
|
-
/** Present on paint leaves; absent on layout containers. */
|
|
62
|
-
leaf?: LeafKind;
|
|
63
|
-
/**
|
|
64
|
-
* A stitch: render the named child plate's skeleton here instead of own
|
|
65
|
-
* structure (ADR 0006). Mutually exclusive with `leaf`/`kids`. Resolved
|
|
66
|
-
* against `Plate.refs` (prod) or the live store (dev).
|
|
67
|
-
*/
|
|
68
|
-
ref?: string;
|
|
69
|
-
kids?: PlateNode[];
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* A node in the shipped tree, after classify (ADR 0007). Carries the
|
|
73
|
-
* content-addressed style classes it needs instead of an id — the CSS targets
|
|
74
|
-
* those classes, not a per-node `data-xr` value, so node scoping (the old
|
|
75
|
-
* `nodeKey`) is gone. `cls` is a space-joined class-token list.
|
|
76
|
-
*/
|
|
77
|
-
interface RenderNode {
|
|
78
|
-
leaf?: LeafKind;
|
|
79
|
-
ref?: string;
|
|
80
|
-
cls?: string;
|
|
81
|
-
kids?: RenderNode[];
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* A captured style rule in id form — selectors are generated at render time,
|
|
85
|
-
* so view merging can remap node ids without string surgery. `ids: []`
|
|
86
|
-
* targets the plate root.
|
|
87
|
-
*/
|
|
88
|
-
interface PlateRule {
|
|
89
|
-
ids: number[];
|
|
90
|
-
decls: string[];
|
|
91
|
-
/** Media-condition stack, outermost first; conditions AND together. */
|
|
92
|
-
media?: string[];
|
|
93
|
-
/** `@container`-condition stack, outermost first; conditions AND together. Absent when none. */
|
|
94
|
-
container?: string[];
|
|
95
|
-
layered?: boolean;
|
|
96
|
-
spec: number;
|
|
97
|
-
order: number;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* One capture of a component within one viewport view — `[min, max)` in px,
|
|
101
|
-
* either side open-ended when undefined. What the dev client ships to the
|
|
102
|
-
* plugin, and what the plate file accumulates (ADR 0004).
|
|
103
|
-
*/
|
|
104
|
-
interface ViewCapture {
|
|
105
|
-
/** Viewport width at capture time. */
|
|
106
|
-
width: number;
|
|
107
|
-
min?: number;
|
|
108
|
-
max?: number;
|
|
109
|
-
tree: PlateNode;
|
|
110
|
-
rules: PlateRule[];
|
|
111
|
-
}
|
|
112
|
-
/** The committed `plates/<name>.json`: per-view captures, merged at load. */
|
|
113
|
-
interface PlateFile {
|
|
114
|
-
v: number;
|
|
115
|
-
name: string;
|
|
116
|
-
/** Width thresholds (view boundaries) derived so far. */
|
|
117
|
-
breakpoints: number[];
|
|
118
|
-
views: ViewCapture[];
|
|
119
|
-
}
|
|
120
|
-
/** The (unserialized) plate an import resolves to before anything has been captured. */
|
|
121
|
-
declare function emptyPlate(name: string): MergedPlate;
|
|
122
|
-
/**
|
|
123
|
-
* A dev-only sticky boolean toggle (get/set/subscribe), persisted per tab.
|
|
124
|
-
* Used for both "light box" (render every `<Skeleton>` regardless of readiness)
|
|
125
|
-
* and "capture" (whether browsing re-captures plates).
|
|
126
|
-
*/
|
|
127
|
-
interface ToggleStore {
|
|
128
|
-
get: () => boolean;
|
|
129
|
-
set: (value: boolean) => void;
|
|
130
|
-
subscribe: (onChange: () => void) => () => void;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Dev-only store of the freshest plate per name, updated in place when a
|
|
134
|
-
* capture lands. `<Skeleton>` reads through it so a new plate re-renders the
|
|
135
|
-
* site rather than reloading the module — which would remount the capture
|
|
136
|
-
* boundary and re-fire the capture, a feedback loop.
|
|
137
|
-
*/
|
|
138
|
-
interface PlateStore {
|
|
139
|
-
get: (name: string) => Plate | undefined;
|
|
140
|
-
subscribe: (name: string, onChange: () => void) => () => void;
|
|
141
|
-
/**
|
|
142
|
-
* Register a plate imported via the ref graph if the store has none yet — so a
|
|
143
|
-
* stitch resolves even before its child is re-captured, and survives a parent
|
|
144
|
-
* hot-swap (whose pushed plate carries no resolved `refs`). Never overwrites a
|
|
145
|
-
* live capture.
|
|
146
|
-
*/
|
|
147
|
-
seed: (name: string, plate: Plate) => void;
|
|
148
|
-
}
|
|
149
|
-
/** A view interval `[min, max)`; an open end is undefined (ADR 0004). */
|
|
150
|
-
interface Span {
|
|
151
|
-
min?: number;
|
|
152
|
-
max?: number;
|
|
153
|
-
}
|
|
154
|
-
/** A plate's detected width thresholds and the spans actually captured, from disk. */
|
|
155
|
-
interface PlateCoverage {
|
|
156
|
-
breakpoints: number[];
|
|
157
|
-
spans: Span[];
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Dev-only per-plate coverage, sourced from the committed plate files (not the
|
|
161
|
-
* session): the breakpoints detected so far and which view spans are captured.
|
|
162
|
-
* The HUD renders every band — covered ones lit, the rest dim — and the
|
|
163
|
-
* capture-all sweep derives its target widths from the breakpoints.
|
|
164
|
-
*/
|
|
165
|
-
interface CoverageStore {
|
|
166
|
-
get: () => ReadonlyMap<string, PlateCoverage>;
|
|
167
|
-
set: (name: string, coverage: PlateCoverage) => void;
|
|
168
|
-
subscribe: (onChange: () => void) => () => void;
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Dev-only capture hook, installed by the dev client before the app boots.
|
|
172
|
-
* Absent in production builds, so `<Skeleton>` carries zero capture weight.
|
|
173
|
-
* `captured` returns a dispose callback (stop observing this site).
|
|
174
|
-
*/
|
|
175
|
-
interface CaptureHook {
|
|
176
|
-
captured: (name: string, el: Element, opts?: {
|
|
177
|
-
delay?: number;
|
|
178
|
-
}) => (() => void) | undefined;
|
|
179
|
-
/** Light Box toggle: force every `<Skeleton>` to its skeleton, read by `<Skeleton>`. */
|
|
180
|
-
lightbox?: ToggleStore;
|
|
181
|
-
/** Capture toggle: whether browsing re-captures plates, flipped from the HUD. */
|
|
182
|
-
capture?: ToggleStore;
|
|
183
|
-
/** Read side for `<Skeleton>`. */
|
|
184
|
-
plates?: PlateStore;
|
|
185
|
-
/** Write side for the dev client's HMR-event handler. */
|
|
186
|
-
updatePlate?: (name: string, plate: Plate) => void;
|
|
187
|
-
/** Dev-only: run the HUD-triggered capture-all-views sweep via the extension (ADR 0010). */
|
|
188
|
-
captureAllViews?: () => Promise<void>;
|
|
189
|
-
/** Dev-only per-plate breakpoints + captured spans, from disk; fed by the bootstrap. */
|
|
190
|
-
coverage?: CoverageStore;
|
|
191
|
-
}
|
|
192
|
-
declare global {
|
|
193
|
-
var __XRAY__: CaptureHook | undefined;
|
|
194
|
-
}
|
|
195
|
-
//#endregion
|
|
196
|
-
export { PlateNode as a, emptyPlate as c, PlateFile as i, MergedPlate as n, RenderNode as o, Plate as r, ViewCapture as s, LeafKind as t };
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { a as ROOT_ATTR, o as ROOT_CLASS, t as BASE_CSS } from "./plate-BuzRMPx4.js";
|
|
2
|
-
import { Fragment } from "react";
|
|
3
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
-
//#region src/react.core.tsx
|
|
5
|
-
/** Layout-neutral wrapper for an HTML island sitting beside React stitch nodes —
|
|
6
|
-
* its children participate in the grandparent's flow, so grid/flex item counts
|
|
7
|
-
* are unchanged. */
|
|
8
|
-
const DISPLAY_CONTENTS = { display: "contents" };
|
|
9
|
-
/**
|
|
10
|
-
* Render a shipped plate's skeleton. `resolveStitch` turns a child plate name
|
|
11
|
-
* into the node that mounts it (static `refs` in prod, a store-subscribed
|
|
12
|
-
* component in dev). `nested` is true for a stitch-mounted child, which skips
|
|
13
|
-
* BASE_CSS — the outermost view already emitted it (ADR 0007). Returns null
|
|
14
|
-
* when the plate has no capture yet; the caller renders the fallback.
|
|
15
|
-
*/
|
|
16
|
-
function renderPlate(plate, resolveStitch, nested) {
|
|
17
|
-
if (!plate.chunks) return null;
|
|
18
|
-
const className = plate.rootCls ? `${ROOT_CLASS} ${plate.rootCls}` : ROOT_CLASS;
|
|
19
|
-
const rootAttrs = {
|
|
20
|
-
[ROOT_ATTR]: plate.name,
|
|
21
|
-
className,
|
|
22
|
-
"aria-hidden": true,
|
|
23
|
-
"aria-busy": true
|
|
24
|
-
};
|
|
25
|
-
const css = nested ? plate.css : `${BASE_CSS}\n${plate.css}`;
|
|
26
|
-
const chunks = plate.chunks;
|
|
27
|
-
if (chunks.length === 1 && typeof chunks[0] === "string") {
|
|
28
|
-
const html = `<style>${css}</style>${chunks[0]}`;
|
|
29
|
-
return /* @__PURE__ */ jsx("div", {
|
|
30
|
-
...rootAttrs,
|
|
31
|
-
dangerouslySetInnerHTML: { __html: html }
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
35
|
-
...rootAttrs,
|
|
36
|
-
children: [/* @__PURE__ */ jsx("style", { children: css }), renderChunks(chunks, resolveStitch)]
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Each `string` chunk is injected as a display:contents innerHTML island; each
|
|
41
|
-
* `{ r }` stitch resolves through `resolveStitch`; a `{ c, k }` node stays a
|
|
42
|
-
* real element and recurses (it holds a stitch deeper). No per-node tree walk —
|
|
43
|
-
* the strings are already serialized.
|
|
44
|
-
*/
|
|
45
|
-
function renderChunks(chunks, resolveStitch) {
|
|
46
|
-
return chunks.map((chunk, i) => {
|
|
47
|
-
if (typeof chunk === "string") return /* @__PURE__ */ jsx("div", {
|
|
48
|
-
style: DISPLAY_CONTENTS,
|
|
49
|
-
dangerouslySetInnerHTML: { __html: chunk }
|
|
50
|
-
}, i);
|
|
51
|
-
if ("r" in chunk) return /* @__PURE__ */ jsx(Fragment, { children: resolveStitch(chunk.r) }, i);
|
|
52
|
-
return /* @__PURE__ */ jsx("div", {
|
|
53
|
-
className: chunk.c,
|
|
54
|
-
children: renderChunks(chunk.k, resolveStitch)
|
|
55
|
-
}, i);
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
//#endregion
|
|
59
|
-
export { renderPlate as t };
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { r as Plate } from "./plate-DboxMKg-.js";
|
|
2
|
-
import { ReactNode } from "react";
|
|
3
|
-
|
|
4
|
-
//#region src/react.core.d.ts
|
|
5
|
-
/** Props accepted by the React `<Skeleton>` component. */
|
|
6
|
-
interface SkeletonProps {
|
|
7
|
-
/** The plate to render while loading, imported from `virtual:xray/plates/<name>`. */
|
|
8
|
-
plate: Plate;
|
|
9
|
-
/** Explicit readiness mode: render the Skeleton while true, then render children and capture them in dev when false. */
|
|
10
|
-
loading?: boolean;
|
|
11
|
-
/** Suspense mode: make `<Skeleton>` the Suspense boundary and use the Plate as the fallback until children resolve. */
|
|
12
|
-
suspense?: boolean;
|
|
13
|
-
/** Shown while loading when the plate has no capture yet. */
|
|
14
|
-
fallback?: ReactNode;
|
|
15
|
-
/** Override the plugin's settle delay before this site is captured (dev only). */
|
|
16
|
-
delay?: number;
|
|
17
|
-
/** Real content. In dev, these children are also the capture source once ready. */
|
|
18
|
-
children?: ReactNode;
|
|
19
|
-
}
|
|
20
|
-
//#endregion
|
|
21
|
-
export { SkeletonProps as t };
|