@pilates/core 1.0.0-rc.1 → 1.0.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/LICENSE +1 -1
- package/README.md +14 -8
- package/dist/algorithm/axis.d.ts +13 -0
- package/dist/algorithm/axis.d.ts.map +1 -1
- package/dist/algorithm/axis.js +24 -0
- package/dist/algorithm/axis.js.map +1 -1
- package/dist/algorithm/cache.d.ts +225 -0
- package/dist/algorithm/cache.d.ts.map +1 -0
- package/dist/algorithm/cache.js +312 -0
- package/dist/algorithm/cache.js.map +1 -0
- package/dist/algorithm/index.d.ts +7 -0
- package/dist/algorithm/index.d.ts.map +1 -1
- package/dist/algorithm/index.js +128 -4
- package/dist/algorithm/index.js.map +1 -1
- package/dist/algorithm/main-axis.d.ts +1 -1
- package/dist/algorithm/main-axis.d.ts.map +1 -1
- package/dist/algorithm/main-axis.js +253 -58
- package/dist/algorithm/main-axis.js.map +1 -1
- package/dist/algorithm/round.d.ts +17 -0
- package/dist/algorithm/round.d.ts.map +1 -1
- package/dist/algorithm/round.js +42 -4
- package/dist/algorithm/round.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/layout.d.ts +8 -0
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +1 -1
- package/dist/layout.js.map +1 -1
- package/dist/node.d.ts +179 -6
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +292 -37
- package/dist/node.js.map +1 -1
- package/dist/style.d.ts +20 -1
- package/dist/style.d.ts.map +1 -1
- package/dist/style.js +5 -1
- package/dist/style.js.map +1 -1
- package/package.json +6 -6
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/pilatesjs/pilates/main/assets/logo-dark.svg">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/pilatesjs/pilates/main/assets/logo.svg" alt="pilates" width="48">
|
|
4
|
+
</picture>
|
|
5
|
+
|
|
1
6
|
# @pilates/core
|
|
2
7
|
|
|
3
8
|
> Headless flex layout engine for terminal UIs. Imperative `Node` API, integer cell
|
|
4
9
|
> coordinates, terminal-correct text measurement. **Pure TypeScript, zero runtime
|
|
5
10
|
> dependencies.**
|
|
6
11
|
|
|
7
|
-
`@pilates/core` is
|
|
8
|
-
the terminal (integer cells, CJK / emoji / wide-char awareness, ANSI
|
|
9
|
-
passthrough),
|
|
10
|
-
in React, Vue, Svelte, or anything else.
|
|
12
|
+
`@pilates/core` is the engine of **Pilates** — a flex layout algorithm built
|
|
13
|
+
for the terminal (integer cells, CJK / emoji / wide-char awareness, ANSI
|
|
14
|
+
escape passthrough), unbundled from any UI framework. Use it directly, or wrap
|
|
15
|
+
it in React, Vue, Svelte, or anything else.
|
|
11
16
|
|
|
12
17
|
## Install
|
|
13
18
|
|
|
@@ -42,12 +47,12 @@ sidebar.getComputedLayout(); // { left:58, top:1, width:20, height:22 }
|
|
|
42
47
|
|
|
43
48
|
## Style API
|
|
44
49
|
|
|
45
|
-
Setters mirror
|
|
50
|
+
Setters mirror CSS Flexbox semantics. All values are in terminal cells.
|
|
46
51
|
|
|
47
52
|
| Category | Setters |
|
|
48
53
|
|---|---|
|
|
49
54
|
| Direction | `setFlexDirection`, `setFlexWrap` |
|
|
50
|
-
| Sizing | `setWidth`, `setHeight`, `setMinWidth`, `setMinHeight`, `setMaxWidth`, `setMaxHeight` |
|
|
55
|
+
| Sizing | `setWidth`, `setHeight`, `setMinWidth`, `setMinHeight`, `setMaxWidth`, `setMaxHeight`, `setAspectRatio` |
|
|
51
56
|
| Flex | `setFlex` (shorthand), `setFlexGrow`, `setFlexShrink`, `setFlexBasis` |
|
|
52
57
|
| Spacing | `setPadding(edge, n)`, `setMargin(edge, n)`, `setGap('row' \| 'column', n)` |
|
|
53
58
|
| Alignment | `setJustifyContent`, `setAlignItems`, `setAlignSelf`, `setAlignContent` |
|
|
@@ -88,9 +93,10 @@ text.setMeasureFunc((width, widthMode, height, heightMode) => {
|
|
|
88
93
|
## Status
|
|
89
94
|
|
|
90
95
|
Release candidate (`1.0.0-rc.1`). The full v1 surface is implemented and
|
|
91
|
-
|
|
96
|
+
validated cell-for-cell against a reference WASM flexbox implementation
|
|
97
|
+
across 30 oracle fixtures.
|
|
92
98
|
|
|
93
|
-
**Out of v1:**
|
|
99
|
+
**Out of v1:** RTL/LTR direction inheritance, baseline alignment.
|
|
94
100
|
|
|
95
101
|
## License
|
|
96
102
|
|
package/dist/algorithm/axis.d.ts
CHANGED
|
@@ -34,6 +34,19 @@ export declare function readEnd(box: readonly number[], axis: Axis): number;
|
|
|
34
34
|
export declare function gapAlong(style: Style, axis: Axis): number;
|
|
35
35
|
/** The style's preferred size along an axis (`width` for row, `height` for column). */
|
|
36
36
|
export declare function preferredSize(style: Style, axis: Axis): number | 'auto';
|
|
37
|
+
/**
|
|
38
|
+
* Like `preferredSize`, but additionally derives the size from `aspectRatio`
|
|
39
|
+
* when this axis is `'auto'` and the perpendicular axis is an explicit number:
|
|
40
|
+
* width = height * aspectRatio (axis === 'row')
|
|
41
|
+
* height = width / aspectRatio (axis === 'column')
|
|
42
|
+
*
|
|
43
|
+
* If both axes are explicit, the explicit value on this axis wins (the ratio
|
|
44
|
+
* is treated as a hint, per the CSS aspect-ratio spec). If both are `'auto'`,
|
|
45
|
+
* we have nothing to derive from and return `'auto'`. The result is NOT
|
|
46
|
+
* clamped — callers are expected to feed it through `clampSize` so min/max
|
|
47
|
+
* still bind on each axis.
|
|
48
|
+
*/
|
|
49
|
+
export declare function effectivePreferredSize(style: Style, axis: Axis): number | 'auto';
|
|
37
50
|
export declare function minSize(style: Style, axis: Axis): number;
|
|
38
51
|
export declare function maxSize(style: Style, axis: Axis): number | undefined;
|
|
39
52
|
/** Clamp a candidate size to the [minSize, maxSize] range for the given axis. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"axis.d.ts","sourceRoot":"","sources":["../../src/algorithm/axis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAExD,MAAM,MAAM,IAAI,GAAG,KAAK,GAAG,QAAQ,CAAC;AAOpC,wBAAgB,QAAQ,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,CAE/C;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,CAEhD;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,aAAa,GAAG,OAAO,CAEnD;AAED,qDAAqD;AACrD,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE5C;AAED,mDAAmD;AACnD,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE1C;AAED,iEAAiE;AACjE,wBAAgB,SAAS,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAEpE;AAED,+DAA+D;AAC/D,wBAAgB,OAAO,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAElE;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAEzD;AAED,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAEvE;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAExD;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAEpE;AAED,iFAAiF;AACjF,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMzE"}
|
|
1
|
+
{"version":3,"file":"axis.d.ts","sourceRoot":"","sources":["../../src/algorithm/axis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAExD,MAAM,MAAM,IAAI,GAAG,KAAK,GAAG,QAAQ,CAAC;AAOpC,wBAAgB,QAAQ,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,CAE/C;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,CAEhD;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,aAAa,GAAG,OAAO,CAEnD;AAED,qDAAqD;AACrD,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE5C;AAED,mDAAmD;AACnD,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE1C;AAED,iEAAiE;AACjE,wBAAgB,SAAS,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAEpE;AAED,+DAA+D;AAC/D,wBAAgB,OAAO,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAElE;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAEzD;AAED,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAEvE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAQhF;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAExD;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAEpE;AAED,iFAAiF;AACjF,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMzE"}
|
package/dist/algorithm/axis.js
CHANGED
|
@@ -54,6 +54,30 @@ export function gapAlong(style, axis) {
|
|
|
54
54
|
export function preferredSize(style, axis) {
|
|
55
55
|
return axis === 'row' ? style.width : style.height;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Like `preferredSize`, but additionally derives the size from `aspectRatio`
|
|
59
|
+
* when this axis is `'auto'` and the perpendicular axis is an explicit number:
|
|
60
|
+
* width = height * aspectRatio (axis === 'row')
|
|
61
|
+
* height = width / aspectRatio (axis === 'column')
|
|
62
|
+
*
|
|
63
|
+
* If both axes are explicit, the explicit value on this axis wins (the ratio
|
|
64
|
+
* is treated as a hint, per the CSS aspect-ratio spec). If both are `'auto'`,
|
|
65
|
+
* we have nothing to derive from and return `'auto'`. The result is NOT
|
|
66
|
+
* clamped — callers are expected to feed it through `clampSize` so min/max
|
|
67
|
+
* still bind on each axis.
|
|
68
|
+
*/
|
|
69
|
+
export function effectivePreferredSize(style, axis) {
|
|
70
|
+
const s = preferredSize(style, axis);
|
|
71
|
+
if (typeof s === 'number')
|
|
72
|
+
return s;
|
|
73
|
+
const ratio = style.aspectRatio;
|
|
74
|
+
if (ratio === undefined)
|
|
75
|
+
return 'auto';
|
|
76
|
+
const other = preferredSize(style, axis === 'row' ? 'column' : 'row');
|
|
77
|
+
if (typeof other !== 'number')
|
|
78
|
+
return 'auto';
|
|
79
|
+
return axis === 'row' ? other * ratio : other / ratio;
|
|
80
|
+
}
|
|
57
81
|
export function minSize(style, axis) {
|
|
58
82
|
return axis === 'row' ? style.minWidth : style.minHeight;
|
|
59
83
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"axis.js","sourceRoot":"","sources":["../../src/algorithm/axis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,MAAM,GAAG,GAAG,CAAC,CAAC;AACd,MAAM,KAAK,GAAG,CAAC,CAAC;AAChB,MAAM,MAAM,GAAG,CAAC,CAAC;AACjB,MAAM,IAAI,GAAG,CAAC,CAAC;AAEf,MAAM,UAAU,QAAQ,CAAC,CAAgB;IACvC,OAAO,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,CAAgB;IACxC,OAAO,QAAQ,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,CAAgB;IACxC,OAAO,CAAC,KAAK,aAAa,IAAI,CAAC,KAAK,gBAAgB,CAAC;AACvD,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,SAAS,CAAC,IAAU;IAClC,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;AACrC,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,OAAO,CAAC,IAAU;IAChC,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;AACzC,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,SAAS,CAAC,GAAsB,EAAE,IAAU;IAC1D,OAAO,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,OAAO,CAAC,GAAsB,EAAE,IAAU;IACxD,OAAO,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;AACjC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAY,EAAE,IAAU;IAC/C,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;AACzD,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,aAAa,CAAC,KAAY,EAAE,IAAU;IACpD,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAY,EAAE,IAAU;IAC9C,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAY,EAAE,IAAU;IAC9C,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC;AAC3D,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,SAAS,CAAC,KAAY,EAAE,IAAU,EAAE,KAAa;IAC/D,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACjC,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACjC,IAAI,CAAC,GAAG,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;IAClC,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,GAAG,GAAG;QAAE,CAAC,GAAG,GAAG,CAAC;IAC1C,OAAO,CAAC,CAAC;AACX,CAAC"}
|
|
1
|
+
{"version":3,"file":"axis.js","sourceRoot":"","sources":["../../src/algorithm/axis.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout-engine caches.
|
|
3
|
+
*
|
|
4
|
+
* Phase 1: `MeasureCache` — caches the result of a leaf node's
|
|
5
|
+
* `MeasureFunc` so repeated calls with the same available-space
|
|
6
|
+
* inputs don't reinvoke the measurer. Wired into every measure-func
|
|
7
|
+
* call site in `main-axis.ts` via the `callMeasureFunc` helper.
|
|
8
|
+
*
|
|
9
|
+
* Caches are owned by `Node` (`_measureCache` field) and cleared
|
|
10
|
+
* automatically by `markDirty()`. Consumers never interact with the
|
|
11
|
+
* cache directly — it is `@internal`.
|
|
12
|
+
*
|
|
13
|
+
* Phase 2 added `LayoutCache` here (P2-T1).
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
import type { ComputedLayout } from '../layout.js';
|
|
18
|
+
import type { MeasureMode } from '../measure-func.js';
|
|
19
|
+
import type { Node } from '../node.js';
|
|
20
|
+
/** @internal */
|
|
21
|
+
export interface MeasureCacheKey {
|
|
22
|
+
availableWidth: number;
|
|
23
|
+
widthMode: MeasureMode;
|
|
24
|
+
availableHeight: number;
|
|
25
|
+
heightMode: MeasureMode;
|
|
26
|
+
}
|
|
27
|
+
/** @internal */
|
|
28
|
+
export interface MeasureCacheValue {
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Up to eight slots — matches Yoga's `LayoutResults::MaxCachedMeasurements`
|
|
34
|
+
* with the documented rationale "98% of analyzed layouts require less
|
|
35
|
+
* than 8 entries" (see facebook/yoga `node/LayoutResults.h`). Covers the
|
|
36
|
+
* hypothetical-vs-final pattern (single leaf measured twice per pass)
|
|
37
|
+
* plus broader reuse patterns where the same leaf is measured at multiple
|
|
38
|
+
* cross-axis sizes during line packing or across consecutive layout calls.
|
|
39
|
+
*
|
|
40
|
+
* Linear scan over 8 slots is a few-cycle hot-path cost; slots are
|
|
41
|
+
* lazy-allocated only on leaves with a `MeasureFunc` installed.
|
|
42
|
+
*
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
export declare class MeasureCache {
|
|
46
|
+
private static readonly MAX_ENTRIES;
|
|
47
|
+
private slots;
|
|
48
|
+
/**
|
|
49
|
+
* Hit/miss counters for diagnostics (bench output, debugging). Always
|
|
50
|
+
* on — the spec originally proposed `__DEV__`-gating these, but the
|
|
51
|
+
* project has no build-tooling `__DEV__` define and the counters cost
|
|
52
|
+
* two property writes per `lookup()` (negligible at any realistic
|
|
53
|
+
* call rate). Counts are incremented only by `lookup()`; `store()`
|
|
54
|
+
* assumes `lookup()` has already been called for the same key on the
|
|
55
|
+
* same call site, so it does not double-count.
|
|
56
|
+
*
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
hits: number;
|
|
60
|
+
/** See {@link hits}. @internal */
|
|
61
|
+
misses: number;
|
|
62
|
+
lookup(key: MeasureCacheKey): MeasureCacheValue | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Store `value` for `key`. If `key` is already present, overwrite in
|
|
65
|
+
* place without growing the slot array; otherwise append, evicting
|
|
66
|
+
* the oldest slot when the array is at `MAX_ENTRIES` capacity.
|
|
67
|
+
*
|
|
68
|
+
* Callers should always invoke `lookup(key)` first and only call
|
|
69
|
+
* `store()` on a miss — the `misses` counter is incremented by
|
|
70
|
+
* `lookup()`, not here, so calling `store()` without a prior lookup
|
|
71
|
+
* would cause silent miss undercounting in diagnostics.
|
|
72
|
+
*/
|
|
73
|
+
store(key: MeasureCacheKey, value: MeasureCacheValue): void;
|
|
74
|
+
/**
|
|
75
|
+
* Drop every cached entry. The `hits`/`misses` counters are
|
|
76
|
+
* deliberately preserved across `clear()` calls — they're lifetime
|
|
77
|
+
* diagnostics, not per-pass metrics, and zeroing them would mask the
|
|
78
|
+
* total-pressure picture during bench runs.
|
|
79
|
+
*/
|
|
80
|
+
clear(): void;
|
|
81
|
+
}
|
|
82
|
+
/** @internal */
|
|
83
|
+
export interface LayoutCacheKey {
|
|
84
|
+
availableWidth: number;
|
|
85
|
+
widthMode: MeasureMode;
|
|
86
|
+
availableHeight: number;
|
|
87
|
+
heightMode: MeasureMode;
|
|
88
|
+
}
|
|
89
|
+
/** @internal */
|
|
90
|
+
export interface CachedChildLayout {
|
|
91
|
+
left: number;
|
|
92
|
+
top: number;
|
|
93
|
+
width: number;
|
|
94
|
+
height: number;
|
|
95
|
+
scrollWidth: number;
|
|
96
|
+
scrollHeight: number;
|
|
97
|
+
/**
|
|
98
|
+
* Pre-rounding (float) left position of this child, captured before
|
|
99
|
+
* `roundLayout` converts positions to integers. Used by
|
|
100
|
+
* `roundLayoutSubtree` to compute the correct float absolute coordinate
|
|
101
|
+
* when re-laying out a dirty boundary node under a cache-hit root.
|
|
102
|
+
* See `Node._floatLeft` for the full explanation.
|
|
103
|
+
*/
|
|
104
|
+
floatLeft: number;
|
|
105
|
+
/** See {@link floatLeft}. */
|
|
106
|
+
floatTop: number;
|
|
107
|
+
}
|
|
108
|
+
/** @internal */
|
|
109
|
+
export interface LayoutCacheValue {
|
|
110
|
+
width: number;
|
|
111
|
+
height: number;
|
|
112
|
+
scrollWidth: number;
|
|
113
|
+
scrollHeight: number;
|
|
114
|
+
childLayouts: CachedChildLayout[];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Single-slot per-node layout cache. Matches Yoga's `cachedLayout`
|
|
118
|
+
* (single overwrite-on-write) and Taffy's 1-slot layout-cache. Internal
|
|
119
|
+
* nodes' final-pass keys converge to one stable input once the parent's
|
|
120
|
+
* flex distribution has settled, so additional slots would be dead memory.
|
|
121
|
+
*
|
|
122
|
+
* Lazy-allocated by the algorithm in `algorithm/main-axis.ts` and
|
|
123
|
+
* `algorithm/index.ts` only on nodes that actually go through the
|
|
124
|
+
* `layoutChildren` recursion. Cleared by `Node.markDirty()` (which fires
|
|
125
|
+
* on every style/tree mutation).
|
|
126
|
+
*
|
|
127
|
+
* Hit/miss counters are always-on (same rationale as `MeasureCache`).
|
|
128
|
+
*
|
|
129
|
+
* @internal
|
|
130
|
+
*/
|
|
131
|
+
export declare class LayoutCache {
|
|
132
|
+
private slot;
|
|
133
|
+
/** @internal */
|
|
134
|
+
hits: number;
|
|
135
|
+
/** @internal */
|
|
136
|
+
misses: number;
|
|
137
|
+
lookup(key: LayoutCacheKey): LayoutCacheValue | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* Store `value` for `key`. Single-slot — replaces the previous entry
|
|
140
|
+
* unconditionally. Caller must construct a fresh `LayoutCacheValue` per
|
|
141
|
+
* store; this method does NOT deep-clone for performance, so any
|
|
142
|
+
* subsequent mutation of `value` is visible through `lookup`. The
|
|
143
|
+
* algorithm builds new values per layout pass so the contract holds.
|
|
144
|
+
*/
|
|
145
|
+
store(key: LayoutCacheKey, value: LayoutCacheValue): void;
|
|
146
|
+
clear(): void;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Pre-order traversal returning a flat array of layout snapshots, one per
|
|
150
|
+
* node. Used by differential mode to capture and compare the entire tree's
|
|
151
|
+
* layout state cheaply.
|
|
152
|
+
*
|
|
153
|
+
* Captures only the six `ComputedLayout` fields. `_dirty` flags, cache
|
|
154
|
+
* contents, and other ancillary state are NOT in scope. Phase 2's
|
|
155
|
+
* layout-cache work should add `_dirty` validation to the harness if
|
|
156
|
+
* dirty-flag semantics ever become load-bearing for cache correctness.
|
|
157
|
+
*
|
|
158
|
+
* @internal
|
|
159
|
+
*/
|
|
160
|
+
export declare function snapshotTreeLayouts(root: Node): ComputedLayout[];
|
|
161
|
+
/**
|
|
162
|
+
* Recursively clear `_measureCache` and `_layoutCache` on every node in
|
|
163
|
+
* the subtree. Used by differential mode and the fuzzer to force the cold
|
|
164
|
+
* path.
|
|
165
|
+
*
|
|
166
|
+
* @internal
|
|
167
|
+
*/
|
|
168
|
+
export declare function clearAllCaches(root: Node): void;
|
|
169
|
+
/**
|
|
170
|
+
* Mark every node in the subtree dirty. Used after `clearAllCaches` to
|
|
171
|
+
* force `calculateLayout` down the full cold path.
|
|
172
|
+
*
|
|
173
|
+
* Uses `_forceDirty()` rather than `markDirty()` so the propagation
|
|
174
|
+
* walks through layout boundaries (Phase 3). Differential-mode and
|
|
175
|
+
* fuzzer validation rely on dirtying the entire tree to force the
|
|
176
|
+
* cold path; without this, boundaries would short-circuit the walk
|
|
177
|
+
* and leave clean ancestors that would never re-run the algorithm.
|
|
178
|
+
*
|
|
179
|
+
* Note: `Node._forceDirty()` also calls `_measureCache?.clear()` and
|
|
180
|
+
* `_layoutCache?.clear()` as a side-effect of dirtying. So calling
|
|
181
|
+
* `clearAllCaches(root)` then `markDirtyDeep(root)` clears each node's
|
|
182
|
+
* caches twice. The second clear is harmless (no-op on an empty
|
|
183
|
+
* structure), but differential-mode callers should know the
|
|
184
|
+
* redundancy is intentional — we want both invariants explicit at the
|
|
185
|
+
* call site.
|
|
186
|
+
*
|
|
187
|
+
* @internal
|
|
188
|
+
*/
|
|
189
|
+
export declare function markDirtyDeep(root: Node): void;
|
|
190
|
+
/**
|
|
191
|
+
* Build a `LayoutCacheValue` from `node`'s current `_layout` + its direct
|
|
192
|
+
* children's `_layout`. Captures only direct children; deeper descendants
|
|
193
|
+
* are reconstituted via their own caches during `restoreFromCache`.
|
|
194
|
+
*
|
|
195
|
+
* Called by the algorithm AFTER `roundLayout` and `computeScrollSizes`
|
|
196
|
+
* have populated all `_layout` fields, so the captured values are
|
|
197
|
+
* already integer-rounded and scroll-aware.
|
|
198
|
+
*
|
|
199
|
+
* @internal
|
|
200
|
+
*/
|
|
201
|
+
export declare function snapshotForCache(node: Node): LayoutCacheValue;
|
|
202
|
+
/**
|
|
203
|
+
* Restore `node`'s own size + scroll metrics, plus its direct children's
|
|
204
|
+
* left/top/width/height/scroll. The caller is responsible for handling
|
|
205
|
+
* the recursion into deeper descendants via per-child cache lookups (or
|
|
206
|
+
* a `layoutChildren` fallback on miss).
|
|
207
|
+
*
|
|
208
|
+
* Pre-conditions: `node`'s child list at this call must match the child
|
|
209
|
+
* list captured at cache-store time. The cache invalidation on
|
|
210
|
+
* `insertChild`/`removeChild` (via `markDirty`) guarantees this — a
|
|
211
|
+
* mismatch indicates a cache-correctness bug. We assert in differential
|
|
212
|
+
* mode.
|
|
213
|
+
*
|
|
214
|
+
* @internal
|
|
215
|
+
*/
|
|
216
|
+
export declare function restoreFromCache(node: Node, value: LayoutCacheValue): void;
|
|
217
|
+
/**
|
|
218
|
+
* Produce a human-readable diff of two tree-layout snapshots. Returns
|
|
219
|
+
* an empty string if they match. Used by differential mode to surface
|
|
220
|
+
* cache bugs with enough context to debug them.
|
|
221
|
+
*
|
|
222
|
+
* @internal
|
|
223
|
+
*/
|
|
224
|
+
export declare function diffLayouts(a: ComputedLayout[], b: ComputedLayout[]): string;
|
|
225
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/algorithm/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout-engine caches.
|
|
3
|
+
*
|
|
4
|
+
* Phase 1: `MeasureCache` — caches the result of a leaf node's
|
|
5
|
+
* `MeasureFunc` so repeated calls with the same available-space
|
|
6
|
+
* inputs don't reinvoke the measurer. Wired into every measure-func
|
|
7
|
+
* call site in `main-axis.ts` via the `callMeasureFunc` helper.
|
|
8
|
+
*
|
|
9
|
+
* Caches are owned by `Node` (`_measureCache` field) and cleared
|
|
10
|
+
* automatically by `markDirty()`. Consumers never interact with the
|
|
11
|
+
* cache directly — it is `@internal`.
|
|
12
|
+
*
|
|
13
|
+
* Phase 2 added `LayoutCache` here (P2-T1).
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Up to eight slots — matches Yoga's `LayoutResults::MaxCachedMeasurements`
|
|
19
|
+
* with the documented rationale "98% of analyzed layouts require less
|
|
20
|
+
* than 8 entries" (see facebook/yoga `node/LayoutResults.h`). Covers the
|
|
21
|
+
* hypothetical-vs-final pattern (single leaf measured twice per pass)
|
|
22
|
+
* plus broader reuse patterns where the same leaf is measured at multiple
|
|
23
|
+
* cross-axis sizes during line packing or across consecutive layout calls.
|
|
24
|
+
*
|
|
25
|
+
* Linear scan over 8 slots is a few-cycle hot-path cost; slots are
|
|
26
|
+
* lazy-allocated only on leaves with a `MeasureFunc` installed.
|
|
27
|
+
*
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export class MeasureCache {
|
|
31
|
+
static MAX_ENTRIES = 8;
|
|
32
|
+
slots = [];
|
|
33
|
+
/**
|
|
34
|
+
* Hit/miss counters for diagnostics (bench output, debugging). Always
|
|
35
|
+
* on — the spec originally proposed `__DEV__`-gating these, but the
|
|
36
|
+
* project has no build-tooling `__DEV__` define and the counters cost
|
|
37
|
+
* two property writes per `lookup()` (negligible at any realistic
|
|
38
|
+
* call rate). Counts are incremented only by `lookup()`; `store()`
|
|
39
|
+
* assumes `lookup()` has already been called for the same key on the
|
|
40
|
+
* same call site, so it does not double-count.
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
hits = 0;
|
|
45
|
+
/** See {@link hits}. @internal */
|
|
46
|
+
misses = 0;
|
|
47
|
+
lookup(key) {
|
|
48
|
+
for (const slot of this.slots) {
|
|
49
|
+
if (slot.availableWidth === key.availableWidth &&
|
|
50
|
+
slot.widthMode === key.widthMode &&
|
|
51
|
+
slot.availableHeight === key.availableHeight &&
|
|
52
|
+
slot.heightMode === key.heightMode) {
|
|
53
|
+
this.hits++;
|
|
54
|
+
return { width: slot.width, height: slot.height };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
this.misses++;
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Store `value` for `key`. If `key` is already present, overwrite in
|
|
62
|
+
* place without growing the slot array; otherwise append, evicting
|
|
63
|
+
* the oldest slot when the array is at `MAX_ENTRIES` capacity.
|
|
64
|
+
*
|
|
65
|
+
* Callers should always invoke `lookup(key)` first and only call
|
|
66
|
+
* `store()` on a miss — the `misses` counter is incremented by
|
|
67
|
+
* `lookup()`, not here, so calling `store()` without a prior lookup
|
|
68
|
+
* would cause silent miss undercounting in diagnostics.
|
|
69
|
+
*/
|
|
70
|
+
store(key, value) {
|
|
71
|
+
for (const slot of this.slots) {
|
|
72
|
+
if (slot.availableWidth === key.availableWidth &&
|
|
73
|
+
slot.widthMode === key.widthMode &&
|
|
74
|
+
slot.availableHeight === key.availableHeight &&
|
|
75
|
+
slot.heightMode === key.heightMode) {
|
|
76
|
+
slot.width = value.width;
|
|
77
|
+
slot.height = value.height;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (this.slots.length < MeasureCache.MAX_ENTRIES) {
|
|
82
|
+
this.slots.push({ ...key, ...value });
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
this.slots.shift();
|
|
86
|
+
this.slots.push({ ...key, ...value });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Drop every cached entry. The `hits`/`misses` counters are
|
|
91
|
+
* deliberately preserved across `clear()` calls — they're lifetime
|
|
92
|
+
* diagnostics, not per-pass metrics, and zeroing them would mask the
|
|
93
|
+
* total-pressure picture during bench runs.
|
|
94
|
+
*/
|
|
95
|
+
clear() {
|
|
96
|
+
this.slots.length = 0;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Single-slot per-node layout cache. Matches Yoga's `cachedLayout`
|
|
101
|
+
* (single overwrite-on-write) and Taffy's 1-slot layout-cache. Internal
|
|
102
|
+
* nodes' final-pass keys converge to one stable input once the parent's
|
|
103
|
+
* flex distribution has settled, so additional slots would be dead memory.
|
|
104
|
+
*
|
|
105
|
+
* Lazy-allocated by the algorithm in `algorithm/main-axis.ts` and
|
|
106
|
+
* `algorithm/index.ts` only on nodes that actually go through the
|
|
107
|
+
* `layoutChildren` recursion. Cleared by `Node.markDirty()` (which fires
|
|
108
|
+
* on every style/tree mutation).
|
|
109
|
+
*
|
|
110
|
+
* Hit/miss counters are always-on (same rationale as `MeasureCache`).
|
|
111
|
+
*
|
|
112
|
+
* @internal
|
|
113
|
+
*/
|
|
114
|
+
export class LayoutCache {
|
|
115
|
+
slot = undefined;
|
|
116
|
+
/** @internal */
|
|
117
|
+
hits = 0;
|
|
118
|
+
/** @internal */
|
|
119
|
+
misses = 0;
|
|
120
|
+
lookup(key) {
|
|
121
|
+
const slot = this.slot;
|
|
122
|
+
if (slot !== undefined &&
|
|
123
|
+
slot.availableWidth === key.availableWidth &&
|
|
124
|
+
slot.widthMode === key.widthMode &&
|
|
125
|
+
slot.availableHeight === key.availableHeight &&
|
|
126
|
+
slot.heightMode === key.heightMode) {
|
|
127
|
+
this.hits++;
|
|
128
|
+
return slot.value;
|
|
129
|
+
}
|
|
130
|
+
this.misses++;
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Store `value` for `key`. Single-slot — replaces the previous entry
|
|
135
|
+
* unconditionally. Caller must construct a fresh `LayoutCacheValue` per
|
|
136
|
+
* store; this method does NOT deep-clone for performance, so any
|
|
137
|
+
* subsequent mutation of `value` is visible through `lookup`. The
|
|
138
|
+
* algorithm builds new values per layout pass so the contract holds.
|
|
139
|
+
*/
|
|
140
|
+
store(key, value) {
|
|
141
|
+
this.slot = { ...key, value };
|
|
142
|
+
}
|
|
143
|
+
clear() {
|
|
144
|
+
this.slot = undefined;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Pre-order traversal returning a flat array of layout snapshots, one per
|
|
149
|
+
* node. Used by differential mode to capture and compare the entire tree's
|
|
150
|
+
* layout state cheaply.
|
|
151
|
+
*
|
|
152
|
+
* Captures only the six `ComputedLayout` fields. `_dirty` flags, cache
|
|
153
|
+
* contents, and other ancillary state are NOT in scope. Phase 2's
|
|
154
|
+
* layout-cache work should add `_dirty` validation to the harness if
|
|
155
|
+
* dirty-flag semantics ever become load-bearing for cache correctness.
|
|
156
|
+
*
|
|
157
|
+
* @internal
|
|
158
|
+
*/
|
|
159
|
+
export function snapshotTreeLayouts(root) {
|
|
160
|
+
const out = [];
|
|
161
|
+
visit(root);
|
|
162
|
+
return out;
|
|
163
|
+
function visit(n) {
|
|
164
|
+
out.push({
|
|
165
|
+
left: n.layout.left,
|
|
166
|
+
top: n.layout.top,
|
|
167
|
+
width: n.layout.width,
|
|
168
|
+
height: n.layout.height,
|
|
169
|
+
scrollWidth: n.layout.scrollWidth,
|
|
170
|
+
scrollHeight: n.layout.scrollHeight,
|
|
171
|
+
});
|
|
172
|
+
for (let i = 0; i < n.getChildCount(); i++)
|
|
173
|
+
visit(n.getChild(i));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Recursively clear `_measureCache` and `_layoutCache` on every node in
|
|
178
|
+
* the subtree. Used by differential mode and the fuzzer to force the cold
|
|
179
|
+
* path.
|
|
180
|
+
*
|
|
181
|
+
* @internal
|
|
182
|
+
*/
|
|
183
|
+
export function clearAllCaches(root) {
|
|
184
|
+
root._measureCache?.clear();
|
|
185
|
+
root._layoutCache?.clear();
|
|
186
|
+
for (let i = 0; i < root.getChildCount(); i++)
|
|
187
|
+
clearAllCaches(root.getChild(i));
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Mark every node in the subtree dirty. Used after `clearAllCaches` to
|
|
191
|
+
* force `calculateLayout` down the full cold path.
|
|
192
|
+
*
|
|
193
|
+
* Uses `_forceDirty()` rather than `markDirty()` so the propagation
|
|
194
|
+
* walks through layout boundaries (Phase 3). Differential-mode and
|
|
195
|
+
* fuzzer validation rely on dirtying the entire tree to force the
|
|
196
|
+
* cold path; without this, boundaries would short-circuit the walk
|
|
197
|
+
* and leave clean ancestors that would never re-run the algorithm.
|
|
198
|
+
*
|
|
199
|
+
* Note: `Node._forceDirty()` also calls `_measureCache?.clear()` and
|
|
200
|
+
* `_layoutCache?.clear()` as a side-effect of dirtying. So calling
|
|
201
|
+
* `clearAllCaches(root)` then `markDirtyDeep(root)` clears each node's
|
|
202
|
+
* caches twice. The second clear is harmless (no-op on an empty
|
|
203
|
+
* structure), but differential-mode callers should know the
|
|
204
|
+
* redundancy is intentional — we want both invariants explicit at the
|
|
205
|
+
* call site.
|
|
206
|
+
*
|
|
207
|
+
* @internal
|
|
208
|
+
*/
|
|
209
|
+
export function markDirtyDeep(root) {
|
|
210
|
+
root._forceDirty();
|
|
211
|
+
for (let i = 0; i < root.getChildCount(); i++)
|
|
212
|
+
markDirtyDeep(root.getChild(i));
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Build a `LayoutCacheValue` from `node`'s current `_layout` + its direct
|
|
216
|
+
* children's `_layout`. Captures only direct children; deeper descendants
|
|
217
|
+
* are reconstituted via their own caches during `restoreFromCache`.
|
|
218
|
+
*
|
|
219
|
+
* Called by the algorithm AFTER `roundLayout` and `computeScrollSizes`
|
|
220
|
+
* have populated all `_layout` fields, so the captured values are
|
|
221
|
+
* already integer-rounded and scroll-aware.
|
|
222
|
+
*
|
|
223
|
+
* @internal
|
|
224
|
+
*/
|
|
225
|
+
export function snapshotForCache(node) {
|
|
226
|
+
const childLayouts = [];
|
|
227
|
+
const count = node.getChildCount();
|
|
228
|
+
for (let i = 0; i < count; i++) {
|
|
229
|
+
const c = node.getChild(i);
|
|
230
|
+
childLayouts.push({
|
|
231
|
+
left: c.layout.left,
|
|
232
|
+
top: c.layout.top,
|
|
233
|
+
width: c.layout.width,
|
|
234
|
+
height: c.layout.height,
|
|
235
|
+
scrollWidth: c.layout.scrollWidth,
|
|
236
|
+
scrollHeight: c.layout.scrollHeight,
|
|
237
|
+
floatLeft: c._floatLeft,
|
|
238
|
+
floatTop: c._floatTop,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
width: node.layout.width,
|
|
243
|
+
height: node.layout.height,
|
|
244
|
+
scrollWidth: node.layout.scrollWidth,
|
|
245
|
+
scrollHeight: node.layout.scrollHeight,
|
|
246
|
+
childLayouts,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Restore `node`'s own size + scroll metrics, plus its direct children's
|
|
251
|
+
* left/top/width/height/scroll. The caller is responsible for handling
|
|
252
|
+
* the recursion into deeper descendants via per-child cache lookups (or
|
|
253
|
+
* a `layoutChildren` fallback on miss).
|
|
254
|
+
*
|
|
255
|
+
* Pre-conditions: `node`'s child list at this call must match the child
|
|
256
|
+
* list captured at cache-store time. The cache invalidation on
|
|
257
|
+
* `insertChild`/`removeChild` (via `markDirty`) guarantees this — a
|
|
258
|
+
* mismatch indicates a cache-correctness bug. We assert in differential
|
|
259
|
+
* mode.
|
|
260
|
+
*
|
|
261
|
+
* @internal
|
|
262
|
+
*/
|
|
263
|
+
export function restoreFromCache(node, value) {
|
|
264
|
+
if (process.env.PILATES_DIFFERENTIAL_LAYOUT === '1') {
|
|
265
|
+
if (node.getChildCount() !== value.childLayouts.length) {
|
|
266
|
+
throw new Error(`[pilates layout cache] restored value has ${value.childLayouts.length} children but node has ${node.getChildCount()} — cache invalidation bug`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
node._layout.width = value.width;
|
|
270
|
+
node._layout.height = value.height;
|
|
271
|
+
node._layout.scrollWidth = value.scrollWidth;
|
|
272
|
+
node._layout.scrollHeight = value.scrollHeight;
|
|
273
|
+
// node._layout.left/top are set by the caller before recursion starts
|
|
274
|
+
// (root sets to 0; child positions come from this restore via the
|
|
275
|
+
// childLayouts array below).
|
|
276
|
+
for (let i = 0; i < node.getChildCount(); i++) {
|
|
277
|
+
const c = node.getChild(i);
|
|
278
|
+
const cl = value.childLayouts[i];
|
|
279
|
+
c._layout.left = cl.left;
|
|
280
|
+
c._layout.top = cl.top;
|
|
281
|
+
c._layout.width = cl.width;
|
|
282
|
+
c._layout.height = cl.height;
|
|
283
|
+
c._layout.scrollWidth = cl.scrollWidth;
|
|
284
|
+
c._layout.scrollHeight = cl.scrollHeight;
|
|
285
|
+
c._floatLeft = cl.floatLeft;
|
|
286
|
+
c._floatTop = cl.floatTop;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Produce a human-readable diff of two tree-layout snapshots. Returns
|
|
291
|
+
* an empty string if they match. Used by differential mode to surface
|
|
292
|
+
* cache bugs with enough context to debug them.
|
|
293
|
+
*
|
|
294
|
+
* @internal
|
|
295
|
+
*/
|
|
296
|
+
export function diffLayouts(a, b) {
|
|
297
|
+
if (a.length !== b.length) {
|
|
298
|
+
return `tree length mismatch: cached has ${a.length} nodes, cold has ${b.length}`;
|
|
299
|
+
}
|
|
300
|
+
const fields = ['left', 'top', 'width', 'height', 'scrollWidth', 'scrollHeight'];
|
|
301
|
+
for (let i = 0; i < a.length; i++) {
|
|
302
|
+
const av = a[i];
|
|
303
|
+
const bv = b[i];
|
|
304
|
+
for (const f of fields) {
|
|
305
|
+
if (av[f] !== bv[f]) {
|
|
306
|
+
return `node[${i}].${f}: cached=${av[f]} cold=${bv[f]}`;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return '';
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=cache.js.map
|