@swiftbrowser/swift 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/Package.swift +23 -0
  2. package/README.md +12 -2
  3. package/Sources/Charts/Charts.swift +65 -0
  4. package/Sources/FoundationBridge/FoundationBridge.swift +77 -0
  5. package/Sources/FoundationBridge/Locale.swift +86 -0
  6. package/Sources/FoundationBridge/Timer.swift +48 -0
  7. package/Sources/FoundationBridge/UITextChecker.swift +30 -0
  8. package/Sources/FoundationLite/Common.swift +3 -0
  9. package/Sources/FoundationLite/FoundationLite.swift +171 -0
  10. package/Sources/FoundationLite/Interaction.swift +146 -0
  11. package/Sources/FoundationLite/Timer.swift +48 -0
  12. package/Sources/SwiftUI/API/Accessibility.swift +132 -0
  13. package/Sources/SwiftUI/API/Alert.swift +221 -0
  14. package/Sources/SwiftUI/API/Animation.swift +253 -0
  15. package/Sources/SwiftUI/API/App.swift +57 -0
  16. package/Sources/SwiftUI/API/Color.swift +161 -0
  17. package/Sources/SwiftUI/API/Controls.swift +268 -0
  18. package/Sources/SwiftUI/API/CustomLayout.swift +403 -0
  19. package/Sources/SwiftUI/API/Decorations.swift +92 -0
  20. package/Sources/SwiftUI/API/EditMode.swift +269 -0
  21. package/Sources/SwiftUI/API/Effects.swift +290 -0
  22. package/Sources/SwiftUI/API/Environment.swift +355 -0
  23. package/Sources/SwiftUI/API/Focus.swift +129 -0
  24. package/Sources/SwiftUI/API/Font.swift +131 -0
  25. package/Sources/SwiftUI/API/Gauge.swift +289 -0
  26. package/Sources/SwiftUI/API/Geometry.swift +89 -0
  27. package/Sources/SwiftUI/API/GeometryReader.swift +124 -0
  28. package/Sources/SwiftUI/API/Gestures.swift +342 -0
  29. package/Sources/SwiftUI/API/Grid.swift +188 -0
  30. package/Sources/SwiftUI/API/LabelStyle.swift +117 -0
  31. package/Sources/SwiftUI/API/Layout.swift +165 -0
  32. package/Sources/SwiftUI/API/Lists.swift +276 -0
  33. package/Sources/SwiftUI/API/Menu.swift +51 -0
  34. package/Sources/SwiftUI/API/Modifiers.swift +243 -0
  35. package/Sources/SwiftUI/API/Navigation.swift +371 -0
  36. package/Sources/SwiftUI/API/ObservableObject.swift +279 -0
  37. package/Sources/SwiftUI/API/OnChange.swift +48 -0
  38. package/Sources/SwiftUI/API/Phase8Controls.swift +394 -0
  39. package/Sources/SwiftUI/API/Picker.swift +176 -0
  40. package/Sources/SwiftUI/API/Presentation.swift +109 -0
  41. package/Sources/SwiftUI/API/Primitives.swift +390 -0
  42. package/Sources/SwiftUI/API/Publishers.swift +107 -0
  43. package/Sources/SwiftUI/API/RowActions.swift +83 -0
  44. package/Sources/SwiftUI/API/ScrollTargets.swift +104 -0
  45. package/Sources/SwiftUI/API/ShapeStyle.swift +311 -0
  46. package/Sources/SwiftUI/API/State.swift +127 -0
  47. package/Sources/SwiftUI/API/StrokeStyle.swift +30 -0
  48. package/Sources/SwiftUI/API/Tabs.swift +119 -0
  49. package/Sources/SwiftUI/API/Tasks.swift +35 -0
  50. package/Sources/SwiftUI/API/Timeline.swift +212 -0
  51. package/Sources/SwiftUI/API/Toolbar.swift +215 -0
  52. package/Sources/SwiftUI/API/View.swift +207 -0
  53. package/Sources/SwiftUI/API/ViewModifier.swift +40 -0
  54. package/Sources/SwiftUI/API/VisualEffect.swift +82 -0
  55. package/Sources/SwiftUI/Charts/Axis.swift +319 -0
  56. package/Sources/SwiftUI/Charts/Chart.swift +646 -0
  57. package/Sources/SwiftUI/Charts/ChartContent.swift +376 -0
  58. package/Sources/SwiftUI/Charts/Marks.swift +204 -0
  59. package/Sources/SwiftUI/Charts/Plottable.swift +99 -0
  60. package/Sources/SwiftUI/Charts/Scales.swift +326 -0
  61. package/Sources/SwiftUI/Runtime/Executor.swift +83 -0
  62. package/Sources/SwiftUI/Runtime/Exports.swift +65 -0
  63. package/Sources/SwiftUI/Runtime/JSON.swift +71 -0
  64. package/Sources/SwiftUI/Runtime/JSONParser.swift +443 -0
  65. package/Sources/SwiftUI/Runtime/Ops.swift +106 -0
  66. package/Sources/SwiftUI/Runtime/Reconciler.swift +210 -0
  67. package/Sources/SwiftUI/Runtime/Runtime.swift +1367 -0
  68. package/Sources/_FoundationCommon/Bundle.swift +82 -0
  69. package/Sources/_FoundationCommon/UserDefaults.swift +170 -0
  70. package/Sources/_FoundationCommon/_FoundationCommon.swift +10 -0
  71. package/index.d.ts +2 -0
  72. package/index.mjs +16 -0
  73. package/package.json +28 -4
  74. package/sync-sources.mjs +19 -0
@@ -0,0 +1,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
+ }