@craft-native/ios 0.0.249 → 0.0.250
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
|
@@ -3788,7 +3788,6 @@ private final class CraftNativeModalView: UIView, UIGestureRecognizerDelegate {
|
|
|
3788
3788
|
private let blocker = UIView()
|
|
3789
3789
|
private let backdropTap = UITapGestureRecognizer()
|
|
3790
3790
|
private var renderedVisible = false
|
|
3791
|
-
private var dismissSuppressedByRequest = false
|
|
3792
3791
|
private var animationGeneration = 0
|
|
3793
3792
|
var onRequestClose: ((String) -> Void)?
|
|
3794
3793
|
|
|
@@ -3877,25 +3876,15 @@ private final class CraftNativeModalView: UIView, UIGestureRecognizerDelegate {
|
|
|
3877
3876
|
transform = .identity
|
|
3878
3877
|
alpha = 0
|
|
3879
3878
|
isUserInteractionEnabled = true
|
|
3880
|
-
dismissSuppressedByRequest = false
|
|
3881
3879
|
onRequestClose = nil
|
|
3882
3880
|
}
|
|
3883
3881
|
|
|
3884
3882
|
func requestClose(reason: String = "backdrop") {
|
|
3885
3883
|
guard let onRequestClose else { return }
|
|
3886
|
-
dismissSuppressedByRequest = true
|
|
3887
3884
|
isUserInteractionEnabled = false
|
|
3888
3885
|
onRequestClose(reason)
|
|
3889
3886
|
}
|
|
3890
3887
|
|
|
3891
|
-
func consumeDismissSuppression() -> Bool {
|
|
3892
|
-
let suppressed = dismissSuppressedByRequest
|
|
3893
|
-
dismissSuppressedByRequest = false
|
|
3894
|
-
return suppressed
|
|
3895
|
-
}
|
|
3896
|
-
|
|
3897
|
-
func clearDismissSuppression() { dismissSuppressedByRequest = false }
|
|
3898
|
-
|
|
3899
3888
|
@objc private func backdropTapped() { requestClose(reason: "backdrop") }
|
|
3900
3889
|
|
|
3901
3890
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
|
|
@@ -3993,9 +3982,10 @@ private final class CraftNativeScrollView: UIScrollView, UIGestureRecognizerDele
|
|
|
3993
3982
|
required init?(coder: NSCoder) { nil }
|
|
3994
3983
|
|
|
3995
3984
|
override func touchesShouldCancel(in view: UIView) -> Bool {
|
|
3996
|
-
//
|
|
3997
|
-
//
|
|
3998
|
-
|
|
3985
|
+
// Buttons and text fields yield once a scroll drag begins; an
|
|
3986
|
+
// ordinary tap still reaches them immediately. Value controls such
|
|
3987
|
+
// as sliders, switches and date pickers keep their tracking gesture.
|
|
3988
|
+
if view is UIButton || view is UITextField { return true }
|
|
3999
3989
|
if view is UIControl { return false }
|
|
4000
3990
|
return super.touchesShouldCancel(in: view)
|
|
4001
3991
|
}
|
|
@@ -7366,11 +7356,9 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
7366
7356
|
modal.onRequestClose = nonEmptyHandler(events["onRequestClose"]).map { handler in
|
|
7367
7357
|
{ [weak self] reason in self?.send(type: "EVENT", payload: ["handlerName": handler, "nativeEvent": ["reason": reason]]) }
|
|
7368
7358
|
}
|
|
7369
|
-
if visible { modal.clearDismissSuppression() }
|
|
7370
|
-
let suppressDismiss = !visible && modal.consumeDismissSuppression()
|
|
7371
7359
|
if wasVisible != visible, visible || wasVisible == true {
|
|
7372
7360
|
let lifecycleHandler = visible ? events["onShow"] : events["onDismiss"]
|
|
7373
|
-
if let lifecycleHandler = nonEmptyHandler(lifecycleHandler)
|
|
7361
|
+
if let lifecycleHandler = nonEmptyHandler(lifecycleHandler) {
|
|
7374
7362
|
send(type: "EVENT", payload: ["handlerName": lifecycleHandler, "nativeEvent": [:]])
|
|
7375
7363
|
}
|
|
7376
7364
|
}
|
|
@@ -7448,10 +7436,10 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
7448
7436
|
// already-stopped view changes hidesWhenStopped to false.
|
|
7449
7437
|
// Android applies the requested visibility on every render;
|
|
7450
7438
|
// keep the two hosts in lockstep for dynamic prop changes.
|
|
7451
|
-
indicator.isHidden = indicator.hidesWhenStopped
|
|
7439
|
+
indicator.isHidden = style["display"] as? String == "none" || indicator.hidesWhenStopped
|
|
7452
7440
|
} else {
|
|
7453
7441
|
indicator.startAnimating()
|
|
7454
|
-
indicator.isHidden =
|
|
7442
|
+
indicator.isHidden = style["display"] as? String == "none"
|
|
7455
7443
|
}
|
|
7456
7444
|
case "Svg":
|
|
7457
7445
|
let drawing = result as! CraftNativeSvgView
|
|
@@ -690,8 +690,15 @@ enum CraftNativePush {
|
|
|
690
690
|
|
|
691
691
|
// MARK: - Share sheet
|
|
692
692
|
|
|
693
|
-
/// A URL stays a URL in Messages
|
|
693
|
+
/// A URL stays a URL in Messages and AirDrop. Its title belongs in the
|
|
694
694
|
/// system preview and mail subject, rather than becoming a second text item.
|
|
695
|
+
///
|
|
696
|
+
/// Copy is the exception: it writes the address as text. A URL-only
|
|
697
|
+
/// pasteboard item has no textual representation, so `UIPasteboard.string`
|
|
698
|
+
/// is nil, a web page's `navigator.clipboard.readText()` gets nothing, and
|
|
699
|
+
/// so does the simulator's `pbpaste` - the shared link copied as nothing
|
|
700
|
+
/// anywhere text is read. Text that is an address is still detected as a
|
|
701
|
+
/// link wherever it is pasted.
|
|
695
702
|
final class CraftShareLink: NSObject, UIActivityItemSource {
|
|
696
703
|
let url: URL
|
|
697
704
|
let title: String
|
|
@@ -703,7 +710,9 @@ final class CraftShareLink: NSObject, UIActivityItemSource {
|
|
|
703
710
|
|
|
704
711
|
func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any { url }
|
|
705
712
|
|
|
706
|
-
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
|
|
713
|
+
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
|
|
714
|
+
activityType == .copyToPasteboard ? url.absoluteString : url
|
|
715
|
+
}
|
|
707
716
|
|
|
708
717
|
func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String { title }
|
|
709
718
|
|
|
@@ -139,6 +139,33 @@ final class DriverTests: XCTestCase {
|
|
|
139
139
|
return nil
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/// Whether `target`, or a field inside it, holds the keyboard.
|
|
143
|
+
func hasKeyboardFocus(_ target: XCUIElement) -> Bool {
|
|
144
|
+
if (target.value(forKey: "hasKeyboardFocus") as? Bool) == true { return true }
|
|
145
|
+
return target.descendants(matching: .any).matching(NSPredicate(format: "hasKeyboardFocus == 1")).firstMatch.exists
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Taps `target` until it holds the keyboard, for up to `seconds`.
|
|
149
|
+
///
|
|
150
|
+
/// A tap is not focus. The first keyboard a freshly created simulator
|
|
151
|
+
/// shows takes its keyboard process many seconds to start - one CI run
|
|
152
|
+
/// spent 20s delivering the tap - and the field had not become first
|
|
153
|
+
/// responder when the text was typed, which failed with "Neither element
|
|
154
|
+
/// nor any descendant has keyboard focus". A later tap lands once the app
|
|
155
|
+
/// is idle again.
|
|
156
|
+
func focus(_ target: XCUIElement, seconds: Double = 15) -> Bool {
|
|
157
|
+
let end = Date().addingTimeInterval(seconds)
|
|
158
|
+
repeat {
|
|
159
|
+
target.tap()
|
|
160
|
+
let settle = Date().addingTimeInterval(3)
|
|
161
|
+
repeat {
|
|
162
|
+
if hasKeyboardFocus(target) { return true }
|
|
163
|
+
Thread.sleep(forTimeInterval: 0.25)
|
|
164
|
+
} while Date() < settle
|
|
165
|
+
} while Date() < end
|
|
166
|
+
return hasKeyboardFocus(target)
|
|
167
|
+
}
|
|
168
|
+
|
|
142
169
|
func waitFor(_ text: String, _ seconds: Double) -> XCUIElement? {
|
|
143
170
|
poll(seconds) { element(containing: text) }
|
|
144
171
|
}
|
|
@@ -375,8 +402,10 @@ final class DriverTests: XCTestCase {
|
|
|
375
402
|
let idParts = arg.split(separator: " ", maxSplits: 1).map(String.init)
|
|
376
403
|
let target = identified(idParts[0])
|
|
377
404
|
if target.waitForExistence(timeout: 10) {
|
|
378
|
-
target
|
|
379
|
-
|
|
405
|
+
guard focus(target) else {
|
|
406
|
+
check(false, "typeid \(idParts[0]) takes keyboard focus")
|
|
407
|
+
return
|
|
408
|
+
}
|
|
380
409
|
app.typeText(idParts.count > 1 ? idParts[1] : "")
|
|
381
410
|
note("typeid \(idParts[0]): ok")
|
|
382
411
|
} else { XCTFail("Element not found"); note("FAIL typeid \(idParts[0]): NOT FOUND") }
|
|
@@ -402,8 +431,10 @@ final class DriverTests: XCTestCase {
|
|
|
402
431
|
let index = Int(fieldParts[0]) ?? 0
|
|
403
432
|
let fields = app.textFields.allElementsBoundByIndex + app.secureTextFields.allElementsBoundByIndex
|
|
404
433
|
if index >= 0 && index < fields.count {
|
|
405
|
-
fields[index]
|
|
406
|
-
|
|
434
|
+
guard focus(fields[index]) else {
|
|
435
|
+
check(false, "field \(index) takes keyboard focus")
|
|
436
|
+
return
|
|
437
|
+
}
|
|
407
438
|
app.typeText(fieldParts.count > 1 ? fieldParts[1] : "")
|
|
408
439
|
note("field \(index): ok")
|
|
409
440
|
} else { XCTFail("Element not found"); note("FAIL field \(index): NOT FOUND (\(fields.count) fields)") }
|