Skip to content

Commit e2a68ab

Browse files
committed
Add spacing to row/column nodes
RowColumnMixin.spacing (default 0) is the gap between consecutive children along the main axis, like Flutter's Row/Column spacing and Figma auto-layout item spacing. RowColumnNode takes it in its constructor and exposes it as a layout field. JSON omits it when 0 and JSON without it loads as 0 for every RowColumnMixin node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UErtKMSFkZWHUh8DrQMcZW
1 parent 274c355 commit e2a68ab

8 files changed

Lines changed: 106 additions & 0 deletions

‎lib/src/api/mixins.dart‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,16 @@ 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+
11121122
/// Returns the main axis based on whether this is a row or column.
11131123
AxisC get mainAxis =>
11141124
(rowColumnType == RowColumnType.row) ? AxisC.horizontal : AxisC.vertical;
@@ -1122,10 +1132,12 @@ mixin RowColumnMixin on BaseNode {
11221132
required RowColumnType rowColumnType,
11231133
required MainAxisAlignmentC mainAxisAlignment,
11241134
required CrossAxisAlignmentC crossAxisAlignment,
1135+
double spacing = 0,
11251136
}) {
11261137
this.rowColumnType = rowColumnType;
11271138
this.mainAxisAlignment = mainAxisAlignment;
11281139
this.crossAxisAlignment = crossAxisAlignment;
1140+
this.spacing = spacing;
11291141
}
11301142

11311143
@override

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

Lines changed: 3 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: 3 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: 3 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: 3 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: 17 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

@@ -34,6 +35,7 @@ class RowColumnNode extends ParentNode
3435
required RowColumnType rowColumnType,
3536
MainAxisAlignmentC mainAxisAlignment = MainAxisAlignmentC.center,
3637
CrossAxisAlignmentC crossAxisAlignment = CrossAxisAlignmentC.center,
38+
double spacing = 0,
3739
super.retainedOuterBoxLocal,
3840
super.visible,
3941
super.enabled,
@@ -86,6 +88,7 @@ class RowColumnNode extends ParentNode
8688
rowColumnType: rowColumnType,
8789
mainAxisAlignment: mainAxisAlignment,
8890
crossAxisAlignment: crossAxisAlignment,
91+
spacing: spacing,
8992
);
9093

9194
setScrollableMixin(
@@ -101,6 +104,20 @@ class RowColumnNode extends ParentNode
101104
);
102105
}
103106

107+
@override
108+
FieldsMap generateFields() => {
109+
...super.generateFields(),
110+
'spacing': NumFieldAccess<double>(
111+
'Spacing',
112+
'Gap between consecutive children along the main axis.',
113+
() => spacing,
114+
(value) => spacing = value,
115+
defaultValue: () => 0,
116+
min: 0,
117+
requiresLayout: true,
118+
),
119+
};
120+
104121
/// Creates a [RowColumnNode] from a JSON data.
105122
factory RowColumnNode.fromJson(Map json) => _$RowColumnNodeFromJson(json);
106123

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

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

‎test/row_column_spacing_test.dart‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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}) => RowColumnNode(
7+
id: 'row',
8+
name: 'Row',
9+
basicBoxLocal: NodeBox(0, 0, 1072, 100),
10+
children: const ['a', 'b', 'c'],
11+
rowColumnType: RowColumnType.row,
12+
spacing: spacing,
13+
);
14+
15+
void main() {
16+
group('RowColumnNode.spacing', () {
17+
test('defaults to 0 and is omitted from JSON', () {
18+
final node = _row();
19+
expect(node.spacing, 0);
20+
expect(node.toJson().containsKey('spacing'), isFalse);
21+
});
22+
23+
test('round-trips a non-zero spacing', () {
24+
final json = jsonDecode(jsonEncode(_row(spacing: 20).toJson())) as Map;
25+
expect(json['spacing'], 20);
26+
final decoded = RowColumnNode.fromJson(json);
27+
expect(decoded.spacing, 20);
28+
expect(decoded.toJson()['spacing'], 20);
29+
});
30+
31+
test('loads JSON written without spacing', () {
32+
final json = _row().toJson()..remove('spacing');
33+
expect(RowColumnNode.fromJson(json).spacing, 0);
34+
});
35+
36+
test('accepts integer spacing in JSON', () {
37+
final json = _row().toJson()..['spacing'] = 12;
38+
expect(RowColumnNode.fromJson(json).spacing, 12.0);
39+
});
40+
41+
test('is exposed as a layout field', () {
42+
final node = _row(spacing: 8);
43+
final field = node.fields['spacing']!;
44+
expect(field.getValue(), 8);
45+
expect(field.requiresLayout, isTrue);
46+
field.setValue(16.0);
47+
expect(node.spacing, 16);
48+
});
49+
50+
test('other RowColumnMixin nodes load old JSON without spacing', () {
51+
final placeholder = AutoPlaceholderNode(
52+
id: 'placeholder',
53+
name: 'Placeholder',
54+
basicBoxLocal: NodeBox(0, 0, 100, 100),
55+
children: const [],
56+
);
57+
final json = placeholder.toJson();
58+
expect(json.containsKey('spacing'), isFalse);
59+
expect(AutoPlaceholderNode.fromJson(json).spacing, 0);
60+
});
61+
});
62+
}

0 commit comments

Comments
 (0)