@hueest/xray 0.6.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.6.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,307 +0,0 @@
1
- import { a as ROOT_ATTR, o as ROOT_CLASS, s as ROOT_INSTANT_ATTR } from "./plate-BwJUgHDc.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. Returns null
215
- * when the plate has no capture yet; the caller renders the fallback.
216
- *
217
- * Two-artifact contract (plan 005 round 2): a `Plate` is tree-only, and this
218
- * renderer neither reads css off it nor references `BASE_CSS` (keeping the
219
- * constant out of consumer bundles — Vite-path styling is entirely the CSS
220
- * pipeline's job: `virtual:xray/base.css` + `virtual:xray/plates/<name>.css`).
221
- * `css` is the one optional styling input: the consumer-composed inline css
222
- * for a runtime-captured (`@hueest/xray/core`) plate, threaded from the
223
- * `<Skeleton css>` prop and injected as a `<style>` on this root. It rides the
224
- * OUTERMOST root only — the adapters never pass it for a nested stitch (the
225
- * page-level/base styles are already in scope there, ADR 0007's once-per-page
226
- * rule by construction).
227
- *
228
- * `instant` (ADR 0020) marks this root a stitch continuation: a standalone
229
- * `<Skeleton>` replacing a stitch a still-showing parent was just rendering. It
230
- * lands as `data-xr-instant` on the OUTERMOST root only (`instant && !nested`) so
231
- * BASE_CSS reveals it without the `--xr-skeleton-delay` fade.
232
- */
233
- function renderPlate(plate, resolveStitch, nested, rootRef, instant, css) {
234
- if (!plate.chunks) return null;
235
- const className = plate.rootCls ? `${ROOT_CLASS} ${plate.rootCls}` : ROOT_CLASS;
236
- const rootAttrs = {
237
- [ROOT_ATTR]: plate.name,
238
- className,
239
- "aria-hidden": true,
240
- "aria-busy": true,
241
- ...rootRef ? { ref: rootRef } : {},
242
- ...instant && !nested ? { [ROOT_INSTANT_ATTR]: "" } : {}
243
- };
244
- const chunks = plate.chunks;
245
- if (chunks.length === 1 && typeof chunks[0] === "string") {
246
- if (css === void 0) return /* @__PURE__ */ jsx("div", {
247
- ...rootAttrs,
248
- dangerouslySetInnerHTML: { __html: chunks[0] }
249
- });
250
- const html = `<style>${escapeStyleText(css)}</style>${chunks[0]}`;
251
- return /* @__PURE__ */ jsx("div", {
252
- ...rootAttrs,
253
- dangerouslySetInnerHTML: { __html: html }
254
- });
255
- }
256
- return /* @__PURE__ */ jsxs("div", {
257
- ...rootAttrs,
258
- children: [css === void 0 ? null : /* @__PURE__ */ jsx("style", { children: css }), renderChunks(chunks, resolveStitch)]
259
- });
260
- }
261
- /**
262
- * Each `string` chunk is injected as a display:contents innerHTML island; each
263
- * `{ r }` stitch resolves through `resolveStitch`; a `{ c, k }` node stays a
264
- * real element and recurses (it holds a stitch deeper). No per-node tree walk —
265
- * the strings are already serialized.
266
- */
267
- function renderChunks(chunks, resolveStitch) {
268
- return chunks.map((chunk, i) => {
269
- if (typeof chunk === "string") return /* @__PURE__ */ jsx("div", {
270
- style: DISPLAY_CONTENTS,
271
- dangerouslySetInnerHTML: { __html: chunk }
272
- }, i);
273
- if ("t" in chunk) return /* @__PURE__ */ jsx("div", {
274
- style: DISPLAY_CONTENTS,
275
- dangerouslySetInnerHTML: { __html: chunk.t.repeat(chunk.n) }
276
- }, i);
277
- if ("r" in chunk) return /* @__PURE__ */ jsx(Fragment, { children: resolveStitch(chunk.r) }, i);
278
- return /* @__PURE__ */ jsx("div", {
279
- className: chunk.c,
280
- children: renderChunks(chunk.k, resolveStitch)
281
- }, i);
282
- });
283
- }
284
- /**
285
- * A never-settling promise for the loading suspender below. It never needs to
286
- * resolve: React retries a suspended boundary when its parent re-renders with new
287
- * props (here, `loading` flipping to false), so the prop change — not the promise —
288
- * drives recovery.
289
- */
290
- const SUSPEND_UNTIL_READY = new Promise(() => {});
291
- /**
292
- * Suspends while `loading` is true. It exists so a `suspense`-mode `<Skeleton>` can
293
- * show its plate through the ONE Suspense fallback across BOTH readiness phases — the
294
- * data-loading phase (this suspender) AND the lazy code-chunk phase (the content
295
- * itself suspending). Because the plate is the SAME fallback DOM node the whole time,
296
- * there is no unmount/remount between the two phases and thus no flicker (a top-level
297
- * `return skeleton` for the data phase followed by a Suspense-fallback skeleton for
298
- * the chunk phase are different tree positions, so they DID remount). While this
299
- * throws, the content sibling is never committed, preserving "children never render
300
- * without data" (ADR 0002).
301
- */
302
- function SuspendWhileLoading({ loading }) {
303
- if (loading) throw SUSPEND_UNTIL_READY;
304
- return null;
305
- }
306
- //#endregion
307
- 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,102 +0,0 @@
1
- import { c as XrayCaptureWalker, p as Plate } from "./serialize-BqbQVxYl.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
- /**
34
- * Inline skeleton css for a plate whose styling does NOT flow through the
35
- * Vite plugin's CSS pipeline — i.e. a runtime-captured plate from
36
- * `@hueest/xray/core`'s `captureElement` (ADR 0003: the no-Vite path). Pass
37
- * the composed string (`composeCss(css)` for the standard BASE_CSS + plate
38
- * rules); it is injected as a `<style>` on the skeleton root, exactly like
39
- * the pre-two-artifact inline path. OMIT for plugin-imported plates — their
40
- * css (base included) is already on the page via `virtual:xray/base.css` +
41
- * `virtual:xray/plates/<name>.css`, and an inline copy would double-apply
42
- * and re-fight the cascade. Applies to THIS plate's root only; stitched
43
- * child plates resolve through refs/the dev store and carry no inline css.
44
- */
45
- css?: string;
46
- /** Explicit readiness mode: render the Skeleton while true, then render children and capture them in dev when false. */
47
- loading?: boolean;
48
- /** Suspense mode: make `<Skeleton>` the Suspense boundary and use the Plate as the fallback until children resolve. */
49
- suspense?: boolean;
50
- /** Shown while loading when the plate has no capture yet. */
51
- fallback?: ReactNode;
52
- /** Override the plugin's settle delay before this site is captured (dev only). */
53
- delay?: number;
54
- /**
55
- * Programmable capture walker for this Skeleton's subtree (ADR 0018, dev only).
56
- * The escape hatch beyond the declarative `data-xr-*` annotations: a closure
57
- * that decides, per element, whether to ignore it, collapse it to one Bone, or
58
- * defer to the built-in walk. Threaded through the dev adapter into
59
- * `captureView`; PRODUCTION ignores it entirely (it never captures). On both
60
- * `root` variants of the union, so a manual root may carry one too.
61
- */
62
- captureWalker?: XrayCaptureWalker;
63
- /**
64
- * Per-Skeleton Collect limits (ADR 0022, dev only): override the plugin's
65
- * `collect` defaults for THIS boundary's capture. Flat primitives — not a nested
66
- * object — so they stay referentially stable across renders and never churn the
67
- * dev capture effect's deps. `maxNodes` caps total surviving nodes, `maxBreadth`
68
- * the widest level, `maxDepth` the nesting depth; an omitted `maxNodes` derives
69
- * from the resolved breadth × depth. PRODUCTION ignores them (it never captures).
70
- */
71
- maxNodes?: number;
72
- maxBreadth?: number;
73
- maxDepth?: number;
74
- }
75
- /**
76
- * Props accepted by the React `<Skeleton>` component, generic over the optional
77
- * render-prop `data` payload `T` (ADR 0014). The generic defaults to `undefined`
78
- * so existing call sites — `<Skeleton plate loading />`, plain children — keep
79
- * working with zero type arguments; passing `data` infers `T` into the render
80
- * prop. `data` is a pure pass-through (the consumer owns nullability), never
81
- * `NonNullable<T>`.
82
- *
83
- * The two members discriminate on `root`:
84
- * - `root="auto"` (default): the existing `display:contents` boundary wrapper
85
- * marks the capture seam (ADR 0006); children may be plain `ReactNode` OR a
86
- * render prop, and the surfaced `ref` is a no-op.
87
- * - `root="manual"`: no wrapper; the consumer owns the capture root element. This
88
- * REQUIRES render-prop children (the only way to hand back the `ref` that
89
- * writes the boundary marker), so plain children + `root="manual"` is a type
90
- * error.
91
- */
92
- type SkeletonProps<T = undefined> = SkeletonBase & ({
93
- /** 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`. */
94
- data?: T; /** Real content. In dev, these children are also the capture source once ready. Either plain `ReactNode` or a render prop. */
95
- children?: ReactNode | SkeletonRenderProp<T>;
96
- } | {
97
- /** 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`. */
98
- data?: T; /** Render prop required: spread the supplied `ref` onto your root element so capture does not leak into the parent plate. */
99
- children: SkeletonRenderProp<T>;
100
- });
101
- //#endregion
102
- export { SkeletonProps as t };