@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,311 @@
|
|
|
1
|
+
/// Paints: colors, hierarchical label/fill levels, gradients and materials.
|
|
2
|
+
/// A style serializes to the JSON the renderer resolves (see
|
|
3
|
+
/// docs/ops-protocol.md, "Phase 6 additions"): a `Color`'s own forms, or
|
|
4
|
+
/// `{"hierarchical": 1...4}`, `{"linearGradient": {...}}`,
|
|
5
|
+
/// `{"radialGradient": {...}}`, `{"colorGradient": color}`, `{"material": name}`.
|
|
6
|
+
public protocol ShapeStyle: Sendable {
|
|
7
|
+
var _json: _JSON { get }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
extension Color: ShapeStyle {}
|
|
11
|
+
|
|
12
|
+
// MARK: - Hierarchical (.primary ... .quaternary)
|
|
13
|
+
|
|
14
|
+
public struct HierarchicalShapeStyle: ShapeStyle, Hashable {
|
|
15
|
+
let level: Int
|
|
16
|
+
|
|
17
|
+
public static let primary = HierarchicalShapeStyle(level: 1)
|
|
18
|
+
public static let secondary = HierarchicalShapeStyle(level: 2)
|
|
19
|
+
public static let tertiary = HierarchicalShapeStyle(level: 3)
|
|
20
|
+
public static let quaternary = HierarchicalShapeStyle(level: 4)
|
|
21
|
+
|
|
22
|
+
public var _json: _JSON { .object(["hierarchical": .number(Double(level))]) }
|
|
23
|
+
|
|
24
|
+
public func opacity(_ opacity: Double) -> AnyShapeStyle {
|
|
25
|
+
AnyShapeStyle(_json: .object(["hierarchical": .number(Double(level)), "opacity": .number(opacity)]))
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
extension ShapeStyle where Self == HierarchicalShapeStyle {
|
|
30
|
+
public static var primary: HierarchicalShapeStyle { .primary }
|
|
31
|
+
public static var secondary: HierarchicalShapeStyle { .secondary }
|
|
32
|
+
public static var tertiary: HierarchicalShapeStyle { .tertiary }
|
|
33
|
+
public static var quaternary: HierarchicalShapeStyle { .quaternary }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// The view's background color (`.background` as a style).
|
|
37
|
+
public struct BackgroundStyle: ShapeStyle, Hashable {
|
|
38
|
+
public init() {}
|
|
39
|
+
public var _json: _JSON { .object(["name": .string("systemBackground")]) }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
extension ShapeStyle where Self == BackgroundStyle {
|
|
43
|
+
public static var background: BackgroundStyle { BackgroundStyle() }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// The current foreground color (`.foreground` as a style; `currentColor`).
|
|
47
|
+
public struct ForegroundStyle: ShapeStyle, Hashable {
|
|
48
|
+
public init() {}
|
|
49
|
+
public var _json: _JSON { .object(["name": .string("primary")]) }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
extension ShapeStyle where Self == ForegroundStyle {
|
|
53
|
+
public static var foreground: ForegroundStyle { ForegroundStyle() }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// The named colors, reachable as styles (`.fill(.indigo)`, `.background(.red)`).
|
|
57
|
+
extension ShapeStyle where Self == Color {
|
|
58
|
+
public static var red: Color { .red }
|
|
59
|
+
public static var orange: Color { .orange }
|
|
60
|
+
public static var yellow: Color { .yellow }
|
|
61
|
+
public static var green: Color { .green }
|
|
62
|
+
public static var mint: Color { .mint }
|
|
63
|
+
public static var teal: Color { .teal }
|
|
64
|
+
public static var cyan: Color { .cyan }
|
|
65
|
+
public static var blue: Color { .blue }
|
|
66
|
+
public static var indigo: Color { .indigo }
|
|
67
|
+
public static var purple: Color { .purple }
|
|
68
|
+
public static var pink: Color { .pink }
|
|
69
|
+
public static var brown: Color { .brown }
|
|
70
|
+
public static var gray: Color { .gray }
|
|
71
|
+
public static var black: Color { .black }
|
|
72
|
+
public static var white: Color { .white }
|
|
73
|
+
public static var clear: Color { .clear }
|
|
74
|
+
public static var accentColor: Color { .accentColor }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// MARK: - Gradients
|
|
78
|
+
|
|
79
|
+
public struct Gradient: Equatable, Sendable {
|
|
80
|
+
public struct Stop: Equatable, Sendable {
|
|
81
|
+
public var color: Color
|
|
82
|
+
public var location: CGFloat
|
|
83
|
+
|
|
84
|
+
public init(color: Color, location: CGFloat) {
|
|
85
|
+
self.color = color
|
|
86
|
+
self.location = location
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public var stops: [Stop]
|
|
91
|
+
|
|
92
|
+
public init(stops: [Stop]) { self.stops = stops }
|
|
93
|
+
|
|
94
|
+
/// Evenly spaced stops.
|
|
95
|
+
public init(colors: [Color]) {
|
|
96
|
+
let count = max(colors.count - 1, 1)
|
|
97
|
+
stops = colors.enumerated().map { index, color in
|
|
98
|
+
Stop(color: color, location: colors.count == 1 ? 0 : CGFloat(index) / CGFloat(count))
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
var _json: _JSON {
|
|
103
|
+
.array(stops.map { .object(["color": $0.color._json, "location": .number($0.location)]) })
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/// `Color.gradient`: a subtle vertical gradient derived from one color.
|
|
108
|
+
public struct AnyGradient: ShapeStyle, Equatable {
|
|
109
|
+
let color: Color
|
|
110
|
+
|
|
111
|
+
public var _json: _JSON { .object(["colorGradient": color._json]) }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
extension Color {
|
|
115
|
+
public var gradient: AnyGradient { AnyGradient(color: self) }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public struct LinearGradient: ShapeStyle, Equatable {
|
|
119
|
+
public var gradient: Gradient
|
|
120
|
+
public var startPoint: UnitPoint
|
|
121
|
+
public var endPoint: UnitPoint
|
|
122
|
+
|
|
123
|
+
public init(gradient: Gradient, startPoint: UnitPoint, endPoint: UnitPoint) {
|
|
124
|
+
self.gradient = gradient
|
|
125
|
+
self.startPoint = startPoint
|
|
126
|
+
self.endPoint = endPoint
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public init(colors: [Color], startPoint: UnitPoint, endPoint: UnitPoint) {
|
|
130
|
+
self.init(gradient: Gradient(colors: colors), startPoint: startPoint, endPoint: endPoint)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public init(stops: [Gradient.Stop], startPoint: UnitPoint, endPoint: UnitPoint) {
|
|
134
|
+
self.init(gradient: Gradient(stops: stops), startPoint: startPoint, endPoint: endPoint)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public var _json: _JSON {
|
|
138
|
+
.object(["linearGradient": .object([
|
|
139
|
+
"stops": gradient._json,
|
|
140
|
+
"start": startPoint._json,
|
|
141
|
+
"end": endPoint._json,
|
|
142
|
+
])])
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public struct RadialGradient: ShapeStyle, Equatable {
|
|
147
|
+
public var gradient: Gradient
|
|
148
|
+
public var center: UnitPoint
|
|
149
|
+
public var startRadius: CGFloat
|
|
150
|
+
public var endRadius: CGFloat
|
|
151
|
+
|
|
152
|
+
public init(gradient: Gradient, center: UnitPoint, startRadius: CGFloat, endRadius: CGFloat) {
|
|
153
|
+
self.gradient = gradient
|
|
154
|
+
self.center = center
|
|
155
|
+
self.startRadius = startRadius
|
|
156
|
+
self.endRadius = endRadius
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public init(colors: [Color], center: UnitPoint, startRadius: CGFloat, endRadius: CGFloat) {
|
|
160
|
+
self.init(gradient: Gradient(colors: colors), center: center, startRadius: startRadius, endRadius: endRadius)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public init(stops: [Gradient.Stop], center: UnitPoint, startRadius: CGFloat, endRadius: CGFloat) {
|
|
164
|
+
self.init(gradient: Gradient(stops: stops), center: center, startRadius: startRadius, endRadius: endRadius)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
public var _json: _JSON {
|
|
168
|
+
.object(["radialGradient": .object([
|
|
169
|
+
"stops": gradient._json,
|
|
170
|
+
"center": center._json,
|
|
171
|
+
"startRadius": .number(startRadius),
|
|
172
|
+
"endRadius": .number(endRadius),
|
|
173
|
+
])])
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
extension ShapeStyle where Self == LinearGradient {
|
|
178
|
+
public static func linearGradient(_ gradient: Gradient, startPoint: UnitPoint, endPoint: UnitPoint) -> LinearGradient {
|
|
179
|
+
LinearGradient(gradient: gradient, startPoint: startPoint, endPoint: endPoint)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public static func linearGradient(colors: [Color], startPoint: UnitPoint, endPoint: UnitPoint) -> LinearGradient {
|
|
183
|
+
LinearGradient(colors: colors, startPoint: startPoint, endPoint: endPoint)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public static func linearGradient(stops: [Gradient.Stop], startPoint: UnitPoint, endPoint: UnitPoint) -> LinearGradient {
|
|
187
|
+
LinearGradient(stops: stops, startPoint: startPoint, endPoint: endPoint)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
extension ShapeStyle where Self == RadialGradient {
|
|
192
|
+
public static func radialGradient(_ gradient: Gradient, center: UnitPoint, startRadius: CGFloat,
|
|
193
|
+
endRadius: CGFloat) -> RadialGradient {
|
|
194
|
+
RadialGradient(gradient: gradient, center: center, startRadius: startRadius, endRadius: endRadius)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
public static func radialGradient(colors: [Color], center: UnitPoint, startRadius: CGFloat,
|
|
198
|
+
endRadius: CGFloat) -> RadialGradient {
|
|
199
|
+
RadialGradient(colors: colors, center: center, startRadius: startRadius, endRadius: endRadius)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
extension UnitPoint {
|
|
204
|
+
var _json: _JSON { .object(["x": .number(x), "y": .number(y)]) }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Gradients as views: a rectangle filled with the gradient (a `shape` element).
|
|
208
|
+
private func _gradientViewProps(_ style: some ShapeStyle) -> _JSON {
|
|
209
|
+
.object([
|
|
210
|
+
"shape": .string("rectangle"),
|
|
211
|
+
"cornerRadius": .number(0),
|
|
212
|
+
"fill": style._json,
|
|
213
|
+
"stroke": .null,
|
|
214
|
+
])
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
extension LinearGradient: _PrimitiveView {}
|
|
218
|
+
extension LinearGradient: _HostView {
|
|
219
|
+
var _kind: String { "shape" }
|
|
220
|
+
func _props(in environment: EnvironmentValues) -> _JSON { _gradientViewProps(self) }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
extension RadialGradient: _PrimitiveView {}
|
|
224
|
+
extension RadialGradient: _HostView {
|
|
225
|
+
var _kind: String { "shape" }
|
|
226
|
+
func _props(in environment: EnvironmentValues) -> _JSON { _gradientViewProps(self) }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
extension AnyGradient: _PrimitiveView {}
|
|
230
|
+
extension AnyGradient: _HostView {
|
|
231
|
+
var _kind: String { "shape" }
|
|
232
|
+
func _props(in environment: EnvironmentValues) -> _JSON { _gradientViewProps(self) }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// MARK: - Shadows
|
|
236
|
+
|
|
237
|
+
/// A shadow a style can carry (`.shadow(.drop(...))`). The renderer has no
|
|
238
|
+
/// per-style shadows, so a shadowed style paints as its base style.
|
|
239
|
+
public struct ShadowStyle: Equatable, Sendable {
|
|
240
|
+
public enum Kind: Equatable, Sendable { case drop, inner }
|
|
241
|
+
|
|
242
|
+
public let kind: Kind
|
|
243
|
+
public let color: Color
|
|
244
|
+
public let radius: CGFloat
|
|
245
|
+
public let x: CGFloat
|
|
246
|
+
public let y: CGFloat
|
|
247
|
+
|
|
248
|
+
public static func drop(color: Color = Color(white: 0, opacity: 0.33), radius: CGFloat,
|
|
249
|
+
x: CGFloat = 0, y: CGFloat = 0) -> ShadowStyle {
|
|
250
|
+
ShadowStyle(kind: .drop, color: color, radius: radius, x: x, y: y)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
public static func inner(color: Color = Color(white: 0, opacity: 0.55), radius: CGFloat,
|
|
254
|
+
x: CGFloat = 0, y: CGFloat = 0) -> ShadowStyle {
|
|
255
|
+
ShadowStyle(kind: .inner, color: color, radius: radius, x: x, y: y)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
extension ShapeStyle {
|
|
260
|
+
/// Approximation: the shadow is dropped and the style paints as before.
|
|
261
|
+
public func shadow(_ style: ShadowStyle) -> AnyShapeStyle {
|
|
262
|
+
AnyShapeStyle(_json: _json)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// MARK: - Materials
|
|
267
|
+
|
|
268
|
+
public struct Material: ShapeStyle, Hashable {
|
|
269
|
+
let name: String
|
|
270
|
+
|
|
271
|
+
public static let ultraThinMaterial = Material(name: "ultraThin")
|
|
272
|
+
public static let thinMaterial = Material(name: "thin")
|
|
273
|
+
public static let regularMaterial = Material(name: "regular")
|
|
274
|
+
public static let thickMaterial = Material(name: "thick")
|
|
275
|
+
public static let ultraThickMaterial = Material(name: "ultraThick")
|
|
276
|
+
public static let bar = Material(name: "regular")
|
|
277
|
+
|
|
278
|
+
public var _json: _JSON { .object(["material": .string(name)]) }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
extension ShapeStyle where Self == Material {
|
|
282
|
+
public static var ultraThinMaterial: Material { .ultraThinMaterial }
|
|
283
|
+
public static var thinMaterial: Material { .thinMaterial }
|
|
284
|
+
public static var regularMaterial: Material { .regularMaterial }
|
|
285
|
+
public static var thickMaterial: Material { .thickMaterial }
|
|
286
|
+
public static var ultraThickMaterial: Material { .ultraThickMaterial }
|
|
287
|
+
public static var bar: Material { .bar }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// MARK: - Type erasure
|
|
291
|
+
|
|
292
|
+
public struct AnyShapeStyle: ShapeStyle {
|
|
293
|
+
public let _json: _JSON
|
|
294
|
+
|
|
295
|
+
public init<S: ShapeStyle>(_ style: S) { _json = style._json }
|
|
296
|
+
|
|
297
|
+
init(_json: _JSON) { self._json = _json }
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/// Opacity on any style: wraps the style's JSON with an `opacity` multiplier.
|
|
301
|
+
extension ShapeStyle {
|
|
302
|
+
public func opacity(_ opacity: Double) -> AnyShapeStyle {
|
|
303
|
+
if let color = self as? Color { return AnyShapeStyle(color.opacity(opacity)) }
|
|
304
|
+
if case .object(var fields) = _json {
|
|
305
|
+
let existing: Double = if case .number(let n)? = fields["opacity"] { n } else { 1 }
|
|
306
|
+
fields["opacity"] = .number(existing * opacity)
|
|
307
|
+
return AnyShapeStyle(_json: .object(fields))
|
|
308
|
+
}
|
|
309
|
+
return AnyShapeStyle(_json: _json)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/// Marker protocol matching SwiftUI's, so user code that constrains on it compiles.
|
|
2
|
+
public protocol DynamicProperty {
|
|
3
|
+
mutating func update()
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
extension DynamicProperty {
|
|
7
|
+
public mutating func update() {}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/// Type-erased access to a `State` box, used by the runtime to carry storage
|
|
11
|
+
/// across re-renders and to observe mutations.
|
|
12
|
+
protocol _AnyStateBox: AnyObject {
|
|
13
|
+
var _anyValue: Any { get set }
|
|
14
|
+
var _onChange: (() -> Void)? { get set }
|
|
15
|
+
/// Shares the persistent storage of a box from a previous render, so that
|
|
16
|
+
/// closures captured earlier keep writing to the live value.
|
|
17
|
+
func _adopt(_ previous: any _AnyStateBox)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Views expose their `State` properties to the runtime through this protocol.
|
|
21
|
+
protocol _StateProperty {
|
|
22
|
+
var _box: any _AnyStateBox { get }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// The persistent value behind a `State`; survives the view struct being
|
|
26
|
+
/// recreated because every box for the same mounted view shares it.
|
|
27
|
+
final class _StateStorage<Value> {
|
|
28
|
+
var value: Value {
|
|
29
|
+
didSet { onChange?() }
|
|
30
|
+
}
|
|
31
|
+
var onChange: (() -> Void)?
|
|
32
|
+
|
|
33
|
+
init(_ value: Value) { self.value = value }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
final class _StateBox<Value>: _AnyStateBox {
|
|
37
|
+
var storage: _StateStorage<Value>
|
|
38
|
+
|
|
39
|
+
var value: Value {
|
|
40
|
+
get { storage.value }
|
|
41
|
+
set { storage.value = newValue }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var _onChange: (() -> Void)? {
|
|
45
|
+
get { storage.onChange }
|
|
46
|
+
set { storage.onChange = newValue }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
init(_ value: Value) { storage = _StateStorage(value) }
|
|
50
|
+
|
|
51
|
+
var _anyValue: Any {
|
|
52
|
+
get { storage.value }
|
|
53
|
+
set { storage.value = newValue as! Value }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func _adopt(_ previous: any _AnyStateBox) {
|
|
57
|
+
guard let previous = previous as? _StateBox<Value> else { return }
|
|
58
|
+
storage = previous.storage
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// A property wrapper that reads and writes a value managed by the runtime.
|
|
63
|
+
///
|
|
64
|
+
/// A view struct is recreated every time its parent's body runs; the runtime
|
|
65
|
+
/// finds `State` properties by reflection and moves the previous values into
|
|
66
|
+
/// the new struct, so the value persists for as long as the view stays mounted.
|
|
67
|
+
@propertyWrapper
|
|
68
|
+
public struct State<Value>: DynamicProperty {
|
|
69
|
+
let box: _StateBox<Value>
|
|
70
|
+
|
|
71
|
+
public init(wrappedValue: Value) {
|
|
72
|
+
box = _StateBox(wrappedValue)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public init(initialValue: Value) {
|
|
76
|
+
self.init(wrappedValue: initialValue)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public var wrappedValue: Value {
|
|
80
|
+
get { box.value }
|
|
81
|
+
nonmutating set { box.value = newValue }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public var projectedValue: Binding<Value> {
|
|
85
|
+
let box = self.box
|
|
86
|
+
return Binding(get: { box.value }, set: { box.value = $0 })
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
extension State where Value: ExpressibleByNilLiteral {
|
|
91
|
+
public init() { self.init(wrappedValue: nil) }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
extension State: _StateProperty {
|
|
95
|
+
var _box: any _AnyStateBox { box }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// A two-way connection to a value owned elsewhere.
|
|
99
|
+
@propertyWrapper
|
|
100
|
+
@dynamicMemberLookup
|
|
101
|
+
public struct Binding<Value>: DynamicProperty {
|
|
102
|
+
let getter: () -> Value
|
|
103
|
+
let setter: (Value) -> Void
|
|
104
|
+
|
|
105
|
+
public init(get: @escaping () -> Value, set: @escaping (Value) -> Void) {
|
|
106
|
+
getter = get
|
|
107
|
+
setter = set
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public static func constant(_ value: Value) -> Binding<Value> {
|
|
111
|
+
Binding(get: { value }, set: { _ in })
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public var wrappedValue: Value {
|
|
115
|
+
get { getter() }
|
|
116
|
+
nonmutating set { setter(newValue) }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public var projectedValue: Binding<Value> { self }
|
|
120
|
+
|
|
121
|
+
public subscript<Subject>(dynamicMember keyPath: WritableKeyPath<Value, Subject>) -> Binding<Subject> {
|
|
122
|
+
Binding<Subject>(
|
|
123
|
+
get: { self.wrappedValue[keyPath: keyPath] },
|
|
124
|
+
set: { self.wrappedValue[keyPath: keyPath] = $0 }
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/// The line cap of a stroke (CoreGraphics' `CGLineCap`).
|
|
2
|
+
public enum CGLineCap: Int32, Hashable, Sendable {
|
|
3
|
+
case butt, round, square
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/// The line join of a stroke (CoreGraphics' `CGLineJoin`).
|
|
7
|
+
public enum CGLineJoin: Int32, Hashable, Sendable {
|
|
8
|
+
case miter, round, bevel
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/// How a line is stroked. Charts read `lineWidth`; the other fields are kept
|
|
12
|
+
/// for source compatibility.
|
|
13
|
+
public struct StrokeStyle: Equatable, Sendable {
|
|
14
|
+
public var lineWidth: CGFloat
|
|
15
|
+
public var lineCap: CGLineCap
|
|
16
|
+
public var lineJoin: CGLineJoin
|
|
17
|
+
public var miterLimit: CGFloat
|
|
18
|
+
public var dash: [CGFloat]
|
|
19
|
+
public var dashPhase: CGFloat
|
|
20
|
+
|
|
21
|
+
public init(lineWidth: CGFloat = 1, lineCap: CGLineCap = .butt, lineJoin: CGLineJoin = .miter,
|
|
22
|
+
miterLimit: CGFloat = 10, dash: [CGFloat] = [], dashPhase: CGFloat = 0) {
|
|
23
|
+
self.lineWidth = lineWidth
|
|
24
|
+
self.lineCap = lineCap
|
|
25
|
+
self.lineJoin = lineJoin
|
|
26
|
+
self.miterLimit = miterLimit
|
|
27
|
+
self.dash = dash
|
|
28
|
+
self.dashPhase = dashPhase
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/// A view that switches between child views with a tab bar.
|
|
2
|
+
public struct TabView<SelectionValue: Hashable, Content: View> {
|
|
3
|
+
let content: Content
|
|
4
|
+
let selection: Binding<SelectionValue>?
|
|
5
|
+
@State var selectedIndex = 0
|
|
6
|
+
|
|
7
|
+
public init(selection: Binding<SelectionValue>?, @ViewBuilder content: () -> Content) {
|
|
8
|
+
self.selection = selection
|
|
9
|
+
self.content = content()
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
extension TabView where SelectionValue == Int {
|
|
14
|
+
public init(@ViewBuilder content: () -> Content) {
|
|
15
|
+
self.selection = nil
|
|
16
|
+
self.content = content()
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protocol _TabViewHost {
|
|
21
|
+
var _tabContent: any View { get }
|
|
22
|
+
/// Index of the selected tab given the tabs' tags, and a setter.
|
|
23
|
+
func _selectedIndex(tags: [AnyHashable?]) -> Int
|
|
24
|
+
func _select(index: Int, tags: [AnyHashable?])
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
extension TabView: _PrimitiveView {}
|
|
28
|
+
extension TabView: _HostView {
|
|
29
|
+
var _kind: String { "tabview" }
|
|
30
|
+
func _props(in environment: EnvironmentValues) -> _JSON { .object([:]) }
|
|
31
|
+
// Children and props are produced by the runtime.
|
|
32
|
+
}
|
|
33
|
+
extension TabView: _TabViewHost {
|
|
34
|
+
var _tabContent: any View { content }
|
|
35
|
+
|
|
36
|
+
func _selectedIndex(tags: [AnyHashable?]) -> Int {
|
|
37
|
+
if let selection {
|
|
38
|
+
let current = AnyHashable(selection.wrappedValue)
|
|
39
|
+
return tags.firstIndex { $0 == current } ?? 0
|
|
40
|
+
}
|
|
41
|
+
return min(selectedIndex, max(0, tags.count - 1))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func _select(index: Int, tags: [AnyHashable?]) {
|
|
45
|
+
if let selection {
|
|
46
|
+
if index < tags.count, let tag = tags[index], let value = tag.base as? SelectionValue {
|
|
47
|
+
selection.wrappedValue = value
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
selectedIndex = index
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// One tab, produced by the runtime from the TabView's content.
|
|
56
|
+
struct _TabScreenView {
|
|
57
|
+
let content: any View
|
|
58
|
+
let title: String
|
|
59
|
+
let systemImage: String?
|
|
60
|
+
/// Only the selected tab's `navigationTitle` and `.toolbar` reach the
|
|
61
|
+
/// enclosing screen (a tab's own `NavigationStack` is flattened into it).
|
|
62
|
+
var isSelected = true
|
|
63
|
+
}
|
|
64
|
+
extension _TabScreenView: _PrimitiveView {}
|
|
65
|
+
extension _TabScreenView: _HostView {
|
|
66
|
+
var _kind: String { "tab" }
|
|
67
|
+
func _props(in environment: EnvironmentValues) -> _JSON {
|
|
68
|
+
.object(["title": .string(title), "systemImage": systemImage.map(_JSON.string) ?? .null])
|
|
69
|
+
}
|
|
70
|
+
var _content: [any View] { [content] }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// MARK: - .tabItem and .tag
|
|
74
|
+
|
|
75
|
+
protocol _TabItemProviding {
|
|
76
|
+
var _tabLabel: any View { get }
|
|
77
|
+
var _tabContent: any View { get }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
struct _TabItemView<Content: View, Label: View> {
|
|
81
|
+
let content: Content
|
|
82
|
+
let label: Label
|
|
83
|
+
}
|
|
84
|
+
extension _TabItemView: _PrimitiveView {}
|
|
85
|
+
extension _TabItemView: _GroupView {
|
|
86
|
+
var _children: [any View] { [content] }
|
|
87
|
+
}
|
|
88
|
+
extension _TabItemView: _TabItemProviding {
|
|
89
|
+
var _tabLabel: any View { label }
|
|
90
|
+
var _tabContent: any View { content }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
protocol _TagProviding {
|
|
94
|
+
var _tag: AnyHashable { get }
|
|
95
|
+
var _tagged: any View { get }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
struct _TagView<Content: View, Tag: Hashable> {
|
|
99
|
+
let content: Content
|
|
100
|
+
let tag: Tag
|
|
101
|
+
}
|
|
102
|
+
extension _TagView: _PrimitiveView {}
|
|
103
|
+
extension _TagView: _GroupView {
|
|
104
|
+
var _children: [any View] { [content] }
|
|
105
|
+
}
|
|
106
|
+
extension _TagView: _TagProviding {
|
|
107
|
+
var _tag: AnyHashable { AnyHashable(tag) }
|
|
108
|
+
var _tagged: any View { content }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
extension View {
|
|
112
|
+
public func tabItem<Label: View>(@ViewBuilder _ label: () -> Label) -> some View {
|
|
113
|
+
_TabItemView(content: self, label: label())
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public func tag<Tag: Hashable>(_ tag: Tag) -> some View {
|
|
117
|
+
_TagView(content: self, tag: tag)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/// Views that start an asynchronous task when they appear.
|
|
2
|
+
protocol _TaskStarting {
|
|
3
|
+
/// Identity of the task; when it changes the task restarts.
|
|
4
|
+
var _taskID: AnyHashable? { get }
|
|
5
|
+
var _taskAction: @Sendable () async -> Void { get }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
struct _TaskView<Content: View> {
|
|
9
|
+
let content: Content
|
|
10
|
+
let id: AnyHashable?
|
|
11
|
+
let action: @Sendable () async -> Void
|
|
12
|
+
}
|
|
13
|
+
extension _TaskView: _PrimitiveView {}
|
|
14
|
+
extension _TaskView: _GroupView {
|
|
15
|
+
var _children: [any View] { [content] }
|
|
16
|
+
}
|
|
17
|
+
extension _TaskView: _TaskStarting {
|
|
18
|
+
var _taskID: AnyHashable? { id }
|
|
19
|
+
var _taskAction: @Sendable () async -> Void { action }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
extension View {
|
|
23
|
+
/// Runs `action` in a task when the view appears, cancelling it when the
|
|
24
|
+
/// view disappears.
|
|
25
|
+
public func task(priority: TaskPriority = .userInitiated,
|
|
26
|
+
@_inheritActorContext _ action: @escaping @Sendable () async -> Void) -> some View {
|
|
27
|
+
_TaskView(content: self, id: nil, action: action)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// Like `task(_:)`, restarting the task whenever `id` changes.
|
|
31
|
+
public func task<T: Hashable>(id: T, priority: TaskPriority = .userInitiated,
|
|
32
|
+
@_inheritActorContext _ action: @escaping @Sendable () async -> Void) -> some View {
|
|
33
|
+
_TaskView(content: self, id: AnyHashable(id), action: action)
|
|
34
|
+
}
|
|
35
|
+
}
|