@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,355 @@
|
|
|
1
|
+
@_exported import Observation
|
|
2
|
+
|
|
3
|
+
public enum ColorScheme: Hashable, Sendable {
|
|
4
|
+
case light, dark
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/// The user's preferred text size.
|
|
8
|
+
public enum DynamicTypeSize: Int, Hashable, Comparable, CaseIterable, Sendable {
|
|
9
|
+
case xSmall, small, medium, large, xLarge, xxLarge, xxxLarge
|
|
10
|
+
case accessibility1, accessibility2, accessibility3, accessibility4, accessibility5
|
|
11
|
+
|
|
12
|
+
public static func < (lhs: DynamicTypeSize, rhs: DynamicTypeSize) -> Bool { lhs.rawValue < rhs.rawValue }
|
|
13
|
+
|
|
14
|
+
public var isAccessibilitySize: Bool { self >= .accessibility1 }
|
|
15
|
+
|
|
16
|
+
var name: String {
|
|
17
|
+
switch self {
|
|
18
|
+
case .xSmall: "xSmall"
|
|
19
|
+
case .small: "small"
|
|
20
|
+
case .medium: "medium"
|
|
21
|
+
case .large: "large"
|
|
22
|
+
case .xLarge: "xLarge"
|
|
23
|
+
case .xxLarge: "xxLarge"
|
|
24
|
+
case .xxxLarge: "xxxLarge"
|
|
25
|
+
case .accessibility1: "accessibility1"
|
|
26
|
+
case .accessibility2: "accessibility2"
|
|
27
|
+
case .accessibility3: "accessibility3"
|
|
28
|
+
case .accessibility4: "accessibility4"
|
|
29
|
+
case .accessibility5: "accessibility5"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
init?(name: String) {
|
|
34
|
+
guard let match = Self.allCases.first(where: { $0.name == name }) else { return nil }
|
|
35
|
+
self = match
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// The horizontal or vertical size class of the window the view is in.
|
|
40
|
+
/// The renderer reports `regular` for screens at least 700 CSS px wide
|
|
41
|
+
/// (see docs/ops-protocol.md, Phase 7); headless runs are compact.
|
|
42
|
+
public enum UserInterfaceSizeClass: Hashable, Sendable {
|
|
43
|
+
case compact, regular
|
|
44
|
+
|
|
45
|
+
init?(name: String) {
|
|
46
|
+
switch name {
|
|
47
|
+
case "compact": self = .compact
|
|
48
|
+
case "regular": self = .regular
|
|
49
|
+
default: return nil
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// A key for custom environment values.
|
|
55
|
+
public protocol EnvironmentKey {
|
|
56
|
+
associatedtype Value
|
|
57
|
+
static var defaultValue: Value { get }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Dismisses the current presentation: in a navigation stack, pops the screen.
|
|
61
|
+
public struct DismissAction: Sendable {
|
|
62
|
+
let action: @MainActor () -> Void
|
|
63
|
+
|
|
64
|
+
init(_ action: @escaping @MainActor () -> Void) { self.action = action }
|
|
65
|
+
|
|
66
|
+
@MainActor public func callAsFunction() { action() }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Values propagated down the view tree.
|
|
70
|
+
public struct EnvironmentValues {
|
|
71
|
+
private var storage: [ObjectIdentifier: Any] = [:]
|
|
72
|
+
|
|
73
|
+
public init() {}
|
|
74
|
+
|
|
75
|
+
public subscript<K: EnvironmentKey>(key: K.Type) -> K.Value {
|
|
76
|
+
get {
|
|
77
|
+
// An absent entry must fall back to the default even for optional
|
|
78
|
+
// values: `nil as? Optional<V>` would succeed as `.some(nil)`.
|
|
79
|
+
guard let stored = storage[ObjectIdentifier(key)] else { return K.defaultValue }
|
|
80
|
+
return stored as? K.Value ?? K.defaultValue
|
|
81
|
+
}
|
|
82
|
+
set { storage[ObjectIdentifier(key)] = newValue }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public var colorScheme: ColorScheme {
|
|
86
|
+
get { self[_ColorSchemeKey.self] }
|
|
87
|
+
set { self[_ColorSchemeKey.self] = newValue }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public var dismiss: DismissAction {
|
|
91
|
+
get { self[_DismissKey.self] }
|
|
92
|
+
set { self[_DismissKey.self] = newValue }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public var dynamicTypeSize: DynamicTypeSize {
|
|
96
|
+
get { self[_DynamicTypeSizeKey.self] }
|
|
97
|
+
set { self[_DynamicTypeSizeKey.self] = newValue }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public var isEnabled: Bool {
|
|
101
|
+
get { self[_IsEnabledKey.self] }
|
|
102
|
+
set { self[_IsEnabledKey.self] = newValue }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// `.compact` until the renderer's first `environment` event says otherwise.
|
|
106
|
+
public var horizontalSizeClass: UserInterfaceSizeClass? {
|
|
107
|
+
get { self[_HorizontalSizeClassKey.self] }
|
|
108
|
+
set { self[_HorizontalSizeClassKey.self] = newValue }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Always `.regular`: the browser never reports a vertically compact window.
|
|
112
|
+
public var verticalSizeClass: UserInterfaceSizeClass? {
|
|
113
|
+
get { self[_VerticalSizeClassKey.self] }
|
|
114
|
+
set { self[_VerticalSizeClassKey.self] = newValue }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
var _buttonStyle: String {
|
|
118
|
+
get { self[_ButtonStyleKey.self] }
|
|
119
|
+
set { self[_ButtonStyleKey.self] = newValue }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
var _textFieldStyle: String {
|
|
123
|
+
get { self[_TextFieldStyleKey.self] }
|
|
124
|
+
set { self[_TextFieldStyleKey.self] = newValue }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Objects supplied by `.environmentObject(_:)`, keyed by their type. A
|
|
128
|
+
// type's identifier never collides with an `EnvironmentKey`'s.
|
|
129
|
+
func _object<T: AnyObject>(_ type: T.Type) -> T? {
|
|
130
|
+
storage[ObjectIdentifier(type)] as? T
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
mutating func _setObject<T: AnyObject>(_ object: T) {
|
|
134
|
+
storage[ObjectIdentifier(T.self)] = object
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// The object stored for a type, looked up dynamically (`@Environment(Favorites.self)`).
|
|
138
|
+
func _anyObject(_ type: Any.Type) -> Any? {
|
|
139
|
+
storage[ObjectIdentifier(type)]
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Lets `@Environment(Type.self)` hand back nil for an optional type that was not supplied.
|
|
144
|
+
protocol _OptionalValue {
|
|
145
|
+
static var _none: Self { get }
|
|
146
|
+
}
|
|
147
|
+
extension Optional: _OptionalValue {
|
|
148
|
+
static var _none: Self { nil }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
enum _ColorSchemeKey: EnvironmentKey { static let defaultValue = ColorScheme.light }
|
|
152
|
+
enum _DismissKey: EnvironmentKey { static let defaultValue = DismissAction {} }
|
|
153
|
+
enum _IsEnabledKey: EnvironmentKey { static let defaultValue = true }
|
|
154
|
+
enum _DynamicTypeSizeKey: EnvironmentKey { static let defaultValue = DynamicTypeSize.large }
|
|
155
|
+
enum _HorizontalSizeClassKey: EnvironmentKey { static let defaultValue: UserInterfaceSizeClass? = .compact }
|
|
156
|
+
enum _VerticalSizeClassKey: EnvironmentKey { static let defaultValue: UserInterfaceSizeClass? = .regular }
|
|
157
|
+
enum _ButtonStyleKey: EnvironmentKey { static let defaultValue = "automatic" }
|
|
158
|
+
enum _TextFieldStyleKey: EnvironmentKey { static let defaultValue = "plain" }
|
|
159
|
+
|
|
160
|
+
// MARK: - @Environment
|
|
161
|
+
|
|
162
|
+
final class _EnvironmentBox {
|
|
163
|
+
var value: Any?
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// Views expose their `Environment` properties to the runtime through this protocol.
|
|
167
|
+
protocol _EnvironmentProperty {
|
|
168
|
+
func _resolve(in environment: EnvironmentValues)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/// Reads a value from the environment the runtime resolved for the view.
|
|
172
|
+
@propertyWrapper
|
|
173
|
+
public struct Environment<Value>: DynamicProperty {
|
|
174
|
+
let keyPath: KeyPath<EnvironmentValues, Value>?
|
|
175
|
+
/// `@Environment(Favorites.self)`: the `@Observable` type to look up (its non-optional form).
|
|
176
|
+
let objectType: Any.Type?
|
|
177
|
+
let box = _EnvironmentBox()
|
|
178
|
+
|
|
179
|
+
public init(_ keyPath: KeyPath<EnvironmentValues, Value>) {
|
|
180
|
+
self.keyPath = keyPath
|
|
181
|
+
objectType = nil
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// Reads the object when an ancestor supplied one, else nil.
|
|
185
|
+
public init<T: AnyObject & Observable>(_ objectType: T?.Type) where Value == T? {
|
|
186
|
+
keyPath = nil
|
|
187
|
+
self.objectType = T.self
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
public var wrappedValue: Value {
|
|
191
|
+
if let value = box.value as? Value { return value }
|
|
192
|
+
if let keyPath { return EnvironmentValues()[keyPath: keyPath] }
|
|
193
|
+
if let none = Value.self as? any _OptionalValue.Type { return none._none as! Value }
|
|
194
|
+
fatalError("No Observable object of type \(objectType.map { String(describing: $0) } ?? "\(Value.self)") found. A View.environment(_:) for \(objectType.map { String(describing: $0) } ?? "\(Value.self)") may be missing as an ancestor of this view.")
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
extension Environment where Value: AnyObject & Observable {
|
|
199
|
+
/// Reads the `@Observable` object a `.environment(_:)` ancestor supplied; a
|
|
200
|
+
/// missing object is a programming error, as in SwiftUI.
|
|
201
|
+
public init(_ objectType: Value.Type) {
|
|
202
|
+
keyPath = nil
|
|
203
|
+
self.objectType = objectType
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
extension Environment: _EnvironmentProperty {
|
|
208
|
+
func _resolve(in environment: EnvironmentValues) {
|
|
209
|
+
if let keyPath {
|
|
210
|
+
box.value = environment[keyPath: keyPath]
|
|
211
|
+
} else if let objectType {
|
|
212
|
+
box.value = environment._anyObject(objectType)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// MARK: - .environment(_:_:)
|
|
218
|
+
|
|
219
|
+
protocol _EnvironmentModifying {
|
|
220
|
+
func _apply(to environment: inout EnvironmentValues)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
struct _EnvironmentView<Content: View> {
|
|
224
|
+
let content: Content
|
|
225
|
+
let apply: (inout EnvironmentValues) -> Void
|
|
226
|
+
}
|
|
227
|
+
extension _EnvironmentView: _PrimitiveView {}
|
|
228
|
+
extension _EnvironmentView: _GroupView {
|
|
229
|
+
var _children: [any View] { [content] }
|
|
230
|
+
}
|
|
231
|
+
extension _EnvironmentView: _EnvironmentModifying {
|
|
232
|
+
func _apply(to environment: inout EnvironmentValues) { apply(&environment) }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
extension View {
|
|
236
|
+
public func environment<V>(_ keyPath: WritableKeyPath<EnvironmentValues, V>, _ value: V) -> some View {
|
|
237
|
+
_EnvironmentView(content: self) { $0[keyPath: keyPath] = value }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/// Supplies an `@Observable` object to `@Environment(T.self)` readers below.
|
|
241
|
+
public func environment<T: AnyObject & Observable>(_ object: T?) -> some View {
|
|
242
|
+
_EnvironmentView(content: self) { if let object { $0._setObject(object) } }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/// The color scheme this view wants for the whole window: the subtree
|
|
246
|
+
/// renders in it and the host switches the page's theme (a root `update`
|
|
247
|
+
/// with `colorScheme`). `nil` returns to the viewer's setting.
|
|
248
|
+
public func preferredColorScheme(_ colorScheme: ColorScheme?) -> some View {
|
|
249
|
+
_PreferredColorSchemeView(content: self, scheme: colorScheme)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
public func dynamicTypeSize(_ size: DynamicTypeSize) -> some View {
|
|
253
|
+
environment(\.dynamicTypeSize, size)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/// Clamps the Dynamic Type size the subtree sees to `range` (`...DynamicTypeSize.xxxLarge`).
|
|
257
|
+
public func dynamicTypeSize<T: RangeExpression>(_ range: T) -> some View where T.Bound == DynamicTypeSize {
|
|
258
|
+
_EnvironmentView(content: self) { environment in
|
|
259
|
+
let current = environment.dynamicTypeSize
|
|
260
|
+
if range.contains(current) { return }
|
|
261
|
+
// The nearest size inside the range.
|
|
262
|
+
let ordered = DynamicTypeSize.allCases.filter { range.contains($0) }
|
|
263
|
+
if let nearest = ordered.min(by: { abs($0.rawValue - current.rawValue) < abs($1.rawValue - current.rawValue) }) {
|
|
264
|
+
environment.dynamicTypeSize = nearest
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/// Read by the runtime, which tells the host; also sets the environment's scheme.
|
|
271
|
+
protocol _PreferredColorSchemeModifying {
|
|
272
|
+
var _preferredColorScheme: ColorScheme? { get }
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
struct _PreferredColorSchemeView<Content: View> {
|
|
276
|
+
let content: Content
|
|
277
|
+
let scheme: ColorScheme?
|
|
278
|
+
}
|
|
279
|
+
extension _PreferredColorSchemeView: _PrimitiveView {}
|
|
280
|
+
extension _PreferredColorSchemeView: _GroupView {
|
|
281
|
+
var _children: [any View] { [content] }
|
|
282
|
+
}
|
|
283
|
+
extension _PreferredColorSchemeView: _EnvironmentModifying {
|
|
284
|
+
func _apply(to environment: inout EnvironmentValues) {
|
|
285
|
+
if let scheme { environment.colorScheme = scheme }
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
extension _PreferredColorSchemeView: _PreferredColorSchemeModifying {
|
|
289
|
+
var _preferredColorScheme: ColorScheme? { scheme }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// MARK: - @Bindable
|
|
293
|
+
|
|
294
|
+
/// Creates bindings to the properties of an `@Observable` object.
|
|
295
|
+
@propertyWrapper
|
|
296
|
+
@dynamicMemberLookup
|
|
297
|
+
public struct Bindable<Value: AnyObject & Observable> {
|
|
298
|
+
public var wrappedValue: Value
|
|
299
|
+
|
|
300
|
+
public init(wrappedValue: Value) { self.wrappedValue = wrappedValue }
|
|
301
|
+
public init(_ wrappedValue: Value) { self.wrappedValue = wrappedValue }
|
|
302
|
+
|
|
303
|
+
public var projectedValue: Bindable<Value> { self }
|
|
304
|
+
|
|
305
|
+
public subscript<Subject>(dynamicMember keyPath: ReferenceWritableKeyPath<Value, Subject>) -> Binding<Subject> {
|
|
306
|
+
let object = wrappedValue
|
|
307
|
+
return Binding(get: { object[keyPath: keyPath] }, set: { object[keyPath: keyPath] = $0 })
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// MARK: - @ScaledMetric
|
|
312
|
+
|
|
313
|
+
/// A value that scales with the Dynamic Type size, like SwiftUI's. The scale
|
|
314
|
+
/// factors follow the body text style's step sizes (17pt at `large`).
|
|
315
|
+
@propertyWrapper
|
|
316
|
+
public struct ScaledMetric<Value: BinaryFloatingPoint>: DynamicProperty {
|
|
317
|
+
let base: Value
|
|
318
|
+
let box = _EnvironmentBox()
|
|
319
|
+
|
|
320
|
+
public init(wrappedValue: Value, relativeTo textStyle: Font.TextStyle = .body) {
|
|
321
|
+
base = wrappedValue
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
public var wrappedValue: Value {
|
|
325
|
+
let size = (box.value as? DynamicTypeSize) ?? .large
|
|
326
|
+
return base * Value(size._bodyScale)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
extension ScaledMetric: _EnvironmentProperty {
|
|
331
|
+
func _resolve(in environment: EnvironmentValues) {
|
|
332
|
+
box.value = environment.dynamicTypeSize
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
extension DynamicTypeSize {
|
|
337
|
+
/// Body point size at this setting divided by the default 17.
|
|
338
|
+
var _bodyScale: Double {
|
|
339
|
+
let bodySize: Double = switch self {
|
|
340
|
+
case .xSmall: 14
|
|
341
|
+
case .small: 15
|
|
342
|
+
case .medium: 16
|
|
343
|
+
case .large: 17
|
|
344
|
+
case .xLarge: 19
|
|
345
|
+
case .xxLarge: 21
|
|
346
|
+
case .xxxLarge: 23
|
|
347
|
+
case .accessibility1: 28
|
|
348
|
+
case .accessibility2: 33
|
|
349
|
+
case .accessibility3: 40
|
|
350
|
+
case .accessibility4: 47
|
|
351
|
+
case .accessibility5: 53
|
|
352
|
+
}
|
|
353
|
+
return bodySize / 17
|
|
354
|
+
}
|
|
355
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/// `@FocusState`, `.focused(_:)`, `.onSubmit` and `.submitLabel` for text
|
|
2
|
+
/// fields (docs/ops-protocol.md, "Phase 12"). A field inside `.focused(...)`
|
|
3
|
+
/// carries a `focused` prop the host honors (focusing or blurring its input)
|
|
4
|
+
/// and reports `focus` events back; a field inside `.onSubmit` carries
|
|
5
|
+
/// `submit: true` and reports `submit` events (the Enter key).
|
|
6
|
+
|
|
7
|
+
// MARK: - @FocusState
|
|
8
|
+
|
|
9
|
+
/// Which view has keyboard focus: a `Bool` for one field, or an optional
|
|
10
|
+
/// `Hashable` naming one of several. Storage is a `State`, so it persists
|
|
11
|
+
/// across re-renders like SwiftUI's.
|
|
12
|
+
@propertyWrapper
|
|
13
|
+
public struct FocusState<Value: Hashable>: DynamicProperty {
|
|
14
|
+
let state: State<Value>
|
|
15
|
+
|
|
16
|
+
public init() where Value == Bool {
|
|
17
|
+
state = State(wrappedValue: false)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public init<T: Hashable>() where Value == T? {
|
|
21
|
+
state = State(wrappedValue: nil)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public var wrappedValue: Value {
|
|
25
|
+
get { state.wrappedValue }
|
|
26
|
+
nonmutating set { state.wrappedValue = newValue }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public var projectedValue: FocusState<Value>.Binding { Binding(state: state) }
|
|
30
|
+
|
|
31
|
+
/// `$focus`: what `.focused(_:)` and `.focused(_:equals:)` take.
|
|
32
|
+
@propertyWrapper
|
|
33
|
+
public struct Binding {
|
|
34
|
+
let state: State<Value>
|
|
35
|
+
|
|
36
|
+
public var wrappedValue: Value {
|
|
37
|
+
get { state.wrappedValue }
|
|
38
|
+
nonmutating set { state.wrappedValue = newValue }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public var projectedValue: FocusState<Value>.Binding { self }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
extension FocusState: _StateProperty {
|
|
46
|
+
var _box: any _AnyStateBox { state.box }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// What a focused field reads from its environment: whether it should have
|
|
50
|
+
/// focus now, and how to record a focus change the host reports.
|
|
51
|
+
struct _FocusRequest {
|
|
52
|
+
let isFocused: () -> Bool
|
|
53
|
+
let setFocused: (Bool) -> Void
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
enum _FocusKey: EnvironmentKey { nonisolated(unsafe) static let defaultValue: _FocusRequest? = nil }
|
|
57
|
+
enum _OnSubmitKey: EnvironmentKey { nonisolated(unsafe) static let defaultValue: (() -> Void)? = nil }
|
|
58
|
+
enum _SubmitLabelKey: EnvironmentKey { static let defaultValue: String? = nil }
|
|
59
|
+
|
|
60
|
+
extension EnvironmentValues {
|
|
61
|
+
var _focus: _FocusRequest? {
|
|
62
|
+
get { self[_FocusKey.self] }
|
|
63
|
+
set { self[_FocusKey.self] = newValue }
|
|
64
|
+
}
|
|
65
|
+
var _onSubmit: (() -> Void)? {
|
|
66
|
+
get { self[_OnSubmitKey.self] }
|
|
67
|
+
set { self[_OnSubmitKey.self] = newValue }
|
|
68
|
+
}
|
|
69
|
+
var _submitLabel: String? {
|
|
70
|
+
get { self[_SubmitLabelKey.self] }
|
|
71
|
+
set { self[_SubmitLabelKey.self] = newValue }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
extension View {
|
|
76
|
+
/// Binds the field's focus to a `@FocusState var isFocused: Bool`.
|
|
77
|
+
public func focused(_ binding: FocusState<Bool>.Binding) -> some View {
|
|
78
|
+
_EnvironmentView(content: self) {
|
|
79
|
+
$0._focus = _FocusRequest(isFocused: { binding.wrappedValue }, setFocused: { binding.wrappedValue = $0 })
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Binds the field's focus to one case of a `@FocusState var field: Field?`.
|
|
84
|
+
public func focused<Value: Hashable>(_ binding: FocusState<Value?>.Binding, equals value: Value) -> some View {
|
|
85
|
+
_EnvironmentView(content: self) {
|
|
86
|
+
$0._focus = _FocusRequest(
|
|
87
|
+
isFocused: { binding.wrappedValue == value },
|
|
88
|
+
setFocused: { focused in
|
|
89
|
+
if focused {
|
|
90
|
+
binding.wrappedValue = value
|
|
91
|
+
} else if binding.wrappedValue == value {
|
|
92
|
+
binding.wrappedValue = nil
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// MARK: - onSubmit / submitLabel
|
|
100
|
+
|
|
101
|
+
public struct SubmitTriggers: OptionSet, Sendable {
|
|
102
|
+
public let rawValue: Int
|
|
103
|
+
public init(rawValue: Int) { self.rawValue = rawValue }
|
|
104
|
+
public static let text = SubmitTriggers(rawValue: 1)
|
|
105
|
+
public static let search = SubmitTriggers(rawValue: 2)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// The label of the keyboard's return key (`enterkeyhint` in the browser).
|
|
109
|
+
public enum SubmitLabel: String, Hashable, Sendable {
|
|
110
|
+
case done, go, send, join, route, search, `return`, next, `continue`
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
extension View {
|
|
114
|
+
/// Runs `action` when a text field in this view submits (the Enter key).
|
|
115
|
+
public func onSubmit(of triggers: SubmitTriggers = .text, _ action: @escaping () -> Void) -> some View {
|
|
116
|
+
_EnvironmentView(content: self) { $0._onSubmit = action }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public func submitLabel(_ label: SubmitLabel) -> some View {
|
|
120
|
+
_EnvironmentView(content: self) { $0._submitLabel = label.rawValue }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// A host view whose event handler needs the environment (the focus and
|
|
125
|
+
/// submit closures a text field inherits); checked by the runtime ahead of
|
|
126
|
+
/// `_HostView._handler`.
|
|
127
|
+
protocol _EnvironmentHandling {
|
|
128
|
+
func _handler(in environment: EnvironmentValues) -> ((_JSON) -> Void)?
|
|
129
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
public struct Font: Hashable, Sendable {
|
|
2
|
+
public struct Weight: Hashable, Sendable {
|
|
3
|
+
let name: String
|
|
4
|
+
public static let ultraLight = Weight(name: "ultraLight")
|
|
5
|
+
public static let thin = Weight(name: "thin")
|
|
6
|
+
public static let light = Weight(name: "light")
|
|
7
|
+
public static let regular = Weight(name: "regular")
|
|
8
|
+
public static let medium = Weight(name: "medium")
|
|
9
|
+
public static let semibold = Weight(name: "semibold")
|
|
10
|
+
public static let bold = Weight(name: "bold")
|
|
11
|
+
public static let heavy = Weight(name: "heavy")
|
|
12
|
+
public static let black = Weight(name: "black")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public enum Design: Hashable, Sendable {
|
|
16
|
+
case `default`, serif, rounded, monospaced
|
|
17
|
+
|
|
18
|
+
var name: String {
|
|
19
|
+
switch self {
|
|
20
|
+
case .default: "default"
|
|
21
|
+
case .serif: "serif"
|
|
22
|
+
case .rounded: "rounded"
|
|
23
|
+
case .monospaced: "monospaced"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public enum TextStyle: Hashable, Sendable, CaseIterable {
|
|
29
|
+
case largeTitle, title, title2, title3, headline, subheadline
|
|
30
|
+
case body, callout, footnote, caption, caption2
|
|
31
|
+
|
|
32
|
+
var name: String {
|
|
33
|
+
switch self {
|
|
34
|
+
case .largeTitle: "largeTitle"
|
|
35
|
+
case .title: "title"
|
|
36
|
+
case .title2: "title2"
|
|
37
|
+
case .title3: "title3"
|
|
38
|
+
case .headline: "headline"
|
|
39
|
+
case .subheadline: "subheadline"
|
|
40
|
+
case .body: "body"
|
|
41
|
+
case .callout: "callout"
|
|
42
|
+
case .footnote: "footnote"
|
|
43
|
+
case .caption: "caption"
|
|
44
|
+
case .caption2: "caption2"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Either a text style (scaled by Dynamic Type in the renderer) or a fixed size.
|
|
50
|
+
var textStyle: TextStyle?
|
|
51
|
+
var size: CGFloat?
|
|
52
|
+
var relativeTo: TextStyle?
|
|
53
|
+
var weight: Weight?
|
|
54
|
+
var design: Design?
|
|
55
|
+
|
|
56
|
+
public static let largeTitle = Font(textStyle: .largeTitle)
|
|
57
|
+
public static let title = Font(textStyle: .title)
|
|
58
|
+
public static let title2 = Font(textStyle: .title2)
|
|
59
|
+
public static let title3 = Font(textStyle: .title3)
|
|
60
|
+
public static let headline = Font(textStyle: .headline)
|
|
61
|
+
public static let subheadline = Font(textStyle: .subheadline)
|
|
62
|
+
public static let body = Font(textStyle: .body)
|
|
63
|
+
public static let callout = Font(textStyle: .callout)
|
|
64
|
+
public static let footnote = Font(textStyle: .footnote)
|
|
65
|
+
public static let caption = Font(textStyle: .caption)
|
|
66
|
+
public static let caption2 = Font(textStyle: .caption2)
|
|
67
|
+
|
|
68
|
+
init(textStyle: TextStyle) {
|
|
69
|
+
self.textStyle = textStyle
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
init(size: CGFloat?, weight: Weight?, design: Design?) {
|
|
73
|
+
self.size = size
|
|
74
|
+
self.weight = weight
|
|
75
|
+
self.design = design
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public static func system(_ style: TextStyle, design: Design? = nil, weight: Weight? = nil) -> Font {
|
|
79
|
+
var font = Font(textStyle: style)
|
|
80
|
+
font.weight = weight
|
|
81
|
+
font.design = design
|
|
82
|
+
return font
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public static func system(size: CGFloat, weight: Weight? = nil, design: Design? = nil) -> Font {
|
|
86
|
+
Font(size: size, weight: weight, design: design)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// A custom font. The browser cannot load app fonts, so the name is
|
|
90
|
+
/// accepted for source compatibility and the system font is used.
|
|
91
|
+
public static func custom(_ name: String, size: CGFloat) -> Font {
|
|
92
|
+
var font = Font(size: size, weight: nil, design: nil)
|
|
93
|
+
font.relativeTo = .body
|
|
94
|
+
return font
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// A custom font whose size scales with Dynamic Type like `textStyle`.
|
|
98
|
+
public static func custom(_ name: String, size: CGFloat, relativeTo textStyle: TextStyle) -> Font {
|
|
99
|
+
var font = Font(size: size, weight: nil, design: nil)
|
|
100
|
+
font.relativeTo = textStyle
|
|
101
|
+
return font
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// A custom font at a fixed size that does not scale.
|
|
105
|
+
public static func custom(_ name: String, fixedSize: CGFloat) -> Font {
|
|
106
|
+
Font(size: fixedSize, weight: nil, design: nil)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public func weight(_ weight: Weight) -> Font {
|
|
110
|
+
var copy = self
|
|
111
|
+
copy.weight = weight
|
|
112
|
+
return copy
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public func bold() -> Font { weight(.bold) }
|
|
116
|
+
public func monospaced() -> Font {
|
|
117
|
+
var copy = self
|
|
118
|
+
copy.design = .monospaced
|
|
119
|
+
return copy
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
var _json: _JSON {
|
|
123
|
+
var object: [String: _JSON] = [:]
|
|
124
|
+
if let textStyle { object["textStyle"] = .string(textStyle.name) }
|
|
125
|
+
if let size { object["size"] = .number(size) }
|
|
126
|
+
if let relativeTo { object["relativeTo"] = .string(relativeTo.name) }
|
|
127
|
+
if let weight { object["weight"] = .string(weight.name) }
|
|
128
|
+
if let design { object["design"] = .string(design.name) }
|
|
129
|
+
return .object(object)
|
|
130
|
+
}
|
|
131
|
+
}
|