@hueest/xray 0.5.0 → 0.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hueest/xray",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Auto-captured skeleton loading screens for Vite — capture the structure, ship the plate.",
5
5
  "keywords": [
6
6
  "loading",
@@ -60,20 +60,22 @@
60
60
  "dependencies": {
61
61
  "@chenglou/pretext": "0.0.8",
62
62
  "devalue": "5.8.1",
63
- "valibot": "1.4.1"
63
+ "valibot": "1.4.2"
64
64
  },
65
65
  "devDependencies": {
66
+ "@arethetypeswrong/core": "0.18.5",
66
67
  "@types/node": "25.9.3",
67
68
  "@types/react": "19.2.17",
68
69
  "@types/react-dom": "19.2.3",
69
- "@typescript/native-preview": "7.0.0-dev.20260619.1",
70
+ "@typescript/native-preview": "7.0.0-dev.20260707.2",
70
71
  "happy-dom": "20.10.6",
72
+ "publint": "0.3.21",
71
73
  "react": "19.2.7",
72
74
  "react-dom": "19.2.7",
73
- "tsdown": "0.22.3",
75
+ "tsdown": "0.22.5",
74
76
  "typescript": "6.0.3",
75
- "vite": "8.0.16",
76
- "vitest": "4.1.9"
77
+ "vite": "8.1.4",
78
+ "vitest": "4.1.10"
77
79
  },
78
80
  "peerDependencies": {
79
81
  "react": "^18.2.0 || ^19.0.0",
package/virtual.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * Type declarations for xray virtual modules.
3
3
  *
4
- * Reference from the consuming app, e.g. in tsconfig:
5
- * `"types": ["@hueest/xray/virtual"]`, or per file:
6
- * `/// <reference types="@hueest/xray/virtual" />`.
4
+ * Reference from the consuming app, e.g. in tsconfig: `"types": ["@hueest/xray/virtual"]`, or per
5
+ * file: `/// <reference types="@hueest/xray/virtual" />`.
7
6
  *
8
7
  * @module @hueest/xray/virtual
9
8
  */
@@ -11,25 +10,22 @@
11
10
  /**
12
11
  * Import a committed or not-yet-captured Plate by name.
13
12
  *
14
- * The import specifier is the Plate identity: `virtual:xray/plates/my-banner`
15
- * reads and writes `plates/my-banner.json`, and that name travels with the
16
- * imported Plate. Pass the imported Plate to `<Skeleton>`; do not repeat the
17
- * name in props.
13
+ * The import specifier is the Plate identity: `virtual:xray/plates/my-banner` reads and writes
14
+ * `plates/my-banner.json`, and that name travels with the imported Plate. Pass the imported Plate
15
+ * to `<Skeleton>`; do not repeat the name in props.
18
16
  *
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).
17
+ * A name is one or more `/`-separated path segments, each `\w+(-\w+)*` — word chars with single
18
+ * interior hyphens. A segment may not contain `--` or a leading/trailing hyphen: the generated
19
+ * class prefix renders `/` as `--` to keep nested names readable, and banning those forms keeps the
20
+ * encoding collision-free (it is the isolation boundary across stitched Plates).
24
21
  *
25
- * If the file does not exist yet, the Vite plugin resolves an empty Plate with
26
- * the same name so `<Skeleton fallback={...}>` can render until the first
27
- * Capture writes the file. Nested `<Skeleton>` boundaries become Stitches; the
28
- * generated virtual module wires child Plates into `Plate.refs`, so production
29
- * rendering follows the static import graph with no global registry.
22
+ * If the file does not exist yet, the Vite plugin resolves an empty Plate with the same name so
23
+ * `<Skeleton fallback={...}>` can render until the first Capture writes the file. Nested
24
+ * `<Skeleton>` boundaries become Stitches; the generated virtual module wires child Plates into
25
+ * `Plate.refs`, so production rendering follows the static import graph with no global registry.
30
26
  *
31
- * Captured Plate files are source artifacts. Commit files under `plates/` with
32
- * the component change they describe.
27
+ * Captured Plate files are source artifacts. Commit files under `plates/` with the component change
28
+ * they describe.
33
29
  */
34
30
  declare module 'virtual:xray/plates/*' {
35
31
  import type { Plate } from '@hueest/xray'
@@ -37,22 +33,19 @@ declare module 'virtual:xray/plates/*' {
37
33
  /**
38
34
  * The captured Plate named by the `virtual:xray/plates/<name>` import specifier.
39
35
  *
40
- * Enable this import in a consuming app with
41
- * `"types": ["@hueest/xray/virtual"]` in tsconfig, or with a per-file
42
- * `/// <reference types="@hueest/xray/virtual" />` directive.
36
+ * Enable this import in a consuming app with `"types": ["@hueest/xray/virtual"]` in tsconfig, or
37
+ * with a per-file `/// <reference types="@hueest/xray/virtual" />` directive.
43
38
  *
44
- * The specifier maps to a file under `plates/`; for example,
45
- * `virtual:xray/plates/my-banner` maps to `plates/my-banner.json`. If that
46
- * file does not exist yet, the plugin returns an empty Plate with the same
47
- * name so a `<Skeleton fallback={...}>` can render until the first Capture.
48
- * Pass the imported Plate to `<Skeleton>`; do not repeat the name in props.
39
+ * The specifier maps to a file under `plates/`; for example, `virtual:xray/plates/my-banner` maps
40
+ * to `plates/my-banner.json`. If that file does not exist yet, the plugin returns an empty Plate
41
+ * with the same name so a `<Skeleton fallback={...}>` can render until the first Capture. Pass
42
+ * the imported Plate to `<Skeleton>`; do not repeat the name in props.
49
43
  *
50
- * Child Plates referenced by Stitches are wired into `Plate.refs` by the
51
- * generated virtual module. Production rendering follows that static import
52
- * graph with no global Plate registry or runtime name lookup.
44
+ * Child Plates referenced by Stitches are wired into `Plate.refs` by the generated virtual
45
+ * module. Production rendering follows that static import graph with no global Plate registry or
46
+ * runtime name lookup.
53
47
  *
54
- * Captured Plate files are source artifacts. Commit them with the component
55
- * change they describe.
48
+ * Captured Plate files are source artifacts. Commit them with the component change they describe.
56
49
  */
57
50
  const plate: Plate
58
51
  export default plate
@@ -1,52 +0,0 @@
1
- //#region src/css-escape.ts
2
- /**
3
- * Dependency-free raw-text safety for the one place a Plate's CSS string is
4
- * interpolated into `<style>…</style>` markup.
5
- *
6
- * A plate's `css` is normally produced by the capture path (`filterLayoutDecls`
7
- * + `classify`), which only ever emits well-formed `selector { prop: value }`
8
- * rules — never a raw-text terminator. But a committed `plates/<name>.json` is
9
- * UNTRUSTED at render time (it can be hand-edited or arrive over git, see the
10
- * capture-input-validation plan's decisions), and a corrupt/malicious `css`
11
- * could carry a `</style>` or an HTML comment delimiter. `<style>` is a
12
- * raw-text element: the parser ends the element at the first `</style` it sees
13
- * and resumes parsing the rest as ordinary HTML, so an unescaped terminator in
14
- * the CSS body turns the tail of the plate into live markup.
15
- *
16
- * The React text-child path (`<style>{css}</style>`) is already safe — React
17
- * escapes the child — but the two STRING paths interpolate `css` directly into
18
- * a markup string (`renderPlateHtml` builds an HTML string; `react.core.tsx`
19
- * feeds one to `dangerouslySetInnerHTML`). Those must run the body through this
20
- * helper first.
21
- *
22
- * This lives in its own dependency-free module ON PURPOSE: it is imported by
23
- * render/browser paths, so it must NOT pull in the Node-only validation code
24
- * (valibot). It neutralizes only the sequences that can escape a raw-text
25
- * `<style>` context — a `<` that begins `</style`, and the HTML comment / CDATA
26
- * delimiters the tokenizer treats specially inside raw text — by inserting a
27
- * CSS-comment break (`/​**​/`) that the CSS parser ignores. It does not, and is
28
- * not meant to, sanitize CSS semantically; POST-time validation
29
- * (`validate.ts`) is what rejects junk declarations.
30
- *
31
- * @module
32
- */
33
- /**
34
- * Make a CSS body safe to interpolate into a `<style>…</style>` STRING. Breaks
35
- * up the only sequences the HTML tokenizer acts on inside a raw-text `<style>`
36
- * element, so none can terminate the element early or open a comment/CDATA run:
37
- *
38
- * - `</style` (the end-tag open, case-insensitive) — inserts the break between
39
- * `<` and `/style` so the parser never matches the end tag.
40
- * - `<!--`, `-->`, `<![CDATA[` — the comment/CDATA delimiters; split so neither
41
- * a comment nor a CDATA section can open.
42
- *
43
- * The inserted `/​**​/` is an empty CSS comment: inert to the CSS parser (it
44
- * cannot appear mid-token in a value we emit, and a stray one only ends a
45
- * token early), so a well-formed plate is rendered byte-for-byte equivalently
46
- * while a corrupt one can no longer break out of the style element.
47
- */
48
- function escapeStyleText(css) {
49
- return css.replace(/<(\/style)/gi, "</**/$1").replace(/<!--/g, "<!/**/--").replace(/-->/g, "--/**/>").replace(/<!\[CDATA\[/g, "<![/**/CDATA[");
50
- }
51
- //#endregion
52
- export { escapeStyleText as t };
@@ -1,294 +0,0 @@
1
- import { a as ROOT_ATTR, o as ROOT_CLASS, s as ROOT_INSTANT_ATTR, t as BASE_CSS } from "./plate-BRR6d8Se.js";
2
- import { t as escapeStyleText } from "./css-escape-N7bOusGW.js";
3
- import { Fragment, useEffect, useRef, useState } from "react";
4
- import { jsx, jsxs } from "react/jsx-runtime";
5
- //#region src/react.core.tsx
6
- /**
7
- * The live-stitch registry (ADR 0020): a module-level ref-counted set of stitch
8
- * names that are CURRENTLY rendered as a stitch inside a still-SHOWING parent
9
- * skeleton. It exists to kill the "stitch flash": when a parent skeleton resolves
10
- * and its real content mounts a descendant's OWN standalone `<Skeleton>` for the
11
- * same plate, that standalone is a NEW outermost skeleton — it would re-pay the
12
- * full `--xr-skeleton-delay` (ADR 0016) from zero and visually disappear for
13
- * ~250ms even though the child was continuously visible as a stitch. A standalone
14
- * skeleton replacing such a live stitch must reveal INSTANTLY (no delay, no fade),
15
- * matching how the stitch inherited its parent's already-revealed channel.
16
- *
17
- * Why a loading-keyed registry and not wall-clock time: the parent marks its
18
- * direct stitch names live in an effect WHILE it shows its skeleton (an earlier
19
- * commit). When the parent resolves, React renders the content tree — the child's
20
- * standalone Skeleton first-renders and reads `isStitchLive(child)` BEFORE the
21
- * parent's unmark cleanup runs (render happens before that commit's effects), so
22
- * the read deterministically sees `child` still live. The unmark then clears it.
23
- * "Recently" is defined by React's render-before-effect ordering, not a timer, so
24
- * it is race-free AND does not over-apply to independent nested skeletons (a
25
- * tab-hidden skeleton was never marked live by a currently-showing parent → not
26
- * live → normal delay). It is portal-correct for free: only genuine stitches
27
- * (names in a parent plate's stitch set) are ever registered, so a portaled child
28
- * is never a stitch, never live, and reveals on the normal delay (ADR 0020).
29
- *
30
- * This ships to PROD: the flash is a production issue too, and the cost is a Map
31
- * plus a register effect and a read-once `useState` per `<Skeleton>` — a few
32
- * bytes, consistent with the prod-hooks relaxation ADR 0016 opened.
33
- */
34
- const liveStitches = /* @__PURE__ */ new Map();
35
- /**
36
- * Mark a parent skeleton's direct stitch names live (ref-counted, so concurrent
37
- * showing parents that stitch the same child stack), returning a disposer that
38
- * decrements each. The adapter calls this in an effect while `showSkeleton` is
39
- * true and runs the disposer when it stops showing (resolve/unmount). Several
40
- * marks of the same name increment the count; the count drops to 0 (entry
41
- * removed) only when every showing parent has unmarked it (ADR 0020).
42
- */
43
- function markStitchesLive(names) {
44
- for (const name of names) liveStitches.set(name, (liveStitches.get(name) ?? 0) + 1);
45
- return () => {
46
- queueMicrotask(() => {
47
- for (const name of names) {
48
- const next = (liveStitches.get(name) ?? 0) - 1;
49
- if (next <= 0) liveStitches.delete(name);
50
- else liveStitches.set(name, next);
51
- }
52
- });
53
- };
54
- }
55
- /** True while `name` is rendered as a stitch inside a still-showing parent
56
- * skeleton — i.e. a standalone `<Skeleton>` for it is a continuation, not a fresh
57
- * skeleton, and must reveal instantly (ADR 0020). */
58
- function isStitchLive(name) {
59
- return (liveStitches.get(name) ?? 0) > 0;
60
- }
61
- /**
62
- * The direct stitch names a plate's chunks reference (ADR 0020): a `{ r: name }`
63
- * chunk IS a stitch; a `{ c, k }` chunk is a stitch-bearing element kept real, so
64
- * recurse its `k`. Walks the SHIPPED `chunks` (the render contract), not
65
- * `plate.refs` (empty in dev, where the live store resolves stitches) nor
66
- * `collectRefs` (which walks the build-time RenderNode tree, not the shipped
67
- * chunks). One level of direct stitch names per Skeleton is enough: each level
68
- * self-registers as it becomes a real `<Skeleton>` (the cascade), so arbitrary
69
- * nesting is covered without recursing into child plates here.
70
- */
71
- function collectStitchNames(chunks) {
72
- if (!chunks) return [];
73
- const names = [];
74
- const walk = (cs) => {
75
- for (const chunk of cs) {
76
- if (typeof chunk === "string") continue;
77
- if ("t" in chunk) continue;
78
- if ("r" in chunk) names.push(chunk.r);
79
- else walk(chunk.k);
80
- }
81
- };
82
- walk(chunks);
83
- return names;
84
- }
85
- /** Narrow unknown children to the render-prop form. */
86
- function isRenderProp(children) {
87
- return typeof children === "function";
88
- }
89
- /**
90
- * Resolve `<Skeleton>` content at the ready (`loading=false`) path: invoke the
91
- * render prop with the verbatim `data` and the adapter's `ref` (a no-op in prod
92
- * and in `root="auto"`, the boundary-writing callback in dev `root="manual"`), or
93
- * pass plain children through untouched. Centralizes the single `data as T` step:
94
- * `data` is declared `data?: T`, so it reads back as `T | undefined`, but it is
95
- * the exact value the consumer passed for the render prop's `T` (ADR 0014 keeps it
96
- * a pass-through, not `NonNullable<T>`).
97
- */
98
- function resolveContent(children, data, ref) {
99
- if (!isRenderProp(children)) return children;
100
- return children({
101
- data,
102
- ref
103
- });
104
- }
105
- /**
106
- * The token defaults that mirror the BASE_CSS `var(--xr-skeleton-*, …)` fallbacks
107
- * (ADR 0016). The hook reads the COMPUTED token off the boundary element, but a
108
- * consumer who never sets it leaves the computed value empty (the tokens are not
109
- * declared on `.xr-root`, only referenced with these inline fallbacks). So when a
110
- * read comes back empty/unparseable the hook falls back to these same numbers,
111
- * keeping the JS hold and the CSS reveal in lockstep on identical defaults.
112
- */
113
- const DEFAULT_SKELETON_DELAY_MS = 250;
114
- const DEFAULT_SKELETON_MIN_DURATION_MS = 600;
115
- /**
116
- * Parse a CSS time token (`getPropertyValue` returns a trimmed string like
117
- * `"250ms"`, `"0.6s"`, or `""`) to milliseconds, falling back to `fallback` when
118
- * empty or unparseable. Supports `ms` and `s`; a bare number is treated as ms.
119
- */
120
- function parseMs(value, fallback) {
121
- const text = value.trim();
122
- if (text === "") return fallback;
123
- const match = /^(-?[\d.]+)(ms|s)?$/.exec(text);
124
- if (!match?.[1]) return fallback;
125
- const n = Number.parseFloat(match[1]);
126
- if (!Number.isFinite(n)) return fallback;
127
- return match[2] === "s" ? n * 1e3 : n;
128
- }
129
- /**
130
- * The min-duration hold (ADR 0016), shared by the production (`react.tsx`) and dev
131
- * (`react.dev.tsx`) adapters so both keep identical timing. Returns whether the
132
- * skeleton should render RIGHT NOW.
133
- *
134
- * The reveal DELAY is pure CSS (BASE_CSS `--xr-reveal`, zero JS); this hook is
135
- * ONLY the part CSS cannot express: keeping the skeleton mounted past
136
- * `loading=false` for a minimum visible window. The hook returns `loading ||
137
- * holding`: the `loading` term is SYNCHRONOUS, so the skeleton shows on the very
138
- * render `loading` flips true (no content frame at a false→true restart, and no
139
- * stale-state lag), and `holding` is state used SOLELY for the post-ready hold.
140
- *
141
- * While loading, an effect anchors `t0` (loading start) and pre-arms `holding`
142
- * true, so that pre-armed value carries into the first `loading=false` render with
143
- * no content flash at the true→false transition. When `loading` flips false the
144
- * effect reads the COMPUTED `--xr-skeleton-delay` + `--xr-skeleton-min-duration`
145
- * off `ref` and holds until `t0 + delay + min-duration`. Two cases skip the hold
146
- * entirely (swap synchronously, no timer): min-duration resolves to 0, or `ref` is
147
- * null / no `t0` was recorded — meaning no skeleton root was ever shown (e.g. a
148
- * plate with no capture, just a fallback), so there is nothing to hold and a hold
149
- * would only keep blank/fallback UI on screen after data is ready.
150
- *
151
- * Reading computed custom props off the skeleton root (open Q4): the root carries
152
- * `display:contents`, but custom-property inheritance and resolution are
153
- * unaffected by `display` — `getComputedStyle(el).getPropertyValue('--xr-…')`
154
- * returns the inherited token value on a `display:contents` element just as on any
155
- * other. The tokens are not declared on `.xr-root` (only referenced with inline
156
- * fallbacks), so a consumer who never themes them yields an empty read; `parseMs`
157
- * then applies the same defaults the CSS uses, so the hold matches the reveal.
158
- */
159
- function useSkeletonTiming(loading, ref) {
160
- const [holding, setHolding] = useState(loading);
161
- const startRef = useRef(null);
162
- const timerRef = useRef(void 0);
163
- useEffect(() => {
164
- const clear = () => {
165
- if (timerRef.current !== void 0) {
166
- clearTimeout(timerRef.current);
167
- timerRef.current = void 0;
168
- }
169
- };
170
- if (loading) {
171
- clear();
172
- if (startRef.current === null) startRef.current = Date.now();
173
- setHolding(true);
174
- return clear;
175
- }
176
- const start = startRef.current;
177
- startRef.current = null;
178
- const el = ref.current;
179
- if (el === null || start === null) {
180
- clear();
181
- setHolding(false);
182
- return clear;
183
- }
184
- const style = getComputedStyle(el);
185
- const delay = parseMs(style.getPropertyValue("--xr-skeleton-delay"), DEFAULT_SKELETON_DELAY_MS);
186
- const minDuration = parseMs(style.getPropertyValue("--xr-skeleton-min-duration"), DEFAULT_SKELETON_MIN_DURATION_MS);
187
- if (minDuration <= 0) {
188
- clear();
189
- setHolding(false);
190
- return clear;
191
- }
192
- const remaining = start + delay + minDuration - Date.now();
193
- if (remaining <= 0) {
194
- clear();
195
- setHolding(false);
196
- return clear;
197
- }
198
- clear();
199
- timerRef.current = setTimeout(() => {
200
- timerRef.current = void 0;
201
- setHolding(false);
202
- }, remaining);
203
- return clear;
204
- }, [loading, ref]);
205
- return loading || holding;
206
- }
207
- /** Layout-neutral wrapper for an HTML island sitting beside React stitch nodes —
208
- * its children participate in the grandparent's flow, so grid/flex item counts
209
- * are unchanged. */
210
- const DISPLAY_CONTENTS = { display: "contents" };
211
- /**
212
- * Render a shipped plate's skeleton. `resolveStitch` turns a child plate name
213
- * into the node that mounts it (static `refs` in prod, a store-subscribed
214
- * component in dev). `nested` is true for a stitch-mounted child, which skips
215
- * BASE_CSS — the outermost view already emitted it (ADR 0007). Returns null
216
- * when the plate has no capture yet; the caller renders the fallback.
217
- *
218
- * `instant` (ADR 0020) marks this root a stitch continuation: a standalone
219
- * `<Skeleton>` replacing a stitch a still-showing parent was just rendering. It
220
- * lands as `data-xr-instant` on the OUTERMOST root only (`instant && !nested`) so
221
- * BASE_CSS reveals it without the `--xr-skeleton-delay` fade.
222
- */
223
- function renderPlate(plate, resolveStitch, nested, rootRef, instant) {
224
- if (!plate.chunks) return null;
225
- const className = plate.rootCls ? `${ROOT_CLASS} ${plate.rootCls}` : ROOT_CLASS;
226
- const rootAttrs = {
227
- [ROOT_ATTR]: plate.name,
228
- className,
229
- "aria-hidden": true,
230
- "aria-busy": true,
231
- ...rootRef ? { ref: rootRef } : {},
232
- ...instant && !nested ? { [ROOT_INSTANT_ATTR]: "" } : {}
233
- };
234
- const css = nested ? plate.css : `${BASE_CSS}\n${plate.css}`;
235
- const chunks = plate.chunks;
236
- if (chunks.length === 1 && typeof chunks[0] === "string") {
237
- const html = `<style>${escapeStyleText(css)}</style>${chunks[0]}`;
238
- return /* @__PURE__ */ jsx("div", {
239
- ...rootAttrs,
240
- dangerouslySetInnerHTML: { __html: html }
241
- });
242
- }
243
- return /* @__PURE__ */ jsxs("div", {
244
- ...rootAttrs,
245
- children: [/* @__PURE__ */ jsx("style", { children: css }), renderChunks(chunks, resolveStitch)]
246
- });
247
- }
248
- /**
249
- * Each `string` chunk is injected as a display:contents innerHTML island; each
250
- * `{ r }` stitch resolves through `resolveStitch`; a `{ c, k }` node stays a
251
- * real element and recurses (it holds a stitch deeper). No per-node tree walk —
252
- * the strings are already serialized.
253
- */
254
- function renderChunks(chunks, resolveStitch) {
255
- return chunks.map((chunk, i) => {
256
- if (typeof chunk === "string") return /* @__PURE__ */ jsx("div", {
257
- style: DISPLAY_CONTENTS,
258
- dangerouslySetInnerHTML: { __html: chunk }
259
- }, i);
260
- if ("t" in chunk) return /* @__PURE__ */ jsx("div", {
261
- style: DISPLAY_CONTENTS,
262
- dangerouslySetInnerHTML: { __html: chunk.t.repeat(chunk.n) }
263
- }, i);
264
- if ("r" in chunk) return /* @__PURE__ */ jsx(Fragment, { children: resolveStitch(chunk.r) }, i);
265
- return /* @__PURE__ */ jsx("div", {
266
- className: chunk.c,
267
- children: renderChunks(chunk.k, resolveStitch)
268
- }, i);
269
- });
270
- }
271
- /**
272
- * A never-settling promise for the loading suspender below. It never needs to
273
- * resolve: React retries a suspended boundary when its parent re-renders with new
274
- * props (here, `loading` flipping to false), so the prop change — not the promise —
275
- * drives recovery.
276
- */
277
- const SUSPEND_UNTIL_READY = new Promise(() => {});
278
- /**
279
- * Suspends while `loading` is true. It exists so a `suspense`-mode `<Skeleton>` can
280
- * show its plate through the ONE Suspense fallback across BOTH readiness phases — the
281
- * data-loading phase (this suspender) AND the lazy code-chunk phase (the content
282
- * itself suspending). Because the plate is the SAME fallback DOM node the whole time,
283
- * there is no unmount/remount between the two phases and thus no flicker (a top-level
284
- * `return skeleton` for the data phase followed by a Suspense-fallback skeleton for
285
- * the chunk phase are different tree positions, so they DID remount). While this
286
- * throws, the content sibling is never committed, preserving "children never render
287
- * without data" (ADR 0002).
288
- */
289
- function SuspendWhileLoading({ loading }) {
290
- if (loading) throw SUSPEND_UNTIL_READY;
291
- return null;
292
- }
293
- //#endregion
294
- export { markStitchesLive as a, useSkeletonTiming as c, isStitchLive as i, collectStitchNames as n, renderPlate as o, isRenderProp as r, resolveContent as s, SuspendWhileLoading as t };
@@ -1,89 +0,0 @@
1
- import { c as XrayCaptureWalker, f as Plate } from "./serialize-BgdGt34A.js";
2
- import { ReactNode } from "react";
3
-
4
- //#region src/react.core.d.ts
5
- /**
6
- * The additive render-prop children form (ADR 0014). Called only at
7
- * `loading=false` (null included — `loading` stays the sole readiness axis, ADR
8
- * 0002), it hands back the typed `data` verbatim plus a `ref` for the manual
9
- * capture root.
10
- *
11
- * `ref` is a CALLBACK ref by contract: in `root="manual"` (dev) it imperatively
12
- * writes the `data-xr-boundary` marker onto the consumer's own element and
13
- * registers it as the capture root, so it must run on the real DOM node. In
14
- * `root="auto"` and in production it is a no-op (omitted), and the existing
15
- * `display:contents` boundary wrapper does the marking instead.
16
- *
17
- * It is typed as a callback ref over the base `Element` rather than a `Ref<T>`.
18
- * A callback that accepts `Element | null` is, by parameter contravariance,
19
- * assignable to a more specific `ref` slot (`<section ref={ref}>` wants
20
- * `(el: HTMLElement | null) => void`), so the consumer spreads it onto any host
21
- * element without a cast. A `RefObject` would NOT compose this way and is not
22
- * offered: writing the boundary attribute needs the imperative callback anyway.
23
- */
24
- type SkeletonRef = (el: Element | null) => void;
25
- type SkeletonRenderProp<T> = (ctx: {
26
- data: T;
27
- ref?: SkeletonRef;
28
- }) => ReactNode;
29
- /** Fields shared by both `root` variants of `<Skeleton>`. */
30
- interface SkeletonBase {
31
- /** The plate to render while loading, imported from `virtual:xray/plates/<name>`. */
32
- plate: Plate;
33
- /** Explicit readiness mode: render the Skeleton while true, then render children and capture them in dev when false. */
34
- loading?: boolean;
35
- /** Suspense mode: make `<Skeleton>` the Suspense boundary and use the Plate as the fallback until children resolve. */
36
- suspense?: boolean;
37
- /** Shown while loading when the plate has no capture yet. */
38
- fallback?: ReactNode;
39
- /** Override the plugin's settle delay before this site is captured (dev only). */
40
- delay?: number;
41
- /**
42
- * Programmable capture walker for this Skeleton's subtree (ADR 0018, dev only).
43
- * The escape hatch beyond the declarative `data-xr-*` annotations: a closure
44
- * that decides, per element, whether to ignore it, collapse it to one Bone, or
45
- * defer to the built-in walk. Threaded through the dev adapter into
46
- * `captureView`; PRODUCTION ignores it entirely (it never captures). On both
47
- * `root` variants of the union, so a manual root may carry one too.
48
- */
49
- captureWalker?: XrayCaptureWalker;
50
- /**
51
- * Per-Skeleton Collect limits (ADR 0022, dev only): override the plugin's
52
- * `collect` defaults for THIS boundary's capture. Flat primitives — not a nested
53
- * object — so they stay referentially stable across renders and never churn the
54
- * dev capture effect's deps. `maxNodes` caps total surviving nodes, `maxBreadth`
55
- * the widest level, `maxDepth` the nesting depth; an omitted `maxNodes` derives
56
- * from the resolved breadth × depth. PRODUCTION ignores them (it never captures).
57
- */
58
- maxNodes?: number;
59
- maxBreadth?: number;
60
- maxDepth?: number;
61
- }
62
- /**
63
- * Props accepted by the React `<Skeleton>` component, generic over the optional
64
- * render-prop `data` payload `T` (ADR 0014). The generic defaults to `undefined`
65
- * so existing call sites — `<Skeleton plate loading />`, plain children — keep
66
- * working with zero type arguments; passing `data` infers `T` into the render
67
- * prop. `data` is a pure pass-through (the consumer owns nullability), never
68
- * `NonNullable<T>`.
69
- *
70
- * The two members discriminate on `root`:
71
- * - `root="auto"` (default): the existing `display:contents` boundary wrapper
72
- * marks the capture seam (ADR 0006); children may be plain `ReactNode` OR a
73
- * render prop, and the surfaced `ref` is a no-op.
74
- * - `root="manual"`: no wrapper; the consumer owns the capture root element. This
75
- * REQUIRES render-prop children (the only way to hand back the `ref` that
76
- * writes the boundary marker), so plain children + `root="manual"` is a type
77
- * error.
78
- */
79
- type SkeletonProps<T = undefined> = SkeletonBase & ({
80
- /** Auto capture root (default): xray inserts a `display:contents` boundary wrapper. */root?: 'auto'; /** Render-prop payload, passed back to the children function verbatim as `T`. */
81
- data?: T; /** Real content. In dev, these children are also the capture source once ready. Either plain `ReactNode` or a render prop. */
82
- children?: ReactNode | SkeletonRenderProp<T>;
83
- } | {
84
- /** Manual capture root: the consumer's element owns the boundary marker; no wrapper (ADR 0014). */root: 'manual'; /** Render-prop payload, passed back to the children function verbatim as `T`. */
85
- data?: T; /** Render prop required: spread the supplied `ref` onto your root element so capture does not leak into the parent plate. */
86
- children: SkeletonRenderProp<T>;
87
- });
88
- //#endregion
89
- export { SkeletonProps as t };