@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,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
+ }