@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,82 @@
|
|
|
1
|
+
/// `.visualEffect { content, proxy in … }` (docs/ops-protocol.md, "Phase 12").
|
|
2
|
+
/// The closure's effects (offset, scale, rotation, opacity, blur, …) apply as
|
|
3
|
+
/// the matching view modifiers. The proxy is empty: the browser does not
|
|
4
|
+
/// report a view's frame on every scroll step, so an effect that reads
|
|
5
|
+
/// `proxy.frame(in: .global)` sees a zero rectangle and the view renders
|
|
6
|
+
/// without that part of the effect. Compile-level support, honestly limited.
|
|
7
|
+
|
|
8
|
+
public protocol VisualEffect {}
|
|
9
|
+
|
|
10
|
+
/// The chained effects, as style changes on the content.
|
|
11
|
+
protocol _VisualEffectChaining {
|
|
12
|
+
var _effects: [(inout _Style) -> Void] { get }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public struct EmptyVisualEffect: VisualEffect, _VisualEffectChaining {
|
|
16
|
+
var _effects: [(inout _Style) -> Void] { [] }
|
|
17
|
+
public init() {}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public struct _VisualEffectChain: VisualEffect, _VisualEffectChaining {
|
|
21
|
+
var _effects: [(inout _Style) -> Void]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
extension VisualEffect {
|
|
25
|
+
func _adding(_ effect: @escaping (inout _Style) -> Void) -> _VisualEffectChain {
|
|
26
|
+
_VisualEffectChain(_effects: ((self as? _VisualEffectChaining)?._effects ?? []) + [effect])
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public func offset(x: CGFloat = 0, y: CGFloat = 0) -> _VisualEffectChain {
|
|
30
|
+
_adding { $0.extras["offset"] = .object(["x": .number(x), "y": .number(y)]) }
|
|
31
|
+
}
|
|
32
|
+
public func offset(_ offset: CGSize) -> _VisualEffectChain { self.offset(x: offset.width, y: offset.height) }
|
|
33
|
+
public func scaleEffect(_ scale: CGFloat, anchor: UnitPoint = .center) -> _VisualEffectChain {
|
|
34
|
+
scaleEffect(x: scale, y: scale, anchor: anchor)
|
|
35
|
+
}
|
|
36
|
+
public func scaleEffect(_ scale: CGSize, anchor: UnitPoint = .center) -> _VisualEffectChain {
|
|
37
|
+
scaleEffect(x: scale.width, y: scale.height, anchor: anchor)
|
|
38
|
+
}
|
|
39
|
+
public func scaleEffect(x: CGFloat = 1, y: CGFloat = 1, anchor: UnitPoint = .center) -> _VisualEffectChain {
|
|
40
|
+
_adding {
|
|
41
|
+
$0.extras["scale"] = .object(["x": .number(x), "y": .number(y)])
|
|
42
|
+
$0.extras["scaleAnchor"] = anchor._json
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
public func rotationEffect(_ angle: Angle, anchor: UnitPoint = .center) -> _VisualEffectChain {
|
|
46
|
+
_adding {
|
|
47
|
+
$0.extras["rotation"] = .number(angle.degrees)
|
|
48
|
+
$0.extras["rotationAnchor"] = anchor._json
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
public func rotation3DEffect(_ angle: Angle, axis: (x: CGFloat, y: CGFloat, z: CGFloat),
|
|
52
|
+
anchor: UnitPoint = .center, anchorZ: CGFloat = 0,
|
|
53
|
+
perspective: CGFloat = 1) -> _VisualEffectChain {
|
|
54
|
+
_adding {
|
|
55
|
+
$0.extras["rotation3D"] = .object([
|
|
56
|
+
"angle": .number(angle.degrees),
|
|
57
|
+
"axis": .object(["x": .number(axis.x), "y": .number(axis.y), "z": .number(axis.z)]),
|
|
58
|
+
"anchor": anchor._json,
|
|
59
|
+
"perspective": .number(perspective),
|
|
60
|
+
])
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
public func opacity(_ opacity: Double) -> _VisualEffectChain { _adding { $0.opacity = opacity } }
|
|
64
|
+
public func blur(radius: CGFloat, opaque: Bool = false) -> _VisualEffectChain { _adding { $0.extras["blur"] = .number(radius) } }
|
|
65
|
+
/// Accepted; the renderer has no filter for these yet.
|
|
66
|
+
public func brightness(_ amount: Double) -> _VisualEffectChain { _adding { _ in } }
|
|
67
|
+
public func contrast(_ amount: Double) -> _VisualEffectChain { _adding { _ in } }
|
|
68
|
+
public func saturation(_ amount: Double) -> _VisualEffectChain { _adding { _ in } }
|
|
69
|
+
public func grayscale(_ amount: Double) -> _VisualEffectChain { _adding { _ in } }
|
|
70
|
+
public func hueRotation(_ angle: Angle) -> _VisualEffectChain { _adding { _ in } }
|
|
71
|
+
public func colorEffect<S: ShapeStyle>(_ shader: S) -> _VisualEffectChain { _adding { _ in } }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
extension View {
|
|
75
|
+
public func visualEffect(_ effect: @escaping (EmptyVisualEffect, GeometryProxy) -> some VisualEffect) -> some View {
|
|
76
|
+
let result = effect(EmptyVisualEffect(), GeometryProxy(size: .zero))
|
|
77
|
+
let effects = (result as? _VisualEffectChaining)?._effects ?? []
|
|
78
|
+
return _styled { style in
|
|
79
|
+
for apply in effects { apply(&style) }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
// Axes: `chartXAxis { AxisMarks(values:) { value in AxisGridLine(); AxisTick();
|
|
2
|
+
// AxisValueLabel() } }`, `AxisValue`, and the axis builders. The resolved
|
|
3
|
+
// axis JSON is produced in Chart.swift.
|
|
4
|
+
|
|
5
|
+
// MARK: - AxisValue
|
|
6
|
+
|
|
7
|
+
/// A tick's value, handed to the `AxisMarks` content closure.
|
|
8
|
+
public struct AxisValue {
|
|
9
|
+
/// The tick's position among the axis's ticks.
|
|
10
|
+
public let index: Int
|
|
11
|
+
/// How many ticks the axis has.
|
|
12
|
+
public let count: Int
|
|
13
|
+
let value: _PlottableValue
|
|
14
|
+
|
|
15
|
+
/// The value as a plottable type (`value.as(Double.self)`, `.as(Date.self)`).
|
|
16
|
+
public func `as`<T: Plottable>(_ type: T.Type) -> T? {
|
|
17
|
+
T(_plottableValue: value)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// MARK: - Axis marks (grid line, tick, label)
|
|
22
|
+
|
|
23
|
+
/// What an `AxisMarks` closure produces for one tick.
|
|
24
|
+
public protocol AxisMark {
|
|
25
|
+
var _items: [_AxisMarkItem] { get }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public enum _AxisMarkLabel {
|
|
29
|
+
/// The default text for the value.
|
|
30
|
+
case automatic
|
|
31
|
+
case text(String)
|
|
32
|
+
case view(any View)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public enum _AxisMarkItem {
|
|
36
|
+
case gridLine
|
|
37
|
+
case tick
|
|
38
|
+
case label(_AxisMarkLabel)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public struct AxisGridLine: AxisMark {
|
|
42
|
+
public init(centered: Bool? = nil, stroke: StrokeStyle? = nil) {}
|
|
43
|
+
public var _items: [_AxisMarkItem] { [.gridLine] }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public struct AxisTick: AxisMark {
|
|
47
|
+
public init(centered: Bool? = nil, length: CGFloat? = nil, stroke: StrokeStyle? = nil) {}
|
|
48
|
+
public init(centered: Bool? = nil, length: AxisTickLength, stroke: StrokeStyle? = nil) {}
|
|
49
|
+
public var _items: [_AxisMarkItem] { [.tick] }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Accepted for source compatibility; the renderer draws 4 pt ticks.
|
|
53
|
+
public enum AxisTickLength: Equatable, Sendable {
|
|
54
|
+
case automatic
|
|
55
|
+
case label
|
|
56
|
+
case length(CGFloat)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public struct AxisValueLabel: AxisMark {
|
|
60
|
+
let label: _AxisMarkLabel
|
|
61
|
+
|
|
62
|
+
/// The default text for the value.
|
|
63
|
+
public init(centered: Bool? = nil, anchor: UnitPoint? = nil, multiLabelAlignment: Alignment? = nil,
|
|
64
|
+
offsetsMarks: Bool? = nil, horizontalSpacing: CGFloat? = nil, verticalSpacing: CGFloat? = nil) {
|
|
65
|
+
label = .automatic
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public init(_ titleKey: LocalizedStringKey, centered: Bool? = nil, anchor: UnitPoint? = nil,
|
|
69
|
+
multiLabelAlignment: Alignment? = nil, offsetsMarks: Bool? = nil,
|
|
70
|
+
horizontalSpacing: CGFloat? = nil, verticalSpacing: CGFloat? = nil) {
|
|
71
|
+
label = .text(titleKey.key)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@_disfavoredOverload
|
|
75
|
+
public init<S: StringProtocol>(_ title: S, centered: Bool? = nil, anchor: UnitPoint? = nil,
|
|
76
|
+
multiLabelAlignment: Alignment? = nil, offsetsMarks: Bool? = nil,
|
|
77
|
+
horizontalSpacing: CGFloat? = nil, verticalSpacing: CGFloat? = nil) {
|
|
78
|
+
label = .text(String(title))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// A view as the label: becomes a `chartitem` child of the chart.
|
|
82
|
+
public init<C: View>(centered: Bool? = nil, anchor: UnitPoint? = nil, multiLabelAlignment: Alignment? = nil,
|
|
83
|
+
offsetsMarks: Bool? = nil, horizontalSpacing: CGFloat? = nil,
|
|
84
|
+
verticalSpacing: CGFloat? = nil, @ViewBuilder content: () -> C) {
|
|
85
|
+
label = .view(content())
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public var _items: [_AxisMarkItem] { [.label(label)] }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// The marks an `AxisMarks` closure built for one tick.
|
|
92
|
+
public struct _AxisMarkList: AxisMark {
|
|
93
|
+
public let _items: [_AxisMarkItem]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@resultBuilder
|
|
97
|
+
public enum AxisMarkBuilder {
|
|
98
|
+
public static func buildBlock(_ parts: any AxisMark...) -> _AxisMarkList {
|
|
99
|
+
_AxisMarkList(_items: parts.flatMap(\._items))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public static func buildExpression<M: AxisMark>(_ mark: M) -> _AxisMarkList { _AxisMarkList(_items: mark._items) }
|
|
103
|
+
|
|
104
|
+
public static func buildOptional(_ list: _AxisMarkList?) -> _AxisMarkList { list ?? _AxisMarkList(_items: []) }
|
|
105
|
+
|
|
106
|
+
public static func buildEither(first: _AxisMarkList) -> _AxisMarkList { first }
|
|
107
|
+
public static func buildEither(second: _AxisMarkList) -> _AxisMarkList { second }
|
|
108
|
+
|
|
109
|
+
public static func buildArray(_ lists: [_AxisMarkList]) -> _AxisMarkList {
|
|
110
|
+
_AxisMarkList(_items: lists.flatMap(\._items))
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// MARK: - AxisMarks
|
|
115
|
+
|
|
116
|
+
/// Which side of the plot an axis sits on.
|
|
117
|
+
public enum AxisMarkPosition: Hashable, Sendable {
|
|
118
|
+
case automatic, bottom, top, leading, trailing
|
|
119
|
+
|
|
120
|
+
var _name: String? {
|
|
121
|
+
switch self {
|
|
122
|
+
case .automatic: nil
|
|
123
|
+
case .bottom: "bottom"
|
|
124
|
+
case .top: "top"
|
|
125
|
+
case .leading: "leading"
|
|
126
|
+
case .trailing: "trailing"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Accepted for source compatibility; every preset renders the same marks.
|
|
132
|
+
public enum AxisMarkPreset: Hashable, Sendable {
|
|
133
|
+
case automatic, extended, aligned, inset
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// How an axis picks its tick values.
|
|
137
|
+
public struct AxisMarkValues: Equatable {
|
|
138
|
+
enum Spec: Equatable {
|
|
139
|
+
case automatic(_TickOptions)
|
|
140
|
+
case stride(Double, roundLowerBound: Bool, roundUpperBound: Bool)
|
|
141
|
+
case explicit([_PlottableValue])
|
|
142
|
+
}
|
|
143
|
+
let spec: Spec
|
|
144
|
+
|
|
145
|
+
public static var automatic: AxisMarkValues { AxisMarkValues(spec: .automatic(_TickOptions())) }
|
|
146
|
+
|
|
147
|
+
public static func automatic(desiredCount: Int? = nil, roundLowerBound: Bool? = nil,
|
|
148
|
+
roundUpperBound: Bool? = nil) -> AxisMarkValues {
|
|
149
|
+
AxisMarkValues(spec: .automatic(_TickOptions(desiredCount: desiredCount, minimumStride: nil,
|
|
150
|
+
roundLowerBound: roundLowerBound ?? true,
|
|
151
|
+
roundUpperBound: roundUpperBound ?? true)))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public static func automatic(minimumStride: Double, desiredCount: Int? = nil, roundLowerBound: Bool? = nil,
|
|
155
|
+
roundUpperBound: Bool? = nil) -> AxisMarkValues {
|
|
156
|
+
AxisMarkValues(spec: .automatic(_TickOptions(desiredCount: desiredCount, minimumStride: minimumStride,
|
|
157
|
+
roundLowerBound: roundLowerBound ?? true,
|
|
158
|
+
roundUpperBound: roundUpperBound ?? true)))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/// Ticks every `stride` along a numeric axis.
|
|
162
|
+
public static func stride(by stride: Double, roundLowerBound: Bool? = nil,
|
|
163
|
+
roundUpperBound: Bool? = nil) -> AxisMarkValues {
|
|
164
|
+
AxisMarkValues(spec: .stride(stride, roundLowerBound: roundLowerBound ?? true,
|
|
165
|
+
roundUpperBound: roundUpperBound ?? true))
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// The marks of one axis: ticks at `values`, each drawn as `content` says
|
|
170
|
+
/// (grid line, tick and label by default).
|
|
171
|
+
public struct AxisMarks: AxisContent {
|
|
172
|
+
let position: AxisMarkPosition
|
|
173
|
+
let values: AxisMarkValues
|
|
174
|
+
let content: ((AxisValue) -> _AxisMarkList)?
|
|
175
|
+
|
|
176
|
+
public init(preset: AxisMarkPreset = .automatic, position: AxisMarkPosition = .automatic,
|
|
177
|
+
values: AxisMarkValues = .automatic, stroke: StrokeStyle? = nil) {
|
|
178
|
+
self.position = position
|
|
179
|
+
self.values = values
|
|
180
|
+
content = nil
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
public init(preset: AxisMarkPreset = .automatic, position: AxisMarkPosition = .automatic,
|
|
184
|
+
values: AxisMarkValues = .automatic,
|
|
185
|
+
@AxisMarkBuilder content: @escaping (AxisValue) -> _AxisMarkList) {
|
|
186
|
+
self.position = position
|
|
187
|
+
self.values = values
|
|
188
|
+
self.content = content
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// Ticks exactly at `values`.
|
|
192
|
+
public init<T: Plottable>(preset: AxisMarkPreset = .automatic, position: AxisMarkPosition = .automatic,
|
|
193
|
+
values: [T], stroke: StrokeStyle? = nil) {
|
|
194
|
+
self.position = position
|
|
195
|
+
self.values = AxisMarkValues(spec: .explicit(values.map(\._plottableValue)))
|
|
196
|
+
content = nil
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public init<T: Plottable>(preset: AxisMarkPreset = .automatic, position: AxisMarkPosition = .automatic,
|
|
200
|
+
values: [T], @AxisMarkBuilder content: @escaping (AxisValue) -> _AxisMarkList) {
|
|
201
|
+
self.position = position
|
|
202
|
+
self.values = AxisMarkValues(spec: .explicit(values.map(\._plottableValue)))
|
|
203
|
+
self.content = content
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
public var _axisMarks: [AxisMarks] { [self] }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// MARK: - AxisContent
|
|
210
|
+
|
|
211
|
+
/// The content of `chartXAxis { }` / `chartYAxis { }`: one or more `AxisMarks`.
|
|
212
|
+
public protocol AxisContent {
|
|
213
|
+
var _axisMarks: [AxisMarks] { get }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public struct _AxisContentList: AxisContent {
|
|
217
|
+
public let _axisMarks: [AxisMarks]
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@resultBuilder
|
|
221
|
+
public enum AxisContentBuilder {
|
|
222
|
+
public static func buildBlock(_ parts: any AxisContent...) -> _AxisContentList {
|
|
223
|
+
_AxisContentList(_axisMarks: parts.flatMap(\._axisMarks))
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
public static func buildExpression<C: AxisContent>(_ content: C) -> _AxisContentList {
|
|
227
|
+
_AxisContentList(_axisMarks: content._axisMarks)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public static func buildOptional(_ list: _AxisContentList?) -> _AxisContentList {
|
|
231
|
+
list ?? _AxisContentList(_axisMarks: [])
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
public static func buildEither(first: _AxisContentList) -> _AxisContentList { first }
|
|
235
|
+
public static func buildEither(second: _AxisContentList) -> _AxisContentList { second }
|
|
236
|
+
|
|
237
|
+
public static func buildArray(_ lists: [_AxisContentList]) -> _AxisContentList {
|
|
238
|
+
_AxisContentList(_axisMarks: lists.flatMap(\._axisMarks))
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// MARK: - Chart modifiers (environment)
|
|
243
|
+
|
|
244
|
+
/// An axis as configured by `chartXAxis` / `chartYAxis`.
|
|
245
|
+
struct _AxisSpec {
|
|
246
|
+
var hidden = false
|
|
247
|
+
/// Empty: the default `AxisMarks()`.
|
|
248
|
+
var marks: [AxisMarks] = []
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/// Everything the `chart...` view modifiers set, read by the nearest `Chart`.
|
|
252
|
+
struct _ChartConfig {
|
|
253
|
+
var xAxis = _AxisSpec()
|
|
254
|
+
var yAxis = _AxisSpec()
|
|
255
|
+
var legendHidden = false
|
|
256
|
+
var legendPosition = AnnotationPosition.automatic
|
|
257
|
+
var xDomain = _ScaleDomainSpec.automatic(includesZero: nil, reversed: false)
|
|
258
|
+
var yDomain = _ScaleDomainSpec.automatic(includesZero: nil, reversed: false)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
enum _ChartConfigKey: EnvironmentKey {
|
|
262
|
+
static var defaultValue: _ChartConfig { _ChartConfig() }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
extension EnvironmentValues {
|
|
266
|
+
var _chartConfig: _ChartConfig {
|
|
267
|
+
get { self[_ChartConfigKey.self] }
|
|
268
|
+
set { self[_ChartConfigKey.self] = newValue }
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
extension View {
|
|
273
|
+
func _chartConfig(_ configure: @escaping (inout _ChartConfig) -> Void) -> some View {
|
|
274
|
+
_EnvironmentView(content: self) { configure(&$0._chartConfig) }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
public func chartXAxis<C: AxisContent>(@AxisContentBuilder content: () -> C) -> some View {
|
|
278
|
+
let marks = content()._axisMarks
|
|
279
|
+
return _chartConfig { $0.xAxis = _AxisSpec(hidden: false, marks: marks) }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
public func chartYAxis<C: AxisContent>(@AxisContentBuilder content: () -> C) -> some View {
|
|
283
|
+
let marks = content()._axisMarks
|
|
284
|
+
return _chartConfig { $0.yAxis = _AxisSpec(hidden: false, marks: marks) }
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
public func chartXAxis(_ visibility: Visibility) -> some View {
|
|
288
|
+
_chartConfig { $0.xAxis.hidden = visibility == .hidden }
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
public func chartYAxis(_ visibility: Visibility) -> some View {
|
|
292
|
+
_chartConfig { $0.yAxis.hidden = visibility == .hidden }
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
public func chartXScale<D: ScaleDomain>(domain: D) -> some View {
|
|
296
|
+
let spec = domain._domainSpec
|
|
297
|
+
return _chartConfig { $0.xDomain = spec }
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
public func chartYScale<D: ScaleDomain>(domain: D) -> some View {
|
|
301
|
+
let spec = domain._domainSpec
|
|
302
|
+
return _chartConfig { $0.yDomain = spec }
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
public func chartLegend(_ visibility: Visibility) -> some View {
|
|
306
|
+
_chartConfig { $0.legendHidden = visibility == .hidden }
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
public func chartLegend(position: AnnotationPosition = .automatic, alignment: Alignment? = nil,
|
|
310
|
+
spacing: CGFloat? = nil) -> some View {
|
|
311
|
+
_chartConfig { $0.legendHidden = false; $0.legendPosition = position }
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/// A custom legend view is not rendered; the position still applies.
|
|
315
|
+
public func chartLegend<C: View>(position: AnnotationPosition = .automatic, alignment: Alignment? = nil,
|
|
316
|
+
spacing: CGFloat? = nil, @ViewBuilder content: () -> C) -> some View {
|
|
317
|
+
_chartConfig { $0.legendHidden = false; $0.legendPosition = position }
|
|
318
|
+
}
|
|
319
|
+
}
|