craft-native 0.0.170 → 0.0.172
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
|
@@ -3303,7 +3303,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
|
|
|
3303
3303
|
`);
|
|
3304
3304
|
}
|
|
3305
3305
|
// package.json
|
|
3306
|
-
var version = "0.0.
|
|
3306
|
+
var version = "0.0.172";
|
|
3307
3307
|
|
|
3308
3308
|
// bin/cli.ts
|
|
3309
3309
|
var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
|
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -1614,17 +1614,22 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
|
|
|
1614
1614
|
return popRecognizers(of: nav).contains { $0 === recognizer }
|
|
1615
1615
|
}
|
|
1616
1616
|
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1617
|
+
/// Whether the touch at `point` (in this controller's view) is on a
|
|
1618
|
+
/// horizontal scroll view of `root` that is past its leading edge, so a
|
|
1619
|
+
/// swipe to the right scrolls it back rather than leaving the screen.
|
|
1620
|
+
private func horizontalScrollCanScrollBack(at point: CGPoint, in root: UIView) -> Bool {
|
|
1621
|
+
let local = root.convert(point, from: view)
|
|
1622
|
+
guard root.bounds.contains(local), let hit = root.hitTest(local, with: nil) else { return false }
|
|
1623
|
+
var candidate: UIView? = hit
|
|
1624
|
+
while let current = candidate, current !== root.superview {
|
|
1625
|
+
if let scroll = current as? UIScrollView, scroll.isScrollEnabled,
|
|
1626
|
+
scroll.contentSize.width + scroll.adjustedContentInset.left + scroll.adjustedContentInset.right > scroll.bounds.width + 1,
|
|
1627
|
+
scroll.contentOffset.x > -scroll.adjustedContentInset.left + 1 {
|
|
1623
1628
|
return true
|
|
1624
1629
|
}
|
|
1625
|
-
|
|
1630
|
+
candidate = current.superview
|
|
1626
1631
|
}
|
|
1627
|
-
return
|
|
1632
|
+
return false
|
|
1628
1633
|
}
|
|
1629
1634
|
|
|
1630
1635
|
/// The edge swipe goes ahead of the web view's own gestures, which
|
|
@@ -1639,8 +1644,14 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
|
|
|
1639
1644
|
|
|
1640
1645
|
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
1641
1646
|
if tabSwipeRecognizers.contains(where: { $0 === gestureRecognizer }) {
|
|
1642
|
-
guard hybrid.tabIds.count > 1, let top =
|
|
1647
|
+
guard hybrid.tabIds.count > 1, let nav = visibleStack, let top = nav.topViewController else { return false }
|
|
1643
1648
|
guard CraftChrome.shared.tabSwipeEnabled else { return false }
|
|
1649
|
+
// Only between the tabs' own first screens, as the bar shows them:
|
|
1650
|
+
// deeper in a tab a swipe to the right is Back, and a screen that
|
|
1651
|
+
// put the bar away (a workout under way, a tour) has the whole
|
|
1652
|
+
// screen to itself.
|
|
1653
|
+
guard nav.viewControllers.count == 1, nav.transitionCoordinator == nil,
|
|
1654
|
+
!CraftChrome.shared.hiddenByScreen else { return false }
|
|
1644
1655
|
if let pan = gestureRecognizer as? UIPanGestureRecognizer {
|
|
1645
1656
|
let velocity = pan.velocity(in: view)
|
|
1646
1657
|
guard abs(velocity.x) > abs(velocity.y) else { return false }
|
|
@@ -1651,15 +1662,19 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
|
|
|
1651
1662
|
}
|
|
1652
1663
|
guard let nav = visibleStack, isPopRecognizer(gestureRecognizer),
|
|
1653
1664
|
nav.viewControllers.count > 1, nav.transitionCoordinator == nil else { return false }
|
|
1654
|
-
if let native = nav.topViewController as? CraftNativeScreenController
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
return false
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1665
|
+
if let native = nav.topViewController as? CraftNativeScreenController {
|
|
1666
|
+
// `swipeBack: false`: the screen's own Back is the only way back.
|
|
1667
|
+
if !native.swipeBack { return false }
|
|
1668
|
+
if gestureRecognizer !== nav.interactivePopGestureRecognizer, !native.contentSwipeBack { return false }
|
|
1669
|
+
// A back-swipe that starts on a pager, a carousel or a chart that
|
|
1670
|
+
// can still scroll back is that view's: the pager goes back a
|
|
1671
|
+
// page. At its first page there is nothing to scroll back to, and
|
|
1672
|
+
// the swipe is the screen's Back again, from the edge or (iOS 26)
|
|
1673
|
+
// from anywhere.
|
|
1674
|
+
let moved = (gestureRecognizer as? UIPanGestureRecognizer)?.translation(in: nav.view) ?? .zero
|
|
1675
|
+
let location = gestureRecognizer.location(in: nav.view)
|
|
1676
|
+
let start = CGPoint(x: location.x - moved.x, y: location.y - moved.y)
|
|
1677
|
+
if horizontalScrollCanScrollBack(at: nav.view.convert(start, to: view), in: native.view) { return false }
|
|
1663
1678
|
}
|
|
1664
1679
|
guard let slot = nav.topViewController as? CraftHybridWebSlot else { return true }
|
|
1665
1680
|
// Over the page, only from its left edge: a swipe across the page is
|
|
@@ -150,6 +150,11 @@ final class CraftNativeMutationDocument {
|
|
|
150
150
|
materialize(id, nodes: nodes)
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
/// The committed parent of a node: nil for the root or a node it does not hold.
|
|
154
|
+
func parentId(of id: String) -> String? {
|
|
155
|
+
nodes[id]?.parent
|
|
156
|
+
}
|
|
157
|
+
|
|
153
158
|
private func createNode(
|
|
154
159
|
_ operation: [String: Any],
|
|
155
160
|
nodes: inout [String: Node],
|
|
@@ -291,8 +296,21 @@ final class CraftNativeMutationDocument {
|
|
|
291
296
|
return values.compactMap { $0 as? String }
|
|
292
297
|
}
|
|
293
298
|
|
|
299
|
+
/// Every type the renderer draws children of (CraftNativeScreen's
|
|
300
|
+
/// reconcile), by the names it accepts (`canonicalType`, so `svg` is
|
|
301
|
+
/// `Svg`). Only View, SafeAreaView and ScrollView used to be here, so a
|
|
302
|
+
/// drawing revealed after the first paint (an `:if` around an `<svg>`)
|
|
303
|
+
/// had its shapes refused as INVALID_PARENT, and the whole screen was
|
|
304
|
+
/// drawn again; Android's host already took the same list.
|
|
305
|
+
static let containerTypes: Set<String> = [
|
|
306
|
+
"View", "SafeAreaView", "ScrollView", "KeyboardAvoidingView", "Modal", "Pressable",
|
|
307
|
+
"TouchableOpacity", "TouchableHighlight", "ImageBackground", "FlatList", "SectionList",
|
|
308
|
+
"Fragment", "Svg", "G",
|
|
309
|
+
]
|
|
310
|
+
private static let containerTypeKeys = Set(containerTypes.map { $0.lowercased() })
|
|
311
|
+
|
|
294
312
|
private func acceptsChildren(_ type: String) -> Bool {
|
|
295
|
-
type
|
|
313
|
+
Self.containerTypeKeys.contains(type.lowercased())
|
|
296
314
|
}
|
|
297
315
|
|
|
298
316
|
private func descendants(of id: String, nodes: [String: Node]) -> Set<String> {
|
|
@@ -2895,7 +2895,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
2895
2895
|
/// - `craft.secureStorage.getSync(key)`: the Keychain item the web page's
|
|
2896
2896
|
/// `craft.secureStorage.set` wrote.
|
|
2897
2897
|
/// - `craft.navigation.setOptions({ title, largeTitle, hidden, backTitle,
|
|
2898
|
-
/// tabBarHidden, contentSwipeBack, tabSwipeEnabled, backgroundColor, rightButtons })`;
|
|
2898
|
+
/// tabBarHidden, swipeBack, contentSwipeBack, tabSwipeEnabled, backgroundColor, rightButtons })`;
|
|
2899
2899
|
/// a right button's tap is the event `navButton`.
|
|
2900
2900
|
///
|
|
2901
2901
|
/// A runtime that assigns `craft.storage` (or the others) afterwards keeps
|
|
@@ -3140,6 +3140,11 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
3140
3140
|
private var navigationBarHidden: Bool?
|
|
3141
3141
|
/// The app's tab bar away while this screen shows (`tabBarHidden`).
|
|
3142
3142
|
private var tabBarHidden = false
|
|
3143
|
+
/// Whether a back-swipe may leave this screen at all, the edge's
|
|
3144
|
+
/// included (`swipeBack: false`): a screen that steps through its own
|
|
3145
|
+
/// pages with a Back of its own (a first-run tour's questions) would
|
|
3146
|
+
/// otherwise lose its place to a stray swipe.
|
|
3147
|
+
private(set) var swipeBack = true
|
|
3143
3148
|
/// Whether iOS 26's content-wide back-swipe may begin on this screen.
|
|
3144
3149
|
/// Setting `contentSwipeBack: false` leaves the 32-point edge gesture.
|
|
3145
3150
|
private(set) var contentSwipeBack = true
|
|
@@ -3186,6 +3191,9 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
3186
3191
|
CraftChrome.shared.setHiddenByScreen(hidden)
|
|
3187
3192
|
}
|
|
3188
3193
|
}
|
|
3194
|
+
if let swipeBack = options["swipeBack"] as? Bool {
|
|
3195
|
+
self.swipeBack = swipeBack
|
|
3196
|
+
}
|
|
3189
3197
|
if let contentSwipeBack = options["contentSwipeBack"] as? Bool {
|
|
3190
3198
|
self.contentSwipeBack = contentSwipeBack
|
|
3191
3199
|
}
|
|
@@ -3265,11 +3273,21 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
3265
3273
|
button.addAction(action, for: .touchUpInside)
|
|
3266
3274
|
button.translatesAutoresizingMaskIntoConstraints = false
|
|
3267
3275
|
NSLayoutConstraint.activate([button.widthAnchor.constraint(equalToConstant: 44), button.heightAnchor.constraint(equalToConstant: 44)])
|
|
3268
|
-
//
|
|
3269
|
-
//
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3276
|
+
// Floating, as the tab bar floats: a round piece of glass on iOS 26
|
|
3277
|
+
// (the bar's own material), the thin material with the tab bar's
|
|
3278
|
+
// hairline and shadow before it. The content scrolls on under it, and
|
|
3279
|
+
// it follows light, dark and Increase Contrast by itself.
|
|
3280
|
+
let surface = Self.floatingSurface(diameter: 44)
|
|
3281
|
+
button.addSubview(surface)
|
|
3282
|
+
// Glass lifts itself off the content; the material needs the tab
|
|
3283
|
+
// bar's shadow to.
|
|
3284
|
+
if surface.effect is UIBlurEffect {
|
|
3285
|
+
button.layer.shadowColor = UIColor.black.cgColor
|
|
3286
|
+
button.layer.shadowOpacity = 0.12
|
|
3287
|
+
button.layer.shadowRadius = 8
|
|
3288
|
+
button.layer.shadowOffset = CGSize(width: 0, height: 3)
|
|
3289
|
+
button.layer.shadowPath = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 44, height: 44)).cgPath
|
|
3290
|
+
}
|
|
3273
3291
|
button.tintColor = .systemBlue
|
|
3274
3292
|
button.setTitleColor(button.tintColor, for: .normal)
|
|
3275
3293
|
button.titleLabel?.font = .systemFont(ofSize: 13, weight: .semibold)
|
|
@@ -3314,8 +3332,11 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
3314
3332
|
}
|
|
3315
3333
|
button.setTitle(nil, for: .normal)
|
|
3316
3334
|
button.setImage(round.withRenderingMode(.alwaysOriginal), for: .normal)
|
|
3335
|
+
if let surface = button.subviews.first(where: { $0 is UIVisualEffectView }) { button.sendSubviewToBack(surface) }
|
|
3317
3336
|
}
|
|
3318
3337
|
}
|
|
3338
|
+
// Under the symbol, the initials, the picture and the badge.
|
|
3339
|
+
if let surface = button.subviews.first(where: { $0 is UIVisualEffectView }) { button.sendSubviewToBack(surface) }
|
|
3319
3340
|
let item = UIBarButtonItem(customView: button)
|
|
3320
3341
|
// iOS 26 groups adjacent bar items on one glass platter and tints
|
|
3321
3342
|
// their custom views with it: the two 44-point circles came out as
|
|
@@ -3328,6 +3349,29 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
3328
3349
|
return item
|
|
3329
3350
|
}
|
|
3330
3351
|
|
|
3352
|
+
/// The round surface under a bar button: glass on iOS 26, the tab bar's
|
|
3353
|
+
/// thin material and hairline before it. It takes no touches.
|
|
3354
|
+
private static func floatingSurface(diameter: CGFloat) -> UIVisualEffectView {
|
|
3355
|
+
let effect: UIVisualEffect
|
|
3356
|
+
#if compiler(>=6.2)
|
|
3357
|
+
if #available(iOS 26.0, *) { effect = UIGlassEffect() } else { effect = UIBlurEffect(style: .systemUltraThinMaterial) }
|
|
3358
|
+
#else
|
|
3359
|
+
effect = UIBlurEffect(style: .systemUltraThinMaterial)
|
|
3360
|
+
#endif
|
|
3361
|
+
let surface = UIVisualEffectView(effect: effect)
|
|
3362
|
+
surface.frame = CGRect(x: 0, y: 0, width: diameter, height: diameter)
|
|
3363
|
+
surface.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
3364
|
+
surface.isUserInteractionEnabled = false
|
|
3365
|
+
surface.layer.cornerRadius = diameter / 2
|
|
3366
|
+
surface.layer.cornerCurve = .circular
|
|
3367
|
+
surface.clipsToBounds = true
|
|
3368
|
+
if effect is UIBlurEffect {
|
|
3369
|
+
surface.layer.borderWidth = 0.5
|
|
3370
|
+
surface.layer.borderColor = UIColor.white.withAlphaComponent(0.35).cgColor
|
|
3371
|
+
}
|
|
3372
|
+
return surface
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3331
3375
|
/// A bar picture, kept for the app's life so each screen does not fetch it again.
|
|
3332
3376
|
private static let barImages = NSCache<NSURL, UIImage>()
|
|
3333
3377
|
|
|
@@ -3816,7 +3860,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
3816
3860
|
let focused = firstResponder(in: rootStack) ?? lastFocusedInput
|
|
3817
3861
|
var applied = Set<String>()
|
|
3818
3862
|
for id in ids {
|
|
3819
|
-
let target =
|
|
3863
|
+
guard let target = drawnTarget(id) else { return false }
|
|
3820
3864
|
guard applied.insert(target).inserted else { continue }
|
|
3821
3865
|
guard let document = mutationDocument.node(target), let previous = renderedNode(target) else { return false }
|
|
3822
3866
|
let next = reconcile(document, identity: previous.identity, path: target, previous: previous)
|
|
@@ -3828,6 +3872,21 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
3828
3872
|
return true
|
|
3829
3873
|
}
|
|
3830
3874
|
|
|
3875
|
+
/// The drawn node a change to `id` redraws: the list or drawing a row or
|
|
3876
|
+
/// shape belongs to, else the node itself. A node this batch created is
|
|
3877
|
+
/// not drawn yet (a drawing an `:if` just revealed, its shapes inserted
|
|
3878
|
+
/// into it), so its nearest drawn ancestor draws it, rather than the whole
|
|
3879
|
+
/// screen being reconciled again for want of it.
|
|
3880
|
+
private func drawnTarget(_ id: String) -> String? {
|
|
3881
|
+
var current: String? = id
|
|
3882
|
+
while let candidate = current {
|
|
3883
|
+
let target = flatListOwners[candidate] ?? svgOwners[candidate] ?? candidate
|
|
3884
|
+
if renderedNode(target) != nil { return target }
|
|
3885
|
+
current = mutationDocument.parentId(of: candidate)
|
|
3886
|
+
}
|
|
3887
|
+
return nil
|
|
3888
|
+
}
|
|
3889
|
+
|
|
3831
3890
|
private func renderedNode(_ id: String, below node: RenderedNode? = nil) -> RenderedNode? {
|
|
3832
3891
|
guard let node = node ?? renderedRoot else { return nil }
|
|
3833
3892
|
if node.protocolId == id { return node }
|