@@ -73,7 +73,7 @@ Parents are derived from `children` and `slots`; they are never stored.
7373| ` layout ` | ` Layout ` | How the node sizes itself and sits in its parent (§3). |
7474| ` decoration ` | ` Decoration ` | Fills, strokes, radius, shadows, blurs, clip (box types). |
7575| ` props ` | per type | The widget's own parameters, Flutter names (§4). |
76- | ` bind ` | ` {path: expression} ` | Properties bound to variables or formulas (` "props.text": "${user.name}" ` ). |
76+ | ` bind ` | ` {path: expression} ` | Properties bound to variables or formulas (` "props.text": "${user.name}" ` ; paths §5.1 ). |
7777| ` actions ` | ` [Reaction] ` | Trigger → action list. |
7878| ` motion ` , ` scale ` , ` loops ` , ` transitions ` , ` inkWell ` , ` states ` , ... | | Behaviours (§4.1), flat in the node. |
7979| ` extra ` | map | Unknown keys, round-tripped. |
@@ -196,6 +196,81 @@ The render layer comes from the prototype's `api_tree.dart`, with v1's
196196(` RenderFitBox ` , ` RenderCodelesslyFlex ` , ` RenderFreeformLayout ` ,
197197` RenderHugCross ` ) are the SDK's own, moved over unchanged in behaviour.
198198
199+ ### 5.1 Variables, conditions and bindings
200+
201+ ** Variables** (` Variable ` , ` lib/src/variables.dart ` ): a name, a ` VariableType `
202+ (` integer ` , ` text ` , ` decimal ` , ` boolean ` , ` color ` , ` list ` , ` map ` , ` image ` ,
203+ ` choice ` ) and a default ` value ` held as its type holds it (JSON-native:
204+ ` 3 ` , ` 4.5 ` , ` true ` , ` "#FFFF0000" ` , ` [...] ` , ` {...} ` ), or a ` formula ` (what goes
205+ in ` ${= ...} ` ) that computes it; a formula with ` parameters ` is a function
206+ formulas call by name. Choice variables list their ` options ` . v1 stored
207+ every value as text and a formula as a ` ${= ...} ` value: the migration
208+ types them once.
209+
210+ ** Conditions** (` Condition ` , ` lib/src/conditions.dart ` ): an ordered list of
211+ branches, ` if ` / ` else if ` / ` else ` . A branch has a ` when ` (` Predicate ` : a
212+ ` Comparison ` ` {left, op, right} ` with v1's loose operators, or a ` Junction `
213+ ` {"and"|"or": [...]} ` ; none for ` else ` ) and ` set ` , the node properties it
214+ gives values (` {node, path, value} ` ; a string value is an expression). For a
215+ property, the first branch that holds and sets it wins; conditions win over
216+ the node's own binding, which wins over the node's value (v1's precedence).
217+
218+ ** Where they live** : per screen, in the layout document next to the nodes
219+ (read typed through ` LayoutLogic ` / ` doc.logic ` ):
220+
221+ ``` json
222+ "variables" : {"canvas-a" : [{"name" : " count" , "type" : " integer" , "value" : 0 }]},
223+ "conditions" : {"canvas-a" : [{"id" : " c1" , "branches" : [
224+ {"when" : {"left" : " ${count}" , "op" : " greaterThan" , "right" : " 5" },
225+ "set" : [{"node" : " badge" , "path" : " visible" , "value" : true }]},
226+ {"set" : [{"node" : " badge" , "path" : " visible" , "value" : false }]}]}]}
227+ ```
228+
229+ ** Expressions** : ` ${path} ` reads a value (` user.name ` , ` cart[0].qty ` ,
230+ ` title.length ` , ` pal[item.kind] ` ), ` ${= formula} ` computes one (SDK 8.x's
231+ formula language, unchanged). A binding that is exactly one of them gives
232+ the value as is (a list stays a list); any other text with ` ${...} ` is text.
233+ A text node's own ` props.text ` may hold ` ${...} ` : it is interpolated run by
234+ run. Names come from scopes: the screen's variables, the list item (` item ` ,
235+ ` index ` , ` parent ` ), and what the app provides (` route ` , ` screen ` ,
236+ ` storage ` , ` auth ` , ` data ` ...).
237+
238+ ** Binding paths** (` Node.bind ` keys, ` BindPaths ` in ` lib/src/bindings.dart ` ):
239+ a path into the node's v2 JSON, with ` [i] ` for a list index, a paint or
240+ effect id for the item with that id, and ` * ` for every item.
241+
242+ | Path | Value | v1 key |
243+ | ---| ---| ---|
244+ | ` visible ` | bool; false collapses the node (no space, no gap) | ` visible ` |
245+ | ` opacity ` | 0–1 | ` opacity ` |
246+ | ` layout.width ` , ` layout.height ` | a number (design size, fit kept) or ` {"fill": n} ` ... | ` width ` , ` height ` |
247+ | ` layout.flex ` | int ≥ 1 | ` flex ` |
248+ | ` layout.rotation ` | degrees | ` rotation ` , ` rotationDegrees ` |
249+ | ` layout.margin ` , ` layout.padding ` | n, ` [h, v] ` , ` [l, t, r, b] ` or ` {top: n} ` | ` margin ` , ` padding ` |
250+ | ` layout.x ` , ` layout.y ` | distance from the start, or a position map | ` x ` , ` y ` |
251+ | ` layout.<field> ` | any other layout field, as its JSON | |
252+ | ` decoration.fills[0] ` | a color, image URL or paint: the first fill | ` fill ` (box types) |
253+ | ` decoration.fills.<paintId> ` | the same, for that paint | ` fill-<id> ` |
254+ | ` decoration.fills.<paintId>.color ` | a color (opacity kept) | |
255+ | ` decoration.fills.<paintId>.image.url ` | the image | ` fill-image-<id> ` |
256+ | ` decoration.fills[0].stops[i].color ` | ` #RRGGBB ` keeps the stop's alpha | ` fill-stop-<i> ` |
257+ | ` decoration.strokes[0] ` , ` decoration.strokes.<paintId> ` | a stroke paint | ` stroke ` , ` stroke-paint-<id> ` |
258+ | ` decoration.strokeWidth ` , ` decoration.radius ` | number; radius also ` [tl, tr, br, bl] ` | ` strokeWeight ` , ` cornerRadius ` |
259+ | ` decoration.effects.*.visible ` , ` .<effectId>.visible ` | bool | ` effectsVisible ` , ` effect-visible-<id> ` |
260+ | ` decoration.effects.*.color ` / ` .offset.dx ` / ` .offset.dy ` / ` .blurRadius ` / ` .spreadRadius ` | shadows | ` effectsColor ` , ` effectsOffsetX ` ... ` shadow-color-<id> ` |
261+ | ` props.<prop> ` | the widget's prop, by its declaration's codec | per type (` value ` , ` max ` , ` spacing ` ...) |
262+ | ` props.<prop>.<field> ` | a field of a structured prop | ` fillColor ` → ` props.decoration.fillColor ` |
263+ | ` props.style.<field> ` (text) | the base style and every run's | ` fill ` /` textColor ` → ` props.style.color ` , ` fontSize ` , ` fontWeight ` ... |
264+ | ` data ` | the items a list, grid or page view builds one per (its item count follows) | ` data ` |
265+ | ` scale ` , ` textDirection ` , ` motion.<field> ` , ` flightKey ` , ` tooltip ` ... | a behaviour (§4.1) | ` drawScale ` , ` layoutDirection ` , ` motion.* ` |
266+ | ` selected ` , ` disabled ` , ` semantics.* ` , ` enabled ` | reserved: kept, not applied yet | ` selected ` , ` semanticLabel ` , ` node_enabled ` |
267+
268+ Values are read loosely, as v1 read them (` "12" ` is a number, ` 1 ` true,
269+ ` "#F00F00" ` a color); a value that can't be read leaves the property as it
270+ was. The SDK compiles a node's bindings once (` NodeBindings ` ) and applies
271+ them on the typed node (no JSON round trip for the common paths), watching
272+ only the names each binding reads.
273+
199274## 6. Migration from v1
200275
201276` v2/migrate ` is a separate package. It depends on the v1 API, so it can use
@@ -206,6 +281,14 @@ imports v2, so both versions coexist without a name clash. Uses:
206281* the parity fixtures: every page in ` CodelesslyEditor/test/fixtures/legacy_pages `
207282 is migrated, and v2 must decode and render it.
208283
284+ v1 binding keys (` BaseNode.variables ` ) become §5.1's paths; a bare v1
285+ value (` dLen ` , which v1 read as ` ${dLen} ` ) is written as ` ${dLen} ` , and
286+ one that read nothing in v1 (` 74.2 ` ) is dropped. A layout's v1 canvas
287+ variables and conditions (the publish model's ` SDKLayoutVariables ` /
288+ ` SDKLayoutConditions ` ) are passed to `migrateDocument(variables:,
289+ conditions:)` and become its ` variables` and ` conditions` keys; each
290+ condition's set-value actions become ` set ` entries on binding paths.
291+
209292## 7. Proving it: parity
210293
211294v2 has to render existing layouts the way v1 does. Goldens come from v1 (the
0 commit comments