@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,394 @@
|
|
|
1
|
+
/// `Stepper`, `Slider`, `DatePicker` and `TextEditor`: the `stepper`,
|
|
2
|
+
/// `slider`, `datepicker` and `texteditor` elements (docs/ops-protocol.md,
|
|
3
|
+
/// Phase 8). Each control's handler receives the event's `value`.
|
|
4
|
+
|
|
5
|
+
// MARK: - Stepper
|
|
6
|
+
|
|
7
|
+
/// Increments or decrements a value. Kind `stepper`, props `canIncrement` /
|
|
8
|
+
/// `canDecrement`; the child is the label (dropped by `labelsHidden`). A
|
|
9
|
+
/// `step` event with `1` / `-1` applies the step within the bounds.
|
|
10
|
+
public struct Stepper<Label: View> {
|
|
11
|
+
let label: Label
|
|
12
|
+
let increment: () -> Void
|
|
13
|
+
let decrement: () -> Void
|
|
14
|
+
let canIncrement: Bool
|
|
15
|
+
let canDecrement: Bool
|
|
16
|
+
let onEditingChanged: (Bool) -> Void
|
|
17
|
+
|
|
18
|
+
public init(@ViewBuilder label: () -> Label,
|
|
19
|
+
onIncrement: (() -> Void)?,
|
|
20
|
+
onDecrement: (() -> Void)?,
|
|
21
|
+
onEditingChanged: @escaping (Bool) -> Void = { _ in }) {
|
|
22
|
+
self.label = label()
|
|
23
|
+
increment = onIncrement ?? {}
|
|
24
|
+
decrement = onDecrement ?? {}
|
|
25
|
+
canIncrement = onIncrement != nil
|
|
26
|
+
canDecrement = onDecrement != nil
|
|
27
|
+
self.onEditingChanged = onEditingChanged
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public init<V: Strideable>(value: Binding<V>, in bounds: ClosedRange<V>, step: V.Stride = 1,
|
|
31
|
+
@ViewBuilder label: () -> Label,
|
|
32
|
+
onEditingChanged: @escaping (Bool) -> Void = { _ in }) {
|
|
33
|
+
self.label = label()
|
|
34
|
+
increment = { value.wrappedValue = min(value.wrappedValue.advanced(by: step), bounds.upperBound) }
|
|
35
|
+
decrement = { value.wrappedValue = max(value.wrappedValue.advanced(by: -step), bounds.lowerBound) }
|
|
36
|
+
canIncrement = value.wrappedValue < bounds.upperBound
|
|
37
|
+
canDecrement = value.wrappedValue > bounds.lowerBound
|
|
38
|
+
self.onEditingChanged = onEditingChanged
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public init<V: Strideable>(value: Binding<V>, step: V.Stride = 1,
|
|
42
|
+
@ViewBuilder label: () -> Label,
|
|
43
|
+
onEditingChanged: @escaping (Bool) -> Void = { _ in }) {
|
|
44
|
+
self.label = label()
|
|
45
|
+
increment = { value.wrappedValue = value.wrappedValue.advanced(by: step) }
|
|
46
|
+
decrement = { value.wrappedValue = value.wrappedValue.advanced(by: -step) }
|
|
47
|
+
canIncrement = true
|
|
48
|
+
canDecrement = true
|
|
49
|
+
self.onEditingChanged = onEditingChanged
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
extension Stepper where Label == Text {
|
|
54
|
+
public init<S: StringProtocol>(_ title: S, onIncrement: (() -> Void)?, onDecrement: (() -> Void)?,
|
|
55
|
+
onEditingChanged: @escaping (Bool) -> Void = { _ in }) {
|
|
56
|
+
self.init(label: { Text(title) }, onIncrement: onIncrement, onDecrement: onDecrement,
|
|
57
|
+
onEditingChanged: onEditingChanged)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public init<S: StringProtocol, V: Strideable>(_ title: S, value: Binding<V>, in bounds: ClosedRange<V>,
|
|
61
|
+
step: V.Stride = 1,
|
|
62
|
+
onEditingChanged: @escaping (Bool) -> Void = { _ in }) {
|
|
63
|
+
self.init(value: value, in: bounds, step: step, label: { Text(title) }, onEditingChanged: onEditingChanged)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public init<S: StringProtocol, V: Strideable>(_ title: S, value: Binding<V>, step: V.Stride = 1,
|
|
67
|
+
onEditingChanged: @escaping (Bool) -> Void = { _ in }) {
|
|
68
|
+
self.init(value: value, step: step, label: { Text(title) }, onEditingChanged: onEditingChanged)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public init(_ titleKey: LocalizedStringKey, onIncrement: (() -> Void)?, onDecrement: (() -> Void)?,
|
|
72
|
+
onEditingChanged: @escaping (Bool) -> Void = { _ in }) {
|
|
73
|
+
self.init(label: { Text(titleKey) }, onIncrement: onIncrement, onDecrement: onDecrement,
|
|
74
|
+
onEditingChanged: onEditingChanged)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public init<V: Strideable>(_ titleKey: LocalizedStringKey, value: Binding<V>, in bounds: ClosedRange<V>,
|
|
78
|
+
step: V.Stride = 1, onEditingChanged: @escaping (Bool) -> Void = { _ in }) {
|
|
79
|
+
self.init(value: value, in: bounds, step: step, label: { Text(titleKey) }, onEditingChanged: onEditingChanged)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public init<V: Strideable>(_ titleKey: LocalizedStringKey, value: Binding<V>, step: V.Stride = 1,
|
|
83
|
+
onEditingChanged: @escaping (Bool) -> Void = { _ in }) {
|
|
84
|
+
self.init(value: value, step: step, label: { Text(titleKey) }, onEditingChanged: onEditingChanged)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
extension Stepper: _PrimitiveView {}
|
|
89
|
+
extension Stepper: _HostView {
|
|
90
|
+
var _kind: String { "stepper" }
|
|
91
|
+
func _props(in environment: EnvironmentValues) -> _JSON {
|
|
92
|
+
.object(["canIncrement": .bool(canIncrement), "canDecrement": .bool(canDecrement)])
|
|
93
|
+
}
|
|
94
|
+
var _content: [any View] { Label.self is EmptyView.Type ? [] : [_GaugeLabel(label: label)] }
|
|
95
|
+
var _handler: ((_JSON) -> Void)? {
|
|
96
|
+
let increment = self.increment
|
|
97
|
+
let decrement = self.decrement
|
|
98
|
+
let canIncrement = self.canIncrement
|
|
99
|
+
let canDecrement = self.canDecrement
|
|
100
|
+
let onEditingChanged = self.onEditingChanged
|
|
101
|
+
return { payload in
|
|
102
|
+
guard case .number(let direction) = payload, direction != 0 else { return }
|
|
103
|
+
if direction > 0, !canIncrement { return }
|
|
104
|
+
if direction < 0, !canDecrement { return }
|
|
105
|
+
onEditingChanged(true)
|
|
106
|
+
direction > 0 ? increment() : decrement()
|
|
107
|
+
onEditingChanged(false)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// MARK: - Slider
|
|
113
|
+
|
|
114
|
+
/// Selects a value from a range. Kind `slider`, props `value`, `min`, `max`,
|
|
115
|
+
/// `step` (null when free) and `labels`, which names the children present in
|
|
116
|
+
/// order: `"label"`, `"min"` (minimumValueLabel), `"max"` (maximumValueLabel).
|
|
117
|
+
public struct Slider<Label: View, ValueLabel: View> {
|
|
118
|
+
let value: Double
|
|
119
|
+
let bounds: ClosedRange<Double>
|
|
120
|
+
let step: Double?
|
|
121
|
+
let set: (Double) -> Void
|
|
122
|
+
let label: Label
|
|
123
|
+
let minimumValueLabel: ValueLabel
|
|
124
|
+
let maximumValueLabel: ValueLabel
|
|
125
|
+
let onEditingChanged: (Bool) -> Void
|
|
126
|
+
|
|
127
|
+
init<V: BinaryFloatingPoint>(value: Binding<V>, in bounds: ClosedRange<V>, step: V.Stride?,
|
|
128
|
+
label: Label, minimumValueLabel: ValueLabel, maximumValueLabel: ValueLabel,
|
|
129
|
+
onEditingChanged: @escaping (Bool) -> Void) where V.Stride: BinaryFloatingPoint {
|
|
130
|
+
self.value = Double(value.wrappedValue)
|
|
131
|
+
self.bounds = Double(bounds.lowerBound)...Double(bounds.upperBound)
|
|
132
|
+
self.step = step.map { Double($0) }
|
|
133
|
+
let range = bounds
|
|
134
|
+
set = { value.wrappedValue = min(max(V($0), range.lowerBound), range.upperBound) }
|
|
135
|
+
self.label = label
|
|
136
|
+
self.minimumValueLabel = minimumValueLabel
|
|
137
|
+
self.maximumValueLabel = maximumValueLabel
|
|
138
|
+
self.onEditingChanged = onEditingChanged
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public init<V: BinaryFloatingPoint>(value: Binding<V>, in bounds: ClosedRange<V>, step: V.Stride? = nil,
|
|
142
|
+
@ViewBuilder label: () -> Label,
|
|
143
|
+
@ViewBuilder minimumValueLabel: () -> ValueLabel,
|
|
144
|
+
@ViewBuilder maximumValueLabel: () -> ValueLabel,
|
|
145
|
+
onEditingChanged: @escaping (Bool) -> Void = { _ in })
|
|
146
|
+
where V.Stride: BinaryFloatingPoint {
|
|
147
|
+
self.init(value: value, in: bounds, step: step, label: label(),
|
|
148
|
+
minimumValueLabel: minimumValueLabel(), maximumValueLabel: maximumValueLabel(),
|
|
149
|
+
onEditingChanged: onEditingChanged)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
extension Slider where ValueLabel == EmptyView {
|
|
154
|
+
public init<V: BinaryFloatingPoint>(value: Binding<V>, in bounds: ClosedRange<V>, step: V.Stride? = nil,
|
|
155
|
+
@ViewBuilder label: () -> Label,
|
|
156
|
+
onEditingChanged: @escaping (Bool) -> Void = { _ in })
|
|
157
|
+
where V.Stride: BinaryFloatingPoint {
|
|
158
|
+
self.init(value: value, in: bounds, step: step, label: label(),
|
|
159
|
+
minimumValueLabel: EmptyView(), maximumValueLabel: EmptyView(),
|
|
160
|
+
onEditingChanged: onEditingChanged)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
extension Slider where Label == EmptyView, ValueLabel == EmptyView {
|
|
165
|
+
public init<V: BinaryFloatingPoint>(value: Binding<V>, in bounds: ClosedRange<V> = 0...1, step: V.Stride? = nil,
|
|
166
|
+
onEditingChanged: @escaping (Bool) -> Void = { _ in })
|
|
167
|
+
where V.Stride: BinaryFloatingPoint {
|
|
168
|
+
self.init(value: value, in: bounds, step: step, label: EmptyView(),
|
|
169
|
+
minimumValueLabel: EmptyView(), maximumValueLabel: EmptyView(),
|
|
170
|
+
onEditingChanged: onEditingChanged)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
extension Slider: _PrimitiveView {}
|
|
175
|
+
extension Slider: _HostView {
|
|
176
|
+
var _kind: String { "slider" }
|
|
177
|
+
|
|
178
|
+
private var hasValueLabels: Bool { !(ValueLabel.self is EmptyView.Type) }
|
|
179
|
+
|
|
180
|
+
func _props(in environment: EnvironmentValues) -> _JSON {
|
|
181
|
+
var labels: [_JSON] = []
|
|
182
|
+
if !(Label.self is EmptyView.Type), !environment.labelsHidden { labels.append(.string("label")) }
|
|
183
|
+
if hasValueLabels { labels += [.string("min"), .string("max")] }
|
|
184
|
+
return .object([
|
|
185
|
+
"value": .number(value),
|
|
186
|
+
"min": .number(bounds.lowerBound),
|
|
187
|
+
"max": .number(bounds.upperBound),
|
|
188
|
+
"step": step.map(_JSON.number) ?? .null,
|
|
189
|
+
"labels": .array(labels),
|
|
190
|
+
])
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
var _content: [any View] {
|
|
194
|
+
var children: [any View] = []
|
|
195
|
+
if !(Label.self is EmptyView.Type) { children.append(_GaugeLabel(label: label)) }
|
|
196
|
+
if hasValueLabels { children += [minimumValueLabel, maximumValueLabel] }
|
|
197
|
+
return children
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
var _handler: ((_JSON) -> Void)? {
|
|
201
|
+
let set = self.set
|
|
202
|
+
let onEditingChanged = self.onEditingChanged
|
|
203
|
+
return { payload in
|
|
204
|
+
guard case .number(let value) = payload else { return }
|
|
205
|
+
onEditingChanged(true)
|
|
206
|
+
set(value)
|
|
207
|
+
onEditingChanged(false)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// MARK: - DatePicker
|
|
213
|
+
|
|
214
|
+
/// A value a `DatePicker` can select: a point in time as seconds since 1970
|
|
215
|
+
/// (UTC). `FoundationLite` conforms `Date`; the shim itself has no date type.
|
|
216
|
+
public protocol _DatePickerValue {
|
|
217
|
+
var _secondsSince1970: Double { get }
|
|
218
|
+
init(_secondsSince1970: Double)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/// Which parts of a date a `DatePicker` shows.
|
|
222
|
+
public struct DatePickerComponents: OptionSet, Sendable {
|
|
223
|
+
public let rawValue: UInt
|
|
224
|
+
public init(rawValue: UInt) { self.rawValue = rawValue }
|
|
225
|
+
public static let hourAndMinute = DatePickerComponents(rawValue: 1 << 0)
|
|
226
|
+
public static let date = DatePickerComponents(rawValue: 1 << 1)
|
|
227
|
+
|
|
228
|
+
var _name: String {
|
|
229
|
+
if self == .date { return "date" }
|
|
230
|
+
if self == .hourAndMinute { return "hourAndMinute" }
|
|
231
|
+
return "dateAndTime"
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/// Picks a date and/or time. Kind `datepicker`, props `seconds`, `components`,
|
|
236
|
+
/// `min`, `max`; the child is the label (dropped by `labelsHidden`). A `date`
|
|
237
|
+
/// event carries the new seconds since 1970.
|
|
238
|
+
public struct DatePicker<Label: View> {
|
|
239
|
+
let label: Label
|
|
240
|
+
let seconds: Double
|
|
241
|
+
let minimum: Double?
|
|
242
|
+
let maximum: Double?
|
|
243
|
+
let components: DatePickerComponents
|
|
244
|
+
let set: (Double) -> Void
|
|
245
|
+
|
|
246
|
+
init<V: _DatePickerValue>(selection: Binding<V>, minimum: Double?, maximum: Double?,
|
|
247
|
+
displayedComponents: DatePickerComponents, label: Label) {
|
|
248
|
+
self.label = label
|
|
249
|
+
seconds = selection.wrappedValue._secondsSince1970
|
|
250
|
+
self.minimum = minimum
|
|
251
|
+
self.maximum = maximum
|
|
252
|
+
components = displayedComponents
|
|
253
|
+
set = { value in
|
|
254
|
+
var clamped = value
|
|
255
|
+
if let minimum { clamped = max(clamped, minimum) }
|
|
256
|
+
if let maximum { clamped = min(clamped, maximum) }
|
|
257
|
+
selection.wrappedValue = V(_secondsSince1970: clamped)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
public init<V: _DatePickerValue>(selection: Binding<V>,
|
|
262
|
+
displayedComponents: DatePickerComponents = [.hourAndMinute, .date],
|
|
263
|
+
@ViewBuilder label: () -> Label) {
|
|
264
|
+
self.init(selection: selection, minimum: nil, maximum: nil, displayedComponents: displayedComponents,
|
|
265
|
+
label: label())
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
public init<V: _DatePickerValue>(selection: Binding<V>, in range: ClosedRange<V>,
|
|
269
|
+
displayedComponents: DatePickerComponents = [.hourAndMinute, .date],
|
|
270
|
+
@ViewBuilder label: () -> Label) {
|
|
271
|
+
self.init(selection: selection, minimum: range.lowerBound._secondsSince1970,
|
|
272
|
+
maximum: range.upperBound._secondsSince1970, displayedComponents: displayedComponents,
|
|
273
|
+
label: label())
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
public init<V: _DatePickerValue>(selection: Binding<V>, in range: PartialRangeFrom<V>,
|
|
277
|
+
displayedComponents: DatePickerComponents = [.hourAndMinute, .date],
|
|
278
|
+
@ViewBuilder label: () -> Label) {
|
|
279
|
+
self.init(selection: selection, minimum: range.lowerBound._secondsSince1970, maximum: nil,
|
|
280
|
+
displayedComponents: displayedComponents, label: label())
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
public init<V: _DatePickerValue>(selection: Binding<V>, in range: PartialRangeThrough<V>,
|
|
284
|
+
displayedComponents: DatePickerComponents = [.hourAndMinute, .date],
|
|
285
|
+
@ViewBuilder label: () -> Label) {
|
|
286
|
+
self.init(selection: selection, minimum: nil, maximum: range.upperBound._secondsSince1970,
|
|
287
|
+
displayedComponents: displayedComponents, label: label())
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
extension DatePicker where Label == Text {
|
|
292
|
+
public init<S: StringProtocol, V: _DatePickerValue>(_ title: S, selection: Binding<V>,
|
|
293
|
+
displayedComponents: DatePickerComponents = [.hourAndMinute, .date]) {
|
|
294
|
+
self.init(selection: selection, displayedComponents: displayedComponents) { Text(title) }
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
public init<S: StringProtocol, V: _DatePickerValue>(_ title: S, selection: Binding<V>, in range: ClosedRange<V>,
|
|
298
|
+
displayedComponents: DatePickerComponents = [.hourAndMinute, .date]) {
|
|
299
|
+
self.init(selection: selection, in: range, displayedComponents: displayedComponents) { Text(title) }
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
public init<S: StringProtocol, V: _DatePickerValue>(_ title: S, selection: Binding<V>, in range: PartialRangeFrom<V>,
|
|
303
|
+
displayedComponents: DatePickerComponents = [.hourAndMinute, .date]) {
|
|
304
|
+
self.init(selection: selection, in: range, displayedComponents: displayedComponents) { Text(title) }
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
public init<S: StringProtocol, V: _DatePickerValue>(_ title: S, selection: Binding<V>, in range: PartialRangeThrough<V>,
|
|
308
|
+
displayedComponents: DatePickerComponents = [.hourAndMinute, .date]) {
|
|
309
|
+
self.init(selection: selection, in: range, displayedComponents: displayedComponents) { Text(title) }
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
public init<V: _DatePickerValue>(_ titleKey: LocalizedStringKey, selection: Binding<V>,
|
|
313
|
+
displayedComponents: DatePickerComponents = [.hourAndMinute, .date]) {
|
|
314
|
+
self.init(selection: selection, displayedComponents: displayedComponents) { Text(titleKey) }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
public init<V: _DatePickerValue>(_ titleKey: LocalizedStringKey, selection: Binding<V>, in range: ClosedRange<V>,
|
|
318
|
+
displayedComponents: DatePickerComponents = [.hourAndMinute, .date]) {
|
|
319
|
+
self.init(selection: selection, in: range, displayedComponents: displayedComponents) { Text(titleKey) }
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
public init<V: _DatePickerValue>(_ titleKey: LocalizedStringKey, selection: Binding<V>, in range: PartialRangeFrom<V>,
|
|
323
|
+
displayedComponents: DatePickerComponents = [.hourAndMinute, .date]) {
|
|
324
|
+
self.init(selection: selection, in: range, displayedComponents: displayedComponents) { Text(titleKey) }
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
public init<V: _DatePickerValue>(_ titleKey: LocalizedStringKey, selection: Binding<V>, in range: PartialRangeThrough<V>,
|
|
328
|
+
displayedComponents: DatePickerComponents = [.hourAndMinute, .date]) {
|
|
329
|
+
self.init(selection: selection, in: range, displayedComponents: displayedComponents) { Text(titleKey) }
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
extension DatePicker: _PrimitiveView {}
|
|
334
|
+
extension DatePicker: _HostView {
|
|
335
|
+
var _kind: String { "datepicker" }
|
|
336
|
+
func _props(in environment: EnvironmentValues) -> _JSON {
|
|
337
|
+
.object([
|
|
338
|
+
"seconds": .number(seconds),
|
|
339
|
+
"components": .string(components._name),
|
|
340
|
+
"min": minimum.map(_JSON.number) ?? .null,
|
|
341
|
+
"max": maximum.map(_JSON.number) ?? .null,
|
|
342
|
+
])
|
|
343
|
+
}
|
|
344
|
+
var _content: [any View] { Label.self is EmptyView.Type ? [] : [_GaugeLabel(label: label)] }
|
|
345
|
+
var _handler: ((_JSON) -> Void)? {
|
|
346
|
+
let set = self.set
|
|
347
|
+
return { payload in
|
|
348
|
+
if case .number(let seconds) = payload { set(seconds) }
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/// `.datePickerStyle`: accepted for source compatibility; pickers are compact.
|
|
354
|
+
public protocol DatePickerStyle {}
|
|
355
|
+
public struct DefaultDatePickerStyle: DatePickerStyle { public init() {} }
|
|
356
|
+
public struct CompactDatePickerStyle: DatePickerStyle { public init() {} }
|
|
357
|
+
public struct GraphicalDatePickerStyle: DatePickerStyle { public init() {} }
|
|
358
|
+
public struct WheelDatePickerStyle: DatePickerStyle { public init() {} }
|
|
359
|
+
extension DatePickerStyle where Self == DefaultDatePickerStyle {
|
|
360
|
+
public static var automatic: DefaultDatePickerStyle { DefaultDatePickerStyle() }
|
|
361
|
+
}
|
|
362
|
+
extension DatePickerStyle where Self == CompactDatePickerStyle {
|
|
363
|
+
public static var compact: CompactDatePickerStyle { CompactDatePickerStyle() }
|
|
364
|
+
}
|
|
365
|
+
extension DatePickerStyle where Self == GraphicalDatePickerStyle {
|
|
366
|
+
public static var graphical: GraphicalDatePickerStyle { GraphicalDatePickerStyle() }
|
|
367
|
+
}
|
|
368
|
+
extension DatePickerStyle where Self == WheelDatePickerStyle {
|
|
369
|
+
public static var wheel: WheelDatePickerStyle { WheelDatePickerStyle() }
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
extension View {
|
|
373
|
+
public func datePickerStyle<S: DatePickerStyle>(_ style: S) -> some View { self }
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// MARK: - TextEditor
|
|
377
|
+
|
|
378
|
+
/// Multi-line editable text. Kind `texteditor`, props `text`; `text` events
|
|
379
|
+
/// set the binding, as for `textfield`.
|
|
380
|
+
public struct TextEditor {
|
|
381
|
+
let text: Binding<String>
|
|
382
|
+
public init(text: Binding<String>) { self.text = text }
|
|
383
|
+
}
|
|
384
|
+
extension TextEditor: _PrimitiveView {}
|
|
385
|
+
extension TextEditor: _HostView {
|
|
386
|
+
var _kind: String { "texteditor" }
|
|
387
|
+
func _props(in environment: EnvironmentValues) -> _JSON { .object(["text": .string(text.wrappedValue)]) }
|
|
388
|
+
var _handler: ((_JSON) -> Void)? {
|
|
389
|
+
let text = self.text
|
|
390
|
+
return { payload in
|
|
391
|
+
if case .string(let value) = payload { text.wrappedValue = value }
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// MARK: - Picker styles
|
|
2
|
+
|
|
3
|
+
public protocol PickerStyle {
|
|
4
|
+
var _name: String { get }
|
|
5
|
+
}
|
|
6
|
+
public struct DefaultPickerStyle: PickerStyle {
|
|
7
|
+
public init() {}
|
|
8
|
+
public var _name: String { "menu" }
|
|
9
|
+
}
|
|
10
|
+
public struct SegmentedPickerStyle: PickerStyle {
|
|
11
|
+
public init() {}
|
|
12
|
+
public var _name: String { "segmented" }
|
|
13
|
+
}
|
|
14
|
+
public struct MenuPickerStyle: PickerStyle {
|
|
15
|
+
public init() {}
|
|
16
|
+
public var _name: String { "menu" }
|
|
17
|
+
}
|
|
18
|
+
public struct InlinePickerStyle: PickerStyle {
|
|
19
|
+
public init() {}
|
|
20
|
+
public var _name: String { "inline" }
|
|
21
|
+
}
|
|
22
|
+
public struct WheelPickerStyle: PickerStyle {
|
|
23
|
+
public init() {}
|
|
24
|
+
public var _name: String { "menu" }
|
|
25
|
+
}
|
|
26
|
+
extension PickerStyle where Self == DefaultPickerStyle {
|
|
27
|
+
public static var automatic: DefaultPickerStyle { DefaultPickerStyle() }
|
|
28
|
+
}
|
|
29
|
+
extension PickerStyle where Self == SegmentedPickerStyle {
|
|
30
|
+
public static var segmented: SegmentedPickerStyle { SegmentedPickerStyle() }
|
|
31
|
+
}
|
|
32
|
+
extension PickerStyle where Self == MenuPickerStyle {
|
|
33
|
+
public static var menu: MenuPickerStyle { MenuPickerStyle() }
|
|
34
|
+
}
|
|
35
|
+
extension PickerStyle where Self == InlinePickerStyle {
|
|
36
|
+
public static var inline: InlinePickerStyle { InlinePickerStyle() }
|
|
37
|
+
}
|
|
38
|
+
extension PickerStyle where Self == WheelPickerStyle {
|
|
39
|
+
public static var wheel: WheelPickerStyle { WheelPickerStyle() }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
extension View {
|
|
43
|
+
public func pickerStyle<S: PickerStyle>(_ style: S) -> some View {
|
|
44
|
+
environment(\._pickerStyle, style._name)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
enum _PickerStyleKey: EnvironmentKey { static let defaultValue = "menu" }
|
|
49
|
+
enum _ListStyleKey: EnvironmentKey { static let defaultValue = "insetGrouped" }
|
|
50
|
+
|
|
51
|
+
extension EnvironmentValues {
|
|
52
|
+
var _pickerStyle: String {
|
|
53
|
+
get { self[_PickerStyleKey.self] }
|
|
54
|
+
set { self[_PickerStyleKey.self] = newValue }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
var _listStyle: String {
|
|
58
|
+
get { self[_ListStyleKey.self] }
|
|
59
|
+
set { self[_ListStyleKey.self] = newValue }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// MARK: - Picker
|
|
64
|
+
|
|
65
|
+
protocol _PickerHost {
|
|
66
|
+
var _labelView: any View { get }
|
|
67
|
+
var _options: any View { get }
|
|
68
|
+
func _selectedIndex(tags: [AnyHashable?]) -> Int
|
|
69
|
+
func _select(index: Int, tags: [AnyHashable?])
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// A control for selecting one of several options. Options are the content's
|
|
73
|
+
/// children; each option's label is its text, and its identity is its `tag`
|
|
74
|
+
/// (or its `ForEach` id).
|
|
75
|
+
public struct Picker<Label: View, SelectionValue: Hashable, Content: View> {
|
|
76
|
+
let label: any View
|
|
77
|
+
let selection: Binding<SelectionValue>
|
|
78
|
+
let content: Content
|
|
79
|
+
|
|
80
|
+
public init(selection: Binding<SelectionValue>, @ViewBuilder content: () -> Content,
|
|
81
|
+
@ViewBuilder label: () -> Label) {
|
|
82
|
+
self.label = label()
|
|
83
|
+
self.selection = selection
|
|
84
|
+
self.content = content()
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
extension Picker where Label == Text {
|
|
89
|
+
public init<S: StringProtocol>(_ title: S, selection: Binding<SelectionValue>,
|
|
90
|
+
@ViewBuilder content: () -> Content) {
|
|
91
|
+
self.label = Text(title)
|
|
92
|
+
self.selection = selection
|
|
93
|
+
self.content = content()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
extension Picker: _PrimitiveView {}
|
|
98
|
+
extension Picker: _HostView {
|
|
99
|
+
var _kind: String { "picker" }
|
|
100
|
+
func _props(in environment: EnvironmentValues) -> _JSON { .object([:]) }
|
|
101
|
+
// Options, selection and the handler are produced by the runtime.
|
|
102
|
+
}
|
|
103
|
+
extension Picker: _PickerHost {
|
|
104
|
+
var _labelView: any View { label }
|
|
105
|
+
var _options: any View { content }
|
|
106
|
+
|
|
107
|
+
func _selectedIndex(tags: [AnyHashable?]) -> Int {
|
|
108
|
+
let current = AnyHashable(selection.wrappedValue)
|
|
109
|
+
return tags.firstIndex { $0 == current } ?? 0
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func _select(index: Int, tags: [AnyHashable?]) {
|
|
113
|
+
guard index < tags.count, let tag = tags[index], let value = tag.base as? SelectionValue else { return }
|
|
114
|
+
selection.wrappedValue = value
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// The plain text of a label view: the first `Text` found, if any.
|
|
119
|
+
@MainActor
|
|
120
|
+
func _textContent(of view: any View) -> String {
|
|
121
|
+
if let text = view as? Text { return text.content }
|
|
122
|
+
if let label = view as? any _TabItemProviding { return _textContent(of: label._tabLabel) }
|
|
123
|
+
if let tagged = view as? any _TagProviding { return _textContent(of: tagged._tagged) }
|
|
124
|
+
if let group = view as? any _GroupView {
|
|
125
|
+
for child in group._children {
|
|
126
|
+
let text = _textContent(of: child)
|
|
127
|
+
if !text.isEmpty { return text }
|
|
128
|
+
}
|
|
129
|
+
return ""
|
|
130
|
+
}
|
|
131
|
+
if let host = view as? any _HostView {
|
|
132
|
+
for child in host._content {
|
|
133
|
+
let text = _textContent(of: child)
|
|
134
|
+
if !text.isEmpty { return text }
|
|
135
|
+
}
|
|
136
|
+
return ""
|
|
137
|
+
}
|
|
138
|
+
func open<V: View>(_ view: V) -> any View { view.body }
|
|
139
|
+
return _textContent(of: open(view))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// The first system image name in a label view, if any.
|
|
143
|
+
@MainActor
|
|
144
|
+
func _systemImage(of view: any View) -> String? {
|
|
145
|
+
if let image = view as? Image { return image.systemName }
|
|
146
|
+
if let label = view as? any _TabItemProviding { return _systemImage(of: label._tabLabel) }
|
|
147
|
+
if let tagged = view as? any _TagProviding { return _systemImage(of: tagged._tagged) }
|
|
148
|
+
if let group = view as? any _GroupView {
|
|
149
|
+
for child in group._children {
|
|
150
|
+
if let name = _systemImage(of: child) { return name }
|
|
151
|
+
}
|
|
152
|
+
return nil
|
|
153
|
+
}
|
|
154
|
+
if let host = view as? any _HostView {
|
|
155
|
+
for child in host._content {
|
|
156
|
+
if let name = _systemImage(of: child) { return name }
|
|
157
|
+
}
|
|
158
|
+
return nil
|
|
159
|
+
}
|
|
160
|
+
func open<V: View>(_ view: V) -> any View { view.body }
|
|
161
|
+
return _systemImage(of: open(view))
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// MARK: - Form
|
|
165
|
+
|
|
166
|
+
/// A container for grouped controls, drawn as a grouped list.
|
|
167
|
+
public struct Form<Content: View> {
|
|
168
|
+
let content: Content
|
|
169
|
+
public init(@ViewBuilder content: () -> Content) { self.content = content() }
|
|
170
|
+
}
|
|
171
|
+
extension Form: _PrimitiveView {}
|
|
172
|
+
extension Form: _HostView {
|
|
173
|
+
var _kind: String { "list" }
|
|
174
|
+
func _props(in environment: EnvironmentValues) -> _JSON { .object(["style": .string("grouped")]) }
|
|
175
|
+
var _content: [any View] { [content] }
|
|
176
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/// A height a sheet can rest at.
|
|
2
|
+
public struct PresentationDetent: Hashable, Sendable {
|
|
3
|
+
let name: String
|
|
4
|
+
public static let medium = PresentationDetent(name: "medium")
|
|
5
|
+
public static let large = PresentationDetent(name: "large")
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/// Views that present a sheet. The runtime mounts the sheet content as a
|
|
9
|
+
/// second child and emits it as a root-level `sheet` element while presented.
|
|
10
|
+
protocol _SheetPresenting {
|
|
11
|
+
var _isPresented: Binding<Bool> { get }
|
|
12
|
+
var _onDismiss: (@MainActor () -> Void)? { get }
|
|
13
|
+
var _base: any View { get }
|
|
14
|
+
var _sheetContent: any View { get }
|
|
15
|
+
/// `.fullScreenCover`: the sheet's detents are `["full"]`.
|
|
16
|
+
var _fullScreen: Bool { get }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
struct _SheetView<Base: View, Content: View> {
|
|
20
|
+
let base: Base
|
|
21
|
+
let isPresented: Binding<Bool>
|
|
22
|
+
let onDismiss: (@MainActor () -> Void)?
|
|
23
|
+
let content: Content
|
|
24
|
+
var fullScreen = false
|
|
25
|
+
}
|
|
26
|
+
extension _SheetView: _PrimitiveView {}
|
|
27
|
+
extension _SheetView: _GroupView {
|
|
28
|
+
// Only the base is a regular child; the runtime adds the sheet content.
|
|
29
|
+
var _children: [any View] { [base] }
|
|
30
|
+
}
|
|
31
|
+
extension _SheetView: _SheetPresenting {
|
|
32
|
+
var _isPresented: Binding<Bool> { isPresented }
|
|
33
|
+
var _onDismiss: (@MainActor () -> Void)? { onDismiss }
|
|
34
|
+
var _base: any View { base }
|
|
35
|
+
var _sheetContent: any View { content }
|
|
36
|
+
var _fullScreen: Bool { fullScreen }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
protocol _DetentsModifying {
|
|
40
|
+
var _detents: [String] { get }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
struct _DetentsView<Content: View> {
|
|
44
|
+
let content: Content
|
|
45
|
+
let detents: [String]
|
|
46
|
+
}
|
|
47
|
+
extension _DetentsView: _PrimitiveView {}
|
|
48
|
+
extension _DetentsView: _GroupView {
|
|
49
|
+
var _children: [any View] { [content] }
|
|
50
|
+
}
|
|
51
|
+
extension _DetentsView: _DetentsModifying {
|
|
52
|
+
var _detents: [String] { detents }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
extension View {
|
|
56
|
+
/// Presents a sheet when `isPresented` is true.
|
|
57
|
+
public func sheet<Content: View>(isPresented: Binding<Bool>,
|
|
58
|
+
onDismiss: (@MainActor () -> Void)? = nil,
|
|
59
|
+
@ViewBuilder content: () -> Content) -> some View {
|
|
60
|
+
_SheetView(base: self, isPresented: isPresented, onDismiss: onDismiss, content: content())
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Presents a sheet for a non-nil item; dismissing sets it back to nil.
|
|
64
|
+
public func sheet<Item: Identifiable, Content: View>(item: Binding<Item?>,
|
|
65
|
+
onDismiss: (@MainActor () -> Void)? = nil,
|
|
66
|
+
@ViewBuilder content: @escaping (Item) -> Content) -> some View {
|
|
67
|
+
let isPresented = Binding<Bool>(
|
|
68
|
+
get: { item.wrappedValue != nil },
|
|
69
|
+
set: { if !$0 { item.wrappedValue = nil } }
|
|
70
|
+
)
|
|
71
|
+
return _SheetView(base: self, isPresented: isPresented, onDismiss: onDismiss,
|
|
72
|
+
content: _ItemSheetContent(item: item, content: content))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Presents a sheet that fills the screen (`detents: ["full"]`).
|
|
76
|
+
public func fullScreenCover<Content: View>(isPresented: Binding<Bool>,
|
|
77
|
+
onDismiss: (@MainActor () -> Void)? = nil,
|
|
78
|
+
@ViewBuilder content: () -> Content) -> some View {
|
|
79
|
+
_SheetView(base: self, isPresented: isPresented, onDismiss: onDismiss, content: content(), fullScreen: true)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public func fullScreenCover<Item: Identifiable, Content: View>(item: Binding<Item?>,
|
|
83
|
+
onDismiss: (@MainActor () -> Void)? = nil,
|
|
84
|
+
@ViewBuilder content: @escaping (Item) -> Content) -> some View {
|
|
85
|
+
let isPresented = Binding<Bool>(
|
|
86
|
+
get: { item.wrappedValue != nil },
|
|
87
|
+
set: { if !$0 { item.wrappedValue = nil } }
|
|
88
|
+
)
|
|
89
|
+
return _SheetView(base: self, isPresented: isPresented, onDismiss: onDismiss,
|
|
90
|
+
content: _ItemSheetContent(item: item, content: content), fullScreen: true)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public func presentationDetents(_ detents: Set<PresentationDetent>) -> some View {
|
|
94
|
+
let order = ["medium", "large"]
|
|
95
|
+
return _DetentsView(content: self, detents: order.filter { name in detents.contains { $0.name == name } })
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// The content of `.sheet(item:)`, built from the current item (empty once it is nil).
|
|
100
|
+
struct _ItemSheetContent<Item: Identifiable, Content: View>: View {
|
|
101
|
+
let item: Binding<Item?>
|
|
102
|
+
let content: (Item) -> Content
|
|
103
|
+
|
|
104
|
+
var body: some View {
|
|
105
|
+
if let value = item.wrappedValue {
|
|
106
|
+
content(value)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|