Skip to content

Commit dfe57bb

Browse files
committed
Merge v2-expressions: variables, conditions, formulas, bindings and preview placeholders
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019VxYXYh9MxaHXdQQXTL5qi # Conflicts: # v2/migrate/lib/codelessly_migrate.dart
2 parents f3f848e + fb39447 commit dfe57bb

14 files changed

Lines changed: 2215 additions & 13 deletions

‎v2/DESIGN.md‎

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

211294
v2 has to render existing layouts the way v1 does. Goldens come from v1 (the

‎v2/lib/codelessly_api.dart‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
library;
33

44
export 'src/behaviours.dart';
5+
export 'src/bindings.dart';
6+
export 'src/conditions.dart';
57
export 'src/decoration.dart';
68
export 'src/document.dart';
79
export 'src/json.dart';
810
export 'src/layout.dart';
11+
export 'src/logic.dart';
912
export 'src/node.dart';
1013
export 'src/props.dart';
1114
export 'src/services/cloud.dart';
@@ -18,6 +21,7 @@ export 'src/values/enums.dart';
1821
export 'src/values/geometry.dart';
1922
export 'src/values/paint.dart';
2023
export 'src/values/text.dart';
24+
export 'src/variables.dart';
2125
export 'src/widgets/canvas.dart';
2226
export 'src/widgets/collections.dart';
2327
export 'src/widgets/indicators.dart';

0 commit comments

Comments
 (0)