@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,165 @@
|
|
|
1
|
+
public enum TextAlignment: Hashable, Sendable {
|
|
2
|
+
case leading, center, trailing
|
|
3
|
+
|
|
4
|
+
var name: String {
|
|
5
|
+
switch self {
|
|
6
|
+
case .leading: "leading"
|
|
7
|
+
case .center: "center"
|
|
8
|
+
case .trailing: "trailing"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
extension View {
|
|
14
|
+
/// Sets the priority by which a parent stack hands out space.
|
|
15
|
+
public func layoutPriority(_ value: Double) -> some View {
|
|
16
|
+
_styled { $0.layoutPriority = value }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// Limits the number of lines text in this view can occupy.
|
|
20
|
+
public func lineLimit(_ number: Int?) -> some View {
|
|
21
|
+
_styled { $0.lineLimit = number ?? -1 }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// `reservesSpace` is accepted and ignored: the text takes the lines it needs, up to `limit`.
|
|
25
|
+
public func lineLimit(_ limit: Int, reservesSpace: Bool) -> some View {
|
|
26
|
+
_styled { $0.lineLimit = limit }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public func multilineTextAlignment(_ alignment: TextAlignment) -> some View {
|
|
30
|
+
_styled { $0.multilineTextAlignment = alignment }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Fixes the view at its ideal size on the given axes.
|
|
34
|
+
public func fixedSize(horizontal: Bool, vertical: Bool) -> some View {
|
|
35
|
+
_styled { $0.fixedSize = (horizontal, vertical) }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public func fixedSize() -> some View {
|
|
39
|
+
fixedSize(horizontal: true, vertical: true)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// MARK: - Color and shapes as views
|
|
44
|
+
|
|
45
|
+
extension Color: _PrimitiveView {}
|
|
46
|
+
extension Color: _HostView {
|
|
47
|
+
var _kind: String { "color" }
|
|
48
|
+
func _props(in environment: EnvironmentValues) -> _JSON { .object(["color": _json]) }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// A shape drawn as a view, optionally filled or stroked. `inset` strokes
|
|
52
|
+
/// (`strokeBorder`) lie inside the shape's bounds.
|
|
53
|
+
public struct _ShapeView<S: Shape> {
|
|
54
|
+
let shape: S
|
|
55
|
+
let fill: (any ShapeStyle)?
|
|
56
|
+
let stroke: (style: any ShapeStyle, lineWidth: CGFloat, inset: Bool)?
|
|
57
|
+
}
|
|
58
|
+
extension _ShapeView: _PrimitiveView {}
|
|
59
|
+
extension _ShapeView: _HostView {
|
|
60
|
+
var _kind: String { "shape" }
|
|
61
|
+
func _props(in environment: EnvironmentValues) -> _JSON {
|
|
62
|
+
var stroke: _JSON = .null
|
|
63
|
+
if let s = self.stroke {
|
|
64
|
+
var object: [String: _JSON] = ["color": s.style._json, "lineWidth": .number(s.lineWidth)]
|
|
65
|
+
if s.inset { object["inset"] = .bool(true) }
|
|
66
|
+
stroke = .object(object)
|
|
67
|
+
}
|
|
68
|
+
return .object([
|
|
69
|
+
"shape": .string(shape._name),
|
|
70
|
+
"cornerRadius": .number(shape._cornerRadius),
|
|
71
|
+
"fill": fill?._json ?? .null,
|
|
72
|
+
"stroke": stroke,
|
|
73
|
+
])
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Options for `fill`; accepted for source compatibility.
|
|
78
|
+
public struct FillStyle: Equatable, Sendable {
|
|
79
|
+
public var isEOFilled: Bool
|
|
80
|
+
public var isAntialiased: Bool
|
|
81
|
+
|
|
82
|
+
public init(eoFill: Bool = false, antialiased: Bool = true) {
|
|
83
|
+
isEOFilled = eoFill
|
|
84
|
+
isAntialiased = antialiased
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
extension Shape {
|
|
89
|
+
public func fill<S: ShapeStyle>(_ style: S, style fillStyle: FillStyle = FillStyle()) -> some View {
|
|
90
|
+
_ShapeView(shape: self, fill: style, stroke: nil)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Filled with the foreground style.
|
|
94
|
+
public func fill(style: FillStyle = FillStyle()) -> some View {
|
|
95
|
+
_ShapeView(shape: self, fill: ForegroundStyle(), stroke: nil)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public func stroke<S: ShapeStyle>(_ style: S, lineWidth: CGFloat = 1) -> some View {
|
|
99
|
+
_ShapeView(shape: self, fill: nil, stroke: (style, lineWidth, false))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public func stroke(lineWidth: CGFloat = 1) -> some View {
|
|
103
|
+
_ShapeView(shape: self, fill: nil, stroke: (ForegroundStyle(), lineWidth, false))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// A stroke drawn inside the shape's bounds.
|
|
107
|
+
public func strokeBorder<S: ShapeStyle>(_ style: S, lineWidth: CGFloat = 1, antialiased: Bool = true) -> some View {
|
|
108
|
+
_ShapeView(shape: self, fill: nil, stroke: (style, lineWidth, true))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public func strokeBorder(lineWidth: CGFloat = 1, antialiased: Bool = true) -> some View {
|
|
112
|
+
_ShapeView(shape: self, fill: nil, stroke: (ForegroundStyle(), lineWidth, true))
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
extension Rectangle: View {
|
|
117
|
+
public typealias Body = Never
|
|
118
|
+
public var body: Never { fatalError("Rectangle is a primitive view") }
|
|
119
|
+
}
|
|
120
|
+
extension RoundedRectangle: View {
|
|
121
|
+
public typealias Body = Never
|
|
122
|
+
public var body: Never { fatalError("RoundedRectangle is a primitive view") }
|
|
123
|
+
}
|
|
124
|
+
extension Circle: View {
|
|
125
|
+
public typealias Body = Never
|
|
126
|
+
public var body: Never { fatalError("Circle is a primitive view") }
|
|
127
|
+
}
|
|
128
|
+
extension Capsule: View {
|
|
129
|
+
public typealias Body = Never
|
|
130
|
+
public var body: Never { fatalError("Capsule is a primitive view") }
|
|
131
|
+
}
|
|
132
|
+
extension Ellipse: View {
|
|
133
|
+
public typealias Body = Never
|
|
134
|
+
public var body: Never { fatalError("Ellipse is a primitive view") }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
extension Rectangle: _HostView {
|
|
138
|
+
var _kind: String { "shape" }
|
|
139
|
+
func _props(in environment: EnvironmentValues) -> _JSON { _shapeProps(self) }
|
|
140
|
+
}
|
|
141
|
+
extension RoundedRectangle: _HostView {
|
|
142
|
+
var _kind: String { "shape" }
|
|
143
|
+
func _props(in environment: EnvironmentValues) -> _JSON { _shapeProps(self) }
|
|
144
|
+
}
|
|
145
|
+
extension Circle: _HostView {
|
|
146
|
+
var _kind: String { "shape" }
|
|
147
|
+
func _props(in environment: EnvironmentValues) -> _JSON { _shapeProps(self) }
|
|
148
|
+
}
|
|
149
|
+
extension Capsule: _HostView {
|
|
150
|
+
var _kind: String { "shape" }
|
|
151
|
+
func _props(in environment: EnvironmentValues) -> _JSON { _shapeProps(self) }
|
|
152
|
+
}
|
|
153
|
+
extension Ellipse: _HostView {
|
|
154
|
+
var _kind: String { "shape" }
|
|
155
|
+
func _props(in environment: EnvironmentValues) -> _JSON { _shapeProps(self) }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private func _shapeProps(_ shape: some Shape) -> _JSON {
|
|
159
|
+
.object([
|
|
160
|
+
"shape": .string(shape._name),
|
|
161
|
+
"cornerRadius": .number(shape._cornerRadius),
|
|
162
|
+
"fill": .null,
|
|
163
|
+
"stroke": .null,
|
|
164
|
+
])
|
|
165
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// MARK: - List
|
|
2
|
+
|
|
3
|
+
public protocol ListStyle {
|
|
4
|
+
var _name: String { get }
|
|
5
|
+
}
|
|
6
|
+
public struct DefaultListStyle: ListStyle {
|
|
7
|
+
public init() {}
|
|
8
|
+
public var _name: String { "insetGrouped" }
|
|
9
|
+
}
|
|
10
|
+
public struct PlainListStyle: ListStyle {
|
|
11
|
+
public init() {}
|
|
12
|
+
public var _name: String { "plain" }
|
|
13
|
+
}
|
|
14
|
+
public struct InsetGroupedListStyle: ListStyle {
|
|
15
|
+
public init() {}
|
|
16
|
+
public var _name: String { "insetGrouped" }
|
|
17
|
+
}
|
|
18
|
+
public struct GroupedListStyle: ListStyle {
|
|
19
|
+
public init() {}
|
|
20
|
+
public var _name: String { "grouped" }
|
|
21
|
+
}
|
|
22
|
+
extension ListStyle where Self == DefaultListStyle {
|
|
23
|
+
public static var automatic: DefaultListStyle { DefaultListStyle() }
|
|
24
|
+
}
|
|
25
|
+
extension ListStyle where Self == PlainListStyle {
|
|
26
|
+
public static var plain: PlainListStyle { PlainListStyle() }
|
|
27
|
+
}
|
|
28
|
+
extension ListStyle where Self == InsetGroupedListStyle {
|
|
29
|
+
public static var insetGrouped: InsetGroupedListStyle { InsetGroupedListStyle() }
|
|
30
|
+
}
|
|
31
|
+
extension ListStyle where Self == GroupedListStyle {
|
|
32
|
+
public static var grouped: GroupedListStyle { GroupedListStyle() }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
extension View {
|
|
36
|
+
public func listStyle<S: ListStyle>(_ style: S) -> some View {
|
|
37
|
+
environment(\._listStyle, style._name)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// A `List(selection:)` binding with its value type erased. Rows read it from
|
|
42
|
+
/// the environment (`_listSelection`): a `NavigationLink(value:)` row whose
|
|
43
|
+
/// value equals `get()` is the selected row, and in a `NavigationSplitView`
|
|
44
|
+
/// sidebar tapping such a row calls `set` instead of pushing.
|
|
45
|
+
struct _ListSelection {
|
|
46
|
+
let get: () -> AnyHashable?
|
|
47
|
+
let set: (AnyHashable?) -> Void
|
|
48
|
+
/// Whether the row with this value is selected.
|
|
49
|
+
let contains: (AnyHashable) -> Bool
|
|
50
|
+
/// Adds or removes a value (a `Set` selection; for an optional, selects or clears).
|
|
51
|
+
let toggle: (AnyHashable) -> Void
|
|
52
|
+
/// Whether a `.tag` / link value is of the selection's value type.
|
|
53
|
+
let accepts: (AnyHashable) -> Bool
|
|
54
|
+
/// `Set<Value>` selections toggle on tap; `Value?` selections set.
|
|
55
|
+
let isMultiple: Bool
|
|
56
|
+
|
|
57
|
+
init<Value: Hashable>(_ binding: Binding<Value?>) {
|
|
58
|
+
get = { binding.wrappedValue.map { AnyHashable($0) } }
|
|
59
|
+
set = { binding.wrappedValue = $0?.base as? Value }
|
|
60
|
+
contains = { binding.wrappedValue.map { AnyHashable($0) } == $0 }
|
|
61
|
+
toggle = { value in
|
|
62
|
+
if binding.wrappedValue.map({ AnyHashable($0) }) == value {
|
|
63
|
+
binding.wrappedValue = nil
|
|
64
|
+
} else {
|
|
65
|
+
binding.wrappedValue = value.base as? Value
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
accepts = { $0.base is Value }
|
|
69
|
+
isMultiple = false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// `get` / `set` reduce a multiple selection to one value: the first of the set.
|
|
73
|
+
init<Value: Hashable>(_ binding: Binding<Set<Value>>) {
|
|
74
|
+
get = { binding.wrappedValue.first.map { AnyHashable($0) } }
|
|
75
|
+
set = { value in
|
|
76
|
+
if let value = value?.base as? Value {
|
|
77
|
+
binding.wrappedValue = [value]
|
|
78
|
+
} else {
|
|
79
|
+
binding.wrappedValue = []
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
contains = { value in
|
|
83
|
+
guard let value = value.base as? Value else { return false }
|
|
84
|
+
return binding.wrappedValue.contains(value)
|
|
85
|
+
}
|
|
86
|
+
toggle = { value in
|
|
87
|
+
guard let value = value.base as? Value else { return }
|
|
88
|
+
if binding.wrappedValue.contains(value) {
|
|
89
|
+
binding.wrappedValue.remove(value)
|
|
90
|
+
} else {
|
|
91
|
+
binding.wrappedValue.insert(value)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
accepts = { $0.base is Value }
|
|
95
|
+
isMultiple = true
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Lists that carry a selection binding; the runtime puts it in the rows' environment.
|
|
100
|
+
protocol _ListSelectionProviding {
|
|
101
|
+
var _selection: _ListSelection? { get }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
enum _ListSelectionKey: EnvironmentKey { static var defaultValue: _ListSelection? { nil } }
|
|
105
|
+
enum _SplitSidebarKey: EnvironmentKey { static let defaultValue = false }
|
|
106
|
+
|
|
107
|
+
extension EnvironmentValues {
|
|
108
|
+
/// The enclosing `List(selection:)`'s binding, for its rows.
|
|
109
|
+
var _listSelection: _ListSelection? {
|
|
110
|
+
get { self[_ListSelectionKey.self] }
|
|
111
|
+
set { self[_ListSelectionKey.self] = newValue }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// True inside a `NavigationSplitView`'s sidebar, where value links select
|
|
115
|
+
/// instead of pushing.
|
|
116
|
+
var _splitSidebar: Bool {
|
|
117
|
+
get { self[_SplitSidebarKey.self] }
|
|
118
|
+
set { self[_SplitSidebarKey.self] = newValue }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public struct List<Content: View> {
|
|
123
|
+
let content: Content
|
|
124
|
+
let selection: _ListSelection?
|
|
125
|
+
/// `.refreshable`: true while the action runs.
|
|
126
|
+
@State var refreshing = false
|
|
127
|
+
|
|
128
|
+
public init(@ViewBuilder content: () -> Content) {
|
|
129
|
+
self.content = content()
|
|
130
|
+
self.selection = nil
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public init<Value: Hashable>(selection: Binding<Value?>, @ViewBuilder content: () -> Content) {
|
|
134
|
+
self.content = content()
|
|
135
|
+
self.selection = _ListSelection(selection)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public init<Value: Hashable>(selection: Binding<Set<Value>>, @ViewBuilder content: () -> Content) {
|
|
139
|
+
self.content = content()
|
|
140
|
+
self.selection = _ListSelection(selection)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
extension List {
|
|
145
|
+
public init<Data: RandomAccessCollection, ID: Hashable, RowContent: View>(
|
|
146
|
+
_ data: Data, id: KeyPath<Data.Element, ID>,
|
|
147
|
+
@ViewBuilder rowContent: @escaping (Data.Element) -> RowContent
|
|
148
|
+
) where Content == ForEach<Data, ID, RowContent> {
|
|
149
|
+
self.content = ForEach(data, id: id, content: rowContent)
|
|
150
|
+
self.selection = nil
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
public init<Data: RandomAccessCollection, RowContent: View>(
|
|
154
|
+
_ data: Data, @ViewBuilder rowContent: @escaping (Data.Element) -> RowContent
|
|
155
|
+
) where Data.Element: Identifiable, Content == ForEach<Data, Data.Element.ID, RowContent> {
|
|
156
|
+
self.content = ForEach(data, content: rowContent)
|
|
157
|
+
self.selection = nil
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public init<Data: RandomAccessCollection, ID: Hashable, Value: Hashable, RowContent: View>(
|
|
161
|
+
_ data: Data, id: KeyPath<Data.Element, ID>, selection: Binding<Value?>,
|
|
162
|
+
@ViewBuilder rowContent: @escaping (Data.Element) -> RowContent
|
|
163
|
+
) where Content == ForEach<Data, ID, RowContent> {
|
|
164
|
+
self.content = ForEach(data, id: id, content: rowContent)
|
|
165
|
+
self.selection = _ListSelection(selection)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public init<Data: RandomAccessCollection, Value: Hashable, RowContent: View>(
|
|
169
|
+
_ data: Data, selection: Binding<Value?>,
|
|
170
|
+
@ViewBuilder rowContent: @escaping (Data.Element) -> RowContent
|
|
171
|
+
) where Data.Element: Identifiable, Content == ForEach<Data, Data.Element.ID, RowContent> {
|
|
172
|
+
self.content = ForEach(data, content: rowContent)
|
|
173
|
+
self.selection = _ListSelection(selection)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
public init<Data: RandomAccessCollection, ID: Hashable, Value: Hashable, RowContent: View>(
|
|
177
|
+
_ data: Data, id: KeyPath<Data.Element, ID>, selection: Binding<Set<Value>>,
|
|
178
|
+
@ViewBuilder rowContent: @escaping (Data.Element) -> RowContent
|
|
179
|
+
) where Content == ForEach<Data, ID, RowContent> {
|
|
180
|
+
self.content = ForEach(data, id: id, content: rowContent)
|
|
181
|
+
self.selection = _ListSelection(selection)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public init<Data: RandomAccessCollection, Value: Hashable, RowContent: View>(
|
|
185
|
+
_ data: Data, selection: Binding<Set<Value>>,
|
|
186
|
+
@ViewBuilder rowContent: @escaping (Data.Element) -> RowContent
|
|
187
|
+
) where Data.Element: Identifiable, Content == ForEach<Data, Data.Element.ID, RowContent> {
|
|
188
|
+
self.content = ForEach(data, content: rowContent)
|
|
189
|
+
self.selection = _ListSelection(selection)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
extension List: _PrimitiveView {}
|
|
193
|
+
extension List: _HostView {
|
|
194
|
+
var _kind: String { "list" }
|
|
195
|
+
/// `editing` (the environment's edit mode) and `refreshable` / `refreshing`
|
|
196
|
+
/// (`.refreshable`) are present only when true / when refreshable.
|
|
197
|
+
func _props(in environment: EnvironmentValues) -> _JSON {
|
|
198
|
+
var props: [String: _JSON] = ["style": .string(environment._listStyle)]
|
|
199
|
+
if environment.editMode?.wrappedValue.isEditing == true { props["editing"] = .bool(true) }
|
|
200
|
+
_refresh(in: environment).addProps(to: &props)
|
|
201
|
+
return .object(props)
|
|
202
|
+
}
|
|
203
|
+
var _content: [any View] { [content] }
|
|
204
|
+
var _handler: ((_JSON) -> Void)? { nil }
|
|
205
|
+
|
|
206
|
+
func _refresh(in environment: EnvironmentValues) -> _Refresh {
|
|
207
|
+
_Refresh(action: environment._refreshAction, refreshing: $refreshing)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
extension List: _Refreshing {}
|
|
211
|
+
|
|
212
|
+
/// `List` and `ScrollView`: hosts whose `refresh` handler the runtime takes
|
|
213
|
+
/// from `_refresh(in:)` (the handler depends on the environment).
|
|
214
|
+
protocol _Refreshing {
|
|
215
|
+
func _refresh(in environment: EnvironmentValues) -> _Refresh
|
|
216
|
+
}
|
|
217
|
+
extension List: _ListSelectionProviding {
|
|
218
|
+
var _selection: _ListSelection? { selection }
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// MARK: - Section
|
|
222
|
+
|
|
223
|
+
public struct Section<Content: View> {
|
|
224
|
+
let header: String?
|
|
225
|
+
let footer: String?
|
|
226
|
+
let content: Content
|
|
227
|
+
|
|
228
|
+
public init(@ViewBuilder content: () -> Content) {
|
|
229
|
+
self.header = nil
|
|
230
|
+
self.footer = nil
|
|
231
|
+
self.content = content()
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
public init<S: StringProtocol>(_ title: S, @ViewBuilder content: () -> Content) {
|
|
235
|
+
self.header = String(title)
|
|
236
|
+
self.footer = nil
|
|
237
|
+
self.content = content()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/// Only `Text` headers and footers are supported; their content becomes the title.
|
|
241
|
+
public init(@ViewBuilder content: () -> Content, @ViewBuilder header: () -> Text) {
|
|
242
|
+
self.header = header().content
|
|
243
|
+
self.footer = nil
|
|
244
|
+
self.content = content()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
public init(@ViewBuilder content: () -> Content, @ViewBuilder footer: () -> Text) {
|
|
248
|
+
self.header = nil
|
|
249
|
+
self.footer = footer().content
|
|
250
|
+
self.content = content()
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
public init(@ViewBuilder content: () -> Content, @ViewBuilder header: () -> Text,
|
|
254
|
+
@ViewBuilder footer: () -> Text) {
|
|
255
|
+
self.header = header().content
|
|
256
|
+
self.footer = footer().content
|
|
257
|
+
self.content = content()
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/// Lets pickers look through sections for their options.
|
|
261
|
+
protocol _SectionView {
|
|
262
|
+
var _sectionContent: any View { get }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
extension Section: _SectionView {
|
|
266
|
+
var _sectionContent: any View { content }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
extension Section: _PrimitiveView {}
|
|
270
|
+
extension Section: _HostView {
|
|
271
|
+
var _kind: String { "section" }
|
|
272
|
+
func _props(in environment: EnvironmentValues) -> _JSON {
|
|
273
|
+
.object(["header": header.map(_JSON.string) ?? .null, "footer": footer.map(_JSON.string) ?? .null])
|
|
274
|
+
}
|
|
275
|
+
var _content: [any View] { [content] }
|
|
276
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/// A button that shows its content as a menu. The renderer lays out only the
|
|
2
|
+
/// label (the first child, wrapped in a `zstack` so it is always one element)
|
|
3
|
+
/// and lists the remaining children (buttons, inline pickers, dividers,
|
|
4
|
+
/// sections) in a popover when tapped.
|
|
5
|
+
public struct Menu<Label: View, Content: View> {
|
|
6
|
+
let label: Label
|
|
7
|
+
let content: Content
|
|
8
|
+
|
|
9
|
+
public init(@ViewBuilder content: () -> Content, @ViewBuilder label: () -> Label) {
|
|
10
|
+
self.content = content()
|
|
11
|
+
self.label = label()
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
extension Menu where Label == Text {
|
|
16
|
+
public init<S: StringProtocol>(_ title: S, @ViewBuilder content: () -> Content) {
|
|
17
|
+
self.label = Text(title)
|
|
18
|
+
self.content = content()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
extension Menu where Label == SwiftUI.Label<Text, Image> {
|
|
23
|
+
public init<S: StringProtocol>(_ title: S, systemImage: String, @ViewBuilder content: () -> Content) {
|
|
24
|
+
self.label = SwiftUI.Label(String(title), systemImage: systemImage)
|
|
25
|
+
self.content = content()
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
extension Menu: _PrimitiveView {}
|
|
30
|
+
extension Menu: _HostView {
|
|
31
|
+
var _kind: String { "menu" }
|
|
32
|
+
func _props(in environment: EnvironmentValues) -> _JSON { .object([:]) }
|
|
33
|
+
var _content: [any View] { [ZStack { label }, content] }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public protocol MenuStyle {}
|
|
37
|
+
public struct DefaultMenuStyle: MenuStyle { public init() {} }
|
|
38
|
+
public struct ButtonMenuStyle: MenuStyle { public init() {} }
|
|
39
|
+
extension MenuStyle where Self == DefaultMenuStyle {
|
|
40
|
+
public static var automatic: DefaultMenuStyle { DefaultMenuStyle() }
|
|
41
|
+
}
|
|
42
|
+
extension MenuStyle where Self == ButtonMenuStyle {
|
|
43
|
+
public static var button: ButtonMenuStyle { ButtonMenuStyle() }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public enum MenuIndicatorVisibility: Hashable, Sendable { case automatic, visible, hidden }
|
|
47
|
+
|
|
48
|
+
extension View {
|
|
49
|
+
public func menuStyle<S: MenuStyle>(_ style: S) -> some View { self }
|
|
50
|
+
public func menuIndicator(_ visibility: MenuIndicatorVisibility) -> some View { self }
|
|
51
|
+
}
|