@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,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
+ }