@hueest/xray 0.1.0 → 0.2.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/{serialize-Bq6yJnNV.js → breakpoints-CMoFUUOv.js} +426 -104
- package/dist/client.d.ts +38 -3
- package/dist/client.js +716 -27
- package/dist/core.d.ts +8 -3
- package/dist/core.js +12 -6
- package/dist/css-escape-N7bOusGW.js +52 -0
- package/dist/hud.js +46 -0
- package/dist/index.d.ts +231 -5
- package/dist/index.js +753 -71
- package/dist/{plate-DboxMKg-.d.ts → plate-Bv6W-GkA.d.ts} +142 -0
- package/dist/plate-DoE1HEXp.js +152 -0
- package/dist/react.core-BzMG_cDy.d.ts +79 -0
- package/dist/react.core-IyFy2b_8.js +267 -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-B--oY2bV.d.ts +143 -0
- package/package.json +7 -7
- package/virtual.d.ts +6 -0
- package/dist/breakpoints-CBNlh7lQ.js +0 -47
- package/dist/plate-BuzRMPx4.js +0 -94
- package/dist/react.core-CqnDjfAJ.js +0 -59
- package/dist/react.core-fgWG_svU.d.ts +0 -21
- package/dist/serialize.d.ts +0 -88
- package/dist/serialize.js +0 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ROOT_ATTR,
|
|
1
|
+
import { a as ROOT_ATTR, l as SPEC_INLINE, u as SPEC_LEAF } from "./plate-DoE1HEXp.js";
|
|
2
2
|
//#region src/classify.ts
|
|
3
3
|
/**
|
|
4
4
|
* Turn id-form rules + an id-tree into the shipped plate: each distinct
|
|
@@ -16,7 +16,7 @@ import { a as ROOT_ATTR, c as SPEC_INLINE, l as SPEC_LEAF } from "./plate-BuzRMP
|
|
|
16
16
|
* Pure data-in/data-out — runs at plugin load (node) and in tests.
|
|
17
17
|
*/
|
|
18
18
|
function classify(rules, tree, plateName) {
|
|
19
|
-
const classBase = `${CLASS_PREFIX}${
|
|
19
|
+
const classBase = `${CLASS_PREFIX}${encodePlateName(plateName)}-`;
|
|
20
20
|
const sorted = rules.toSorted((a, b) => Number(b.layered ?? false) - Number(a.layered ?? false) || a.spec - b.spec || a.order - b.order);
|
|
21
21
|
const nameForKey = /* @__PURE__ */ new Map();
|
|
22
22
|
const body = /* @__PURE__ */ new Map();
|
|
@@ -90,9 +90,23 @@ function render(node, classes) {
|
|
|
90
90
|
}
|
|
91
91
|
/** Content-class prefix; distinct from the fixed `xr-root/node/leaf` base words. */
|
|
92
92
|
const CLASS_PREFIX = "xr-";
|
|
93
|
-
/**
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Encode a plate name into a class-token prefix, injectively. This prefix *is*
|
|
95
|
+
* the isolation boundary across stitched plates (ADR 0007), so distinct plate
|
|
96
|
+
* names must never produce the same prefix — a collision would let one plate's
|
|
97
|
+
* `<style>` block reorder another's classes.
|
|
98
|
+
*
|
|
99
|
+
* Plate names are constrained by `isValidPlateName`: each path segment is
|
|
100
|
+
* `\w+(-\w+)*` (word chars with single interior hyphens, no doubled or edge
|
|
101
|
+
* hyphen) and segments are joined by `/`. The slash is the only char that is
|
|
102
|
+
* not class-safe, so rendering it as `--` is enough — and because no segment
|
|
103
|
+
* holds a literal `--` or an edge hyphen, no `-` ever abuts a `/`, so every
|
|
104
|
+
* `--` in the prefix unambiguously came from a `/`, making the map injective.
|
|
105
|
+
* Keeping `/` visually as `--` also leaves nested names readable in the
|
|
106
|
+
* generated CSS (`product--card`).
|
|
107
|
+
*/
|
|
108
|
+
function encodePlateName(name) {
|
|
109
|
+
return name.replace(/\//g, "--");
|
|
96
110
|
}
|
|
97
111
|
/**
|
|
98
112
|
* What the class identity keys on, alongside media + declarations:
|
|
@@ -374,7 +388,146 @@ function similarity(a, b) {
|
|
|
374
388
|
return total === 0 ? 1 : 1 - diff / total;
|
|
375
389
|
}
|
|
376
390
|
//#endregion
|
|
391
|
+
//#region src/diagnostics.ts
|
|
392
|
+
function createDiagnostics() {
|
|
393
|
+
const byCode = /* @__PURE__ */ new Map();
|
|
394
|
+
return {
|
|
395
|
+
add(code, options) {
|
|
396
|
+
const bump = options?.count ?? 1;
|
|
397
|
+
const existing = byCode.get(code);
|
|
398
|
+
if (existing) {
|
|
399
|
+
existing.count = (existing.count ?? 0) + bump;
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
byCode.set(code, {
|
|
403
|
+
code,
|
|
404
|
+
message: DIAGNOSTIC_MESSAGES[code],
|
|
405
|
+
count: bump,
|
|
406
|
+
...options?.detail !== void 0 ? { detail: options.detail } : {}
|
|
407
|
+
});
|
|
408
|
+
},
|
|
409
|
+
list() {
|
|
410
|
+
return [...byCode.values()];
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Build a single diagnostic record with its canonical message, for callers
|
|
416
|
+
* outside the walk that surface a diagnostic directly (e.g. the client mapping
|
|
417
|
+
* `CaptureTooLargeError`, or the server reporting an invalid committed plate).
|
|
418
|
+
* Uses the same message table as the collector so the text is identical
|
|
419
|
+
* everywhere. `detail` must never carry user CSS or captured text.
|
|
420
|
+
*/
|
|
421
|
+
function makeDiagnostic(code, options) {
|
|
422
|
+
return {
|
|
423
|
+
code,
|
|
424
|
+
message: DIAGNOSTIC_MESSAGES[code],
|
|
425
|
+
...options?.count !== void 0 ? { count: options.count } : {},
|
|
426
|
+
...options?.detail !== void 0 ? { detail: options.detail } : {}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* The human-readable message per code, shared verbatim by browser and server so
|
|
431
|
+
* their text cannot drift. Messages describe the omission and its fidelity
|
|
432
|
+
* cost; none echoes user CSS or captured text. `formatDiagnostic` appends the
|
|
433
|
+
* aggregate count and any short detail.
|
|
434
|
+
*/
|
|
435
|
+
const DIAGNOSTIC_MESSAGES = {
|
|
436
|
+
"unreadable-stylesheet": "could not read a stylesheet (likely cross-origin); rules from it were not captured",
|
|
437
|
+
"unreadable-import": "could not read an @import (likely cross-origin); its rules were not captured",
|
|
438
|
+
"skipped-dynamic-pseudo": "skipped interaction pseudo-class selectors (:hover, :focus, etc.) — a static skeleton never enters those states",
|
|
439
|
+
"skipped-pseudo-element": "skipped pseudo-element selectors (::before, ::after, etc.) — v1 has no box to map them onto",
|
|
440
|
+
"unsupported-rule": "skipped CSS rules of a kind the serializer does not lift",
|
|
441
|
+
"dropped-tag": "dropped hidden or non-visual elements (display:none, visibility:hidden, script/style/etc.)",
|
|
442
|
+
"pruned-run": "collapsed long runs of similar siblings (a list/grid) to their first few items to keep the plate small",
|
|
443
|
+
"too-large": "capture exceeded the node limit and was skipped; the <Skeleton> likely sits too high in the tree",
|
|
444
|
+
"invalid-plate-file": "a committed plate file could not be read (corrupt JSON, wrong shape, or a version mismatch) and was ignored"
|
|
445
|
+
};
|
|
446
|
+
/**
|
|
447
|
+
* The single shared formatter. Renders one diagnostic to a stable one-line
|
|
448
|
+
* string used IDENTICALLY by the browser console and the Vite server output, so
|
|
449
|
+
* the two can never disagree. Shape: `<message> (<count>)[: <detail>]`. Count is
|
|
450
|
+
* omitted when 1 or absent; detail is appended only when present and is never
|
|
451
|
+
* user content.
|
|
452
|
+
*/
|
|
453
|
+
function formatDiagnostic(diagnostic) {
|
|
454
|
+
const count = diagnostic.count !== void 0 && diagnostic.count > 1 ? ` (${diagnostic.count})` : "";
|
|
455
|
+
const detail = diagnostic.detail !== void 0 ? `: ${diagnostic.detail}` : "";
|
|
456
|
+
return `${diagnostic.message}${count}${detail}`;
|
|
457
|
+
}
|
|
458
|
+
/** The grouped-warning header for a plate's diagnostics, shared by browser and server. */
|
|
459
|
+
function diagnosticsHeader(name) {
|
|
460
|
+
return `[xray] "${name}" captured with reduced fidelity:`;
|
|
461
|
+
}
|
|
462
|
+
//#endregion
|
|
377
463
|
//#region src/serialize.ts
|
|
464
|
+
/**
|
|
465
|
+
* Capture-only DOM annotations (ADR 0018), read DURING the walk to shape the
|
|
466
|
+
* `PlateNode` tree and NEVER written into the tree, the Plate, or the persisted
|
|
467
|
+
* JSON. They are the declarative 90% of capture customization; the programmable
|
|
468
|
+
* `captureWalker` is the 10% escape hatch.
|
|
469
|
+
*
|
|
470
|
+
* - `data-xr-ignore` drops the element AND its subtree (it contributes no bone).
|
|
471
|
+
* - `data-xr-bone-kind="text|media|box"` collapses the element's whole subtree
|
|
472
|
+
* into ONE Bone leaf of the given kind, sized from the element's own box.
|
|
473
|
+
*
|
|
474
|
+
* Both are subordinate to the stitch guard (ADR 0006): a nested `<Skeleton>`
|
|
475
|
+
* boundary always becomes a stitch first, so neither annotation can make a
|
|
476
|
+
* boundary leak into the parent plate. See `walk`.
|
|
477
|
+
*/
|
|
478
|
+
const XR_IGNORE_ATTR = "data-xr-ignore";
|
|
479
|
+
const XR_BONE_KIND_ATTR = "data-xr-bone-kind";
|
|
480
|
+
/** The `LeafKind` values an author may legally name in `data-xr-bone-kind`. */
|
|
481
|
+
const LEAF_KINDS = [
|
|
482
|
+
"text",
|
|
483
|
+
"media",
|
|
484
|
+
"box"
|
|
485
|
+
];
|
|
486
|
+
/** Narrow an arbitrary `data-xr-bone-kind` string to a `LeafKind`, or null if unknown. */
|
|
487
|
+
function asLeafKind(value) {
|
|
488
|
+
return LEAF_KINDS.find((kind) => kind === value) ?? null;
|
|
489
|
+
}
|
|
490
|
+
/** Reinterpret the internal decision as the opaque result at the ctx/next boundary. Zero runtime cost. */
|
|
491
|
+
function wrap(node) {
|
|
492
|
+
return node;
|
|
493
|
+
}
|
|
494
|
+
/** Recover the internal decision from an opaque result inside the engine. Zero runtime cost. */
|
|
495
|
+
function unwrap(result) {
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Typed identity wrapper for authoring a capture walker (ADR 0018) — the
|
|
500
|
+
* `defineConfig` pattern. It returns its argument unchanged at runtime; its only
|
|
501
|
+
* job is to infer and check the `XrayCaptureWalker` shape at the call site so an
|
|
502
|
+
* author gets completion and a typo in a hook name is caught.
|
|
503
|
+
*/
|
|
504
|
+
function defineXrayCaptureWalker(walker) {
|
|
505
|
+
return walker;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* A per-capture memo of `getComputedStyle(el)` keyed by element, scoped to ONE
|
|
509
|
+
* synchronous capture (capture performance plan, piece 5). The walk reads each
|
|
510
|
+
* entry node's own style through here, and the ancestor-walking helpers
|
|
511
|
+
* (contrast, line-height, clipping) memoize the parents they climb. A
|
|
512
|
+
* `CSSStyleDeclaration` is a live view, so this WeakMap lives only as long as
|
|
513
|
+
* the single capture that built it and is then dropped — it is NEVER cached
|
|
514
|
+
* across captures or time (a stored style would silently go stale; the plan
|
|
515
|
+
* forbids it). `get` lazily computes and stores on first lookup.
|
|
516
|
+
*/
|
|
517
|
+
var StyleCache = class {
|
|
518
|
+
#win;
|
|
519
|
+
#map = /* @__PURE__ */ new WeakMap();
|
|
520
|
+
constructor(win) {
|
|
521
|
+
this.#win = win;
|
|
522
|
+
}
|
|
523
|
+
get(el) {
|
|
524
|
+
const hit = this.#map.get(el);
|
|
525
|
+
if (hit) return hit;
|
|
526
|
+
const cs = this.#win.getComputedStyle(el);
|
|
527
|
+
this.#map.set(el, cs);
|
|
528
|
+
return cs;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
378
531
|
function isElementNode(node) {
|
|
379
532
|
return node.nodeType === 1;
|
|
380
533
|
}
|
|
@@ -488,14 +641,14 @@ const FILL_CONTRAST = 72;
|
|
|
488
641
|
* mostly opaque and clearly differs from the nearest ancestor's background
|
|
489
642
|
* (so a white button on a white card doesn't, but a blue one does).
|
|
490
643
|
*/
|
|
491
|
-
function hasContrastingFill(el, cs, win) {
|
|
644
|
+
function hasContrastingFill(el, cs, win, styles) {
|
|
492
645
|
if (hasPaintedImage(cs)) return true;
|
|
493
646
|
const bg = toRgb(cs.backgroundColor, win);
|
|
494
647
|
if (!bg || bg.a < .5) return false;
|
|
495
648
|
let ancestor = el.parentElement;
|
|
496
649
|
let behind = null;
|
|
497
650
|
while (ancestor) {
|
|
498
|
-
const candidate = toRgb(
|
|
651
|
+
const candidate = toRgb(styles.get(ancestor).backgroundColor, win);
|
|
499
652
|
if (candidate && candidate.a > 0) {
|
|
500
653
|
behind = candidate;
|
|
501
654
|
break;
|
|
@@ -516,7 +669,10 @@ function hasContrastingFill(el, cs, win) {
|
|
|
516
669
|
* wrapper); the synthetic root node (id 0) stands in for the wrapper itself.
|
|
517
670
|
*/
|
|
518
671
|
function capture(roots, options) {
|
|
519
|
-
const view = captureRegime(roots
|
|
672
|
+
const view = captureRegime(roots, {
|
|
673
|
+
walker: options.walker,
|
|
674
|
+
captureRootIsBoundary: options.captureRootIsBoundary
|
|
675
|
+
});
|
|
520
676
|
const { tree, css } = classify(view.rules, view.tree, options.name);
|
|
521
677
|
return {
|
|
522
678
|
v: 1,
|
|
@@ -533,6 +689,8 @@ function capture(roots, options) {
|
|
|
533
689
|
*/
|
|
534
690
|
var CaptureTooLargeError = class extends Error {
|
|
535
691
|
nodeCount;
|
|
692
|
+
/** Folds the oversized-capture warning into the shared diagnostics vocabulary (diagnostics.ts). */
|
|
693
|
+
code = "too-large";
|
|
536
694
|
constructor(nodeCount) {
|
|
537
695
|
super(`xray: capture has ${nodeCount} nodes, over the limit`);
|
|
538
696
|
this.name = "CaptureTooLargeError";
|
|
@@ -549,43 +707,31 @@ function captureRegime(roots, options = {}) {
|
|
|
549
707
|
const doc = roots[0]?.ownerDocument;
|
|
550
708
|
const win = doc?.defaultView;
|
|
551
709
|
if (!doc || !win) throw new Error("xray: capture roots must be attached to a document");
|
|
552
|
-
const
|
|
710
|
+
const diagnostics = createDiagnostics();
|
|
711
|
+
const styles = new StyleCache(win);
|
|
712
|
+
const { tree, entries } = walkAll(roots, win, styles, diagnostics, options.walker, options.captureRootIsBoundary ?? false);
|
|
553
713
|
if (options.maxNodes !== void 0 && entries.length > options.maxNodes) throw new CaptureTooLargeError(entries.length);
|
|
714
|
+
const rules = copyRules(entries, doc, win, styles, diagnostics);
|
|
715
|
+
const list = diagnostics.list();
|
|
554
716
|
return {
|
|
555
717
|
width: win.innerWidth,
|
|
556
718
|
tree,
|
|
557
|
-
rules
|
|
719
|
+
rules,
|
|
720
|
+
...list.length > 0 ? { diagnostics: list } : {}
|
|
558
721
|
};
|
|
559
722
|
}
|
|
560
|
-
|
|
561
|
-
* Walk a live subtree exactly like `capture` does, but return per-node
|
|
562
|
-
* geometry instead of a plate. Rendering the captured plate and auditing both
|
|
563
|
-
* sides gives an id-aligned fidelity diff (used by the M1 bake-off).
|
|
564
|
-
*/
|
|
565
|
-
function audit(roots) {
|
|
566
|
-
const win = roots[0]?.ownerDocument?.defaultView;
|
|
567
|
-
if (!win) throw new Error("xray: audit roots must be attached to a document");
|
|
568
|
-
const { entries } = walkAll(roots, win);
|
|
569
|
-
return entries.map((entry) => {
|
|
570
|
-
const rect = entry.el?.getBoundingClientRect();
|
|
571
|
-
return {
|
|
572
|
-
id: entry.id,
|
|
573
|
-
tag: entry.el?.tagName.toLowerCase() ?? "#text",
|
|
574
|
-
...entry.leaf ? { leaf: entry.leaf } : {},
|
|
575
|
-
w: Math.round(rect?.width ?? 0),
|
|
576
|
-
h: Math.round(rect?.height ?? 0)
|
|
577
|
-
};
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
function walkAll(roots, win) {
|
|
723
|
+
function walkAll(roots, win, styles, diagnostics, walker, captureRootIsBoundary = false) {
|
|
581
724
|
const state = {
|
|
582
725
|
nextId: 1,
|
|
583
726
|
entries: [],
|
|
584
|
-
win
|
|
727
|
+
win,
|
|
728
|
+
styles,
|
|
729
|
+
diagnostics,
|
|
730
|
+
walker
|
|
585
731
|
};
|
|
586
732
|
const kids = [];
|
|
587
733
|
for (const root of roots) {
|
|
588
|
-
const node = walk(root, state);
|
|
734
|
+
const node = walk(root, state, captureRootIsBoundary);
|
|
589
735
|
if (node) kids.push(node);
|
|
590
736
|
}
|
|
591
737
|
const raw = kids.length > 0 ? {
|
|
@@ -594,7 +740,7 @@ function walkAll(roots, win) {
|
|
|
594
740
|
} : { id: 0 };
|
|
595
741
|
const dropped = /* @__PURE__ */ new Set();
|
|
596
742
|
return {
|
|
597
|
-
tree: pruneRuns(raw, dropped),
|
|
743
|
+
tree: pruneRuns(raw, dropped, diagnostics),
|
|
598
744
|
entries: dropped.size > 0 ? state.entries.filter((entry) => !dropped.has(entry.id)) : state.entries
|
|
599
745
|
};
|
|
600
746
|
}
|
|
@@ -609,7 +755,7 @@ const RUN_SIMILARITY = .9;
|
|
|
609
755
|
* first items. The dropped ids are pruned from the entries so no rules are
|
|
610
756
|
* emitted for them.
|
|
611
757
|
*/
|
|
612
|
-
function pruneRuns(node, dropped) {
|
|
758
|
+
function pruneRuns(node, dropped, diagnostics) {
|
|
613
759
|
const kids = node.kids;
|
|
614
760
|
if (!kids || kids.length === 0) return node;
|
|
615
761
|
const pruned = [];
|
|
@@ -624,9 +770,10 @@ function pruneRuns(node, dropped) {
|
|
|
624
770
|
j++;
|
|
625
771
|
}
|
|
626
772
|
const keep = j - i > RUN_LIMIT ? RUN_KEEP : j - i;
|
|
773
|
+
if (j - i > keep) diagnostics.add("pruned-run", { count: j - i - keep });
|
|
627
774
|
for (let k = i; k < i + keep; k++) {
|
|
628
775
|
const kid = kids[k];
|
|
629
|
-
if (kid) pruned.push(pruneRuns(kid, dropped));
|
|
776
|
+
if (kid) pruned.push(pruneRuns(kid, dropped, diagnostics));
|
|
630
777
|
}
|
|
631
778
|
for (let k = i + keep; k < j; k++) {
|
|
632
779
|
const kid = kids[k];
|
|
@@ -643,20 +790,122 @@ function collectIds(node, out) {
|
|
|
643
790
|
out.add(node.id);
|
|
644
791
|
for (const kid of node.kids ?? []) collectIds(kid, out);
|
|
645
792
|
}
|
|
646
|
-
function walk(el, state) {
|
|
793
|
+
function walk(el, state, isCaptureRoot = false) {
|
|
647
794
|
const tag = el.tagName.toUpperCase();
|
|
648
|
-
if (SKIP_TAGS.has(tag))
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
795
|
+
if (SKIP_TAGS.has(tag)) {
|
|
796
|
+
state.diagnostics.add("dropped-tag");
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
const cs = state.styles.get(el);
|
|
800
|
+
if (cs.display === "none" || cs.visibility === "hidden" || cs.visibility === "collapse") {
|
|
801
|
+
state.diagnostics.add("dropped-tag");
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
if (!isCaptureRoot) {
|
|
805
|
+
const refName = el.getAttribute("data-xr-boundary") ?? el.getAttribute("data-xr-root");
|
|
806
|
+
if (refName) return {
|
|
807
|
+
id: state.nextId++,
|
|
808
|
+
ref: refName
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
const walker = state.walker;
|
|
812
|
+
if (walker?.element) {
|
|
813
|
+
const next = () => wrap(annotationWalk(el, cs, state));
|
|
814
|
+
const ctx = {
|
|
815
|
+
el,
|
|
816
|
+
ignore: () => wrap(ignoreElement(state)),
|
|
817
|
+
bone: (opts) => wrap(boneElement(el, cs, opts?.kind, state)),
|
|
818
|
+
walk: next
|
|
819
|
+
};
|
|
820
|
+
return unwrap(walker.element(ctx, next));
|
|
821
|
+
}
|
|
822
|
+
return annotationWalk(el, cs, state);
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* The built-in annotation walker (ADR 0018), the rung between the custom walker
|
|
826
|
+
* and the default classifier. It honors the capture-only `data-xr-*`
|
|
827
|
+
* annotations and otherwise falls through to the default classifier. The stitch
|
|
828
|
+
* guard already ran in `walk`, so a boundary element never reaches here — an
|
|
829
|
+
* annotation can never override a nested `<Skeleton>` (ADR 0006).
|
|
830
|
+
*/
|
|
831
|
+
function annotationWalk(el, cs, state) {
|
|
832
|
+
if (el.hasAttribute(XR_IGNORE_ATTR)) return ignoreElement(state);
|
|
833
|
+
const kindAttr = el.getAttribute(XR_BONE_KIND_ATTR);
|
|
834
|
+
if (kindAttr !== null) {
|
|
835
|
+
const kind = asLeafKind(kindAttr.trim());
|
|
836
|
+
if (kind !== null) return boneElement(el, cs, kind, state);
|
|
837
|
+
state.diagnostics.add("dropped-tag");
|
|
838
|
+
}
|
|
839
|
+
return defaultClassify(el, cs, state);
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* `ctx.ignore()` / `data-xr-ignore`: drop the element and its subtree. No Entry,
|
|
843
|
+
* no node, no descent — it contributes nothing to the Plate. Aggregated under
|
|
844
|
+
* the existing `dropped-tag` diagnostic (a hidden/non-visual drop is the same
|
|
845
|
+
* fidelity story; do not spam a new code per ignored element).
|
|
846
|
+
*/
|
|
847
|
+
function ignoreElement(state) {
|
|
848
|
+
state.diagnostics.add("dropped-tag");
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* `ctx.bone()` / `data-xr-bone-kind`: collapse the element's whole subtree into
|
|
853
|
+
* ONE Bone leaf, sized from the element's own measured box (ADR 0018). When
|
|
854
|
+
* `kind` is omitted it shares the default classifier's kind-inference heuristic
|
|
855
|
+
* (`inferLeafKind`, ONE code path — Q4). The subtree is NOT walked: this is the
|
|
856
|
+
* point of the helper. Reuses the default classifier's leaf sizing so the bone
|
|
857
|
+
* reserves the real space.
|
|
858
|
+
*/
|
|
859
|
+
function boneElement(el, cs, kind, state) {
|
|
860
|
+
const entry = {
|
|
653
861
|
id: state.nextId++,
|
|
654
|
-
|
|
862
|
+
el,
|
|
863
|
+
fallback: [],
|
|
864
|
+
cs
|
|
655
865
|
};
|
|
866
|
+
state.entries.push(entry);
|
|
867
|
+
const leaf = kind ?? inferLeafKind(el);
|
|
868
|
+
const rect = el.getBoundingClientRect();
|
|
869
|
+
entry.leaf = leaf;
|
|
870
|
+
if (isInlineish(cs.display)) {
|
|
871
|
+
entry.fallback.push("display: inline-block");
|
|
872
|
+
if (leaf === "media") entry.fallback.push("vertical-align: middle");
|
|
873
|
+
} else if (cs.display && cs.display !== "block") entry.fallback.push(`display: ${cs.display}`);
|
|
874
|
+
entry.sizeFallback = [
|
|
875
|
+
"box-sizing: border-box",
|
|
876
|
+
`width: ${px(rect.width)}`,
|
|
877
|
+
`height: ${px(rect.height)}`
|
|
878
|
+
];
|
|
879
|
+
return {
|
|
880
|
+
id: entry.id,
|
|
881
|
+
leaf
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* The kind-inference heuristic shared by `ctx.bone()` without an explicit kind
|
|
886
|
+
* and the default classifier (ADR 0018, Q4 — ONE code path). A media tag is
|
|
887
|
+
* `media`; anything else collapsed to a single bone reads as a `box`. The
|
|
888
|
+
* default classifier reaches the same outcomes through its own structural
|
|
889
|
+
* branches (MEDIA_TAGS -> media, contrasting fill / painted empty -> box), so
|
|
890
|
+
* keeping this one function is the single source of truth for "what kind is an
|
|
891
|
+
* element that has no children to descend into".
|
|
892
|
+
*/
|
|
893
|
+
function inferLeafKind(el) {
|
|
894
|
+
return MEDIA_TAGS.has(el.tagName.toUpperCase()) ? "media" : "box";
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* The DEFAULT classifier — the unchanged step-4 body, extracted so the
|
|
898
|
+
* precedence ladder's `next()` can call it (ADR 0018). With no walker and no
|
|
899
|
+
* annotations this is the only path taken, and its output is byte-identical to
|
|
900
|
+
* before the ladder was introduced.
|
|
901
|
+
*/
|
|
902
|
+
function defaultClassify(el, cs, state) {
|
|
903
|
+
const tag = el.tagName.toUpperCase();
|
|
656
904
|
const entry = {
|
|
657
905
|
id: state.nextId++,
|
|
658
906
|
el,
|
|
659
|
-
fallback: []
|
|
907
|
+
fallback: [],
|
|
908
|
+
cs
|
|
660
909
|
};
|
|
661
910
|
state.entries.push(entry);
|
|
662
911
|
const node = { id: entry.id };
|
|
@@ -668,7 +917,7 @@ function walk(el, state) {
|
|
|
668
917
|
let { width, height } = rect;
|
|
669
918
|
const parent = el.parentElement;
|
|
670
919
|
if ((width < 8 || height < 8) && parent && parent.childElementCount === 1) {
|
|
671
|
-
const pcs = state.
|
|
920
|
+
const pcs = state.styles.get(parent);
|
|
672
921
|
const prect = parent.getBoundingClientRect();
|
|
673
922
|
const pw = prect.width - pad(pcs.paddingLeft) - pad(pcs.paddingRight);
|
|
674
923
|
const ph = prect.height - pad(pcs.paddingTop) - pad(pcs.paddingBottom);
|
|
@@ -704,7 +953,7 @@ function walk(el, state) {
|
|
|
704
953
|
} else if (isTextNode(child)) textRun.push(child);
|
|
705
954
|
flushTextRun();
|
|
706
955
|
if (kids.length > 0) {
|
|
707
|
-
if (kids.every((kid) => kid.leaf !== void 0 && kid.kids === void 0) && hasContrastingFill(el, cs, state.win)) {
|
|
956
|
+
if (kids.every((kid) => kid.leaf !== void 0 && kid.kids === void 0) && hasContrastingFill(el, cs, state.win, state.styles)) {
|
|
708
957
|
state.entries.length = entriesBefore;
|
|
709
958
|
entry.leaf = "box";
|
|
710
959
|
node.leaf = "box";
|
|
@@ -750,14 +999,14 @@ function textBar(run, state) {
|
|
|
750
999
|
range.setStartBefore(first);
|
|
751
1000
|
range.setEndAfter(last);
|
|
752
1001
|
const rect = range.getBoundingClientRect();
|
|
753
|
-
const { width, height } = clampToClipping(rect.width, rect.height, first.parentElement, state.
|
|
1002
|
+
const { width, height } = clampToClipping(rect.width, rect.height, first.parentElement, { getComputedStyle: (el) => state.styles.get(el) });
|
|
754
1003
|
const entry = {
|
|
755
1004
|
id: state.nextId++,
|
|
756
1005
|
el: null,
|
|
757
1006
|
leaf: "text",
|
|
758
1007
|
fallback: ["display: inline-block", "vertical-align: middle"]
|
|
759
1008
|
};
|
|
760
|
-
if (width > 0 && height > 0) entry.sizeFallback = [`width: ${px(width)}`, `height: ${px(snapToLines(height, first.parentElement, state.
|
|
1009
|
+
if (width > 0 && height > 0) entry.sizeFallback = [`width: ${px(width)}`, `height: ${px(snapToLines(height, first.parentElement, state.styles))}`];
|
|
761
1010
|
state.entries.push(entry);
|
|
762
1011
|
return {
|
|
763
1012
|
id: entry.id,
|
|
@@ -773,9 +1022,9 @@ function textBar(run, state) {
|
|
|
773
1022
|
* A single line is left untouched — its line box comes from the parent's pinned
|
|
774
1023
|
* line-height, and the thin glyph bar reads as text rather than a fat block.
|
|
775
1024
|
*/
|
|
776
|
-
function snapToLines(height, parent,
|
|
1025
|
+
function snapToLines(height, parent, styles) {
|
|
777
1026
|
if (!parent) return height;
|
|
778
|
-
const lh = pxValue(
|
|
1027
|
+
const lh = pxValue(styles.get(parent).lineHeight);
|
|
779
1028
|
if (lh === null || lh <= 0 || height < lh * 1.5) return height;
|
|
780
1029
|
return Math.round(height / lh) * lh;
|
|
781
1030
|
}
|
|
@@ -801,12 +1050,12 @@ function clampToClipping(width, height, fromParent, win) {
|
|
|
801
1050
|
};
|
|
802
1051
|
}
|
|
803
1052
|
/** The capture-time measurements everything starts from, at the lowest precedence. */
|
|
804
|
-
function baseRules(entries,
|
|
1053
|
+
function baseRules(entries, styles) {
|
|
805
1054
|
const rules = [];
|
|
806
1055
|
let order = 0;
|
|
807
1056
|
const context = entries.find((e) => e.el)?.el?.parentElement;
|
|
808
1057
|
if (context) {
|
|
809
|
-
const cs =
|
|
1058
|
+
const cs = styles.get(context);
|
|
810
1059
|
rules.push({
|
|
811
1060
|
ids: [],
|
|
812
1061
|
decls: [
|
|
@@ -881,7 +1130,7 @@ function supportsCondition(win, condition) {
|
|
|
881
1130
|
}
|
|
882
1131
|
}
|
|
883
1132
|
/** Flatten every reachable author rule, tracking media + container conditions and layer membership. */
|
|
884
|
-
function collectStyleRules(doc, win) {
|
|
1133
|
+
function collectStyleRules(doc, win, diagnostics) {
|
|
885
1134
|
const out = [];
|
|
886
1135
|
const visit = (list, media, container, layered) => {
|
|
887
1136
|
if (!list) return;
|
|
@@ -913,10 +1162,16 @@ function collectStyleRules(doc, win) {
|
|
|
913
1162
|
visit(rule.cssRules, media, container, true);
|
|
914
1163
|
continue;
|
|
915
1164
|
}
|
|
916
|
-
if (isCssImportRule(rule))
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1165
|
+
if (isCssImportRule(rule)) {
|
|
1166
|
+
try {
|
|
1167
|
+
const importMedia = rule.media.mediaText;
|
|
1168
|
+
visit(rule.styleSheet?.cssRules, importMedia && importMedia !== "all" ? [...media, importMedia] : media, container, layered || importLayerName(rule) != null);
|
|
1169
|
+
} catch {
|
|
1170
|
+
diagnostics.add("unreadable-import");
|
|
1171
|
+
}
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1174
|
+
diagnostics.add("unsupported-rule");
|
|
920
1175
|
}
|
|
921
1176
|
};
|
|
922
1177
|
const sheets = [...Array.from(doc.styleSheets), ...doc.adoptedStyleSheets ?? []];
|
|
@@ -924,7 +1179,9 @@ function collectStyleRules(doc, win) {
|
|
|
924
1179
|
const sheetMedia = sheet.media.mediaText;
|
|
925
1180
|
try {
|
|
926
1181
|
visit(sheet.cssRules, sheetMedia && sheetMedia !== "all" ? [sheetMedia] : [], [], false);
|
|
927
|
-
} catch {
|
|
1182
|
+
} catch {
|
|
1183
|
+
diagnostics.add("unreadable-stylesheet");
|
|
1184
|
+
}
|
|
928
1185
|
}
|
|
929
1186
|
return out;
|
|
930
1187
|
}
|
|
@@ -935,10 +1192,10 @@ function safeMatches(el, selector) {
|
|
|
935
1192
|
return false;
|
|
936
1193
|
}
|
|
937
1194
|
}
|
|
938
|
-
function
|
|
1195
|
+
function pushSelector(map, key, sel) {
|
|
939
1196
|
const list = map.get(key);
|
|
940
|
-
if (list) list.push(
|
|
941
|
-
else map.set(key, [
|
|
1197
|
+
if (list) list.push(sel);
|
|
1198
|
+
else map.set(key, [sel]);
|
|
942
1199
|
}
|
|
943
1200
|
/** The rightmost compound of a selector — what an element must itself satisfy to match. */
|
|
944
1201
|
function rightmostCompound(selector) {
|
|
@@ -965,28 +1222,33 @@ function rightmostCompound(selector) {
|
|
|
965
1222
|
* near-linear one. Selectors keyed on nothing concrete (`*`, attribute-only)
|
|
966
1223
|
* fall back to a universal bucket tested against every element.
|
|
967
1224
|
*/
|
|
968
|
-
function indexRules(doc, win) {
|
|
969
|
-
const
|
|
1225
|
+
function indexRules(doc, win, diagnostics) {
|
|
1226
|
+
const index = {
|
|
970
1227
|
byId: /* @__PURE__ */ new Map(),
|
|
971
1228
|
byClass: /* @__PURE__ */ new Map(),
|
|
972
1229
|
byTag: /* @__PURE__ */ new Map(),
|
|
973
1230
|
universal: []
|
|
974
1231
|
};
|
|
975
1232
|
let order = 1e3;
|
|
976
|
-
for (const found of collectStyleRules(doc, win)) {
|
|
1233
|
+
for (const found of collectStyleRules(doc, win, diagnostics)) {
|
|
977
1234
|
order++;
|
|
978
1235
|
for (const selector of splitSelectorList(found.rule.selectorText)) {
|
|
979
|
-
if (
|
|
980
|
-
|
|
1236
|
+
if (PSEUDO_ELEMENT.test(selector)) {
|
|
1237
|
+
diagnostics.add("skipped-pseudo-element");
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
if (DYNAMIC_PSEUDO.test(selector)) {
|
|
1241
|
+
diagnostics.add("skipped-dynamic-pseudo");
|
|
1242
|
+
continue;
|
|
1243
|
+
}
|
|
1244
|
+
const sel = {
|
|
981
1245
|
rule: found.rule,
|
|
982
1246
|
selector,
|
|
983
1247
|
media: found.media,
|
|
984
1248
|
container: found.container,
|
|
985
1249
|
layered: found.layered,
|
|
986
1250
|
spec: specificity(selector),
|
|
987
|
-
order
|
|
988
|
-
ids: [],
|
|
989
|
-
decls: null
|
|
1251
|
+
order
|
|
990
1252
|
};
|
|
991
1253
|
const compound = rightmostCompound(selector).replace(/:{1,2}[\w-]+\([^()]*\)/g, "");
|
|
992
1254
|
const id = /#(-?[A-Za-z_][\w-]*)/.exec(compound);
|
|
@@ -995,22 +1257,34 @@ function indexRules(doc, win) {
|
|
|
995
1257
|
const idName = id?.[1];
|
|
996
1258
|
const className = cls?.[1];
|
|
997
1259
|
const tagName = tag?.[1];
|
|
998
|
-
if (idName)
|
|
999
|
-
else if (className)
|
|
1000
|
-
else if (tagName)
|
|
1001
|
-
else
|
|
1260
|
+
if (idName) pushSelector(index.byId, idName, sel);
|
|
1261
|
+
else if (className) pushSelector(index.byClass, className, sel);
|
|
1262
|
+
else if (tagName) pushSelector(index.byTag, tagName.toLowerCase(), sel);
|
|
1263
|
+
else index.universal.push(sel);
|
|
1002
1264
|
}
|
|
1003
1265
|
}
|
|
1004
|
-
return
|
|
1266
|
+
return index;
|
|
1005
1267
|
}
|
|
1006
1268
|
function hasInlineStyle(el) {
|
|
1007
1269
|
return "style" in el && typeof el.style === "object" && el.style !== null && "length" in el.style && "getPropertyValue" in el.style && "getPropertyPriority" in el.style;
|
|
1008
1270
|
}
|
|
1009
1271
|
/** Lift matching author rules, rewritten to plate-local ids (ADR 0005). */
|
|
1010
|
-
function copyRules(entries, doc, win) {
|
|
1011
|
-
const rules = baseRules(entries,
|
|
1272
|
+
function copyRules(entries, doc, win, styles, diagnostics) {
|
|
1273
|
+
const rules = baseRules(entries, styles);
|
|
1012
1274
|
const elEntries = entries.filter((e) => e.el !== null);
|
|
1013
|
-
const
|
|
1275
|
+
const index = indexRules(doc, win, diagnostics);
|
|
1276
|
+
const matches = /* @__PURE__ */ new Map();
|
|
1277
|
+
const matchOf = (sel) => {
|
|
1278
|
+
let m = matches.get(sel);
|
|
1279
|
+
if (!m) {
|
|
1280
|
+
m = {
|
|
1281
|
+
ids: [],
|
|
1282
|
+
decls: null
|
|
1283
|
+
};
|
|
1284
|
+
matches.set(sel, m);
|
|
1285
|
+
}
|
|
1286
|
+
return m;
|
|
1287
|
+
};
|
|
1014
1288
|
const seen = /* @__PURE__ */ new Set();
|
|
1015
1289
|
for (const entry of elEntries) {
|
|
1016
1290
|
const el = entry.el;
|
|
@@ -1018,31 +1292,33 @@ function copyRules(entries, doc, win) {
|
|
|
1018
1292
|
const candidates = [];
|
|
1019
1293
|
const collect = (list) => {
|
|
1020
1294
|
if (!list) return;
|
|
1021
|
-
for (const
|
|
1022
|
-
seen.add(
|
|
1023
|
-
candidates.push(
|
|
1295
|
+
for (const sel of list) if (!seen.has(sel)) {
|
|
1296
|
+
seen.add(sel);
|
|
1297
|
+
candidates.push(sel);
|
|
1024
1298
|
}
|
|
1025
1299
|
};
|
|
1026
|
-
if (el.id) collect(
|
|
1027
|
-
for (const cls of el.classList) collect(
|
|
1028
|
-
collect(
|
|
1029
|
-
collect(
|
|
1030
|
-
for (const
|
|
1031
|
-
if (!safeMatches(el,
|
|
1032
|
-
|
|
1033
|
-
if (
|
|
1300
|
+
if (el.id) collect(index.byId.get(el.id));
|
|
1301
|
+
for (const cls of el.classList) collect(index.byClass.get(cls));
|
|
1302
|
+
collect(index.byTag.get(el.tagName.toLowerCase()));
|
|
1303
|
+
collect(index.universal);
|
|
1304
|
+
for (const sel of candidates) {
|
|
1305
|
+
if (!safeMatches(el, sel.selector)) continue;
|
|
1306
|
+
const m = matchOf(sel);
|
|
1307
|
+
if (m.decls === null) m.decls = filterLayoutDecls(sel.rule.style, entry.cs ?? styles.get(el));
|
|
1308
|
+
if (m.decls.length > 0) m.ids.push(entry.id);
|
|
1034
1309
|
}
|
|
1035
1310
|
}
|
|
1036
|
-
for (const
|
|
1037
|
-
|
|
1311
|
+
for (const sel of allSelectors(index)) {
|
|
1312
|
+
const m = matches.get(sel);
|
|
1313
|
+
if (!m || m.ids.length === 0 || !m.decls || m.decls.length === 0) continue;
|
|
1038
1314
|
rules.push({
|
|
1039
|
-
ids:
|
|
1040
|
-
decls:
|
|
1041
|
-
media:
|
|
1042
|
-
...
|
|
1043
|
-
layered:
|
|
1044
|
-
spec:
|
|
1045
|
-
order:
|
|
1315
|
+
ids: m.ids,
|
|
1316
|
+
decls: m.decls,
|
|
1317
|
+
media: sel.media,
|
|
1318
|
+
...sel.container.length > 0 ? { container: sel.container } : {},
|
|
1319
|
+
layered: sel.layered,
|
|
1320
|
+
spec: sel.spec,
|
|
1321
|
+
order: sel.order
|
|
1046
1322
|
});
|
|
1047
1323
|
}
|
|
1048
1324
|
let order = 1e7;
|
|
@@ -1050,7 +1326,7 @@ function copyRules(entries, doc, win) {
|
|
|
1050
1326
|
if (!hasInlineStyle(entry.el)) continue;
|
|
1051
1327
|
const style = entry.el.style;
|
|
1052
1328
|
if (!style || style.length === 0) continue;
|
|
1053
|
-
const decls = filterLayoutDecls(style,
|
|
1329
|
+
const decls = filterLayoutDecls(style, entry.cs ?? styles.get(entry.el));
|
|
1054
1330
|
if (decls.length === 0) continue;
|
|
1055
1331
|
rules.push({
|
|
1056
1332
|
ids: [entry.id],
|
|
@@ -1061,11 +1337,57 @@ function copyRules(entries, doc, win) {
|
|
|
1061
1337
|
}
|
|
1062
1338
|
return rules;
|
|
1063
1339
|
}
|
|
1064
|
-
function*
|
|
1065
|
-
for (const list of
|
|
1066
|
-
for (const list of
|
|
1067
|
-
for (const list of
|
|
1068
|
-
yield*
|
|
1340
|
+
function* allSelectors(index) {
|
|
1341
|
+
for (const list of index.byId.values()) yield* list;
|
|
1342
|
+
for (const list of index.byClass.values()) yield* list;
|
|
1343
|
+
for (const list of index.byTag.values()) yield* list;
|
|
1344
|
+
yield* index.universal;
|
|
1345
|
+
}
|
|
1346
|
+
//#endregion
|
|
1347
|
+
//#region src/breakpoints.ts
|
|
1348
|
+
/**
|
|
1349
|
+
* Per-plate breakpoint derivation (ADR 0004): the width thresholds that could
|
|
1350
|
+
* change this plate come from the `@media` conditions on its own copied rules
|
|
1351
|
+
* plus the queries the app evaluated through `matchMedia` during capture.
|
|
1352
|
+
* Thresholds partition the width axis into views; identical captures in
|
|
1353
|
+
* adjacent views collapse at merge time.
|
|
1354
|
+
*/
|
|
1355
|
+
const FONT_SIZE_PX = 16;
|
|
1356
|
+
/** A breakpoint is the first integer px width of the view ABOVE the boundary. */
|
|
1357
|
+
function thresholdsOf(condition) {
|
|
1358
|
+
const out = [];
|
|
1359
|
+
const toPx = (value, unit) => Number.parseFloat(value) * (unit === "px" ? 1 : FONT_SIZE_PX);
|
|
1360
|
+
const push = (raw, exclusiveBelow) => {
|
|
1361
|
+
const bp = exclusiveBelow ? Number.isInteger(raw) ? raw + 1 : Math.ceil(raw) : Math.ceil(raw);
|
|
1362
|
+
if (Number.isFinite(bp) && bp > 0) out.push(bp);
|
|
1363
|
+
};
|
|
1364
|
+
for (const m of condition.matchAll(/min-width:\s*([\d.]+)(px|em|rem)/g)) push(toPx(m[1] ?? "", m[2] ?? "px"), false);
|
|
1365
|
+
for (const m of condition.matchAll(/max-width:\s*([\d.]+)(px|em|rem)/g)) push(toPx(m[1] ?? "", m[2] ?? "px"), true);
|
|
1366
|
+
for (const m of condition.matchAll(/width\s*(>=?)\s*([\d.]+)(px|em|rem)/g)) push(toPx(m[2] ?? "", m[3] ?? "px"), m[1] === ">");
|
|
1367
|
+
for (const m of condition.matchAll(/width\s*(<=?)\s*([\d.]+)(px|em|rem)/g)) push(toPx(m[2] ?? "", m[3] ?? "px"), m[1] === "<=");
|
|
1368
|
+
for (const m of condition.matchAll(/([\d.]+)(px|em|rem)\s*(<=?)\s*width/g)) push(toPx(m[1] ?? "", m[2] ?? "px"), m[3] === "<");
|
|
1369
|
+
return out;
|
|
1370
|
+
}
|
|
1371
|
+
/** Width thresholds for this plate: its rules' media conditions + the app's matchMedia queries. */
|
|
1372
|
+
function deriveBreakpoints(rules, matchMediaQueries = []) {
|
|
1373
|
+
const set = /* @__PURE__ */ new Set();
|
|
1374
|
+
for (const rule of rules) for (const condition of rule.media ?? []) for (const bp of thresholdsOf(condition)) set.add(bp);
|
|
1375
|
+
for (const query of matchMediaQueries) for (const bp of thresholdsOf(query)) set.add(bp);
|
|
1376
|
+
return [...set].toSorted((a, b) => a - b);
|
|
1377
|
+
}
|
|
1378
|
+
/** The view interval `[min, max)` a given viewport width falls into. */
|
|
1379
|
+
function regimeFor(width, breakpoints) {
|
|
1380
|
+
let min;
|
|
1381
|
+
let max;
|
|
1382
|
+
for (const bp of breakpoints) if (bp <= width) min = bp;
|
|
1383
|
+
else {
|
|
1384
|
+
max = bp;
|
|
1385
|
+
break;
|
|
1386
|
+
}
|
|
1387
|
+
return {
|
|
1388
|
+
...min === void 0 ? {} : { min },
|
|
1389
|
+
...max === void 0 ? {} : { max }
|
|
1390
|
+
};
|
|
1069
1391
|
}
|
|
1070
1392
|
//#endregion
|
|
1071
|
-
export {
|
|
1393
|
+
export { captureRegime as a, formatDiagnostic as c, capture as i, makeDiagnostic as l, regimeFor as n, defineXrayCaptureWalker as o, CaptureTooLargeError as r, diagnosticsHeader as s, deriveBreakpoints as t, classify as u };
|