craft-native 0.0.175 → 0.0.176
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/dist/android/src/index.d.ts +19 -0
- package/dist/android/src/index.js +19 -2
- package/dist/android/templates/AndroidManifest.xml.template +1 -0
- package/dist/android/templates/CraftBridge.kt.template +51 -0
- package/dist/android/templates/CraftCrashReporter.kt.template +544 -0
- package/dist/android/templates/CraftNative.kt.template +17 -0
- package/dist/android/templates/CraftNativeFlatList.kt.template +20 -9
- package/dist/android/templates/CraftNativeMutation.kt.template +4 -1
- package/dist/android/templates/CraftNativeServices.kt.template +31 -1
- package/dist/android/templates/MainActivityNative.kt.template +986 -304
- package/dist/android/templates/build.gradle.kts.app.template +4 -2
- package/dist/api/mobile.d.ts +31 -0
- package/dist/cli.js +1 -1
- package/dist/components/index.d.ts +203 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/ios/src/index.d.ts +19 -0
- package/dist/ios/src/index.js +7 -1
- package/dist/ios/templates/CraftApp.swift +51 -1
- package/dist/ios/templates/CraftCrashReporter.swift +1010 -0
- package/dist/ios/templates/CraftHybrid.swift +10 -0
- package/dist/ios/templates/CraftNativeActions.swift +49 -1
- package/dist/ios/templates/CraftNativeFlatList.swift +13 -21
- package/dist/ios/templates/CraftNativeMutation.swift +1 -1
- package/dist/ios/templates/CraftNativeScreen.swift +393 -55
- package/dist/ios/templates/project.yml.template +1 -0
- package/dist/macos/templates/CraftNativeMutation.swift +1 -1
- package/dist/macos/templates/native_layout.zig +389 -17
- package/dist/mobile.js +66 -0
- package/package.json +1 -1
|
@@ -305,7 +305,7 @@ final class CraftNativeMutationDocument {
|
|
|
305
305
|
static let containerTypes: Set<String> = [
|
|
306
306
|
"View", "SafeAreaView", "ScrollView", "KeyboardAvoidingView", "Modal", "Pressable",
|
|
307
307
|
"TouchableOpacity", "TouchableHighlight", "ImageBackground", "FlatList", "SectionList",
|
|
308
|
-
"Fragment", "Svg", "G",
|
|
308
|
+
"Fragment", "Svg", "G", "Map",
|
|
309
309
|
]
|
|
310
310
|
private static let containerTypeKeys = Set(containerTypes.map { $0.lowercased() })
|
|
311
311
|
|
|
@@ -147,6 +147,152 @@ pub const GridItem = extern struct {
|
|
|
147
147
|
alignment: u32,
|
|
148
148
|
};
|
|
149
149
|
|
|
150
|
+
/// Flat, C-compatible input for the host-neutral tree layout entry point.
|
|
151
|
+
///
|
|
152
|
+
/// Optional lengths use NaN as their unset value. Enum fields intentionally
|
|
153
|
+
/// use the same zero-based values as the public `Style` enums, except for
|
|
154
|
+
/// `align_self`: zero means `auto` and values one through five map to the
|
|
155
|
+
/// corresponding `AlignItems` value. Children are a contiguous preorder slice
|
|
156
|
+
/// described by `first_child` and `child_count`, so a host can build the tree
|
|
157
|
+
/// without allocating platform objects in the shared core.
|
|
158
|
+
pub const LayoutStyleInput = extern struct {
|
|
159
|
+
direction: u32,
|
|
160
|
+
width: f32,
|
|
161
|
+
height: f32,
|
|
162
|
+
intrinsic_width: f32,
|
|
163
|
+
intrinsic_height: f32,
|
|
164
|
+
baseline: f32,
|
|
165
|
+
aspect_ratio: f32,
|
|
166
|
+
flex_basis: f32,
|
|
167
|
+
flex_grow: f32,
|
|
168
|
+
flex_shrink: f32,
|
|
169
|
+
min_width: f32,
|
|
170
|
+
max_width: f32,
|
|
171
|
+
min_height: f32,
|
|
172
|
+
max_height: f32,
|
|
173
|
+
gap: f32,
|
|
174
|
+
justify_content: u32,
|
|
175
|
+
align_items: u32,
|
|
176
|
+
align_self: u32,
|
|
177
|
+
flex_wrap: u32,
|
|
178
|
+
align_content: u32,
|
|
179
|
+
margin_top: f32,
|
|
180
|
+
margin_right: f32,
|
|
181
|
+
margin_bottom: f32,
|
|
182
|
+
margin_left: f32,
|
|
183
|
+
padding_top: f32,
|
|
184
|
+
padding_right: f32,
|
|
185
|
+
padding_bottom: f32,
|
|
186
|
+
padding_left: f32,
|
|
187
|
+
grid: u32,
|
|
188
|
+
grid_columns: u32,
|
|
189
|
+
row_gap: f32,
|
|
190
|
+
column_gap: f32,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
pub const LayoutNodeInput = extern struct {
|
|
194
|
+
style: LayoutStyleInput,
|
|
195
|
+
first_child: u32,
|
|
196
|
+
child_count: u32,
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const max_layout_nodes: usize = 65_536;
|
|
200
|
+
|
|
201
|
+
/// Resolve an entire native view tree using the same layout rules used by the
|
|
202
|
+
/// host-neutral tests. Hosts may keep their platform measurement pass, flatten
|
|
203
|
+
/// the measured tree into `LayoutNodeInput`, and then apply these absolute
|
|
204
|
+
/// frames to UIKit or Android views. A non-zero result means the host should
|
|
205
|
+
/// retain its compatibility fallback; no partial frame array is considered
|
|
206
|
+
/// valid.
|
|
207
|
+
pub export fn craft_native_layout_tree(
|
|
208
|
+
viewport_width: f32,
|
|
209
|
+
viewport_height: f32,
|
|
210
|
+
nodes: [*]const LayoutNodeInput,
|
|
211
|
+
node_count: usize,
|
|
212
|
+
frames: [*]Frame,
|
|
213
|
+
frame_count: usize,
|
|
214
|
+
) callconv(.c) c_int {
|
|
215
|
+
if (node_count == 0 or node_count > max_layout_nodes or frame_count < node_count) return -1;
|
|
216
|
+
|
|
217
|
+
const input = nodes[0..node_count];
|
|
218
|
+
const visited = std.heap.page_allocator.alloc(bool, node_count) catch return -1;
|
|
219
|
+
defer std.heap.page_allocator.free(visited);
|
|
220
|
+
@memset(visited, false);
|
|
221
|
+
if (!validateLayoutTree(input, 0, visited)) return -1;
|
|
222
|
+
|
|
223
|
+
var tree = std.heap.page_allocator.alloc(Node, node_count) catch return -1;
|
|
224
|
+
defer std.heap.page_allocator.free(tree);
|
|
225
|
+
for (input, 0..) |item, index| {
|
|
226
|
+
const first = @as(usize, @intCast(item.first_child));
|
|
227
|
+
const count = @as(usize, @intCast(item.child_count));
|
|
228
|
+
tree[index] = .{
|
|
229
|
+
.style = layoutStyle(item.style),
|
|
230
|
+
.children = tree[first .. first + count],
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const result = layout(
|
|
235
|
+
tree[0],
|
|
236
|
+
.{ .width = finiteNonNegative(viewport_width), .height = finiteNonNegative(viewport_height) },
|
|
237
|
+
frames[0..frame_count],
|
|
238
|
+
) catch return -1;
|
|
239
|
+
return if (result == node_count) 0 else -1;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fn validateLayoutTree(input: []const LayoutNodeInput, index: usize, visited: []bool) bool {
|
|
243
|
+
if (index >= input.len or visited[index]) return false;
|
|
244
|
+
visited[index] = true;
|
|
245
|
+
const item = input[index];
|
|
246
|
+
const first = @as(usize, @intCast(item.first_child));
|
|
247
|
+
const count = @as(usize, @intCast(item.child_count));
|
|
248
|
+
if (first > input.len or count > input.len - first or (count > 0 and first <= index)) return false;
|
|
249
|
+
for (first..first + count) |child| {
|
|
250
|
+
if (!validateLayoutTree(input, child, visited)) return false;
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
fn layoutStyle(input: LayoutStyleInput) Style {
|
|
256
|
+
return .{
|
|
257
|
+
.direction = if (input.direction == 1) .column else .row,
|
|
258
|
+
.width = optionalFinite(input.width),
|
|
259
|
+
.height = optionalFinite(input.height),
|
|
260
|
+
.intrinsic_width = optionalFinite(input.intrinsic_width),
|
|
261
|
+
.intrinsic_height = optionalFinite(input.intrinsic_height),
|
|
262
|
+
.baseline = optionalFinite(input.baseline),
|
|
263
|
+
.aspect_ratio = optionalFinite(input.aspect_ratio),
|
|
264
|
+
.flex_basis = optionalFinite(input.flex_basis),
|
|
265
|
+
.flex_grow = finiteNonNegative(input.flex_grow),
|
|
266
|
+
.flex_shrink = finiteNonNegative(input.flex_shrink),
|
|
267
|
+
.min_width = optionalFinite(input.min_width),
|
|
268
|
+
.max_width = optionalFinite(input.max_width),
|
|
269
|
+
.min_height = optionalFinite(input.min_height),
|
|
270
|
+
.max_height = optionalFinite(input.max_height),
|
|
271
|
+
.gap = finiteNonNegative(input.gap),
|
|
272
|
+
.justify_content = @fromBackingInt(@intCast(@min(input.justify_content, 5))),
|
|
273
|
+
.align_items = @fromBackingInt(@intCast(@min(input.align_items, 4))),
|
|
274
|
+
.align_self = if (input.align_self == 0) null else @fromBackingInt(@intCast(@min(input.align_self - 1, 4))),
|
|
275
|
+
.flex_wrap = input.flex_wrap != 0,
|
|
276
|
+
.align_content = @fromBackingInt(@intCast(@min(input.align_content, 6))),
|
|
277
|
+
.margin_top = finiteNonNegative(input.margin_top),
|
|
278
|
+
.margin_right = finiteNonNegative(input.margin_right),
|
|
279
|
+
.margin_bottom = finiteNonNegative(input.margin_bottom),
|
|
280
|
+
.margin_left = finiteNonNegative(input.margin_left),
|
|
281
|
+
.padding_top = finiteNonNegative(input.padding_top),
|
|
282
|
+
.padding_right = finiteNonNegative(input.padding_right),
|
|
283
|
+
.padding_bottom = finiteNonNegative(input.padding_bottom),
|
|
284
|
+
.padding_left = finiteNonNegative(input.padding_left),
|
|
285
|
+
.grid = input.grid != 0,
|
|
286
|
+
.grid_columns = @max(@as(u32, 1), input.grid_columns),
|
|
287
|
+
.row_gap = finiteNonNegative(input.row_gap),
|
|
288
|
+
.column_gap = finiteNonNegative(input.column_gap),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
fn optionalFinite(value: f32) ?f32 {
|
|
293
|
+
return if (std.math.isFinite(value)) value else null;
|
|
294
|
+
}
|
|
295
|
+
|
|
150
296
|
/// Resolve an equal-track grid in content coordinates for native hosts.
|
|
151
297
|
pub export fn craft_native_grid(
|
|
152
298
|
available_width: f32,
|
|
@@ -591,16 +737,20 @@ fn layoutNode(
|
|
|
591
737
|
frames[frame_index] = .{ .x = x, .y = y, .width = width, .height = height };
|
|
592
738
|
|
|
593
739
|
if (node.children.len == 0) return frame_index + 1;
|
|
594
|
-
const
|
|
595
|
-
const
|
|
596
|
-
const
|
|
597
|
-
const
|
|
740
|
+
const padding_left = finiteNonNegative(node.style.padding_left);
|
|
741
|
+
const padding_right = finiteNonNegative(node.style.padding_right);
|
|
742
|
+
const padding_top = finiteNonNegative(node.style.padding_top);
|
|
743
|
+
const padding_bottom = finiteNonNegative(node.style.padding_bottom);
|
|
744
|
+
const content_width = @max(0, width - padding_left - padding_right);
|
|
745
|
+
const content_height = @max(0, height - padding_top - padding_bottom);
|
|
746
|
+
const content_x = x + padding_left;
|
|
747
|
+
const content_y = y + padding_top;
|
|
598
748
|
if (node.style.grid) return layoutGrid(node, content_width, content_height, content_x, content_y, frame_index, frames);
|
|
599
749
|
|
|
600
750
|
const main_size = if (node.style.direction == .row) content_width else content_height;
|
|
601
|
-
const
|
|
751
|
+
const main_gap = flexAxisGap(node.style, node.style.direction);
|
|
602
752
|
const gap_count = if (node.children.len > 1) node.children.len - 1 else 0;
|
|
603
|
-
const gap_total =
|
|
753
|
+
const gap_total = main_gap * @as(f32, @floatFromInt(gap_count));
|
|
604
754
|
if (node.style.flex_wrap) return layoutWrapped(node, content_width, content_height, content_x, content_y, frame_index, frames);
|
|
605
755
|
const flex = resolveFlex(node.children, node.style.direction, main_size, gap_total);
|
|
606
756
|
const line_baseline = baselineForChildren(node.children, node.style.direction, content_width, content_height, content_height, node.style.align_items);
|
|
@@ -610,7 +760,7 @@ fn layoutNode(
|
|
|
610
760
|
}
|
|
611
761
|
const free_main = @max(0, main_size - used_main);
|
|
612
762
|
var leading_space: f32 = 0;
|
|
613
|
-
var between_space: f32 =
|
|
763
|
+
var between_space: f32 = main_gap;
|
|
614
764
|
switch (node.style.justify_content) {
|
|
615
765
|
.center => leading_space = free_main / 2,
|
|
616
766
|
.flex_end => leading_space = free_main,
|
|
@@ -667,21 +817,22 @@ fn layoutWrapped(
|
|
|
667
817
|
const direction = node.style.direction;
|
|
668
818
|
const main_size = if (direction == .row) width else height;
|
|
669
819
|
const cross_size = if (direction == .row) height else width;
|
|
670
|
-
const
|
|
820
|
+
const main_gap = flexAxisGap(node.style, direction);
|
|
821
|
+
const cross_gap = crossAxisGap(node.style, direction);
|
|
671
822
|
var line_count: usize = 0;
|
|
672
823
|
var cross_total: f32 = 0;
|
|
673
824
|
var start: usize = 0;
|
|
674
825
|
while (start < node.children.len) {
|
|
675
|
-
const end = wrappedLineEnd(node.children, start, direction, main_size,
|
|
826
|
+
const end = wrappedLineEnd(node.children, start, direction, main_size, main_gap);
|
|
676
827
|
cross_total += wrappedLineCross(node.children[start..end], direction);
|
|
677
|
-
if (line_count > 0) cross_total +=
|
|
828
|
+
if (line_count > 0) cross_total += cross_gap;
|
|
678
829
|
line_count += 1;
|
|
679
830
|
start = end;
|
|
680
831
|
}
|
|
681
832
|
const free_cross = @max(0, cross_size - cross_total);
|
|
682
833
|
var line_extra: f32 = 0;
|
|
683
834
|
var cross_offset: f32 = 0;
|
|
684
|
-
var cross_between =
|
|
835
|
+
var cross_between = cross_gap;
|
|
685
836
|
switch (node.style.align_content) {
|
|
686
837
|
.center => cross_offset = free_cross / 2,
|
|
687
838
|
.flex_end => cross_offset = free_cross,
|
|
@@ -702,15 +853,15 @@ fn layoutWrapped(
|
|
|
702
853
|
var next_index = frame_index + 1;
|
|
703
854
|
start = 0;
|
|
704
855
|
while (start < node.children.len) {
|
|
705
|
-
const end = wrappedLineEnd(node.children, start, direction, main_size,
|
|
856
|
+
const end = wrappedLineEnd(node.children, start, direction, main_size, main_gap);
|
|
706
857
|
const children = node.children[start..end];
|
|
707
858
|
const line_cross = wrappedLineCross(children, direction) + line_extra;
|
|
708
|
-
const flex = resolveFlex(children, direction, main_size,
|
|
709
|
-
var used_main: f32 =
|
|
859
|
+
const flex = resolveFlex(children, direction, main_size, main_gap * @as(f32, @floatFromInt(if (children.len > 1) children.len - 1 else 0)));
|
|
860
|
+
var used_main: f32 = main_gap * @as(f32, @floatFromInt(if (children.len > 1) children.len - 1 else 0));
|
|
710
861
|
for (children) |child| used_main += flex.size(child, childMainBasis(child, direction)) + mainMargins(child, direction);
|
|
711
862
|
const free_main = @max(0, main_size - used_main);
|
|
712
863
|
var leading_space: f32 = 0;
|
|
713
|
-
var between_space: f32 =
|
|
864
|
+
var between_space: f32 = main_gap;
|
|
714
865
|
distributeJustify(node.style.justify_content, free_main, children.len, &leading_space, &between_space);
|
|
715
866
|
const line_baseline = baselineForChildren(children, direction, width, height, line_cross, node.style.align_items);
|
|
716
867
|
var main_offset = leading_space;
|
|
@@ -867,8 +1018,8 @@ fn layoutGrid(
|
|
|
867
1018
|
) LayoutError!usize {
|
|
868
1019
|
const columns = @max(@as(u32, 1), node.style.grid_columns);
|
|
869
1020
|
const column_count: usize = @intCast(columns);
|
|
870
|
-
const column_gap =
|
|
871
|
-
const row_gap =
|
|
1021
|
+
const column_gap = axisGap(node.style.column_gap, node.style.gap);
|
|
1022
|
+
const row_gap = axisGap(node.style.row_gap, node.style.gap);
|
|
872
1023
|
const available_width = @max(0, width - column_gap * @as(f32, @floatFromInt(column_count - 1)));
|
|
873
1024
|
const cell_width = available_width / @as(f32, @floatFromInt(column_count));
|
|
874
1025
|
var next_index = frame_index + 1;
|
|
@@ -1073,6 +1224,66 @@ fn finiteNonNegative(value: f32) f32 {
|
|
|
1073
1224
|
return if (!std.math.isFinite(value) or value < 0) 0 else value;
|
|
1074
1225
|
}
|
|
1075
1226
|
|
|
1227
|
+
/// `gap` is the shorthand fallback for an axis-specific gap. The flat ABI
|
|
1228
|
+
/// uses zero for an omitted row/column value, so a positive axis value wins
|
|
1229
|
+
/// while an unset value continues to inherit the shorthand.
|
|
1230
|
+
fn axisGap(explicit: f32, shorthand: f32) f32 {
|
|
1231
|
+
const value = finiteNonNegative(explicit);
|
|
1232
|
+
return if (value > 0) value else finiteNonNegative(shorthand);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
fn flexAxisGap(style: Style, direction: Axis) f32 {
|
|
1236
|
+
return if (direction == .row)
|
|
1237
|
+
axisGap(style.column_gap, style.gap)
|
|
1238
|
+
else
|
|
1239
|
+
axisGap(style.row_gap, style.gap);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
fn crossAxisGap(style: Style, direction: Axis) f32 {
|
|
1243
|
+
return if (direction == .row)
|
|
1244
|
+
axisGap(style.row_gap, style.gap)
|
|
1245
|
+
else
|
|
1246
|
+
axisGap(style.column_gap, style.gap);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
fn emptyLayoutStyleInput() LayoutStyleInput {
|
|
1250
|
+
const unset = std.math.nan(f32);
|
|
1251
|
+
return .{
|
|
1252
|
+
.direction = 0,
|
|
1253
|
+
.width = unset,
|
|
1254
|
+
.height = unset,
|
|
1255
|
+
.intrinsic_width = unset,
|
|
1256
|
+
.intrinsic_height = unset,
|
|
1257
|
+
.baseline = unset,
|
|
1258
|
+
.aspect_ratio = unset,
|
|
1259
|
+
.flex_basis = unset,
|
|
1260
|
+
.flex_grow = 0,
|
|
1261
|
+
.flex_shrink = 0,
|
|
1262
|
+
.min_width = unset,
|
|
1263
|
+
.max_width = unset,
|
|
1264
|
+
.min_height = unset,
|
|
1265
|
+
.max_height = unset,
|
|
1266
|
+
.gap = 0,
|
|
1267
|
+
.justify_content = 0,
|
|
1268
|
+
.align_items = 3,
|
|
1269
|
+
.align_self = 0,
|
|
1270
|
+
.flex_wrap = 0,
|
|
1271
|
+
.align_content = 6,
|
|
1272
|
+
.margin_top = 0,
|
|
1273
|
+
.margin_right = 0,
|
|
1274
|
+
.margin_bottom = 0,
|
|
1275
|
+
.margin_left = 0,
|
|
1276
|
+
.padding_top = 0,
|
|
1277
|
+
.padding_right = 0,
|
|
1278
|
+
.padding_bottom = 0,
|
|
1279
|
+
.padding_left = 0,
|
|
1280
|
+
.grid = 0,
|
|
1281
|
+
.grid_columns = 1,
|
|
1282
|
+
.row_gap = 0,
|
|
1283
|
+
.column_gap = 0,
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1076
1287
|
fn clampBounds(value: f32, lower: f32, upper: f32) f32 {
|
|
1077
1288
|
var result = finiteNonNegative(value);
|
|
1078
1289
|
if (std.math.isFinite(lower) and lower >= 0) result = @max(result, lower);
|
|
@@ -1088,6 +1299,92 @@ fn optionalDimension(explicit: f32, intrinsic: f32) f32 {
|
|
|
1088
1299
|
return finiteNonNegative(if (isUnset(explicit)) intrinsic else explicit);
|
|
1089
1300
|
}
|
|
1090
1301
|
|
|
1302
|
+
test "exports the host-neutral tree layout contract" {
|
|
1303
|
+
var root_style = emptyLayoutStyleInput();
|
|
1304
|
+
root_style.width = 100;
|
|
1305
|
+
root_style.height = 40;
|
|
1306
|
+
root_style.padding_left = 8;
|
|
1307
|
+
root_style.padding_top = 4;
|
|
1308
|
+
root_style.gap = 5;
|
|
1309
|
+
root_style.justify_content = 1;
|
|
1310
|
+
|
|
1311
|
+
var child_style = emptyLayoutStyleInput();
|
|
1312
|
+
child_style.intrinsic_width = 20;
|
|
1313
|
+
child_style.intrinsic_height = 10;
|
|
1314
|
+
|
|
1315
|
+
const nodes = [_]LayoutNodeInput{
|
|
1316
|
+
.{ .style = root_style, .first_child = 1, .child_count = 1 },
|
|
1317
|
+
.{ .style = child_style, .first_child = 0, .child_count = 0 },
|
|
1318
|
+
};
|
|
1319
|
+
var frames: [2]Frame = undefined;
|
|
1320
|
+
try std.testing.expectEqual(@as(c_int, 0), craft_native_layout_tree(100, 40, &nodes, nodes.len, &frames, frames.len));
|
|
1321
|
+
try std.testing.expectEqual(@as(f32, 100), frames[0].width);
|
|
1322
|
+
try std.testing.expectEqual(@as(f32, 40), frames[0].height);
|
|
1323
|
+
try std.testing.expectEqual(@as(f32, 44), frames[1].x);
|
|
1324
|
+
try std.testing.expectEqual(@as(f32, 4), frames[1].y);
|
|
1325
|
+
try std.testing.expectEqual(@as(f32, 20), frames[1].width);
|
|
1326
|
+
try std.testing.expectEqual(@as(f32, 10), frames[1].height);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
test "lays out nested flat trees with absolute descendant frames" {
|
|
1330
|
+
var root_style = emptyLayoutStyleInput();
|
|
1331
|
+
root_style.width = 200;
|
|
1332
|
+
root_style.height = 100;
|
|
1333
|
+
root_style.padding_left = 10;
|
|
1334
|
+
root_style.padding_top = 5;
|
|
1335
|
+
root_style.gap = 10;
|
|
1336
|
+
|
|
1337
|
+
var column_style = emptyLayoutStyleInput();
|
|
1338
|
+
column_style.direction = 1;
|
|
1339
|
+
column_style.width = 80;
|
|
1340
|
+
column_style.height = 60;
|
|
1341
|
+
column_style.padding_left = 4;
|
|
1342
|
+
column_style.padding_top = 3;
|
|
1343
|
+
column_style.gap = 2;
|
|
1344
|
+
|
|
1345
|
+
var first_style = emptyLayoutStyleInput();
|
|
1346
|
+
first_style.intrinsic_width = 20;
|
|
1347
|
+
first_style.intrinsic_height = 10;
|
|
1348
|
+
var second_style = emptyLayoutStyleInput();
|
|
1349
|
+
second_style.intrinsic_width = 30;
|
|
1350
|
+
second_style.intrinsic_height = 15;
|
|
1351
|
+
|
|
1352
|
+
const nodes = [_]LayoutNodeInput{
|
|
1353
|
+
.{ .style = root_style, .first_child = 1, .child_count = 1 },
|
|
1354
|
+
.{ .style = column_style, .first_child = 2, .child_count = 2 },
|
|
1355
|
+
.{ .style = first_style, .first_child = 0, .child_count = 0 },
|
|
1356
|
+
.{ .style = second_style, .first_child = 0, .child_count = 0 },
|
|
1357
|
+
};
|
|
1358
|
+
var frames: [4]Frame = undefined;
|
|
1359
|
+
try std.testing.expectEqual(@as(c_int, 0), craft_native_layout_tree(200, 100, &nodes, nodes.len, &frames, frames.len));
|
|
1360
|
+
try std.testing.expectEqual(@as(f32, 200), frames[0].width);
|
|
1361
|
+
try std.testing.expectEqual(@as(f32, 100), frames[0].height);
|
|
1362
|
+
try std.testing.expectEqual(@as(f32, 10), frames[1].x);
|
|
1363
|
+
try std.testing.expectEqual(@as(f32, 5), frames[1].y);
|
|
1364
|
+
try std.testing.expectEqual(@as(f32, 80), frames[1].width);
|
|
1365
|
+
try std.testing.expectEqual(@as(f32, 60), frames[1].height);
|
|
1366
|
+
try std.testing.expectEqual(@as(f32, 14), frames[2].x);
|
|
1367
|
+
try std.testing.expectEqual(@as(f32, 8), frames[2].y);
|
|
1368
|
+
try std.testing.expectEqual(@as(f32, 20), frames[2].width);
|
|
1369
|
+
try std.testing.expectEqual(@as(f32, 10), frames[2].height);
|
|
1370
|
+
try std.testing.expectEqual(@as(f32, 14), frames[3].x);
|
|
1371
|
+
try std.testing.expectEqual(@as(f32, 20), frames[3].y);
|
|
1372
|
+
try std.testing.expectEqual(@as(f32, 30), frames[3].width);
|
|
1373
|
+
try std.testing.expectEqual(@as(f32, 15), frames[3].height);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
test "rejects malformed host-neutral layout trees without writing frames" {
|
|
1377
|
+
var style = emptyLayoutStyleInput();
|
|
1378
|
+
style.width = 20;
|
|
1379
|
+
style.height = 20;
|
|
1380
|
+
const nodes = [_]LayoutNodeInput{
|
|
1381
|
+
.{ .style = style, .first_child = 0, .child_count = 1 },
|
|
1382
|
+
};
|
|
1383
|
+
var frames = [_]Frame{.{ .x = 7, .y = 8, .width = 9, .height = 10 }};
|
|
1384
|
+
try std.testing.expectEqual(@as(c_int, -1), craft_native_layout_tree(20, 20, &nodes, nodes.len, &frames, frames.len));
|
|
1385
|
+
try std.testing.expectEqual(@as(f32, 7), frames[0].x);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1091
1388
|
test "distributes positive flex grow from a shared basis" {
|
|
1092
1389
|
const children = [_]Node{
|
|
1093
1390
|
.{ .style = .{ .flex_basis = 40, .flex_grow = 1 } },
|
|
@@ -1219,6 +1516,32 @@ test "aligns wrapped children using align self" {
|
|
|
1219
1516
|
try std.testing.expectApproxEqAbs(@as(f32, 24), frames[3].y, 0.001);
|
|
1220
1517
|
}
|
|
1221
1518
|
|
|
1519
|
+
test "keeps independent row and column gaps in wrapped flex" {
|
|
1520
|
+
const children = [_]Node{
|
|
1521
|
+
.{ .style = .{ .width = 45, .height = 10 } },
|
|
1522
|
+
.{ .style = .{ .width = 45, .height = 10 } },
|
|
1523
|
+
.{ .style = .{ .width = 45, .height = 10 } },
|
|
1524
|
+
};
|
|
1525
|
+
const root = Node{
|
|
1526
|
+
.style = .{
|
|
1527
|
+
.direction = .row,
|
|
1528
|
+
.width = 100,
|
|
1529
|
+
.height = 80,
|
|
1530
|
+
.flex_wrap = true,
|
|
1531
|
+
.column_gap = 5,
|
|
1532
|
+
.row_gap = 12,
|
|
1533
|
+
.align_items = .flex_start,
|
|
1534
|
+
.align_content = .flex_start,
|
|
1535
|
+
},
|
|
1536
|
+
.children = &children,
|
|
1537
|
+
};
|
|
1538
|
+
var frames: [4]Frame = undefined;
|
|
1539
|
+
|
|
1540
|
+
_ = try layout(root, .{ .width = 100, .height = 80 }, &frames);
|
|
1541
|
+
try std.testing.expectApproxEqAbs(@as(f32, 50), frames[2].x, 0.001);
|
|
1542
|
+
try std.testing.expectApproxEqAbs(@as(f32, 22), frames[3].y, 0.001);
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1222
1545
|
test "uses viewport size when the root is intrinsic" {
|
|
1223
1546
|
var frames: [1]Frame = undefined;
|
|
1224
1547
|
const count = try layout(.{}, .{ .width = 320, .height = 640 }, &frames);
|
|
@@ -1309,6 +1632,55 @@ test "sanitizes non-finite tree dimensions" {
|
|
|
1309
1632
|
}
|
|
1310
1633
|
}
|
|
1311
1634
|
|
|
1635
|
+
test "sanitizes non-finite and negative tree padding" {
|
|
1636
|
+
const children = [_]Node{.{ .style = .{ .flex_basis = 10 } }};
|
|
1637
|
+
const root = Node{
|
|
1638
|
+
.style = .{
|
|
1639
|
+
.width = 100,
|
|
1640
|
+
.height = 40,
|
|
1641
|
+
.padding_left = -8,
|
|
1642
|
+
.padding_right = std.math.nan(f32),
|
|
1643
|
+
.padding_top = std.math.inf(f32),
|
|
1644
|
+
.padding_bottom = -4,
|
|
1645
|
+
},
|
|
1646
|
+
.children = &children,
|
|
1647
|
+
};
|
|
1648
|
+
var frames: [2]Frame = undefined;
|
|
1649
|
+
|
|
1650
|
+
_ = try layout(root, .{ .width = 100, .height = 40 }, &frames);
|
|
1651
|
+
try std.testing.expectApproxEqAbs(@as(f32, 0), frames[1].x, 0.001);
|
|
1652
|
+
try std.testing.expectApproxEqAbs(@as(f32, 0), frames[1].y, 0.001);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
test "sanitizes non-finite tree flex and grid gaps" {
|
|
1656
|
+
const flex_children = [_]Node{
|
|
1657
|
+
.{ .style = .{ .flex_basis = 10 } },
|
|
1658
|
+
.{ .style = .{ .flex_basis = 10 } },
|
|
1659
|
+
};
|
|
1660
|
+
const flex_root = Node{
|
|
1661
|
+
.style = .{ .direction = .row, .width = 40, .height = 20, .gap = std.math.nan(f32) },
|
|
1662
|
+
.children = &flex_children,
|
|
1663
|
+
};
|
|
1664
|
+
var flex_frames: [3]Frame = undefined;
|
|
1665
|
+
_ = try layout(flex_root, .{ .width = 40, .height = 20 }, &flex_frames);
|
|
1666
|
+
try std.testing.expectApproxEqAbs(@as(f32, 10), flex_frames[2].x, 0.001);
|
|
1667
|
+
|
|
1668
|
+
const grid_children = [_]Node{
|
|
1669
|
+
.{ .style = .{ .width = 10, .height = 10 } },
|
|
1670
|
+
.{ .style = .{ .width = 10, .height = 10 } },
|
|
1671
|
+
};
|
|
1672
|
+
const grid_root = Node{
|
|
1673
|
+
.style = .{ .grid = true, .grid_columns = 2, .width = 40, .height = 20, .column_gap = std.math.inf(f32), .row_gap = -4 },
|
|
1674
|
+
.children = &grid_children,
|
|
1675
|
+
};
|
|
1676
|
+
var grid_frames: [3]Frame = undefined;
|
|
1677
|
+
_ = try layout(grid_root, .{ .width = 40, .height = 20 }, &grid_frames);
|
|
1678
|
+
for (grid_frames) |frame| {
|
|
1679
|
+
try std.testing.expect(std.math.isFinite(frame.x));
|
|
1680
|
+
try std.testing.expect(std.math.isFinite(frame.y));
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1312
1684
|
test "recurses across row and column axes without host concepts" {
|
|
1313
1685
|
const grandchildren = [_]Node{
|
|
1314
1686
|
.{ .style = .{ .flex_basis = 10, .flex_grow = 1 } },
|
package/dist/mobile.js
CHANGED
|
@@ -713,6 +713,71 @@ var speech = {
|
|
|
713
713
|
getWebSpeech()?.synthesis.cancel();
|
|
714
714
|
}
|
|
715
715
|
};
|
|
716
|
+
function crashReporterBridge() {
|
|
717
|
+
const root = globalThis.craft;
|
|
718
|
+
return root?.crashReporter && typeof root.crashReporter.capture === "function" ? root.crashReporter : undefined;
|
|
719
|
+
}
|
|
720
|
+
var crashReporter = {
|
|
721
|
+
isAvailable() {
|
|
722
|
+
const root = globalThis.craft;
|
|
723
|
+
if (!crashReporterBridge())
|
|
724
|
+
return false;
|
|
725
|
+
const caps = root?.capabilities;
|
|
726
|
+
if (Array.isArray(caps))
|
|
727
|
+
return caps.includes("crashReporter");
|
|
728
|
+
return caps?.crashReporter !== false;
|
|
729
|
+
},
|
|
730
|
+
async capture(event) {
|
|
731
|
+
const bridge = crashReporterBridge();
|
|
732
|
+
if (!bridge || !event || typeof event.message !== "string")
|
|
733
|
+
return false;
|
|
734
|
+
try {
|
|
735
|
+
return await bridge.capture(event) !== false;
|
|
736
|
+
} catch {
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
},
|
|
740
|
+
async setUser(id) {
|
|
741
|
+
const bridge = crashReporterBridge();
|
|
742
|
+
if (!bridge)
|
|
743
|
+
return false;
|
|
744
|
+
try {
|
|
745
|
+
return await bridge.setUser(id == null ? null : String(id)) !== false;
|
|
746
|
+
} catch {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
},
|
|
750
|
+
async addBreadcrumb(breadcrumb) {
|
|
751
|
+
const bridge = crashReporterBridge();
|
|
752
|
+
if (!bridge)
|
|
753
|
+
return false;
|
|
754
|
+
try {
|
|
755
|
+
return await bridge.addBreadcrumb(breadcrumb) !== false;
|
|
756
|
+
} catch {
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
},
|
|
760
|
+
async flush() {
|
|
761
|
+
const bridge = crashReporterBridge();
|
|
762
|
+
if (!bridge)
|
|
763
|
+
return false;
|
|
764
|
+
try {
|
|
765
|
+
return await bridge.flush() !== false;
|
|
766
|
+
} catch {
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
},
|
|
770
|
+
async context() {
|
|
771
|
+
const bridge = crashReporterBridge();
|
|
772
|
+
if (!bridge)
|
|
773
|
+
return null;
|
|
774
|
+
try {
|
|
775
|
+
return await bridge.context();
|
|
776
|
+
} catch {
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
};
|
|
716
781
|
var MUSIC_AUTHORIZATIONS = ["authorized", "denied", "restricted", "notDetermined", "unavailable"];
|
|
717
782
|
var MUSIC_STATES = ["stopped", "playing", "paused", "interrupted", "seekingForward", "seekingBackward", "unknown"];
|
|
718
783
|
function musicAuthorization(value) {
|
|
@@ -1607,6 +1672,7 @@ export {
|
|
|
1607
1672
|
dialog,
|
|
1608
1673
|
device,
|
|
1609
1674
|
deepLinks,
|
|
1675
|
+
crashReporter,
|
|
1610
1676
|
contextMenu,
|
|
1611
1677
|
chrome,
|
|
1612
1678
|
camera,
|