@swiftbrowser/swift 0.0.0-stage → 0.1.0
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.swift +23 -0
- package/README.md +12 -2
- package/Sources/Charts/Charts.swift +65 -0
- package/Sources/FoundationBridge/FoundationBridge.swift +77 -0
- package/Sources/FoundationBridge/Locale.swift +86 -0
- package/Sources/FoundationBridge/Timer.swift +48 -0
- package/Sources/FoundationBridge/UITextChecker.swift +30 -0
- package/Sources/FoundationLite/Common.swift +3 -0
- package/Sources/FoundationLite/FoundationLite.swift +171 -0
- package/Sources/FoundationLite/Interaction.swift +146 -0
- package/Sources/FoundationLite/Timer.swift +48 -0
- package/Sources/SwiftUI/API/Accessibility.swift +132 -0
- package/Sources/SwiftUI/API/Alert.swift +221 -0
- package/Sources/SwiftUI/API/Animation.swift +253 -0
- package/Sources/SwiftUI/API/App.swift +57 -0
- package/Sources/SwiftUI/API/Color.swift +161 -0
- package/Sources/SwiftUI/API/Controls.swift +268 -0
- package/Sources/SwiftUI/API/CustomLayout.swift +403 -0
- package/Sources/SwiftUI/API/Decorations.swift +92 -0
- package/Sources/SwiftUI/API/EditMode.swift +269 -0
- package/Sources/SwiftUI/API/Effects.swift +290 -0
- package/Sources/SwiftUI/API/Environment.swift +355 -0
- package/Sources/SwiftUI/API/Focus.swift +129 -0
- package/Sources/SwiftUI/API/Font.swift +131 -0
- package/Sources/SwiftUI/API/Gauge.swift +289 -0
- package/Sources/SwiftUI/API/Geometry.swift +89 -0
- package/Sources/SwiftUI/API/GeometryReader.swift +124 -0
- package/Sources/SwiftUI/API/Gestures.swift +342 -0
- package/Sources/SwiftUI/API/Grid.swift +188 -0
- package/Sources/SwiftUI/API/LabelStyle.swift +117 -0
- package/Sources/SwiftUI/API/Layout.swift +165 -0
- package/Sources/SwiftUI/API/Lists.swift +276 -0
- package/Sources/SwiftUI/API/Menu.swift +51 -0
- package/Sources/SwiftUI/API/Modifiers.swift +243 -0
- package/Sources/SwiftUI/API/Navigation.swift +371 -0
- package/Sources/SwiftUI/API/ObservableObject.swift +279 -0
- package/Sources/SwiftUI/API/OnChange.swift +48 -0
- package/Sources/SwiftUI/API/Phase8Controls.swift +394 -0
- package/Sources/SwiftUI/API/Picker.swift +176 -0
- package/Sources/SwiftUI/API/Presentation.swift +109 -0
- package/Sources/SwiftUI/API/Primitives.swift +390 -0
- package/Sources/SwiftUI/API/Publishers.swift +107 -0
- package/Sources/SwiftUI/API/RowActions.swift +83 -0
- package/Sources/SwiftUI/API/ScrollTargets.swift +104 -0
- package/Sources/SwiftUI/API/ShapeStyle.swift +311 -0
- package/Sources/SwiftUI/API/State.swift +127 -0
- package/Sources/SwiftUI/API/StrokeStyle.swift +30 -0
- package/Sources/SwiftUI/API/Tabs.swift +119 -0
- package/Sources/SwiftUI/API/Tasks.swift +35 -0
- package/Sources/SwiftUI/API/Timeline.swift +212 -0
- package/Sources/SwiftUI/API/Toolbar.swift +215 -0
- package/Sources/SwiftUI/API/View.swift +207 -0
- package/Sources/SwiftUI/API/ViewModifier.swift +40 -0
- package/Sources/SwiftUI/API/VisualEffect.swift +82 -0
- package/Sources/SwiftUI/Charts/Axis.swift +319 -0
- package/Sources/SwiftUI/Charts/Chart.swift +646 -0
- package/Sources/SwiftUI/Charts/ChartContent.swift +376 -0
- package/Sources/SwiftUI/Charts/Marks.swift +204 -0
- package/Sources/SwiftUI/Charts/Plottable.swift +99 -0
- package/Sources/SwiftUI/Charts/Scales.swift +326 -0
- package/Sources/SwiftUI/Runtime/Executor.swift +83 -0
- package/Sources/SwiftUI/Runtime/Exports.swift +65 -0
- package/Sources/SwiftUI/Runtime/JSON.swift +71 -0
- package/Sources/SwiftUI/Runtime/JSONParser.swift +443 -0
- package/Sources/SwiftUI/Runtime/Ops.swift +106 -0
- package/Sources/SwiftUI/Runtime/Reconciler.swift +210 -0
- package/Sources/SwiftUI/Runtime/Runtime.swift +1367 -0
- package/Sources/_FoundationCommon/Bundle.swift +82 -0
- package/Sources/_FoundationCommon/UserDefaults.swift +170 -0
- package/Sources/_FoundationCommon/_FoundationCommon.swift +10 -0
- package/index.d.ts +2 -0
- package/index.mjs +16 -0
- package/package.json +28 -4
- package/sync-sources.mjs +19 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Glue between FoundationEssentials and the SwiftUI shim for the Phase 8
|
|
2
|
+
// controls and list editing (docs/ops-protocol.md, "Phase 8"): the shim
|
|
3
|
+
// cannot name Foundation types, so it is generic over small protocols that
|
|
4
|
+
// this file conforms Foundation's types to. Also the Foundation API the
|
|
5
|
+
// Hacking with SwiftUI projects use that FoundationEssentials lacks:
|
|
6
|
+
// `IndexSet`, `trimmingCharacters(in:)` and the number format styles.
|
|
7
|
+
|
|
8
|
+
import FoundationEssentials
|
|
9
|
+
import SwiftUI
|
|
10
|
+
|
|
11
|
+
// MARK: - Dates
|
|
12
|
+
|
|
13
|
+
extension Date: _DatePickerValue {
|
|
14
|
+
public var _secondsSince1970: Double { timeIntervalSince1970 }
|
|
15
|
+
public init(_secondsSince1970 seconds: Double) {
|
|
16
|
+
self.init(timeIntervalSince1970: seconds)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// MARK: - IndexSet
|
|
21
|
+
|
|
22
|
+
/// The subset of Foundation's `IndexSet` that `onDelete` / `onMove` and
|
|
23
|
+
/// `remove(atOffsets:)` need. FoundationEssentials does not ship `IndexSet`.
|
|
24
|
+
public struct IndexSet: Hashable, Sendable, Sequence, _OffsetSet {
|
|
25
|
+
public private(set) var _offsets: [Int]
|
|
26
|
+
|
|
27
|
+
public init() { _offsets = [] }
|
|
28
|
+
public init(integer: Int) { _offsets = [integer] }
|
|
29
|
+
public init(integersIn range: Range<Int>) { _offsets = Array(range) }
|
|
30
|
+
public init(integersIn range: ClosedRange<Int>) { _offsets = Array(range) }
|
|
31
|
+
public init<S: Sequence>(_ indexes: S) where S.Element == Int {
|
|
32
|
+
_offsets = Array(Set(indexes)).sorted()
|
|
33
|
+
}
|
|
34
|
+
public init(_offsets: [Int]) {
|
|
35
|
+
self._offsets = Array(Set(_offsets)).sorted()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public var count: Int { _offsets.count }
|
|
39
|
+
public var isEmpty: Bool { _offsets.isEmpty }
|
|
40
|
+
public var first: Int? { _offsets.first }
|
|
41
|
+
public var last: Int? { _offsets.last }
|
|
42
|
+
public func contains(_ integer: Int) -> Bool { _offsets.contains(integer) }
|
|
43
|
+
|
|
44
|
+
public mutating func insert(_ integer: Int) {
|
|
45
|
+
if !_offsets.contains(integer) { _offsets = (_offsets + [integer]).sorted() }
|
|
46
|
+
}
|
|
47
|
+
public mutating func remove(_ integer: Int) { _offsets.removeAll { $0 == integer } }
|
|
48
|
+
|
|
49
|
+
public func makeIterator() -> IndexingIterator<[Int]> { _offsets.makeIterator() }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// MARK: - Strings
|
|
53
|
+
|
|
54
|
+
/// The subset of Foundation's `CharacterSet` that `trimmingCharacters(in:)`
|
|
55
|
+
/// needs (FoundationEssentials does not ship `CharacterSet`).
|
|
56
|
+
public struct CharacterSet: Hashable, Sendable {
|
|
57
|
+
var scalars: Set<Unicode.Scalar>
|
|
58
|
+
|
|
59
|
+
public init(charactersIn string: String) {
|
|
60
|
+
scalars = Set(string.unicodeScalars)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public func contains(_ scalar: Unicode.Scalar) -> Bool { scalars.contains(scalar) }
|
|
64
|
+
|
|
65
|
+
/// Space and tab, like Foundation's `whitespaces`.
|
|
66
|
+
public static var whitespaces: CharacterSet { CharacterSet(charactersIn: " \t") }
|
|
67
|
+
public static var whitespacesAndNewlines: CharacterSet { CharacterSet(charactersIn: " \t\n\r") }
|
|
68
|
+
public static var newlines: CharacterSet { CharacterSet(charactersIn: "\n\r") }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
extension StringProtocol {
|
|
72
|
+
/// Removes the characters of `set` from both ends.
|
|
73
|
+
public func trimmingCharacters(in set: CharacterSet) -> String {
|
|
74
|
+
var scalars = Substring(self).unicodeScalars[...]
|
|
75
|
+
while let first = scalars.first, set.contains(first) { scalars.removeFirst() }
|
|
76
|
+
while let last = scalars.last, set.contains(last) { scalars.removeLast() }
|
|
77
|
+
return String(scalars)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Every occurrence of `target` replaced by `replacement`.
|
|
81
|
+
public func replacingOccurrences(of target: String, with replacement: String) -> String {
|
|
82
|
+
guard !target.isEmpty else { return String(self) }
|
|
83
|
+
return String(self).split(separator: target, omittingEmptySubsequences: false).joined(separator: replacement)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// MARK: - Number format styles
|
|
88
|
+
|
|
89
|
+
/// `Text(value, format: .number)` / `.currency(code:)` for floating-point
|
|
90
|
+
/// values, mirroring `Foundation.FloatingPointFormatStyle` (not in
|
|
91
|
+
/// FoundationEssentials). US English only.
|
|
92
|
+
public struct FloatingPointFormatStyle<Value: BinaryFloatingPoint & CustomStringConvertible & LosslessStringConvertible>: _TextFormat, Sendable {
|
|
93
|
+
public typealias Input = Value
|
|
94
|
+
|
|
95
|
+
public init() {}
|
|
96
|
+
|
|
97
|
+
public func _formatText(_ value: Value) -> String { value.formatted() }
|
|
98
|
+
|
|
99
|
+
public func _parseText(_ text: String) -> Value? {
|
|
100
|
+
Value(text.replacingOccurrences(of: ",", with: "").trimmingCharacters(in: .whitespaces))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// `$1,234.50`; negative amounts `-$1.00`. Three-letter codes other than
|
|
104
|
+
/// USD print as `EUR 1,234.50`, like `Locale(identifier: "en_US")`.
|
|
105
|
+
public struct Currency: _TextFormat, Sendable {
|
|
106
|
+
public typealias Input = Value
|
|
107
|
+
public var code: String
|
|
108
|
+
|
|
109
|
+
public init(code: String) { self.code = code }
|
|
110
|
+
|
|
111
|
+
public func _formatText(_ value: Value) -> String {
|
|
112
|
+
let amount = Double(value)
|
|
113
|
+
let digits = _grouped(Substring(String(Int(abs(amount).rounded(.down))))) + "." + _twoDigits(Int(((abs(amount) * 100).rounded()) ) % 100)
|
|
114
|
+
let sign = amount < 0 ? "-" : ""
|
|
115
|
+
return code == "USD" ? "\(sign)$\(digits)" : "\(sign)\(code) \(digits)"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public func _parseText(_ text: String) -> Value? {
|
|
119
|
+
var cleaned = text.trimmingCharacters(in: .whitespaces)
|
|
120
|
+
for token in ["$", ",", code, " "] { cleaned = cleaned.replacingOccurrences(of: token, with: "") }
|
|
121
|
+
return Value(cleaned)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// `Text(count, format: .number)` for integers.
|
|
127
|
+
public struct IntegerFormatStyle<Value: BinaryInteger & LosslessStringConvertible>: _TextFormat, Sendable {
|
|
128
|
+
public typealias Input = Value
|
|
129
|
+
public init() {}
|
|
130
|
+
public func _formatText(_ value: Value) -> String { value.formatted() }
|
|
131
|
+
public func _parseText(_ text: String) -> Value? { Value(text.replacingOccurrences(of: ",", with: "")) }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
extension _TextFormat where Self == FloatingPointFormatStyle<Double> {
|
|
135
|
+
public static var number: FloatingPointFormatStyle<Double> { FloatingPointFormatStyle() }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
extension _TextFormat where Self == FloatingPointFormatStyle<Double>.Currency {
|
|
139
|
+
public static func currency(code: String) -> FloatingPointFormatStyle<Double>.Currency {
|
|
140
|
+
FloatingPointFormatStyle<Double>.Currency(code: code)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
extension _TextFormat where Self == IntegerFormatStyle<Int> {
|
|
145
|
+
public static var number: IntegerFormatStyle<Int> { IntegerFormatStyle() }
|
|
146
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// `Timer` for the browser: `Timer.publish(every:on:in:).autoconnect()` with
|
|
2
|
+
// SwiftUI's `onReceive`, and `scheduledTimer(withTimeInterval:repeats:block:)`.
|
|
3
|
+
// Ticks come from the runtime's executor (`_IntervalPublisher` and
|
|
4
|
+
// `_ExecutorTimer` in the SwiftUI shim), so headless runs stay deterministic.
|
|
5
|
+
// FoundationEssentials has no Timer or RunLoop; these are the pieces SwiftUI
|
|
6
|
+
// code uses. FoundationBridge has the same two types for full Foundation,
|
|
7
|
+
// whose own Timer needs CFRunLoop and cannot link on WASI.
|
|
8
|
+
|
|
9
|
+
import FoundationEssentials
|
|
10
|
+
import SwiftUI
|
|
11
|
+
|
|
12
|
+
public final class RunLoop: @unchecked Sendable {
|
|
13
|
+
public struct Mode: Hashable, Sendable {
|
|
14
|
+
public let rawValue: String
|
|
15
|
+
public init(rawValue: String) { self.rawValue = rawValue }
|
|
16
|
+
public static let `default` = Mode(rawValue: "default")
|
|
17
|
+
public static let common = Mode(rawValue: "common")
|
|
18
|
+
}
|
|
19
|
+
nonisolated(unsafe) public static let main = RunLoop()
|
|
20
|
+
public static var current: RunLoop { main }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public final class Timer: @unchecked Sendable {
|
|
24
|
+
/// Emits the current date every `interval` seconds once subscribed.
|
|
25
|
+
public typealias TimerPublisher = _IntervalPublisher<Date>
|
|
26
|
+
|
|
27
|
+
public static func publish(every interval: TimeInterval, tolerance: TimeInterval? = nil, on runLoop: RunLoop = .main, in mode: RunLoop.Mode = .default, options: Never? = nil) -> TimerPublisher {
|
|
28
|
+
_IntervalPublisher(every: interval) { Date() }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private var core: _ExecutorTimer!
|
|
32
|
+
|
|
33
|
+
public var isValid: Bool { core.isValid }
|
|
34
|
+
public var timeInterval: TimeInterval { core.timeInterval }
|
|
35
|
+
|
|
36
|
+
init(interval: TimeInterval, repeats: Bool, block: @escaping @Sendable (Timer) -> Void) {
|
|
37
|
+
// The core's task holds this timer until it has fired or is invalidated.
|
|
38
|
+
core = _ExecutorTimer(interval: interval, repeats: repeats) { _ in block(self) }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Schedules `block` after `interval` seconds, repeating when asked, on the executor.
|
|
42
|
+
@discardableResult
|
|
43
|
+
public static func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping @Sendable (Timer) -> Void) -> Timer {
|
|
44
|
+
Timer(interval: interval, repeats: repeats, block: block)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public func invalidate() { core.invalidate() }
|
|
48
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/// Scene phase, accessibility environment and text-input hints. The browser
|
|
2
|
+
/// has no assistive-technology bridge, so the accessibility modifiers are
|
|
3
|
+
/// accepted without effect and the accessibility settings read `false`
|
|
4
|
+
/// (see docs/ops-protocol.md, Phase 8).
|
|
5
|
+
|
|
6
|
+
// MARK: - Scene phase
|
|
7
|
+
|
|
8
|
+
/// Whether the app is in the foreground. The renderer reports it with the
|
|
9
|
+
/// `environment` event from the document's visibility and focus.
|
|
10
|
+
public enum ScenePhase: Comparable, Hashable, Sendable {
|
|
11
|
+
case background, inactive, active
|
|
12
|
+
|
|
13
|
+
init?(name: String) {
|
|
14
|
+
switch name {
|
|
15
|
+
case "active": self = .active
|
|
16
|
+
case "inactive": self = .inactive
|
|
17
|
+
case "background": self = .background
|
|
18
|
+
default: return nil
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
enum _ScenePhaseKey: EnvironmentKey { static let defaultValue = ScenePhase.active }
|
|
24
|
+
enum _AccessibilityFlagKey: EnvironmentKey { static let defaultValue = false }
|
|
25
|
+
|
|
26
|
+
extension EnvironmentValues {
|
|
27
|
+
/// `.active` until the renderer's `environment` event says otherwise.
|
|
28
|
+
public var scenePhase: ScenePhase {
|
|
29
|
+
get { self[_ScenePhaseKey.self] }
|
|
30
|
+
set { self[_ScenePhaseKey.self] = newValue }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public var accessibilityDifferentiateWithoutColor: Bool { self[_AccessibilityFlagKey.self] }
|
|
34
|
+
public var accessibilityVoiceOverEnabled: Bool { self[_AccessibilityFlagKey.self] }
|
|
35
|
+
public var accessibilityReduceMotion: Bool { self[_AccessibilityFlagKey.self] }
|
|
36
|
+
public var accessibilityReduceTransparency: Bool { self[_AccessibilityFlagKey.self] }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// MARK: - Accessibility modifiers (no effect)
|
|
40
|
+
|
|
41
|
+
public struct AccessibilityTraits: OptionSet, Sendable {
|
|
42
|
+
public let rawValue: UInt64
|
|
43
|
+
public init(rawValue: UInt64) { self.rawValue = rawValue }
|
|
44
|
+
|
|
45
|
+
public static let isButton = AccessibilityTraits(rawValue: 1 << 0)
|
|
46
|
+
public static let isHeader = AccessibilityTraits(rawValue: 1 << 1)
|
|
47
|
+
public static let isSelected = AccessibilityTraits(rawValue: 1 << 2)
|
|
48
|
+
public static let isLink = AccessibilityTraits(rawValue: 1 << 3)
|
|
49
|
+
public static let isSearchField = AccessibilityTraits(rawValue: 1 << 4)
|
|
50
|
+
public static let isImage = AccessibilityTraits(rawValue: 1 << 5)
|
|
51
|
+
public static let playsSound = AccessibilityTraits(rawValue: 1 << 6)
|
|
52
|
+
public static let isKeyboardKey = AccessibilityTraits(rawValue: 1 << 7)
|
|
53
|
+
public static let isStaticText = AccessibilityTraits(rawValue: 1 << 8)
|
|
54
|
+
public static let isSummaryElement = AccessibilityTraits(rawValue: 1 << 9)
|
|
55
|
+
public static let updatesFrequently = AccessibilityTraits(rawValue: 1 << 10)
|
|
56
|
+
public static let startsMediaSession = AccessibilityTraits(rawValue: 1 << 11)
|
|
57
|
+
public static let allowsDirectInteraction = AccessibilityTraits(rawValue: 1 << 12)
|
|
58
|
+
public static let causesPageTurn = AccessibilityTraits(rawValue: 1 << 13)
|
|
59
|
+
public static let isModal = AccessibilityTraits(rawValue: 1 << 14)
|
|
60
|
+
public static let isToggle = AccessibilityTraits(rawValue: 1 << 15)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public struct AccessibilityChildBehavior: Hashable, Sendable {
|
|
64
|
+
let name: String
|
|
65
|
+
public static let ignore = AccessibilityChildBehavior(name: "ignore")
|
|
66
|
+
public static let contain = AccessibilityChildBehavior(name: "contain")
|
|
67
|
+
public static let combine = AccessibilityChildBehavior(name: "combine")
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
extension View {
|
|
71
|
+
public func accessibilityLabel(_ label: Text) -> some View { self }
|
|
72
|
+
public func accessibilityLabel<S: StringProtocol>(_ label: S) -> some View { self }
|
|
73
|
+
public func accessibilityLabel(_ labelKey: LocalizedStringKey) -> some View { self }
|
|
74
|
+
public func accessibilityHint(_ hint: Text) -> some View { self }
|
|
75
|
+
public func accessibilityHint<S: StringProtocol>(_ hint: S) -> some View { self }
|
|
76
|
+
public func accessibilityHint(_ hintKey: LocalizedStringKey) -> some View { self }
|
|
77
|
+
public func accessibilityValue(_ value: Text) -> some View { self }
|
|
78
|
+
public func accessibilityValue<S: StringProtocol>(_ value: S) -> some View { self }
|
|
79
|
+
public func accessibilityValue(_ valueKey: LocalizedStringKey) -> some View { self }
|
|
80
|
+
public func accessibilityHidden(_ hidden: Bool) -> some View { self }
|
|
81
|
+
public func accessibilityAddTraits(_ traits: AccessibilityTraits) -> some View { self }
|
|
82
|
+
public func accessibilityRemoveTraits(_ traits: AccessibilityTraits) -> some View { self }
|
|
83
|
+
public func accessibilityIdentifier(_ identifier: String) -> some View { self }
|
|
84
|
+
public func accessibilityElement(children: AccessibilityChildBehavior = .ignore) -> some View { self }
|
|
85
|
+
public func accessibilitySortPriority(_ sortPriority: Double) -> some View { self }
|
|
86
|
+
public func accessibilityInputLabels(_ inputLabels: [Text]) -> some View { self }
|
|
87
|
+
public func accessibilityInputLabels<S: StringProtocol>(_ inputLabels: [S]) -> some View { self }
|
|
88
|
+
public func accessibilityInputLabels(_ inputLabelKeys: [LocalizedStringKey]) -> some View { self }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// MARK: - Text input hints (no effect)
|
|
92
|
+
|
|
93
|
+
/// UIKit's keyboard types; the browser shows its own keyboard.
|
|
94
|
+
public enum UIKeyboardType: Int, Hashable, Sendable {
|
|
95
|
+
case `default`, asciiCapable, numbersAndPunctuation, URL, numberPad, phonePad, namePhonePad,
|
|
96
|
+
emailAddress, decimalPad, twitter, webSearch, asciiCapableNumberPad
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// UIKit's content types for autofill, as names.
|
|
100
|
+
public struct UITextContentType: Hashable, Sendable {
|
|
101
|
+
public let rawValue: String
|
|
102
|
+
public init(rawValue: String) { self.rawValue = rawValue }
|
|
103
|
+
|
|
104
|
+
public static let name = UITextContentType(rawValue: "name")
|
|
105
|
+
public static let givenName = UITextContentType(rawValue: "givenName")
|
|
106
|
+
public static let familyName = UITextContentType(rawValue: "familyName")
|
|
107
|
+
public static let emailAddress = UITextContentType(rawValue: "emailAddress")
|
|
108
|
+
public static let telephoneNumber = UITextContentType(rawValue: "telephoneNumber")
|
|
109
|
+
public static let username = UITextContentType(rawValue: "username")
|
|
110
|
+
public static let password = UITextContentType(rawValue: "password")
|
|
111
|
+
public static let newPassword = UITextContentType(rawValue: "newPassword")
|
|
112
|
+
public static let oneTimeCode = UITextContentType(rawValue: "oneTimeCode")
|
|
113
|
+
public static let URL = UITextContentType(rawValue: "URL")
|
|
114
|
+
public static let fullStreetAddress = UITextContentType(rawValue: "fullStreetAddress")
|
|
115
|
+
public static let postalCode = UITextContentType(rawValue: "postalCode")
|
|
116
|
+
public static let creditCardNumber = UITextContentType(rawValue: "creditCardNumber")
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public struct TextInputAutocapitalization: Hashable, Sendable {
|
|
120
|
+
let name: String
|
|
121
|
+
public static let never = TextInputAutocapitalization(name: "never")
|
|
122
|
+
public static let words = TextInputAutocapitalization(name: "words")
|
|
123
|
+
public static let sentences = TextInputAutocapitalization(name: "sentences")
|
|
124
|
+
public static let characters = TextInputAutocapitalization(name: "characters")
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
extension View {
|
|
128
|
+
public func keyboardType(_ type: UIKeyboardType) -> some View { self }
|
|
129
|
+
public func textContentType(_ textContentType: UITextContentType?) -> some View { self }
|
|
130
|
+
public func autocorrectionDisabled(_ disable: Bool = true) -> some View { self }
|
|
131
|
+
public func textInputAutocapitalization(_ autocapitalization: TextInputAutocapitalization?) -> some View { self }
|
|
132
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/// Alerts and confirmation dialogs. Like sheets, the runtime mounts the
|
|
2
|
+
/// actions as a second child of the presenting view while `isPresented` and
|
|
3
|
+
/// emits them inside a root-level `alert` element (see docs/ops-protocol.md,
|
|
4
|
+
/// Phase 8). A tap on one of the action buttons runs its action and dismisses;
|
|
5
|
+
/// a tap on the alert's own id dismisses without an action.
|
|
6
|
+
|
|
7
|
+
/// Views that present an alert or a confirmation dialog.
|
|
8
|
+
protocol _AlertPresenting {
|
|
9
|
+
var _isPresented: Binding<Bool> { get }
|
|
10
|
+
var _base: any View { get }
|
|
11
|
+
var _alertActions: any View { get }
|
|
12
|
+
var _alertTitle: String { get }
|
|
13
|
+
/// The message `Text`'s string, or nil when there is none.
|
|
14
|
+
var _alertMessage: String? { get }
|
|
15
|
+
/// `"alert"` or `"dialog"`.
|
|
16
|
+
var _alertStyle: String { get }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
struct _AlertView<Base: View, Actions: View> {
|
|
20
|
+
let base: Base
|
|
21
|
+
let isPresented: Binding<Bool>
|
|
22
|
+
let title: String
|
|
23
|
+
let message: String?
|
|
24
|
+
let style: String
|
|
25
|
+
let actions: Actions
|
|
26
|
+
}
|
|
27
|
+
extension _AlertView: _PrimitiveView {}
|
|
28
|
+
extension _AlertView: _GroupView {
|
|
29
|
+
// Only the base is a regular child; the runtime adds the actions while presented.
|
|
30
|
+
var _children: [any View] { [base] }
|
|
31
|
+
}
|
|
32
|
+
extension _AlertView: _AlertPresenting {
|
|
33
|
+
var _isPresented: Binding<Bool> { isPresented }
|
|
34
|
+
var _base: any View { base }
|
|
35
|
+
var _alertActions: any View { actions }
|
|
36
|
+
var _alertTitle: String { title }
|
|
37
|
+
var _alertMessage: String? { message }
|
|
38
|
+
var _alertStyle: String { style }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// The message of an alert: the string of the first `Text` in the view, if any.
|
|
42
|
+
@MainActor
|
|
43
|
+
private func _message(of view: any View) -> String? {
|
|
44
|
+
let text = _textContent(of: view)
|
|
45
|
+
return text.isEmpty ? nil : text
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// `presenting:` forms: the alert shows while `isPresented` and the data is
|
|
49
|
+
/// non-nil, and the closures receive the data.
|
|
50
|
+
@MainActor
|
|
51
|
+
private func _presentedData<T>(_ isPresented: Binding<Bool>, _ data: T?) -> (Binding<Bool>, T?) {
|
|
52
|
+
guard data != nil else {
|
|
53
|
+
return (Binding(get: { false }, set: { if !$0 { isPresented.wrappedValue = false } }), nil)
|
|
54
|
+
}
|
|
55
|
+
return (isPresented, data)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Whether a confirmation dialog's title is sent. iOS hides it for
|
|
59
|
+
/// `.automatic`, so only `.visible` sends it.
|
|
60
|
+
private func _dialogTitle(_ title: String, _ visibility: Visibility) -> String {
|
|
61
|
+
visibility == .visible ? title : ""
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
extension View {
|
|
65
|
+
// MARK: Alerts
|
|
66
|
+
|
|
67
|
+
public func alert<S: StringProtocol, A: View>(_ title: S, isPresented: Binding<Bool>,
|
|
68
|
+
@ViewBuilder actions: () -> A) -> some View {
|
|
69
|
+
_AlertView(base: self, isPresented: isPresented, title: String(title), message: nil,
|
|
70
|
+
style: "alert", actions: actions())
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public func alert<S: StringProtocol, A: View, M: View>(_ title: S, isPresented: Binding<Bool>,
|
|
74
|
+
@ViewBuilder actions: () -> A,
|
|
75
|
+
@ViewBuilder message: () -> M) -> some View {
|
|
76
|
+
_AlertView(base: self, isPresented: isPresented, title: String(title), message: _message(of: message()),
|
|
77
|
+
style: "alert", actions: actions())
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public func alert<A: View>(_ title: Text, isPresented: Binding<Bool>,
|
|
81
|
+
@ViewBuilder actions: () -> A) -> some View {
|
|
82
|
+
_AlertView(base: self, isPresented: isPresented, title: title.content, message: nil,
|
|
83
|
+
style: "alert", actions: actions())
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public func alert<A: View, M: View>(_ title: Text, isPresented: Binding<Bool>,
|
|
87
|
+
@ViewBuilder actions: () -> A,
|
|
88
|
+
@ViewBuilder message: () -> M) -> some View {
|
|
89
|
+
_AlertView(base: self, isPresented: isPresented, title: title.content, message: _message(of: message()),
|
|
90
|
+
style: "alert", actions: actions())
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public func alert<A: View>(_ titleKey: LocalizedStringKey, isPresented: Binding<Bool>,
|
|
94
|
+
@ViewBuilder actions: () -> A) -> some View {
|
|
95
|
+
_AlertView(base: self, isPresented: isPresented, title: titleKey.key, message: nil,
|
|
96
|
+
style: "alert", actions: actions())
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public func alert<A: View, M: View>(_ titleKey: LocalizedStringKey, isPresented: Binding<Bool>,
|
|
100
|
+
@ViewBuilder actions: () -> A,
|
|
101
|
+
@ViewBuilder message: () -> M) -> some View {
|
|
102
|
+
_AlertView(base: self, isPresented: isPresented, title: titleKey.key, message: _message(of: message()),
|
|
103
|
+
style: "alert", actions: actions())
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// An alert built from `data`: shown while `isPresented` and `data` is non-nil.
|
|
107
|
+
public func alert<S: StringProtocol, T, A: View>(_ title: S, isPresented: Binding<Bool>, presenting data: T?,
|
|
108
|
+
@ViewBuilder actions: (T) -> A) -> some View {
|
|
109
|
+
let (presented, data) = _presentedData(isPresented, data)
|
|
110
|
+
return _AlertView(base: self, isPresented: presented, title: String(title), message: nil,
|
|
111
|
+
style: "alert", actions: data.map(actions))
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public func alert<S: StringProtocol, T, A: View, M: View>(_ title: S, isPresented: Binding<Bool>, presenting data: T?,
|
|
115
|
+
@ViewBuilder actions: (T) -> A,
|
|
116
|
+
@ViewBuilder message: (T) -> M) -> some View {
|
|
117
|
+
let (presented, data) = _presentedData(isPresented, data)
|
|
118
|
+
return _AlertView(base: self, isPresented: presented, title: String(title),
|
|
119
|
+
message: data.flatMap { _message(of: message($0)) },
|
|
120
|
+
style: "alert", actions: data.map(actions))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public func alert<T, A: View>(_ title: Text, isPresented: Binding<Bool>, presenting data: T?,
|
|
124
|
+
@ViewBuilder actions: (T) -> A) -> some View {
|
|
125
|
+
let (presented, data) = _presentedData(isPresented, data)
|
|
126
|
+
return _AlertView(base: self, isPresented: presented, title: title.content, message: nil,
|
|
127
|
+
style: "alert", actions: data.map(actions))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public func alert<T, A: View, M: View>(_ title: Text, isPresented: Binding<Bool>, presenting data: T?,
|
|
131
|
+
@ViewBuilder actions: (T) -> A,
|
|
132
|
+
@ViewBuilder message: (T) -> M) -> some View {
|
|
133
|
+
let (presented, data) = _presentedData(isPresented, data)
|
|
134
|
+
return _AlertView(base: self, isPresented: presented, title: title.content,
|
|
135
|
+
message: data.flatMap { _message(of: message($0)) },
|
|
136
|
+
style: "alert", actions: data.map(actions))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public func alert<T, A: View>(_ titleKey: LocalizedStringKey, isPresented: Binding<Bool>, presenting data: T?,
|
|
140
|
+
@ViewBuilder actions: (T) -> A) -> some View {
|
|
141
|
+
let (presented, data) = _presentedData(isPresented, data)
|
|
142
|
+
return _AlertView(base: self, isPresented: presented, title: titleKey.key, message: nil,
|
|
143
|
+
style: "alert", actions: data.map(actions))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public func alert<T, A: View, M: View>(_ titleKey: LocalizedStringKey, isPresented: Binding<Bool>, presenting data: T?,
|
|
147
|
+
@ViewBuilder actions: (T) -> A,
|
|
148
|
+
@ViewBuilder message: (T) -> M) -> some View {
|
|
149
|
+
let (presented, data) = _presentedData(isPresented, data)
|
|
150
|
+
return _AlertView(base: self, isPresented: presented, title: titleKey.key,
|
|
151
|
+
message: data.flatMap { _message(of: message($0)) },
|
|
152
|
+
style: "alert", actions: data.map(actions))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// MARK: Confirmation dialogs
|
|
156
|
+
|
|
157
|
+
public func confirmationDialog<S: StringProtocol, A: View>(_ title: S, isPresented: Binding<Bool>,
|
|
158
|
+
titleVisibility: Visibility = .automatic,
|
|
159
|
+
@ViewBuilder actions: () -> A) -> some View {
|
|
160
|
+
_AlertView(base: self, isPresented: isPresented, title: _dialogTitle(String(title), titleVisibility),
|
|
161
|
+
message: nil, style: "dialog", actions: actions())
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
public func confirmationDialog<S: StringProtocol, A: View, M: View>(_ title: S, isPresented: Binding<Bool>,
|
|
165
|
+
titleVisibility: Visibility = .automatic,
|
|
166
|
+
@ViewBuilder actions: () -> A,
|
|
167
|
+
@ViewBuilder message: () -> M) -> some View {
|
|
168
|
+
_AlertView(base: self, isPresented: isPresented, title: _dialogTitle(String(title), titleVisibility),
|
|
169
|
+
message: _message(of: message()), style: "dialog", actions: actions())
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
public func confirmationDialog<A: View>(_ title: Text, isPresented: Binding<Bool>,
|
|
173
|
+
titleVisibility: Visibility = .automatic,
|
|
174
|
+
@ViewBuilder actions: () -> A) -> some View {
|
|
175
|
+
_AlertView(base: self, isPresented: isPresented, title: _dialogTitle(title.content, titleVisibility),
|
|
176
|
+
message: nil, style: "dialog", actions: actions())
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public func confirmationDialog<A: View, M: View>(_ title: Text, isPresented: Binding<Bool>,
|
|
180
|
+
titleVisibility: Visibility = .automatic,
|
|
181
|
+
@ViewBuilder actions: () -> A,
|
|
182
|
+
@ViewBuilder message: () -> M) -> some View {
|
|
183
|
+
_AlertView(base: self, isPresented: isPresented, title: _dialogTitle(title.content, titleVisibility),
|
|
184
|
+
message: _message(of: message()), style: "dialog", actions: actions())
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public func confirmationDialog<A: View>(_ titleKey: LocalizedStringKey, isPresented: Binding<Bool>,
|
|
188
|
+
titleVisibility: Visibility = .automatic,
|
|
189
|
+
@ViewBuilder actions: () -> A) -> some View {
|
|
190
|
+
_AlertView(base: self, isPresented: isPresented, title: _dialogTitle(titleKey.key, titleVisibility),
|
|
191
|
+
message: nil, style: "dialog", actions: actions())
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
public func confirmationDialog<A: View, M: View>(_ titleKey: LocalizedStringKey, isPresented: Binding<Bool>,
|
|
195
|
+
titleVisibility: Visibility = .automatic,
|
|
196
|
+
@ViewBuilder actions: () -> A,
|
|
197
|
+
@ViewBuilder message: () -> M) -> some View {
|
|
198
|
+
_AlertView(base: self, isPresented: isPresented, title: _dialogTitle(titleKey.key, titleVisibility),
|
|
199
|
+
message: _message(of: message()), style: "dialog", actions: actions())
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
public func confirmationDialog<S: StringProtocol, T, A: View>(_ title: S, isPresented: Binding<Bool>,
|
|
203
|
+
titleVisibility: Visibility = .automatic,
|
|
204
|
+
presenting data: T?,
|
|
205
|
+
@ViewBuilder actions: (T) -> A) -> some View {
|
|
206
|
+
let (presented, data) = _presentedData(isPresented, data)
|
|
207
|
+
return _AlertView(base: self, isPresented: presented, title: _dialogTitle(String(title), titleVisibility),
|
|
208
|
+
message: nil, style: "dialog", actions: data.map(actions))
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public func confirmationDialog<S: StringProtocol, T, A: View, M: View>(_ title: S, isPresented: Binding<Bool>,
|
|
212
|
+
titleVisibility: Visibility = .automatic,
|
|
213
|
+
presenting data: T?,
|
|
214
|
+
@ViewBuilder actions: (T) -> A,
|
|
215
|
+
@ViewBuilder message: (T) -> M) -> some View {
|
|
216
|
+
let (presented, data) = _presentedData(isPresented, data)
|
|
217
|
+
return _AlertView(base: self, isPresented: presented, title: _dialogTitle(String(title), titleVisibility),
|
|
218
|
+
message: data.flatMap { _message(of: message($0)) },
|
|
219
|
+
style: "dialog", actions: data.map(actions))
|
|
220
|
+
}
|
|
221
|
+
}
|