@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,92 @@
1
+ /// `.overlay` and `.background` with view content: a `decorated` element
2
+ /// whose first child decides the size and whose second child is drawn over
3
+ /// (`overlay`) or under (`background`) it, aligned inside its box.
4
+ struct _DecoratedView<Content: View, Decoration: View> {
5
+ let content: Content
6
+ let decoration: Decoration
7
+ let role: String
8
+ let alignment: Alignment
9
+ }
10
+ extension _DecoratedView: _PrimitiveView {}
11
+ extension _DecoratedView: _HostView {
12
+ var _kind: String { "decorated" }
13
+ func _props(in environment: EnvironmentValues) -> _JSON {
14
+ .object(["role": .string(role), "alignment": .string(alignment._name)])
15
+ }
16
+ var _content: [any View] { [content, decoration] }
17
+ }
18
+
19
+ extension EnvironmentValues {
20
+ /// `.backgroundStyle(_:)`: the paint `.background(in:)` fills shapes with.
21
+ var _backgroundStyle: (any ShapeStyle)? {
22
+ get { self[_BackgroundStyleKey.self] }
23
+ set { self[_BackgroundStyleKey.self] = newValue }
24
+ }
25
+ }
26
+
27
+ private struct _BackgroundStyleKey: EnvironmentKey {
28
+ static var defaultValue: (any ShapeStyle)? { nil }
29
+ }
30
+
31
+ /// Fills `shape` with the environment's background style (or the system
32
+ /// background) — `.background(in:)` without an explicit style.
33
+ struct _EnvironmentBackgroundShape<S: Shape> {
34
+ let shape: S
35
+ }
36
+ extension _EnvironmentBackgroundShape: _PrimitiveView {}
37
+ extension _EnvironmentBackgroundShape: _HostView {
38
+ var _kind: String { "shape" }
39
+ func _props(in environment: EnvironmentValues) -> _JSON {
40
+ let style = environment._backgroundStyle ?? BackgroundStyle()
41
+ return .object([
42
+ "shape": .string(shape._name),
43
+ "cornerRadius": .number(shape._cornerRadius),
44
+ "fill": style._json,
45
+ "stroke": .null,
46
+ ])
47
+ }
48
+ }
49
+
50
+ extension View {
51
+ public func overlay<V: View>(alignment: Alignment = .center, @ViewBuilder content: () -> V) -> some View {
52
+ _DecoratedView(content: self, decoration: content(), role: "overlay", alignment: alignment)
53
+ }
54
+
55
+ /// The pre-iOS 15 spelling, `overlay(view, alignment:)`.
56
+ @_disfavoredOverload
57
+ public func overlay<V: View>(_ overlay: V, alignment: Alignment = .center) -> some View {
58
+ _DecoratedView(content: self, decoration: overlay, role: "overlay", alignment: alignment)
59
+ }
60
+
61
+ public func overlay<S: ShapeStyle>(_ style: S, ignoresSafeAreaEdges edges: Edge.Set = .all) -> some View {
62
+ _DecoratedView(content: self, decoration: Rectangle().fill(style), role: "overlay", alignment: .center)
63
+ }
64
+
65
+ public func overlay<S: ShapeStyle, T: Shape>(_ style: S, in shape: T, fillStyle: FillStyle = FillStyle()) -> some View {
66
+ _DecoratedView(content: self, decoration: shape.fill(style), role: "overlay", alignment: .center)
67
+ }
68
+
69
+ public func background<V: View>(alignment: Alignment = .center, @ViewBuilder content: () -> V) -> some View {
70
+ _DecoratedView(content: self, decoration: content(), role: "background", alignment: alignment)
71
+ }
72
+
73
+ /// The pre-iOS 15 spelling, `background(view, alignment:)`.
74
+ @_disfavoredOverload
75
+ public func background<V: View>(_ background: V, alignment: Alignment = .center) -> some View {
76
+ _DecoratedView(content: self, decoration: background, role: "background", alignment: alignment)
77
+ }
78
+
79
+ /// `shape` filled with `style`, behind the view.
80
+ public func background<S: ShapeStyle, T: Shape>(_ style: S, in shape: T, fillStyle: FillStyle = FillStyle()) -> some View {
81
+ _DecoratedView(content: self, decoration: shape.fill(style), role: "background", alignment: .center)
82
+ }
83
+
84
+ /// `shape` filled with the background style (`.backgroundStyle`), behind the view.
85
+ public func background<T: Shape>(in shape: T, fillStyle: FillStyle = FillStyle()) -> some View {
86
+ _DecoratedView(content: self, decoration: _EnvironmentBackgroundShape(shape: shape), role: "background", alignment: .center)
87
+ }
88
+
89
+ public func backgroundStyle<S: ShapeStyle>(_ style: S) -> some View {
90
+ environment(\._backgroundStyle, style)
91
+ }
92
+ }
@@ -0,0 +1,269 @@
1
+ /// Edit mode, `EditButton`, `ForEach.onDelete` / `onMove`, offset sets and
2
+ /// `refreshable`. See docs/ops-protocol.md, "Phase 8: interaction".
3
+
4
+ // MARK: - Offset sets
5
+
6
+ /// A set of offsets into a collection, as `onDelete` / `onMove` report and
7
+ /// `remove(atOffsets:)` / `move(fromOffsets:toOffset:)` consume. Foundation's
8
+ /// `IndexSet` is not in FoundationEssentials, so the shim is generic over this
9
+ /// protocol: `FoundationLite.IndexSet` conforms, and `_IndexSetLite` serves
10
+ /// apps that import neither (it is what `onDelete` infers when nothing else
11
+ /// pins the type).
12
+ public protocol _OffsetSet {
13
+ init(_offsets: [Int])
14
+ /// The offsets in ascending order, without duplicates.
15
+ var _offsets: [Int] { get }
16
+ }
17
+
18
+ /// A minimal `IndexSet`: sorted unique integers.
19
+ public struct _IndexSetLite: _OffsetSet, Hashable, Sendable, Sequence, ExpressibleByArrayLiteral {
20
+ public private(set) var _offsets: [Int]
21
+
22
+ public init(_offsets: [Int]) {
23
+ self._offsets = Array(Set(_offsets)).sorted()
24
+ }
25
+
26
+ public init() { _offsets = [] }
27
+ public init(integer: Int) { _offsets = [integer] }
28
+ public init<S: Sequence>(_ elements: S) where S.Element == Int { self.init(_offsets: Array(elements)) }
29
+ public init(arrayLiteral elements: Int...) { self.init(_offsets: elements) }
30
+ public init(integersIn range: Range<Int>) { _offsets = Array(range) }
31
+
32
+ public var count: Int { _offsets.count }
33
+ public var isEmpty: Bool { _offsets.isEmpty }
34
+ public var first: Int? { _offsets.first }
35
+ public var last: Int? { _offsets.last }
36
+ public func contains(_ integer: Int) -> Bool { _offsets.contains(integer) }
37
+
38
+ public mutating func insert(_ integer: Int) {
39
+ guard !contains(integer) else { return }
40
+ _offsets.append(integer)
41
+ _offsets.sort()
42
+ }
43
+
44
+ public mutating func remove(_ integer: Int) {
45
+ _offsets.removeAll { $0 == integer }
46
+ }
47
+
48
+ public func makeIterator() -> IndexingIterator<[Int]> { _offsets.makeIterator() }
49
+ }
50
+
51
+ extension RangeReplaceableCollection {
52
+ /// Removes the elements at `offsets` (offsets from `startIndex`).
53
+ public mutating func remove<O: _OffsetSet>(atOffsets offsets: O) {
54
+ for offset in offsets._offsets.sorted(by: >) where offset >= 0 && offset < count {
55
+ remove(at: index(startIndex, offsetBy: offset))
56
+ }
57
+ }
58
+ }
59
+
60
+ extension MutableCollection where Self: RangeReplaceableCollection {
61
+ /// Moves the elements at `source` so that they start at `destination`
62
+ /// (an offset into the collection before the move), like Foundation's.
63
+ public mutating func move<O: _OffsetSet>(fromOffsets source: O, toOffset destination: Int) {
64
+ let offsets = source._offsets.filter { $0 >= 0 && $0 < count }
65
+ guard !offsets.isEmpty else { return }
66
+ let moving = offsets.map { self[index(startIndex, offsetBy: $0)] }
67
+ let before = offsets.filter { $0 < destination }.count
68
+ for offset in offsets.sorted(by: >) {
69
+ remove(at: index(startIndex, offsetBy: offset))
70
+ }
71
+ let target = Swift.max(0, Swift.min(destination - before, count))
72
+ insert(contentsOf: moving, at: index(startIndex, offsetBy: target))
73
+ }
74
+ }
75
+
76
+ // MARK: - onDelete / onMove
77
+
78
+ /// A view built from a collection, like SwiftUI's: `ForEach` and what
79
+ /// `onDelete` / `onMove` return.
80
+ public protocol DynamicViewContent: View {
81
+ associatedtype Data: Collection
82
+ var data: Data { get }
83
+ }
84
+
85
+ extension ForEach: DynamicViewContent where Content: View {}
86
+
87
+ /// A keyed group whose rows a `List` can delete (and, when the renderer learns
88
+ /// to, move). The runtime wraps each row in a `listrow` with `deletable`.
89
+ protocol _DeletableGroup {
90
+ /// Calls the group's `onDelete` with the row at `offset`; nil without one.
91
+ var _delete: ((Int) -> Void)? { get }
92
+ var _move: ((Int, Int) -> Void)? { get }
93
+ }
94
+
95
+ public struct _EditableForEach<Base: DynamicViewContent> {
96
+ let base: Base
97
+ let delete: ((Int) -> Void)?
98
+ let move: ((Int, Int) -> Void)?
99
+ }
100
+ extension _EditableForEach: _PrimitiveView {}
101
+ extension _EditableForEach: DynamicViewContent {
102
+ public var data: Base.Data { base.data }
103
+ }
104
+ extension _EditableForEach: _GroupView {
105
+ var _children: [any View] { (base as? any _GroupView)?._children ?? [base] }
106
+ var _keys: [AnyHashable]? { (base as? any _GroupView)?._keys }
107
+ }
108
+ extension _EditableForEach: _DeletableGroup {
109
+ var _delete: ((Int) -> Void)? { delete }
110
+ var _move: ((Int, Int) -> Void)? { move }
111
+ }
112
+
113
+ extension DynamicViewContent {
114
+ /// Lets a `List` delete the rows: `action` receives the offsets of the
115
+ /// removed elements in `data`. Pass `nil` to opt out.
116
+ public func onDelete<O: _OffsetSet>(perform action: ((O) -> Void)?) -> _EditableForEach<Self> {
117
+ _EditableForEach(base: self, delete: action.map { action in { action(O(_offsets: [$0])) } }, move: nil)
118
+ }
119
+
120
+ /// The offsets default to `_IndexSetLite` when nothing else pins the type.
121
+ public func onDelete(perform action: ((_IndexSetLite) -> Void)?) -> _EditableForEach<Self> {
122
+ _EditableForEach(base: self, delete: action.map { action in { action(_IndexSetLite(_offsets: [$0])) } }, move: nil)
123
+ }
124
+
125
+ /// Accepted for source compatibility; the renderer offers no reordering yet.
126
+ public func onMove<O: _OffsetSet>(perform action: ((O, Int) -> Void)?) -> _EditableForEach<Self> {
127
+ _EditableForEach(base: self, delete: nil, move: action.map { action in { action(O(_offsets: [$0]), $1) } })
128
+ }
129
+
130
+ public func onMove(perform action: ((_IndexSetLite, Int) -> Void)?) -> _EditableForEach<Self> {
131
+ _EditableForEach(base: self, delete: nil, move: action.map { action in { action(_IndexSetLite(_offsets: [$0]), $1) } })
132
+ }
133
+ }
134
+
135
+ extension _EditableForEach {
136
+ /// `ForEach(...).onDelete(...).onMove(...)`: the second modifier merges into the first.
137
+ public func onDelete<O: _OffsetSet>(perform action: ((O) -> Void)?) -> _EditableForEach<Base> {
138
+ _EditableForEach(base: base, delete: action.map { action in { action(O(_offsets: [$0])) } }, move: move)
139
+ }
140
+
141
+ public func onDelete(perform action: ((_IndexSetLite) -> Void)?) -> _EditableForEach<Base> {
142
+ _EditableForEach(base: base, delete: action.map { action in { action(_IndexSetLite(_offsets: [$0])) } }, move: move)
143
+ }
144
+
145
+ public func onMove<O: _OffsetSet>(perform action: ((O, Int) -> Void)?) -> _EditableForEach<Base> {
146
+ _EditableForEach(base: base, delete: delete, move: action.map { action in { action(O(_offsets: [$0]), $1) } })
147
+ }
148
+
149
+ public func onMove(perform action: ((_IndexSetLite, Int) -> Void)?) -> _EditableForEach<Base> {
150
+ _EditableForEach(base: base, delete: delete, move: action.map { action in { action(_IndexSetLite(_offsets: [$0]), $1) } })
151
+ }
152
+ }
153
+
154
+ /// `.deleteDisabled` / `.moveDisabled` on a row.
155
+ protocol _RowEditingDisabling {
156
+ var _deleteDisabled: Bool { get }
157
+ }
158
+
159
+ struct _DeleteDisabledView<Content: View> {
160
+ let content: Content
161
+ let disabled: Bool
162
+ }
163
+ extension _DeleteDisabledView: _PrimitiveView {}
164
+ extension _DeleteDisabledView: _GroupView {
165
+ var _children: [any View] { [content] }
166
+ }
167
+ extension _DeleteDisabledView: _RowEditingDisabling {
168
+ var _deleteDisabled: Bool { disabled }
169
+ }
170
+
171
+ extension View {
172
+ public func deleteDisabled(_ isDisabled: Bool) -> some View {
173
+ _DeleteDisabledView(content: self, disabled: isDisabled)
174
+ }
175
+
176
+ /// Accepted for source compatibility; rows cannot be moved yet.
177
+ public func moveDisabled(_ isDisabled: Bool) -> some View { self }
178
+ }
179
+
180
+ // MARK: - Edit mode
181
+
182
+ /// Whether a view is being edited. Each `navscreen` (and a `List` outside
183
+ /// a navigation stack) owns an edit mode the runtime puts in the environment
184
+ /// as a binding; `EditButton` toggles it and `List` reports it as `editing`.
185
+ public enum EditMode: Hashable, Sendable {
186
+ case inactive
187
+ case transient
188
+ case active
189
+
190
+ public var isEditing: Bool { self != .inactive }
191
+ }
192
+
193
+ enum _EditModeKey: EnvironmentKey { static var defaultValue: Binding<EditMode>? { nil } }
194
+
195
+ extension EnvironmentValues {
196
+ public var editMode: Binding<EditMode>? {
197
+ get { self[_EditModeKey.self] }
198
+ set { self[_EditModeKey.self] = newValue }
199
+ }
200
+ }
201
+
202
+ /// A button that toggles the enclosing edit mode between "Edit" and "Done".
203
+ public struct EditButton: View {
204
+ @Environment(\.editMode) private var editMode
205
+
206
+ public init() {}
207
+
208
+ public var body: some View {
209
+ let isEditing = editMode?.wrappedValue.isEditing == true
210
+ let mode = editMode
211
+ return Button(isEditing ? "Done" : "Edit") {
212
+ mode?.wrappedValue = isEditing ? .inactive : .active
213
+ }
214
+ }
215
+ }
216
+
217
+ // MARK: - Refreshable
218
+
219
+ enum _RefreshActionKey: EnvironmentKey {
220
+ static var defaultValue: (@Sendable () async -> Void)? { nil }
221
+ }
222
+
223
+ extension EnvironmentValues {
224
+ /// `.refreshable`'s action, read by the `List` / `ScrollView` it wraps.
225
+ var _refreshAction: (@Sendable () async -> Void)? {
226
+ get { self[_RefreshActionKey.self] }
227
+ set { self[_RefreshActionKey.self] = newValue }
228
+ }
229
+ }
230
+
231
+ extension View {
232
+ /// Pull to refresh on the `List` or `ScrollView` this modifies: the view
233
+ /// shows a spinner (`refreshing`) while `action` runs.
234
+ public func refreshable(action: @escaping @Sendable () async -> Void) -> some View {
235
+ environment(\._refreshAction, action)
236
+ }
237
+ }
238
+
239
+ /// Shared by `List` and `ScrollView`: the `refreshable` / `refreshing` props
240
+ /// and the `refresh` handler, driven by a `@State` flag on the view.
241
+ struct _Refresh {
242
+ let action: (@Sendable () async -> Void)?
243
+ let refreshing: Binding<Bool>
244
+
245
+ func addProps(to props: inout [String: _JSON]) {
246
+ guard action != nil else { return }
247
+ props["refreshable"] = .bool(true)
248
+ props["refreshing"] = .bool(refreshing.wrappedValue)
249
+ }
250
+
251
+ @MainActor var handler: ((_JSON) -> Void)? {
252
+ guard let action else { return nil }
253
+ // Main-actor closures are Sendable; the binding itself is not.
254
+ let refreshing = self.refreshing
255
+ let isRefreshing: @MainActor () -> Bool = { refreshing.wrappedValue }
256
+ let setRefreshing: @MainActor (Bool) -> Void = { refreshing.wrappedValue = $0 }
257
+ return { payload in
258
+ guard case .object(let fields) = payload, case .string("refresh")? = fields["type"] else { return }
259
+ MainActor.assumeIsolated {
260
+ guard !isRefreshing() else { return }
261
+ setRefreshing(true)
262
+ Task {
263
+ await action()
264
+ setRefreshing(false)
265
+ }
266
+ }
267
+ }
268
+ }
269
+ }
@@ -0,0 +1,290 @@
1
+ /// Visual effects and hints that need no layout logic on the Swift side: the
2
+ /// renderer applies them from the `styled` element's style keys (see
3
+ /// docs/ops-protocol.md, "Phase 6 additions").
4
+
5
+ public struct Angle: Hashable, Sendable, Comparable {
6
+ public var radians: Double
7
+
8
+ public var degrees: Double {
9
+ get { radians * 180 / .pi }
10
+ set { radians = newValue * .pi / 180 }
11
+ }
12
+
13
+ public init() { radians = 0 }
14
+ public init(radians: Double) { self.radians = radians }
15
+ public init(degrees: Double) { radians = degrees * .pi / 180 }
16
+
17
+ public static func radians(_ radians: Double) -> Angle { Angle(radians: radians) }
18
+ public static func degrees(_ degrees: Double) -> Angle { Angle(degrees: degrees) }
19
+ public static let zero = Angle()
20
+
21
+ public static func < (lhs: Angle, rhs: Angle) -> Bool { lhs.radians < rhs.radians }
22
+
23
+ public static prefix func - (angle: Angle) -> Angle { Angle(radians: -angle.radians) }
24
+ public static func + (lhs: Angle, rhs: Angle) -> Angle { Angle(radians: lhs.radians + rhs.radians) }
25
+ public static func - (lhs: Angle, rhs: Angle) -> Angle { Angle(radians: lhs.radians - rhs.radians) }
26
+ public static func * (lhs: Angle, rhs: Double) -> Angle { Angle(radians: lhs.radians * rhs) }
27
+ public static func * (lhs: Double, rhs: Angle) -> Angle { Angle(radians: lhs * rhs.radians) }
28
+ public static func / (lhs: Angle, rhs: Double) -> Angle { Angle(radians: lhs.radians / rhs) }
29
+ public static func += (lhs: inout Angle, rhs: Angle) { lhs = lhs + rhs }
30
+ public static func -= (lhs: inout Angle, rhs: Angle) { lhs = lhs - rhs }
31
+ }
32
+
33
+ /// `.formStyle`: accepted for source compatibility; forms are always grouped.
34
+ public protocol FormStyle {}
35
+ public struct AutomaticFormStyle: FormStyle { public init() {} }
36
+ public struct GroupedFormStyle: FormStyle { public init() {} }
37
+ public struct ColumnsFormStyle: FormStyle { public init() {} }
38
+ extension FormStyle where Self == AutomaticFormStyle {
39
+ public static var automatic: AutomaticFormStyle { AutomaticFormStyle() }
40
+ }
41
+ extension FormStyle where Self == GroupedFormStyle {
42
+ public static var grouped: GroupedFormStyle { GroupedFormStyle() }
43
+ }
44
+ extension FormStyle where Self == ColumnsFormStyle {
45
+ public static var columns: ColumnsFormStyle { ColumnsFormStyle() }
46
+ }
47
+
48
+ extension Image {
49
+ public enum Scale: Hashable, Sendable {
50
+ case small, medium, large
51
+
52
+ var name: String {
53
+ switch self {
54
+ case .small: "small"
55
+ case .medium: "medium"
56
+ case .large: "large"
57
+ }
58
+ }
59
+ }
60
+ }
61
+
62
+ extension View {
63
+ /// Translates the rendered view without affecting layout.
64
+ public func offset(x: CGFloat = 0, y: CGFloat = 0) -> some View {
65
+ _styled { $0.extras["offset"] = .object(["x": .number(x), "y": .number(y)]) }
66
+ }
67
+
68
+ public func offset(_ offset: CGSize) -> some View {
69
+ self.offset(x: offset.width, y: offset.height)
70
+ }
71
+
72
+ public func rotationEffect(_ angle: Angle, anchor: UnitPoint = .center) -> some View {
73
+ _styled {
74
+ $0.extras["rotation"] = .number(angle.degrees)
75
+ $0.extras["rotationAnchor"] = anchor._json
76
+ }
77
+ }
78
+
79
+ public func scaleEffect(_ scale: CGFloat, anchor: UnitPoint = .center) -> some View {
80
+ scaleEffect(x: scale, y: scale, anchor: anchor)
81
+ }
82
+
83
+ public func scaleEffect(_ scale: CGSize, anchor: UnitPoint = .center) -> some View {
84
+ scaleEffect(x: scale.width, y: scale.height, anchor: anchor)
85
+ }
86
+
87
+ public func scaleEffect(x: CGFloat = 1, y: CGFloat = 1, anchor: UnitPoint = .center) -> some View {
88
+ _styled {
89
+ $0.extras["scale"] = .object(["x": .number(x), "y": .number(y)])
90
+ $0.extras["scaleAnchor"] = anchor._json
91
+ }
92
+ }
93
+
94
+ /// Rotates the view in 3D about `axis` through `anchor`: the `rotation3D`
95
+ /// style hint (angle in degrees; the renderer applies the perspective).
96
+ public func rotation3DEffect(_ angle: Angle, axis: (x: CGFloat, y: CGFloat, z: CGFloat),
97
+ anchor: UnitPoint = .center, anchorZ: CGFloat = 0,
98
+ perspective: CGFloat = 1) -> some View {
99
+ _styled {
100
+ $0.extras["rotation3D"] = .object([
101
+ "angle": .number(angle.degrees),
102
+ "axis": .object(["x": .number(axis.x), "y": .number(axis.y), "z": .number(axis.z)]),
103
+ "anchor": anchor._json,
104
+ "perspective": .number(perspective),
105
+ ])
106
+ }
107
+ }
108
+
109
+ /// A Gaussian blur of `radius` points (`opaque` is accepted and ignored).
110
+ public func blur(radius: CGFloat, opaque: Bool = false) -> some View {
111
+ _styled { $0.extras["blur"] = .number(radius) }
112
+ }
113
+
114
+ /// `false` makes the subtree ignore pointer events (`hitTesting: false`).
115
+ public func allowsHitTesting(_ enabled: Bool) -> some View {
116
+ _styled { if !enabled { $0.extras["hitTesting"] = .bool(false) } }
117
+ }
118
+
119
+ /// Lets the view extend under the safe area on `edges`: the
120
+ /// `ignoresSafeArea` hint, `"all"` or the list of edges. `regions` is accepted
121
+ /// and ignored (the browser has no keyboard safe area).
122
+ public func ignoresSafeArea(_ regions: SafeAreaRegions = .all, edges: Edge.Set = .all) -> some View {
123
+ _styled {
124
+ if edges == .all {
125
+ $0.extras["ignoresSafeArea"] = .string("all")
126
+ } else {
127
+ let names = Edge.allCases.filter { edges.contains(Edge.Set(rawValue: 1 << $0.rawValue)) }.map(\.name)
128
+ $0.extras["ignoresSafeArea"] = .array(names.map(_JSON.string))
129
+ }
130
+ }
131
+ }
132
+
133
+ public func shadow(color: Color = Color(white: 0, opacity: 0.33), radius: CGFloat, x: CGFloat = 0, y: CGFloat = 0) -> some View {
134
+ _styled {
135
+ $0.extras["shadow"] = .object([
136
+ "color": color._json, "radius": .number(radius), "x": .number(x), "y": .number(y),
137
+ ])
138
+ }
139
+ }
140
+
141
+ public func clipped(antialiased: Bool = false) -> some View {
142
+ _styled { $0.extras["clipped"] = .bool(true) }
143
+ }
144
+
145
+ /// Keeps the view's layout but draws nothing.
146
+ public func hidden() -> some View {
147
+ _styled { $0.extras["hidden"] = .bool(true) }
148
+ }
149
+
150
+ /// Masks the view with a paint: a `Color` or gradient mask becomes the
151
+ /// `mask` style hint (its alpha clips the view). Any other mask view is
152
+ /// not supported and leaves the view unmasked.
153
+ public func mask<Mask: View>(alignment: Alignment = .center, @ViewBuilder _ mask: () -> Mask) -> some View {
154
+ self.mask(mask())
155
+ }
156
+
157
+ public func mask<Mask: View>(_ mask: Mask) -> some View {
158
+ _styled {
159
+ if let style = mask as? any ShapeStyle { $0.extras["mask"] = style._json }
160
+ }
161
+ }
162
+
163
+ /// Fixes the view's center at a point in its parent's coordinate space; the
164
+ /// view then fills the proposed space.
165
+ public func position(x: CGFloat, y: CGFloat) -> some View {
166
+ _styled { $0.extras["position"] = .object(["x": .number(x), "y": .number(y)]) }
167
+ }
168
+
169
+ public func position(_ position: CGPoint) -> some View {
170
+ self.position(x: position.x, y: position.y)
171
+ }
172
+
173
+ /// The accent color for controls in the subtree (gauges, toggles, buttons).
174
+ public func tint(_ tint: Color?) -> some View {
175
+ _styled { $0.extras["tint"] = tint?._json ?? .null }
176
+ }
177
+
178
+ public func imageScale(_ scale: Image.Scale) -> some View {
179
+ _styled { $0.extras["imageScale"] = .string(scale.name) }
180
+ }
181
+
182
+ // MARK: List rows
183
+
184
+ /// Trailing text in a list row.
185
+ public func badge(_ count: Int) -> some View {
186
+ _styled { $0.extras["badge"] = count == 0 ? .null : .string(String(count)) }
187
+ }
188
+
189
+ public func badge<S: StringProtocol>(_ label: S?) -> some View {
190
+ _styled { $0.extras["badge"] = label.map { .string(String($0)) } ?? .null }
191
+ }
192
+
193
+ public func badge(_ label: Text?) -> some View {
194
+ _styled { $0.extras["badge"] = label.map { .string($0.content) } ?? .null }
195
+ }
196
+
197
+ public func listRowInsets(_ insets: EdgeInsets?) -> some View {
198
+ _styled { $0.extras["listRowInsets"] = insets?._json ?? .null }
199
+ }
200
+
201
+ // MARK: Accepted without effect (yet)
202
+
203
+ public func listRowBackground<V: View>(_ view: V?) -> some View { self }
204
+ public func listItemTint(_ tint: Color?) -> some View { self }
205
+ public func formStyle<S: FormStyle>(_ style: S) -> some View { self }
206
+ public func headerProminence(_ prominence: Prominence) -> some View { self }
207
+ public func containerShape<T: Shape>(_ shape: T) -> some View { self }
208
+ public func symbolVariant(_ variant: SymbolVariants) -> some View { self }
209
+ public func symbolRenderingMode(_ mode: SymbolRenderingMode?) -> some View { self }
210
+ public func blendMode(_ mode: BlendMode) -> some View { self }
211
+ public func contentTransition(_ transition: ContentTransition) -> some View { self }
212
+ public func textSelection<S: TextSelectability>(_ selectability: S) -> some View { self }
213
+ public func toolbarRole(_ role: ToolbarRole) -> some View { self }
214
+ public func controlSize(_ size: ControlSize) -> some View { self }
215
+ }
216
+
217
+ public enum Prominence: Hashable, Sendable { case standard, increased }
218
+
219
+ /// `.controlSize`: accepted for source compatibility; controls keep one size.
220
+ public enum ControlSize: Hashable, CaseIterable, Sendable { case mini, small, regular, large, extraLarge }
221
+
222
+ public struct SymbolVariants: Hashable, Sendable {
223
+ let name: String
224
+ public static let none = SymbolVariants(name: "none")
225
+ public static let circle = SymbolVariants(name: "circle")
226
+ public static let square = SymbolVariants(name: "square")
227
+ public static let rectangle = SymbolVariants(name: "rectangle")
228
+ public static let fill = SymbolVariants(name: "fill")
229
+ public static let slash = SymbolVariants(name: "slash")
230
+ }
231
+
232
+ public enum SymbolRenderingMode: Hashable, Sendable { case monochrome, multicolor, hierarchical, palette }
233
+
234
+ public enum BlendMode: Hashable, Sendable {
235
+ case normal, multiply, screen, overlay, darken, lighten, colorDodge, colorBurn, softLight, hardLight,
236
+ difference, exclusion, hue, saturation, color, luminosity, sourceAtop, destinationOver,
237
+ destinationOut, plusDarker, plusLighter
238
+ }
239
+
240
+ public struct SafeAreaRegions: OptionSet, Sendable {
241
+ public let rawValue: UInt
242
+ public init(rawValue: UInt) { self.rawValue = rawValue }
243
+ public static let container = SafeAreaRegions(rawValue: 1)
244
+ public static let keyboard = SafeAreaRegions(rawValue: 2)
245
+ public static let all: SafeAreaRegions = [.container, .keyboard]
246
+ }
247
+
248
+ public struct ContentTransition: Hashable, Sendable {
249
+ let name: String
250
+ public static let identity = ContentTransition(name: "identity")
251
+ public static let opacity = ContentTransition(name: "opacity")
252
+ public static let interpolate = ContentTransition(name: "interpolate")
253
+ public static func numericText(countsDown: Bool = false) -> ContentTransition { ContentTransition(name: "numericText") }
254
+ }
255
+
256
+ public protocol TextSelectability {}
257
+ public struct EnabledTextSelectability: TextSelectability { public init() {} }
258
+ public struct DisabledTextSelectability: TextSelectability { public init() {} }
259
+ extension TextSelectability where Self == EnabledTextSelectability {
260
+ public static var enabled: EnabledTextSelectability { EnabledTextSelectability() }
261
+ }
262
+ extension TextSelectability where Self == DisabledTextSelectability {
263
+ public static var disabled: DisabledTextSelectability { DisabledTextSelectability() }
264
+ }
265
+
266
+ public enum ToolbarRole: Hashable, Sendable { case automatic, navigationStack, browser, editor }
267
+
268
+ // MARK: - Tap gesture
269
+
270
+ /// `.onTapGesture`: a `tapgesture` element whose tap event runs the action.
271
+ struct _TapGestureView<Content: View> {
272
+ let content: Content
273
+ let action: @MainActor () -> Void
274
+ }
275
+ extension _TapGestureView: _PrimitiveView {}
276
+ extension _TapGestureView: _HostView {
277
+ var _kind: String { "tapgesture" }
278
+ func _props(in environment: EnvironmentValues) -> _JSON { .object([:]) }
279
+ var _content: [any View] { [content] }
280
+ var _handler: ((_JSON) -> Void)? {
281
+ let action = self.action
282
+ return { _ in MainActor.assumeIsolated { action() } }
283
+ }
284
+ }
285
+
286
+ extension View {
287
+ public func onTapGesture(count: Int = 1, perform action: @escaping @MainActor () -> Void) -> some View {
288
+ _TapGestureView(content: self, action: action)
289
+ }
290
+ }