@craft-native/ios 0.0.248 → 0.0.249
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/package.json
CHANGED
package/templates/CraftApp.swift
CHANGED
|
@@ -648,6 +648,9 @@ final class CraftChrome: ObservableObject {
|
|
|
648
648
|
var titleOverride: String? = nil
|
|
649
649
|
var iconTint: String? = nil
|
|
650
650
|
var iconPulse = false
|
|
651
|
+
/// The page's own symbol for the tab while it says so (a live
|
|
652
|
+
/// reading's filled heart), in place of the configured one.
|
|
653
|
+
var iconSymbol: String? = nil
|
|
651
654
|
var selectedSymbol: String? = nil
|
|
652
655
|
var selectedIconTint: String? = nil
|
|
653
656
|
var selectedIconPulse: Bool? = nil
|
|
@@ -658,7 +661,7 @@ final class CraftChrome: ObservableObject {
|
|
|
658
661
|
}
|
|
659
662
|
|
|
660
663
|
func iconAppearance(selected: Bool, motionAllowed: Bool) -> (symbol: String, tint: String?, pulse: Bool) {
|
|
661
|
-
let name = selected ? selectedSymbol ?? symbol : symbol
|
|
664
|
+
let name = selected ? selectedSymbol ?? iconSymbol ?? symbol : iconSymbol ?? symbol
|
|
662
665
|
let tint = selected ? selectedIconTint ?? iconTint : iconTint
|
|
663
666
|
let pulse = selected ? selectedIconPulse ?? iconPulse : iconPulse
|
|
664
667
|
return (name, tint, pulse && motionAllowed)
|
|
@@ -750,6 +753,9 @@ final class CraftChrome: ObservableObject {
|
|
|
750
753
|
if item.keys.contains("iconPulse") {
|
|
751
754
|
next.iconPulse = item["iconPulse"] as? Bool ?? false
|
|
752
755
|
}
|
|
756
|
+
if item.keys.contains("symbol") {
|
|
757
|
+
next.iconSymbol = Tab.normalizedSelectedSymbol(item["symbol"] as? String)
|
|
758
|
+
}
|
|
753
759
|
if item.keys.contains("selectedSymbol") {
|
|
754
760
|
next.selectedSymbol = Tab.normalizedSelectedSymbol(item["selectedSymbol"] as? String)
|
|
755
761
|
}
|
|
@@ -1249,8 +1249,7 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
|
|
|
1249
1249
|
// create a second web root here: the shared WebView cannot draw
|
|
1250
1250
|
// both pages during the same gesture.
|
|
1251
1251
|
guard hybrid.routes.match(id) != nil else { return false }
|
|
1252
|
-
target = CraftFramePacing.shared.measure("tab swipe mount \(id)") { makeStack(tab: id, rootPath: id) }
|
|
1253
|
-
(target.topViewController as? CraftNativeScreenController)?.screenVisibilityChanged(false)
|
|
1252
|
+
target = CraftFramePacing.shared.measure("tab swipe mount \(id)") { makeStack(tab: id, rootPath: id, hidden: true) }
|
|
1254
1253
|
}
|
|
1255
1254
|
guard current.view !== target.view,
|
|
1256
1255
|
!(current.topViewController is CraftHybridWebSlot && target.topViewController is CraftHybridWebSlot),
|
|
@@ -1546,6 +1545,12 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
|
|
|
1546
1545
|
|
|
1547
1546
|
private var visibleStack: UINavigationController? { stacks[currentTab] }
|
|
1548
1547
|
|
|
1548
|
+
/// A swipe preview can be visible without owning navigation or focus.
|
|
1549
|
+
/// UIKit appearance callbacks must follow the committed native stack.
|
|
1550
|
+
func isFocusedScreen(_ screen: CraftNativeScreenController) -> Bool {
|
|
1551
|
+
visibleStack?.topViewController === screen
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1549
1554
|
var webIsVisible: Bool {
|
|
1550
1555
|
guard let webHost, let slot = visibleStack?.topViewController as? CraftHybridWebSlot else { return false }
|
|
1551
1556
|
return webHost.view.superview === slot.view
|
|
@@ -1557,7 +1562,11 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
|
|
|
1557
1562
|
/// the view hierarchy, so its screen never takes the shell's chrome (the
|
|
1558
1563
|
/// bar, the tab accessory) from the one on screen.
|
|
1559
1564
|
private func makeStack(tab: String, rootPath: String, hidden: Bool = false) -> UINavigationController {
|
|
1560
|
-
let
|
|
1565
|
+
let root = entry(for: rootPath, isTabRoot: true, depth: nil)
|
|
1566
|
+
// Constructing the navigation controller can load the root's view and
|
|
1567
|
+
// mount its script immediately. Set its initial focus before that.
|
|
1568
|
+
if hidden { (root as? CraftNativeScreenController)?.screenVisibilityChanged(false) }
|
|
1569
|
+
let nav = UINavigationController(rootViewController: root)
|
|
1561
1570
|
nav.delegate = self
|
|
1562
1571
|
nav.view.backgroundColor = config.resolvedBackgroundColor
|
|
1563
1572
|
nav.additionalSafeAreaInsets.bottom = safeAreaForTabBar()
|
|
@@ -3993,6 +3993,9 @@ private final class CraftNativeScrollView: UIScrollView, UIGestureRecognizerDele
|
|
|
3993
3993
|
required init?(coder: NSCoder) { nil }
|
|
3994
3994
|
|
|
3995
3995
|
override func touchesShouldCancel(in view: UIView) -> Bool {
|
|
3996
|
+
// A drag beginning on a button scrolls; lifting without dragging
|
|
3997
|
+
// remains a tap. Sliders, switches and text fields keep tracking.
|
|
3998
|
+
if view is UIButton { return true }
|
|
3996
3999
|
if view is UIControl { return false }
|
|
3997
4000
|
return super.touchesShouldCancel(in: view)
|
|
3998
4001
|
}
|
|
@@ -5143,6 +5146,8 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5143
5146
|
/// sees, not a tab prepared behind it.
|
|
5144
5147
|
private var isShownOnScreen: Bool {
|
|
5145
5148
|
guard let window = view.window else { return false }
|
|
5149
|
+
if let shell = navigationController?.parent as? CraftHybridController,
|
|
5150
|
+
!shell.isFocusedScreen(self) { return false }
|
|
5146
5151
|
var candidate: UIView? = view
|
|
5147
5152
|
while let current = candidate, current !== window {
|
|
5148
5153
|
if current.isHidden { return false }
|
|
@@ -5326,13 +5331,14 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5326
5331
|
}
|
|
5327
5332
|
|
|
5328
5333
|
/// What a screen's script starts with that only the main thread may read:
|
|
5329
|
-
/// the app's state, the appearance, what the device can do.
|
|
5334
|
+
/// the app's state, the appearance, initial visibility, what the device can do.
|
|
5330
5335
|
private struct ScriptInputs {
|
|
5331
5336
|
let capabilityJSON: String
|
|
5332
5337
|
let appState: String
|
|
5333
5338
|
let colorScheme: String
|
|
5334
5339
|
let appearanceJSON: String
|
|
5335
5340
|
let sessionTokenKey: String
|
|
5341
|
+
let focused: Bool?
|
|
5336
5342
|
}
|
|
5337
5343
|
|
|
5338
5344
|
private func scriptInputs() -> ScriptInputs {
|
|
@@ -5343,7 +5349,8 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5343
5349
|
appState: CraftNativeActions.currentAppState(),
|
|
5344
5350
|
colorScheme: colorScheme,
|
|
5345
5351
|
appearanceJSON: appearanceJSON(),
|
|
5346
|
-
sessionTokenKey: Self.jsonString(CraftSession.tokenKey(config))
|
|
5352
|
+
sessionTokenKey: Self.jsonString(CraftSession.tokenKey(config)),
|
|
5353
|
+
focused: reportedVisible
|
|
5347
5354
|
)
|
|
5348
5355
|
}
|
|
5349
5356
|
|
|
@@ -5355,7 +5362,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5355
5362
|
jsContext.setObject(routeProps as NSDictionary, forKeyedSubscript: "__stxNativeProps" as NSString)
|
|
5356
5363
|
// Hidden before its script ran: the SCREEN_FOCUS saying so reached no
|
|
5357
5364
|
// callback, so the script reads it as it starts instead.
|
|
5358
|
-
if
|
|
5365
|
+
if inputs.focused == false {
|
|
5359
5366
|
jsContext.setObject(false, forKeyedSubscript: "__stxNativeFocused" as NSString)
|
|
5360
5367
|
}
|
|
5361
5368
|
jsContext.exceptionHandler = { [weak self] context, exception in
|
|
@@ -9097,7 +9104,9 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
9097
9104
|
// A video with its system controls is a group too: VoiceOver reaches
|
|
9098
9105
|
// play, the scrubber, skipping and full screen inside it.
|
|
9099
9106
|
let controlledVideo = type == "Video" && (props["controls"] as? Bool) == true
|
|
9100
|
-
|
|
9107
|
+
// UIPickerView exposes adjustable component wheels. Making the
|
|
9108
|
+
// picker itself a leaf hides those wheels and their selected values.
|
|
9109
|
+
let container = ["ScrollView", "FlatList", "SectionList", "Picker"].contains(type)
|
|
9101
9110
|
|| ["tablist", "radiogroup", "list", "menu", "menubar", "toolbar"].contains(role ?? "")
|
|
9102
9111
|
|| controlledVideo
|
|
9103
9112
|
view.isAccessibilityElement = !container && role != "none" && (view.accessibilityLabel != nil || role != nil || ["Text", "Button", "Link", "Checkbox", "Image", "ImageBackground", "Video", "TextInput", "Picker", "DatePicker", "Switch", "Slider"].contains(type))
|
|
@@ -9108,7 +9117,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
9108
9117
|
// screen omits redundant accessibility metadata.
|
|
9109
9118
|
var traits: UIAccessibilityTraits = []
|
|
9110
9119
|
switch type {
|
|
9111
|
-
case "Slider", "
|
|
9120
|
+
case "Slider", "DatePicker": traits.insert(.adjustable)
|
|
9112
9121
|
default: break
|
|
9113
9122
|
}
|
|
9114
9123
|
switch role ?? type.lowercased() {
|