Skip to content

Commit a6ecf87

Browse files
committed
RowColumnNode: spacing, wrap and runSpacing
spacing is the gap between children on the main axis, wrap makes children flow onto new runs when they don't fit, and runSpacing is the gap between runs. All default to off (0, false, 0) and are left out of the JSON at their defaults, so existing documents don't change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyH8twyrBoe4wFCTw78Cz5
1 parent 7a28374 commit a6ecf87

3 files changed

Lines changed: 133 additions & 0 deletions

File tree

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

Lines changed: 49 additions & 0 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

@@ -24,6 +25,22 @@ class RowColumnNode extends ParentNode
2425
@override
2526
set scrollDirection(AxisC value) {}
2627

28+
/// The gap between consecutive children along the main axis, in logical
29+
/// pixels. Corresponds to [Flex.spacing] in Flutter, or [Wrap.spacing] when
30+
/// [wrap] is true.
31+
double spacing;
32+
33+
/// Whether children that don't fit along the main axis flow onto new runs,
34+
/// like Flutter's [Wrap], instead of overflowing in a single line.
35+
///
36+
/// Expanded and flexible children can't flex inside a wrap; they keep their
37+
/// design size along the main axis.
38+
bool wrap;
39+
40+
/// The gap between runs along the cross axis when [wrap] is true.
41+
/// Corresponds to [Wrap.runSpacing] in Flutter.
42+
double runSpacing;
43+
2744
/// Creates a [RowColumnNode] with the given data.
2845
RowColumnNode({
2946
required super.id,
@@ -34,6 +51,9 @@ class RowColumnNode extends ParentNode
3451
required RowColumnType rowColumnType,
3552
MainAxisAlignmentC mainAxisAlignment = MainAxisAlignmentC.center,
3653
CrossAxisAlignmentC crossAxisAlignment = CrossAxisAlignmentC.center,
54+
this.spacing = 0,
55+
this.wrap = false,
56+
this.runSpacing = 0,
3757
super.retainedOuterBoxLocal,
3858
super.visible,
3959
super.enabled,
@@ -101,6 +121,35 @@ class RowColumnNode extends ParentNode
101121
);
102122
}
103123

124+
@override
125+
FieldsMap generateFields() => {
126+
...super.generateFields(),
127+
'spacing': NumFieldAccess<double>(
128+
'Gap',
129+
'Gap between children along the main axis.',
130+
() => spacing,
131+
(value) => spacing = value,
132+
defaultValue: () => 0,
133+
requiresLayout: true,
134+
),
135+
'wrap': BoolFieldAccess(
136+
'Wrap',
137+
'Whether children flow onto new runs when they don\'t fit.',
138+
() => wrap,
139+
(value) => wrap = value,
140+
defaultValue: () => false,
141+
requiresLayout: true,
142+
),
143+
'runSpacing': NumFieldAccess<double>(
144+
'Run Gap',
145+
'Gap between runs along the cross axis when wrapping.',
146+
() => runSpacing,
147+
(value) => runSpacing = value,
148+
defaultValue: () => 0,
149+
requiresLayout: true,
150+
),
151+
};
152+
104153
/// Creates a [RowColumnNode] from a JSON data.
105154
factory RowColumnNode.fromJson(Map json) => _$RowColumnNodeFromJson(json);
106155

‎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: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import 'dart:convert';
2+
3+
import 'package:codelessly_api/codelessly_api.dart';
4+
import 'package:test/test.dart';
5+
6+
RowColumnNode _row({double spacing = 0, bool wrap = false, double run = 0}) =>
7+
RowColumnNode(
8+
id: 'row',
9+
name: 'Row',
10+
basicBoxLocal: NodeBox(0, 0, 200, 40),
11+
children: ['a', 'b'],
12+
rowColumnType: RowColumnType.row,
13+
spacing: spacing,
14+
wrap: wrap,
15+
runSpacing: run,
16+
);
17+
18+
Map _roundTrip(Map json) => jsonDecode(jsonEncode(json)) as Map;
19+
20+
void main() {
21+
group('RowColumnNode spacing / wrap', () {
22+
test('defaults are zero gap, no wrap', () {
23+
final node = _row();
24+
expect(node.spacing, 0);
25+
expect(node.wrap, isFalse);
26+
expect(node.runSpacing, 0);
27+
});
28+
29+
test('defaults are omitted from JSON', () {
30+
final json = _row().toJson();
31+
expect(json.containsKey('spacing'), isFalse);
32+
expect(json.containsKey('wrap'), isFalse);
33+
expect(json.containsKey('runSpacing'), isFalse);
34+
});
35+
36+
test('missing keys decode to defaults', () {
37+
final json = _roundTrip(_row().toJson());
38+
final node = RowColumnNode.fromJson(json);
39+
expect(node.spacing, 0);
40+
expect(node.wrap, isFalse);
41+
expect(node.runSpacing, 0);
42+
});
43+
44+
test('non-default values round-trip', () {
45+
final json = _roundTrip(_row(spacing: 14, wrap: true, run: 6).toJson());
46+
expect(json['spacing'], 14);
47+
expect(json['wrap'], true);
48+
expect(json['runSpacing'], 6);
49+
final node = RowColumnNode.fromJson(json);
50+
expect(node.spacing, 14);
51+
expect(node.wrap, isTrue);
52+
expect(node.runSpacing, 6);
53+
});
54+
55+
test('integer JSON values decode as doubles', () {
56+
final json = _roundTrip(_row().toJson())
57+
..['spacing'] = 8
58+
..['runSpacing'] = 3;
59+
final node = RowColumnNode.fromJson(json);
60+
expect(node.spacing, 8.0);
61+
expect(node.runSpacing, 3.0);
62+
});
63+
64+
test('field access edits the new fields', () {
65+
final node = _row();
66+
final fields = node.generateFields();
67+
fields['spacing']!.setValue(12.0);
68+
fields['wrap']!.setValue(true);
69+
fields['runSpacing']!.setValue(4.0);
70+
expect(node.spacing, 12);
71+
expect(node.wrap, isTrue);
72+
expect(node.runSpacing, 4);
73+
});
74+
});
75+
}

0 commit comments

Comments
 (0)