Skip to content

Commit cf41355

Browse files
committed
Merge master: take its row/column spacing and wrap
Master landed RowColumnNode spacing, wrap and runSpacing in RowColumnMixin, the same JSON fields this branch added on the node. Keep master's, so a row's spacing applies whether or not it wraps; this branch keeps only the click, long-press and hold triggers rows offer. Regenerated JSON code and formatted master's wrap test for CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0147gqWiq3xoEdAGwWSpq9JK
2 parents 830076e + 37fe436 commit cf41355

12 files changed

Lines changed: 306 additions & 54 deletions

‎CHANGELOG.md‎

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
11
## Unreleased
22

3-
- `RowColumnNode.wrap`, `spacing` and `runSpacing`: a row or column flows its
4-
children onto new runs when they don't fit, like Flutter's `Wrap`.
5-
- `strokeWeight` is a bindable style property of every node with strokes.
6-
- A navigate-back (`pop`) action reads from JSON without `destinationId`,
7-
which it has no use for. It is still written, as `''`, for readers that
8-
require it.
3+
### Layout
4+
5+
- Row/column `spacing`: a gap between children along the main axis, taken
6+
out before expanded children share the free space.
7+
- Row/column `wrap` and `runSpacing`: children wrap onto further runs, like
8+
Flutter's `Wrap`.
9+
10+
### Variables
11+
12+
- A variable whose value is a `${= ...}` formula keeps it for every type
13+
(computed variables).
14+
15+
### Nodes
16+
917
- `SignatureNode` (`signature`): a box to sign in. Its `value` is a list of
1018
strokes, each a list of `[x, y]` points normalized to the box; `inkColor`,
1119
`strokeWidth` and `readOnly` are in `SignatureProperties`.
20+
- `SpacerNode` parses JSON without `positioningMode`, `aspectRatioLock` or
21+
`reactions`, like other nodes. Its constructor didn't take them, so the
22+
generated `fromJson` required them.
23+
24+
### Triggers and actions
25+
1226
- `TriggerType.hold` fires once the target has been held for the trigger's
1327
`duration` (milliseconds, 1200 by default). Frames, rectangles, rows and
1428
columns offer it.
@@ -17,14 +31,18 @@
1731
- `ShowDialogAction.presentation` (`DialogPresentation`): a dialog (the
1832
default), a bottom sheet, `adaptive` (a sheet below 600 wide, a dialog
1933
above), or full screen.
34+
- A navigate-back (`pop`) action reads from JSON without `destinationId`,
35+
which it has no use for. It is still written, as `''`, for readers that
36+
require it.
37+
38+
### Bindings
39+
40+
- `strokeWeight` is a bindable style property of every node with strokes.
2041
- `flex` is one of the properties every node can bind, set by a condition
2142
or change with an action.
2243
- `IconNode` declares `iconColor` and `iconName` as properties that can be
2344
bound, set by conditions or changed by actions; `iconName` names the
2445
Material Symbol to show.
25-
- `SpacerNode` parses JSON without `positioningMode`, `aspectRatioLock` or
26-
`reactions`, like other nodes. Its constructor didn't take them, so the
27-
generated `fromJson` required them.
2846

2947
## 6.1.0
3048

‎lib/src/api/mixins.dart‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,6 +1114,46 @@ mixin RowColumnMixin on BaseNode {
11141114
/// for details on available alignments.
11151115
late CrossAxisAlignmentC crossAxisAlignment;
11161116

1117+
/// The gap between consecutive children along the main axis, in logical
1118+
/// pixels. Corresponds to the `spacing` property of [Row] and [Column] in
1119+
/// Flutter and to item spacing in Figma auto-layout.
1120+
///
1121+
/// Unlike margins, the spacing is subtracted from the main axis extent
1122+
/// before free space is distributed to flexible children, so expanded
1123+
/// children with equal flex receive equal sizes.
1124+
@JsonKey(defaultValue: 0.0)
1125+
double spacing = 0;
1126+
1127+
/// Whether the children wrap onto further runs (lines) when they do not
1128+
/// fit along the main axis, like Flutter's [Wrap] and Figma auto-layout
1129+
/// wrap. A wrapping row lays its children out like
1130+
/// `Wrap(direction: Axis.horizontal)` and a wrapping column like
1131+
/// `Wrap(direction: Axis.vertical)`.
1132+
///
1133+
/// While wrapping:
1134+
/// - [spacing] is the gap between consecutive children within a run and
1135+
/// [runSpacing] the gap between runs. Children hidden by a visibility
1136+
/// formula take no space and no gaps.
1137+
/// - [mainAxisAlignment] aligns the children within each run (Flutter's
1138+
/// `Wrap.alignment`).
1139+
/// - [crossAxisAlignment] aligns the children within their run
1140+
/// (`Wrap.crossAxisAlignment`; stretch and baseline act as start) and
1141+
/// also aligns the runs in the cross-axis free space
1142+
/// (`Wrap.runAlignment`: start, center or end), as in Figma.
1143+
/// - Children cannot be flexible: an expanded (or flex) child is laid out
1144+
/// at its own size, as a fixed child, like inside a Flutter [Wrap].
1145+
/// A child that fills the main axis takes a whole run.
1146+
/// - A shrink-wrapping cross axis hugs the runs; a shrink-wrapping main
1147+
/// axis hugs the widest run.
1148+
@JsonKey(defaultValue: false)
1149+
bool wrap = false;
1150+
1151+
/// The gap between consecutive runs along the cross axis, in logical
1152+
/// pixels, when [wrap] is on. Corresponds to `Wrap.runSpacing` in
1153+
/// Flutter. Has no effect when [wrap] is off.
1154+
@JsonKey(defaultValue: 0.0)
1155+
double runSpacing = 0;
1156+
11171157
/// Returns the main axis based on whether this is a row or column.
11181158
AxisC get mainAxis =>
11191159
(rowColumnType == RowColumnType.row) ? AxisC.horizontal : AxisC.vertical;
@@ -1127,10 +1167,16 @@ mixin RowColumnMixin on BaseNode {
11271167
required RowColumnType rowColumnType,
11281168
required MainAxisAlignmentC mainAxisAlignment,
11291169
required CrossAxisAlignmentC crossAxisAlignment,
1170+
double spacing = 0,
1171+
bool wrap = false,
1172+
double runSpacing = 0,
11301173
}) {
11311174
this.rowColumnType = rowColumnType;
11321175
this.mainAxisAlignment = mainAxisAlignment;
11331176
this.crossAxisAlignment = crossAxisAlignment;
1177+
this.spacing = spacing;
1178+
this.wrap = wrap;
1179+
this.runSpacing = runSpacing;
11341180
}
11351181

11361182
@override

‎lib/src/api/models/variables_model.dart‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,12 @@ class VariableData
228228
String? sanitizeValueForVariableType(Object? value, VariableType type) {
229229
if (value == null) return null;
230230
// if (value.isEmpty) return '';
231-
// Do not sanitize if the value contains a variable path.
232-
if (value.toString().contains(variablePathPattern)) return value.toString();
231+
// Do not sanitize if the value contains a variable path or a formula: a
232+
// computed variable's value is a `${= ...}` formula whatever its type.
233+
if (value.toString().contains(variablePathPattern) ||
234+
value.toString().contains(r'${=')) {
235+
return value.toString();
236+
}
233237
switch (type) {
234238
case VariableType.text:
235239
case VariableType.image:

‎lib/src/api/nodes/accordion_node.g.dart‎

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎lib/src/api/nodes/auto_placeholder_node.g.dart‎

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎lib/src/api/nodes/canvas_node.g.dart‎

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎lib/src/api/nodes/expansion_tile_node.g.dart‎

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎lib/src/api/nodes/row_column_node.dart‎

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,18 @@ class RowColumnNode extends ParentNode
1717
@override
1818
final bool supportsPadding = true;
1919

20+
/// A row/column scrolls along its main axis. One that [wrap]s scrolls
21+
/// along its cross axis instead, through its runs: its main axis is where
22+
/// it wraps, so it needs a bounded extent there.
2023
@JsonKey(includeFromJson: false)
2124
@override
22-
AxisC get scrollDirection =>
23-
rowColumnType == RowColumnType.row ? AxisC.horizontal : AxisC.vertical;
25+
AxisC get scrollDirection => (rowColumnType == RowColumnType.row) != wrap
26+
? AxisC.horizontal
27+
: AxisC.vertical;
2428

2529
@override
2630
set scrollDirection(AxisC value) {}
2731

28-
/// Whether the children wrap onto new runs when they don't fit the main
29-
/// axis, like Flutter's [Wrap] widget, instead of being laid out in a single
30-
/// line.
31-
bool wrap;
32-
33-
/// The gap between adjacent children along the main axis within a run.
34-
/// Only used when [wrap] is true.
35-
double spacing;
36-
37-
/// The gap between adjacent runs along the cross axis.
38-
/// Only used when [wrap] is true.
39-
double runSpacing;
40-
4132
/// Creates a [RowColumnNode] with the given data.
4233
RowColumnNode({
4334
required super.id,
@@ -48,6 +39,9 @@ class RowColumnNode extends ParentNode
4839
required RowColumnType rowColumnType,
4940
MainAxisAlignmentC mainAxisAlignment = MainAxisAlignmentC.center,
5041
CrossAxisAlignmentC crossAxisAlignment = CrossAxisAlignmentC.center,
42+
double spacing = 0,
43+
bool wrap = false,
44+
double runSpacing = 0,
5145
super.retainedOuterBoxLocal,
5246
super.visible,
5347
super.enabled,
@@ -90,9 +84,6 @@ class RowColumnNode extends ParentNode
9084
ScrollViewKeyboardDismissBehaviorC.manual,
9185
bool useFlutterListView = false,
9286
bool shouldAlwaysScroll = true,
93-
this.wrap = false,
94-
this.spacing = 0,
95-
this.runSpacing = 0,
9687
}) {
9788
setCornerMixin(
9889
cornerRadius: cornerRadius,
@@ -103,6 +94,9 @@ class RowColumnNode extends ParentNode
10394
rowColumnType: rowColumnType,
10495
mainAxisAlignment: mainAxisAlignment,
10596
crossAxisAlignment: crossAxisAlignment,
97+
spacing: spacing,
98+
wrap: wrap,
99+
runSpacing: runSpacing,
106100
);
107101

108102
setScrollableMixin(
@@ -118,7 +112,6 @@ class RowColumnNode extends ParentNode
118112
);
119113
}
120114

121-
/// Creates a [RowColumnNode] from a JSON data.
122115
/// Rows and columns are the usual buttons and cards of a layout, so they
123116
/// take taps, long presses and holds.
124117
@override
@@ -131,35 +124,35 @@ class RowColumnNode extends ParentNode
131124
@override
132125
FieldsMap generateFields() => {
133126
...super.generateFields(),
134-
'wrap': BoolFieldAccess(
135-
'Wrap',
136-
'Whether the children wrap onto new runs when they overflow.',
137-
() => wrap,
138-
(value) => wrap = value,
139-
defaultValue: () => false,
140-
requiresLayout: true,
141-
),
142127
'spacing': NumFieldAccess<double>(
143128
'Spacing',
144-
'Gap between children within a run when wrapping.',
129+
'Gap between consecutive children along the main axis.',
145130
() => spacing,
146131
(value) => spacing = value,
147-
min: 0,
148132
defaultValue: () => 0,
133+
min: 0,
134+
requiresLayout: true,
135+
),
136+
'wrap': BoolFieldAccess(
137+
'Wrap',
138+
'Whether children wrap onto further runs when they do not fit.',
139+
() => wrap,
140+
(value) => wrap = value,
141+
defaultValue: () => false,
149142
requiresLayout: true,
150143
),
151144
'runSpacing': NumFieldAccess<double>(
152145
'Run Spacing',
153-
'Gap between runs when wrapping.',
146+
'Gap between runs along the cross axis when wrapping.',
154147
() => runSpacing,
155148
(value) => runSpacing = value,
156-
min: 0,
157149
defaultValue: () => 0,
150+
min: 0,
158151
requiresLayout: true,
159152
),
160153
};
161154

162-
/// Creates a [RowColumnNode] from [json].
155+
/// Creates a [RowColumnNode] from a JSON data.
163156
factory RowColumnNode.fromJson(Map json) => _$RowColumnNodeFromJson(json);
164157

165158
@override

0 commit comments

Comments
 (0)