@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,243 @@
|
|
|
1
|
+
/// How `aspectRatio` fits content into the proposed space.
|
|
2
|
+
public enum ContentMode: Hashable, Sendable {
|
|
3
|
+
case fit, fill
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/// The style a `styled` element applies to the box around its child.
|
|
7
|
+
/// See docs/ops-protocol.md.
|
|
8
|
+
struct _Style: Equatable {
|
|
9
|
+
struct Frame: Equatable {
|
|
10
|
+
var width: CGFloat?
|
|
11
|
+
var height: CGFloat?
|
|
12
|
+
var minWidth: CGFloat?
|
|
13
|
+
var maxWidth: CGFloat?
|
|
14
|
+
var minHeight: CGFloat?
|
|
15
|
+
var maxHeight: CGFloat?
|
|
16
|
+
var alignment: Alignment = .center
|
|
17
|
+
|
|
18
|
+
var _json: _JSON {
|
|
19
|
+
func dimension(_ value: CGFloat?) -> _JSON? {
|
|
20
|
+
guard let value else { return nil }
|
|
21
|
+
return value == .infinity ? .string("infinity") : .number(value)
|
|
22
|
+
}
|
|
23
|
+
var object: [String: _JSON] = ["alignment": .string(alignment._name)]
|
|
24
|
+
if let v = dimension(width) { object["width"] = v }
|
|
25
|
+
if let v = dimension(height) { object["height"] = v }
|
|
26
|
+
if let v = dimension(minWidth) { object["minWidth"] = v }
|
|
27
|
+
if let v = dimension(maxWidth) { object["maxWidth"] = v }
|
|
28
|
+
if let v = dimension(minHeight) { object["minHeight"] = v }
|
|
29
|
+
if let v = dimension(maxHeight) { object["maxHeight"] = v }
|
|
30
|
+
return .object(object)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var padding: EdgeInsets?
|
|
35
|
+
var font: Font?
|
|
36
|
+
var foreground: (any ShapeStyle)?
|
|
37
|
+
var background: (any ShapeStyle)?
|
|
38
|
+
var frame: Frame?
|
|
39
|
+
var cornerRadius: CGFloat?
|
|
40
|
+
var opacity: Double?
|
|
41
|
+
var disabled: Bool?
|
|
42
|
+
var layoutPriority: Double?
|
|
43
|
+
/// `-1` clears an inherited limit (`lineLimit(nil)`).
|
|
44
|
+
var lineLimit: Int?
|
|
45
|
+
var multilineTextAlignment: TextAlignment?
|
|
46
|
+
var fixedSize: (horizontal: Bool, vertical: Bool)?
|
|
47
|
+
var transition: AnyTransition?
|
|
48
|
+
/// `.aspectRatio` / `.scaledToFit` / `.scaledToFill`; a nil ratio means the
|
|
49
|
+
/// child's ideal size decides.
|
|
50
|
+
var aspectRatio: (ratio: CGFloat?, contentMode: ContentMode)?
|
|
51
|
+
/// Hints with no layout logic in Swift (`offset`, `rotation`, `shadow`,
|
|
52
|
+
/// `badge`, `tint`, ...), passed through to the renderer by key.
|
|
53
|
+
var extras: [String: _JSON] = [:]
|
|
54
|
+
|
|
55
|
+
static func == (lhs: _Style, rhs: _Style) -> Bool { lhs._json == rhs._json }
|
|
56
|
+
|
|
57
|
+
var _json: _JSON {
|
|
58
|
+
var object: [String: _JSON] = [:]
|
|
59
|
+
if let padding { object["padding"] = padding._json }
|
|
60
|
+
if let font { object["font"] = font._json }
|
|
61
|
+
if let foreground { object["foreground"] = foreground._json }
|
|
62
|
+
if let background { object["background"] = background._json }
|
|
63
|
+
if let frame { object["frame"] = frame._json }
|
|
64
|
+
if let cornerRadius { object["cornerRadius"] = .number(cornerRadius) }
|
|
65
|
+
if let opacity { object["opacity"] = .number(opacity) }
|
|
66
|
+
if let disabled { object["disabled"] = .bool(disabled) }
|
|
67
|
+
if let layoutPriority { object["layoutPriority"] = .number(layoutPriority) }
|
|
68
|
+
if let lineLimit { object["lineLimit"] = lineLimit < 0 ? .null : .number(Double(lineLimit)) }
|
|
69
|
+
if let multilineTextAlignment { object["multilineTextAlignment"] = .string(multilineTextAlignment.name) }
|
|
70
|
+
if let fixedSize {
|
|
71
|
+
object["fixedSize"] = .object(["horizontal": .bool(fixedSize.horizontal), "vertical": .bool(fixedSize.vertical)])
|
|
72
|
+
}
|
|
73
|
+
if let transition { object["transition"] = transition._json }
|
|
74
|
+
if let aspectRatio {
|
|
75
|
+
object["aspectRatio"] = .object([
|
|
76
|
+
"ratio": aspectRatio.ratio.map(_JSON.number) ?? .null,
|
|
77
|
+
"contentMode": .string(aspectRatio.contentMode == .fit ? "fit" : "fill"),
|
|
78
|
+
])
|
|
79
|
+
}
|
|
80
|
+
for (key, value) in extras { object[key] = value }
|
|
81
|
+
return .object(object)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// The view a modifier produces. Nesting preserves modifier order.
|
|
86
|
+
public struct _StyledView<Content: View> {
|
|
87
|
+
let content: Content
|
|
88
|
+
let style: _Style
|
|
89
|
+
}
|
|
90
|
+
extension _StyledView: _PrimitiveView {}
|
|
91
|
+
extension _StyledView: _HostView {
|
|
92
|
+
var _kind: String { "styled" }
|
|
93
|
+
func _props(in environment: EnvironmentValues) -> _JSON { .object(["style": style._json]) }
|
|
94
|
+
var _content: [any View] { [content] }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// A modifier applied to a group (`ForEach`, `Group`, a tuple, a conditional)
|
|
98
|
+
/// styles each of the group's views, as in SwiftUI, instead of wrapping the
|
|
99
|
+
/// group in one box. Single views still get one `styled` element.
|
|
100
|
+
public struct _StyledGroup<Content: View> {
|
|
101
|
+
let content: Content
|
|
102
|
+
let style: _Style
|
|
103
|
+
}
|
|
104
|
+
extension _StyledGroup: _PrimitiveView {}
|
|
105
|
+
extension _StyledGroup: _GroupView {
|
|
106
|
+
var _children: [any View] {
|
|
107
|
+
if let group = content as? any _GroupView, group._children.count != 1 || group._keys != nil {
|
|
108
|
+
return group._children.map { _StyledView(content: AnyView($0), style: style) }
|
|
109
|
+
}
|
|
110
|
+
return [_StyledView(content: content, style: style)]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
var _keys: [AnyHashable]? {
|
|
114
|
+
if let group = content as? any _GroupView, group._children.count != 1 || group._keys != nil {
|
|
115
|
+
return group._keys
|
|
116
|
+
}
|
|
117
|
+
return nil
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
extension View {
|
|
122
|
+
func _styled(_ configure: (inout _Style) -> Void) -> _StyledGroup<Self> {
|
|
123
|
+
var style = _Style()
|
|
124
|
+
configure(&style)
|
|
125
|
+
return _StyledGroup(content: self, style: style)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// MARK: Padding
|
|
129
|
+
|
|
130
|
+
public func padding(_ edges: Edge.Set = .all, _ length: CGFloat? = nil) -> some View {
|
|
131
|
+
let amount = length ?? 16
|
|
132
|
+
return _styled {
|
|
133
|
+
$0.padding = EdgeInsets(
|
|
134
|
+
top: edges.contains(.top) ? amount : 0,
|
|
135
|
+
leading: edges.contains(.leading) ? amount : 0,
|
|
136
|
+
bottom: edges.contains(.bottom) ? amount : 0,
|
|
137
|
+
trailing: edges.contains(.trailing) ? amount : 0
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public func padding(_ length: CGFloat) -> some View {
|
|
143
|
+
padding(.all, length)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public func padding(_ insets: EdgeInsets) -> some View {
|
|
147
|
+
_styled { $0.padding = insets }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// MARK: Typography
|
|
151
|
+
|
|
152
|
+
public func font(_ font: Font?) -> some View {
|
|
153
|
+
_styled { $0.font = font }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public func fontWeight(_ weight: Font.Weight?) -> some View {
|
|
157
|
+
_styled { $0.font = weight.map { Font(size: nil, weight: $0, design: nil) } }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public func bold(_ isActive: Bool = true) -> some View {
|
|
161
|
+
_styled { $0.font = isActive ? Font(size: nil, weight: .bold, design: nil) : nil }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// MARK: Color
|
|
165
|
+
|
|
166
|
+
public func foregroundStyle<S: ShapeStyle>(_ style: S) -> some View {
|
|
167
|
+
_styled { $0.foreground = style }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public func foregroundColor(_ color: Color?) -> some View {
|
|
171
|
+
_styled { $0.foreground = color }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// Hierarchical foreground styles: the primary style paints everything.
|
|
175
|
+
public func foregroundStyle<S1: ShapeStyle, S2: ShapeStyle>(_ primary: S1, _ secondary: S2) -> some View {
|
|
176
|
+
_styled { $0.foreground = primary }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public func foregroundStyle<S1: ShapeStyle, S2: ShapeStyle, S3: ShapeStyle>(_ primary: S1, _ secondary: S2,
|
|
180
|
+
_ tertiary: S3) -> some View {
|
|
181
|
+
_styled { $0.foreground = primary }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// A paint behind the view: a color, gradient or material filling its box.
|
|
185
|
+
public func background<S: ShapeStyle>(_ style: S, ignoresSafeAreaEdges edges: Edge.Set = .all) -> some View {
|
|
186
|
+
_styled { $0.background = style }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// The system background color behind the view.
|
|
190
|
+
public func background(ignoresSafeAreaEdges edges: Edge.Set = .all) -> some View {
|
|
191
|
+
_styled { $0.background = BackgroundStyle() }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
public func opacity(_ opacity: Double) -> some View {
|
|
195
|
+
_styled { $0.opacity = opacity }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// MARK: Layout
|
|
199
|
+
|
|
200
|
+
public func frame(width: CGFloat? = nil, height: CGFloat? = nil, alignment: Alignment = .center) -> some View {
|
|
201
|
+
_styled { $0.frame = _Style.Frame(width: width, height: height, alignment: alignment) }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
public func frame(minWidth: CGFloat? = nil, idealWidth: CGFloat? = nil, maxWidth: CGFloat? = nil,
|
|
205
|
+
minHeight: CGFloat? = nil, idealHeight: CGFloat? = nil, maxHeight: CGFloat? = nil,
|
|
206
|
+
alignment: Alignment = .center) -> some View {
|
|
207
|
+
_styled {
|
|
208
|
+
$0.frame = _Style.Frame(width: idealWidth, height: idealHeight,
|
|
209
|
+
minWidth: minWidth, maxWidth: maxWidth,
|
|
210
|
+
minHeight: minHeight, maxHeight: maxHeight,
|
|
211
|
+
alignment: alignment)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/// Constrains the view to an aspect ratio: `fit` is the largest such box
|
|
216
|
+
/// inside the proposal, `fill` the smallest covering it. A nil ratio uses
|
|
217
|
+
/// the child's ideal size (what `Image.resizable().scaledToFit()` wants).
|
|
218
|
+
public func aspectRatio(_ aspectRatio: CGFloat? = nil, contentMode: ContentMode) -> some View {
|
|
219
|
+
_styled { $0.aspectRatio = (aspectRatio, contentMode) }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
public func aspectRatio(_ aspectRatio: CGSize, contentMode: ContentMode) -> some View {
|
|
223
|
+
_styled { $0.aspectRatio = (aspectRatio.height == 0 ? nil : aspectRatio.width / aspectRatio.height, contentMode) }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
public func scaledToFit() -> some View { aspectRatio(nil, contentMode: .fit) }
|
|
227
|
+
|
|
228
|
+
public func scaledToFill() -> some View { aspectRatio(nil, contentMode: .fill) }
|
|
229
|
+
|
|
230
|
+
/// Identity and compositing hints; accepted for source compatibility and
|
|
231
|
+
/// currently without effect on the rendered tree.
|
|
232
|
+
public func id<ID: Hashable>(_ id: ID) -> some View { self }
|
|
233
|
+
|
|
234
|
+
public func compositingGroup() -> some View { self }
|
|
235
|
+
|
|
236
|
+
public func cornerRadius(_ radius: CGFloat, antialiased: Bool = true) -> some View {
|
|
237
|
+
_styled { $0.cornerRadius = radius }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public func clipShape<S: Shape>(_ shape: S) -> some View {
|
|
241
|
+
_styled { $0.cornerRadius = shape._cornerRadius }
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/// A screen pushed onto a `NavigationStack`.
|
|
2
|
+
struct _PushedScreen {
|
|
3
|
+
let view: any View
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/// A type-erased list of values pushed onto a navigation stack.
|
|
7
|
+
public struct NavigationPath: Equatable {
|
|
8
|
+
var values: [AnyHashable] = []
|
|
9
|
+
|
|
10
|
+
public init() {}
|
|
11
|
+
|
|
12
|
+
public init<S: Sequence>(_ elements: S) where S.Element: Hashable {
|
|
13
|
+
values = elements.map { AnyHashable($0) }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public var count: Int { values.count }
|
|
17
|
+
public var isEmpty: Bool { values.isEmpty }
|
|
18
|
+
|
|
19
|
+
public mutating func append<V: Hashable>(_ value: V) { values.append(AnyHashable(value)) }
|
|
20
|
+
|
|
21
|
+
public mutating func removeLast(_ k: Int = 1) {
|
|
22
|
+
values.removeLast(min(k, values.count))
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
protocol _NavigationStackView {
|
|
27
|
+
var _root: any View { get }
|
|
28
|
+
var _pushedBox: _StateBox<[_PushedScreen]> { get }
|
|
29
|
+
/// Value-based path, when the stack was created with one.
|
|
30
|
+
var _path: _PathBinding? { get }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Reads and writes a navigation path of any element type as `AnyHashable`s.
|
|
34
|
+
struct _PathBinding {
|
|
35
|
+
let get: () -> [AnyHashable]
|
|
36
|
+
let set: ([AnyHashable]) -> Void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// A view that displays a root view and lets you push views onto it.
|
|
40
|
+
public struct NavigationStack<Data, Content: View> {
|
|
41
|
+
let root: Content
|
|
42
|
+
let path: _PathBinding?
|
|
43
|
+
@State var pushed: [_PushedScreen] = []
|
|
44
|
+
|
|
45
|
+
public init(@ViewBuilder root: () -> Content) where Data == NavigationPath {
|
|
46
|
+
self.root = root()
|
|
47
|
+
self.path = nil
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public init(path: Binding<NavigationPath>, @ViewBuilder root: () -> Content) where Data == NavigationPath {
|
|
51
|
+
self.root = root()
|
|
52
|
+
self.path = _PathBinding(
|
|
53
|
+
get: { path.wrappedValue.values },
|
|
54
|
+
set: { path.wrappedValue.values = $0 }
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public init<Element: Hashable>(path: Binding<[Element]>, @ViewBuilder root: () -> Content)
|
|
59
|
+
where Data == [Element] {
|
|
60
|
+
self.root = root()
|
|
61
|
+
self.path = _PathBinding(
|
|
62
|
+
get: { path.wrappedValue.map { AnyHashable($0) } },
|
|
63
|
+
set: { path.wrappedValue = $0.compactMap { $0.base as? Element } }
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
extension NavigationStack: _PrimitiveView {}
|
|
68
|
+
extension NavigationStack: _HostView {
|
|
69
|
+
var _kind: String { "navstack" }
|
|
70
|
+
func _props(in environment: EnvironmentValues) -> _JSON { .object([:]) }
|
|
71
|
+
// Children are produced by the runtime from `pushed`.
|
|
72
|
+
}
|
|
73
|
+
extension NavigationStack: _NavigationStackView {
|
|
74
|
+
var _root: any View { root }
|
|
75
|
+
var _pushedBox: _StateBox<[_PushedScreen]> { _pushed.box }
|
|
76
|
+
var _path: _PathBinding? { path }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// A pushed screen whose view is resolved from a value through the enclosing
|
|
80
|
+
/// stack's `navigationDestination(for:)` registrations.
|
|
81
|
+
struct _PathScreenValue {
|
|
82
|
+
let value: AnyHashable
|
|
83
|
+
}
|
|
84
|
+
extension _PathScreenValue: _PrimitiveView {}
|
|
85
|
+
extension _PathScreenValue: _GroupView {
|
|
86
|
+
// Resolved by the runtime through the stack's registrations.
|
|
87
|
+
var _children: [any View] { [] }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
protocol _DestinationRegistering {
|
|
91
|
+
var _destinationType: ObjectIdentifier { get }
|
|
92
|
+
var _destinationTypeName: String { get }
|
|
93
|
+
func _destination(for value: Any) -> (any View)?
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
struct _DestinationView<Content: View, Value: Hashable, Destination: View> {
|
|
97
|
+
let content: Content
|
|
98
|
+
let builder: (Value) -> Destination
|
|
99
|
+
}
|
|
100
|
+
extension _DestinationView: _PrimitiveView {}
|
|
101
|
+
extension _DestinationView: _GroupView {
|
|
102
|
+
var _children: [any View] { [content] }
|
|
103
|
+
}
|
|
104
|
+
extension _DestinationView: _DestinationRegistering {
|
|
105
|
+
var _destinationType: ObjectIdentifier { ObjectIdentifier(Value.self) }
|
|
106
|
+
var _destinationTypeName: String { String(describing: Value.self) }
|
|
107
|
+
func _destination(for value: Any) -> (any View)? {
|
|
108
|
+
guard let value = value as? Value else { return nil }
|
|
109
|
+
return builder(value)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
extension View {
|
|
114
|
+
/// Associates a destination view with values of `data`'s type pushed by
|
|
115
|
+
/// `NavigationLink(value:)` or appended to the stack's path.
|
|
116
|
+
public func navigationDestination<D: Hashable, C: View>(for data: D.Type,
|
|
117
|
+
@ViewBuilder destination: @escaping (D) -> C) -> some View {
|
|
118
|
+
_DestinationView(content: self, builder: destination)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
protocol _NavigationValueLinkView {
|
|
123
|
+
var _value: AnyHashable? { get }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
extension NavigationLink where Destination == Never {
|
|
127
|
+
/// A link that pushes `value` onto the enclosing stack's path.
|
|
128
|
+
public init<P: Hashable>(value: P?, @ViewBuilder label: () -> Label) {
|
|
129
|
+
self.destination = nil
|
|
130
|
+
self.value = value.map { AnyHashable($0) }
|
|
131
|
+
self.label = label()
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
extension NavigationLink where Label == Text, Destination == Never {
|
|
136
|
+
public init<S: StringProtocol, P: Hashable>(_ title: S, value: P?) {
|
|
137
|
+
self.destination = nil
|
|
138
|
+
self.value = value.map { AnyHashable($0) }
|
|
139
|
+
self.label = Text(title)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
extension NavigationLink: _NavigationValueLinkView {
|
|
144
|
+
var _value: AnyHashable? { value }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// One screen of a navigation stack. Created by the runtime, never by apps.
|
|
148
|
+
struct _NavScreenView {
|
|
149
|
+
let content: any View
|
|
150
|
+
let depth: Int
|
|
151
|
+
}
|
|
152
|
+
extension _NavScreenView: _PrimitiveView {}
|
|
153
|
+
extension _NavScreenView: _HostView {
|
|
154
|
+
var _kind: String { "navscreen" }
|
|
155
|
+
func _props(in environment: EnvironmentValues) -> _JSON { .object(["depth": .number(Double(depth))]) }
|
|
156
|
+
var _content: [any View] { [content] }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
protocol _NavigationLinkView {
|
|
160
|
+
var _destination: any View { get }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// A control that pushes its destination onto the enclosing navigation stack.
|
|
164
|
+
public struct NavigationLink<Label: View, Destination: View> {
|
|
165
|
+
let destination: Destination?
|
|
166
|
+
let value: AnyHashable?
|
|
167
|
+
let label: Label
|
|
168
|
+
|
|
169
|
+
public init(@ViewBuilder destination: () -> Destination, @ViewBuilder label: () -> Label) {
|
|
170
|
+
self.destination = destination()
|
|
171
|
+
self.value = nil
|
|
172
|
+
self.label = label()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public init(destination: Destination, @ViewBuilder label: () -> Label) {
|
|
176
|
+
self.destination = destination
|
|
177
|
+
self.value = nil
|
|
178
|
+
self.label = label()
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
extension NavigationLink where Label == Text {
|
|
183
|
+
public init<S: StringProtocol>(_ title: S, @ViewBuilder destination: () -> Destination) {
|
|
184
|
+
self.destination = destination()
|
|
185
|
+
self.value = nil
|
|
186
|
+
self.label = Text(title)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public init<S: StringProtocol>(_ title: S, destination: Destination) {
|
|
190
|
+
self.destination = destination
|
|
191
|
+
self.value = nil
|
|
192
|
+
self.label = Text(title)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
extension NavigationLink: _PrimitiveView {}
|
|
196
|
+
extension NavigationLink: _HostView {
|
|
197
|
+
var _kind: String { "navlink" }
|
|
198
|
+
/// `selected` marks the row of a `List(selection:)` whose value is the
|
|
199
|
+
/// current selection (see docs/ops-protocol.md, Phase 7); omitted otherwise.
|
|
200
|
+
func _props(in environment: EnvironmentValues) -> _JSON {
|
|
201
|
+
if let value, let selection = environment._listSelection, selection.get() == value {
|
|
202
|
+
return .object(["selected": .bool(true)])
|
|
203
|
+
}
|
|
204
|
+
return .object([:])
|
|
205
|
+
}
|
|
206
|
+
var _content: [any View] { [label] }
|
|
207
|
+
// The handler is supplied by the runtime, which knows the enclosing stack.
|
|
208
|
+
}
|
|
209
|
+
extension NavigationLink: _NavigationLinkView {
|
|
210
|
+
var _destination: any View { destination ?? EmptyView() }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// MARK: - NavigationSplitView
|
|
214
|
+
|
|
215
|
+
/// Which columns of a `NavigationSplitView` are shown. Accepted for source
|
|
216
|
+
/// compatibility; the shim shows the sidebar and the detail (see below).
|
|
217
|
+
public struct NavigationSplitViewVisibility: Hashable, Sendable {
|
|
218
|
+
let name: String
|
|
219
|
+
public static let automatic = NavigationSplitViewVisibility(name: "automatic")
|
|
220
|
+
public static let all = NavigationSplitViewVisibility(name: "all")
|
|
221
|
+
public static let doubleColumn = NavigationSplitViewVisibility(name: "doubleColumn")
|
|
222
|
+
public static let detailOnly = NavigationSplitViewVisibility(name: "detailOnly")
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/// `.navigationSplitViewStyle`: accepted for source compatibility.
|
|
226
|
+
public protocol NavigationSplitViewStyle {}
|
|
227
|
+
public struct AutomaticNavigationSplitViewStyle: NavigationSplitViewStyle { public init() {} }
|
|
228
|
+
public struct BalancedNavigationSplitViewStyle: NavigationSplitViewStyle { public init() {} }
|
|
229
|
+
public struct ProminentDetailNavigationSplitViewStyle: NavigationSplitViewStyle { public init() {} }
|
|
230
|
+
extension NavigationSplitViewStyle where Self == AutomaticNavigationSplitViewStyle {
|
|
231
|
+
public static var automatic: AutomaticNavigationSplitViewStyle { AutomaticNavigationSplitViewStyle() }
|
|
232
|
+
}
|
|
233
|
+
extension NavigationSplitViewStyle where Self == BalancedNavigationSplitViewStyle {
|
|
234
|
+
public static var balanced: BalancedNavigationSplitViewStyle { BalancedNavigationSplitViewStyle() }
|
|
235
|
+
}
|
|
236
|
+
extension NavigationSplitViewStyle where Self == ProminentDetailNavigationSplitViewStyle {
|
|
237
|
+
public static var prominentDetail: ProminentDetailNavigationSplitViewStyle { ProminentDetailNavigationSplitViewStyle() }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
extension View {
|
|
241
|
+
public func navigationSplitViewColumnWidth(_ width: CGFloat) -> some View { self }
|
|
242
|
+
public func navigationSplitViewColumnWidth(min: CGFloat? = nil, ideal: CGFloat, max: CGFloat? = nil) -> some View { self }
|
|
243
|
+
public func navigationSplitViewStyle<S: NavigationSplitViewStyle>(_ style: S) -> some View { self }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// The runtime's view of a split view (see Runtime.swift): by size class it
|
|
247
|
+
/// renders a `navsplit` of two stacks (regular) or one stack rooted at the
|
|
248
|
+
/// sidebar that pushes the detail while the sidebar's list selection is set
|
|
249
|
+
/// (compact). The sidebar's `List(selection:)` is found as it is reconciled.
|
|
250
|
+
protocol _NavigationSplitHost {
|
|
251
|
+
var _sidebar: any View { get }
|
|
252
|
+
var _detail: any View { get }
|
|
253
|
+
/// Screens pushed from the compact detail (links inside it).
|
|
254
|
+
var _pushedBox: _StateBox<[_PushedScreen]> { get }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/// A two-column navigation interface: a sidebar whose list selection drives
|
|
258
|
+
/// the detail column. The optional middle `content` column is treated as part
|
|
259
|
+
/// of the detail and not shown.
|
|
260
|
+
public struct NavigationSplitView<Sidebar: View, Content: View, Detail: View> {
|
|
261
|
+
let sidebar: Sidebar
|
|
262
|
+
let detail: Detail
|
|
263
|
+
@State var pushed: [_PushedScreen] = []
|
|
264
|
+
|
|
265
|
+
public init(@ViewBuilder sidebar: () -> Sidebar, @ViewBuilder detail: () -> Detail) where Content == EmptyView {
|
|
266
|
+
self.sidebar = sidebar()
|
|
267
|
+
self.detail = detail()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
public init(@ViewBuilder sidebar: () -> Sidebar, @ViewBuilder content: () -> Content,
|
|
271
|
+
@ViewBuilder detail: () -> Detail) {
|
|
272
|
+
self.sidebar = sidebar()
|
|
273
|
+
_ = content()
|
|
274
|
+
self.detail = detail()
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/// The visibility binding is accepted and ignored.
|
|
278
|
+
public init(columnVisibility: Binding<NavigationSplitViewVisibility>,
|
|
279
|
+
@ViewBuilder sidebar: () -> Sidebar, @ViewBuilder detail: () -> Detail) where Content == EmptyView {
|
|
280
|
+
self.sidebar = sidebar()
|
|
281
|
+
self.detail = detail()
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
public init(columnVisibility: Binding<NavigationSplitViewVisibility>,
|
|
285
|
+
@ViewBuilder sidebar: () -> Sidebar, @ViewBuilder content: () -> Content,
|
|
286
|
+
@ViewBuilder detail: () -> Detail) {
|
|
287
|
+
self.sidebar = sidebar()
|
|
288
|
+
_ = content()
|
|
289
|
+
self.detail = detail()
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
extension NavigationSplitView: _PrimitiveView {}
|
|
293
|
+
extension NavigationSplitView: _HostView {
|
|
294
|
+
var _kind: String { "navsplit" }
|
|
295
|
+
func _props(in environment: EnvironmentValues) -> _JSON { .object([:]) }
|
|
296
|
+
// Children are produced by the runtime from the size class.
|
|
297
|
+
}
|
|
298
|
+
extension NavigationSplitView: _NavigationSplitHost {
|
|
299
|
+
var _sidebar: any View { sidebar }
|
|
300
|
+
var _detail: any View { detail }
|
|
301
|
+
var _pushedBox: _StateBox<[_PushedScreen]> { _pushed.box }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/// Marks the sidebar subtree of a split view: value links there select
|
|
305
|
+
/// instead of pushing, and the sidebar's `List(selection:)` registers its
|
|
306
|
+
/// binding with the split view's node (`split`) as it is reconciled.
|
|
307
|
+
struct _SplitSidebarView {
|
|
308
|
+
let content: any View
|
|
309
|
+
unowned let split: _MountedNode
|
|
310
|
+
}
|
|
311
|
+
extension _SplitSidebarView: _PrimitiveView {}
|
|
312
|
+
extension _SplitSidebarView: _GroupView {
|
|
313
|
+
var _children: [any View] { [content] }
|
|
314
|
+
}
|
|
315
|
+
extension _SplitSidebarView: _EnvironmentModifying {
|
|
316
|
+
func _apply(to environment: inout EnvironmentValues) { environment._splitSidebar = true }
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// MARK: - Titles
|
|
320
|
+
|
|
321
|
+
public enum NavigationBarItem {
|
|
322
|
+
public enum TitleDisplayMode: Hashable, Sendable {
|
|
323
|
+
case automatic, inline, large
|
|
324
|
+
|
|
325
|
+
var name: String {
|
|
326
|
+
switch self {
|
|
327
|
+
case .automatic: "automatic"
|
|
328
|
+
case .inline: "inline"
|
|
329
|
+
case .large: "large"
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
protocol _NavigationTitleModifying {
|
|
336
|
+
var _title: String? { get }
|
|
337
|
+
var _displayMode: String? { get }
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
struct _NavigationTitleView<Content: View> {
|
|
341
|
+
let content: Content
|
|
342
|
+
let title: String?
|
|
343
|
+
let displayMode: String?
|
|
344
|
+
}
|
|
345
|
+
extension _NavigationTitleView: _PrimitiveView {}
|
|
346
|
+
extension _NavigationTitleView: _GroupView {
|
|
347
|
+
var _children: [any View] { [content] }
|
|
348
|
+
}
|
|
349
|
+
extension _NavigationTitleView: _NavigationTitleModifying {
|
|
350
|
+
var _title: String? { title }
|
|
351
|
+
var _displayMode: String? { displayMode }
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
extension View {
|
|
355
|
+
public func navigationTitle<S: StringProtocol>(_ title: S) -> some View {
|
|
356
|
+
_NavigationTitleView(content: self, title: String(title), displayMode: nil)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
public func navigationTitle(_ title: Text) -> some View {
|
|
360
|
+
_NavigationTitleView(content: self, title: title._content, displayMode: nil)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/// An editable title (iOS 16): the browser shows the bound value and does not edit it.
|
|
364
|
+
public func navigationTitle(_ title: Binding<String>) -> some View {
|
|
365
|
+
_NavigationTitleView(content: self, title: title.wrappedValue, displayMode: nil)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
public func navigationBarTitleDisplayMode(_ mode: NavigationBarItem.TitleDisplayMode) -> some View {
|
|
369
|
+
_NavigationTitleView(content: self, title: nil, displayMode: mode.name)
|
|
370
|
+
}
|
|
371
|
+
}
|