craft-native 0.0.215 → 0.0.217

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/cli.js CHANGED
@@ -3424,7 +3424,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
3424
3424
  `);
3425
3425
  }
3426
3426
  // package.json
3427
- var version = "0.0.215";
3427
+ var version = "0.0.217";
3428
3428
 
3429
3429
  // bin/cli.ts
3430
3430
  var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
package/dist/index.cjs CHANGED
@@ -309,7 +309,7 @@ function craftBinaryNotFoundMessage(triedPath) {
309
309
  `);
310
310
  }
311
311
  // package.json
312
- var version = "0.0.215";
312
+ var version = "0.0.217";
313
313
 
314
314
  // src/sdk-version.ts
315
315
  var SDK_VERSION = version;
package/dist/index.js CHANGED
@@ -57,7 +57,7 @@ function craftBinaryNotFoundMessage(triedPath) {
57
57
  `);
58
58
  }
59
59
  // package.json
60
- var version = "0.0.215";
60
+ var version = "0.0.217";
61
61
 
62
62
  // src/sdk-version.ts
63
63
  var SDK_VERSION = version;
@@ -797,9 +797,11 @@ final class CraftChrome: ObservableObject {
797
797
 
798
798
  /// Move the tab indicator with an in-flight native-stack swipe. The
799
799
  /// controller commits or restores this preview when the finger lifts.
800
- func beginTabSwipe(to id: String) {
801
- guard id != selected, tabs.contains(where: { $0.id == id }) else { return }
802
- tabSwipePrevious = selected
800
+ func beginTabSwipe(to id: String, from previous: String) {
801
+ guard id != previous, tabs.contains(where: { $0.id == id }) else { return }
802
+ // A page's pending selection report may disagree with the native
803
+ // stack. Cancellation belongs to the stack the finger left.
804
+ tabSwipePrevious = previous
803
805
  withAnimation(nil) { selected = id }
804
806
  }
805
807
 
@@ -1203,7 +1203,7 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
1203
1203
  tabSwipeFrom = -direction * width
1204
1204
  target.view.frame = view.bounds.offsetBy(dx: tabSwipeFrom, dy: 0)
1205
1205
  view.bringSubviewToFront(target.view)
1206
- CraftChrome.shared.beginTabSwipe(to: id)
1206
+ CraftChrome.shared.beginTabSwipe(to: id, from: currentTab)
1207
1207
  return true
1208
1208
  }
1209
1209
 
@@ -1668,12 +1668,16 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
1668
1668
  /// tab's root: over a native screen, or on an entry the shell pushed
1669
1669
  /// there, the page cannot know which tab it is in (a workout page
1670
1670
  /// loaded on its own lights Calendar, though Today opened it). The bar
1671
- /// then keeps the tab that is on screen.
1671
+ /// then keeps the tab that is on screen, or the finger's active preview.
1672
1672
  func pageSelectedTab(_ id: String) {
1673
- guard id != currentTab, pendingTab?.id != id else { return }
1674
- guard let top = visibleStack?.topViewController as? CraftHybridWebSlot, top.isTabRoot else {
1675
- let shown = currentTab
1676
- DispatchQueue.main.async {
1673
+ guard id != (activeTabSwipeTarget ?? currentTab), pendingTab?.id != id else { return }
1674
+ guard activeTabSwipeTarget == nil,
1675
+ let top = visibleStack?.topViewController as? CraftHybridWebSlot, top.isTabRoot else {
1676
+ DispatchQueue.main.async { [weak self] in
1677
+ guard let self, self.hybrid.controller === self else { return }
1678
+ // Resolve ownership when the correction runs: a newer tap
1679
+ // or swipe may have arrived while a native root was mounting.
1680
+ let shown = self.activeTabSwipeTarget ?? self.currentTab
1677
1681
  guard CraftChrome.shared.selected != shown else { return }
1678
1682
  CraftChrome.shared.selected = shown
1679
1683
  }
@@ -308,7 +308,7 @@ final class CraftNativeFlatList: UICollectionView, UICollectionViewDelegateFlowL
308
308
 
309
309
  func setContentContainerStyle(_ raw: [String: Any]?) {
310
310
  func nonNegative(_ value: Any?) -> CGFloat? {
311
- guard !(value is Bool), let number = value as? NSNumber else { return nil }
311
+ guard let number = craftNativeNumber(value) else { return nil }
312
312
  let points = CGFloat(truncating: number)
313
313
  guard points.isFinite, points >= 0 else { return nil }
314
314
  return points
@@ -1,4 +1,5 @@
1
1
  import Foundation
2
+ import CoreFoundation
2
3
  import AVFoundation
3
4
  import AVKit
4
5
  import CryptoKit
@@ -12,32 +13,41 @@ import UIKit
12
13
 
13
14
  private let craftNativeCapabilityTimeoutMilliseconds = 30_000
14
15
 
16
+ /// Foundation's `NSNumber(0/1) is Bool` is true even for JSON numbers.
17
+ /// Check the Core Foundation identity so real booleans stay invalid numeric
18
+ /// props while zero lengths, flex factors and opacity 1 remain numbers.
19
+ func craftNativeNumber(_ value: Any?) -> NSNumber? {
20
+ guard let number = value as? NSNumber,
21
+ CFGetTypeID(number) != CFBooleanGetTypeID() else { return nil }
22
+ return number
23
+ }
24
+
15
25
  /// A finite number from a prop, for the views outside the screen controller,
16
26
  /// which has its own: the keyboard-avoiding view and SVG text read theirs
17
27
  /// with it, and without it the template did not compile.
18
28
  private func number(_ value: Any?) -> CGFloat? {
19
29
  // JavaScript booleans bridge to NSNumber on Apple platforms; they are
20
30
  // invalid numeric styles and must stay unset like Android's Number cast.
21
- guard !(value is Bool), let number = value as? NSNumber else { return nil }
31
+ guard let number = craftNativeNumber(value) else { return nil }
22
32
  let value = CGFloat(truncating: number)
23
33
  return value.isFinite ? value : nil
24
34
  }
25
35
 
26
36
  private func craftNativeFontWeight(_ value: Any?) -> String? {
27
37
  if let value = value as? String { return value }
28
- guard !(value is Bool), let number = value as? NSNumber, number.doubleValue.isFinite else { return nil }
38
+ guard let number = craftNativeNumber(value), number.doubleValue.isFinite else { return nil }
29
39
  let integer = number.doubleValue.rounded()
30
40
  return integer == number.doubleValue ? String(Int(integer)) : nil
31
41
  }
32
42
 
33
43
  private func craftNativePickerValue(_ value: Any?) -> String? {
34
44
  if let value = value as? String, !value.isEmpty { return value }
35
- if !(value is Bool), let value = value as? NSNumber, value.doubleValue.isFinite { return String(value.doubleValue) }
45
+ if let value = craftNativeNumber(value), value.doubleValue.isFinite { return String(value.doubleValue) }
36
46
  return nil
37
47
  }
38
48
 
39
49
  private func craftNativeGridColumnCount(_ value: Any?) -> Int {
40
- if !(value is Bool), let number = value as? NSNumber { return max(1, number.intValue) }
50
+ if let number = craftNativeNumber(value) { return max(1, number.intValue) }
41
51
  guard let text = value as? String else { return 1 }
42
52
  let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
43
53
  if trimmed.hasPrefix("repeat(") {
@@ -1387,7 +1397,7 @@ private final class CraftNativeMapView: MKMapView, MKMapViewDelegate {
1387
1397
  }
1388
1398
 
1389
1399
  private static func numeric(_ value: Any?) -> Double? {
1390
- guard !(value is Bool), let number = value as? NSNumber else { return nil }
1400
+ guard let number = craftNativeNumber(value) else { return nil }
1391
1401
  let value = number.doubleValue
1392
1402
  return value.isFinite ? value : nil
1393
1403
  }
@@ -1458,7 +1468,7 @@ private enum CraftNativeLength: Equatable {
1458
1468
  init?(_ value: Any?) {
1459
1469
  // JavaScript booleans can bridge to NSNumber on Apple platforms;
1460
1470
  // they are invalid CSS lengths and must stay unset like Android.
1461
- if !(value is Bool), let number = value as? NSNumber {
1471
+ if let number = craftNativeNumber(value) {
1462
1472
  let points = CGFloat(truncating: number)
1463
1473
  guard points.isFinite else { return nil }
1464
1474
  self = .points(points)
@@ -1516,7 +1526,7 @@ private struct CraftNativeLayoutStyle {
1516
1526
 
1517
1527
  init(_ raw: [String: Any]) {
1518
1528
  func number(_ value: Any?) -> CGFloat? {
1519
- guard !(value is Bool), let number = value as? NSNumber else { return nil }
1529
+ guard let number = craftNativeNumber(value) else { return nil }
1520
1530
  let value = CGFloat(truncating: number)
1521
1531
  return value.isFinite ? value : nil
1522
1532
  }
@@ -5000,7 +5010,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
5000
5010
  else if let symbol = spec["symbol"] as? String { button.setImage(UIImage(systemName: symbol, withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular)), for: .normal) }
5001
5011
  button.accessibilityIdentifier = spec["id"] as? String
5002
5012
  button.accessibilityLabel = spec["accessibilityLabel"] as? String
5003
- if !(spec["badge"] is Bool), let value = spec["badge"] as? NSNumber, value.intValue > 0 {
5013
+ if let value = craftNativeNumber(spec["badge"]), value.intValue > 0 {
5004
5014
  let dot = spec["badgeStyle"] as? String == "dot"
5005
5015
  let size: CGFloat = dot ? 9 : 18
5006
5016
  // The fill is a tinted symbol, not a background colour: on iOS 26
@@ -7658,7 +7668,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
7658
7668
  /// Countdown values use seconds when numeric, or `HH:mm[:ss]` text.
7659
7669
  static func countdownDuration(_ value: Any?) -> TimeInterval? {
7660
7670
  let maximum: TimeInterval = 23 * 3600 + 59 * 60 + 59
7661
- if let number = value as? NSNumber, !(value is Bool) {
7671
+ if let number = craftNativeNumber(value) {
7662
7672
  let seconds = number.doubleValue
7663
7673
  guard seconds.isFinite else { return nil }
7664
7674
  return min(maximum, max(0, seconds))
@@ -7679,7 +7689,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
7679
7689
  /// time), `2026-10-12T07:30` (local), a full ISO 8601 string, or
7680
7690
  /// milliseconds since 1970. Nil for anything else.
7681
7691
  static func pickerDate(_ value: Any?) -> Date? {
7682
- if let number = value as? NSNumber, !(value is Bool) {
7692
+ if let number = craftNativeNumber(value) {
7683
7693
  let milliseconds = number.doubleValue
7684
7694
  guard milliseconds.isFinite else { return nil }
7685
7695
  return Date(timeIntervalSince1970: milliseconds / 1000)
@@ -7851,7 +7861,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
7851
7861
  }
7852
7862
 
7853
7863
  private func number(_ value: Any?) -> CGFloat? {
7854
- guard !(value is Bool), let number = value as? NSNumber else { return nil }
7864
+ guard let number = craftNativeNumber(value) else { return nil }
7855
7865
  let value = CGFloat(truncating: number)
7856
7866
  return value.isFinite ? value : nil
7857
7867
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "craft-native",
3
- "version": "0.0.215",
3
+ "version": "0.0.217",
4
4
  "type": "module",
5
5
  "description": "Build desktop apps with web languages - TypeScript SDK for Craft",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",