craft-native 0.0.183 → 0.0.185
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 +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/ios/templates/CraftNativeScreen.swift +145 -68
- package/package.json +1 -1
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.185";
|
|
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
|
@@ -6332,9 +6332,17 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
6332
6332
|
view.accessibilityValue = nil
|
|
6333
6333
|
}
|
|
6334
6334
|
let role = props["accessibilityRole"] as? String
|
|
6335
|
-
|
|
6335
|
+
// A scroll view, a list or a group of controls holds what VoiceOver
|
|
6336
|
+
// reads: its label names the group, as a tab strip or a pager's
|
|
6337
|
+
// name, and never stands in for its content. As an element of its
|
|
6338
|
+
// own, a labelled pager hid every page from VoiceOver.
|
|
6339
|
+
let container = ["ScrollView", "FlatList", "SectionList"].contains(type)
|
|
6340
|
+
|| ["tablist", "radiogroup", "list", "menu", "menubar", "toolbar"].contains(role ?? "")
|
|
6341
|
+
view.isAccessibilityElement = !container && role != "none" && (view.accessibilityLabel != nil || role != nil || ["Text", "Button", "Link", "Checkbox", "Image", "ImageBackground", "Video", "TextInput", "Picker", "DatePicker", "Switch", "Slider"].contains(type))
|
|
6342
|
+
view.accessibilityContainerType = container && view.accessibilityLabel != nil ? .semanticGroup : .none
|
|
6336
6343
|
var traits: UIAccessibilityTraits = []
|
|
6337
6344
|
switch role ?? type.lowercased() {
|
|
6345
|
+
case "tablist": traits.insert(.tabBar)
|
|
6338
6346
|
case "button", "checkbox": traits.insert(.button)
|
|
6339
6347
|
case "image": traits.insert(.image)
|
|
6340
6348
|
case "header": traits.insert(.header)
|
|
@@ -6917,10 +6925,15 @@ final class CraftNativeAnimationRecord {
|
|
|
6917
6925
|
let fontSize: CGFloat?
|
|
6918
6926
|
let textColor: UIColor?
|
|
6919
6927
|
let transitionID: String?
|
|
6928
|
+
/// Where it sat in its parent, to be put back after it faded elsewhere.
|
|
6929
|
+
let index: Int?
|
|
6930
|
+
let arrangedIndex: Int?
|
|
6920
6931
|
|
|
6921
6932
|
init(view: UIView, screen: UIView, transitionID: String?) {
|
|
6922
6933
|
self.view = view
|
|
6923
6934
|
superview = view.superview
|
|
6935
|
+
index = view.superview?.subviews.firstIndex(of: view)
|
|
6936
|
+
arrangedIndex = (view.superview as? UIStackView)?.arrangedSubviews.firstIndex(of: view)
|
|
6924
6937
|
frame = view.frame
|
|
6925
6938
|
frameOnScreen = view.superview?.convert(view.frame, to: screen) ?? view.frame
|
|
6926
6939
|
alpha = view.alpha
|
|
@@ -6939,6 +6952,10 @@ final class CraftNativeAnimationTransaction {
|
|
|
6939
6952
|
var screenBackground: UIColor?
|
|
6940
6953
|
/// Reduce Motion: the screen as it was, crossfaded into the new one.
|
|
6941
6954
|
var picture: UIView?
|
|
6955
|
+
/// Commits the change as the run loop finishes the pass it came in.
|
|
6956
|
+
var observer: CFRunLoopObserver?
|
|
6957
|
+
/// The screen as it was, drawn over it until the animation is set up.
|
|
6958
|
+
var cover: UIView?
|
|
6942
6959
|
|
|
6943
6960
|
init(spec: CraftNativeAnimationSpec, reduceMotion: Bool) {
|
|
6944
6961
|
self.spec = spec
|
|
@@ -6948,7 +6965,11 @@ final class CraftNativeAnimationTransaction {
|
|
|
6948
6965
|
|
|
6949
6966
|
/// An animation under way: finished at once if another change comes first.
|
|
6950
6967
|
final class CraftNativeRunningAnimation {
|
|
6968
|
+
/// The Core Animation key every animation of a change goes under.
|
|
6969
|
+
static let key = "craft.change"
|
|
6951
6970
|
var animators: [UIViewPropertyAnimator] = []
|
|
6971
|
+
/// Live layers carrying an animation of the change, under `key` + a suffix.
|
|
6972
|
+
var layers: [(CALayer, String)] = []
|
|
6952
6973
|
var cleanup: [() -> Void] = []
|
|
6953
6974
|
private var done = false
|
|
6954
6975
|
|
|
@@ -6959,6 +6980,10 @@ final class CraftNativeRunningAnimation {
|
|
|
6959
6980
|
animator.stopAnimation(false)
|
|
6960
6981
|
animator.finishAnimation(at: .end)
|
|
6961
6982
|
}
|
|
6983
|
+
// A live view's own values were never touched: taking the animation
|
|
6984
|
+
// off leaves it exactly as the screen last drew it.
|
|
6985
|
+
for (layer, key) in layers { layer.removeAnimation(forKey: key) }
|
|
6986
|
+
layers.removeAll()
|
|
6962
6987
|
cleanup.forEach { $0() }
|
|
6963
6988
|
cleanup.removeAll()
|
|
6964
6989
|
}
|
|
@@ -6983,16 +7008,40 @@ extension CraftNativeScreenController {
|
|
|
6983
7008
|
let transaction = CraftNativeAnimationTransaction(spec: CraftNativeAnimationSpec(payload), reduceMotion: UIAccessibility.isReduceMotionEnabled)
|
|
6984
7009
|
transaction.screenBackground = view.backgroundColor
|
|
6985
7010
|
if transaction.reduceMotion {
|
|
6986
|
-
|
|
7011
|
+
// Held over the screen until the change is in, then faded away.
|
|
7012
|
+
if view.window != nil, let picture = view.snapshotView(afterScreenUpdates: false) {
|
|
7013
|
+
picture.frame = view.bounds
|
|
7014
|
+
picture.isUserInteractionEnabled = false
|
|
7015
|
+
view.addSubview(picture)
|
|
7016
|
+
transaction.picture = picture
|
|
7017
|
+
}
|
|
6987
7018
|
} else {
|
|
6988
7019
|
view.layoutIfNeeded()
|
|
6989
7020
|
recordAnimationState(renderedRoot, into: transaction)
|
|
7021
|
+
// The screen as it is, held over it until the change's animation
|
|
7022
|
+
// is set up: UIKit can draw the new screen before the run loop's
|
|
7023
|
+
// pass ends, which showed it for a frame before it animated.
|
|
7024
|
+
if view.window != nil, let cover = view.snapshotView(afterScreenUpdates: false) {
|
|
7025
|
+
cover.frame = view.bounds
|
|
7026
|
+
cover.isUserInteractionEnabled = false
|
|
7027
|
+
view.addSubview(cover)
|
|
7028
|
+
transaction.cover = cover
|
|
7029
|
+
}
|
|
6990
7030
|
}
|
|
6991
7031
|
animationTransaction = transaction
|
|
6992
7032
|
CraftNativeAnimationClock.duration = transaction.spec.duration
|
|
6993
7033
|
// The change comes from the same turn of the script (the state it
|
|
6994
|
-
// changed renders in a microtask)
|
|
6995
|
-
|
|
7034
|
+
// changed renders in a microtask), so it is set up as the run loop
|
|
7035
|
+
// finishes this pass, before Core Animation draws it: drawn first, the
|
|
7036
|
+
// new screen showed for a frame and then jumped back to animate. A
|
|
7037
|
+
// change that comes later still animates.
|
|
7038
|
+
let observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFRunLoopActivity.beforeWaiting.rawValue, false, 0) { [weak self] _, _ in
|
|
7039
|
+
self?.commitAnimatedChange()
|
|
7040
|
+
}
|
|
7041
|
+
if let observer {
|
|
7042
|
+
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, .commonModes)
|
|
7043
|
+
transaction.observer = observer
|
|
7044
|
+
}
|
|
6996
7045
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { [weak self] in self?.commitAnimatedChange() }
|
|
6997
7046
|
}
|
|
6998
7047
|
|
|
@@ -7012,6 +7061,13 @@ extension CraftNativeScreenController {
|
|
|
7012
7061
|
private func commitAnimatedChange() {
|
|
7013
7062
|
guard let transaction = animationTransaction else { return }
|
|
7014
7063
|
animationTransaction = nil
|
|
7064
|
+
if let observer = transaction.observer {
|
|
7065
|
+
CFRunLoopRemoveObserver(CFRunLoopGetMain(), observer, .commonModes)
|
|
7066
|
+
transaction.observer = nil
|
|
7067
|
+
}
|
|
7068
|
+
// Taken off in the same transaction the animations start in.
|
|
7069
|
+
transaction.cover?.removeFromSuperview()
|
|
7070
|
+
transaction.cover = nil
|
|
7015
7071
|
let spec = transaction.spec
|
|
7016
7072
|
let running = CraftNativeRunningAnimation()
|
|
7017
7073
|
runningAnimation = running
|
|
@@ -7019,9 +7075,6 @@ extension CraftNativeScreenController {
|
|
|
7019
7075
|
|
|
7020
7076
|
if transaction.reduceMotion {
|
|
7021
7077
|
guard let picture = transaction.picture else { running.finish(); return }
|
|
7022
|
-
picture.frame = view.bounds
|
|
7023
|
-
picture.isUserInteractionEnabled = false
|
|
7024
|
-
view.addSubview(picture)
|
|
7025
7078
|
let fade = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) { picture.alpha = 0 }
|
|
7026
7079
|
fade.addCompletion { _ in running.finish() }
|
|
7027
7080
|
running.animators.append(fade)
|
|
@@ -7065,26 +7118,69 @@ extension CraftNativeScreenController {
|
|
|
7065
7118
|
view.addSubview(overlay)
|
|
7066
7119
|
running.cleanup.append { overlay.removeFromSuperview() }
|
|
7067
7120
|
|
|
7068
|
-
|
|
7069
|
-
//
|
|
7121
|
+
// Views that are going are the change's own now: they move into the
|
|
7122
|
+
// overlay where they were and fade there. Put back as they were
|
|
7123
|
+
// afterwards, in case the screen shows them again.
|
|
7070
7124
|
let fadeOut = UIViewPropertyAnimator(duration: spec.duration * 0.5, curve: .easeOut)
|
|
7071
|
-
let
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
for record in removedRoots {
|
|
7125
|
+
let quickFade = UIViewPropertyAnimator(duration: spec.duration * 0.35, curve: .easeOut)
|
|
7126
|
+
let travel = UIViewPropertyAnimator(duration: spec.duration, timingParameters: spec.timing)
|
|
7127
|
+
running.animators = [fadeOut, quickFade, travel]
|
|
7128
|
+
func adopt(_ record: CraftNativeAnimationRecord, fading fade: UIViewPropertyAnimator) {
|
|
7076
7129
|
let going = record.view
|
|
7130
|
+
let resizes = going.translatesAutoresizingMaskIntoConstraints
|
|
7131
|
+
let base = going.transform
|
|
7077
7132
|
going.translatesAutoresizingMaskIntoConstraints = true
|
|
7078
7133
|
going.transform = .identity
|
|
7079
7134
|
going.frame = record.frameOnScreen
|
|
7135
|
+
going.alpha = record.alpha
|
|
7080
7136
|
overlay.addSubview(going)
|
|
7081
|
-
|
|
7137
|
+
fade.addAnimations { going.alpha = 0 }
|
|
7138
|
+
running.cleanup.append {
|
|
7139
|
+
going.removeFromSuperview()
|
|
7140
|
+
going.transform = base
|
|
7141
|
+
going.alpha = record.alpha
|
|
7142
|
+
going.translatesAutoresizingMaskIntoConstraints = resizes
|
|
7143
|
+
going.frame = record.frame
|
|
7144
|
+
// Back in a parent that went with it, as it was: a screen
|
|
7145
|
+
// that shows that subtree again finds it whole.
|
|
7146
|
+
if let parent = record.superview, parent.window == nil {
|
|
7147
|
+
if let stack = parent as? UIStackView, let at = record.arrangedIndex {
|
|
7148
|
+
stack.insertArrangedSubview(going, at: min(at, stack.arrangedSubviews.count))
|
|
7149
|
+
} else if let at = record.index {
|
|
7150
|
+
parent.insertSubview(going, at: min(at, parent.subviews.count))
|
|
7151
|
+
}
|
|
7152
|
+
}
|
|
7153
|
+
}
|
|
7154
|
+
}
|
|
7155
|
+
for record in removedRoots { adopt(record, fading: fadeOut) }
|
|
7156
|
+
|
|
7157
|
+
// Live views are animated on their layers only, from how they were to
|
|
7158
|
+
// how the screen draws them now: nothing the screen sets on them
|
|
7159
|
+
// meanwhile (the next second on a timer, a style patch) is undone.
|
|
7160
|
+
let fades = spec.duration * 0.6
|
|
7161
|
+
func animate(_ layer: CALayer, _ keyPath: String, from: Any, duration: TimeInterval? = nil, spring: Bool = true) {
|
|
7162
|
+
let animation: CABasicAnimation
|
|
7163
|
+
if spring, duration == nil, spec.curve == "spring" {
|
|
7164
|
+
let bouncy = CASpringAnimation(keyPath: keyPath)
|
|
7165
|
+
bouncy.mass = 1
|
|
7166
|
+
bouncy.stiffness = pow(2 * .pi / spec.duration, 2)
|
|
7167
|
+
bouncy.damping = 2 * sqrt(bouncy.stiffness) * (1 - spec.bounce)
|
|
7168
|
+
bouncy.duration = bouncy.settlingDuration
|
|
7169
|
+
animation = bouncy
|
|
7170
|
+
} else {
|
|
7171
|
+
animation = CABasicAnimation(keyPath: keyPath)
|
|
7172
|
+
animation.duration = duration ?? spec.duration
|
|
7173
|
+
animation.timingFunction = CAMediaTimingFunction(name: spec.curve == "linear" ? .linear : .easeInEaseOut)
|
|
7174
|
+
}
|
|
7175
|
+
animation.fromValue = from
|
|
7176
|
+
let key = "\(CraftNativeRunningAnimation.key).\(keyPath)"
|
|
7177
|
+
layer.add(animation, forKey: key)
|
|
7178
|
+
running.layers.append((layer, key))
|
|
7082
7179
|
}
|
|
7083
7180
|
|
|
7084
|
-
// The background, the screen's own included,
|
|
7181
|
+
// The background, the screen's own included, comes from the old colour.
|
|
7085
7182
|
if let before = transaction.screenBackground, let now = view.backgroundColor, !before.isEqual(now) {
|
|
7086
|
-
view.backgroundColor
|
|
7087
|
-
main.addAnimations { self.view.backgroundColor = now }
|
|
7183
|
+
animate(view.layer, "backgroundColor", from: before.cgColor, spring: false)
|
|
7088
7184
|
}
|
|
7089
7185
|
|
|
7090
7186
|
// Shared elements: the new view starts where the old one was, at its
|
|
@@ -7092,58 +7188,56 @@ extension CraftNativeScreenController {
|
|
|
7092
7188
|
for (old, new) in pairs {
|
|
7093
7189
|
let target = new.superview?.convert(new.frame, to: view) ?? new.frame
|
|
7094
7190
|
let move = morph(from: old.frameOnScreen, to: target, uniform: true)
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
new.
|
|
7098
|
-
|
|
7099
|
-
main.addAnimations { new.transform = base }
|
|
7100
|
-
fadeIn.addAnimations { new.alpha = finalAlpha }
|
|
7191
|
+
animate(new.layer, "transform", from: CATransform3DMakeAffineTransform(new.transform.concatenating(move)))
|
|
7192
|
+
// Crossed quickly, so the two are never both half there for long.
|
|
7193
|
+
animate(new.layer, "opacity", from: 0, duration: spec.duration * 0.35)
|
|
7194
|
+
adopt(old, fading: quickFade)
|
|
7101
7195
|
let going = old.view
|
|
7102
|
-
going.
|
|
7103
|
-
going.transform = .identity
|
|
7104
|
-
going.frame = old.frameOnScreen
|
|
7105
|
-
going.alpha = old.alpha
|
|
7106
|
-
overlay.addSubview(going)
|
|
7107
|
-
main.addAnimations { going.transform = move.inverted() }
|
|
7108
|
-
fadeOut.addAnimations { going.alpha = 0 }
|
|
7109
|
-
running.cleanup.append { new.transform = base; new.alpha = finalAlpha }
|
|
7196
|
+
travel.addAnimations { going.transform = move.inverted() }
|
|
7110
7197
|
}
|
|
7111
7198
|
|
|
7112
7199
|
// New views fade in. A new container holding a shared element keeps it
|
|
7113
7200
|
// in sight: only the rest of it fades, and its colour comes up.
|
|
7201
|
+
func fadeIn(_ added: UIView) {
|
|
7202
|
+
if pairedNew.contains(ObjectIdentifier(added)) { return }
|
|
7203
|
+
guard containsShared(added, pairedNew: pairedNew) else {
|
|
7204
|
+
animate(added.layer, "opacity", from: 0, duration: fades)
|
|
7205
|
+
return
|
|
7206
|
+
}
|
|
7207
|
+
if let colour = added.backgroundColor, colour != .clear {
|
|
7208
|
+
animate(added.layer, "backgroundColor", from: colour.withAlphaComponent(0).cgColor, duration: fades)
|
|
7209
|
+
}
|
|
7210
|
+
added.subviews.forEach(fadeIn)
|
|
7211
|
+
}
|
|
7114
7212
|
for added in inserted where !(added.superview.map { insertedIds.contains(ObjectIdentifier($0)) } ?? false) {
|
|
7115
|
-
|
|
7213
|
+
fadeIn(added)
|
|
7116
7214
|
}
|
|
7117
7215
|
|
|
7118
7216
|
// Views that stay go to their new places and sizes.
|
|
7119
7217
|
for record in records.values where present.contains(ObjectIdentifier(record.view)) {
|
|
7120
7218
|
let stays = record.view
|
|
7121
7219
|
if let label = stays as? UILabel, let before = record.fontSize, abs(before - label.font.pointSize) > 0.5 {
|
|
7122
|
-
let base = label.transform
|
|
7123
7220
|
let target = label.superview?.convert(label.frame, to: view) ?? label.frame
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7221
|
+
let move = morph(from: record.frameOnScreen, to: target, uniform: true)
|
|
7222
|
+
animate(label.layer, "transform", from: CATransform3DMakeAffineTransform(label.transform.concatenating(move)))
|
|
7223
|
+
} else if record.superview === stays.superview, record.frame != stays.frame, stays.transform.isIdentity {
|
|
7224
|
+
animate(stays.layer, "position", from: CGPoint(x: record.frame.midX, y: record.frame.midY))
|
|
7225
|
+
if record.frame.size != stays.frame.size {
|
|
7226
|
+
animate(stays.layer, "bounds.size", from: record.frame.size)
|
|
7227
|
+
}
|
|
7131
7228
|
}
|
|
7132
7229
|
if let before = record.backgroundColor, let now = stays.backgroundColor, !before.isEqual(now) {
|
|
7133
|
-
stays.backgroundColor
|
|
7134
|
-
main.addAnimations { stays.backgroundColor = now }
|
|
7135
|
-
}
|
|
7136
|
-
if let label = stays as? UILabel, let before = record.textColor, !before.isEqual(label.textColor) {
|
|
7137
|
-
let now = label.textColor
|
|
7138
|
-
label.textColor = before
|
|
7139
|
-
UIView.transition(with: label, duration: spec.duration * 0.6, options: [.transitionCrossDissolve, .allowAnimatedContent]) { label.textColor = now }
|
|
7230
|
+
animate(stays.layer, "backgroundColor", from: before.cgColor, spring: false)
|
|
7140
7231
|
}
|
|
7141
7232
|
}
|
|
7142
7233
|
|
|
7143
|
-
|
|
7234
|
+
travel.addCompletion { _ in running.finish() }
|
|
7144
7235
|
fadeOut.startAnimation()
|
|
7145
|
-
|
|
7146
|
-
|
|
7236
|
+
quickFade.startAnimation()
|
|
7237
|
+
travel.startAnimation()
|
|
7238
|
+
// The springs settle a little after `duration`; the views are already
|
|
7239
|
+
// where the screen draws them, so finishing then changes nothing.
|
|
7240
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + spec.duration * 2) { running.finish() }
|
|
7147
7241
|
}
|
|
7148
7242
|
|
|
7149
7243
|
/// The transform that puts a view at `final` where `start` was: moved
|
|
@@ -7158,23 +7252,6 @@ extension CraftNativeScreenController {
|
|
|
7158
7252
|
return CGAffineTransform(translationX: dx, y: dy).scaledBy(x: sx, y: sy)
|
|
7159
7253
|
}
|
|
7160
7254
|
|
|
7161
|
-
private func fadeInInserted(_ view: UIView, pairedNew: Set<ObjectIdentifier>, animator: UIViewPropertyAnimator, running: CraftNativeRunningAnimation) {
|
|
7162
|
-
if pairedNew.contains(ObjectIdentifier(view)) { return }
|
|
7163
|
-
guard containsShared(view, pairedNew: pairedNew) else {
|
|
7164
|
-
let finalAlpha = view.alpha
|
|
7165
|
-
view.alpha = 0
|
|
7166
|
-
animator.addAnimations { view.alpha = finalAlpha }
|
|
7167
|
-
running.cleanup.append { view.alpha = finalAlpha }
|
|
7168
|
-
return
|
|
7169
|
-
}
|
|
7170
|
-
if let colour = view.backgroundColor, colour != .clear {
|
|
7171
|
-
view.backgroundColor = colour.withAlphaComponent(0)
|
|
7172
|
-
animator.addAnimations { view.backgroundColor = colour }
|
|
7173
|
-
running.cleanup.append { view.backgroundColor = colour }
|
|
7174
|
-
}
|
|
7175
|
-
for child in view.subviews { fadeInInserted(child, pairedNew: pairedNew, animator: animator, running: running) }
|
|
7176
|
-
}
|
|
7177
|
-
|
|
7178
7255
|
private func containsShared(_ view: UIView, pairedNew: Set<ObjectIdentifier>) -> Bool {
|
|
7179
7256
|
if pairedNew.contains(ObjectIdentifier(view)) { return true }
|
|
7180
7257
|
return view.subviews.contains { containsShared($0, pairedNew: pairedNew) }
|