@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.
Files changed (74) hide show
  1. package/Package.swift +23 -0
  2. package/README.md +12 -2
  3. package/Sources/Charts/Charts.swift +65 -0
  4. package/Sources/FoundationBridge/FoundationBridge.swift +77 -0
  5. package/Sources/FoundationBridge/Locale.swift +86 -0
  6. package/Sources/FoundationBridge/Timer.swift +48 -0
  7. package/Sources/FoundationBridge/UITextChecker.swift +30 -0
  8. package/Sources/FoundationLite/Common.swift +3 -0
  9. package/Sources/FoundationLite/FoundationLite.swift +171 -0
  10. package/Sources/FoundationLite/Interaction.swift +146 -0
  11. package/Sources/FoundationLite/Timer.swift +48 -0
  12. package/Sources/SwiftUI/API/Accessibility.swift +132 -0
  13. package/Sources/SwiftUI/API/Alert.swift +221 -0
  14. package/Sources/SwiftUI/API/Animation.swift +253 -0
  15. package/Sources/SwiftUI/API/App.swift +57 -0
  16. package/Sources/SwiftUI/API/Color.swift +161 -0
  17. package/Sources/SwiftUI/API/Controls.swift +268 -0
  18. package/Sources/SwiftUI/API/CustomLayout.swift +403 -0
  19. package/Sources/SwiftUI/API/Decorations.swift +92 -0
  20. package/Sources/SwiftUI/API/EditMode.swift +269 -0
  21. package/Sources/SwiftUI/API/Effects.swift +290 -0
  22. package/Sources/SwiftUI/API/Environment.swift +355 -0
  23. package/Sources/SwiftUI/API/Focus.swift +129 -0
  24. package/Sources/SwiftUI/API/Font.swift +131 -0
  25. package/Sources/SwiftUI/API/Gauge.swift +289 -0
  26. package/Sources/SwiftUI/API/Geometry.swift +89 -0
  27. package/Sources/SwiftUI/API/GeometryReader.swift +124 -0
  28. package/Sources/SwiftUI/API/Gestures.swift +342 -0
  29. package/Sources/SwiftUI/API/Grid.swift +188 -0
  30. package/Sources/SwiftUI/API/LabelStyle.swift +117 -0
  31. package/Sources/SwiftUI/API/Layout.swift +165 -0
  32. package/Sources/SwiftUI/API/Lists.swift +276 -0
  33. package/Sources/SwiftUI/API/Menu.swift +51 -0
  34. package/Sources/SwiftUI/API/Modifiers.swift +243 -0
  35. package/Sources/SwiftUI/API/Navigation.swift +371 -0
  36. package/Sources/SwiftUI/API/ObservableObject.swift +279 -0
  37. package/Sources/SwiftUI/API/OnChange.swift +48 -0
  38. package/Sources/SwiftUI/API/Phase8Controls.swift +394 -0
  39. package/Sources/SwiftUI/API/Picker.swift +176 -0
  40. package/Sources/SwiftUI/API/Presentation.swift +109 -0
  41. package/Sources/SwiftUI/API/Primitives.swift +390 -0
  42. package/Sources/SwiftUI/API/Publishers.swift +107 -0
  43. package/Sources/SwiftUI/API/RowActions.swift +83 -0
  44. package/Sources/SwiftUI/API/ScrollTargets.swift +104 -0
  45. package/Sources/SwiftUI/API/ShapeStyle.swift +311 -0
  46. package/Sources/SwiftUI/API/State.swift +127 -0
  47. package/Sources/SwiftUI/API/StrokeStyle.swift +30 -0
  48. package/Sources/SwiftUI/API/Tabs.swift +119 -0
  49. package/Sources/SwiftUI/API/Tasks.swift +35 -0
  50. package/Sources/SwiftUI/API/Timeline.swift +212 -0
  51. package/Sources/SwiftUI/API/Toolbar.swift +215 -0
  52. package/Sources/SwiftUI/API/View.swift +207 -0
  53. package/Sources/SwiftUI/API/ViewModifier.swift +40 -0
  54. package/Sources/SwiftUI/API/VisualEffect.swift +82 -0
  55. package/Sources/SwiftUI/Charts/Axis.swift +319 -0
  56. package/Sources/SwiftUI/Charts/Chart.swift +646 -0
  57. package/Sources/SwiftUI/Charts/ChartContent.swift +376 -0
  58. package/Sources/SwiftUI/Charts/Marks.swift +204 -0
  59. package/Sources/SwiftUI/Charts/Plottable.swift +99 -0
  60. package/Sources/SwiftUI/Charts/Scales.swift +326 -0
  61. package/Sources/SwiftUI/Runtime/Executor.swift +83 -0
  62. package/Sources/SwiftUI/Runtime/Exports.swift +65 -0
  63. package/Sources/SwiftUI/Runtime/JSON.swift +71 -0
  64. package/Sources/SwiftUI/Runtime/JSONParser.swift +443 -0
  65. package/Sources/SwiftUI/Runtime/Ops.swift +106 -0
  66. package/Sources/SwiftUI/Runtime/Reconciler.swift +210 -0
  67. package/Sources/SwiftUI/Runtime/Runtime.swift +1367 -0
  68. package/Sources/_FoundationCommon/Bundle.swift +82 -0
  69. package/Sources/_FoundationCommon/UserDefaults.swift +170 -0
  70. package/Sources/_FoundationCommon/_FoundationCommon.swift +10 -0
  71. package/index.d.ts +2 -0
  72. package/index.mjs +16 -0
  73. package/package.json +28 -4
  74. 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
+ }