@meonode/canvas 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +320 -225
- package/dist/cjs/canvas/canvas.type.d.ts +38 -7
- package/dist/cjs/canvas/canvas.type.d.ts.map +1 -1
- package/dist/cjs/canvas/grid.canvas.util.d.ts +25 -18
- package/dist/cjs/canvas/grid.canvas.util.d.ts.map +1 -1
- package/dist/cjs/canvas/grid.canvas.util.js +304 -210
- package/dist/cjs/canvas/grid.canvas.util.js.map +1 -1
- package/dist/cjs/canvas/text.canvas.util.d.ts.map +1 -1
- package/dist/cjs/canvas/text.canvas.util.js +84 -51
- package/dist/cjs/canvas/text.canvas.util.js.map +1 -1
- package/dist/esm/canvas/canvas.type.d.ts +38 -7
- package/dist/esm/canvas/canvas.type.d.ts.map +1 -1
- package/dist/esm/canvas/grid.canvas.util.d.ts +25 -18
- package/dist/esm/canvas/grid.canvas.util.d.ts.map +1 -1
- package/dist/esm/canvas/grid.canvas.util.js +304 -210
- package/dist/esm/canvas/text.canvas.util.d.ts.map +1 -1
- package/dist/esm/canvas/text.canvas.util.js +84 -51
- package/package.json +22 -22
|
@@ -2,276 +2,370 @@
|
|
|
2
2
|
|
|
3
3
|
var layout_canvas_util = require('./layout.canvas.util.js');
|
|
4
4
|
var common_const = require('../constant/common.const.js');
|
|
5
|
+
var canvas_helper = require('./canvas.helper.js');
|
|
5
6
|
|
|
6
|
-
// TODO: Add comprehensive unit tests for this file.
|
|
7
7
|
/**
|
|
8
|
-
* Grid layout node that arranges children in a
|
|
9
|
-
*
|
|
10
|
-
* @extends RowNode
|
|
8
|
+
* Grid layout node that arranges children in a 2D grid.
|
|
9
|
+
* Implements a simplified version of the CSS Grid Layout algorithm.
|
|
11
10
|
*/
|
|
12
11
|
class GridNode extends layout_canvas_util.RowNode {
|
|
13
|
-
columns;
|
|
14
|
-
columnGapValue;
|
|
15
|
-
rowGapValue;
|
|
16
|
-
isVertical; // True if the main axis is vertical (flexDirection: column or column-reverse)
|
|
17
12
|
/**
|
|
18
13
|
* Creates a new grid layout node
|
|
19
14
|
* @param props Grid configuration properties
|
|
20
15
|
*/
|
|
21
16
|
constructor(props) {
|
|
22
|
-
const columns = Math.max(1, props.columns || 1);
|
|
23
|
-
const direction = props.direction || 'row'; // Default to horizontal row
|
|
24
|
-
const isVertical = direction === 'column' || direction === 'column-reverse';
|
|
25
|
-
// Map direction string to Yoga FlexDirection
|
|
26
|
-
let flexDirection;
|
|
27
|
-
switch (direction) {
|
|
28
|
-
case 'row':
|
|
29
|
-
flexDirection = common_const.Style.FlexDirection.Row;
|
|
30
|
-
break;
|
|
31
|
-
case 'column':
|
|
32
|
-
flexDirection = common_const.Style.FlexDirection.Column;
|
|
33
|
-
break;
|
|
34
|
-
case 'row-reverse':
|
|
35
|
-
flexDirection = common_const.Style.FlexDirection.RowReverse;
|
|
36
|
-
break;
|
|
37
|
-
case 'column-reverse':
|
|
38
|
-
flexDirection = common_const.Style.FlexDirection.ColumnReverse;
|
|
39
|
-
break;
|
|
40
|
-
default:
|
|
41
|
-
console.warn(`[GridNode] Invalid direction "${direction}". Defaulting to "row".`);
|
|
42
|
-
flexDirection = common_const.Style.FlexDirection.Row;
|
|
43
|
-
}
|
|
44
|
-
// Determine the column and row gap values from props
|
|
45
|
-
let columnGap = 0;
|
|
46
|
-
let rowGap = 0;
|
|
47
|
-
if (typeof props.gap === 'number' || (typeof props.gap === 'string' && props.gap.trim() !== '')) {
|
|
48
|
-
// Single value applies to both row and column gaps
|
|
49
|
-
columnGap = props.gap;
|
|
50
|
-
rowGap = props.gap;
|
|
51
|
-
}
|
|
52
|
-
else if (props.gap && typeof props.gap === 'object') {
|
|
53
|
-
// Object format: prioritize a specific direction (Column/Row), then All
|
|
54
|
-
columnGap = props.gap.Column ?? props.gap.All ?? 0;
|
|
55
|
-
rowGap = props.gap.Row ?? props.gap.All ?? 0;
|
|
56
|
-
}
|
|
57
17
|
super({
|
|
58
|
-
name: 'Grid',
|
|
59
|
-
flexWrap: common_const.Style.Wrap.Wrap, // Essential for grid behavior
|
|
60
|
-
flexDirection,
|
|
61
18
|
...props,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// Pass undefined for gap to prevent BoxNode from trying to parse it
|
|
65
|
-
gap: undefined,
|
|
19
|
+
name: props.name || 'Grid',
|
|
20
|
+
flexWrap: common_const.Style.Wrap.Wrap,
|
|
66
21
|
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
this.node.setGap(common_const.Style.Gutter.Column, columnGap);
|
|
75
|
-
}
|
|
76
|
-
else if (typeof columnGap === 'string' && columnGap.endsWith('%')) {
|
|
77
|
-
this.node.setGapPercent(common_const.Style.Gutter.Column, parseFloat(columnGap));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Helper to parse a track size definition.
|
|
25
|
+
*/
|
|
26
|
+
parseTrack(track, availableSpace) {
|
|
27
|
+
if (typeof track === 'number') {
|
|
28
|
+
return { type: 'px', value: track };
|
|
78
29
|
}
|
|
79
|
-
if (
|
|
80
|
-
|
|
30
|
+
if (track === 'auto') {
|
|
31
|
+
return { type: 'auto', value: 0 };
|
|
81
32
|
}
|
|
82
|
-
|
|
83
|
-
|
|
33
|
+
if (typeof track === 'string') {
|
|
34
|
+
if (track.endsWith('fr')) {
|
|
35
|
+
return { type: 'fr', value: parseFloat(track) };
|
|
36
|
+
}
|
|
37
|
+
if (track.endsWith('%')) {
|
|
38
|
+
return { type: '%', value: canvas_helper.parsePercentage(track, availableSpace) };
|
|
39
|
+
}
|
|
40
|
+
// Try parsing as number (px) if just string "100"
|
|
41
|
+
const num = parseFloat(track);
|
|
42
|
+
if (!isNaN(num))
|
|
43
|
+
return { type: 'px', value: num };
|
|
84
44
|
}
|
|
45
|
+
return { type: 'auto', value: 0 };
|
|
85
46
|
}
|
|
86
47
|
/**
|
|
87
|
-
*
|
|
88
|
-
* Overridden primarily for documentation/clarity, functionality is inherited.
|
|
89
|
-
* @param child Child node to append
|
|
90
|
-
* @param index Index at which to insert the child
|
|
48
|
+
* Parses the gap property into pixels.
|
|
91
49
|
*/
|
|
92
|
-
|
|
93
|
-
|
|
50
|
+
getGapPixels(gap, width, height) {
|
|
51
|
+
let rowGap = 0;
|
|
52
|
+
let colGap = 0;
|
|
53
|
+
if (typeof gap === 'number') {
|
|
54
|
+
rowGap = colGap = gap;
|
|
55
|
+
}
|
|
56
|
+
else if (typeof gap === 'string') {
|
|
57
|
+
const val = canvas_helper.parsePercentage(gap, width); // Use width as base for simplicity if %
|
|
58
|
+
rowGap = colGap = val;
|
|
59
|
+
}
|
|
60
|
+
else if (gap && typeof gap === 'object') {
|
|
61
|
+
const colVal = gap.Column ?? gap.All ?? 0;
|
|
62
|
+
const rowVal = gap.Row ?? gap.All ?? 0;
|
|
63
|
+
colGap = canvas_helper.parsePercentage(colVal, width);
|
|
64
|
+
rowGap = canvas_helper.parsePercentage(rowVal, height);
|
|
65
|
+
}
|
|
66
|
+
return { rowGap, colGap };
|
|
94
67
|
}
|
|
95
68
|
/**
|
|
96
69
|
* Update layout calculations after the initial layout is computed.
|
|
97
|
-
* This method calculates the appropriate flex-basis for children based on the
|
|
98
|
-
* number of columns and gaps, respecting the container's padding,
|
|
99
|
-
* and applies the gaps using Yoga's built-in properties.
|
|
100
70
|
*/
|
|
101
71
|
updateLayoutBasedOnComputedSize() {
|
|
102
|
-
//
|
|
103
|
-
if (this.columns <= 0 || this.children.length === 0) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
// Step 2: Get container dimensions and padding after the initial layout
|
|
72
|
+
// 1. Get Container Dimensions
|
|
107
73
|
const width = this.node.getComputedWidth();
|
|
108
|
-
const height = this.node.getComputedHeight();
|
|
109
74
|
const paddingLeft = this.node.getComputedPadding(common_const.Style.Edge.Left);
|
|
110
75
|
const paddingRight = this.node.getComputedPadding(common_const.Style.Edge.Right);
|
|
111
76
|
const paddingTop = this.node.getComputedPadding(common_const.Style.Edge.Top);
|
|
112
77
|
const paddingBottom = this.node.getComputedPadding(common_const.Style.Edge.Bottom);
|
|
113
|
-
// Calculate content box dimensions
|
|
114
78
|
const contentWidth = Math.max(0, width - paddingLeft - paddingRight);
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
if (this.columns > 1)
|
|
125
|
-
return;
|
|
79
|
+
const computedHeight = this.node.getComputedHeight();
|
|
80
|
+
const contentHeight = Math.max(0, computedHeight - paddingTop - paddingBottom);
|
|
81
|
+
const { templateColumns, templateRows, autoRows = 'auto', gap, columns } = this.props;
|
|
82
|
+
// 2. Resolve Gaps
|
|
83
|
+
const { rowGap, colGap } = this.getGapPixels(gap, contentWidth, contentHeight);
|
|
84
|
+
// 3. Resolve Columns (Tracks)
|
|
85
|
+
let explicitColTracks = templateColumns || [];
|
|
86
|
+
if (explicitColTracks.length === 0 && columns) {
|
|
87
|
+
explicitColTracks = Array(columns).fill('1fr');
|
|
126
88
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
89
|
+
if (explicitColTracks.length === 0)
|
|
90
|
+
explicitColTracks = ['1fr'];
|
|
91
|
+
const resolvedColTracks = this.resolveTracks(explicitColTracks, contentWidth, colGap);
|
|
92
|
+
// Pre-calculate Col Offsets needed for placement/width
|
|
93
|
+
const colOffsetsValues = [0];
|
|
94
|
+
for (let i = 0; i < resolvedColTracks.length; i++) {
|
|
95
|
+
colOffsetsValues.push(colOffsetsValues[i] + resolvedColTracks[i] + colGap);
|
|
131
96
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
97
|
+
// 4. Place Items & Resolve Explicit Row Tracks
|
|
98
|
+
const explicitRowTracks = templateRows || [];
|
|
99
|
+
const resolvedExplicitRowTracks = this.resolveTracks(explicitRowTracks, contentHeight, rowGap);
|
|
100
|
+
const cells = []; // true if occupied
|
|
101
|
+
const items = [];
|
|
102
|
+
const isOccupied = (r, c) => {
|
|
103
|
+
if (!cells[r])
|
|
104
|
+
return false;
|
|
105
|
+
return cells[r][c] === true;
|
|
106
|
+
};
|
|
107
|
+
const setOccupied = (r, c) => {
|
|
108
|
+
if (!cells[r])
|
|
109
|
+
cells[r] = [];
|
|
110
|
+
cells[r][c] = true;
|
|
111
|
+
};
|
|
112
|
+
let cursorRow = 0;
|
|
113
|
+
let cursorCol = 0;
|
|
114
|
+
for (const child of this.children) {
|
|
115
|
+
const childProps = child.props;
|
|
116
|
+
const { gridColumn, gridRow } = childProps;
|
|
117
|
+
let colStart;
|
|
118
|
+
let colEnd;
|
|
119
|
+
let colSpan = 1;
|
|
120
|
+
let rowStart;
|
|
121
|
+
let rowEnd;
|
|
122
|
+
let rowSpan = 1;
|
|
123
|
+
// ... Grid Placement Logic ...
|
|
124
|
+
if (gridColumn) {
|
|
125
|
+
const parts = gridColumn.split('/').map(s => s.trim());
|
|
126
|
+
if (parts[0]) {
|
|
127
|
+
if (parts[0].startsWith('span')) {
|
|
128
|
+
colSpan = parseInt(parts[0].replace('span', '')) || 1;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
colStart = parseInt(parts[0]) - 1;
|
|
132
|
+
}
|
|
140
133
|
}
|
|
141
|
-
|
|
142
|
-
|
|
134
|
+
if (parts[1]) {
|
|
135
|
+
if (parts[1].startsWith('span')) {
|
|
136
|
+
const span = parseInt(parts[1].replace('span', '')) || 1;
|
|
137
|
+
if (colStart !== undefined) {
|
|
138
|
+
colEnd = colStart + span;
|
|
139
|
+
colSpan = span;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
// If start is undefined but end is span? Unusual. Treat as span.
|
|
143
|
+
colSpan = span;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
colEnd = parseInt(parts[1]) - 1;
|
|
148
|
+
if (colStart !== undefined) {
|
|
149
|
+
colSpan = colEnd - colStart;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
143
152
|
}
|
|
144
153
|
}
|
|
145
|
-
|
|
146
|
-
|
|
154
|
+
if (gridRow) {
|
|
155
|
+
const parts = gridRow.split('/').map(s => s.trim());
|
|
156
|
+
if (parts[0]) {
|
|
157
|
+
if (parts[0].startsWith('span')) {
|
|
158
|
+
rowSpan = parseInt(parts[0].replace('span', '')) || 1;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
rowStart = parseInt(parts[0]) - 1;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (parts[1]) {
|
|
165
|
+
if (parts[1].startsWith('span')) {
|
|
166
|
+
const span = parseInt(parts[1].replace('span', '')) || 1;
|
|
167
|
+
if (rowStart !== undefined) {
|
|
168
|
+
rowEnd = rowStart + span;
|
|
169
|
+
rowSpan = span;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
rowSpan = span;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
rowEnd = parseInt(parts[1]) - 1;
|
|
177
|
+
if (rowStart !== undefined) {
|
|
178
|
+
rowSpan = rowEnd - rowStart;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
147
182
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
183
|
+
if (colStart !== undefined && rowStart !== undefined) ;
|
|
184
|
+
else {
|
|
185
|
+
// Auto placement
|
|
186
|
+
let placed = false;
|
|
187
|
+
while (!placed) {
|
|
188
|
+
if (!cells[cursorRow])
|
|
189
|
+
cells[cursorRow] = [];
|
|
190
|
+
if (colStart !== undefined)
|
|
191
|
+
cursorCol = colStart;
|
|
192
|
+
let fits = true;
|
|
193
|
+
for (let r = 0; r < rowSpan; r++) {
|
|
194
|
+
for (let c = 0; c < colSpan; c++) {
|
|
195
|
+
if (isOccupied(cursorRow + r, cursorCol + c)) {
|
|
196
|
+
fits = false;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (!fits)
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
if (fits) {
|
|
204
|
+
rowStart = cursorRow;
|
|
205
|
+
colStart = cursorCol;
|
|
206
|
+
placed = true;
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
cursorCol++;
|
|
210
|
+
if (cursorCol + colSpan > resolvedColTracks.length) {
|
|
211
|
+
cursorCol = 0;
|
|
212
|
+
cursorRow++;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
161
215
|
}
|
|
162
|
-
|
|
163
|
-
|
|
216
|
+
cursorCol += colSpan;
|
|
217
|
+
if (cursorCol >= resolvedColTracks.length) {
|
|
218
|
+
cursorCol = 0;
|
|
219
|
+
cursorRow++;
|
|
164
220
|
}
|
|
165
|
-
|
|
166
|
-
|
|
221
|
+
}
|
|
222
|
+
rowEnd = (rowStart ?? 0) + rowSpan;
|
|
223
|
+
colEnd = (colStart ?? 0) + colSpan;
|
|
224
|
+
for (let r = rowStart; r < rowEnd; r++) {
|
|
225
|
+
for (let c = colStart; c < colEnd; c++) {
|
|
226
|
+
setOccupied(r, c);
|
|
167
227
|
}
|
|
168
228
|
}
|
|
169
|
-
|
|
170
|
-
|
|
229
|
+
// CRITICAL FIX: Pre-set width on item to ensure height calculation is accurate later
|
|
230
|
+
const itemColStart = colStart;
|
|
231
|
+
const itemColEnd = colEnd;
|
|
232
|
+
// Extend local offsets if needed for spanned columns beyond track count (rare but safe)
|
|
233
|
+
while (colOffsetsValues.length <= itemColEnd) {
|
|
234
|
+
colOffsetsValues.push(colOffsetsValues[colOffsetsValues.length - 1] + 0 + colGap);
|
|
171
235
|
}
|
|
236
|
+
const cs = Math.min(itemColStart, colOffsetsValues.length - 1);
|
|
237
|
+
const ce = Math.min(itemColEnd, colOffsetsValues.length - 1);
|
|
238
|
+
const targetWidth = Math.max(0, colOffsetsValues[ce] - colOffsetsValues[cs] - colGap);
|
|
239
|
+
child.node.setWidth(targetWidth);
|
|
240
|
+
child.node.calculateLayout(targetWidth, Number.NaN, common_const.Style.Direction.LTR);
|
|
241
|
+
// Recursively finalize nested children (e.g. inner Grids) so their
|
|
242
|
+
// computed heights are accurate before we measure row sizes.
|
|
243
|
+
child.finalizeLayout();
|
|
244
|
+
if (child.node.isDirty()) {
|
|
245
|
+
child.node.calculateLayout(targetWidth, Number.NaN, common_const.Style.Direction.LTR);
|
|
246
|
+
}
|
|
247
|
+
items.push({ node: child, rowStart: rowStart, rowEnd: rowEnd, colStart: itemColStart, colEnd: itemColEnd });
|
|
172
248
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
// Total space taken up by gaps on the main axis
|
|
185
|
-
const totalGapSpaceOnMainAxis = this.columns > 1 ? mainAxisGapPixels * (this.columns - 1) : 0;
|
|
186
|
-
// Calculate the space available *only* for the items themselves
|
|
187
|
-
const availableSpaceOnMainAxis = Math.max(0, mainAxisContentSize - totalGapSpaceOnMainAxis);
|
|
188
|
-
// Calculate the exact pixel of the total content size that each item should occupy
|
|
189
|
-
const exactItemWidth = availableSpaceOnMainAxis / this.columns;
|
|
190
|
-
// Ensure it's not negative (shouldn't happen, but safety)
|
|
191
|
-
childWidth = Math.max(0, exactItemWidth - 0.5); // Slightly reduce to avoid rounding issues
|
|
192
|
-
}
|
|
193
|
-
else if (this.columns === 1) {
|
|
194
|
-
// If only one column, it takes up the full basis (gaps don't apply)
|
|
195
|
-
childWidth = mainAxisContentSize;
|
|
196
|
-
}
|
|
197
|
-
// Clamp basis percentage between 0 and 100 (mostly redundant after floor/max(0) but safe)
|
|
198
|
-
childWidth = Math.max(0, Math.min(mainAxisContentSize, childWidth));
|
|
199
|
-
// Step 6: Apply layout properties to children
|
|
200
|
-
let childrenNeedRecalculation = false;
|
|
201
|
-
for (const child of this.children) {
|
|
202
|
-
let childChanged = false;
|
|
203
|
-
const currentLayoutWidth = child.node.getWidth();
|
|
204
|
-
const currentWidthValue = currentLayoutWidth.value;
|
|
205
|
-
const currentWidthUnit = currentLayoutWidth.unit;
|
|
206
|
-
let widthNeedsUpdate = false;
|
|
207
|
-
if (currentWidthUnit === common_const.Style.Unit.Point) {
|
|
208
|
-
// If current width is in points, check if the value is significantly different
|
|
209
|
-
if (Math.abs(currentWidthValue - childWidth) > 0.01) {
|
|
210
|
-
widthNeedsUpdate = true;
|
|
249
|
+
// 6. Finalize Rows (Implicit)
|
|
250
|
+
const totalRowsNeeded = Math.max(resolvedExplicitRowTracks.length, ...items.map(i => i.rowEnd));
|
|
251
|
+
const resolvedRowTracks = [...resolvedExplicitRowTracks];
|
|
252
|
+
// Fill implicit rows
|
|
253
|
+
for (let r = resolvedExplicitRowTracks.length; r < totalRowsNeeded; r++) {
|
|
254
|
+
let rowSize = 0;
|
|
255
|
+
// Better 'auto' handling:
|
|
256
|
+
if (autoRows === 'auto') {
|
|
257
|
+
const rowItems = items.filter(i => i.rowStart === r && i.rowEnd - i.rowStart === 1);
|
|
258
|
+
for (const item of rowItems) {
|
|
259
|
+
rowSize = Math.max(rowSize, item.node.node.getComputedHeight());
|
|
211
260
|
}
|
|
212
261
|
}
|
|
213
262
|
else {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
if (widthNeedsUpdate) {
|
|
218
|
-
child.node.setWidth(childWidth);
|
|
219
|
-
childChanged = true;
|
|
263
|
+
const parsed = this.parseTrack(autoRows, contentHeight);
|
|
264
|
+
rowSize = parsed.value;
|
|
220
265
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
266
|
+
resolvedRowTracks.push(rowSize);
|
|
267
|
+
}
|
|
268
|
+
// 6. Calculate Offsets (Rows) & Final Layout Application
|
|
269
|
+
const colOffsets = colOffsetsValues; // Re-use
|
|
270
|
+
const rowOffsets = [0];
|
|
271
|
+
for (let i = 0; i < resolvedRowTracks.length; i++) {
|
|
272
|
+
let size = resolvedRowTracks[i];
|
|
273
|
+
// Re-check auto-sized explicit rows (value 0)
|
|
274
|
+
if (size === 0) {
|
|
275
|
+
const rowItems = items.filter(it => it.rowStart === i && it.rowEnd - it.rowStart === 1);
|
|
276
|
+
for (const item of rowItems) {
|
|
277
|
+
size = Math.max(size, item.node.node.getComputedHeight());
|
|
278
|
+
}
|
|
279
|
+
resolvedRowTracks[i] = size;
|
|
225
280
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
281
|
+
rowOffsets.push(rowOffsets[i] + size + rowGap);
|
|
282
|
+
}
|
|
283
|
+
// 7. Apply Positions
|
|
284
|
+
let childrenChanged = false;
|
|
285
|
+
for (const item of items) {
|
|
286
|
+
const x = colOffsets[item.colStart] + paddingLeft;
|
|
287
|
+
while (colOffsets.length <= item.colEnd) {
|
|
288
|
+
colOffsets.push(colOffsets[colOffsets.length - 1] + 0 + colGap);
|
|
229
289
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
290
|
+
const widthStart = colOffsets[item.colStart];
|
|
291
|
+
const widthEnd = colOffsets[item.colEnd];
|
|
292
|
+
const totalWidth = Math.max(0, widthEnd - widthStart - colGap);
|
|
293
|
+
const y = rowOffsets[item.rowStart] + paddingTop;
|
|
294
|
+
const heightStart = rowOffsets[item.rowStart];
|
|
295
|
+
const heightEnd = rowOffsets[item.rowEnd];
|
|
296
|
+
const totalHeight = Math.max(0, heightEnd - heightStart - rowGap);
|
|
297
|
+
const childNode = item.node.node;
|
|
298
|
+
if (childNode.getPositionType() !== common_const.Style.PositionType.Absolute) {
|
|
299
|
+
childNode.setPositionType(common_const.Style.PositionType.Absolute);
|
|
300
|
+
childrenChanged = true;
|
|
234
301
|
}
|
|
235
|
-
if (
|
|
236
|
-
|
|
237
|
-
|
|
302
|
+
if (childNode.getPosition(common_const.Style.Edge.Left).value !== x) {
|
|
303
|
+
childNode.setPosition(common_const.Style.Edge.Left, x);
|
|
304
|
+
childrenChanged = true;
|
|
238
305
|
}
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
|
|
306
|
+
if (childNode.getPosition(common_const.Style.Edge.Top).value !== y) {
|
|
307
|
+
childNode.setPosition(common_const.Style.Edge.Top, y);
|
|
308
|
+
childrenChanged = true;
|
|
242
309
|
}
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
|
|
310
|
+
if (childNode.getWidth().unit !== common_const.Style.Unit.Point || Math.abs(childNode.getWidth().value - totalWidth) > 0.1) {
|
|
311
|
+
childNode.setWidth(totalWidth);
|
|
312
|
+
childrenChanged = true;
|
|
246
313
|
}
|
|
247
|
-
if (
|
|
248
|
-
|
|
249
|
-
|
|
314
|
+
if (childNode.getHeight().unit !== common_const.Style.Unit.Point || Math.abs(childNode.getHeight().value - totalHeight) > 0.1) {
|
|
315
|
+
childNode.setHeight(totalHeight);
|
|
316
|
+
childrenChanged = true;
|
|
250
317
|
}
|
|
251
318
|
}
|
|
252
|
-
//
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
gapsChanged = true;
|
|
260
|
-
}
|
|
261
|
-
if (Math.abs(currentRowGap - rowGapPixels) > 0.001) {
|
|
262
|
-
this.node.setGap(common_const.Style.Gutter.Row, rowGapPixels);
|
|
263
|
-
gapsChanged = true;
|
|
319
|
+
// 9. Update Grid Height
|
|
320
|
+
const totalGridHeight = Math.max(0, rowOffsets[rowOffsets.length - 1] - rowGap);
|
|
321
|
+
const currentHeightStyle = this.node.getHeight();
|
|
322
|
+
if (currentHeightStyle.unit === common_const.Style.Unit.Auto || currentHeightStyle.unit === common_const.Style.Unit.Undefined) {
|
|
323
|
+
const targetTotalHeight = totalGridHeight + paddingTop + paddingBottom;
|
|
324
|
+
this.node.setHeight(targetTotalHeight);
|
|
325
|
+
childrenChanged = true;
|
|
264
326
|
}
|
|
265
|
-
|
|
266
|
-
if ((gapsChanged || childrenNeedRecalculation) && !this.node.isDirty()) {
|
|
327
|
+
if (childrenChanged && !this.node.isDirty()) {
|
|
267
328
|
this.node.markDirty();
|
|
268
329
|
}
|
|
269
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* Resolves track sizes to pixels.
|
|
333
|
+
*/
|
|
334
|
+
resolveTracks(tracks, availableSpace, gap) {
|
|
335
|
+
const resolved = [];
|
|
336
|
+
let usedSpace = 0;
|
|
337
|
+
let totalFr = 0;
|
|
338
|
+
const frIndices = [];
|
|
339
|
+
tracks.forEach((t, i) => {
|
|
340
|
+
const parsed = this.parseTrack(t, availableSpace);
|
|
341
|
+
if (parsed.type === 'px' || parsed.type === '%') {
|
|
342
|
+
resolved[i] = parsed.value;
|
|
343
|
+
usedSpace += parsed.value;
|
|
344
|
+
}
|
|
345
|
+
else if (parsed.type === 'fr') {
|
|
346
|
+
totalFr += parsed.value;
|
|
347
|
+
resolved[i] = 0;
|
|
348
|
+
frIndices.push(i);
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
resolved[i] = 0;
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
const totalGaps = Math.max(0, tracks.length - 1) * gap;
|
|
355
|
+
usedSpace += totalGaps;
|
|
356
|
+
const remainingSpace = Math.max(0, availableSpace - usedSpace);
|
|
357
|
+
if (totalFr > 0) {
|
|
358
|
+
frIndices.forEach(i => {
|
|
359
|
+
const parsed = this.parseTrack(tracks[i], availableSpace);
|
|
360
|
+
const share = (parsed.value / totalFr) * remainingSpace;
|
|
361
|
+
resolved[i] = share;
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
return resolved;
|
|
365
|
+
}
|
|
270
366
|
}
|
|
271
367
|
/**
|
|
272
368
|
* Factory function to create a new GridNode instance.
|
|
273
|
-
* @param props Grid configuration properties.
|
|
274
|
-
* @returns A new GridNode instance.
|
|
275
369
|
*/
|
|
276
370
|
const Grid = (props) => new GridNode(props);
|
|
277
371
|
|