diff --git a/.agents/docs/api-codegen.md b/.agents/docs/api-codegen.md index b91cf7f..ec0af3a 100644 --- a/.agents/docs/api-codegen.md +++ b/.agents/docs/api-codegen.md @@ -36,7 +36,7 @@ Verification follows the repository-wide rule below: `check:codegen` detects sta ## Generated Style codec -`api/style-codec.json` and `api/schemas/style-codec.schema.json` are the maintained versioned model for the 41 public Style input fields, their canonical order, their encoding categories, their referenced numeric families, and their public descriptions. The compiler resolves numeric-family references and derives field indexes and the presence-map width once. Neither target keeps a handwritten second field inventory. +`api/style-codec.json` and `api/schemas/style-codec.schema.json` are the maintained versioned model for the 42 public Style input fields, their canonical order, their encoding categories, their referenced numeric families, and their public descriptions. The compiler resolves numeric-family references and derives field indexes and the presence-map width once. Neither target keeps a handwritten second field inventory. The TypeScript emitter writes `packages/taffyjs-node/src/style-input.ts`, which owns the public `StyleInput` and `StyleUpdate` declarations and a straight-line encoder that reads each known property once in canonical order. The Rust emitter writes `crates/taffyjs_binding/src/style_input.rs`, which applies the matching fields in the same order through `decode_into`. Handwritten `style-codec.ts` and `style_codec.rs` own only the closed category encodings, validation primitives, buffer mechanics, and Taffy-specific conversion used by those generated call sites. @@ -44,7 +44,7 @@ The wire version is distinct from the maintained input format version. A change ## Generated Layout codec -The complete public `Layout` field tree and its fixed 21-number private transport have one versioned description in `api/layout-codec.json`. Input order is the public property and slot order. The compiler validates the supported scalar and geometry shapes, resolves JavaScript and Rust field paths, assigns every slot once, and derives the 168-byte buffer size. +The complete public `Layout` field tree and its fixed 23-number private transport have one versioned description in `api/layout-codec.json`. Input order is the public property and slot order. The compiler validates the supported scalar and geometry shapes, resolves JavaScript and Rust field paths, assigns every slot once, and derives the 184-byte buffer size. The TypeScript emitter owns the public `Layout` declaration, slot constants, and straight-line reconstruction of fresh ordinary objects. The Rust emitter owns the same slot constants and the straight-line writer from Taffy's stored `Layout`. The authored tree wrapper owns one module-local `Float64Array` scratch buffer built over an explicit `ArrayBuffer`; the private binding writers synchronously fill it and never retain a pointer to it. The explicit `ArrayBuffer` is required because JavaScriptCore materializes the backing buffer of a length-constructed typed array lazily and Bun loses the first pointer write into any such buffer. Native targets fill the buffer through the borrowed slice; the Wasm target fills the same slots through `napi_set_element`, because a Wasm module cannot receive a pointer into the JavaScript heap. Both targets share one public method name, so the wrapper and the generated decoder never branch on the runtime. diff --git a/.agents/docs/binding-cases.md b/.agents/docs/binding-cases.md index 9288d98..3efa241 100644 --- a/.agents/docs/binding-cases.md +++ b/.agents/docs/binding-cases.md @@ -8,7 +8,7 @@ The feedback loop is: apply the current rules to concrete upstream behavior; dis Direct reads still materialize complete snapshots. The measured callback path now preserves the same complete Style capability through on-demand `getStyle()` delivery as recorded in the [read boundary](architecture.md#read-boundary); selective reads remain separate work under the [performance TODO](api-alignment-todos.md#performance). -The evidence baseline for this case is Taffy at revision [`55cda62a`](https://github.com/DioxusLabs/taffy/commit/55cda62a5df9a5d04c0023be6f6dd607b1474fe9), napi 3.12.0, napi-derive 3.6.2, and @napi-rs/cli 3.8.2. +The evidence baseline for this case is Taffy at revision [`8d13fdc8`](https://github.com/DioxusLabs/taffy/commit/8d13fdc88468c83f01b13b36fadc0349950c6f51), napi 3.12.0, napi-derive 3.6.2, and @napi-rs/cli 3.8.2. ## Case 1: TaffyTree layout state and node identities @@ -91,9 +91,9 @@ This case is closed as an API mapping exercise. It fixes the outer state owner, ### Evidence -- [TaffyTree and NodeData implementation](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/tree/taffy_tree.rs) -- [NodeId implementation](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/tree/node.rs) -- [Layout implementation](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/tree/layout.rs) +- [TaffyTree and NodeData implementation](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/tree/taffy_tree.rs) +- [NodeId implementation](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/tree/node.rs) +- [Layout implementation](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/tree/layout.rs) - [ECMAScript Map objects](https://tc39.es/ecma262/multipage/keyed-collections.html#sec-map-objects) - [ECMAScript SameValueZero comparison](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevaluezero) @@ -101,13 +101,13 @@ This case is closed as an API mapping exercise. It fixes the outer state owner, This case maps the complete Style value that JavaScript supplies to node creation and replacement and the owned readonly Style value returned by direct reads or a measure callback's `getStyle()`. It is intended to establish reusable value-mapping rules, not merely settle the spelling of one Style field. -This case is complete as an API-alignment example. Its reference value is that the selected container and value-family rules are sufficient to classify every currently known Style field without reviewing all 41 fields individually. The exhaustive inventory is intentionally outside the example because repeating already covered categories would add no new alignment reasoning. +This case is complete as an API-alignment example. Its reference value is that the selected container and value-family rules are sufficient to classify every currently exposed Style field without reviewing the complete inventory individually. The exhaustive inventory is intentionally outside the example because repeating already covered categories would add no new alignment reasoning. ### Rust behavior `Style` is one complete owned value. `new_leaf` moves a Style into the new node. `set_style` replaces the node's entire Style and then marks the node dirty; it does not merge the supplied value with the previous Style. `style` returns a borrowed reference to the complete stored Style, and `compute_layout_with_measure` passes a borrowed reference to that same complete value during measurement. -Taffy's examples normally construct a small set of fields and use `..Default::default()` for the rest. Under the repository's pinned default feature set, Style has 41 semantic fields after excluding its Rust-only phantom field. Their transitive types cover booleans, numeric values, optional values, closed keywords, alignment records, generic `Point`, `Size`, `Rect`, and `Line` records, semantic length variants, nested grid collections, custom grid identifiers, and integer counts and indices. +Taffy's examples normally construct a small set of fields and use `..Default::default()` for the rest. The binding currently exposes 42 semantic Style fields after excluding Taffy's Rust-only phantom field and the separately open `flex_line_count` surface. Their transitive types cover booleans, numeric values, optional values, closed keywords and finite containment flags, alignment records, generic `Point`, `Size`, `Rect`, and `Line` records, semantic length variants, nested grid collections, custom grid identifiers, and integer counts and indices. The `calc` feature is enabled by Taffy's default features, which this repository keeps enabled, but its public length types represent calc values through opaque pointers. The high-level `TaffyTree` implementation resolves every calc pointer to `0.0`; it does not expose an application resolver. Raw calc pointers therefore cannot define a JavaScript Style value, so the selected public high-level Style vocabulary excludes calc. @@ -176,7 +176,7 @@ The earlier rich-object implementation was justified by a focused end-to-end nat | ------------------------------------------------------------ | ------------------------: | ------------: | ------------------------: | | One retained scalar; replace one scalar | 3.83 µs | 3.89 µs | Effectively equal | | 13 retained sparse fields; replace one scalar | 11.8 µs | 3.89 µs | `updateStyle` 3.0× faster | -| Complete 41-field `getStyle` snapshot; replace one scalar | 23.1 µs | 3.97 µs | `updateStyle` 5.8× faster | +| Complete `getStyle` snapshot; replace one scalar | 23.1 µs | 3.97 µs | `updateStyle` 5.8× faster | | 1,000 retained `gridAutoRows`; replace one scalar | 428 µs | 4.60 µs | `updateStyle` 93× faster | | Replace 1,000 `gridAutoRows` | 446 µs | 448 µs | Effectively equal | | 1,000 retained nested string grid values; replace one scalar | 139 µs | 64.3 µs | `updateStyle` 2.2× faster | @@ -582,15 +582,15 @@ The shared definition and stable codes that this case originally left open are n ### Evidence -- [Taffy Style definition and defaults](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/style/mod.rs) -- [Taffy semantic length types](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/style/dimension.rs) -- [Taffy alignment types](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/style/alignment.rs) +- [Taffy Style definition and defaults](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/style/mod.rs) +- [Taffy semantic length types](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/style/dimension.rs) +- [Taffy alignment types](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/style/alignment.rs) - [CSS Box Alignment overflow-position values](https://www.w3.org/TR/css-align-3/#overflow-values) -- [Taffy geometry types](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/geometry.rs) -- [Taffy grid Style types](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/style/grid.rs) +- [Taffy geometry types](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/geometry.rs) +- [Taffy grid Style types](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/style/grid.rs) - [Yoga 3.2.1 JavaScript wrapper](https://github.com/facebook/yoga/blob/v3.2.1/javascript/src/wrapAssembly.ts) - [Yoga 3.2.1 native Style normalization](https://github.com/facebook/yoga/blob/v3.2.1/yoga/style/Style.h) -- [TaffyTree Style operations and measurement](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/tree/taffy_tree.rs) +- [TaffyTree Style operations and measurement](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/tree/taffy_tree.rs) - [napi-rs object conversion](https://napi.rs/docs/concepts/type-conversions) - [napi-rs enum conversion](https://napi.rs/docs/concepts/enum) - [TypeScript enum objects and const-enum publication pitfalls](https://www.typescriptlang.org/docs/handbook/enums) @@ -686,10 +686,10 @@ Retaining a callback's owned `getStyle` provider remains supported and does not ### Evidence -- [TaffyTree context, dirty-state, and compute operations](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/tree/taffy_tree.rs) -- [Taffy root and cached layout computation](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/compute/mod.rs) -- [Taffy leaf measurement](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/compute/leaf.rs) -- [Taffy per-node cache behavior](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/tree/cache.rs) +- [TaffyTree context, dirty-state, and compute operations](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/tree/taffy_tree.rs) +- [Taffy root and cached layout computation](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/compute/mod.rs) +- [Taffy leaf measurement](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/compute/leaf.rs) +- [Taffy per-node cache behavior](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/tree/cache.rs) - [napi-rs scoped function calls and pending-exception capture](https://github.com/napi-rs/napi-rs/blob/napi-v3.12.0/crates/napi/src/bindgen_runtime/js_values/function.rs) - [napi-rs JavaScript exception retention](https://github.com/napi-rs/napi-rs/blob/napi-v3.12.0/crates/napi/src/error.rs) - [Yoga 3.2.1 JavaScript measure wrapper](https://github.com/facebook/yoga/blob/v3.2.1/javascript/src/wrapAssembly.ts) diff --git a/.agents/docs/binding-mapping.md b/.agents/docs/binding-mapping.md index a5a868b..e79400f 100644 --- a/.agents/docs/binding-mapping.md +++ b/.agents/docs/binding-mapping.md @@ -2,7 +2,7 @@ This is the current reference for Rust/JavaScript conversion and safety in `@taffyjs/node`. It describes the implemented Taffy boundary. Product choices that should constrain future work are recorded in [@taffyjs/node decisions](taffyjs-node-decisions.md). -Recheck version-sensitive behavior whenever Taffy, napi-rs, Node.js, or TypeScript changes. The primary upstream references are [TaffyTree](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/tree/taffy_tree.rs), [Style](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/style/mod.rs), [geometry](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/geometry.rs), and [napi-rs conversions](https://napi.rs/docs/concepts/type-conversions). +Recheck version-sensitive behavior whenever Taffy, napi-rs, Node.js, or TypeScript changes. The primary upstream references are [TaffyTree](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/tree/taffy_tree.rs), [Style](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/style/mod.rs), [geometry](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/geometry.rs), and [napi-rs conversions](https://napi.rs/docs/concepts/type-conversions). ## Scope and ownership @@ -58,6 +58,8 @@ Indices and Rust integer payloads must be finite exact integers in their public Fieldless families such as `Display`, `Overflow`, and `AlignItems` use stable numeric literal members exposed through frozen PascalCase objects. Public code should use the names, while an exact valid raw code remains accepted. Rust conversion checks exact family membership and does not derive codes from Rust declaration order. +`Contain` uses the same finite numeric-family boundary for the four combinations Taffy can store: `None`, `Layout`, `Paint`, and `Content` (`Layout | Paint`). This exposes Taffy's layout-affecting containment flags without importing CSS parsing or accepting unknown bits. + ### Lengths, available space, and other tagged values Length inputs accept a direct number as shorthand for an absolute length. The complete tagged form remains supported through values such as `Dimension.Length(20)`, `Dimension.Percent(50)`, and `Dimension.Auto`, and tagged length outputs remain valid later inputs. Percent helpers use user-facing percentages, so `50` maps to Taffy's `0.5`. CSS strings are not length values. @@ -82,6 +84,8 @@ Non-Style scalar and fixed-object inputs use concrete napi-rs types where their Borrowed Rust values never escape. Direct Style reads, Layout, child arrays, detailed Grid data, available space, and nested records are copied into complete detached JavaScript values. A measure callback's Style is first cloned into an owned Rust snapshot and is converted into a complete detached JavaScript value only when its `getStyle()` function is called. +Layout snapshots expose Taffy's `scrollable_overflow_rect` as `scrollableOverflowRect`; `right` and `bottom` are the reachable content extents formerly represented by `contentSize`, while negative `left` and `top` preserve start-side overflow that the old size could not express. Detailed Grid track snapshots expose per-track `positions` and resolved `lineNames`, replacing the old derived `sizes` and `gutters` arrays and preserving content-alignment offsets and logical RTL order. + Binding-produced records and arrays are recursively readonly in TypeScript because mutation cannot update Taffy. Runtime objects remain ordinary mutable, unfrozen objects, and each read or callback `getStyle()` call returns an independent snapshot. There are no live native views, output caches, lazy properties, selectors, prepared queries, or batch snapshots; the callback function is an explicit on-demand operation rather than a property that hides an already materialized object. Public TypeScript declarations and JSDoc live in `packages/taffyjs-node/src` and are emitted by `vp pack` into `index.d.ts`. The private native declarations remain napi-rs-generated. diff --git a/.agents/docs/query-api-design.md b/.agents/docs/query-api-design.md index 18dc468..1ed178e 100644 --- a/.agents/docs/query-api-design.md +++ b/.agents/docs/query-api-design.md @@ -82,13 +82,13 @@ This design is deliberately smaller than a general query language. ## Why this boundary remains valid -The values covered by this design are finite and non-recursive in the Taffy revision currently pinned by TaffyJS, [`55cda62a`](https://github.com/DioxusLabs/taffy/commit/55cda62a5df9a5d04c0023be6f6dd607b1474fe9). +The values covered by this design are finite and non-recursive in the Taffy revision currently pinned by TaffyJS, [`8d13fdc8`](https://github.com/DioxusLabs/taffy/commit/8d13fdc88468c83f01b13b36fadc0349950c6f51). -- [`Layout`](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/tree/layout.rs#L262-L284) contains numbers and fixed Point, Size, and Rect records. -- [`DetailedLayoutInfo`](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/tree/layout.rs#L389-L395) currently contains either no details or a Grid value. The Grid details contain fixed records and arrays of fixed records or numbers rather than another DetailedLayoutInfo value. -- Style contains fixed records, tagged variants, and collections, but those collections have bounded element shapes. For example, [`GridTemplateComponent::Repeat`](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/style/grid.rs#L1507-L1513) contains a list of TrackSizingFunction values rather than another list of GridTemplateComponent values. CSS Grid also specifies that [`repeat()` cannot be nested](https://www.w3.org/TR/css-grid/#repeat-notation). +- [`Layout`](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/tree/layout.rs#L263-L294) contains numbers and fixed Point, Size, and Rect records. +- [`DetailedLayoutInfo`](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/tree/layout.rs#L402-L410) currently contains either no details or a Grid value. The Grid details contain fixed records plus arrays of fixed line-position records, line-name strings, and numbers rather than another DetailedLayoutInfo value. +- Style contains fixed records, tagged variants, and collections, but those collections have bounded element shapes. For example, [`GridTemplateComponent::Repeat`](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/style/grid.rs#L1639-L1648) contains a list of TrackSizingFunction values rather than another list of GridTemplateComponent values. CSS Grid also specifies that [`repeat()` cannot be nested](https://www.w3.org/TR/css-grid/#repeat-notation). -Two related structures are recursive, but neither is embedded in these per-node values. The Taffy node tree refers to children through NodeId and belongs to the tree API. CSS math functions such as `calc()`, `min()`, `max()`, and `clamp()` have a recursive [calculation-tree model](https://www.w3.org/TR/css-values-4/#calc-internal), but [Taffy stores a calc value as a reference resolved by the surrounding tree implementation](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/style/dimension.rs#L57-L64), not as an expression tree inside Style. +Two related structures are recursive, but neither is embedded in these per-node values. The Taffy node tree refers to children through NodeId and belongs to the tree API. CSS math functions such as `calc()`, `min()`, `max()`, and `clamp()` have a recursive [calculation-tree model](https://www.w3.org/TR/css-values-4/#calc-internal), but [Taffy stores a calc value as an opaque pointer-like handle resolved by the surrounding tree implementation](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/style/compact_length.rs#L210-L270), not as an expression tree inside Style. New layout features may add fields, variants, collections, or relationships between nodes without making a per-node Style or Layout recursively contain itself. Those changes can extend the generated selectors without changing this model. If TaffyJS later exposes a genuinely recursive value such as a raw CSS expression tree or layout-fragment tree, the existing query can return the whole value or a reference to it without descending recursively, or that new value can receive a separately designed API. diff --git a/.agents/docs/style-codec.md b/.agents/docs/style-codec.md index 0a9579b..2b7f161 100644 --- a/.agents/docs/style-codec.md +++ b/.agents/docs/style-codec.md @@ -10,11 +10,11 @@ The generated encoder reads each known top-level property once through ordinary ## Generated ownership -`api/style-codec.json` is the canonical versioned inventory for all 41 fields, their order, encoding categories, referenced numeric families, and public descriptions; `api/schemas/style-codec.schema.json` closes that vocabulary. `tools/api-codegen` validates and compiles the model, then generates `packages/taffyjs-node/src/style-input.ts` and `crates/taffyjs_binding/src/style_input.rs`. The TypeScript output owns `StyleInput`, `StyleUpdate`, and the straight-line property encoder. The Rust output owns the matching straight-line `decode_into(target, encoded) -> Result` field application. Handwritten codec modules implement the finite encoding categories rather than interpreting schema metadata at runtime. +`api/style-codec.json` is the canonical versioned inventory for all 42 fields, their order, encoding categories, referenced numeric families, and public descriptions; `api/schemas/style-codec.schema.json` closes that vocabulary. `tools/api-codegen` validates and compiles the model, then generates `packages/taffyjs-node/src/style-input.ts` and `crates/taffyjs_binding/src/style_input.rs`. The TypeScript output owns `StyleInput`, `StyleUpdate`, and the straight-line property encoder. The Rust output owns the matching straight-line `decode_into(target, encoded) -> Result` field application. Handwritten codec modules implement the finite encoding categories rather than interpreting schema metadata at runtime. -## Wire version 1 +## Wire version 2 -Every encoded value begins with two magic bytes (`0x54`, `0x53`), one wire-version byte, one presence-width byte, and the fixed-width presence bitmap. Version 1 has 41 fields and therefore six presence bytes. Set bits identify payloads that follow in the canonical model order; unset bits carry no payload. The decoder rejects bad magic, a different version or presence width, bits beyond the known field count, truncation, and trailing data. +Every encoded value begins with two magic bytes (`0x54`, `0x53`), one wire-version byte, one presence-width byte, and the fixed-width presence bitmap. Version 2 has 42 fields and therefore six presence bytes. It replaced version 1 when Taffy's `contain` field entered the canonical order and shifted later field indexes. Set bits identify payloads that follow in the canonical model order; unset bits carry no payload. The decoder rejects bad magic, a different version or presence width, bits beyond the known field count, truncation, and trailing data. Payload integers and IEEE-754 values are little-endian. Booleans, enum discriminators, nullable markers, tagged-union discriminators, and nested component masks use bytes; all public discriminator assignments are consumed through the TypeScript constants and Rust code enums generated from `api/numeric-families.json`, rather than repeated as codec literals. Ordinary numbers use `f64` and undergo the existing Rust `f64`-to-`f32` conversion; Grid indexes and counts use their checked `i16`, `u16`, or `u32` widths. Lengths carry a unit byte and a number only for branches that need one. Strings carry a `u32` UTF-8 byte length followed by bytes, and collections carry a `u32` element count followed by recursively encoded complete elements. Partial geometry carries a component mask, with a reserved scalar-expansion marker where the public shorthand permits it. @@ -38,4 +38,4 @@ Acceptance performance is measured through the complete public API rather than b ## Verification boundary -Shared public behavior tests run against both `@taffyjs/node` and `@taffyjs/wasm` and cover all five operations, defaults, replacement, partial update, absence, null, empty collections, every Grid/string family, unknown and invalid fields, atomic failure, unchanged updates, floating-point edge cases, NodeId representation precedence, getters, Proxies, reentrancy, children, and context. Rust safety tests exercise malformed headers, versions, bitmaps, lengths, counts, UTF-8, truncation, and trailing bytes without freezing a particular byte snapshot. Two guards run over the complete public field list rather than over a sample: every field accepts explicit `undefined` as absence, and exactly the eight publicly nullable fields accept `null` while the other thirty-three reject it. Generated freshness is enforced by `vp run check:codegen` in a clean checkout. +Shared public behavior tests run against both `@taffyjs/node` and `@taffyjs/wasm` and cover all five operations, defaults, replacement, partial update, absence, null, empty collections, every Grid/string family, unknown and invalid fields, atomic failure, unchanged updates, floating-point edge cases, NodeId representation precedence, getters, Proxies, reentrancy, children, and context. Rust safety tests exercise malformed headers, versions, bitmaps, lengths, counts, UTF-8, truncation, and trailing bytes without freezing a particular byte snapshot. Two guards run over the complete public field list rather than over a sample: every field accepts explicit `undefined` as absence, and exactly the eight publicly nullable fields accept `null` while the other thirty-four reject it. Generated freshness is enforced by `vp run check:codegen` in a clean checkout. diff --git a/.agents/docs/taffyjs-yoga-reference.md b/.agents/docs/taffyjs-yoga-reference.md index 4c71a14..17e50e6 100644 --- a/.agents/docs/taffyjs-yoga-reference.md +++ b/.agents/docs/taffyjs-yoga-reference.md @@ -33,8 +33,8 @@ Primary sources: - [Yoga pixel-grid processing](https://github.com/facebook/yoga/blob/v3.2.1/yoga/algorithm/PixelGrid.cpp) - [Yoga flex-basis experiment branch](https://github.com/facebook/yoga/blob/v3.2.1/yoga/algorithm/CalculateLayout.cpp) - [Yoga WebFlexBasis relayout regression](https://github.com/facebook/yoga/blob/v3.2.1/tests/YGRelayoutTest.cpp) -- [Taffy Style](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/style/mod.rs) -- [Taffy tree and layout storage](https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/tree/taffy_tree.rs) +- [Taffy Style](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/style/mod.rs) +- [Taffy tree and layout storage](https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/tree/taffy_tree.rs) - [Current @taffyjs/node tree wrapper](../../packages/taffyjs-node/src/tree.ts) The inventory includes package exports, intentional named runtime and type exports, members on the default Yoga object, declared factories and methods, and deliberate behavior added by the JavaScript wrapper. It excludes undocumented Emscripten or Embind implementation artifacts such as the runtime Z property found on factory objects. diff --git a/.agents/docs/technology-stack.md b/.agents/docs/technology-stack.md index ee8da99..bfbef88 100644 --- a/.agents/docs/technology-stack.md +++ b/.agents/docs/technology-stack.md @@ -2,7 +2,7 @@ ## Native bindings and distribution -The binding depends on Taffy by exact Git revision, currently [`55cda62a`](https://github.com/DioxusLabs/taffy/commit/55cda62a5df9a5d04c0023be6f6dd607b1474fe9). The repository publishes npm packages and no Rust crate, so a revision requirement carries no crates.io restriction, and the pinned revision contains upstream fixes that no Taffy release contains. A 500-node nested-flexbox measurement on one macOS arm64 host, taken on 2026-08-22 under the scenario suite of that date, reported a median of 26.15 ms for `@taffyjs/node` against Taffy 0.13.0 and 3.24 ms against the pinned revision, with the `yoga-layout` baseline unchanged at 9.7 ms; the retained public dataset is measured on the fixed benchmark host and is refreshed there after a dependency change. Each published package names the exact revision in its README. +The binding depends on Taffy by exact Git revision, currently [`8d13fdc8`](https://github.com/DioxusLabs/taffy/commit/8d13fdc88468c83f01b13b36fadc0349950c6f51). The repository publishes npm packages and no Rust crate, so a revision requirement carries no crates.io restriction, and the pinned revision contains upstream fixes that no Taffy release contains. The Git-pin strategy was originally supported by a 500-node nested-flexbox measurement on one macOS arm64 host: on 2026-08-22, Taffy 0.13.0 took 26.15 ms while the then-current post-release revision `55cda62a` took 3.24 ms and the `yoga-layout` baseline remained 9.7 ms. That comparison explains why the project does not fall back to the release tag; it is not a performance claim for the current pin. The retained public dataset is measured on the fixed benchmark host and is normally refreshed after a dependency change. A refresh for `8d13fdc8` remains pending: three complete attempts on 2026-08-23 were rejected by the existing stability gates in different scenarios, so `benchmarks/results/published.json` deliberately remains the dataset from source commit `ccb0c0c108e390bcc88a6832d930a2498bd0630f` and must not be treated as evidence for the current pin. Each published package names the exact revision in its README. napi-rs owns the Rust-to-Node boundary, private native declarations, native loader, and target-specific package metadata. It generates an ESM loader, which Vite+ bundles into the ESM public entry without maintaining a custom loader. The pinned napi-rs template deliberately emits bare Node builtin specifiers to retain Node 12 compatibility; TaffyJS targets Node 22.20 or newer, so the repository mechanically normalizes those generated specifiers to the explicit `node:` protocol before formatting and bundling the loader. diff --git a/Cargo.lock b/Cargo.lock index b284993..ec5b803 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -314,7 +314,7 @@ dependencies = [ [[package]] name = "taffy" version = "0.13.0" -source = "git+https://github.com/DioxusLabs/taffy?rev=55cda62a5df9a5d04c0023be6f6dd607b1474fe9#55cda62a5df9a5d04c0023be6f6dd607b1474fe9" +source = "git+https://github.com/DioxusLabs/taffy?rev=8d13fdc88468c83f01b13b36fadc0349950c6f51#8d13fdc88468c83f01b13b36fadc0349950c6f51" dependencies = [ "arrayvec", "serde", diff --git a/Cargo.toml b/Cargo.toml index 5511e9a..4b85260 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ rust-version = "1.88" napi = { version = "=3.12.0", features = ["napi8"] } napi-build = "=2.4.0" napi-derive = "=3.6.2" -taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "55cda62a5df9a5d04c0023be6f6dd607b1474fe9" } +taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "8d13fdc88468c83f01b13b36fadc0349950c6f51" } [profile.release] lto = true diff --git a/api/layout-codec.json b/api/layout-codec.json index f1567a8..d321872 100644 --- a/api/layout-codec.json +++ b/api/layout-codec.json @@ -20,10 +20,10 @@ "documentation": "Reports this node's outer width and height." }, { - "name": "contentSize", - "shape": "size", - "components": ["width", "height"], - "documentation": "Reports the width and height of this node's laid-out content. The node's own end padding counts only for a scroll container, and content that overflows past the scroll origin does not count." + "name": "scrollableOverflowRect", + "shape": "rect", + "components": ["left", "right", "top", "bottom"], + "documentation": "Reports the scrollable overflow rectangle relative to this node's scroll origin; right and bottom are the reachable content extents, while negative left and top values represent unreachable start-side overflow." }, { "name": "scrollbarSize", diff --git a/api/numeric-families.json b/api/numeric-families.json index 0f26499..a714395 100644 --- a/api/numeric-families.json +++ b/api/numeric-families.json @@ -35,6 +35,15 @@ { "name": "Scroll", "value": 3 } ] }, + { + "name": "Contain", + "members": [ + { "name": "None", "value": 0 }, + { "name": "Layout", "value": 1 }, + { "name": "Paint", "value": 2 }, + { "name": "Content", "value": 3 } + ] + }, { "name": "Float", "members": [ diff --git a/api/style-codec.json b/api/style-codec.json index 80b3d74..4eefba4 100644 --- a/api/style-codec.json +++ b/api/style-codec.json @@ -1,7 +1,7 @@ { "$schema": "./schemas/style-codec.schema.json", "formatVersion": 1, - "wireVersion": 1, + "wireVersion": 2, "fields": [ { "name": "display", @@ -42,6 +42,12 @@ "category": "number", "description": "Supplies the node's scrollbar width style when present." }, + { + "name": "contain", + "category": "enum", + "numericFamily": "Contain", + "description": "Supplies the node's layout and paint containment style when present." + }, { "name": "float", "category": "enum", diff --git a/apps/website/guide/block.md b/apps/website/guide/block.md index 5cb1312..aa6638f 100644 --- a/apps/website/guide/block.md +++ b/apps/website/guide/block.md @@ -59,6 +59,7 @@ The fields used most often around Block layout are: - `border` and `padding` contribute to its box. - `boxSizing` selects how an explicit size relates to border and padding. - `overflow` and `scrollbarWidth` affect overflow and the stored scrollbar size. +- `contain` can establish an independent formatting context through `Contain.Layout`, `Contain.Paint`, or their combined `Contain.Content` value. Layout containment also suppresses the box's natural baseline; Taffy does not implement non-layout paint effects such as clipping or stacking contexts. Each geometry field accepts either one supported semantic length for every side or axis, or a partial named record. TaffyJS does not parse CSS shorthand strings. diff --git a/apps/website/guide/grid.md b/apps/website/guide/grid.md index f793085..5ec6b6e 100644 --- a/apps/website/guide/grid.md +++ b/apps/website/guide/grid.md @@ -97,11 +97,12 @@ After computing a Grid root, `getDetailedLayoutInfo(root)` can expose the resolv const detail = tree.getDetailedLayoutInfo(root); if (detail.kind === DetailedLayoutInfoKind.Grid) { - console.log(detail.value.columns.sizes); + console.log(detail.value.columns.positions); + console.log(detail.value.columns.lineNames); console.log(detail.value.items); } ``` -The row and column records distinguish negative implicit, explicit, and positive implicit tracks and include resolved gutter and track sizes. Item records report the resolved start and end lines. This data is a detached snapshot, just like ordinary layout output. +The row and column records distinguish negative implicit, explicit, and positive implicit tracks. Each `positions` entry gives one track's start and end relative to the Grid container's border box, including gaps and content alignment; `lineNames` reports the names attached to the corresponding Grid lines. Item records report resolved start and end line numbers. This data is a detached snapshot, just like ordinary layout output. The opening example uses the same line-based placement model as the named and automatic forms described here. diff --git a/apps/website/node/index.md b/apps/website/node/index.md index f81480b..4dab853 100644 --- a/apps/website/node/index.md +++ b/apps/website/node/index.md @@ -1,6 +1,6 @@ # `@taffyjs/node` -`@taffyjs/node` is the native Node-API binding for Taffy at revision [`55cda62a`](https://github.com/DioxusLabs/taffy/commit/55cda62a5df9a5d04c0023be6f6dd607b1474fe9), an upstream commit after the 0.13.0 release. It provides an explicit in-memory layout tree for Block, Flexbox, and Grid through readable JavaScript inputs and outputs. +`@taffyjs/node` is the native Node-API binding for Taffy at revision [`8d13fdc8`](https://github.com/DioxusLabs/taffy/commit/8d13fdc88468c83f01b13b36fadc0349950c6f51), an upstream commit after the 0.13.0 release. It provides an explicit in-memory layout tree for Block, Flexbox, and Grid through readable JavaScript inputs and outputs. Follow [Getting Started](../guide/getting-started.md) to install the package and compute a first layout. diff --git a/apps/website/node/layout-results.md b/apps/website/node/layout-results.md index d93dcbe..3e71a2b 100644 --- a/apps/website/node/layout-results.md +++ b/apps/website/node/layout-results.md @@ -14,16 +14,16 @@ A new node has a zero layout before its first computation. After a successful co Both getters return the same `Layout` shape: -| Field | Meaning | -| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `order` | Stable traversal order in the stored layout. | -| `location` | `{ x, y }` position relative to the parent. | -| `size` | Outer `{ width, height }` of the node. | -| `contentSize` | Width and height of the node's laid-out content. The node's own end padding counts only for a scroll container, and content overflowing past the scroll origin does not count. | -| `scrollbarSize` | Width and height reserved for scrollbars. | -| `border` | Resolved left, right, top, and bottom border widths. | -| `padding` | Resolved left, right, top, and bottom padding widths. | -| `margin` | Resolved left, right, top, and bottom margins. | +| Field | Meaning | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `order` | Stable traversal order in the stored layout. | +| `location` | `{ x, y }` position relative to the parent. | +| `size` | Outer `{ width, height }` of the node. | +| `scrollableOverflowRect` | Scrollable overflow relative to the node's scroll origin. `right` and `bottom` are reachable content extents; negative `left` and `top` values report start-side overflow that cannot be reached by ordinary forward scrolling. | +| `scrollbarSize` | Width and height reserved for scrollbars. | +| `border` | Resolved left, right, top, and bottom border widths. | +| `padding` | Resolved left, right, top, and bottom padding widths. | +| `margin` | Resolved left, right, top, and bottom margins. | Every read returns a new detached object with detached nested geometry. The fields are readonly in TypeScript but are not frozen at runtime. Mutating one result never updates the native tree or another result. @@ -35,8 +35,8 @@ Every read returns a new detached object with detached nested geometry. The fiel const detail = tree.getDetailedLayoutInfo(node); if (detail.kind === DetailedLayoutInfoKind.Grid) { - detail.value.rows.sizes; - detail.value.columns.gutters; + detail.value.rows.positions; + detail.value.columns.lineNames; detail.value.items; } ``` @@ -44,7 +44,8 @@ if (detail.kind === DetailedLayoutInfoKind.Grid) { The Grid payload contains: - row and column records with negative implicit, explicit, and positive implicit track counts; -- resolved gutter and track-size arrays; +- resolved start/end positions for every track, including content-alignment offsets; +- line-name groups aligned with Grid lines, or an empty array when the axis has no names; - resolved row and column start and end lines for each reported item. `DetailedLayoutInfoKind.None` has no payload. As with ordinary layouts, this getter returns stored data rather than computing or proving freshness. Taffy can retain earlier Grid detail after a later non-Grid computation, so use it in the same update step as the Grid computation whose details you intend to inspect. diff --git a/apps/website/node/style.md b/apps/website/node/style.md index 9c4d85e..14c5260 100644 --- a/apps/website/node/style.md +++ b/apps/website/node/style.md @@ -11,7 +11,7 @@ These fields describe the node itself or are shared by more than one layout mode | Fields | Purpose | | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ | | `display`, `itemIsTable`, `itemIsReplaced` | Select layout participation and Taffy's table or replaced-item behavior. | -| `boxSizing`, `direction`, `overflow`, `scrollbarWidth` | Control box sizing, writing direction, and overflow contributions. | +| `boxSizing`, `direction`, `overflow`, `scrollbarWidth`, `contain` | Control box sizing, writing direction, overflow, and containment. | | `position`, `inset` | Keep a node in normal flow or position it relatively or absolutely. | | `size`, `minSize`, `maxSize`, `aspectRatio` | Supply preferred and limiting dimensions. | | `margin`, `padding`, `border`, `gap` | Supply spacing around, inside, and between boxes. | diff --git a/apps/website/node/value-helpers.md b/apps/website/node/value-helpers.md index e5d5e44..ed68ba1 100644 --- a/apps/website/node/value-helpers.md +++ b/apps/website/node/value-helpers.md @@ -8,15 +8,16 @@ Each constant family is a frozen object whose members are stable numeric literal The general style families are: -| Family | What it selects | -| ------------------- | ------------------------------------------------- | -| `Display` | Block, Flow Root, Flexbox, Grid, or no layout. | -| `BoxSizing` | Border-box or content-box sizing. | -| `Direction` | Left-to-right or right-to-left writing direction. | -| `Overflow` | Overflow handling on each axis. | -| `Float` and `Clear` | Block float placement and clearing. | -| `Position` | Relative or absolute positioning. | -| `TextAlign` | Taffy's text-alignment contribution to layout. | +| Family | What it selects | +| ------------------- | --------------------------------------------------- | +| `Display` | Block, Flow Root, Flexbox, Grid, or no layout. | +| `BoxSizing` | Border-box or content-box sizing. | +| `Direction` | Left-to-right or right-to-left writing direction. | +| `Overflow` | Overflow handling on each axis. | +| `Contain` | No, layout, paint, or combined content containment. | +| `Float` and `Clear` | Block float placement and clearing. | +| `Position` | Relative or absolute positioning. | +| `TextAlign` | Taffy's text-alignment contribution to layout. | Flexbox and alignment use `FlexDirection`, `FlexWrap`, `AlignItems`, and `AlignContent`. Grid automatic placement uses `GridAutoFlow`. diff --git a/apps/website/wasm/index.md b/apps/website/wasm/index.md index f0c47a6..8997e7b 100644 --- a/apps/website/wasm/index.md +++ b/apps/website/wasm/index.md @@ -1,6 +1,6 @@ # `@taffyjs/wasm` -`@taffyjs/wasm` runs the same TaffyJS API through WebAssembly, over Taffy at revision [`55cda62a`](https://github.com/DioxusLabs/taffy/commit/55cda62a5df9a5d04c0023be6f6dd607b1474fe9). Choose it for a bundled browser application, or when you want to use WebAssembly explicitly in Node.js instead of loading a native addon. +`@taffyjs/wasm` runs the same TaffyJS API through WebAssembly, over Taffy at revision [`8d13fdc8`](https://github.com/DioxusLabs/taffy/commit/8d13fdc88468c83f01b13b36fadc0349950c6f51). Choose it for a bundled browser application, or when you want to use WebAssembly explicitly in Node.js instead of loading a native addon. Read the [Design](./design.md) to understand how the package keeps one direct API across native and WebAssembly runtimes. diff --git a/crates/taffyjs_binding/src/detailed.rs b/crates/taffyjs_binding/src/detailed.rs index b444f26..9ce2efd 100644 --- a/crates/taffyjs_binding/src/detailed.rs +++ b/crates/taffyjs_binding/src/detailed.rs @@ -8,8 +8,14 @@ pub struct DetailedGridTracksOutput { pub negative_implicit_tracks: u16, pub explicit_tracks: u16, pub positive_implicit_tracks: u16, - pub gutters: Vec, - pub sizes: Vec, + pub positions: Vec, + pub line_names: Vec>, +} + +#[napi(object, object_from_js = false)] +pub struct DetailedGridTrackPositionOutput { + pub start: f64, + pub end: f64, } #[napi(object, object_from_js = false)] @@ -38,8 +44,18 @@ fn tracks_output(value: &DetailedGridTracksInfo) -> DetailedGridTracksOutput { negative_implicit_tracks: value.negative_implicit_tracks, explicit_tracks: value.explicit_tracks, positive_implicit_tracks: value.positive_implicit_tracks, - gutters: value.gutters.iter().copied().map(f64::from).collect(), - sizes: value.sizes.iter().copied().map(f64::from).collect(), + positions: value + .positions + .iter() + .map(|position| DetailedGridTrackPositionOutput { + start: f64::from(position.start), + end: f64::from(position.end), + }) + .collect(), + line_names: value + .iter_line_names() + .map(|names| names.to_vec()) + .collect(), } } diff --git a/crates/taffyjs_binding/src/layout_codec.rs b/crates/taffyjs_binding/src/layout_codec.rs index bee41b6..84a9f99 100644 --- a/crates/taffyjs_binding/src/layout_codec.rs +++ b/crates/taffyjs_binding/src/layout_codec.rs @@ -4,28 +4,30 @@ use taffy::Layout; -pub(crate) const OUTPUT_LENGTH: usize = 21; +pub(crate) const OUTPUT_LENGTH: usize = 23; pub(crate) const ORDER_SLOT: usize = 0; pub(crate) const LOCATION_X_SLOT: usize = 1; pub(crate) const LOCATION_Y_SLOT: usize = 2; pub(crate) const SIZE_WIDTH_SLOT: usize = 3; pub(crate) const SIZE_HEIGHT_SLOT: usize = 4; -pub(crate) const CONTENT_SIZE_WIDTH_SLOT: usize = 5; -pub(crate) const CONTENT_SIZE_HEIGHT_SLOT: usize = 6; -pub(crate) const SCROLLBAR_SIZE_WIDTH_SLOT: usize = 7; -pub(crate) const SCROLLBAR_SIZE_HEIGHT_SLOT: usize = 8; -pub(crate) const BORDER_LEFT_SLOT: usize = 9; -pub(crate) const BORDER_RIGHT_SLOT: usize = 10; -pub(crate) const BORDER_TOP_SLOT: usize = 11; -pub(crate) const BORDER_BOTTOM_SLOT: usize = 12; -pub(crate) const PADDING_LEFT_SLOT: usize = 13; -pub(crate) const PADDING_RIGHT_SLOT: usize = 14; -pub(crate) const PADDING_TOP_SLOT: usize = 15; -pub(crate) const PADDING_BOTTOM_SLOT: usize = 16; -pub(crate) const MARGIN_LEFT_SLOT: usize = 17; -pub(crate) const MARGIN_RIGHT_SLOT: usize = 18; -pub(crate) const MARGIN_TOP_SLOT: usize = 19; -pub(crate) const MARGIN_BOTTOM_SLOT: usize = 20; +pub(crate) const SCROLLABLE_OVERFLOW_RECT_LEFT_SLOT: usize = 5; +pub(crate) const SCROLLABLE_OVERFLOW_RECT_RIGHT_SLOT: usize = 6; +pub(crate) const SCROLLABLE_OVERFLOW_RECT_TOP_SLOT: usize = 7; +pub(crate) const SCROLLABLE_OVERFLOW_RECT_BOTTOM_SLOT: usize = 8; +pub(crate) const SCROLLBAR_SIZE_WIDTH_SLOT: usize = 9; +pub(crate) const SCROLLBAR_SIZE_HEIGHT_SLOT: usize = 10; +pub(crate) const BORDER_LEFT_SLOT: usize = 11; +pub(crate) const BORDER_RIGHT_SLOT: usize = 12; +pub(crate) const BORDER_TOP_SLOT: usize = 13; +pub(crate) const BORDER_BOTTOM_SLOT: usize = 14; +pub(crate) const PADDING_LEFT_SLOT: usize = 15; +pub(crate) const PADDING_RIGHT_SLOT: usize = 16; +pub(crate) const PADDING_TOP_SLOT: usize = 17; +pub(crate) const PADDING_BOTTOM_SLOT: usize = 18; +pub(crate) const MARGIN_LEFT_SLOT: usize = 19; +pub(crate) const MARGIN_RIGHT_SLOT: usize = 20; +pub(crate) const MARGIN_TOP_SLOT: usize = 21; +pub(crate) const MARGIN_BOTTOM_SLOT: usize = 22; pub(crate) fn write_output(value: &Layout, output: &mut [f64; OUTPUT_LENGTH]) { output[ORDER_SLOT] = f64::from(value.order); @@ -33,8 +35,10 @@ pub(crate) fn write_output(value: &Layout, output: &mut [f64; OUTPUT_LENGTH]) { output[LOCATION_Y_SLOT] = f64::from(value.location.y); output[SIZE_WIDTH_SLOT] = f64::from(value.size.width); output[SIZE_HEIGHT_SLOT] = f64::from(value.size.height); - output[CONTENT_SIZE_WIDTH_SLOT] = f64::from(value.content_size.width); - output[CONTENT_SIZE_HEIGHT_SLOT] = f64::from(value.content_size.height); + output[SCROLLABLE_OVERFLOW_RECT_LEFT_SLOT] = f64::from(value.scrollable_overflow_rect.left); + output[SCROLLABLE_OVERFLOW_RECT_RIGHT_SLOT] = f64::from(value.scrollable_overflow_rect.right); + output[SCROLLABLE_OVERFLOW_RECT_TOP_SLOT] = f64::from(value.scrollable_overflow_rect.top); + output[SCROLLABLE_OVERFLOW_RECT_BOTTOM_SLOT] = f64::from(value.scrollable_overflow_rect.bottom); output[SCROLLBAR_SIZE_WIDTH_SLOT] = f64::from(value.scrollbar_size.width); output[SCROLLBAR_SIZE_HEIGHT_SLOT] = f64::from(value.scrollbar_size.height); output[BORDER_LEFT_SLOT] = f64::from(value.border.left); diff --git a/crates/taffyjs_binding/src/numeric.rs b/crates/taffyjs_binding/src/numeric.rs index 3dc45ec..10af6b4 100644 --- a/crates/taffyjs_binding/src/numeric.rs +++ b/crates/taffyjs_binding/src/numeric.rs @@ -54,6 +54,12 @@ numeric_codes! { Hidden = 2, Scroll = 3, } + enum ContainCode { + None = 0, + Layout = 1, + Paint = 2, + Content = 3, + } enum FloatCode { Left = 0, Right = 1, diff --git a/crates/taffyjs_binding/src/style.rs b/crates/taffyjs_binding/src/style.rs index 6e79334..c92b745 100644 --- a/crates/taffyjs_binding/src/style.rs +++ b/crates/taffyjs_binding/src/style.rs @@ -1,15 +1,15 @@ use napi_derive::napi; use taffy::geometry::{Rect, Size}; use taffy::style::{ - AlignContent, AlignItems, BoxSizing, Clear, Direction, Display, FlexDirection, FlexWrap, Float, - GridAutoFlow, Overflow, Position, Style, TextAlign, + AlignContent, AlignItems, BoxSizing, Clear, Contain, Direction, Display, FlexDirection, + FlexWrap, Float, GridAutoFlow, Overflow, Position, Style, TextAlign, }; use crate::error::BindingResult; use crate::numeric::{ - AlignContentCode, AlignItemsCode, BoxSizingCode, ClearCode, DirectionCode, DisplayCode, - FlexDirectionCode, FlexWrapCode, FloatCode, GridAutoFlowCode, OverflowCode, PositionCode, - TextAlignCode, + AlignContentCode, AlignItemsCode, BoxSizingCode, ClearCode, ContainCode, DirectionCode, + DisplayCode, FlexDirectionCode, FlexWrapCode, FloatCode, GridAutoFlowCode, OverflowCode, + PositionCode, TextAlignCode, }; use crate::{grid, length, number}; @@ -48,6 +48,7 @@ pub struct StyleOutput { pub direction: u8, pub overflow: OverflowOutput, pub scrollbar_width: f64, + pub contain: u8, #[napi(js_name = "float")] pub r#float: u8, pub clear: u8, @@ -125,6 +126,15 @@ pub(crate) fn overflow(value: f64) -> BindingResult { }) } +pub(crate) fn contain(value: f64) -> BindingResult { + Ok(match integer::(value)? { + ContainCode::None => Contain::NONE, + ContainCode::Layout => Contain::LAYOUT, + ContainCode::Paint => Contain::PAINT, + ContainCode::Content => Contain::CONTENT, + }) +} + pub(crate) fn float(value: f64) -> BindingResult { Ok(match integer::(value)? { FloatCode::Left => Float::Left, @@ -263,6 +273,20 @@ fn overflow_output(value: Overflow) -> u8 { } } +fn contain_output(value: Contain) -> u8 { + if value == Contain::NONE { + ContainCode::None as u8 + } else if value == Contain::LAYOUT { + ContainCode::Layout as u8 + } else if value == Contain::PAINT { + ContainCode::Paint as u8 + } else if value == Contain::CONTENT { + ContainCode::Content as u8 + } else { + panic!("unsupported Taffy containment") + } +} + fn float_output(value: Float) -> u8 { match value { Float::Left => FloatCode::Left as u8, @@ -419,6 +443,7 @@ pub(crate) fn output(style: &Style) -> StyleOutput { y: overflow_output(style.overflow.y), }, scrollbar_width: f64::from(style.scrollbar_width), + contain: contain_output(style.contain), r#float: float_output(style.float), clear: clear_output(style.clear), position: position_output(style.position), diff --git a/crates/taffyjs_binding/src/style_codec.rs b/crates/taffyjs_binding/src/style_codec.rs index 952fffc..8287327 100644 --- a/crates/taffyjs_binding/src/style_codec.rs +++ b/crates/taffyjs_binding/src/style_codec.rs @@ -590,7 +590,7 @@ mod tests { const PRESENCE_BYTES: usize = 6; fn packet(field: Option, payload: &[u8]) -> Vec { - let mut encoded = vec![STYLE_MAGIC_0, STYLE_MAGIC_1, 1, PRESENCE_BYTES as u8]; + let mut encoded = vec![STYLE_MAGIC_0, STYLE_MAGIC_1, 2, PRESENCE_BYTES as u8]; encoded.resize(4 + PRESENCE_BYTES, 0); if let Some(field) = field { encoded[4 + (field >> 3)] |= 1 << (field & 7); @@ -604,18 +604,18 @@ mod tests { for encoded in [ Vec::new(), vec![STYLE_MAGIC_0], - vec![0, STYLE_MAGIC_1, 1, PRESENCE_BYTES as u8], - vec![STYLE_MAGIC_0, STYLE_MAGIC_1, 2, PRESENCE_BYTES as u8], - vec![STYLE_MAGIC_0, STYLE_MAGIC_1, 1, 0], + vec![0, STYLE_MAGIC_1, 2, PRESENCE_BYTES as u8], + vec![STYLE_MAGIC_0, STYLE_MAGIC_1, 1, PRESENCE_BYTES as u8], + vec![STYLE_MAGIC_0, STYLE_MAGIC_1, 2, 0], ] { - assert!(StyleDecoder::new(&encoded, 1, PRESENCE_BYTES, 41).is_err()); + assert!(StyleDecoder::new(&encoded, 2, PRESENCE_BYTES, 42).is_err()); } } #[test] fn rejects_truncation_trailing_data_and_unknown_presence_bits() { let mut target = Style::default(); - assert!(style_input::decode_into(&mut target, &packet(Some(29), &[])).is_err()); + assert!(style_input::decode_into(&mut target, &packet(Some(30), &[])).is_err()); assert!(style_input::decode_into(&mut target, &packet(None, &[0])).is_err()); assert!(style_input::decode_into(&mut target, &packet(Some(47), &[])).is_err()); } @@ -625,7 +625,7 @@ mod tests { let mut target = Style::default(); let impossible_count = u32::MAX.to_le_bytes(); assert!( - style_input::decode_into(&mut target, &packet(Some(38), &impossible_count)).is_err() + style_input::decode_into(&mut target, &packet(Some(39), &impossible_count)).is_err() ); } @@ -637,6 +637,6 @@ mod tests { payload.extend_from_slice(&1u32.to_le_bytes()); payload.push(0xff); let mut target = Style::default(); - assert!(style_input::decode_into(&mut target, &packet(Some(38), &payload)).is_err()); + assert!(style_input::decode_into(&mut target, &packet(Some(39), &payload)).is_err()); } } diff --git a/crates/taffyjs_binding/src/style_input.rs b/crates/taffyjs_binding/src/style_input.rs index fcf34ef..1898d87 100644 --- a/crates/taffyjs_binding/src/style_input.rs +++ b/crates/taffyjs_binding/src/style_input.rs @@ -9,7 +9,7 @@ use crate::style; use crate::style_codec::{StyleDecoder, replace, replace_f32, replace_optional_f32}; pub(crate) fn decode_into(target: &mut Style, encoded: &[u8]) -> BindingResult { - let mut decoder = StyleDecoder::new(encoded, 1, 6, 41)?; + let mut decoder = StyleDecoder::new(encoded, 2, 6, 42)?; let mut changed = false; if decoder.field(0) { let value = style::display(f64::from(decoder.enumeration("Style.display")?))?; @@ -45,163 +45,167 @@ pub(crate) fn decode_into(target: &mut Style, encoded: &[u8]) -> BindingResult; } +export interface DetailedGridTrackPositionOutput { + start: number; + end: number; +} + export interface DetailedGridTracksOutput { negativeImplicitTracks: number; explicitTracks: number; positiveImplicitTracks: number; - gutters: Array; - sizes: Array; + positions: Array; + lineNames: Array>; } export interface DetailedLayoutOutput { @@ -159,6 +164,7 @@ export interface StyleOutput { direction: number; overflow: OverflowOutput; scrollbarWidth: number; + contain: number; float: number; clear: number; position: number; diff --git a/packages/taffyjs-node/index.d.ts b/packages/taffyjs-node/index.d.ts index e40fcd3..2e97274 100644 --- a/packages/taffyjs-node/index.d.ts +++ b/packages/taffyjs-node/index.d.ts @@ -43,6 +43,18 @@ declare const Overflow: Readonly<{ readonly Scroll: 3; }>; type Overflow = EnumValue; +/** Lists the supported contain choices as stable numeric constants. */ +declare const Contain: Readonly<{ + /** Selects the None choice from the Contain numeric family. */ + readonly None: 0; + /** Selects the Layout choice from the Contain numeric family. */ + readonly Layout: 1; + /** Selects the Paint choice from the Contain numeric family. */ + readonly Paint: 2; + /** Selects the Content choice from the Contain numeric family. */ + readonly Content: 3; +}>; +type Contain = EnumValue; /** Lists the supported float choices as stable numeric constants. */ declare const Float: Readonly<{ /** Selects the Left choice from the Float numeric family. */ @@ -576,6 +588,7 @@ interface Style { /** Reports the node's stored direction style value. */ readonly direction: Direction; /** Reports the node's stored overflow style value. */ readonly overflow: Point; /** Reports the node's stored scrollbar width style value. */ readonly scrollbarWidth: number; + /** Reports the node's stored layout and paint containment style value. */ readonly contain: Contain; /** Reports the node's stored float style value. */ readonly float: Float; /** Reports which preceding floats this node must clear. */ readonly clear: Clear; /** Reports the node's stored position style value. */ readonly position: Position; @@ -629,8 +642,8 @@ interface DetailedGridTracksInfo { /** Reports the negative implicit tracks value stored in DetailedGridTracksInfo. */ readonly negativeImplicitTracks: number; /** Reports the explicit tracks value stored in DetailedGridTracksInfo. */ readonly explicitTracks: number; /** Reports the positive implicit tracks value stored in DetailedGridTracksInfo. */ readonly positiveImplicitTracks: number; - /** Reports the gutters value stored in DetailedGridTracksInfo. */ readonly gutters: readonly number[]; - /** Reports the sizes value stored in DetailedGridTracksInfo. */ readonly sizes: readonly number[]; + /** Reports each track's start and end position relative to the Grid container's border box. */ readonly positions: readonly Line[]; + /** Reports the names attached to each Grid line; empty when the axis has no named lines. */ readonly lineNames: readonly (readonly string[])[]; } /** Reports detached readonly detailed grid item info from a completed Grid layout. */ interface DetailedGridItemInfo { @@ -745,7 +758,7 @@ interface Layout { /** Reports this node's stable traversal order in the stored layout. */ readonly order: number; /** Reports this node's position relative to its parent. */ readonly location: Point; /** Reports this node's outer width and height. */ readonly size: Size; - /** Reports the width and height of this node's laid-out content. The node's own end padding counts only for a scroll container, and content that overflows past the scroll origin does not count. */ readonly contentSize: Size; + /** Reports the scrollable overflow rectangle relative to this node's scroll origin; right and bottom are the reachable content extents, while negative left and top values represent unreachable start-side overflow. */ readonly scrollableOverflowRect: Rect; /** Reports the width and height reserved for scrollbars. */ readonly scrollbarSize: Size; /** Reports this node's resolved border widths. */ readonly border: Rect; /** Reports this node's resolved padding widths. */ readonly padding: Rect; @@ -762,6 +775,7 @@ interface StyleInput { /** Supplies the node's direction style when present. */ direction?: Direction | undefined; /** Supplies the node's overflow style when present. */ overflow?: PartialPointInput | undefined; /** Supplies the node's scrollbar width style when present. */ scrollbarWidth?: number | undefined; + /** Supplies the node's layout and paint containment style when present. */ contain?: Contain | undefined; /** Supplies the node's float style when present. */ float?: Float | undefined; /** Sets which preceding floats this node must clear. */ clear?: Clear | undefined; /** Supplies the node's position style when present. */ position?: Position | undefined; @@ -870,4 +884,4 @@ declare class TaffyTree { computeLayout(options: ComputeLayoutOptions): void; } //#endregion -export { AlignContent, AlignItems, type AutoInput, AvailableSpace, type AvailableSpaceInput, AvailableSpaceKind, BoxSizing, type ChildRangeInput, Clear, type ComputeLayoutOptions, type DetailedGridInfo, type DetailedGridItemInfo, type DetailedGridTracksInfo, type DetailedLayoutInfo, DetailedLayoutInfoKind, Dimension, type DimensionInput, Direction, Display, type EnumValue, FlexDirection, FlexWrap, Float, GridAutoFlow, GridPlacement, type GridPlacementInput, GridPlacementKind, type GridTemplateArea, type GridTemplateAreaInput, type GridTemplateAreas, type GridTemplateAreasInput, GridTemplateComponent, type GridTemplateComponentInput, GridTemplateComponentKind, type GridTemplateRepetition, type GridTemplateRepetitionInput, type Layout, type LengthInput, type LengthPercentage, type LengthPercentageAuto, type LengthPercentageAutoInput, type LengthPercentageInput, LengthUnit, type Line, type LineInput, type MaxTrackSizingFunction, type MaxTrackSizingFunctionInput, type MeasureArgs, type MeasureFunction, type MinTrackSizingFunction, type MinTrackSizingFunctionInput, type NodeId, Overflow, type PartialLineInput, type PartialPointInput, type PartialRectInput, type PartialSizeInput, type PercentInput, type Point, type PointInput, Position, type Rect, type RectInput, RepetitionCount, type RepetitionCountInput, RepetitionCountKind, type Size, type SizeInput, type Style, type StyleInput, type StyleUpdate, TaffyTree, TextAlign, TrackSizingFunction, type TrackSizingFunctionInput, TrackSizingKind }; \ No newline at end of file +export { AlignContent, AlignItems, type AutoInput, AvailableSpace, type AvailableSpaceInput, AvailableSpaceKind, BoxSizing, type ChildRangeInput, Clear, type ComputeLayoutOptions, Contain, type DetailedGridInfo, type DetailedGridItemInfo, type DetailedGridTracksInfo, type DetailedLayoutInfo, DetailedLayoutInfoKind, Dimension, type DimensionInput, Direction, Display, type EnumValue, FlexDirection, FlexWrap, Float, GridAutoFlow, GridPlacement, type GridPlacementInput, GridPlacementKind, type GridTemplateArea, type GridTemplateAreaInput, type GridTemplateAreas, type GridTemplateAreasInput, GridTemplateComponent, type GridTemplateComponentInput, GridTemplateComponentKind, type GridTemplateRepetition, type GridTemplateRepetitionInput, type Layout, type LengthInput, type LengthPercentage, type LengthPercentageAuto, type LengthPercentageAutoInput, type LengthPercentageInput, LengthUnit, type Line, type LineInput, type MaxTrackSizingFunction, type MaxTrackSizingFunctionInput, type MeasureArgs, type MeasureFunction, type MinTrackSizingFunction, type MinTrackSizingFunctionInput, type NodeId, Overflow, type PartialLineInput, type PartialPointInput, type PartialRectInput, type PartialSizeInput, type PercentInput, type Point, type PointInput, Position, type Rect, type RectInput, RepetitionCount, type RepetitionCountInput, RepetitionCountKind, type Size, type SizeInput, type Style, type StyleInput, type StyleUpdate, TaffyTree, TextAlign, TrackSizingFunction, type TrackSizingFunctionInput, TrackSizingKind }; \ No newline at end of file diff --git a/packages/taffyjs-node/index.js b/packages/taffyjs-node/index.js index 59b660a..fd49524 100644 --- a/packages/taffyjs-node/index.js +++ b/packages/taffyjs-node/index.js @@ -38,6 +38,17 @@ const Overflow = Object.freeze({ /** Selects the Scroll choice from the Overflow numeric family. */ Scroll: 3 }); +/** Lists the supported contain choices as stable numeric constants. */ +const Contain = Object.freeze({ + /** Selects the None choice from the Contain numeric family. */ + None: 0, + /** Selects the Layout choice from the Contain numeric family. */ + Layout: 1, + /** Selects the Paint choice from the Contain numeric family. */ + Paint: 2, + /** Selects the Content choice from the Contain numeric family. */ + Content: 3 +}); /** Lists the supported float choices as stable numeric constants. */ const Float = Object.freeze({ /** Selects the Left choice from the Float numeric family. */ @@ -953,22 +964,24 @@ const LOCATION_X_SLOT = 1; const LOCATION_Y_SLOT = 2; const SIZE_WIDTH_SLOT = 3; const SIZE_HEIGHT_SLOT = 4; -const CONTENT_SIZE_WIDTH_SLOT = 5; -const CONTENT_SIZE_HEIGHT_SLOT = 6; -const SCROLLBAR_SIZE_WIDTH_SLOT = 7; -const SCROLLBAR_SIZE_HEIGHT_SLOT = 8; -const BORDER_LEFT_SLOT = 9; -const BORDER_RIGHT_SLOT = 10; -const BORDER_TOP_SLOT = 11; -const BORDER_BOTTOM_SLOT = 12; -const PADDING_LEFT_SLOT = 13; -const PADDING_RIGHT_SLOT = 14; -const PADDING_TOP_SLOT = 15; -const PADDING_BOTTOM_SLOT = 16; -const MARGIN_LEFT_SLOT = 17; -const MARGIN_RIGHT_SLOT = 18; -const MARGIN_TOP_SLOT = 19; -const MARGIN_BOTTOM_SLOT = 20; +const SCROLLABLE_OVERFLOW_RECT_LEFT_SLOT = 5; +const SCROLLABLE_OVERFLOW_RECT_RIGHT_SLOT = 6; +const SCROLLABLE_OVERFLOW_RECT_TOP_SLOT = 7; +const SCROLLABLE_OVERFLOW_RECT_BOTTOM_SLOT = 8; +const SCROLLBAR_SIZE_WIDTH_SLOT = 9; +const SCROLLBAR_SIZE_HEIGHT_SLOT = 10; +const BORDER_LEFT_SLOT = 11; +const BORDER_RIGHT_SLOT = 12; +const BORDER_TOP_SLOT = 13; +const BORDER_BOTTOM_SLOT = 14; +const PADDING_LEFT_SLOT = 15; +const PADDING_RIGHT_SLOT = 16; +const PADDING_TOP_SLOT = 17; +const PADDING_BOTTOM_SLOT = 18; +const MARGIN_LEFT_SLOT = 19; +const MARGIN_RIGHT_SLOT = 20; +const MARGIN_TOP_SLOT = 21; +const MARGIN_BOTTOM_SLOT = 22; function decodeLayout(output) { return { order: output[ORDER_SLOT], @@ -980,9 +993,11 @@ function decodeLayout(output) { width: output[SIZE_WIDTH_SLOT], height: output[SIZE_HEIGHT_SLOT] }, - contentSize: { - width: output[CONTENT_SIZE_WIDTH_SLOT], - height: output[CONTENT_SIZE_HEIGHT_SLOT] + scrollableOverflowRect: { + left: output[SCROLLABLE_OVERFLOW_RECT_LEFT_SLOT], + right: output[SCROLLABLE_OVERFLOW_RECT_RIGHT_SLOT], + top: output[SCROLLABLE_OVERFLOW_RECT_TOP_SLOT], + bottom: output[SCROLLABLE_OVERFLOW_RECT_BOTTOM_SLOT] }, scrollbarSize: { width: output[SCROLLBAR_SIZE_WIDTH_SLOT], @@ -1424,7 +1439,7 @@ var StyleEncoder = class { //#endregion //#region src/style-input.ts function withEncodedStyle(style, use) { - return withStyleEncoder(style, 1, 6, (encoder) => { + return withStyleEncoder(style, 2, 6, (encoder) => { const display = style.display; if (display !== void 0) { encoder.field(0); @@ -1467,207 +1482,213 @@ function withEncodedStyle(style, use) { const value = scrollbarWidth; encoder.number(value, "Style.scrollbarWidth"); } + const contain = style.contain; + if (contain !== void 0) { + encoder.field(7); + const value = contain; + encoder.enumeration(value, 15, "Style.contain"); + } const float = style.float; if (float !== void 0) { - encoder.field(7); + encoder.field(8); const value = float; encoder.enumeration(value, 7, "Style.float"); } const clear = style.clear; if (clear !== void 0) { - encoder.field(8); + encoder.field(9); const value = clear; encoder.enumeration(value, 15, "Style.clear"); } const position = style.position; if (position !== void 0) { - encoder.field(9); + encoder.field(10); const value = position; encoder.enumeration(value, 3, "Style.position"); } const inset = style.inset; if (inset !== void 0) { - encoder.field(10); + encoder.field(11); const value = inset; encoder.partialRectLengthPercentageAuto(value, "Style.inset"); } const size = style.size; if (size !== void 0) { - encoder.field(11); + encoder.field(12); const value = size; encoder.partialSizeDimension(value, "Style.size"); } const minSize = style.minSize; if (minSize !== void 0) { - encoder.field(12); + encoder.field(13); const value = minSize; encoder.partialSizeDimension(value, "Style.minSize"); } const maxSize = style.maxSize; if (maxSize !== void 0) { - encoder.field(13); + encoder.field(14); const value = maxSize; encoder.partialSizeDimension(value, "Style.maxSize"); } const aspectRatio = style.aspectRatio; if (aspectRatio !== void 0) { - encoder.field(14); + encoder.field(15); const value = aspectRatio; encoder.nullableNumber(value, "Style.aspectRatio"); } const margin = style.margin; if (margin !== void 0) { - encoder.field(15); + encoder.field(16); const value = margin; encoder.partialRectLengthPercentageAuto(value, "Style.margin"); } const padding = style.padding; if (padding !== void 0) { - encoder.field(16); + encoder.field(17); const value = padding; encoder.partialRectLengthPercentage(value, "Style.padding"); } const border = style.border; if (border !== void 0) { - encoder.field(17); + encoder.field(18); const value = border; encoder.partialRectLengthPercentage(value, "Style.border"); } const alignItems = style.alignItems; if (alignItems !== void 0) { - encoder.field(18); + encoder.field(19); const value = alignItems; encoder.nullableEnumeration(value, 65535, "Style.alignItems"); } const alignSelf = style.alignSelf; if (alignSelf !== void 0) { - encoder.field(19); + encoder.field(20); const value = alignSelf; encoder.nullableEnumeration(value, 65535, "Style.alignSelf"); } const justifyItems = style.justifyItems; if (justifyItems !== void 0) { - encoder.field(20); + encoder.field(21); const value = justifyItems; encoder.nullableEnumeration(value, 65535, "Style.justifyItems"); } const justifySelf = style.justifySelf; if (justifySelf !== void 0) { - encoder.field(21); + encoder.field(22); const value = justifySelf; encoder.nullableEnumeration(value, 65535, "Style.justifySelf"); } const alignContent = style.alignContent; if (alignContent !== void 0) { - encoder.field(22); + encoder.field(23); const value = alignContent; encoder.nullableEnumeration(value, 16383, "Style.alignContent"); } const justifyContent = style.justifyContent; if (justifyContent !== void 0) { - encoder.field(23); + encoder.field(24); const value = justifyContent; encoder.nullableEnumeration(value, 16383, "Style.justifyContent"); } const gap = style.gap; if (gap !== void 0) { - encoder.field(24); + encoder.field(25); const value = gap; encoder.partialSizeLengthPercentage(value, "Style.gap"); } const textAlign = style.textAlign; if (textAlign !== void 0) { - encoder.field(25); + encoder.field(26); const value = textAlign; encoder.enumeration(value, 15, "Style.textAlign"); } const flexDirection = style.flexDirection; if (flexDirection !== void 0) { - encoder.field(26); + encoder.field(27); const value = flexDirection; encoder.enumeration(value, 15, "Style.flexDirection"); } const flexWrap = style.flexWrap; if (flexWrap !== void 0) { - encoder.field(27); + encoder.field(28); const value = flexWrap; encoder.enumeration(value, 7, "Style.flexWrap"); } const flexBasis = style.flexBasis; if (flexBasis !== void 0) { - encoder.field(28); + encoder.field(29); const value = flexBasis; encoder.dimension(value, "Style.flexBasis"); } const flexGrow = style.flexGrow; if (flexGrow !== void 0) { - encoder.field(29); + encoder.field(30); const value = flexGrow; encoder.number(value, "Style.flexGrow"); } const flexShrink = style.flexShrink; if (flexShrink !== void 0) { - encoder.field(30); + encoder.field(31); const value = flexShrink; encoder.number(value, "Style.flexShrink"); } const gridTemplateRows = style.gridTemplateRows; if (gridTemplateRows !== void 0) { - encoder.field(31); + encoder.field(32); const value = gridTemplateRows; encoder.gridTemplateComponents(value, "Style.gridTemplateRows"); } const gridTemplateColumns = style.gridTemplateColumns; if (gridTemplateColumns !== void 0) { - encoder.field(32); + encoder.field(33); const value = gridTemplateColumns; encoder.gridTemplateComponents(value, "Style.gridTemplateColumns"); } const gridAutoRows = style.gridAutoRows; if (gridAutoRows !== void 0) { - encoder.field(33); + encoder.field(34); const value = gridAutoRows; encoder.trackSizingFunctions(value, "Style.gridAutoRows"); } const gridAutoColumns = style.gridAutoColumns; if (gridAutoColumns !== void 0) { - encoder.field(34); + encoder.field(35); const value = gridAutoColumns; encoder.trackSizingFunctions(value, "Style.gridAutoColumns"); } const gridAutoFlow = style.gridAutoFlow; if (gridAutoFlow !== void 0) { - encoder.field(35); + encoder.field(36); const value = gridAutoFlow; encoder.enumeration(value, 15, "Style.gridAutoFlow"); } const gridTemplateAreas = style.gridTemplateAreas; if (gridTemplateAreas !== void 0) { - encoder.field(36); + encoder.field(37); const value = gridTemplateAreas; encoder.nullableGridTemplateAreas(value, "Style.gridTemplateAreas"); } const gridTemplateColumnNames = style.gridTemplateColumnNames; if (gridTemplateColumnNames !== void 0) { - encoder.field(37); + encoder.field(38); const value = gridTemplateColumnNames; encoder.stringMatrix(value, "Style.gridTemplateColumnNames"); } const gridTemplateRowNames = style.gridTemplateRowNames; if (gridTemplateRowNames !== void 0) { - encoder.field(38); + encoder.field(39); const value = gridTemplateRowNames; encoder.stringMatrix(value, "Style.gridTemplateRowNames"); } const gridRow = style.gridRow; if (gridRow !== void 0) { - encoder.field(39); + encoder.field(40); const value = gridRow; encoder.partialLineGridPlacement(value, "Style.gridRow"); } const gridColumn = style.gridColumn; if (gridColumn !== void 0) { - encoder.field(40); + encoder.field(41); const value = gridColumn; encoder.partialLineGridPlacement(value, "Style.gridColumn"); } @@ -1712,7 +1733,7 @@ function decodeConstraints(slots) { }; } const DEFAULT_STYLE_INPUT = {}; -const layoutCodecBuffer = new Float64Array(/* @__PURE__ */ new ArrayBuffer(168)); +const layoutCodecBuffer = new Float64Array(/* @__PURE__ */ new ArrayBuffer(184)); function checkedChildIndex(index) { if (typeof index !== "number") throw new TypeError("Child index must be a number"); return index; @@ -1908,4 +1929,4 @@ var TaffyTree = class { } }; //#endregion -export { AlignContent, AlignItems, AvailableSpace, AvailableSpaceKind, BoxSizing, Clear, DetailedLayoutInfoKind, Dimension, Direction, Display, FlexDirection, FlexWrap, Float, GridAutoFlow, GridPlacement, GridPlacementKind, GridTemplateComponent, GridTemplateComponentKind, LengthUnit, Overflow, Position, RepetitionCount, RepetitionCountKind, TaffyTree, TextAlign, TrackSizingFunction, TrackSizingKind }; +export { AlignContent, AlignItems, AvailableSpace, AvailableSpaceKind, BoxSizing, Clear, Contain, DetailedLayoutInfoKind, Dimension, Direction, Display, FlexDirection, FlexWrap, Float, GridAutoFlow, GridPlacement, GridPlacementKind, GridTemplateComponent, GridTemplateComponentKind, LengthUnit, Overflow, Position, RepetitionCount, RepetitionCountKind, TaffyTree, TextAlign, TrackSizingFunction, TrackSizingKind }; diff --git a/packages/taffyjs-node/src/index.ts b/packages/taffyjs-node/src/index.ts index 3ac434f..a34e66e 100644 --- a/packages/taffyjs-node/src/index.ts +++ b/packages/taffyjs-node/src/index.ts @@ -4,6 +4,7 @@ export { AvailableSpaceKind, BoxSizing, Clear, + Contain, DetailedLayoutInfoKind, Direction, Display, diff --git a/packages/taffyjs-node/src/layout-codec.ts b/packages/taffyjs-node/src/layout-codec.ts index d0a86ae..84672f1 100644 --- a/packages/taffyjs-node/src/layout-codec.ts +++ b/packages/taffyjs-node/src/layout-codec.ts @@ -9,7 +9,7 @@ export interface Layout { /** Reports this node's stable traversal order in the stored layout. */ readonly order: number; /** Reports this node's position relative to its parent. */ readonly location: Point; /** Reports this node's outer width and height. */ readonly size: Size; - /** Reports the width and height of this node's laid-out content. The node's own end padding counts only for a scroll container, and content that overflows past the scroll origin does not count. */ readonly contentSize: Size; + /** Reports the scrollable overflow rectangle relative to this node's scroll origin; right and bottom are the reachable content extents, while negative left and top values represent unreachable start-side overflow. */ readonly scrollableOverflowRect: Rect; /** Reports the width and height reserved for scrollbars. */ readonly scrollbarSize: Size; /** Reports this node's resolved border widths. */ readonly border: Rect; /** Reports this node's resolved padding widths. */ readonly padding: Rect; @@ -21,25 +21,27 @@ const LOCATION_X_SLOT = 1; const LOCATION_Y_SLOT = 2; const SIZE_WIDTH_SLOT = 3; const SIZE_HEIGHT_SLOT = 4; -const CONTENT_SIZE_WIDTH_SLOT = 5; -const CONTENT_SIZE_HEIGHT_SLOT = 6; -const SCROLLBAR_SIZE_WIDTH_SLOT = 7; -const SCROLLBAR_SIZE_HEIGHT_SLOT = 8; -const BORDER_LEFT_SLOT = 9; -const BORDER_RIGHT_SLOT = 10; -const BORDER_TOP_SLOT = 11; -const BORDER_BOTTOM_SLOT = 12; -const PADDING_LEFT_SLOT = 13; -const PADDING_RIGHT_SLOT = 14; -const PADDING_TOP_SLOT = 15; -const PADDING_BOTTOM_SLOT = 16; -const MARGIN_LEFT_SLOT = 17; -const MARGIN_RIGHT_SLOT = 18; -const MARGIN_TOP_SLOT = 19; -const MARGIN_BOTTOM_SLOT = 20; +const SCROLLABLE_OVERFLOW_RECT_LEFT_SLOT = 5; +const SCROLLABLE_OVERFLOW_RECT_RIGHT_SLOT = 6; +const SCROLLABLE_OVERFLOW_RECT_TOP_SLOT = 7; +const SCROLLABLE_OVERFLOW_RECT_BOTTOM_SLOT = 8; +const SCROLLBAR_SIZE_WIDTH_SLOT = 9; +const SCROLLBAR_SIZE_HEIGHT_SLOT = 10; +const BORDER_LEFT_SLOT = 11; +const BORDER_RIGHT_SLOT = 12; +const BORDER_TOP_SLOT = 13; +const BORDER_BOTTOM_SLOT = 14; +const PADDING_LEFT_SLOT = 15; +const PADDING_RIGHT_SLOT = 16; +const PADDING_TOP_SLOT = 17; +const PADDING_BOTTOM_SLOT = 18; +const MARGIN_LEFT_SLOT = 19; +const MARGIN_RIGHT_SLOT = 20; +const MARGIN_TOP_SLOT = 21; +const MARGIN_BOTTOM_SLOT = 22; -export const layoutCodecLength = 21; -export const layoutCodecByteLength = 168; +export const layoutCodecLength = 23; +export const layoutCodecByteLength = 184; export function decodeLayout(output: Float64Array): Layout { return { @@ -52,9 +54,11 @@ export function decodeLayout(output: Float64Array): Layout { width: output[SIZE_WIDTH_SLOT], height: output[SIZE_HEIGHT_SLOT], }, - contentSize: { - width: output[CONTENT_SIZE_WIDTH_SLOT], - height: output[CONTENT_SIZE_HEIGHT_SLOT], + scrollableOverflowRect: { + left: output[SCROLLABLE_OVERFLOW_RECT_LEFT_SLOT], + right: output[SCROLLABLE_OVERFLOW_RECT_RIGHT_SLOT], + top: output[SCROLLABLE_OVERFLOW_RECT_TOP_SLOT], + bottom: output[SCROLLABLE_OVERFLOW_RECT_BOTTOM_SLOT], }, scrollbarSize: { width: output[SCROLLBAR_SIZE_WIDTH_SLOT], diff --git a/packages/taffyjs-node/src/numeric-families.ts b/packages/taffyjs-node/src/numeric-families.ts index a68b604..adbb22b 100644 --- a/packages/taffyjs-node/src/numeric-families.ts +++ b/packages/taffyjs-node/src/numeric-families.ts @@ -55,6 +55,20 @@ export const Overflow = Object.freeze({ export type Overflow = EnumValue; +/** Lists the supported contain choices as stable numeric constants. */ +export const Contain = Object.freeze({ + /** Selects the None choice from the Contain numeric family. */ + None: 0, + /** Selects the Layout choice from the Contain numeric family. */ + Layout: 1, + /** Selects the Paint choice from the Contain numeric family. */ + Paint: 2, + /** Selects the Content choice from the Contain numeric family. */ + Content: 3, +} as const); + +export type Contain = EnumValue; + /** Lists the supported float choices as stable numeric constants. */ export const Float = Object.freeze({ /** Selects the Left choice from the Float numeric family. */ diff --git a/packages/taffyjs-node/src/public-types.ts b/packages/taffyjs-node/src/public-types.ts index 789fef4..0470f4d 100644 --- a/packages/taffyjs-node/src/public-types.ts +++ b/packages/taffyjs-node/src/public-types.ts @@ -3,6 +3,7 @@ import { AlignItems, BoxSizing, Clear, + Contain, DetailedLayoutInfoKind, Direction, Display, @@ -333,6 +334,7 @@ export interface Style { /** Reports the node's stored direction style value. */ readonly direction: Direction; /** Reports the node's stored overflow style value. */ readonly overflow: Point; /** Reports the node's stored scrollbar width style value. */ readonly scrollbarWidth: number; + /** Reports the node's stored layout and paint containment style value. */ readonly contain: Contain; /** Reports the node's stored float style value. */ readonly float: Float; /** Reports which preceding floats this node must clear. */ readonly clear: Clear; /** Reports the node's stored position style value. */ readonly position: Position; @@ -391,8 +393,8 @@ export interface DetailedGridTracksInfo { /** Reports the negative implicit tracks value stored in DetailedGridTracksInfo. */ readonly negativeImplicitTracks: number; /** Reports the explicit tracks value stored in DetailedGridTracksInfo. */ readonly explicitTracks: number; /** Reports the positive implicit tracks value stored in DetailedGridTracksInfo. */ readonly positiveImplicitTracks: number; - /** Reports the gutters value stored in DetailedGridTracksInfo. */ readonly gutters: readonly number[]; - /** Reports the sizes value stored in DetailedGridTracksInfo. */ readonly sizes: readonly number[]; + /** Reports each track's start and end position relative to the Grid container's border box. */ readonly positions: readonly Line[]; + /** Reports the names attached to each Grid line; empty when the axis has no named lines. */ readonly lineNames: readonly (readonly string[])[]; } /** Reports detached readonly detailed grid item info from a completed Grid layout. */ diff --git a/packages/taffyjs-node/src/style-input.ts b/packages/taffyjs-node/src/style-input.ts index a9a7479..c2a6f47 100644 --- a/packages/taffyjs-node/src/style-input.ts +++ b/packages/taffyjs-node/src/style-input.ts @@ -7,6 +7,7 @@ import type { AlignItems, BoxSizing, Clear, + Contain, Direction, Display, FlexDirection, @@ -49,6 +50,9 @@ export interface StyleInput { /** Supplies the node's scrollbar width style when present. */ scrollbarWidth?: | number | undefined; + /** Supplies the node's layout and paint containment style when present. */ contain?: + | Contain + | undefined; /** Supplies the node's float style when present. */ float?: Float | undefined; /** Sets which preceding floats this node must clear. */ clear?: Clear | undefined; /** Supplies the node's position style when present. */ position?: Position | undefined; @@ -157,7 +161,7 @@ export interface StyleInput { export type StyleUpdate = StyleInput; export function withEncodedStyle(style: StyleInput, use: (encoded: Uint8Array) => T): T { - return withStyleEncoder(style, 1, 6, (encoder) => { + return withStyleEncoder(style, 2, 6, (encoder) => { const display = style.display; if (display !== undefined) { encoder.field(0); @@ -200,207 +204,213 @@ export function withEncodedStyle(style: StyleInput, use: (encoded: Uint8Array const value = scrollbarWidth; encoder.number(value, "Style.scrollbarWidth"); } + const contain = style.contain; + if (contain !== undefined) { + encoder.field(7); + const value = contain; + encoder.enumeration(value, 15, "Style.contain"); + } const float = style.float; if (float !== undefined) { - encoder.field(7); + encoder.field(8); const value = float; encoder.enumeration(value, 7, "Style.float"); } const clear = style.clear; if (clear !== undefined) { - encoder.field(8); + encoder.field(9); const value = clear; encoder.enumeration(value, 15, "Style.clear"); } const position = style.position; if (position !== undefined) { - encoder.field(9); + encoder.field(10); const value = position; encoder.enumeration(value, 3, "Style.position"); } const inset = style.inset; if (inset !== undefined) { - encoder.field(10); + encoder.field(11); const value = inset; encoder.partialRectLengthPercentageAuto(value, "Style.inset"); } const size = style.size; if (size !== undefined) { - encoder.field(11); + encoder.field(12); const value = size; encoder.partialSizeDimension(value, "Style.size"); } const minSize = style.minSize; if (minSize !== undefined) { - encoder.field(12); + encoder.field(13); const value = minSize; encoder.partialSizeDimension(value, "Style.minSize"); } const maxSize = style.maxSize; if (maxSize !== undefined) { - encoder.field(13); + encoder.field(14); const value = maxSize; encoder.partialSizeDimension(value, "Style.maxSize"); } const aspectRatio = style.aspectRatio; if (aspectRatio !== undefined) { - encoder.field(14); + encoder.field(15); const value = aspectRatio; encoder.nullableNumber(value, "Style.aspectRatio"); } const margin = style.margin; if (margin !== undefined) { - encoder.field(15); + encoder.field(16); const value = margin; encoder.partialRectLengthPercentageAuto(value, "Style.margin"); } const padding = style.padding; if (padding !== undefined) { - encoder.field(16); + encoder.field(17); const value = padding; encoder.partialRectLengthPercentage(value, "Style.padding"); } const border = style.border; if (border !== undefined) { - encoder.field(17); + encoder.field(18); const value = border; encoder.partialRectLengthPercentage(value, "Style.border"); } const alignItems = style.alignItems; if (alignItems !== undefined) { - encoder.field(18); + encoder.field(19); const value = alignItems; encoder.nullableEnumeration(value, 65535, "Style.alignItems"); } const alignSelf = style.alignSelf; if (alignSelf !== undefined) { - encoder.field(19); + encoder.field(20); const value = alignSelf; encoder.nullableEnumeration(value, 65535, "Style.alignSelf"); } const justifyItems = style.justifyItems; if (justifyItems !== undefined) { - encoder.field(20); + encoder.field(21); const value = justifyItems; encoder.nullableEnumeration(value, 65535, "Style.justifyItems"); } const justifySelf = style.justifySelf; if (justifySelf !== undefined) { - encoder.field(21); + encoder.field(22); const value = justifySelf; encoder.nullableEnumeration(value, 65535, "Style.justifySelf"); } const alignContent = style.alignContent; if (alignContent !== undefined) { - encoder.field(22); + encoder.field(23); const value = alignContent; encoder.nullableEnumeration(value, 16383, "Style.alignContent"); } const justifyContent = style.justifyContent; if (justifyContent !== undefined) { - encoder.field(23); + encoder.field(24); const value = justifyContent; encoder.nullableEnumeration(value, 16383, "Style.justifyContent"); } const gap = style.gap; if (gap !== undefined) { - encoder.field(24); + encoder.field(25); const value = gap; encoder.partialSizeLengthPercentage(value, "Style.gap"); } const textAlign = style.textAlign; if (textAlign !== undefined) { - encoder.field(25); + encoder.field(26); const value = textAlign; encoder.enumeration(value, 15, "Style.textAlign"); } const flexDirection = style.flexDirection; if (flexDirection !== undefined) { - encoder.field(26); + encoder.field(27); const value = flexDirection; encoder.enumeration(value, 15, "Style.flexDirection"); } const flexWrap = style.flexWrap; if (flexWrap !== undefined) { - encoder.field(27); + encoder.field(28); const value = flexWrap; encoder.enumeration(value, 7, "Style.flexWrap"); } const flexBasis = style.flexBasis; if (flexBasis !== undefined) { - encoder.field(28); + encoder.field(29); const value = flexBasis; encoder.dimension(value, "Style.flexBasis"); } const flexGrow = style.flexGrow; if (flexGrow !== undefined) { - encoder.field(29); + encoder.field(30); const value = flexGrow; encoder.number(value, "Style.flexGrow"); } const flexShrink = style.flexShrink; if (flexShrink !== undefined) { - encoder.field(30); + encoder.field(31); const value = flexShrink; encoder.number(value, "Style.flexShrink"); } const gridTemplateRows = style.gridTemplateRows; if (gridTemplateRows !== undefined) { - encoder.field(31); + encoder.field(32); const value = gridTemplateRows; encoder.gridTemplateComponents(value, "Style.gridTemplateRows"); } const gridTemplateColumns = style.gridTemplateColumns; if (gridTemplateColumns !== undefined) { - encoder.field(32); + encoder.field(33); const value = gridTemplateColumns; encoder.gridTemplateComponents(value, "Style.gridTemplateColumns"); } const gridAutoRows = style.gridAutoRows; if (gridAutoRows !== undefined) { - encoder.field(33); + encoder.field(34); const value = gridAutoRows; encoder.trackSizingFunctions(value, "Style.gridAutoRows"); } const gridAutoColumns = style.gridAutoColumns; if (gridAutoColumns !== undefined) { - encoder.field(34); + encoder.field(35); const value = gridAutoColumns; encoder.trackSizingFunctions(value, "Style.gridAutoColumns"); } const gridAutoFlow = style.gridAutoFlow; if (gridAutoFlow !== undefined) { - encoder.field(35); + encoder.field(36); const value = gridAutoFlow; encoder.enumeration(value, 15, "Style.gridAutoFlow"); } const gridTemplateAreas = style.gridTemplateAreas; if (gridTemplateAreas !== undefined) { - encoder.field(36); + encoder.field(37); const value = gridTemplateAreas; encoder.nullableGridTemplateAreas(value, "Style.gridTemplateAreas"); } const gridTemplateColumnNames = style.gridTemplateColumnNames; if (gridTemplateColumnNames !== undefined) { - encoder.field(37); + encoder.field(38); const value = gridTemplateColumnNames; encoder.stringMatrix(value, "Style.gridTemplateColumnNames"); } const gridTemplateRowNames = style.gridTemplateRowNames; if (gridTemplateRowNames !== undefined) { - encoder.field(38); + encoder.field(39); const value = gridTemplateRowNames; encoder.stringMatrix(value, "Style.gridTemplateRowNames"); } const gridRow = style.gridRow; if (gridRow !== undefined) { - encoder.field(39); + encoder.field(40); const value = gridRow; encoder.partialLineGridPlacement(value, "Style.gridRow"); } const gridColumn = style.gridColumn; if (gridColumn !== undefined) { - encoder.field(40); + encoder.field(41); const value = gridColumn; encoder.partialLineGridPlacement(value, "Style.gridColumn"); } diff --git a/packages/taffyjs-wasm/README.md b/packages/taffyjs-wasm/README.md index a3e3ce1..f100cc7 100644 --- a/packages/taffyjs-wasm/README.md +++ b/packages/taffyjs-wasm/README.md @@ -1,6 +1,6 @@ # `@taffyjs/wasm` -`@taffyjs/wasm` runs the Taffy layout engine, at revision [`55cda62a`](https://github.com/DioxusLabs/taffy/commit/55cda62a5df9a5d04c0023be6f6dd607b1474fe9), as threadless WebAssembly in Node.js, Bun 1.2+ within major 1, Deno 2.2+ within major 2, and bundled browsers. It exposes the same public TaffyJS API as `@taffyjs/node`; choosing this package guarantees that the implementation is Wasm rather than a native addon. +`@taffyjs/wasm` runs the Taffy layout engine, at revision [`8d13fdc8`](https://github.com/DioxusLabs/taffy/commit/8d13fdc88468c83f01b13b36fadc0349950c6f51), as threadless WebAssembly in Node.js, Bun 1.2+ within major 1, Deno 2.2+ within major 2, and bundled browsers. It exposes the same public TaffyJS API as `@taffyjs/node`; choosing this package guarantees that the implementation is Wasm rather than a native addon. The [Design](../../apps/website/wasm/design.md) explains how the package keeps one direct API across native and WebAssembly runtimes. diff --git a/packages/taffyjs-yoga-wasm/README.md b/packages/taffyjs-yoga-wasm/README.md index e6e13f8..fc29bcd 100644 --- a/packages/taffyjs-yoga-wasm/README.md +++ b/packages/taffyjs-yoga-wasm/README.md @@ -1,6 +1,6 @@ # @taffyjs/yoga-wasm -`@taffyjs/yoga-wasm` is an ESM Yoga 3.2.1 compatibility facade backed by `@taffyjs/wasm`. It uses the same TypeScript facade, public entries, declarations, and compatibility classifications as `@taffyjs/yoga`; only the TaffyJS runtime backend changes. Taffy is at revision [`55cda62a`](https://github.com/DioxusLabs/taffy/commit/55cda62a5df9a5d04c0023be6f6dd607b1474fe9). +`@taffyjs/yoga-wasm` is an ESM Yoga 3.2.1 compatibility facade backed by `@taffyjs/wasm`. It uses the same TypeScript facade, public entries, declarations, and compatibility classifications as `@taffyjs/yoga`; only the TaffyJS runtime backend changes. Taffy is at revision [`8d13fdc8`](https://github.com/DioxusLabs/taffy/commit/8d13fdc88468c83f01b13b36fadc0349950c6f51). The [Design](../../apps/website/yoga-wasm/design.md) explains why the Yoga-facing model and runtime transport remain separate choices. diff --git a/packages/taffyjs-yoga/COMPATIBILITY.md b/packages/taffyjs-yoga/COMPATIBILITY.md index 67f7f14..36ef08b 100644 --- a/packages/taffyjs-yoga/COMPATIBILITY.md +++ b/packages/taffyjs-yoga/COMPATIBILITY.md @@ -61,12 +61,12 @@ When an upstream enum type also contains values that are Unsupported for one pro ## Upstream work in progress -The compatibility tables describe what the current package can do with its pinned Taffy dependency, revision [`55cda62a`](https://github.com/DioxusLabs/taffy/commit/55cda62a5df9a5d04c0023be6f6dd607b1474fe9). Upstream work is tracked separately so that a possible compatibility improvement is visible without presenting unshipped code as available behavior. +The compatibility tables describe what the current package can do with its pinned Taffy dependency, revision [`8d13fdc8`](https://github.com/DioxusLabs/taffy/commit/8d13fdc88468c83f01b13b36fadc0349950c6f51). Upstream work is tracked separately so that a possible compatibility improvement is visible without presenting unshipped code as available behavior. | Capability | Upstream status | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Display.Contents` | [DioxusLabs/taffy#944](https://github.com/DioxusLabs/taffy/pull/944) is open as of 2026-08-16. It proposes native `Display::Contents` support and recursive child flattening in `TaffyTree`. | -| `PositionType.Static` | [DioxusLabs/taffy#1009](https://github.com/DioxusLabs/taffy/pull/1009) is open as of 2026-08-16. It proposes native `Position::Static` support and the containing-block machinery needed by absolute descendants of static nodes. | +| `Display.Contents` | [DioxusLabs/taffy#944](https://github.com/DioxusLabs/taffy/pull/944) is open as of 2026-08-23. It proposes native `Display::Contents` support and recursive child flattening in `TaffyTree`. | +| `PositionType.Static` | [DioxusLabs/taffy#1009](https://github.com/DioxusLabs/taffy/pull/1009) is open as of 2026-08-23. It proposes native `Position::Static` support and the containing-block machinery needed by absolute descendants of static nodes. | An upstream pull request is not a support promise or an ETA. Before reclassification, the capability must ship in the Taffy revision this package pins, `@taffyjs/node` must update and expose the needed native surface, and Yoga 3.2.1 differential tests must verify the complete observable behavior. Until then, each current classification above remains authoritative. diff --git a/packages/taffyjs-yoga/README.md b/packages/taffyjs-yoga/README.md index 205434d..f9cbbb4 100644 --- a/packages/taffyjs-yoga/README.md +++ b/packages/taffyjs-yoga/README.md @@ -1,6 +1,6 @@ # @taffyjs/yoga -`@taffyjs/yoga` is a Node.js compatibility facade for `yoga-layout@3.2.1`, implemented in TypeScript over `@taffyjs/node`. It keeps Yoga's package entries and public API shape while Taffy, at revision [`55cda62a`](https://github.com/DioxusLabs/taffy/commit/55cda62a5df9a5d04c0023be6f6dd607b1474fe9), remains the only layout engine. +`@taffyjs/yoga` is a Node.js compatibility facade for `yoga-layout@3.2.1`, implemented in TypeScript over `@taffyjs/node`. It keeps Yoga's package entries and public API shape while Taffy, at revision [`8d13fdc8`](https://github.com/DioxusLabs/taffy/commit/8d13fdc88468c83f01b13b36fadc0349950c6f51), remains the only layout engine. The [Design](../../apps/website/yoga/design.md) explains which parts of Yoga the facade preserves, which parts stay owned by Taffy, and how unsupported behavior is reported. diff --git a/tests/taffyjs-node/tests/layouts/algorithms.test.mts b/tests/taffyjs-node/tests/layouts/algorithms.test.mts index a63a34c..634e2b4 100644 --- a/tests/taffyjs-node/tests/layouts/algorithms.test.mts +++ b/tests/taffyjs-node/tests/layouts/algorithms.test.mts @@ -41,14 +41,14 @@ function computeChildren(rootStyle: StyleInput, childStyles: readonly StyleInput }; } -// Every fixed layout value below is pinned to the exact Taffy sources at revision 55cda62a: -// https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/compute/block.rs -// https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/compute/float.rs -// https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/compute/flexbox.rs -// https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/compute/grid/mod.rs -// https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/compute/grid/placement.rs -// https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/compute/grid/track_sizing.rs -// https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/compute/mod.rs +// Every fixed layout value below is pinned to the exact Taffy sources at revision 8d13fdc8: +// https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/compute/block.rs +// https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/compute/float.rs +// https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/compute/flexbox.rs +// https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/compute/grid/mod.rs +// https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/compute/grid/placement.rs +// https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/compute/grid/track_sizing.rs +// https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/compute/mod.rs test("block-float", () => { const block = computeChildren({ display: Display.Block, size: { width: 100 } }, [ @@ -72,7 +72,7 @@ test("block-float", () => { assert.deepEqual(block.children[0].location, { x: 0, y: 0 }); assert.deepEqual(block.children[1].location, { x: 0, y: 10 }); assert.deepEqual(block.children[2].location, { x: 5, y: 22 }); - assert.deepEqual(block.root.contentSize, { width: 30, height: 32 }); + assert.deepEqual(block.root.scrollableOverflowRect, { left: 0, right: 30, top: 0, bottom: 32 }); const flowRoot = computeChildren({ display: Display.FlowRoot, size: { width: 100 } }, [ { @@ -82,7 +82,12 @@ test("block-float", () => { }, ]); assert.deepEqual(flowRoot.children[0].location, { x: 80, y: 0 }); - assert.deepEqual(flowRoot.root.contentSize, { width: 100, height: 10 }); + assert.deepEqual(flowRoot.root.scrollableOverflowRect, { + left: 0, + right: 100, + top: 0, + bottom: 10, + }); const positioned = computeChildren( { @@ -206,7 +211,7 @@ test("grid", () => { assert.deepEqual(tree.getUnroundedLayout(first).size, { width: 40, height: 20 }); assert.deepEqual(tree.getUnroundedLayout(second).location, { x: 40, y: 20 }); const layout = tree.getUnroundedLayout(root); - assert.deepEqual(layout.contentSize, { width: 100, height: 50 }); + assert.deepEqual(layout.scrollableOverflowRect, { left: 0, right: 100, top: 0, bottom: 50 }); assert.deepEqual(layout.scrollbarSize, { width: 7, height: 7 }); const detail = tree.getDetailedLayoutInfo(root); @@ -216,15 +221,21 @@ test("grid", () => { negativeImplicitTracks: 0, explicitTracks: 2, positiveImplicitTracks: 0, - gutters: [0, 0, 0], - sizes: [20, 30], + positions: [ + { start: 0, end: 20 }, + { start: 20, end: 50 }, + ], + lineNames: [], }); assert.deepEqual(detail.value.columns, { negativeImplicitTracks: 0, explicitTracks: 2, positiveImplicitTracks: 0, - gutters: [0, 0, 0], - sizes: [40, 60], + positions: [ + { start: 0, end: 40 }, + { start: 40, end: 100 }, + ], + lineNames: [], }); assert.deepEqual(detail.value.items, [ { rowStart: 1, rowEnd: 2, columnStart: 1, columnEnd: 2 }, @@ -317,7 +328,12 @@ test("topology-cache", () => { tree.computeLayout(options); assert.equal(calls > firstCalls, true, "a new child is measured"); assert.deepEqual(tree.getChildren(root), [first, second]); - assert.deepEqual(tree.getUnroundedLayout(root).contentSize, { width: 30, height: 10 }); + assert.deepEqual(tree.getUnroundedLayout(root).scrollableOverflowRect, { + left: 0, + right: 30, + top: 0, + bottom: 10, + }); tree.removeChild(root, first); assert.equal(tree.getNodeCount(), 3); @@ -325,7 +341,12 @@ test("topology-cache", () => { tree.computeLayout(options); assert.equal(tree.isDirty(root), false); assert.deepEqual(tree.getChildren(root), [second]); - assert.deepEqual(tree.getUnroundedLayout(root).contentSize, { width: 20, height: 10 }); + assert.deepEqual(tree.getUnroundedLayout(root).scrollableOverflowRect, { + left: 0, + right: 20, + top: 0, + bottom: 10, + }); tree.remove(first); assert.equal(tree.getNodeCount(), 2); diff --git a/tests/taffyjs-node/tests/tree/compute-layout.test.mts b/tests/taffyjs-node/tests/tree/compute-layout.test.mts index a1b2094..4e69cd6 100644 --- a/tests/taffyjs-node/tests/tree/compute-layout.test.mts +++ b/tests/taffyjs-node/tests/tree/compute-layout.test.mts @@ -40,8 +40,8 @@ function wrapperState(tree: TaffyTree, nodes: readonly NodeId[]) { test("algorithms", () => { // Pinned upstream routing, cache, and hidden-layout sources for these values: - // https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/tree/taffy_tree.rs#L284-L329 - // https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/compute/mod.rs#L278-L290 + // https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/tree/taffy_tree.rs#L284-L329 + // https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/compute/mod.rs#L278-L290 const cases = [ ["Flex", Display.Flex, {}], ["Grid", Display.Grid, {}], @@ -66,7 +66,11 @@ test("algorithms", () => { const childLayout = tree.getUnroundedLayout(child); assert.deepEqual(rootLayout.location, { x: 0, y: 0 }, name); assert.deepEqual(rootLayout.size, { width: 100, height: 50 }, name); - assert.deepEqual(rootLayout.contentSize, { width: 30, height: 10 }, name); + assert.deepEqual( + rootLayout.scrollableOverflowRect, + { left: 0, right: 30, top: 0, bottom: 10 }, + name, + ); assert.deepEqual(childLayout.location, { x: 0, y: 0 }, name); assert.deepEqual(childLayout.size, { width: 30, height: 10 }, name); } @@ -84,13 +88,13 @@ test("algorithms", () => { const layout = noneTree.getUnroundedLayout(node); assert.deepEqual(layout.location, { x: 0, y: 0 }); assert.deepEqual(layout.size, { width: 0, height: 0 }); - assert.deepEqual(layout.contentSize, { width: 0, height: 0 }); + assert.deepEqual(layout.scrollableOverflowRect, { left: 0, right: 0, top: 0, bottom: 0 }); } }); test("percentage-content", () => { - // Pinned upstream block percentage and content-size source for these values: - // https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/compute/block.rs#L566-L746 + // Pinned upstream block percentage and scrollable-overflow source for these values: + // https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/compute/block.rs#L566-L746 const tree = new TaffyTree(); const child = tree.newLeaf({ size: { width: Dimension.Percent(50), height: 80 }, @@ -102,13 +106,18 @@ test("percentage-content", () => { tree.computeLayout({ root, availableSpace: maxContentSpace() }); assert.deepEqual(tree.getUnroundedLayout(child).size, { width: 100, height: 80 }); - assert.deepEqual(tree.getUnroundedLayout(root).contentSize, { width: 100, height: 80 }); + assert.deepEqual(tree.getUnroundedLayout(root).scrollableOverflowRect, { + left: 0, + right: 100, + top: 0, + bottom: 80, + }); }); -test("content-size-overflow", () => { - // Pinned upstream content-size sources for these values: - // https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/compute/common/content_size.rs - // https://github.com/DioxusLabs/taffy/blob/55cda62a5df9a5d04c0023be6f6dd607b1474fe9/src/compute/block.rs#L566-L746 +test("scrollable-overflow", () => { + // Pinned upstream scrollable-overflow sources for these values: + // https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/compute/common/scrollable_overflow.rs + // https://github.com/DioxusLabs/taffy/blob/8d13fdc88468c83f01b13b36fadc0349950c6f51/src/compute/block.rs const padded = new TaffyTree(); padded.disableRounding(); const paddedChild = padded.newLeaf({ size: { width: 30, height: 20 } }); @@ -118,7 +127,12 @@ test("content-size-overflow", () => { padding: 10, }); padded.computeLayout({ root: paddedRoot, availableSpace: { width: 40, height: 30 } }); - assert.deepEqual(padded.getUnroundedLayout(paddedRoot).contentSize, { width: 40, height: 30 }); + assert.deepEqual(padded.getUnroundedLayout(paddedRoot).scrollableOverflowRect, { + left: 0, + right: 40, + top: 0, + bottom: 30, + }); const overflowing = new TaffyTree(); overflowing.disableRounding(); @@ -132,9 +146,11 @@ test("content-size-overflow", () => { size: { width: 10, height: 10 }, }); overflowing.computeLayout({ root: overflowingRoot, availableSpace: { width: 10, height: 10 } }); - assert.deepEqual(overflowing.getUnroundedLayout(overflowingRoot).contentSize, { - width: 10, - height: 10, + assert.deepEqual(overflowing.getUnroundedLayout(overflowingRoot).scrollableOverflowRect, { + left: -20, + right: 10, + top: -20, + bottom: 10, }); }); diff --git a/tests/taffyjs-node/tests/tree/get-detailed-layout-info.test.mts b/tests/taffyjs-node/tests/tree/get-detailed-layout-info.test.mts index aaf5b8c..2cf06f1 100644 --- a/tests/taffyjs-node/tests/tree/get-detailed-layout-info.test.mts +++ b/tests/taffyjs-node/tests/tree/get-detailed-layout-info.test.mts @@ -2,6 +2,7 @@ import assert from "node:assert/strict"; import { AvailableSpace, DetailedLayoutInfoKind, + Direction, Display, GridPlacement, GridTemplateComponent, @@ -15,8 +16,8 @@ type Tracks = { negativeImplicitTracks: number; explicitTracks: number; positiveImplicitTracks: number; - gutters: number[]; - sizes: number[]; + positions: Array<{ start: number; end: number }>; + lineNames: string[][]; }; type GridInfo = { rows: Tracks; @@ -55,6 +56,8 @@ function explicitGrid(tree: TaffyTree) { display: Display.Grid, gridTemplateRows: [singleLengthTrack(12.25), singleLengthTrack(8.5)], gridTemplateColumns: [singleLengthTrack(7.75), singleLengthTrack(3.25)], + gridTemplateRowNames: [["row-start"], [], ["row-end"]], + gridTemplateColumnNames: [["column-start"], ["middle"], ["column-end"]], }); return { first, second, root }; } @@ -100,15 +103,21 @@ test("grid-payload", () => { negativeImplicitTracks: 0, explicitTracks: 2, positiveImplicitTracks: 0, - gutters: [0, 0, 0], - sizes: [Math.fround(12.25), Math.fround(8.5)], + positions: [ + { start: 0, end: Math.fround(12.25) }, + { start: Math.fround(12.25), end: Math.fround(20.75) }, + ], + lineNames: [["row-start"], [], ["row-end"]], }); assert.deepEqual(grid.columns, { negativeImplicitTracks: 0, explicitTracks: 2, positiveImplicitTracks: 0, - gutters: [0, 0, 0], - sizes: [Math.fround(7.75), Math.fround(3.25)], + positions: [ + { start: 0, end: Math.fround(7.75) }, + { start: Math.fround(7.75), end: 11 }, + ], + lineNames: [["column-start"], ["middle"], ["column-end"]], }); assert.deepEqual(grid.items, [ { rowStart: 1, rowEnd: 2, columnStart: 1, columnEnd: 2 }, @@ -116,6 +125,38 @@ test("grid-payload", () => { ]); }); +test("rtl-logical-order-and-implicit-line-names", () => { + const tree = new TaffyTree(); + tree.disableRounding(); + const child = tree.newLeaf({ + gridColumn: { start: GridPlacement.Line(4), end: GridPlacement.Line(5) }, + size: { height: 5 }, + }); + const root = tree.newWithChildren([child], { + display: Display.Grid, + direction: Direction.Rtl, + size: { width: 100, height: 20 }, + gridTemplateColumns: [singleLengthTrack(10), singleLengthTrack(20)], + gridAutoColumns: [TrackSizingFunction.Length(7)], + gridTemplateColumnNames: [["inline-start"], ["middle"], ["inline-end"]], + }); + + compute(tree, root); + const columns = gridValue(tree.getDetailedLayoutInfo(root)).columns; + assert.deepEqual(columns, { + negativeImplicitTracks: 0, + explicitTracks: 2, + positiveImplicitTracks: 2, + positions: [ + { start: 90, end: 100 }, + { start: 70, end: 90 }, + { start: 63, end: 70 }, + { start: 56, end: 63 }, + ], + lineNames: [["inline-start"], ["middle"], ["inline-end"], [], []], + }); +}); + test("deep-detached", () => { const tree = new TaffyTree(); const { root } = explicitGrid(tree); @@ -125,10 +166,14 @@ test("deep-detached", () => { assert.notEqual(first, second); assert.notEqual(first.rows, second.rows); - assert.notEqual(first.rows.sizes, second.rows.sizes); + assert.notEqual(first.rows.positions, second.rows.positions); + assert.notEqual(first.rows.positions[0], second.rows.positions[0]); + assert.notEqual(first.rows.lineNames, second.rows.lineNames); + assert.notEqual(first.rows.lineNames[0], second.rows.lineNames[0]); assert.notEqual(first.items, second.items); assert.notEqual(first.items[0], second.items[0]); - first.rows.sizes[0] = 99; + first.rows.positions[0].start = 99; + first.rows.lineNames[0][0] = "changed"; first.items[0].rowStart = 99; assert.deepEqual(tree.getDetailedLayoutInfo(root), { kind: DetailedLayoutInfoKind.Grid, diff --git a/tests/taffyjs-node/tests/tree/get-layout.test.mts b/tests/taffyjs-node/tests/tree/get-layout.test.mts index ca063be..7e74f3f 100644 --- a/tests/taffyjs-node/tests/tree/get-layout.test.mts +++ b/tests/taffyjs-node/tests/tree/get-layout.test.mts @@ -6,7 +6,7 @@ type Layout = { order: number; location: { x: number; y: number }; size: { width: number; height: number }; - contentSize: { width: number; height: number }; + scrollableOverflowRect: { left: number; right: number; top: number; bottom: number }; scrollbarSize: { width: number; height: number }; border: { left: number; right: number; top: number; bottom: number }; padding: { left: number; right: number; top: number; bottom: number }; @@ -17,7 +17,7 @@ const ZERO_LAYOUT: Layout = { order: 0, location: { x: 0, y: 0 }, size: { width: 0, height: 0 }, - contentSize: { width: 0, height: 0 }, + scrollableOverflowRect: { left: 0, right: 0, top: 0, bottom: 0 }, scrollbarSize: { width: 0, height: 0 }, border: { left: 0, right: 0, top: 0, bottom: 0 }, padding: { left: 0, right: 0, top: 0, bottom: 0 }, @@ -38,7 +38,7 @@ test("exact-zero", () => { "order", "location", "size", - "contentSize", + "scrollableOverflowRect", "scrollbarSize", "border", "padding", diff --git a/tests/taffyjs-node/tests/tree/get-style.test.mts b/tests/taffyjs-node/tests/tree/get-style.test.mts index f2101b5..a928139 100644 --- a/tests/taffyjs-node/tests/tree/get-style.test.mts +++ b/tests/taffyjs-node/tests/tree/get-style.test.mts @@ -10,6 +10,7 @@ const STYLE_FIELDS = [ "direction", "overflow", "scrollbarWidth", + "contain", "float", "clear", "position", diff --git a/tests/taffyjs-node/tests/tree/get-unrounded-layout.test.mts b/tests/taffyjs-node/tests/tree/get-unrounded-layout.test.mts index c9299d9..c2dd847 100644 --- a/tests/taffyjs-node/tests/tree/get-unrounded-layout.test.mts +++ b/tests/taffyjs-node/tests/tree/get-unrounded-layout.test.mts @@ -6,7 +6,7 @@ type Layout = { order: number; location: { x: number; y: number }; size: { width: number; height: number }; - contentSize: { width: number; height: number }; + scrollableOverflowRect: { left: number; right: number; top: number; bottom: number }; scrollbarSize: { width: number; height: number }; border: { left: number; right: number; top: number; bottom: number }; padding: { left: number; right: number; top: number; bottom: number }; @@ -17,7 +17,7 @@ const ZERO_LAYOUT: Layout = { order: 0, location: { x: 0, y: 0 }, size: { width: 0, height: 0 }, - contentSize: { width: 0, height: 0 }, + scrollableOverflowRect: { left: 0, right: 0, top: 0, bottom: 0 }, scrollbarSize: { width: 0, height: 0 }, border: { left: 0, right: 0, top: 0, bottom: 0 }, padding: { left: 0, right: 0, top: 0, bottom: 0 }, diff --git a/tests/taffyjs-node/tests/tree/update-style.test.mts b/tests/taffyjs-node/tests/tree/update-style.test.mts index 752d372..aab72e0 100644 --- a/tests/taffyjs-node/tests/tree/update-style.test.mts +++ b/tests/taffyjs-node/tests/tree/update-style.test.mts @@ -5,6 +5,7 @@ import { AvailableSpace, BoxSizing, Clear, + Contain, Dimension, Direction, Display, @@ -44,6 +45,7 @@ const fieldCases = [ { x: Overflow.Clip, y: Overflow.Visible }, ], ["scrollbarWidth", 3, 4], + ["contain", Contain.Layout, Contain.Content], ["float", Float.Left, Float.Right], ["clear", Clear.Left, Clear.Both], ["position", Position.Absolute, Position.Relative], @@ -113,7 +115,7 @@ const allStyleUpdateFieldsCovered: [MissingStyleUpdateField] extends [never] ? t test("applies every top-level field and skips equal or undefined values", () => { assert.equal(allStyleUpdateFieldsCovered, true); - assert.equal(fieldCases.length, 41); + assert.equal(fieldCases.length, 42); for (const [field, initial, next] of fieldCases) { const tree = new TaffyTree(); diff --git a/tests/taffyjs-node/tests/types/values.test-d.ts b/tests/taffyjs-node/tests/types/values.test-d.ts index d6ca820..b64ec28 100644 --- a/tests/taffyjs-node/tests/types/values.test-d.ts +++ b/tests/taffyjs-node/tests/types/values.test-d.ts @@ -82,7 +82,7 @@ declare const detail: DetailedLayoutInfo; if (detail.kind === DetailedLayoutInfoKind.Grid) { const rows: number = detail.value.rows.explicitTracks; // @ts-expect-error Detailed output arrays are readonly. - detail.value.rows.sizes.push(1); + detail.value.rows.positions.push({ start: 0, end: 1 }); void rows; } else { // @ts-expect-error None has no Grid payload. diff --git a/tests/taffyjs-node/tests/values/layout.test.mts b/tests/taffyjs-node/tests/values/layout.test.mts index 73e1918..09715df 100644 --- a/tests/taffyjs-node/tests/values/layout.test.mts +++ b/tests/taffyjs-node/tests/values/layout.test.mts @@ -5,7 +5,7 @@ type Layout = { order: number; location: { x: number; y: number }; size: { width: number; height: number }; - contentSize: { width: number; height: number }; + scrollableOverflowRect: { left: number; right: number; top: number; bottom: number }; scrollbarSize: { width: number; height: number }; border: { left: number; right: number; top: number; bottom: number }; padding: { left: number; right: number; top: number; bottom: number }; @@ -16,7 +16,7 @@ const ZERO_LAYOUT: Layout = { order: 0, location: { x: 0, y: 0 }, size: { width: 0, height: 0 }, - contentSize: { width: 0, height: 0 }, + scrollableOverflowRect: { left: 0, right: 0, top: 0, bottom: 0 }, scrollbarSize: { width: 0, height: 0 }, border: { left: 0, right: 0, top: 0, bottom: 0 }, padding: { left: 0, right: 0, top: 0, bottom: 0 }, @@ -77,7 +77,7 @@ test("complete-values", () => { order: 0, location: { x: 3, y: 7 }, size: { width: 30, height: 40 }, - contentSize: { width: 11, height: 15 }, + scrollableOverflowRect: { left: 0, right: 11, top: 0, bottom: 15 }, scrollbarSize: { width: 13, height: 13 }, border: { left: 9, right: 10, top: 11, bottom: 12 }, padding: { left: 5, right: 6, top: 7, bottom: 8 }, @@ -93,7 +93,7 @@ test("exact-keys", () => { "order", "location", "size", - "contentSize", + "scrollableOverflowRect", "scrollbarSize", "border", "padding", @@ -101,7 +101,7 @@ test("exact-keys", () => { ]); assert.deepEqual(Object.keys(layout.location), ["x", "y"]); assert.deepEqual(Object.keys(layout.size), ["width", "height"]); - assert.deepEqual(Object.keys(layout.contentSize), ["width", "height"]); + assert.deepEqual(Object.keys(layout.scrollableOverflowRect), ["left", "right", "top", "bottom"]); assert.deepEqual(Object.keys(layout.scrollbarSize), ["width", "height"]); for (const field of ["border", "padding", "margin"] as const) { assert.deepEqual(Object.keys(layout[field]), ["left", "right", "top", "bottom"]); @@ -110,7 +110,7 @@ test("exact-keys", () => { layout, layout.location, layout.size, - layout.contentSize, + layout.scrollableOverflowRect, layout.scrollbarSize, layout.border, layout.padding, @@ -135,7 +135,7 @@ test("detached", () => { assert.notEqual(first, second); assert.notEqual(first.location, second.location); assert.notEqual(first.size, second.size); - assert.notEqual(first.contentSize, second.contentSize); + assert.notEqual(first.scrollableOverflowRect, second.scrollableOverflowRect); assert.notEqual(first.scrollbarSize, second.scrollbarSize); assert.notEqual(first.border, second.border); assert.notEqual(first.padding, second.padding); @@ -197,7 +197,7 @@ test("shared-converter", () => { for (const field of [ "location", "size", - "contentSize", + "scrollableOverflowRect", "scrollbarSize", "border", "padding", diff --git a/tests/taffyjs-node/tests/values/style-fields.test.mts b/tests/taffyjs-node/tests/values/style-fields.test.mts index 2b48e99..c096919 100644 --- a/tests/taffyjs-node/tests/values/style-fields.test.mts +++ b/tests/taffyjs-node/tests/values/style-fields.test.mts @@ -55,6 +55,7 @@ test("default Style contains every public field and value", () => { direction: 0, overflow: { x: 0, y: 0 }, scrollbarWidth: 0, + contain: 0, float: 2, clear: 3, position: 0, diff --git a/tools/api-codegen/src/compiler/layout-codec.ts b/tools/api-codegen/src/compiler/layout-codec.ts index 592c5f8..94fc9f5 100644 --- a/tools/api-codegen/src/compiler/layout-codec.ts +++ b/tools/api-codegen/src/compiler/layout-codec.ts @@ -30,7 +30,7 @@ export interface LayoutCodecModel { // Pinned deliberately. Changing the slot count changes the public Layout shape, so this pin and // the maintained model must be updated together rather than one drifting behind the other. -const pinnedSlotCount = 21; +const pinnedSlotCount = 23; const identifier = /^[a-z][A-Za-z0-9]*$/u; const shapeDefinitions: Readonly< Record