@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,279 @@
|
|
|
1
|
+
// Combine-style observation: `ObservableObject`, `@Published`, `@StateObject`,
|
|
2
|
+
// `@ObservedObject` and `@EnvironmentObject`, with the public signatures user
|
|
3
|
+
// code expects from SwiftUI and Combine, minus Combine itself.
|
|
4
|
+
//
|
|
5
|
+
// The runtime re-renders the whole tree when it is invalidated, so a publisher
|
|
6
|
+
// does not need subscribers in the Combine sense: it only has to know how to
|
|
7
|
+
// invalidate the runtime once a mounted view observes its object.
|
|
8
|
+
|
|
9
|
+
// MARK: - ObservableObject
|
|
10
|
+
|
|
11
|
+
/// A type whose changes a view can observe.
|
|
12
|
+
///
|
|
13
|
+
/// Combine declares `objectWillChange` with an associated publisher type; this
|
|
14
|
+
/// shim uses the concrete `ObservableObjectPublisher`, which is what the
|
|
15
|
+
/// default implementation produces on Apple's platforms as well, so a class
|
|
16
|
+
/// that only writes `class Model: ObservableObject` compiles unchanged.
|
|
17
|
+
///
|
|
18
|
+
/// The protocol is deliberately not actor-isolated, so both a plain
|
|
19
|
+
/// `final class` and a `@MainActor class` can conform.
|
|
20
|
+
public protocol ObservableObject: AnyObject {
|
|
21
|
+
var objectWillChange: ObservableObjectPublisher { get }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
extension ObservableObject {
|
|
25
|
+
/// The object's publisher, created on first access and kept for as long
|
|
26
|
+
/// as the object lives (see `ObservableObjectPublisher._publisher(for:)`).
|
|
27
|
+
public var objectWillChange: ObservableObjectPublisher {
|
|
28
|
+
ObservableObjectPublisher._publisher(for: self)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Publishes before an `ObservableObject` changes.
|
|
33
|
+
///
|
|
34
|
+
/// `send()` invalidates the runtime when some mounted view observes the
|
|
35
|
+
/// object. All view code and every `@Published` mutation in this shim run on
|
|
36
|
+
/// the main actor, which is where the runtime invalidation happens.
|
|
37
|
+
public final class ObservableObjectPublisher {
|
|
38
|
+
/// Installed by the runtime through the property wrappers: one entry per
|
|
39
|
+
/// observing mounted view (or `StateObject` storage), so a change marks
|
|
40
|
+
/// every view that observes the object. Entries whose owner is gone are
|
|
41
|
+
/// dropped on the next `send()`.
|
|
42
|
+
private var subscribers: [ObjectIdentifier: (owner: _WeakOwner, onChange: () -> Void)] = [:]
|
|
43
|
+
/// The object this publisher belongs to; used to detect a recycled
|
|
44
|
+
/// `ObjectIdentifier`.
|
|
45
|
+
weak var _owner: AnyObject?
|
|
46
|
+
|
|
47
|
+
/// Publishers keyed by the identity of their object. The protocol's
|
|
48
|
+
/// default implementation has no per-object storage to put the publisher
|
|
49
|
+
/// in, so the shim keeps it here. A publisher outlives its object until a
|
|
50
|
+
/// new object reuses the address, which replaces the stale entry; the leak
|
|
51
|
+
/// is one small object per `ObservableObject` instance. The registry is
|
|
52
|
+
/// unsynchronized because the shim runs single-threaded on the main actor.
|
|
53
|
+
nonisolated(unsafe) private static var registry: [ObjectIdentifier: ObservableObjectPublisher] = [:]
|
|
54
|
+
|
|
55
|
+
public init() {}
|
|
56
|
+
|
|
57
|
+
/// Tells observing views that the object is about to change.
|
|
58
|
+
public func send() {
|
|
59
|
+
for (key, entry) in subscribers {
|
|
60
|
+
if entry.owner.object == nil {
|
|
61
|
+
subscribers[key] = nil
|
|
62
|
+
} else {
|
|
63
|
+
entry.onChange()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Registers `onChange` to run on `send()` for as long as `owner` (the
|
|
69
|
+
/// observing view's mounted node or state storage) is alive; a second
|
|
70
|
+
/// subscription by the same owner replaces the first.
|
|
71
|
+
func _subscribe(owner: AnyObject, _ onChange: @escaping () -> Void) {
|
|
72
|
+
subscribers[ObjectIdentifier(owner)] = (_WeakOwner(owner), onChange)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static func _publisher(for object: AnyObject) -> ObservableObjectPublisher {
|
|
76
|
+
let id = ObjectIdentifier(object)
|
|
77
|
+
if let existing = registry[id], existing._owner === object { return existing }
|
|
78
|
+
let publisher = ObservableObjectPublisher()
|
|
79
|
+
publisher._owner = object
|
|
80
|
+
registry[id] = publisher
|
|
81
|
+
return publisher
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// MARK: - @Published
|
|
86
|
+
|
|
87
|
+
/// Publishes the enclosing `ObservableObject`'s `objectWillChange` before a
|
|
88
|
+
/// property changes.
|
|
89
|
+
///
|
|
90
|
+
/// Combine's `Published.Publisher` (`$property`) is not provided.
|
|
91
|
+
@propertyWrapper
|
|
92
|
+
public struct Published<Value> {
|
|
93
|
+
var value: Value
|
|
94
|
+
|
|
95
|
+
public init(wrappedValue: Value) { value = wrappedValue }
|
|
96
|
+
public init(initialValue: Value) { value = initialValue }
|
|
97
|
+
|
|
98
|
+
@available(*, unavailable, message: "@Published is only available on properties of classes")
|
|
99
|
+
public var wrappedValue: Value {
|
|
100
|
+
get { fatalError("@Published is only available on properties of classes") }
|
|
101
|
+
set { fatalError("@Published is only available on properties of classes") }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public static subscript<T: AnyObject>(
|
|
105
|
+
_enclosingInstance instance: T,
|
|
106
|
+
wrapped _: ReferenceWritableKeyPath<T, Value>,
|
|
107
|
+
storage storageKeyPath: ReferenceWritableKeyPath<T, Published<Value>>
|
|
108
|
+
) -> Value {
|
|
109
|
+
get { instance[keyPath: storageKeyPath].value }
|
|
110
|
+
set {
|
|
111
|
+
(instance as? any ObservableObject)?.objectWillChange.send()
|
|
112
|
+
instance[keyPath: storageKeyPath].value = newValue
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// MARK: - Runtime hooks
|
|
118
|
+
|
|
119
|
+
/// Views expose properties that observe an `ObservableObject` through this
|
|
120
|
+
/// protocol; the runtime finds them by reflection and wires invalidation.
|
|
121
|
+
protocol _ObservingProperty {
|
|
122
|
+
/// Subscribes `onChange` to the observed object on behalf of `owner`
|
|
123
|
+
/// (the mounted view), see `ObservableObjectPublisher._subscribe`.
|
|
124
|
+
func _observe(owner: AnyObject, _ onChange: @escaping () -> Void)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// A weak reference, for the publisher's subscriber table.
|
|
128
|
+
final class _WeakOwner {
|
|
129
|
+
weak var object: AnyObject?
|
|
130
|
+
init(_ object: AnyObject) { self.object = object }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// MARK: - @ObservedObject
|
|
134
|
+
|
|
135
|
+
/// Observes an `ObservableObject` owned elsewhere; changes re-render the view.
|
|
136
|
+
@propertyWrapper
|
|
137
|
+
public struct ObservedObject<ObjectType: ObservableObject>: DynamicProperty {
|
|
138
|
+
/// Bindings to the object's properties (`$model.property`).
|
|
139
|
+
@dynamicMemberLookup
|
|
140
|
+
public struct Wrapper {
|
|
141
|
+
let object: ObjectType
|
|
142
|
+
|
|
143
|
+
public subscript<Subject>(dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject>) -> Binding<Subject> {
|
|
144
|
+
let object = self.object
|
|
145
|
+
return Binding(get: { object[keyPath: keyPath] }, set: { object[keyPath: keyPath] = $0 })
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public var wrappedValue: ObjectType
|
|
150
|
+
|
|
151
|
+
public init(wrappedValue: ObjectType) { self.wrappedValue = wrappedValue }
|
|
152
|
+
public init(initialValue: ObjectType) { wrappedValue = initialValue }
|
|
153
|
+
|
|
154
|
+
public var projectedValue: Wrapper { Wrapper(object: wrappedValue) }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
extension ObservedObject: _ObservingProperty {
|
|
158
|
+
func _observe(owner: AnyObject, _ onChange: @escaping () -> Void) {
|
|
159
|
+
wrappedValue.objectWillChange._subscribe(owner: owner, onChange)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// MARK: - @StateObject
|
|
164
|
+
|
|
165
|
+
/// The object behind a `StateObject`, created once and shared by every box
|
|
166
|
+
/// for the same mounted view (like `_StateStorage`).
|
|
167
|
+
final class _StateObjectStorage<ObjectType: ObservableObject> {
|
|
168
|
+
private var make: (() -> ObjectType)?
|
|
169
|
+
private(set) var object: ObjectType?
|
|
170
|
+
var onChange: (() -> Void)? {
|
|
171
|
+
didSet { subscribe() }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
init(_ make: @escaping () -> ObjectType) { self.make = make }
|
|
175
|
+
|
|
176
|
+
/// Creates the object on first access, so a view struct that is
|
|
177
|
+
/// re-created but then adopts a previous render's storage never runs its
|
|
178
|
+
/// initializer expression.
|
|
179
|
+
func resolve() -> ObjectType {
|
|
180
|
+
if let object { return object }
|
|
181
|
+
let created = make!()
|
|
182
|
+
make = nil
|
|
183
|
+
object = created
|
|
184
|
+
subscribe()
|
|
185
|
+
return created
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private func subscribe() {
|
|
189
|
+
guard let object, let onChange else { return }
|
|
190
|
+
object.objectWillChange._subscribe(owner: self, onChange)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
final class _StateObjectBox<ObjectType: ObservableObject>: _AnyStateBox {
|
|
195
|
+
var storage: _StateObjectStorage<ObjectType>
|
|
196
|
+
|
|
197
|
+
init(_ make: @escaping () -> ObjectType) { storage = _StateObjectStorage(make) }
|
|
198
|
+
|
|
199
|
+
var _onChange: (() -> Void)? {
|
|
200
|
+
get { storage.onChange }
|
|
201
|
+
set { storage.onChange = newValue }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/// The object, or `nil` before it is created. Never a JSON primitive, so
|
|
205
|
+
/// hot-reload snapshots skip it.
|
|
206
|
+
var _anyValue: Any {
|
|
207
|
+
get { storage.object as Any }
|
|
208
|
+
set {}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
func _adopt(_ previous: any _AnyStateBox) {
|
|
212
|
+
guard let previous = previous as? _StateObjectBox<ObjectType> else { return }
|
|
213
|
+
storage = previous.storage
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/// Owns an `ObservableObject` for the lifetime of the mounted view.
|
|
218
|
+
///
|
|
219
|
+
/// The initializer expression runs once, when the view first mounts; the
|
|
220
|
+
/// runtime carries the object across re-renders exactly like `State`.
|
|
221
|
+
@propertyWrapper
|
|
222
|
+
public struct StateObject<ObjectType: ObservableObject>: DynamicProperty {
|
|
223
|
+
let box: _StateObjectBox<ObjectType>
|
|
224
|
+
|
|
225
|
+
public init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType) {
|
|
226
|
+
box = _StateObjectBox(thunk)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
public var wrappedValue: ObjectType { box.storage.resolve() }
|
|
230
|
+
|
|
231
|
+
public var projectedValue: ObservedObject<ObjectType>.Wrapper {
|
|
232
|
+
ObservedObject<ObjectType>.Wrapper(object: wrappedValue)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
extension StateObject: _StateProperty {
|
|
237
|
+
var _box: any _AnyStateBox { box }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// MARK: - @EnvironmentObject
|
|
241
|
+
|
|
242
|
+
/// Reads an `ObservableObject` an ancestor supplied with `environmentObject(_:)`.
|
|
243
|
+
@propertyWrapper
|
|
244
|
+
public struct EnvironmentObject<ObjectType: ObservableObject>: DynamicProperty {
|
|
245
|
+
/// Bindings to the object's properties (`$model.property`).
|
|
246
|
+
public typealias Wrapper = ObservedObject<ObjectType>.Wrapper
|
|
247
|
+
|
|
248
|
+
let box = _EnvironmentBox()
|
|
249
|
+
|
|
250
|
+
public init() {}
|
|
251
|
+
|
|
252
|
+
public var wrappedValue: ObjectType {
|
|
253
|
+
guard let object = box.value as? ObjectType else {
|
|
254
|
+
fatalError("No ObservableObject of type \(ObjectType.self) found. A View.environmentObject(_:) for \(ObjectType.self) may be missing as an ancestor of this view.")
|
|
255
|
+
}
|
|
256
|
+
return object
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
public var projectedValue: Wrapper { Wrapper(object: wrappedValue) }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
extension EnvironmentObject: _EnvironmentProperty {
|
|
263
|
+
func _resolve(in environment: EnvironmentValues) {
|
|
264
|
+
box.value = environment._object(ObjectType.self)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
extension EnvironmentObject: _ObservingProperty {
|
|
269
|
+
func _observe(owner: AnyObject, _ onChange: @escaping () -> Void) {
|
|
270
|
+
(box.value as? ObjectType)?.objectWillChange._subscribe(owner: owner, onChange)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
extension View {
|
|
275
|
+
/// Supplies `object` to every `EnvironmentObject` of its type in the subtree.
|
|
276
|
+
public func environmentObject<T: ObservableObject>(_ object: T) -> some View {
|
|
277
|
+
_EnvironmentView(content: self) { $0._setObject(object) }
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/// `.onChange(of:)`: remembers the last value across re-renders (in a `@State`
|
|
2
|
+
/// slot, so it survives like any state) and runs the action when the value
|
|
3
|
+
/// differs. The action runs while the body is evaluated; state it changes
|
|
4
|
+
/// schedules another render pass, which is what SwiftUI does too.
|
|
5
|
+
final class _ChangeSlot<Value> {
|
|
6
|
+
var value: Value?
|
|
7
|
+
var seeded = false
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
struct _OnChangeView<Value: Equatable, Content: View>: View {
|
|
11
|
+
let content: Content
|
|
12
|
+
let value: Value
|
|
13
|
+
let initial: Bool
|
|
14
|
+
let action: (Value, Value) -> Void
|
|
15
|
+
@State private var slot = _ChangeSlot<Value>()
|
|
16
|
+
|
|
17
|
+
var body: some View {
|
|
18
|
+
if !slot.seeded {
|
|
19
|
+
slot.seeded = true
|
|
20
|
+
slot.value = value
|
|
21
|
+
if initial { action(value, value) }
|
|
22
|
+
} else if let previous = slot.value, previous != value {
|
|
23
|
+
slot.value = value
|
|
24
|
+
action(previous, value)
|
|
25
|
+
}
|
|
26
|
+
return content
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
extension View {
|
|
31
|
+
/// The iOS 17 form: `(oldValue, newValue)`.
|
|
32
|
+
public func onChange<V: Equatable>(of value: V, initial: Bool = false,
|
|
33
|
+
_ action: @escaping (_ oldValue: V, _ newValue: V) -> Void) -> some View {
|
|
34
|
+
_OnChangeView(content: self, value: value, initial: initial, action: action)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// The iOS 17 form without parameters.
|
|
38
|
+
public func onChange<V: Equatable>(of value: V, initial: Bool = false,
|
|
39
|
+
_ action: @escaping () -> Void) -> some View {
|
|
40
|
+
_OnChangeView(content: self, value: value, initial: initial) { _, _ in action() }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// The pre-iOS 17 form: `(newValue)`.
|
|
44
|
+
@_disfavoredOverload
|
|
45
|
+
public func onChange<V: Equatable>(of value: V, perform action: @escaping (_ newValue: V) -> Void) -> some View {
|
|
46
|
+
_OnChangeView(content: self, value: value, initial: false) { _, new in action(new) }
|
|
47
|
+
}
|
|
48
|
+
}
|