craft-native 0.0.247 → 0.0.248
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 +2 -0
- package/dist/android/src/index.js +22 -18
- package/dist/android/templates/CraftNative.kt.template +17 -0
- package/dist/android/templates/CraftNativeFlatList.kt.template +115 -40
- package/dist/android/templates/CraftNativeMapGoogle.kt.template +92 -5
- package/dist/android/templates/CraftNativeMapLibre.kt.template +90 -3
- package/dist/android/templates/CraftNativeMutation.kt.template +32 -0
- package/dist/android/templates/CraftNativeRefreshableFlatList.kt.template +7 -2
- package/dist/android/templates/CraftNativeServices.kt.template +25 -0
- package/dist/android/templates/CraftNativeShell.kt.template +34 -8
- package/dist/android/templates/MainActivityNative.kt.template +369 -69
- package/dist/android/templates/build.gradle.kts.project.template +1 -1
- package/dist/api/mobile.d.ts +37 -1
- package/dist/browser.js +9 -9
- package/dist/cli.js +90 -673
- package/dist/components/index.d.ts +50 -9
- package/dist/index.cjs +394 -438
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +446 -493
- package/dist/ios/src/index.d.ts +2 -0
- package/dist/ios/src/index.js +54 -53
- package/dist/ios/templates/CraftApp.swift +37 -2
- package/dist/ios/templates/CraftHealth.swift +217 -0
- package/dist/ios/templates/CraftHeartRate.swift +6 -3
- package/dist/ios/templates/CraftHybrid.swift +18 -19
- package/dist/ios/templates/CraftLiveActivities.swift +1 -1
- package/dist/ios/templates/CraftNativeActions.swift +87 -8
- package/dist/ios/templates/CraftNativeFiles.swift +15 -3
- package/dist/ios/templates/CraftNativeFlatList.swift +18 -1
- package/dist/ios/templates/CraftNativeMutation.swift +36 -0
- package/dist/ios/templates/CraftNativeScreen.swift +813 -65
- package/dist/ios/templates/CraftNativeServices.swift +4 -2
- package/dist/ios/templates/CraftNativeUI.swift +10 -2
- package/dist/ios/templates/testing/DriverTests.swift.template +1 -0
- package/dist/ios/templates/testing/HostApp.swift +9 -2
- package/dist/macos/templates/CraftNativeMutation.swift +36 -0
- package/dist/macos/templates/native_layout.zig +226 -2
- package/dist/mobile-testing.js +2 -2
- package/dist/mobile.js +101 -63
- package/package.json +1 -1
|
@@ -313,8 +313,10 @@ final class CraftNativeSounds {
|
|
|
313
313
|
func play(id: String, url: URL, options: [String: Any], answer: @escaping (Result<Any, CraftNativeActionError>) -> Void) {
|
|
314
314
|
stop(id: id, fadeOut: 0)
|
|
315
315
|
let loop = options["loop"] as? Bool ?? false
|
|
316
|
-
let
|
|
317
|
-
let
|
|
316
|
+
let volumeValue = craftNativeNumber(options["volume"]).map { $0.doubleValue }.flatMap { $0.isFinite ? $0 : nil } ?? 1
|
|
317
|
+
let fadeInValue = craftNativeNumber(options["fadeIn"]).map { $0.doubleValue }.flatMap { $0.isFinite ? $0 : nil } ?? 0
|
|
318
|
+
let volume = Float(min(max(volumeValue, 0), 1))
|
|
319
|
+
let fadeIn = max(0, fadeInValue) / 1000
|
|
318
320
|
Self.claimSession(respectSilentSwitch: options["respectSilentSwitch"] as? Bool ?? true)
|
|
319
321
|
let item = AVPlayerItem(url: url)
|
|
320
322
|
let player = AVQueuePlayer()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import AuthenticationServices
|
|
2
|
+
import CoreFoundation
|
|
2
3
|
import SafariServices
|
|
3
4
|
import UIKit
|
|
4
5
|
import WebKit
|
|
@@ -54,7 +55,12 @@ enum CraftPresenter {
|
|
|
54
55
|
/// view's points, as a rect. Nil when it is not one.
|
|
55
56
|
static func rect(_ value: Any?) -> CGRect? {
|
|
56
57
|
guard let value = value as? [String: Any] else { return nil }
|
|
57
|
-
func number(_ key: String) -> CGFloat? {
|
|
58
|
+
func number(_ key: String) -> CGFloat? {
|
|
59
|
+
guard let number = value[key] as? NSNumber,
|
|
60
|
+
CFGetTypeID(number) != CFBooleanGetTypeID() else { return nil }
|
|
61
|
+
let value = CGFloat(truncating: number)
|
|
62
|
+
return value.isFinite ? value : nil
|
|
63
|
+
}
|
|
58
64
|
guard let x = number("x"), let y = number("y") else { return nil }
|
|
59
65
|
return CGRect(x: x, y: y, width: max(0, number("width") ?? 0), height: max(0, number("height") ?? 0))
|
|
60
66
|
}
|
|
@@ -464,7 +470,9 @@ extension CraftSignInWithApple: ASAuthorizationControllerDelegate, ASAuthorizati
|
|
|
464
470
|
/// show the same glyph the system's own controls use.
|
|
465
471
|
enum CraftSymbols {
|
|
466
472
|
static func image(named name: String, options: [String: Any]) -> String? {
|
|
467
|
-
let pointSize = (options["pointSize"]
|
|
473
|
+
let pointSize = craftNativeNumber(options["pointSize"])
|
|
474
|
+
.map { CGFloat(truncating: $0) }
|
|
475
|
+
.flatMap { $0.isFinite ? $0 : nil } ?? 17
|
|
468
476
|
let weight: UIImage.SymbolWeight
|
|
469
477
|
switch options["weight"] as? String {
|
|
470
478
|
case "ultraLight": weight = .ultraLight
|
|
@@ -276,6 +276,7 @@ final class DriverTests: XCTestCase {
|
|
|
276
276
|
let host = XCUIApplication(bundleIdentifier: "{{BUNDLE_PREFIX}}.host")
|
|
277
277
|
host.launchArguments = ["--craft-universal-link", url.absoluteString]
|
|
278
278
|
host.launch()
|
|
279
|
+
host.buttons["craft-driver-open-universal-link"].tap()
|
|
279
280
|
let opened = app.wait(for: .runningForeground, timeout: 75)
|
|
280
281
|
if !opened {
|
|
281
282
|
post("/save?name=universal-link-host.txt", Data(host.debugDescription.utf8))
|
|
@@ -7,8 +7,15 @@ struct HostApp: App {
|
|
|
7
7
|
|
|
8
8
|
var body: some Scene {
|
|
9
9
|
WindowGroup {
|
|
10
|
-
|
|
11
|
-
.
|
|
10
|
+
VStack {
|
|
11
|
+
Text(status).accessibilityIdentifier("craft-driver-status")
|
|
12
|
+
// XCTest must finish acquiring the host's launch assertion
|
|
13
|
+
// before a handoff can move another application foreground.
|
|
14
|
+
Button("Open Universal Link") {
|
|
15
|
+
Task { await openUniversalLinkIfRequested() }
|
|
16
|
+
}
|
|
17
|
+
.accessibilityIdentifier("craft-driver-open-universal-link")
|
|
18
|
+
}
|
|
12
19
|
}
|
|
13
20
|
}
|
|
14
21
|
|
|
@@ -108,6 +108,13 @@ final class CraftNativeMutationDocument {
|
|
|
108
108
|
case "moveChild":
|
|
109
109
|
try moveChild(operation, nodes: &candidateNodes)
|
|
110
110
|
if let id = operation["parentId"] as? String { appendUnique(id, to: &affectedNodeIds) }
|
|
111
|
+
case "reparentChild":
|
|
112
|
+
let previousParent = try reparentChild(operation, nodes: &candidateNodes)
|
|
113
|
+
if let id = operation["parentId"] as? String { appendUnique(id, to: &affectedNodeIds) }
|
|
114
|
+
appendUnique(previousParent, to: &affectedNodeIds)
|
|
115
|
+
// A child crossing parents needs the committed tree to be
|
|
116
|
+
// reconciled so the host can move its existing native view.
|
|
117
|
+
requiresFullRender = true
|
|
111
118
|
case "removeNode":
|
|
112
119
|
if let id = operation["id"] as? String, let parent = candidateNodes[id]?.parent {
|
|
113
120
|
appendUnique(parent, to: &affectedNodeIds)
|
|
@@ -251,6 +258,35 @@ final class CraftNativeMutationDocument {
|
|
|
251
258
|
nodes[parentId] = parent
|
|
252
259
|
}
|
|
253
260
|
|
|
261
|
+
private func reparentChild(_ operation: [String: Any], nodes: inout [String: Node]) throws -> String {
|
|
262
|
+
let parentId = try requiredId(operation, field: "parentId", name: "reparentChild")
|
|
263
|
+
let childId = try requiredId(operation, field: "childId", name: "reparentChild")
|
|
264
|
+
guard var parent = nodes[parentId] else {
|
|
265
|
+
throw CraftNativeMutationFailure("UNKNOWN_PARENT", "node \(parentId) does not exist")
|
|
266
|
+
}
|
|
267
|
+
guard var child = nodes[childId], let previousParentId = child.parent,
|
|
268
|
+
var previousParent = nodes[previousParentId] else {
|
|
269
|
+
throw CraftNativeMutationFailure("NOT_ATTACHED", "node \(childId) is not attached to a parent")
|
|
270
|
+
}
|
|
271
|
+
guard acceptsChildren(parent.type) else {
|
|
272
|
+
throw CraftNativeMutationFailure("INVALID_PARENT", "node \(parentId) cannot contain child nodes")
|
|
273
|
+
}
|
|
274
|
+
guard parentId != childId, !descendants(of: childId, nodes: nodes).contains(parentId) else {
|
|
275
|
+
throw CraftNativeMutationFailure("CYCLE", "reparenting node \(childId) would create a cycle")
|
|
276
|
+
}
|
|
277
|
+
guard parentId != previousParentId else {
|
|
278
|
+
throw CraftNativeMutationFailure("SAME_PARENT", "node \(childId) is already a child of \(parentId)")
|
|
279
|
+
}
|
|
280
|
+
let index = try insertionIndex(operation, count: parent.children.count)
|
|
281
|
+
previousParent.children.removeAll { $0 == childId }
|
|
282
|
+
parent.children.insert(childId, at: index)
|
|
283
|
+
child.parent = parentId
|
|
284
|
+
nodes[previousParentId] = previousParent
|
|
285
|
+
nodes[parentId] = parent
|
|
286
|
+
nodes[childId] = child
|
|
287
|
+
return previousParentId
|
|
288
|
+
}
|
|
289
|
+
|
|
254
290
|
private func removeNode(
|
|
255
291
|
_ operation: [String: Any],
|
|
256
292
|
nodes: inout [String: Node],
|
|
@@ -105,6 +105,8 @@ pub const Style = struct {
|
|
|
105
105
|
/// ABI and remains host-neutral because hosts resolve percentages before
|
|
106
106
|
/// serializing the style.
|
|
107
107
|
grid_auto_rows: ?f32 = null,
|
|
108
|
+
/// Explicit equal row-track count. Null means derive rows from children.
|
|
109
|
+
grid_rows: ?u32 = null,
|
|
108
110
|
};
|
|
109
111
|
|
|
110
112
|
pub const Node = struct {
|
|
@@ -297,6 +299,51 @@ pub const LayoutNodeInputV4 = extern struct {
|
|
|
297
299
|
child_count: u32,
|
|
298
300
|
};
|
|
299
301
|
|
|
302
|
+
/// v5 appends the resolved explicit grid-row count to the v4 payload.
|
|
303
|
+
pub const LayoutStyleInputV5 = extern struct {
|
|
304
|
+
direction: u32,
|
|
305
|
+
width: f32,
|
|
306
|
+
height: f32,
|
|
307
|
+
intrinsic_width: f32,
|
|
308
|
+
intrinsic_height: f32,
|
|
309
|
+
baseline: f32,
|
|
310
|
+
aspect_ratio: f32,
|
|
311
|
+
flex_basis: f32,
|
|
312
|
+
flex_grow: f32,
|
|
313
|
+
flex_shrink: f32,
|
|
314
|
+
min_width: f32,
|
|
315
|
+
max_width: f32,
|
|
316
|
+
min_height: f32,
|
|
317
|
+
max_height: f32,
|
|
318
|
+
gap: f32,
|
|
319
|
+
justify_content: u32,
|
|
320
|
+
align_items: u32,
|
|
321
|
+
align_self: u32,
|
|
322
|
+
flex_wrap: u32,
|
|
323
|
+
align_content: u32,
|
|
324
|
+
margin_top: f32,
|
|
325
|
+
margin_right: f32,
|
|
326
|
+
margin_bottom: f32,
|
|
327
|
+
margin_left: f32,
|
|
328
|
+
padding_top: f32,
|
|
329
|
+
padding_right: f32,
|
|
330
|
+
padding_bottom: f32,
|
|
331
|
+
padding_left: f32,
|
|
332
|
+
grid: u32,
|
|
333
|
+
grid_columns: u32,
|
|
334
|
+
row_gap: f32,
|
|
335
|
+
column_gap: f32,
|
|
336
|
+
reverse: u32,
|
|
337
|
+
grid_auto_rows: f32,
|
|
338
|
+
grid_rows: u32,
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
pub const LayoutNodeInputV5 = extern struct {
|
|
342
|
+
style: LayoutStyleInputV5,
|
|
343
|
+
first_child: u32,
|
|
344
|
+
child_count: u32,
|
|
345
|
+
};
|
|
346
|
+
|
|
300
347
|
const max_layout_nodes: usize = 65_536;
|
|
301
348
|
|
|
302
349
|
/// Resolve an entire native view tree using the same layout rules used by the
|
|
@@ -452,6 +499,43 @@ pub export fn craft_native_layout_tree_v4(
|
|
|
452
499
|
return if (result == node_count) 0 else -1;
|
|
453
500
|
}
|
|
454
501
|
|
|
502
|
+
/// v5 tree layout entry point. It preserves v4's auto-row and reverse fields,
|
|
503
|
+
/// adding an explicit equal-track row count for gridTemplateRows.
|
|
504
|
+
pub export fn craft_native_layout_tree_v5(
|
|
505
|
+
viewport_width: f32,
|
|
506
|
+
viewport_height: f32,
|
|
507
|
+
nodes: [*]const LayoutNodeInputV5,
|
|
508
|
+
node_count: usize,
|
|
509
|
+
frames: [*]Frame,
|
|
510
|
+
frame_count: usize,
|
|
511
|
+
) callconv(.c) c_int {
|
|
512
|
+
if (node_count == 0 or node_count > max_layout_nodes or frame_count < node_count) return -1;
|
|
513
|
+
|
|
514
|
+
const input = nodes[0..node_count];
|
|
515
|
+
const visited = std.heap.page_allocator.alloc(bool, node_count) catch return -1;
|
|
516
|
+
defer std.heap.page_allocator.free(visited);
|
|
517
|
+
@memset(visited, false);
|
|
518
|
+
if (!validateLayoutTreeV5(input, 0, visited)) return -1;
|
|
519
|
+
|
|
520
|
+
var tree = std.heap.page_allocator.alloc(Node, node_count) catch return -1;
|
|
521
|
+
defer std.heap.page_allocator.free(tree);
|
|
522
|
+
for (input, 0..) |item, index| {
|
|
523
|
+
const first = @as(usize, @intCast(item.first_child));
|
|
524
|
+
const count = @as(usize, @intCast(item.child_count));
|
|
525
|
+
tree[index] = .{
|
|
526
|
+
.style = layoutStyleV5(item.style),
|
|
527
|
+
.children = tree[first .. first + count],
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const result = layout(
|
|
532
|
+
tree[0],
|
|
533
|
+
.{ .width = finiteNonNegative(viewport_width), .height = finiteNonNegative(viewport_height) },
|
|
534
|
+
frames[0..frame_count],
|
|
535
|
+
) catch return -1;
|
|
536
|
+
return if (result == node_count) 0 else -1;
|
|
537
|
+
}
|
|
538
|
+
|
|
455
539
|
fn validateLayoutTree(input: []const LayoutNodeInput, index: usize, visited: []bool) bool {
|
|
456
540
|
if (index >= input.len or visited[index]) return false;
|
|
457
541
|
visited[index] = true;
|
|
@@ -491,6 +575,19 @@ fn validateLayoutTreeV4(input: []const LayoutNodeInputV4, index: usize, visited:
|
|
|
491
575
|
return true;
|
|
492
576
|
}
|
|
493
577
|
|
|
578
|
+
fn validateLayoutTreeV5(input: []const LayoutNodeInputV5, index: usize, visited: []bool) bool {
|
|
579
|
+
if (index >= input.len or visited[index]) return false;
|
|
580
|
+
visited[index] = true;
|
|
581
|
+
const item = input[index];
|
|
582
|
+
const first = @as(usize, @intCast(item.first_child));
|
|
583
|
+
const count = @as(usize, @intCast(item.child_count));
|
|
584
|
+
if (first > input.len or count > input.len - first or (count > 0 and first <= index)) return false;
|
|
585
|
+
for (first..first + count) |child| {
|
|
586
|
+
if (!validateLayoutTreeV5(input, child, visited)) return false;
|
|
587
|
+
}
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
|
|
494
591
|
fn layoutStyle(input: LayoutStyleInput) Style {
|
|
495
592
|
return .{
|
|
496
593
|
.direction = if (input.direction == 1) .column else .row,
|
|
@@ -614,6 +711,47 @@ fn layoutStyleV4(input: LayoutStyleInputV4) Style {
|
|
|
614
711
|
};
|
|
615
712
|
}
|
|
616
713
|
|
|
714
|
+
fn layoutStyleV5(input: LayoutStyleInputV5) Style {
|
|
715
|
+
return .{
|
|
716
|
+
.direction = if (input.direction == 1) .column else .row,
|
|
717
|
+
.reverse = (input.reverse & 1) != 0,
|
|
718
|
+
.wrap_reverse = (input.reverse & 2) != 0,
|
|
719
|
+
.width = optionalFinite(input.width),
|
|
720
|
+
.height = optionalFinite(input.height),
|
|
721
|
+
.intrinsic_width = optionalFinite(input.intrinsic_width),
|
|
722
|
+
.intrinsic_height = optionalFinite(input.intrinsic_height),
|
|
723
|
+
.baseline = optionalFinite(input.baseline),
|
|
724
|
+
.aspect_ratio = optionalFinite(input.aspect_ratio),
|
|
725
|
+
.flex_basis = optionalFinite(input.flex_basis),
|
|
726
|
+
.flex_grow = finiteNonNegative(input.flex_grow),
|
|
727
|
+
.flex_shrink = finiteNonNegative(input.flex_shrink),
|
|
728
|
+
.min_width = optionalFinite(input.min_width),
|
|
729
|
+
.max_width = optionalFinite(input.max_width),
|
|
730
|
+
.min_height = optionalFinite(input.min_height),
|
|
731
|
+
.max_height = optionalFinite(input.max_height),
|
|
732
|
+
.gap = finiteNonNegative(input.gap),
|
|
733
|
+
.justify_content = @fromBackingInt(@intCast(@min(input.justify_content, 5))),
|
|
734
|
+
.align_items = @fromBackingInt(@intCast(@min(input.align_items, 4))),
|
|
735
|
+
.align_self = if (input.align_self == 0) null else @fromBackingInt(@intCast(@min(input.align_self - 1, 4))),
|
|
736
|
+
.flex_wrap = input.flex_wrap != 0,
|
|
737
|
+
.align_content = @fromBackingInt(@intCast(@min(input.align_content, 6))),
|
|
738
|
+
.margin_top = finiteNonNegative(input.margin_top),
|
|
739
|
+
.margin_right = finiteNonNegative(input.margin_right),
|
|
740
|
+
.margin_bottom = finiteNonNegative(input.margin_bottom),
|
|
741
|
+
.margin_left = finiteNonNegative(input.margin_left),
|
|
742
|
+
.padding_top = finiteNonNegative(input.padding_top),
|
|
743
|
+
.padding_right = finiteNonNegative(input.padding_right),
|
|
744
|
+
.padding_bottom = finiteNonNegative(input.padding_bottom),
|
|
745
|
+
.padding_left = finiteNonNegative(input.padding_left),
|
|
746
|
+
.grid = input.grid != 0,
|
|
747
|
+
.grid_columns = @max(@as(u32, 1), input.grid_columns),
|
|
748
|
+
.row_gap = finiteNonNegative(input.row_gap),
|
|
749
|
+
.column_gap = finiteNonNegative(input.column_gap),
|
|
750
|
+
.grid_auto_rows = optionalFinite(input.grid_auto_rows),
|
|
751
|
+
.grid_rows = if (input.grid_rows == 0) null else input.grid_rows,
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
|
|
617
755
|
fn optionalFinite(value: f32) ?f32 {
|
|
618
756
|
return if (std.math.isFinite(value)) value else null;
|
|
619
757
|
}
|
|
@@ -1359,12 +1497,26 @@ fn layoutGrid(
|
|
|
1359
1497
|
const row_gap = axisGap(node.style.row_gap, node.style.gap);
|
|
1360
1498
|
const available_width = @max(0, width - column_gap * @as(f32, @floatFromInt(column_count - 1)));
|
|
1361
1499
|
const cell_width = available_width / @as(f32, @floatFromInt(column_count));
|
|
1500
|
+
const natural_rows = if (node.children.len == 0) 0 else (node.children.len + column_count - 1) / column_count;
|
|
1501
|
+
const row_count = @max(natural_rows, @as(usize, @intCast(node.style.grid_rows orelse 0)));
|
|
1502
|
+
const row_heights = std.heap.page_allocator.alloc(f32, row_count) catch return error.FrameBufferTooSmall;
|
|
1503
|
+
defer std.heap.page_allocator.free(row_heights);
|
|
1504
|
+
for (0..row_count) |row| {
|
|
1505
|
+
var track = gridRowHeight(node, row, column_count);
|
|
1506
|
+
if (node.style.grid_rows != null) {
|
|
1507
|
+
const gap_count = if (row_count > 1) row_count - 1 else 0;
|
|
1508
|
+
const gaps = row_gap * @as(f32, @floatFromInt(gap_count));
|
|
1509
|
+
const equal = @max(0, (height - gaps) / @as(f32, @floatFromInt(@max(row_count, 1))));
|
|
1510
|
+
track = @max(track, equal);
|
|
1511
|
+
}
|
|
1512
|
+
row_heights[row] = track;
|
|
1513
|
+
}
|
|
1362
1514
|
var next_index = frame_index + 1;
|
|
1363
1515
|
var row_y = y;
|
|
1364
1516
|
for (node.children, 0..) |child, index| {
|
|
1365
1517
|
const row = index / column_count;
|
|
1366
1518
|
const column = index % column_count;
|
|
1367
|
-
const row_height =
|
|
1519
|
+
const row_height = row_heights[row];
|
|
1368
1520
|
const child_available_width = @max(0, cell_width - horizontalMargins(child));
|
|
1369
1521
|
const ratio_width = if (validAspectRatio(child.style.aspect_ratio)) |ratio|
|
|
1370
1522
|
if (child.style.width == null and child.style.height != null) child.style.height.? * ratio else null
|
|
@@ -1395,7 +1547,6 @@ fn layoutGrid(
|
|
|
1395
1547
|
row_y += row_height + row_gap;
|
|
1396
1548
|
}
|
|
1397
1549
|
}
|
|
1398
|
-
_ = height;
|
|
1399
1550
|
return next_index;
|
|
1400
1551
|
}
|
|
1401
1552
|
|
|
@@ -1708,6 +1859,47 @@ fn emptyLayoutStyleInputV4() LayoutStyleInputV4 {
|
|
|
1708
1859
|
};
|
|
1709
1860
|
}
|
|
1710
1861
|
|
|
1862
|
+
fn emptyLayoutStyleInputV5() LayoutStyleInputV5 {
|
|
1863
|
+
const unset = std.math.nan(f32);
|
|
1864
|
+
return .{
|
|
1865
|
+
.direction = 0,
|
|
1866
|
+
.width = unset,
|
|
1867
|
+
.height = unset,
|
|
1868
|
+
.intrinsic_width = unset,
|
|
1869
|
+
.intrinsic_height = unset,
|
|
1870
|
+
.baseline = unset,
|
|
1871
|
+
.aspect_ratio = unset,
|
|
1872
|
+
.flex_basis = unset,
|
|
1873
|
+
.flex_grow = 0,
|
|
1874
|
+
.flex_shrink = 0,
|
|
1875
|
+
.min_width = unset,
|
|
1876
|
+
.max_width = unset,
|
|
1877
|
+
.min_height = unset,
|
|
1878
|
+
.max_height = unset,
|
|
1879
|
+
.gap = 0,
|
|
1880
|
+
.justify_content = 0,
|
|
1881
|
+
.align_items = 3,
|
|
1882
|
+
.align_self = 0,
|
|
1883
|
+
.flex_wrap = 0,
|
|
1884
|
+
.align_content = 5,
|
|
1885
|
+
.margin_top = 0,
|
|
1886
|
+
.margin_right = 0,
|
|
1887
|
+
.margin_bottom = 0,
|
|
1888
|
+
.margin_left = 0,
|
|
1889
|
+
.padding_top = 0,
|
|
1890
|
+
.padding_right = 0,
|
|
1891
|
+
.padding_bottom = 0,
|
|
1892
|
+
.padding_left = 0,
|
|
1893
|
+
.grid = 0,
|
|
1894
|
+
.grid_columns = 1,
|
|
1895
|
+
.row_gap = 0,
|
|
1896
|
+
.column_gap = 0,
|
|
1897
|
+
.reverse = 0,
|
|
1898
|
+
.grid_auto_rows = unset,
|
|
1899
|
+
.grid_rows = 0,
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1711
1903
|
fn clampBounds(value: f32, lower: f32, upper: f32) f32 {
|
|
1712
1904
|
var result = finiteNonNegative(value);
|
|
1713
1905
|
if (std.math.isFinite(lower) and lower >= 0) result = @max(result, lower);
|
|
@@ -1818,6 +2010,38 @@ test "v4 tree layout carries auto rows through nested grids" {
|
|
|
1818
2010
|
try std.testing.expectEqual(@as(f32, 18), frames[4].y);
|
|
1819
2011
|
}
|
|
1820
2012
|
|
|
2013
|
+
test "v5 tree layout carries explicit equal grid rows through the shared ABI" {
|
|
2014
|
+
var root_style = emptyLayoutStyleInputV5();
|
|
2015
|
+
root_style.width = 100;
|
|
2016
|
+
root_style.height = 100;
|
|
2017
|
+
root_style.grid = 1;
|
|
2018
|
+
root_style.grid_columns = 2;
|
|
2019
|
+
root_style.row_gap = 4;
|
|
2020
|
+
root_style.grid_rows = 2;
|
|
2021
|
+
|
|
2022
|
+
var first_style = emptyLayoutStyleInputV5();
|
|
2023
|
+
first_style.intrinsic_width = 10;
|
|
2024
|
+
first_style.intrinsic_height = 6;
|
|
2025
|
+
var second_style = emptyLayoutStyleInputV5();
|
|
2026
|
+
second_style.intrinsic_width = 10;
|
|
2027
|
+
second_style.intrinsic_height = 7;
|
|
2028
|
+
var third_style = emptyLayoutStyleInputV5();
|
|
2029
|
+
third_style.intrinsic_width = 10;
|
|
2030
|
+
third_style.intrinsic_height = 5;
|
|
2031
|
+
|
|
2032
|
+
const nodes = [_]LayoutNodeInputV5{
|
|
2033
|
+
.{ .style = root_style, .first_child = 1, .child_count = 3 },
|
|
2034
|
+
.{ .style = first_style, .first_child = 0, .child_count = 0 },
|
|
2035
|
+
.{ .style = second_style, .first_child = 0, .child_count = 0 },
|
|
2036
|
+
.{ .style = third_style, .first_child = 0, .child_count = 0 },
|
|
2037
|
+
};
|
|
2038
|
+
var frames: [4]Frame = undefined;
|
|
2039
|
+
try std.testing.expectEqual(@as(c_int, 0), craft_native_layout_tree_v5(100, 100, &nodes, nodes.len, &frames, frames.len));
|
|
2040
|
+
try std.testing.expectEqual(@as(f32, 0), frames[1].y);
|
|
2041
|
+
try std.testing.expectEqual(@as(f32, 0), frames[2].y);
|
|
2042
|
+
try std.testing.expectEqual(@as(f32, 52), frames[3].y);
|
|
2043
|
+
}
|
|
2044
|
+
|
|
1821
2045
|
test "lays out nested flat trees with absolute descendant frames" {
|
|
1822
2046
|
var root_style = emptyLayoutStyleInput();
|
|
1823
2047
|
root_style.width = 200;
|
package/dist/mobile-testing.js
CHANGED
package/dist/mobile.js
CHANGED
|
@@ -1145,9 +1145,9 @@ function hrSubscribe(set, callback) {
|
|
|
1145
1145
|
if (reading)
|
|
1146
1146
|
hrNotify(heartRateWatch.readings, reading);
|
|
1147
1147
|
} else if (detail.type === "device") {
|
|
1148
|
-
const
|
|
1149
|
-
if (
|
|
1150
|
-
hrNotify(heartRateWatch.devices,
|
|
1148
|
+
const device = normalizeHeartRateDevice(detail.device);
|
|
1149
|
+
if (device)
|
|
1150
|
+
hrNotify(heartRateWatch.devices, device);
|
|
1151
1151
|
} else if (detail.type === "state") {
|
|
1152
1152
|
hrNotify(heartRateWatch.states, normalizeHeartRateState(detail));
|
|
1153
1153
|
}
|
|
@@ -1184,7 +1184,7 @@ var heartRate = {
|
|
|
1184
1184
|
requestAuthorization: () => heartRateCall("requestAuthorization", () => "unavailable", (value) => HR_AUTHORIZATIONS.includes(value) ? value : "unavailable"),
|
|
1185
1185
|
scan: (options = {}) => heartRateCall("scan", noHeartRate, normalizeHeartRateState, options),
|
|
1186
1186
|
stopScan: () => heartRateCall("stopScan", noHeartRate, normalizeHeartRateState),
|
|
1187
|
-
getDevices: () => heartRateCall("getDevices", () => [], (value) => Array.isArray(value) ? value.map(normalizeHeartRateDevice).filter((
|
|
1187
|
+
getDevices: () => heartRateCall("getDevices", () => [], (value) => Array.isArray(value) ? value.map(normalizeHeartRateDevice).filter((device) => device !== null) : []),
|
|
1188
1188
|
connect: (id) => heartRateCall("connect", noHeartRate, normalizeHeartRateState, id),
|
|
1189
1189
|
reconnect: () => heartRateCall("reconnect", noHeartRate, normalizeHeartRateState),
|
|
1190
1190
|
disconnect: (options = {}) => heartRateCall("disconnect", noHeartRate, normalizeHeartRateState, options),
|
|
@@ -1459,14 +1459,17 @@ var hybrid = {
|
|
|
1459
1459
|
return postHybrid({ type: "back" });
|
|
1460
1460
|
}
|
|
1461
1461
|
};
|
|
1462
|
-
async function callNative(namespace, method, args, fallback) {
|
|
1462
|
+
async function callNative(namespace, method, args, fallback, options = {}) {
|
|
1463
1463
|
const target = getCraftRoot()?.[namespace];
|
|
1464
1464
|
if (!target || typeof target[method] !== "function")
|
|
1465
1465
|
return fallback;
|
|
1466
1466
|
try {
|
|
1467
1467
|
const value = await target[method](...args);
|
|
1468
1468
|
return value === undefined || value === null ? fallback : value;
|
|
1469
|
-
} catch {
|
|
1469
|
+
} catch (error) {
|
|
1470
|
+
if (options.warnOnError) {
|
|
1471
|
+
console.warn(`[craft] Native ${namespace}.${method} failed; using its fallback value.`, error);
|
|
1472
|
+
}
|
|
1470
1473
|
return fallback;
|
|
1471
1474
|
}
|
|
1472
1475
|
}
|
|
@@ -1484,7 +1487,7 @@ var dialog = {
|
|
|
1484
1487
|
return callNative("dialog", "confirm", [options], false);
|
|
1485
1488
|
},
|
|
1486
1489
|
actionSheet(options) {
|
|
1487
|
-
return callNative("dialog", "actionSheet", [options], null);
|
|
1490
|
+
return callNative("dialog", "actionSheet", [options], null, { warnOnError: true });
|
|
1488
1491
|
}
|
|
1489
1492
|
};
|
|
1490
1493
|
var contextMenu = {
|
|
@@ -1669,11 +1672,11 @@ var health = {
|
|
|
1669
1672
|
throw new Error("Reading sleep intervals is unavailable");
|
|
1670
1673
|
return craft.health.getSleepIntervals(options);
|
|
1671
1674
|
},
|
|
1672
|
-
async saveMindfulSession(
|
|
1675
|
+
async saveMindfulSession(session) {
|
|
1673
1676
|
const craft = getCraftRoot();
|
|
1674
1677
|
if (!craft?.health?.saveMindfulSession)
|
|
1675
1678
|
throw new Error("Saving mindful sessions is unavailable");
|
|
1676
|
-
return craft.health.saveMindfulSession(
|
|
1679
|
+
return craft.health.saveMindfulSession(session);
|
|
1677
1680
|
},
|
|
1678
1681
|
async getMindfulSessions(options = {}) {
|
|
1679
1682
|
const craft = getCraftRoot();
|
|
@@ -1693,6 +1696,41 @@ var health = {
|
|
|
1693
1696
|
throw new Error("Heart-rate updates are unavailable");
|
|
1694
1697
|
return Boolean(await craft.health.stopHeartRateUpdates());
|
|
1695
1698
|
},
|
|
1699
|
+
async startWorkout(options) {
|
|
1700
|
+
const craft = getCraftRoot();
|
|
1701
|
+
if (!craft?.health?.startWorkout)
|
|
1702
|
+
throw new Error("Live workouts are unavailable");
|
|
1703
|
+
return craft.health.startWorkout(options);
|
|
1704
|
+
},
|
|
1705
|
+
async pauseWorkout() {
|
|
1706
|
+
const craft = getCraftRoot();
|
|
1707
|
+
if (!craft?.health?.pauseWorkout)
|
|
1708
|
+
throw new Error("Live workouts are unavailable");
|
|
1709
|
+
return Boolean(await craft.health.pauseWorkout());
|
|
1710
|
+
},
|
|
1711
|
+
async resumeWorkout() {
|
|
1712
|
+
const craft = getCraftRoot();
|
|
1713
|
+
if (!craft?.health?.resumeWorkout)
|
|
1714
|
+
throw new Error("Live workouts are unavailable");
|
|
1715
|
+
return Boolean(await craft.health.resumeWorkout());
|
|
1716
|
+
},
|
|
1717
|
+
async endWorkout() {
|
|
1718
|
+
const craft = getCraftRoot();
|
|
1719
|
+
if (!craft?.health?.endWorkout)
|
|
1720
|
+
throw new Error("Live workouts are unavailable");
|
|
1721
|
+
return craft.health.endWorkout();
|
|
1722
|
+
},
|
|
1723
|
+
onWorkout(callback) {
|
|
1724
|
+
if (typeof window === "undefined" || typeof getCraftRoot()?.health?.startWorkout !== "function")
|
|
1725
|
+
return () => {};
|
|
1726
|
+
const listener = (event) => {
|
|
1727
|
+
const detail = event.detail;
|
|
1728
|
+
if (detail && typeof detail.id === "string" && (detail.event === "state" || detail.event === "error"))
|
|
1729
|
+
callback(detail);
|
|
1730
|
+
};
|
|
1731
|
+
window.addEventListener("craftHealthWorkout", listener);
|
|
1732
|
+
return () => window.removeEventListener("craftHealthWorkout", listener);
|
|
1733
|
+
},
|
|
1696
1734
|
onHeartRate(callback) {
|
|
1697
1735
|
if (typeof window === "undefined" || typeof getCraftRoot()?.health?.startHeartRateUpdates !== "function")
|
|
1698
1736
|
return () => {};
|
|
@@ -1819,59 +1857,59 @@ function getCraftMobile() {
|
|
|
1819
1857
|
return;
|
|
1820
1858
|
}
|
|
1821
1859
|
export {
|
|
1822
|
-
|
|
1823
|
-
tabBar,
|
|
1824
|
-
symbols,
|
|
1825
|
-
storage,
|
|
1826
|
-
statusBar,
|
|
1827
|
-
splash,
|
|
1828
|
-
speech,
|
|
1829
|
-
snapshots,
|
|
1830
|
-
share,
|
|
1831
|
-
session,
|
|
1832
|
-
secureStorage,
|
|
1833
|
-
scheduledWorkoutsUnsupported,
|
|
1834
|
-
scheduledWorkouts,
|
|
1835
|
-
refresh,
|
|
1836
|
-
pushNotifications,
|
|
1837
|
-
permissions,
|
|
1838
|
-
notifications,
|
|
1839
|
-
normalizeScheduledWorkout,
|
|
1840
|
-
normalizeNowPlaying,
|
|
1841
|
-
normalizeHeartRateState,
|
|
1842
|
-
normalizeHeartRateSamples,
|
|
1843
|
-
normalizeHeartRateReading,
|
|
1844
|
-
normalizeHeartRateDevice,
|
|
1845
|
-
normalizeDeepLinkURL,
|
|
1846
|
-
network,
|
|
1847
|
-
music,
|
|
1848
|
-
location,
|
|
1849
|
-
liveActivities,
|
|
1850
|
-
lifecycle,
|
|
1851
|
-
keepAwake,
|
|
1852
|
-
isSnapshotName,
|
|
1853
|
-
hybrid,
|
|
1854
|
-
heartRate,
|
|
1855
|
-
health,
|
|
1856
|
-
haptics,
|
|
1857
|
-
dialog,
|
|
1858
|
-
device,
|
|
1859
|
-
deepLinks,
|
|
1860
|
-
crypto,
|
|
1861
|
-
crashReporter,
|
|
1862
|
-
contextMenu,
|
|
1863
|
-
chrome,
|
|
1864
|
-
camera,
|
|
1865
|
-
browser,
|
|
1866
|
-
brightness,
|
|
1867
|
-
bridgeNotification,
|
|
1868
|
-
biometrics,
|
|
1869
|
-
background,
|
|
1870
|
-
appearance,
|
|
1871
|
-
appReview,
|
|
1872
|
-
_webFallbackDeviceId,
|
|
1873
|
-
_resetWebFallbackDeviceIdForTests,
|
|
1874
|
-
SESSION_MESSAGE,
|
|
1860
|
+
DEFAULT_SESSION_TOKEN_KEY,
|
|
1875
1861
|
SESSION_EVENT,
|
|
1876
|
-
|
|
1862
|
+
SESSION_MESSAGE,
|
|
1863
|
+
_resetWebFallbackDeviceIdForTests,
|
|
1864
|
+
_webFallbackDeviceId,
|
|
1865
|
+
appReview,
|
|
1866
|
+
appearance,
|
|
1867
|
+
background,
|
|
1868
|
+
biometrics,
|
|
1869
|
+
bridgeNotification,
|
|
1870
|
+
brightness,
|
|
1871
|
+
browser,
|
|
1872
|
+
camera,
|
|
1873
|
+
chrome,
|
|
1874
|
+
contextMenu,
|
|
1875
|
+
crashReporter,
|
|
1876
|
+
crypto,
|
|
1877
|
+
deepLinks,
|
|
1878
|
+
device,
|
|
1879
|
+
dialog,
|
|
1880
|
+
haptics,
|
|
1881
|
+
health,
|
|
1882
|
+
heartRate,
|
|
1883
|
+
hybrid,
|
|
1884
|
+
isSnapshotName,
|
|
1885
|
+
keepAwake,
|
|
1886
|
+
lifecycle,
|
|
1887
|
+
liveActivities,
|
|
1888
|
+
location,
|
|
1889
|
+
music,
|
|
1890
|
+
network,
|
|
1891
|
+
normalizeDeepLinkURL,
|
|
1892
|
+
normalizeHeartRateDevice,
|
|
1893
|
+
normalizeHeartRateReading,
|
|
1894
|
+
normalizeHeartRateSamples,
|
|
1895
|
+
normalizeHeartRateState,
|
|
1896
|
+
normalizeNowPlaying,
|
|
1897
|
+
normalizeScheduledWorkout,
|
|
1898
|
+
notifications,
|
|
1899
|
+
permissions,
|
|
1900
|
+
pushNotifications,
|
|
1901
|
+
refresh,
|
|
1902
|
+
scheduledWorkouts,
|
|
1903
|
+
scheduledWorkoutsUnsupported,
|
|
1904
|
+
secureStorage,
|
|
1905
|
+
session,
|
|
1906
|
+
share,
|
|
1907
|
+
snapshots,
|
|
1908
|
+
speech,
|
|
1909
|
+
splash,
|
|
1910
|
+
statusBar,
|
|
1911
|
+
storage,
|
|
1912
|
+
symbols,
|
|
1913
|
+
tabBar,
|
|
1914
|
+
watchConnectivity
|
|
1877
1915
|
};
|