Skip to content

Commit 37fe436

Browse files
Merge claude/bold-shannon-4t04vs: row/column spacing and wrap; formula values of any variable type
Row/column spacing and wrap; keep formula values of any variable type
2 parents f6b4028 + 964dbd1 commit 37fe436

12 files changed

Lines changed: 311 additions & 4 deletions

‎CHANGELOG.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
## Unreleased
2+
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+
115
## 6.1.0
216

317
### States and conditions

‎lib/src/api/mixins.dart‎

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

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

11311177
@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: 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/row_column_node.dart‎

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:json_annotation/json_annotation.dart';
22

3+
import '../field_access.dart';
34
import '../mixins.dart';
45
import '../models/models.dart';
56

@@ -16,10 +17,14 @@ class RowColumnNode extends ParentNode
1617
@override
1718
final bool supportsPadding = true;
1819

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.
1923
@JsonKey(includeFromJson: false)
2024
@override
21-
AxisC get scrollDirection =>
22-
rowColumnType == RowColumnType.row ? AxisC.horizontal : AxisC.vertical;
25+
AxisC get scrollDirection => (rowColumnType == RowColumnType.row) != wrap
26+
? AxisC.horizontal
27+
: AxisC.vertical;
2328

2429
@override
2530
set scrollDirection(AxisC value) {}
@@ -34,6 +39,9 @@ class RowColumnNode extends ParentNode
3439
required RowColumnType rowColumnType,
3540
MainAxisAlignmentC mainAxisAlignment = MainAxisAlignmentC.center,
3641
CrossAxisAlignmentC crossAxisAlignment = CrossAxisAlignmentC.center,
42+
double spacing = 0,
43+
bool wrap = false,
44+
double runSpacing = 0,
3745
super.retainedOuterBoxLocal,
3846
super.visible,
3947
super.enabled,
@@ -86,6 +94,9 @@ class RowColumnNode extends ParentNode
8694
rowColumnType: rowColumnType,
8795
mainAxisAlignment: mainAxisAlignment,
8896
crossAxisAlignment: crossAxisAlignment,
97+
spacing: spacing,
98+
wrap: wrap,
99+
runSpacing: runSpacing,
89100
);
90101

91102
setScrollableMixin(
@@ -101,6 +112,37 @@ class RowColumnNode extends ParentNode
101112
);
102113
}
103114

115+
@override
116+
FieldsMap generateFields() => {
117+
...super.generateFields(),
118+
'spacing': NumFieldAccess<double>(
119+
'Spacing',
120+
'Gap between consecutive children along the main axis.',
121+
() => spacing,
122+
(value) => spacing = value,
123+
defaultValue: () => 0,
124+
min: 0,
125+
requiresLayout: true,
126+
),
127+
'wrap': BoolFieldAccess(
128+
'Wrap',
129+
'Whether children wrap onto further runs when they do not fit.',
130+
() => wrap,
131+
(value) => wrap = value,
132+
defaultValue: () => false,
133+
requiresLayout: true,
134+
),
135+
'runSpacing': NumFieldAccess<double>(
136+
'Run Spacing',
137+
'Gap between runs along the cross axis when wrapping.',
138+
() => runSpacing,
139+
(value) => runSpacing = value,
140+
defaultValue: () => 0,
141+
min: 0,
142+
requiresLayout: true,
143+
),
144+
};
145+
104146
/// Creates a [RowColumnNode] from a JSON data.
105147
factory RowColumnNode.fromJson(Map json) => _$RowColumnNodeFromJson(json);
106148

‎lib/src/api/nodes/row_column_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.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import 'package:codelessly_api/codelessly_api.dart';
2+
import 'package:test/test.dart';
3+
4+
void main() {
5+
test('a formula value survives sanitizing for every type', () {
6+
const String formula = r"${= find(trips, 'id', selected)}";
7+
for (final VariableType type in VariableType.values) {
8+
expect(
9+
VariableData(id: 'v', name: 'v', type: type, value: formula).value,
10+
formula,
11+
reason: type.name,
12+
);
13+
}
14+
});
15+
}

0 commit comments

Comments
 (0)