@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,342 @@
1
+ /// Gestures: `DragGesture`, `LongPressGesture` and `TapGesture`, attached with
2
+ /// `.gesture` (and its `highPriority` / `simultaneous` twins, which the shim
3
+ /// treats identically). A drag or long press becomes a `gesture` element
4
+ /// whose child is the content; the renderer tracks pointers on it and sends
5
+ /// `drag` / `longpress` events (see docs/ops-protocol.md, Phase 8). A plain
6
+ /// tap gesture is the Phase 6 `tapgesture` element.
7
+
8
+ /// Which gestures of a subtree a `.gesture` modifier competes with. Accepted
9
+ /// for source compatibility; the renderer resolves the innermost control.
10
+ public struct GestureMask: OptionSet, Sendable {
11
+ public let rawValue: UInt
12
+ public init(rawValue: UInt) { self.rawValue = rawValue }
13
+ public static let none = GestureMask([])
14
+ public static let gesture = GestureMask(rawValue: 1)
15
+ public static let subviews = GestureMask(rawValue: 2)
16
+ public static let all: GestureMask = [.gesture, .subviews]
17
+ }
18
+
19
+ /// What the renderer must track for a gesture and what to do with its events.
20
+ /// Combined gestures (`sequenced`, `simultaneously`) merge their configurations.
21
+ public struct _GestureConfig: Sendable {
22
+ var drag = false
23
+ var longPress = false
24
+ var minimumDistance: CGFloat = 10
25
+ var minimumDuration: Double = 0.5
26
+ var onDragChanged: [@MainActor (DragGesture.Value) -> Void] = []
27
+ var onDragEnded: [@MainActor (DragGesture.Value) -> Void] = []
28
+ var onLongPressChanged: [@MainActor (Bool) -> Void] = []
29
+ var onLongPressEnded: [@MainActor (Bool) -> Void] = []
30
+ var onTap: [@MainActor () -> Void] = []
31
+
32
+ func merged(with other: _GestureConfig) -> _GestureConfig {
33
+ var result = self
34
+ result.drag = drag || other.drag
35
+ result.longPress = longPress || other.longPress
36
+ result.minimumDistance = drag && other.drag ? min(minimumDistance, other.minimumDistance)
37
+ : (other.drag ? other.minimumDistance : minimumDistance)
38
+ result.minimumDuration = longPress && other.longPress ? min(minimumDuration, other.minimumDuration)
39
+ : (other.longPress ? other.minimumDuration : minimumDuration)
40
+ result.onDragChanged += other.onDragChanged
41
+ result.onDragEnded += other.onDragEnded
42
+ result.onLongPressChanged += other.onLongPressChanged
43
+ result.onLongPressEnded += other.onLongPressEnded
44
+ result.onTap += other.onTap
45
+ return result
46
+ }
47
+ }
48
+
49
+ /// A gesture the renderer recognizes. `Value` is what the callbacks receive.
50
+ public protocol Gesture {
51
+ associatedtype Value
52
+ var _config: _GestureConfig { get }
53
+ /// Adds callbacks for the gesture's changed / ended phases to a config
54
+ /// (each gesture type knows which lists its `Value` belongs in).
55
+ static func _adding(to config: _GestureConfig,
56
+ changed: (@MainActor (Value) -> Void)?,
57
+ ended: (@MainActor (Value) -> Void)?) -> _GestureConfig
58
+ }
59
+
60
+ extension Gesture {
61
+ public func onChanged(_ action: @escaping @MainActor (Value) -> Void) -> _ModifiedGesture<Self> {
62
+ _ModifiedGesture(_config: Self._adding(to: _config, changed: action, ended: nil))
63
+ }
64
+
65
+ public func onEnded(_ action: @escaping @MainActor (Value) -> Void) -> _ModifiedGesture<Self> {
66
+ _ModifiedGesture(_config: Self._adding(to: _config, changed: nil, ended: action))
67
+ }
68
+
69
+ /// Both gestures are recognized; the shim does not order them.
70
+ public func sequenced<Other: Gesture>(before other: Other) -> _CombinedGesture<Self, Other> {
71
+ _CombinedGesture(_config: _config.merged(with: other._config))
72
+ }
73
+
74
+ public func simultaneously<Other: Gesture>(with other: Other) -> _CombinedGesture<Self, Other> {
75
+ _CombinedGesture(_config: _config.merged(with: other._config))
76
+ }
77
+
78
+ public func exclusively<Other: Gesture>(before other: Other) -> _CombinedGesture<Self, Other> {
79
+ _CombinedGesture(_config: _config.merged(with: other._config))
80
+ }
81
+ }
82
+
83
+ /// A gesture with `onChanged` / `onEnded` callbacks attached; keeps the base
84
+ /// gesture's `Value` so further callbacks can be chained.
85
+ public struct _ModifiedGesture<Base: Gesture>: Gesture {
86
+ public typealias Value = Base.Value
87
+ public let _config: _GestureConfig
88
+
89
+ public static func _adding(to config: _GestureConfig,
90
+ changed: (@MainActor (Value) -> Void)?,
91
+ ended: (@MainActor (Value) -> Void)?) -> _GestureConfig {
92
+ Base._adding(to: config, changed: changed, ended: ended)
93
+ }
94
+ }
95
+
96
+ /// Two gestures combined. Its `Value` is never produced: attach callbacks to
97
+ /// the parts before combining them.
98
+ public struct _CombinedGesture<First: Gesture, Second: Gesture>: Gesture {
99
+ public typealias Value = Void
100
+ public let _config: _GestureConfig
101
+
102
+ public static func _adding(to config: _GestureConfig,
103
+ changed: (@MainActor (Value) -> Void)?,
104
+ ended: (@MainActor (Value) -> Void)?) -> _GestureConfig {
105
+ config
106
+ }
107
+ }
108
+
109
+ // MARK: - DragGesture
110
+
111
+ public struct DragGesture: Gesture {
112
+ /// The drag so far. Coordinates are in the gesture element's own box
113
+ /// (`coordinateSpace` is accepted and ignored). `predictedEnd…` project
114
+ /// the velocity a quarter second ahead.
115
+ public struct Value: Equatable, Sendable {
116
+ public var location: CGPoint
117
+ public var startLocation: CGPoint
118
+ public var translation: CGSize
119
+ public var velocity: CGSize
120
+
121
+ public init(location: CGPoint = .zero, startLocation: CGPoint = .zero,
122
+ translation: CGSize = .zero, velocity: CGSize = .zero) {
123
+ self.location = location
124
+ self.startLocation = startLocation
125
+ self.translation = translation
126
+ self.velocity = velocity
127
+ }
128
+
129
+ public var predictedEndLocation: CGPoint {
130
+ CGPoint(x: location.x + velocity.width / 4, y: location.y + velocity.height / 4)
131
+ }
132
+
133
+ public var predictedEndTranslation: CGSize {
134
+ CGSize(width: translation.width + velocity.width / 4, height: translation.height + velocity.height / 4)
135
+ }
136
+ }
137
+
138
+ public var minimumDistance: CGFloat
139
+ public var coordinateSpace: CoordinateSpace
140
+
141
+ public init(minimumDistance: CGFloat = 10, coordinateSpace: CoordinateSpace = .local) {
142
+ self.minimumDistance = minimumDistance
143
+ self.coordinateSpace = coordinateSpace
144
+ }
145
+
146
+ public var _config: _GestureConfig {
147
+ var config = _GestureConfig()
148
+ config.drag = true
149
+ config.minimumDistance = minimumDistance
150
+ return config
151
+ }
152
+
153
+ public static func _adding(to config: _GestureConfig,
154
+ changed: (@MainActor (Value) -> Void)?,
155
+ ended: (@MainActor (Value) -> Void)?) -> _GestureConfig {
156
+ var config = config
157
+ if let changed { config.onDragChanged.append(changed) }
158
+ if let ended { config.onDragEnded.append(ended) }
159
+ return config
160
+ }
161
+ }
162
+
163
+ // MARK: - LongPressGesture
164
+
165
+ /// `Value` is `true` once the press is recognized. The renderer reports only
166
+ /// the recognition, so `onChanged(true)` and `onEnded(true)` fire together.
167
+ public struct LongPressGesture: Gesture {
168
+ public typealias Value = Bool
169
+ public var minimumDuration: Double
170
+ public var maximumDistance: CGFloat
171
+
172
+ public init(minimumDuration: Double = 0.5, maximumDistance: CGFloat = 10) {
173
+ self.minimumDuration = minimumDuration
174
+ self.maximumDistance = maximumDistance
175
+ }
176
+
177
+ public var _config: _GestureConfig {
178
+ var config = _GestureConfig()
179
+ config.longPress = true
180
+ config.minimumDuration = minimumDuration
181
+ return config
182
+ }
183
+
184
+ public static func _adding(to config: _GestureConfig,
185
+ changed: (@MainActor (Value) -> Void)?,
186
+ ended: (@MainActor (Value) -> Void)?) -> _GestureConfig {
187
+ var config = config
188
+ if let changed { config.onLongPressChanged.append(changed) }
189
+ if let ended { config.onLongPressEnded.append(ended) }
190
+ return config
191
+ }
192
+ }
193
+
194
+ // MARK: - TapGesture
195
+
196
+ /// A tap; `count` is accepted and ignored (every tap is reported).
197
+ public struct TapGesture: Gesture {
198
+ public typealias Value = Void
199
+ public var count: Int
200
+
201
+ public init(count: Int = 1) { self.count = count }
202
+
203
+ public var _config: _GestureConfig { _GestureConfig() }
204
+
205
+ public static func _adding(to config: _GestureConfig,
206
+ changed: (@MainActor (Value) -> Void)?,
207
+ ended: (@MainActor (Value) -> Void)?) -> _GestureConfig {
208
+ var config = config
209
+ if let ended { config.onTap.append { ended(()) } }
210
+ return config
211
+ }
212
+ }
213
+
214
+ // MARK: - The element
215
+
216
+ /// The `gesture` element: props say what to track, the handler parses the
217
+ /// renderer's `drag` / `longpress` events. A `cancelled` drag ends the
218
+ /// gesture with its last value (`onEnded` runs), so state the app resets in
219
+ /// `onEnded` is reset.
220
+ struct _GestureHost {
221
+ let content: any View
222
+ let config: _GestureConfig
223
+ }
224
+ extension _GestureHost: _PrimitiveView {}
225
+ extension _GestureHost: _HostView {
226
+ var _kind: String { "gesture" }
227
+ func _props(in environment: EnvironmentValues) -> _JSON {
228
+ .object([
229
+ "drag": .bool(config.drag),
230
+ "longPress": .bool(config.longPress),
231
+ "minimumDistance": .number(config.minimumDistance),
232
+ "minimumDuration": .number(config.minimumDuration),
233
+ ])
234
+ }
235
+ var _content: [any View] { [content] }
236
+ var _handler: ((_JSON) -> Void)? {
237
+ let config = self.config
238
+ return { payload in
239
+ guard case .object(let fields) = payload, case .string(let type)? = fields["type"] else { return }
240
+ MainActor.assumeIsolated {
241
+ switch type {
242
+ case "drag":
243
+ let value = DragGesture.Value(fields)
244
+ let phase: String = if case .string(let p)? = fields["phase"] { p } else { "changed" }
245
+ switch phase {
246
+ case "began", "changed":
247
+ for action in config.onDragChanged { action(value) }
248
+ case "ended", "cancelled":
249
+ for action in config.onDragEnded { action(value) }
250
+ default:
251
+ break
252
+ }
253
+ case "longpress":
254
+ for action in config.onLongPressChanged { action(true) }
255
+ for action in config.onLongPressEnded { action(true) }
256
+ default:
257
+ break
258
+ }
259
+ }
260
+ }
261
+ }
262
+ }
263
+
264
+ extension DragGesture.Value {
265
+ /// Parses a `drag` event's fields; absent fields are zero.
266
+ init(_ fields: [String: _JSON]) {
267
+ func point(_ key: String) -> CGPoint {
268
+ guard case .object(let o)? = fields[key] else { return .zero }
269
+ return CGPoint(x: _number(o["x"]), y: _number(o["y"]))
270
+ }
271
+ func size(_ key: String) -> CGSize {
272
+ guard case .object(let o)? = fields[key] else { return .zero }
273
+ return CGSize(width: _number(o["width"]), height: _number(o["height"]))
274
+ }
275
+ self.init(location: point("location"), startLocation: point("startLocation"),
276
+ translation: size("translation"), velocity: size("velocity"))
277
+ }
278
+ }
279
+
280
+ private func _number(_ json: _JSON?) -> Double {
281
+ if case .number(let n)? = json { return n }
282
+ return 0
283
+ }
284
+
285
+ /// `.gesture(_:)`: the content under a `gesture` element when the gesture
286
+ /// drags or long-presses, under a `tapgesture` when it taps, both when it does
287
+ /// both, and plain when the gesture has no callbacks.
288
+ struct _GestureView<Content: View> {
289
+ let content: Content
290
+ let config: _GestureConfig
291
+ }
292
+ extension _GestureView: _PrimitiveView {}
293
+ extension _GestureView: _GroupView {
294
+ var _children: [any View] {
295
+ var inner: any View = content
296
+ if !config.onTap.isEmpty {
297
+ let taps = config.onTap
298
+ inner = _TapGestureView(content: AnyView(inner)) { for action in taps { action() } }
299
+ }
300
+ if config.drag || config.longPress {
301
+ return [_GestureHost(content: inner, config: config)]
302
+ }
303
+ return [inner]
304
+ }
305
+ }
306
+
307
+ extension View {
308
+ public func gesture<G: Gesture>(_ gesture: G, including mask: GestureMask = .all) -> some View {
309
+ _GestureView(content: self, config: gesture._config)
310
+ }
311
+
312
+ public func highPriorityGesture<G: Gesture>(_ gesture: G, including mask: GestureMask = .all) -> some View {
313
+ _GestureView(content: self, config: gesture._config)
314
+ }
315
+
316
+ public func simultaneousGesture<G: Gesture>(_ gesture: G, including mask: GestureMask = .all) -> some View {
317
+ _GestureView(content: self, config: gesture._config)
318
+ }
319
+
320
+ /// `onPressingChanged` is told `true` then `false` around `action`, when
321
+ /// the press is recognized (the renderer reports no earlier touch-down).
322
+ public func onLongPressGesture(minimumDuration: Double = 0.5, maximumDistance: CGFloat = 10,
323
+ perform action: @escaping @MainActor () -> Void,
324
+ onPressingChanged: (@MainActor (Bool) -> Void)? = nil) -> some View {
325
+ var config = _GestureConfig()
326
+ config.longPress = true
327
+ config.minimumDuration = minimumDuration
328
+ config.onLongPressEnded = [{ _ in
329
+ onPressingChanged?(true)
330
+ action()
331
+ onPressingChanged?(false)
332
+ }]
333
+ return _GestureView(content: self, config: config)
334
+ }
335
+
336
+ public func onLongPressGesture(minimumDuration: Double = 0.5, maximumDistance: CGFloat = 10,
337
+ pressing: (@MainActor (Bool) -> Void)?,
338
+ perform action: @escaping @MainActor () -> Void) -> some View {
339
+ onLongPressGesture(minimumDuration: minimumDuration, maximumDistance: maximumDistance,
340
+ perform: action, onPressingChanged: pressing)
341
+ }
342
+ }
@@ -0,0 +1,188 @@
1
+ // Grid containers. The renderer lays them out; the Swift side sends the
2
+ // configuration and the children. See docs/ops-protocol.md (Phase 6).
3
+
4
+ // MARK: - Grid / GridRow
5
+
6
+ /// A two-dimensional layout of rows (`GridRow`) and columns.
7
+ public struct Grid<Content: View> {
8
+ let alignment: Alignment
9
+ let horizontalSpacing: CGFloat?
10
+ let verticalSpacing: CGFloat?
11
+ let content: Content
12
+
13
+ public init(alignment: Alignment = .center, horizontalSpacing: CGFloat? = nil,
14
+ verticalSpacing: CGFloat? = nil, @ViewBuilder content: () -> Content) {
15
+ self.alignment = alignment
16
+ self.horizontalSpacing = horizontalSpacing
17
+ self.verticalSpacing = verticalSpacing
18
+ self.content = content()
19
+ }
20
+ }
21
+ extension Grid: _PrimitiveView {}
22
+ extension Grid: _HostView {
23
+ var _kind: String { "grid" }
24
+ func _props(in environment: EnvironmentValues) -> _JSON {
25
+ .object([
26
+ "horizontalSpacing": horizontalSpacing.map(_JSON.number) ?? .null,
27
+ "verticalSpacing": verticalSpacing.map(_JSON.number) ?? .null,
28
+ "alignment": .string(alignment._name),
29
+ ])
30
+ }
31
+ var _content: [any View] { [content] }
32
+ }
33
+
34
+ /// One row of a `Grid`; each child is a cell.
35
+ public struct GridRow<Content: View> {
36
+ let alignment: VerticalAlignment?
37
+ let content: Content
38
+
39
+ public init(alignment: VerticalAlignment? = nil, @ViewBuilder content: () -> Content) {
40
+ self.alignment = alignment
41
+ self.content = content()
42
+ }
43
+ }
44
+ extension GridRow: _PrimitiveView {}
45
+ extension GridRow: _HostView {
46
+ var _kind: String { "gridrow" }
47
+ func _props(in environment: EnvironmentValues) -> _JSON {
48
+ // Baseline alignments are approximated as `center` (see VerticalAlignment).
49
+ .object(["alignment": alignment.map { .string($0.name) } ?? .null])
50
+ }
51
+ var _content: [any View] { [content] }
52
+ }
53
+
54
+ /// A `styled` element carrying grid cell hints. `_Style` lives in
55
+ /// Modifiers.swift; this view is protocol-identical for the host.
56
+ struct _GridCellHintView<Content: View> {
57
+ let content: Content
58
+ let style: [String: _JSON]
59
+ }
60
+ extension _GridCellHintView: _PrimitiveView {}
61
+ extension _GridCellHintView: _HostView {
62
+ var _kind: String { "styled" }
63
+ func _props(in environment: EnvironmentValues) -> _JSON { .object(["style": .object(style)]) }
64
+ var _content: [any View] { [content] }
65
+ }
66
+
67
+ extension View {
68
+ /// Makes the cell span `count` columns of its `GridRow`.
69
+ public func gridCellColumns(_ count: Int) -> some View {
70
+ _GridCellHintView(content: self, style: ["gridCellColumns": .number(Double(count))])
71
+ }
72
+
73
+ /// Where the cell's content sits when the cell is larger than the content.
74
+ public func gridCellAnchor(_ anchor: UnitPoint) -> some View {
75
+ _GridCellHintView(content: self, style: ["gridCellAnchor": .object(["x": .number(anchor.x), "y": .number(anchor.y)])])
76
+ }
77
+
78
+ /// Accepted for source compatibility; the renderer aligns columns by the grid's alignment.
79
+ public func gridColumnAlignment(_ guide: HorizontalAlignment) -> some View { self }
80
+
81
+ /// Accepted for source compatibility; cells are sized by their content.
82
+ public func gridCellUnsizedAxes(_ axes: Axis.Set) -> some View { self }
83
+ }
84
+
85
+ // MARK: - GridItem
86
+
87
+ /// A column of a `LazyVGrid` (or a row of a `LazyHGrid`).
88
+ public struct GridItem: Sendable {
89
+ public enum Size: Sendable {
90
+ case fixed(CGFloat)
91
+ case flexible(minimum: CGFloat = 10, maximum: CGFloat = .infinity)
92
+ case adaptive(minimum: CGFloat, maximum: CGFloat = .infinity)
93
+ }
94
+
95
+ public var size: Size
96
+ public var spacing: CGFloat?
97
+ public var alignment: Alignment?
98
+
99
+ public init(_ size: Size = .flexible(), spacing: CGFloat? = nil, alignment: Alignment? = nil) {
100
+ self.size = size
101
+ self.spacing = spacing
102
+ self.alignment = alignment
103
+ }
104
+
105
+ var _json: _JSON {
106
+ func bound(_ value: CGFloat) -> _JSON { value == .infinity ? .string("infinity") : .number(value) }
107
+ let size: _JSON = switch self.size {
108
+ case .fixed(let value):
109
+ .object(["kind": .string("fixed"), "value": .number(value)])
110
+ case .flexible(let minimum, let maximum):
111
+ .object(["kind": .string("flexible"), "minimum": .number(minimum), "maximum": bound(maximum)])
112
+ case .adaptive(let minimum, let maximum):
113
+ .object(["kind": .string("adaptive"), "minimum": .number(minimum), "maximum": bound(maximum)])
114
+ }
115
+ return .object([
116
+ "size": size,
117
+ "spacing": spacing.map(_JSON.number) ?? .null,
118
+ "alignment": alignment.map { .string($0._name) } ?? .null,
119
+ ])
120
+ }
121
+ }
122
+
123
+ /// Views a lazy grid keeps pinned while scrolling. Accepted for source
124
+ /// compatibility; the renderer does not pin.
125
+ public struct PinnedScrollableViews: OptionSet, Sendable {
126
+ public let rawValue: UInt32
127
+ public init(rawValue: UInt32) { self.rawValue = rawValue }
128
+ public static let sectionHeaders = PinnedScrollableViews(rawValue: 1 << 0)
129
+ public static let sectionFooters = PinnedScrollableViews(rawValue: 1 << 1)
130
+ }
131
+
132
+ // MARK: - LazyVGrid / LazyHGrid
133
+
134
+ /// A grid that grows vertically, filling the given columns row by row.
135
+ public struct LazyVGrid<Content: View> {
136
+ let columns: [GridItem]
137
+ let alignment: HorizontalAlignment
138
+ let spacing: CGFloat?
139
+ let content: Content
140
+
141
+ public init(columns: [GridItem], alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil,
142
+ pinnedViews: PinnedScrollableViews = .init(), @ViewBuilder content: () -> Content) {
143
+ self.columns = columns
144
+ self.alignment = alignment
145
+ self.spacing = spacing
146
+ self.content = content()
147
+ }
148
+ }
149
+ extension LazyVGrid: _PrimitiveView {}
150
+ extension LazyVGrid: _HostView {
151
+ var _kind: String { "lazyvgrid" }
152
+ func _props(in environment: EnvironmentValues) -> _JSON {
153
+ .object([
154
+ "columns": .array(columns.map(\._json)),
155
+ "spacing": spacing.map(_JSON.number) ?? .null,
156
+ "alignment": .string(alignment.name),
157
+ ])
158
+ }
159
+ var _content: [any View] { [content] }
160
+ }
161
+
162
+ /// A grid that grows horizontally, filling the given rows column by column.
163
+ public struct LazyHGrid<Content: View> {
164
+ let rows: [GridItem]
165
+ let alignment: VerticalAlignment
166
+ let spacing: CGFloat?
167
+ let content: Content
168
+
169
+ public init(rows: [GridItem], alignment: VerticalAlignment = .center, spacing: CGFloat? = nil,
170
+ pinnedViews: PinnedScrollableViews = .init(), @ViewBuilder content: () -> Content) {
171
+ self.rows = rows
172
+ self.alignment = alignment
173
+ self.spacing = spacing
174
+ self.content = content()
175
+ }
176
+ }
177
+ extension LazyHGrid: _PrimitiveView {}
178
+ extension LazyHGrid: _HostView {
179
+ var _kind: String { "lazyhgrid" }
180
+ func _props(in environment: EnvironmentValues) -> _JSON {
181
+ .object([
182
+ "rows": .array(rows.map(\._json)),
183
+ "spacing": spacing.map(_JSON.number) ?? .null,
184
+ "alignment": .string(alignment.name),
185
+ ])
186
+ }
187
+ var _content: [any View] { [content] }
188
+ }
@@ -0,0 +1,117 @@
1
+ /// `LabelStyle`: how a `Label` arranges its title and icon. `Label.body`
2
+ /// consults the environment's style (set by `.labelStyle`).
3
+ public protocol LabelStyle {
4
+ associatedtype Body: View
5
+ typealias Configuration = LabelStyleConfiguration
6
+ @ViewBuilder @MainActor func makeBody(configuration: Configuration) -> Body
7
+ }
8
+
9
+ public struct LabelStyleConfiguration {
10
+ public struct Title: View {
11
+ let view: any View
12
+ public var body: some View { AnyView(view) }
13
+ }
14
+
15
+ public struct Icon: View {
16
+ let view: any View
17
+ public var body: some View { AnyView(view) }
18
+ }
19
+
20
+ public let title: Title
21
+ public let icon: Icon
22
+ }
23
+
24
+ public struct DefaultLabelStyle: LabelStyle {
25
+ public init() {}
26
+ public func makeBody(configuration: Configuration) -> some View {
27
+ HStack(spacing: 8) {
28
+ configuration.icon
29
+ configuration.title
30
+ }
31
+ }
32
+ }
33
+
34
+ public struct TitleAndIconLabelStyle: LabelStyle {
35
+ public init() {}
36
+ public func makeBody(configuration: Configuration) -> some View {
37
+ HStack(spacing: 8) {
38
+ configuration.icon
39
+ configuration.title
40
+ }
41
+ }
42
+ }
43
+
44
+ public struct IconOnlyLabelStyle: LabelStyle {
45
+ public init() {}
46
+ public func makeBody(configuration: Configuration) -> some View { configuration.icon }
47
+ }
48
+
49
+ public struct TitleOnlyLabelStyle: LabelStyle {
50
+ public init() {}
51
+ public func makeBody(configuration: Configuration) -> some View { configuration.title }
52
+ }
53
+
54
+ extension LabelStyle where Self == DefaultLabelStyle {
55
+ public static var automatic: DefaultLabelStyle { DefaultLabelStyle() }
56
+ }
57
+ extension LabelStyle where Self == TitleAndIconLabelStyle {
58
+ public static var titleAndIcon: TitleAndIconLabelStyle { TitleAndIconLabelStyle() }
59
+ }
60
+ extension LabelStyle where Self == IconOnlyLabelStyle {
61
+ public static var iconOnly: IconOnlyLabelStyle { IconOnlyLabelStyle() }
62
+ }
63
+ extension LabelStyle where Self == TitleOnlyLabelStyle {
64
+ public static var titleOnly: TitleOnlyLabelStyle { TitleOnlyLabelStyle() }
65
+ }
66
+
67
+ /// Type-erased style stored in the environment.
68
+ struct _AnyLabelStyle {
69
+ let make: @MainActor (LabelStyleConfiguration) -> any View
70
+
71
+ @MainActor
72
+ init<S: LabelStyle>(_ style: S) {
73
+ make = { configuration in style.makeBody(configuration: configuration) }
74
+ }
75
+
76
+ nonisolated init(make: @escaping @MainActor (LabelStyleConfiguration) -> any View) {
77
+ self.make = make
78
+ }
79
+
80
+ /// Usable from nonisolated code (toolbars build their children there).
81
+ nonisolated static var iconOnly: _AnyLabelStyle {
82
+ _AnyLabelStyle(make: { configuration in IconOnlyLabelStyle().makeBody(configuration: configuration) })
83
+ }
84
+ }
85
+
86
+ private struct _LabelStyleKey: EnvironmentKey {
87
+ static var defaultValue: _AnyLabelStyle? { nil }
88
+ }
89
+
90
+ extension EnvironmentValues {
91
+ var _labelStyle: _AnyLabelStyle? {
92
+ get { self[_LabelStyleKey.self] }
93
+ set { self[_LabelStyleKey.self] = newValue }
94
+ }
95
+ }
96
+
97
+ extension View {
98
+ public func labelStyle<S: LabelStyle>(_ style: S) -> some View {
99
+ environment(\._labelStyle, _AnyLabelStyle(style))
100
+ }
101
+ }
102
+
103
+ /// Resolves a label through the environment's style. `Label.body` returns
104
+ /// this so the style is read where the label is mounted.
105
+ struct _StyledLabel: View {
106
+ let title: any View
107
+ let icon: any View
108
+ @Environment(\._labelStyle) private var style
109
+
110
+ var body: some View {
111
+ let configuration = LabelStyleConfiguration(title: .init(view: title), icon: .init(view: icon))
112
+ if let style {
113
+ return AnyView(style.make(configuration))
114
+ }
115
+ return AnyView(DefaultLabelStyle().makeBody(configuration: configuration))
116
+ }
117
+ }