@swiftbrowser/native 0.0.0-stage → 0.10.1
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/README.md +143 -2
- package/bin/swiftbrowser-native.mjs +49 -0
- package/dist/bridge.d.ts +50 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +10 -0
- package/dist/bridge.js.map +1 -0
- package/dist/build.d.ts +47 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +156 -0
- package/dist/build.js.map +1 -0
- package/dist/devBuild.d.ts +34 -0
- package/dist/devBuild.d.ts.map +1 -0
- package/dist/devBuild.js +133 -0
- package/dist/devBuild.js.map +1 -0
- package/dist/encoding.d.ts +32 -0
- package/dist/encoding.d.ts.map +1 -0
- package/dist/encoding.js +136 -0
- package/dist/encoding.js.map +1 -0
- package/dist/fetch.d.ts +117 -0
- package/dist/fetch.d.ts.map +1 -0
- package/dist/fetch.js +272 -0
- package/dist/fetch.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/native.d.ts +19 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +17 -0
- package/dist/native.js.map +1 -0
- package/dist/refresh.d.ts +18 -0
- package/dist/refresh.d.ts.map +1 -0
- package/dist/refresh.js +18 -0
- package/dist/refresh.js.map +1 -0
- package/dist/run.d.ts +27 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +76 -0
- package/dist/run.js.map +1 -0
- package/dist/runtime.d.ts +30 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +193 -0
- package/dist/runtime.js.map +1 -0
- package/dist/stubs/react-dom-client.d.ts +8 -0
- package/dist/stubs/react-dom-client.d.ts.map +1 -0
- package/dist/stubs/react-dom-client.js +11 -0
- package/dist/stubs/react-dom-client.js.map +1 -0
- package/dist/stubs/react-dom.d.ts +9 -0
- package/dist/stubs/react-dom.d.ts.map +1 -0
- package/dist/stubs/react-dom.js +13 -0
- package/dist/stubs/react-dom.js.map +1 -0
- package/dist/url.d.ts +48 -0
- package/dist/url.d.ts.map +1 -0
- package/dist/url.js +213 -0
- package/dist/url.js.map +1 -0
- package/dist/vendor.d.ts +11 -0
- package/dist/vendor.d.ts.map +1 -0
- package/dist/vendor.js +27 -0
- package/dist/vendor.js.map +1 -0
- package/dist/wasmHost.d.ts +35 -0
- package/dist/wasmHost.d.ts.map +1 -0
- package/dist/wasmHost.js +120 -0
- package/dist/wasmHost.js.map +1 -0
- package/package.json +73 -4
- package/src/bridge.ts +58 -0
- package/src/build.ts +185 -0
- package/src/devBuild.ts +152 -0
- package/src/encoding.ts +130 -0
- package/src/fetch.ts +305 -0
- package/src/index.ts +20 -0
- package/src/native.ts +35 -0
- package/src/refresh.ts +29 -0
- package/src/run.ts +98 -0
- package/src/runtime.ts +208 -0
- package/src/stubs/react-dom-client.ts +17 -0
- package/src/stubs/react-dom.ts +15 -0
- package/src/types/react-refresh.d.ts +38 -0
- package/src/url.ts +202 -0
- package/src/vendor.ts +30 -0
- package/src/wasmHost.ts +147 -0
- package/swift/Package.swift +31 -0
- package/swift/Sources/SwiftBrowserNative/Host/HostBridge.swift +228 -0
- package/swift/Sources/SwiftBrowserNative/Host/JSHost.swift +42 -0
- package/swift/Sources/SwiftBrowserNative/Host/JavaScriptCoreHost.swift +102 -0
- package/swift/Sources/SwiftBrowserNative/Host/ShimAliases.swift +25 -0
- package/swift/Sources/SwiftBrowserNative/Host/WebHost.swift +138 -0
- package/swift/Sources/SwiftBrowserNative/Runtime/NativeApp.swift +251 -0
- package/swift/Sources/SwiftBrowserNative/Runtime/NativeRegistry.swift +53 -0
- package/swift/Sources/SwiftBrowserNative/SwiftBrowserNative.swift +6 -0
- package/swift/Sources/SwiftBrowserNative/Views/Conversions.swift +361 -0
- package/swift/Sources/SwiftBrowserNative/Views/ElementView.swift +169 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Controls.swift +268 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Lists.swift +175 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/More.swift +196 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Navigation.swift +246 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Shapes.swift +108 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Stacks.swift +189 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/TextAndImages.swift +102 -0
- package/swift/Sources/SwiftBrowserNative/Views/NativeRootView.swift +190 -0
- package/swift/Sources/SwiftBrowserNative/Views/StyleModifier.swift +182 -0
- package/swift/Sources/SwiftBrowserProtocol/JSONValue.swift +268 -0
- package/swift/Sources/SwiftBrowserProtocol/Op.swift +100 -0
- package/swift/Sources/SwiftBrowserProtocol/Props.swift +469 -0
- package/swift/Sources/SwiftBrowserProtocol/RendererEvent.swift +117 -0
- package/swift/Sources/SwiftBrowserProtocol/Style.swift +148 -0
- package/swift/Sources/SwiftBrowserProtocol/Values.swift +279 -0
- package/swift/Sources/SwiftBrowserTree/ElementNode.swift +65 -0
- package/swift/Sources/SwiftBrowserTree/OpTree.swift +168 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
// Typed readers over an element's props, one per kind a renderer draws
|
|
4
|
+
// (docs/ops-protocol.md, "Element kinds and props"). Each reads the fields
|
|
5
|
+
// the protocol defines with the protocol's defaults, from the JSON the tree
|
|
6
|
+
// keeps; `init(props:)` never fails, since a renderer draws what it can.
|
|
7
|
+
|
|
8
|
+
public struct TextProps: Equatable, Sendable {
|
|
9
|
+
public var text: String
|
|
10
|
+
/// Rich text (`runs`), when the text has styled runs; empty for plain text.
|
|
11
|
+
public var runs: [TextRunSpec]
|
|
12
|
+
|
|
13
|
+
public init(props: JSONObject) {
|
|
14
|
+
text = props.string("text") ?? ""
|
|
15
|
+
runs = (props.array("runs") ?? []).compactMap(TextRunSpec.init(json:))
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// One run of a rich `text`: its text or inline image, and the attributes over the text's own.
|
|
20
|
+
public struct TextRunSpec: Equatable, Sendable {
|
|
21
|
+
public var text: String?
|
|
22
|
+
public var image: ImageProps?
|
|
23
|
+
public var font: FontSpec?
|
|
24
|
+
public var foreground: ShapeStyleSpec?
|
|
25
|
+
public var italic: Bool
|
|
26
|
+
public var underline: Bool
|
|
27
|
+
public var underlineColor: ColorSpec?
|
|
28
|
+
public var strikethrough: Bool
|
|
29
|
+
public var strikethroughColor: ColorSpec?
|
|
30
|
+
public var kerning: Double?
|
|
31
|
+
public var baselineOffset: Double?
|
|
32
|
+
public var link: String?
|
|
33
|
+
|
|
34
|
+
public init?(json: JSONValue) {
|
|
35
|
+
guard let fields = json.object else { return nil }
|
|
36
|
+
text = fields.string("text")
|
|
37
|
+
image = fields.object("image").map(ImageProps.init(props:))
|
|
38
|
+
font = FontSpec(json: fields.value("font"))
|
|
39
|
+
foreground = ShapeStyleSpec(json: fields.value("foreground"))
|
|
40
|
+
italic = fields.bool("italic") ?? false
|
|
41
|
+
underline = fields["underline"] != nil
|
|
42
|
+
underlineColor = ColorSpec(json: fields.value("underline")["color"])
|
|
43
|
+
strikethrough = fields["strikethrough"] != nil
|
|
44
|
+
strikethroughColor = ColorSpec(json: fields.value("strikethrough")["color"])
|
|
45
|
+
kerning = fields.double("kerning")
|
|
46
|
+
baselineOffset = fields.double("baselineOffset")
|
|
47
|
+
link = fields.string("link")
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public struct StackProps: Equatable, Sendable {
|
|
52
|
+
/// Nil is the system default spacing.
|
|
53
|
+
public var spacing: Double?
|
|
54
|
+
/// A `VStack`'s horizontal alignment, an `HStack`'s vertical one, a `ZStack`'s frame alignment.
|
|
55
|
+
public var alignment: String
|
|
56
|
+
|
|
57
|
+
public init(props: JSONObject) {
|
|
58
|
+
spacing = props.double("spacing")
|
|
59
|
+
alignment = props.string("alignment") ?? "center"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public struct SpacerProps: Equatable, Sendable {
|
|
64
|
+
public var minLength: Double?
|
|
65
|
+
public init(props: JSONObject) { minLength = props.double("minLength") }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public struct ButtonProps: Equatable, Sendable {
|
|
69
|
+
/// `automatic`, `plain`, `bordered`, `borderedProminent`, `glass`.
|
|
70
|
+
public var style: String
|
|
71
|
+
/// `destructive`, `cancel` or nil.
|
|
72
|
+
public var role: String?
|
|
73
|
+
public var controlSize: String?
|
|
74
|
+
/// An `EditButton`: `active` or `inactive`, the mode it shows and flips.
|
|
75
|
+
public var editMode: String?
|
|
76
|
+
|
|
77
|
+
public init(props: JSONObject) {
|
|
78
|
+
style = props.string("style") ?? "automatic"
|
|
79
|
+
role = props.string("role")
|
|
80
|
+
controlSize = props.string("controlSize")
|
|
81
|
+
editMode = props.string("editMode")
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// An `image`: an SF Symbol, an asset catalog image, a remote URL or a bitmap recipe.
|
|
86
|
+
public enum ImageProps: Equatable, Sendable {
|
|
87
|
+
case symbol(name: String, paletteRendering: Bool)
|
|
88
|
+
case asset(name: String, resizable: Bool)
|
|
89
|
+
case url(String, resizable: Bool, width: Double, height: Double)
|
|
90
|
+
case bitmap(recipe: JSONValue, resizable: Bool, width: Double, height: Double)
|
|
91
|
+
case unknown
|
|
92
|
+
|
|
93
|
+
public init(props: JSONObject) {
|
|
94
|
+
let resizable = props.bool("resizable") ?? false
|
|
95
|
+
if let name = props.string("systemName") {
|
|
96
|
+
self = .symbol(name: name, paletteRendering: props.string("renderingMode") == "palette")
|
|
97
|
+
} else if let name = props.string("name") {
|
|
98
|
+
self = .asset(name: name, resizable: resizable)
|
|
99
|
+
} else if let url = props.string("url") {
|
|
100
|
+
self = .url(url, resizable: resizable, width: props.double("width") ?? 0, height: props.double("height") ?? 0)
|
|
101
|
+
} else if let recipe = props["bitmap"] {
|
|
102
|
+
self = .bitmap(recipe: recipe, resizable: resizable, width: props.double("width") ?? 0, height: props.double("height") ?? 0)
|
|
103
|
+
} else {
|
|
104
|
+
self = .unknown
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public struct ScrollViewProps: Equatable, Sendable {
|
|
110
|
+
/// `vertical`, `horizontal` or `both`.
|
|
111
|
+
public var axes: String
|
|
112
|
+
public var showsIndicators: Bool
|
|
113
|
+
public var refreshable: Bool
|
|
114
|
+
public init(props: JSONObject) {
|
|
115
|
+
axes = props.string("axes") ?? "vertical"
|
|
116
|
+
showsIndicators = props.bool("showsIndicators") ?? true
|
|
117
|
+
refreshable = props.bool("refreshable") ?? false
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public struct ListProps: Equatable, Sendable {
|
|
122
|
+
/// `insetGrouped` (the default), `grouped`, `plain`, `sidebar`.
|
|
123
|
+
public var style: String
|
|
124
|
+
public var refreshable: Bool
|
|
125
|
+
public var editing: Bool?
|
|
126
|
+
public init(props: JSONObject) {
|
|
127
|
+
style = props.string("style") ?? "insetGrouped"
|
|
128
|
+
refreshable = props.bool("refreshable") ?? false
|
|
129
|
+
editing = props.bool("editing")
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public struct SectionProps: Equatable, Sendable {
|
|
134
|
+
public var header: String?
|
|
135
|
+
public var footer: String?
|
|
136
|
+
public init(props: JSONObject) {
|
|
137
|
+
header = props.string("header")
|
|
138
|
+
footer = props.string("footer")
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public struct TextFieldProps: Equatable, Sendable {
|
|
143
|
+
public var text: String
|
|
144
|
+
public var placeholder: String
|
|
145
|
+
/// `plain` or `roundedBorder`.
|
|
146
|
+
public var style: String
|
|
147
|
+
public var secure: Bool
|
|
148
|
+
public var focused: Bool?
|
|
149
|
+
public var submit: Bool
|
|
150
|
+
public var submitLabel: String?
|
|
151
|
+
public var vertical: Bool
|
|
152
|
+
public init(props: JSONObject) {
|
|
153
|
+
text = props.string("text") ?? ""
|
|
154
|
+
placeholder = props.string("placeholder") ?? ""
|
|
155
|
+
style = props.string("style") ?? "plain"
|
|
156
|
+
secure = props.bool("secure") ?? false
|
|
157
|
+
focused = props.bool("focused")
|
|
158
|
+
submit = props.bool("submit") ?? false
|
|
159
|
+
submitLabel = props.string("submitLabel")
|
|
160
|
+
vertical = props.string("axis") == "vertical"
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
public struct ToggleProps: Equatable, Sendable {
|
|
165
|
+
public var isOn: Bool
|
|
166
|
+
public init(props: JSONObject) { isOn = props.bool("isOn") ?? false }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public struct NavScreenProps: Equatable, Sendable {
|
|
170
|
+
public var title: String
|
|
171
|
+
/// `automatic`, `large` or `inline`.
|
|
172
|
+
public var displayMode: String
|
|
173
|
+
/// 0 for the stack's root screen.
|
|
174
|
+
public var depth: Int
|
|
175
|
+
public var toolbarRole: String?
|
|
176
|
+
public var backButtonHidden: Bool
|
|
177
|
+
public init(props: JSONObject) {
|
|
178
|
+
title = props.string("title") ?? ""
|
|
179
|
+
displayMode = props.string("displayMode") ?? "automatic"
|
|
180
|
+
depth = props.int("depth") ?? 0
|
|
181
|
+
toolbarRole = props.string("toolbarRole")
|
|
182
|
+
backButtonHidden = props.bool("backButtonHidden") ?? false
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public struct NavLinkProps: Equatable, Sendable {
|
|
187
|
+
public var selected: Bool
|
|
188
|
+
public var plain: Bool
|
|
189
|
+
public init(props: JSONObject) {
|
|
190
|
+
selected = props.bool("selected") ?? false
|
|
191
|
+
plain = props.string("style") == "plain"
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public struct ColorProps: Equatable, Sendable {
|
|
196
|
+
public var color: ColorSpec?
|
|
197
|
+
public init(props: JSONObject) { color = ColorSpec(json: props.value("color")) }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
public struct StrokeSpec: Equatable, Sendable {
|
|
201
|
+
public var style: ShapeStyleSpec?
|
|
202
|
+
public var lineWidth: Double
|
|
203
|
+
public var inset: Bool
|
|
204
|
+
public var dash: [Double]
|
|
205
|
+
public init?(json: JSONValue) {
|
|
206
|
+
guard let fields = json.object else { return nil }
|
|
207
|
+
style = ShapeStyleSpec(json: fields.value("color"))
|
|
208
|
+
lineWidth = fields.double("lineWidth") ?? 1
|
|
209
|
+
inset = fields.bool("inset") ?? false
|
|
210
|
+
dash = (fields.array("dash") ?? []).compactMap(\.double)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
public struct ShapeProps: Equatable, Sendable {
|
|
215
|
+
/// `rectangle`, `roundedRectangle`, `circle`, `capsule`, `ellipse`, `containerRelative`, `path`.
|
|
216
|
+
public var shape: String
|
|
217
|
+
public var cornerRadius: Double
|
|
218
|
+
/// Nil fills with the current foreground; `.some(nil)` is an explicit no fill.
|
|
219
|
+
public var fill: ShapeStyleSpec?
|
|
220
|
+
public var fillIsNull: Bool
|
|
221
|
+
public var stroke: StrokeSpec?
|
|
222
|
+
public var trimFrom: Double?
|
|
223
|
+
public var trimTo: Double?
|
|
224
|
+
public var path: String?
|
|
225
|
+
/// The size `path` was drawn for.
|
|
226
|
+
public var size: SizeSpec?
|
|
227
|
+
public init(props: JSONObject) {
|
|
228
|
+
shape = props.string("shape") ?? "rectangle"
|
|
229
|
+
cornerRadius = props.double("cornerRadius") ?? 0
|
|
230
|
+
fill = ShapeStyleSpec(json: props.value("fill"))
|
|
231
|
+
fillIsNull = props["fill"]?.isNull ?? true
|
|
232
|
+
stroke = StrokeSpec(json: props.value("stroke"))
|
|
233
|
+
trimFrom = props.object("trim")?.double("from")
|
|
234
|
+
trimTo = props.object("trim")?.double("to")
|
|
235
|
+
path = props.string("path")
|
|
236
|
+
size = SizeSpec(json: props.value("size"))
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public struct SheetProps: Equatable, Sendable {
|
|
241
|
+
/// `medium`, `large`, `full`, `height:<points>`, `fraction:<f>`.
|
|
242
|
+
public var detents: [String]
|
|
243
|
+
public var interactiveDismissDisabled: Bool
|
|
244
|
+
public init(props: JSONObject) {
|
|
245
|
+
detents = (props.array("detents") ?? []).compactMap(\.string)
|
|
246
|
+
interactiveDismissDisabled = props.bool("interactiveDismissDisabled") ?? false
|
|
247
|
+
}
|
|
248
|
+
public var isFullScreen: Bool { detents == ["full"] }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
public struct TabViewProps: Equatable, Sendable {
|
|
252
|
+
public var selected: Int
|
|
253
|
+
public init(props: JSONObject) { selected = props.int("selected") ?? 0 }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
public struct TabProps: Equatable, Sendable {
|
|
257
|
+
public var title: String
|
|
258
|
+
public var systemImage: String?
|
|
259
|
+
public var image: String?
|
|
260
|
+
public var badge: String?
|
|
261
|
+
public var isSearch: Bool
|
|
262
|
+
public init(props: JSONObject) {
|
|
263
|
+
title = props.string("title") ?? ""
|
|
264
|
+
systemImage = props.string("systemImage")
|
|
265
|
+
image = props.string("image")
|
|
266
|
+
badge = props.string("badge")
|
|
267
|
+
isSearch = props.string("role") == "search"
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
public struct PickerProps: Equatable, Sendable {
|
|
272
|
+
/// `segmented`, `menu`, `inline`, `wheel`.
|
|
273
|
+
public var style: String
|
|
274
|
+
public var label: String
|
|
275
|
+
public var options: [String]
|
|
276
|
+
public var selected: Int
|
|
277
|
+
public init(props: JSONObject) {
|
|
278
|
+
style = props.string("style") ?? "menu"
|
|
279
|
+
label = props.string("label") ?? ""
|
|
280
|
+
options = (props.array("options") ?? []).compactMap(\.string)
|
|
281
|
+
selected = props.int("selected") ?? 0
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
public struct GeometryProps: Equatable, Sendable {
|
|
286
|
+
public var reportsFrame: Bool
|
|
287
|
+
public var reader: Int?
|
|
288
|
+
public init(props: JSONObject) {
|
|
289
|
+
reportsFrame = props.bool("reportsFrame") ?? false
|
|
290
|
+
reader = props.int("reader")
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
public struct ListRowProps: Equatable, Sendable {
|
|
295
|
+
public var selectable: Bool
|
|
296
|
+
public var selected: Bool
|
|
297
|
+
public var deletable: Bool
|
|
298
|
+
public var movable: Bool
|
|
299
|
+
public init(props: JSONObject) {
|
|
300
|
+
selectable = props.bool("selectable") ?? false
|
|
301
|
+
selected = props.bool("selected") ?? false
|
|
302
|
+
deletable = props.bool("deletable") ?? false
|
|
303
|
+
movable = props.bool("movable") ?? false
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
public struct ToolbarProps: Equatable, Sendable {
|
|
308
|
+
/// `trailing`, `leading`, `principal`, `bottom`, `secondary`, `largeTitle`.
|
|
309
|
+
public var placement: String
|
|
310
|
+
/// A `ToolbarSpacer`: `fixed` or `flexible`; the element has no items.
|
|
311
|
+
public var spacer: String?
|
|
312
|
+
/// `hidden` for `.sharedBackgroundVisibility(.hidden)`: no glass of the bar's around the items.
|
|
313
|
+
public var sharedBackground: String?
|
|
314
|
+
public init(props: JSONObject) {
|
|
315
|
+
placement = props.string("placement") ?? "trailing"
|
|
316
|
+
spacer = props.string("spacer")
|
|
317
|
+
sharedBackground = props.string("sharedBackground")
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
public struct SearchFieldProps: Equatable, Sendable {
|
|
322
|
+
public var text: String
|
|
323
|
+
public var placeholder: String
|
|
324
|
+
public init(props: JSONObject) {
|
|
325
|
+
text = props.string("text") ?? ""
|
|
326
|
+
placeholder = props.string("placeholder") ?? "Search"
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
public struct DecoratedProps: Equatable, Sendable {
|
|
331
|
+
/// `overlay` or `background`.
|
|
332
|
+
public var role: String
|
|
333
|
+
public var alignment: String
|
|
334
|
+
public init(props: JSONObject) {
|
|
335
|
+
role = props.string("role") ?? "overlay"
|
|
336
|
+
alignment = props.string("alignment") ?? "center"
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
public struct AlertProps: Equatable, Sendable {
|
|
341
|
+
public var title: String
|
|
342
|
+
public var message: String?
|
|
343
|
+
/// `alert` or `confirmationDialog`.
|
|
344
|
+
public var style: String
|
|
345
|
+
public init(props: JSONObject) {
|
|
346
|
+
title = props.string("title") ?? ""
|
|
347
|
+
message = props.string("message")
|
|
348
|
+
style = props.string("style") ?? "alert"
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
public struct StepperProps: Equatable, Sendable {
|
|
353
|
+
public var canIncrement: Bool
|
|
354
|
+
public var canDecrement: Bool
|
|
355
|
+
public init(props: JSONObject) {
|
|
356
|
+
canIncrement = props.bool("canIncrement") ?? true
|
|
357
|
+
canDecrement = props.bool("canDecrement") ?? true
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
public struct SliderProps: Equatable, Sendable {
|
|
362
|
+
public var value: Double
|
|
363
|
+
public var min: Double
|
|
364
|
+
public var max: Double
|
|
365
|
+
public var step: Double?
|
|
366
|
+
public init(props: JSONObject) {
|
|
367
|
+
value = props.double("value") ?? 0
|
|
368
|
+
min = props.double("min") ?? 0
|
|
369
|
+
max = props.double("max") ?? 1
|
|
370
|
+
step = props.double("step")
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
public struct TapGestureProps: Equatable, Sendable {
|
|
375
|
+
public var location: Bool
|
|
376
|
+
public init(props: JSONObject) { location = props.bool("location") ?? false }
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/// The producer-only `native` kind (docs/native.md, "The native escape hatch").
|
|
380
|
+
public struct NativeProps: Equatable, Sendable {
|
|
381
|
+
public var name: String
|
|
382
|
+
public var props: JSONValue
|
|
383
|
+
public init(props: JSONObject) {
|
|
384
|
+
name = props.string("name") ?? ""
|
|
385
|
+
self.props = props.value("props")
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/// The root container's props (`update` of id 0): `.preferredColorScheme`.
|
|
390
|
+
public struct RootProps: Equatable, Sendable {
|
|
391
|
+
/// `light`, `dark`, or nil for the viewer's setting.
|
|
392
|
+
public var colorScheme: String?
|
|
393
|
+
public init(props: JSONObject) { colorScheme = props.string("colorScheme") }
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
public struct GaugeProps: Equatable, Sendable {
|
|
397
|
+
/// Normalized to 0...1.
|
|
398
|
+
public var value: Double
|
|
399
|
+
/// `automatic`, `linearCapacity`, `accessoryLinear`, `accessoryCircular`, `accessoryCircularCapacity`, `progress`.
|
|
400
|
+
public var style: String
|
|
401
|
+
public var hasLabel: Bool
|
|
402
|
+
public var hasCurrentValueLabel: Bool
|
|
403
|
+
public init(props: JSONObject) {
|
|
404
|
+
value = min(1, max(0, props.double("value") ?? 0))
|
|
405
|
+
style = props.string("style") ?? "automatic"
|
|
406
|
+
hasLabel = props.bool("hasLabel") ?? false
|
|
407
|
+
hasCurrentValueLabel = props.bool("hasCurrentValueLabel") ?? false
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
public struct DatePickerProps: Equatable, Sendable {
|
|
412
|
+
/// Seconds since 1970.
|
|
413
|
+
public var seconds: Double
|
|
414
|
+
/// `date`, `hourAndMinute`, `dateAndTime`.
|
|
415
|
+
public var components: String
|
|
416
|
+
public var min: Double?
|
|
417
|
+
public var max: Double?
|
|
418
|
+
public var timeZone: String?
|
|
419
|
+
public var labelsHidden: Bool
|
|
420
|
+
public init(props: JSONObject) {
|
|
421
|
+
seconds = props.double("seconds") ?? 0
|
|
422
|
+
components = props.string("components") ?? "date"
|
|
423
|
+
min = props.double("min")
|
|
424
|
+
max = props.double("max")
|
|
425
|
+
timeZone = props.string("timeZone")
|
|
426
|
+
labelsHidden = props.bool("labelsHidden") ?? false
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
public struct GestureProps: Equatable, Sendable {
|
|
431
|
+
public var drag: Bool
|
|
432
|
+
public var longPress: Bool
|
|
433
|
+
public var minimumDistance: Double
|
|
434
|
+
public var minimumDuration: Double
|
|
435
|
+
public init(props: JSONObject) {
|
|
436
|
+
drag = props.bool("drag") ?? false
|
|
437
|
+
longPress = props.bool("longPress") ?? false
|
|
438
|
+
minimumDistance = props.double("minimumDistance") ?? 10
|
|
439
|
+
minimumDuration = props.double("minimumDuration") ?? 0.5
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
public struct ViewThatFitsProps: Equatable, Sendable {
|
|
444
|
+
/// `horizontal`, `vertical` or `both`.
|
|
445
|
+
public var axes: String
|
|
446
|
+
public init(props: JSONObject) { axes = props.string("axes") ?? "both" }
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
public struct LazySpacerProps: Equatable, Sendable {
|
|
450
|
+
public var axis: String
|
|
451
|
+
public var remaining: Int
|
|
452
|
+
public init(props: JSONObject) {
|
|
453
|
+
axis = props.string("axis") ?? "vertical"
|
|
454
|
+
remaining = props.int("remaining") ?? 0
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/// `zstack` and `shape` props that ask for the element's laid-out size
|
|
459
|
+
/// (`geometry` events, like a `GeometryReader`'s): anchor preferences, custom shapes.
|
|
460
|
+
public struct SizeReportProps: Equatable, Sendable {
|
|
461
|
+
public var reportsSize: Bool
|
|
462
|
+
public var reportsFrame: Bool
|
|
463
|
+
public var reader: Int?
|
|
464
|
+
public init(props: JSONObject) {
|
|
465
|
+
reportsSize = props.bool("reportsSize") ?? false
|
|
466
|
+
reportsFrame = props.bool("reportsFrame") ?? false
|
|
467
|
+
reader = props.int("reader")
|
|
468
|
+
}
|
|
469
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// An event the renderer sends the app (docs/ops-protocol.md, "Events"):
|
|
4
|
+
/// a `type`, usually the `id` of the element it concerns, and the fields of
|
|
5
|
+
/// that type. Built with the constructors below and sent as JSON.
|
|
6
|
+
public struct RendererEvent: Equatable, Sendable {
|
|
7
|
+
public var type: String
|
|
8
|
+
public var fields: JSONObject
|
|
9
|
+
|
|
10
|
+
public init(type: String, fields: JSONObject = [:]) {
|
|
11
|
+
self.type = type
|
|
12
|
+
self.fields = fields
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// The element the event names, when it names one.
|
|
16
|
+
public var id: Int? { fields.int("id") }
|
|
17
|
+
|
|
18
|
+
/// The event as a JSON object (`type` and the fields), sorted keys.
|
|
19
|
+
public var json: JSONValue {
|
|
20
|
+
var members = fields
|
|
21
|
+
members["type"] = .string(type)
|
|
22
|
+
return .object(members)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public var jsonString: String { json.json }
|
|
26
|
+
|
|
27
|
+
public init?(json: JSONValue) {
|
|
28
|
+
guard var members = json.object, let type = members["type"]?.string else { return nil }
|
|
29
|
+
members["type"] = nil
|
|
30
|
+
self.init(type: type, fields: members)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private static func with(_ type: String, id: Int, _ extra: JSONObject = [:]) -> RendererEvent {
|
|
34
|
+
var fields = extra
|
|
35
|
+
fields["id"] = .number(Double(id))
|
|
36
|
+
return RendererEvent(type: type, fields: fields)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// A `button`, `navlink`, `tapgesture`, `listrow` was tapped; a `navscreen`'s back button; a `sheet` dismissed.
|
|
40
|
+
public static func tap(_ id: Int) -> RendererEvent { with("tap", id: id) }
|
|
41
|
+
/// A `tapgesture` with `location`.
|
|
42
|
+
public static func tap(_ id: Int, x: Double, y: Double) -> RendererEvent { with("tap", id: id, ["x": .number(x), "y": .number(y)]) }
|
|
43
|
+
/// A `textfield`, `searchfield` or `texteditor` received input.
|
|
44
|
+
public static func text(_ id: Int, _ value: String) -> RendererEvent { with("text", id: id, ["value": .string(value)]) }
|
|
45
|
+
/// A `toggle` was switched.
|
|
46
|
+
public static func toggle(_ id: Int, _ value: Bool) -> RendererEvent { with("toggle", id: id, ["value": .bool(value)]) }
|
|
47
|
+
/// A `tabview` item or `picker` option was chosen, by index.
|
|
48
|
+
public static func select(_ id: Int, _ index: Int) -> RendererEvent { with("select", id: id, ["value": .number(Double(index))]) }
|
|
49
|
+
/// A link run of a `text` was tapped.
|
|
50
|
+
public static func link(_ id: Int, _ url: String) -> RendererEvent { with("link", id: id, ["value": .string(url)]) }
|
|
51
|
+
/// A `geometry` element's laid-out size (and, with `reportsFrame`, its origin).
|
|
52
|
+
public static func geometry(_ id: Int, width: Double, height: Double, x: Double? = nil, y: Double? = nil) -> RendererEvent {
|
|
53
|
+
var fields: JSONObject = ["width": .number(width), "height": .number(height)]
|
|
54
|
+
if let x { fields["x"] = .number(x) }
|
|
55
|
+
if let y { fields["y"] = .number(y) }
|
|
56
|
+
return with("geometry", id: id, fields)
|
|
57
|
+
}
|
|
58
|
+
/// A `stepper` half was tapped (`value` is ±1).
|
|
59
|
+
public static func step(_ id: Int, _ delta: Int) -> RendererEvent { with("step", id: id, ["value": .number(Double(delta))]) }
|
|
60
|
+
/// A `slider` moved.
|
|
61
|
+
public static func slide(_ id: Int, _ value: Double) -> RendererEvent { with("slide", id: id, ["value": .number(value)]) }
|
|
62
|
+
/// A `datepicker` changed; `seconds` since 1970.
|
|
63
|
+
public static func date(_ id: Int, seconds: Double) -> RendererEvent { with("date", id: id, ["value": .number(seconds)]) }
|
|
64
|
+
/// A `textfield` with `focused` gained or lost focus.
|
|
65
|
+
public static func focus(_ id: Int, _ focused: Bool) -> RendererEvent { with("focus", id: id, ["value": .bool(focused)]) }
|
|
66
|
+
/// The Return key in a `textfield` with `submit`.
|
|
67
|
+
public static func submit(_ id: Int) -> RendererEvent { with("submit", id: id) }
|
|
68
|
+
/// A `listrow` was deleted by a swipe or in edit mode.
|
|
69
|
+
public static func delete(_ id: Int) -> RendererEvent { with("delete", id: id) }
|
|
70
|
+
/// A `listrow` was moved to index `to` among its siblings.
|
|
71
|
+
public static func move(_ id: Int, to: Int) -> RendererEvent { with("move", id: id, ["to": .number(Double(to))]) }
|
|
72
|
+
/// A `gesture` drag: `phase` is `began`, `changed`, `ended` or `cancelled`; points in the element's coordinates.
|
|
73
|
+
public static func drag(
|
|
74
|
+
_ id: Int, phase: String, translation: (width: Double, height: Double), location: (x: Double, y: Double),
|
|
75
|
+
startLocation: (x: Double, y: Double), velocity: (width: Double, height: Double)
|
|
76
|
+
) -> RendererEvent {
|
|
77
|
+
with("drag", id: id, [
|
|
78
|
+
"phase": .string(phase),
|
|
79
|
+
"translation": .object(["width": .number(translation.width), "height": .number(translation.height)]),
|
|
80
|
+
"location": .object(["x": .number(location.x), "y": .number(location.y)]),
|
|
81
|
+
"startLocation": .object(["x": .number(startLocation.x), "y": .number(startLocation.y)]),
|
|
82
|
+
"velocity": .object(["width": .number(velocity.width), "height": .number(velocity.height)]),
|
|
83
|
+
])
|
|
84
|
+
}
|
|
85
|
+
/// A `lazyspacer` came into view: the app mounts the next batch of rows.
|
|
86
|
+
public static func lazy(_ id: Int) -> RendererEvent { with("lazy", id: id) }
|
|
87
|
+
/// A `gesture` long press.
|
|
88
|
+
public static func longPress(_ id: Int) -> RendererEvent { with("longpress", id: id) }
|
|
89
|
+
/// A `list` or `scrollview` with `refreshable` was pulled.
|
|
90
|
+
public static func refresh(_ id: Int) -> RendererEvent { with("refresh", id: id) }
|
|
91
|
+
/// A `menu` opened or closed (lazy menus build their items on `open`).
|
|
92
|
+
public static func menu(_ id: Int, open: Bool) -> RendererEvent { with("menu", id: id, ["open": .bool(open)]) }
|
|
93
|
+
/// The host's answer to a `native` element's own event (docs/native.md, the escape hatch).
|
|
94
|
+
public static func native(_ id: Int, name: String, value: JSONValue) -> RendererEvent {
|
|
95
|
+
with("native", id: id, ["name": .string(name), "value": value])
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// The device appearance, text size or size class (sent once at launch and on change).
|
|
99
|
+
public static func environment(
|
|
100
|
+
colorScheme: String? = nil,
|
|
101
|
+
dynamicTypeSize: String? = nil,
|
|
102
|
+
horizontalSizeClass: String? = nil,
|
|
103
|
+
scenePhase: String? = nil
|
|
104
|
+
) -> RendererEvent {
|
|
105
|
+
var fields: JSONObject = [:]
|
|
106
|
+
if let colorScheme { fields["colorScheme"] = .string(colorScheme) }
|
|
107
|
+
if let dynamicTypeSize { fields["dynamicTypeSize"] = .string(dynamicTypeSize) }
|
|
108
|
+
if let horizontalSizeClass { fields["horizontalSizeClass"] = .string(horizontalSizeClass) }
|
|
109
|
+
if let scenePhase { fields["scenePhase"] = .string(scenePhase) }
|
|
110
|
+
return RendererEvent(type: "environment", fields: fields)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Several events delivered at once; the app renders once for all of them.
|
|
114
|
+
public static func batch(_ events: [RendererEvent]) -> RendererEvent {
|
|
115
|
+
RendererEvent(type: "batch", fields: ["events": .array(events.map(\.json))])
|
|
116
|
+
}
|
|
117
|
+
}
|