@swiftbrowser/native 0.18.3 → 0.21.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 (102) hide show
  1. package/README.md +53 -11
  2. package/bin/swiftbrowser-native.mjs +47 -0
  3. package/dist/blob.d.ts +85 -11
  4. package/dist/blob.d.ts.map +1 -1
  5. package/dist/blob.js +184 -34
  6. package/dist/blob.js.map +1 -1
  7. package/dist/bridge.d.ts +66 -8
  8. package/dist/bridge.d.ts.map +1 -1
  9. package/dist/bridge.js.map +1 -1
  10. package/dist/encoding.d.ts +13 -0
  11. package/dist/encoding.d.ts.map +1 -1
  12. package/dist/encoding.js +24 -0
  13. package/dist/encoding.js.map +1 -1
  14. package/dist/fetch.d.ts +27 -13
  15. package/dist/fetch.d.ts.map +1 -1
  16. package/dist/fetch.js +117 -45
  17. package/dist/fetch.js.map +1 -1
  18. package/dist/hostRequests.d.ts +24 -6
  19. package/dist/hostRequests.d.ts.map +1 -1
  20. package/dist/hostRequests.js +108 -15
  21. package/dist/hostRequests.js.map +1 -1
  22. package/dist/index.d.ts +1 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1 -2
  25. package/dist/index.js.map +1 -1
  26. package/dist/ios.d.ts +79 -0
  27. package/dist/ios.d.ts.map +1 -0
  28. package/dist/ios.js +181 -0
  29. package/dist/ios.js.map +1 -0
  30. package/dist/runtime.d.ts.map +1 -1
  31. package/dist/runtime.js +16 -7
  32. package/dist/runtime.js.map +1 -1
  33. package/dist/subtle.d.ts +1 -1
  34. package/dist/subtle.d.ts.map +1 -1
  35. package/dist/subtle.js +9 -5
  36. package/dist/subtle.js.map +1 -1
  37. package/dist/webSocket.d.ts +12 -5
  38. package/dist/webSocket.d.ts.map +1 -1
  39. package/dist/webSocket.js +37 -11
  40. package/dist/webSocket.js.map +1 -1
  41. package/package.json +7 -3
  42. package/src/blob.ts +215 -34
  43. package/src/bridge.ts +56 -8
  44. package/src/encoding.ts +22 -0
  45. package/src/fetch.ts +111 -48
  46. package/src/hostRequests.ts +129 -22
  47. package/src/index.ts +1 -2
  48. package/src/ios.ts +253 -0
  49. package/src/runtime.ts +23 -8
  50. package/src/subtle.ts +10 -6
  51. package/src/webSocket.ts +37 -13
  52. package/swift/Packages/SwiftBrowserDSP/Package.swift +31 -0
  53. package/swift/Packages/SwiftBrowserDSP/README.md +42 -0
  54. package/swift/Packages/SwiftBrowserDSP/Sources/SwiftBrowserDSP/ProgramNode.swift +306 -0
  55. package/swift/Packages/SwiftBrowserDSP/Sources/SwiftBrowserDSP/ScopeView.swift +79 -0
  56. package/swift/Packages/SwiftBrowserDSP/Sources/SwiftBrowserDSPCore/Machine.swift +1561 -0
  57. package/swift/Packages/SwiftBrowserDSP/Sources/SwiftBrowserDSPCore/Program.swift +483 -0
  58. package/swift/Packages/SwiftBrowserLocalModel/Package.swift +35 -0
  59. package/swift/Packages/SwiftBrowserLocalModel/README.md +66 -0
  60. package/swift/Packages/SwiftBrowserLocalModel/Sources/SwiftBrowserLocalModel/LlamaEngine.swift +223 -0
  61. package/swift/Packages/SwiftBrowserLocalModel/Sources/SwiftBrowserLocalModel/LocalLanguageModel.swift +409 -0
  62. package/swift/Packages/SwiftBrowserShader/Package.swift +31 -0
  63. package/swift/Packages/SwiftBrowserShader/README.md +45 -0
  64. package/swift/Packages/SwiftBrowserShader/Sources/SwiftBrowserShader/ShaderRenderer.swift +103 -0
  65. package/swift/Packages/SwiftBrowserShader/Sources/SwiftBrowserShader/ShaderView.swift +168 -0
  66. package/swift/Packages/SwiftBrowserShader/Sources/SwiftBrowserShaderCore/ShaderProps.swift +120 -0
  67. package/swift/Sources/SwiftBrowserNative/Host/AudioEngineBridge.swift +921 -0
  68. package/swift/Sources/SwiftBrowserNative/Host/AudioEngineNodes.swift +70 -0
  69. package/swift/Sources/SwiftBrowserNative/Host/BodyStream.swift +166 -0
  70. package/swift/Sources/SwiftBrowserNative/Host/FilesBridge.swift +144 -10
  71. package/swift/Sources/SwiftBrowserNative/Host/HostBridge.swift +151 -27
  72. package/swift/Sources/SwiftBrowserNative/Host/HostRequestBridge.swift +139 -40
  73. package/swift/Sources/SwiftBrowserNative/Host/JSHost.swift +116 -0
  74. package/swift/Sources/SwiftBrowserNative/Host/JavaScriptCoreHost.swift +141 -2
  75. package/swift/Sources/SwiftBrowserNative/Host/LanguageModelBridge.swift +511 -0
  76. package/swift/Sources/SwiftBrowserNative/Host/LanguageModelProvider.swift +195 -0
  77. package/swift/Sources/SwiftBrowserNative/Host/WebSocketBridge.swift +24 -11
  78. package/swift/Sources/SwiftBrowserNative/Host/WorkerHost.swift +3 -2
  79. package/swift/Sources/SwiftBrowserNative/Runtime/NativeFunctions.swift +1 -1
  80. package/swift/Sources/SwiftBrowserNative/Runtime/ViewRegistry.swift +57 -0
  81. package/swift/Sources/SwiftBrowserNative/Views/ElementView.swift +12 -7
  82. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Controls.swift +18 -1
  83. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Drawing.swift +378 -0
  84. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Lists.swift +41 -1
  85. package/swift/Sources/SwiftBrowserNative/Views/Kinds/More.swift +88 -17
  86. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Navigation.swift +87 -21
  87. package/swift/Sources/SwiftBrowserNative/Views/Kinds/PinchTracker.swift +200 -0
  88. package/swift/Sources/SwiftBrowserNative/Views/Kinds/PopTap.swift +103 -0
  89. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Stacks.swift +3 -2
  90. package/swift/Sources/SwiftBrowserNative/Views/StyleModifier.swift +146 -0
  91. package/swift/Sources/SwiftBrowserProtocol/Drawing.swift +306 -0
  92. package/swift/Sources/SwiftBrowserProtocol/Props.swift +31 -2
  93. package/swift/Sources/SwiftBrowserProtocol/RendererEvent.swift +24 -3
  94. package/swift/Sources/SwiftBrowserProtocol/Style.swift +87 -0
  95. package/swift/Sources/SwiftBrowserTree/OpTree.swift +25 -0
  96. package/wasm/SwiftBrowserNative.wasm +2 -2
  97. package/dist/native.d.ts +0 -19
  98. package/dist/native.d.ts.map +0 -1
  99. package/dist/native.js +0 -17
  100. package/dist/native.js.map +0 -1
  101. package/src/native.ts +0 -35
  102. package/swift/Sources/SwiftBrowserNative/Runtime/NativeRegistry.swift +0 -53
@@ -38,6 +38,7 @@ public struct Style: Equatable, Sendable {
38
38
  public var scale: PointSpec?
39
39
  public var scaleAnchor: UnitPointSpec?
40
40
  public var shadow: ShadowSpec?
41
+ public var compositingGroup: Bool
41
42
  public var clipped: Bool
42
43
  public var tint: ColorSpec?
43
44
  public var position: PointSpec?
@@ -77,6 +78,22 @@ public struct Style: Equatable, Sendable {
77
78
  public var scrollTo: ScrollToSpec?
78
79
  /// `.scrollTransition`: the effects in its three phases.
79
80
  public var scrollTransition: ScrollTransitionSpec?
81
+ /// `.gridCellColumns(_:)`: the `grid` columns the cell spans.
82
+ public var gridCellColumns: Int?
83
+ /// `.gridCellAnchor(_:)`: the unit point the cell aligns by in its cell.
84
+ public var gridCellAnchor: UnitPointSpec?
85
+ /// `.onScrollVisibilityChange(threshold:)`: the share of the box that counts as visible; the box sends `scrollvisibility`.
86
+ public var scrollVisibilityThreshold: Double?
87
+ /// `.alignmentGuide(_:computeValue:)`, as `a + b·width + c·height`.
88
+ public var alignmentGuide: AlignmentGuideSpec?
89
+ /// `.rotation3DEffect(_:axis:anchor:perspective:)`.
90
+ public var rotation3D: Rotation3DSpec?
91
+ /// `.onHover`: the box sends `hover` as a pointer enters and leaves it.
92
+ public var hover: Bool
93
+ /// `.pointerStyle(_:)`: the pointer over the box, by its CSS name (`pointer`, `text`, `grab`…).
94
+ public var pointerStyle: String?
95
+ /// `.symbolEffect(_:options:isActive:)` / `.symbolEffect(_:options:value:)`.
96
+ public var symbolEffect: SymbolEffectSpec?
80
97
 
81
98
  public init(json: JSONValue) {
82
99
  let fields = json.object ?? [:]
@@ -115,6 +132,7 @@ public struct Style: Equatable, Sendable {
115
132
  scale = PointSpec(json: fields.value("scale"))
116
133
  scaleAnchor = UnitPointSpec(json: fields.value("scaleAnchor"))
117
134
  shadow = ShadowSpec(json: fields.value("shadow"))
135
+ compositingGroup = fields.bool("compositingGroup") ?? false
118
136
  clipped = fields.bool("clipped") ?? false
119
137
  tint = ColorSpec(json: fields.value("tint"))
120
138
  position = PointSpec(json: fields.value("position"))
@@ -152,6 +170,14 @@ public struct Style: Equatable, Sendable {
152
170
  scrollID = fields.bool("scrollID") ?? false
153
171
  scrollTo = ScrollToSpec(json: fields.value("scrollTo"))
154
172
  scrollTransition = ScrollTransitionSpec(json: fields.value("scrollTransition"))
173
+ gridCellColumns = fields.int("gridCellColumns")
174
+ gridCellAnchor = UnitPointSpec(json: fields.value("gridCellAnchor"))
175
+ scrollVisibilityThreshold = fields.object("scrollVisibility")?.double("threshold")
176
+ alignmentGuide = AlignmentGuideSpec(json: fields.value("alignmentGuide"))
177
+ rotation3D = Rotation3DSpec(json: fields.value("rotation3D"))
178
+ hover = fields.bool("hover") ?? false
179
+ pointerStyle = fields.string("pointerStyle")
180
+ symbolEffect = SymbolEffectSpec(json: fields.value("symbolEffect"))
155
181
  }
156
182
 
157
183
  public init() {
@@ -162,6 +188,67 @@ public struct Style: Equatable, Sendable {
162
188
  public var fieldNames: [String] { raw.keys.sorted() }
163
189
  }
164
190
 
191
+ /// `.alignmentGuide(_:computeValue:)`: the box's guide `name` on `axis` is `a + b·width + c·height`.
192
+ public struct AlignmentGuideSpec: Equatable, Sendable {
193
+ /// `horizontal` (a `HorizontalAlignment`: leading, center, trailing…) or `vertical`.
194
+ public var axis: String
195
+ public var name: String
196
+ public var a: Double
197
+ public var b: Double
198
+ public var c: Double
199
+ public init?(json: JSONValue) {
200
+ guard let fields = json.object, let axis = fields.string("axis"), let name = fields.string("name") else { return nil }
201
+ let value = (fields.array("value") ?? []).compactMap(\.double)
202
+ self.axis = axis
203
+ self.name = name
204
+ a = value.count > 0 ? value[0] : 0
205
+ b = value.count > 1 ? value[1] : 0
206
+ c = value.count > 2 ? value[2] : 0
207
+ }
208
+ }
209
+
210
+ /// `.rotation3DEffect`: degrees about `axis`, around `anchor`, with SwiftUI's perspective factor.
211
+ public struct Rotation3DSpec: Equatable, Sendable {
212
+ public var angle: Double
213
+ public var x: Double
214
+ public var y: Double
215
+ public var z: Double
216
+ public var anchor: UnitPointSpec?
217
+ public var perspective: Double
218
+ public init?(json: JSONValue) {
219
+ guard let fields = json.object, let angle = fields.double("angle") else { return nil }
220
+ self.angle = angle
221
+ let axis = fields.object("axis") ?? [:]
222
+ x = axis.double("x") ?? 0
223
+ y = axis.double("y") ?? 0
224
+ z = axis.double("z") ?? 0
225
+ anchor = UnitPointSpec(json: fields.value("anchor"))
226
+ perspective = fields.double("perspective") ?? 1
227
+ }
228
+ }
229
+
230
+ /// An SF Symbols effect: `effect` by name (`bounce`, `pulse`, `variableColor`, `scale`, `rotate`, `wiggle`,
231
+ /// `breathe`, `appear`, `disappear`), run while the style has it, or once each time `trigger` changes.
232
+ public struct SymbolEffectSpec: Equatable, Sendable {
233
+ public var effect: String
234
+ /// `up` or `down`.
235
+ public var direction: String?
236
+ public var speed: Double?
237
+ /// How many times a run repeats; nil with `forever` false for the effect's own.
238
+ public var repeatCount: Int?
239
+ public var forever: Bool
240
+ public var trigger: Double?
241
+ public init?(json: JSONValue) {
242
+ guard let fields = json.object, let effect = fields.string("effect") else { return nil }
243
+ self.effect = effect
244
+ direction = fields.string("direction")
245
+ speed = fields.double("speed")
246
+ repeatCount = fields.int("repeat")
247
+ forever = fields.string("repeat") == "forever"
248
+ trigger = fields.double("trigger")
249
+ }
250
+ }
251
+
165
252
  /// `.containerRelativeFrame(axes)` as the style hint carries it: `length = a × container + b`.
166
253
  public struct ContainerRelativeFrameSpec: Equatable, Sendable {
167
254
  public var horizontal: Bool
@@ -26,6 +26,13 @@ public final class OpTree {
26
26
  /// The root container, element id 0.
27
27
  public let root: ElementNode
28
28
  private var nodes: [Int: ElementNode]
29
+ /// Screens (`navscreen`) removed lately, with when: a navigation stack
30
+ /// keeps drawing one through the pop that takes it off (SwiftUI asks
31
+ /// the destination for it while the transition runs; the app has
32
+ /// removed it by then, synchronously on the pop's tap).
33
+ private var poppedScreens: [Int: (node: ElementNode, at: Date)] = [:]
34
+ /// How long a popped screen stays available: past any pop transition.
35
+ private static let poppedScreenLife: TimeInterval = 3
29
36
  /// The ops of the current pass, until its `commit`.
30
37
  private var pending: [Op] = []
31
38
  /// How many passes were committed.
@@ -57,6 +64,18 @@ public final class OpTree {
57
64
  /// The element with this id, when it exists (created and not removed).
58
65
  public func node(_ id: Int) -> ElementNode? { nodes[id] }
59
66
 
67
+ /// A `navscreen` removed within the last moments (`poppedScreenLife`),
68
+ /// its subtree as it was: what a stack shows while the pop that removed
69
+ /// it is still animating. Nil once it is forgotten or its id reused.
70
+ public func poppedScreen(_ id: Int) -> ElementNode? {
71
+ guard let entry = poppedScreens[id] else { return nil }
72
+ guard Date().timeIntervalSince(entry.at) < Self.poppedScreenLife else {
73
+ poppedScreens[id] = nil
74
+ return nil
75
+ }
76
+ return entry.node
77
+ }
78
+
60
79
  /// How many elements exist, the root included.
61
80
  public var count: Int { nodes.count }
62
81
 
@@ -126,6 +145,7 @@ public final class OpTree {
126
145
  // for a live id replaces it in place (the web renderer does the same).
127
146
  detach(existing)
128
147
  }
148
+ poppedScreens[id] = nil
129
149
  nodes[id] = ElementNode(id: id, kind: kind, props: props)
130
150
  case .update(let id, let props):
131
151
  guard let node = nodes[id] else { throw OpTreeError("update: no element \(id)") }
@@ -155,6 +175,11 @@ public final class OpTree {
155
175
  guard let node = nodes[id] else { throw OpTreeError("remove: no element \(id)") }
156
176
  detach(node)
157
177
  forget(node)
178
+ if node.kind == "navscreen" {
179
+ let now = Date()
180
+ poppedScreens = poppedScreens.filter { now.timeIntervalSince($0.value.at) < Self.poppedScreenLife }
181
+ poppedScreens[id] = (node, now)
182
+ }
158
183
  case .commit:
159
184
  break
160
185
  case .other(let name, let fields):
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/wasm/SwiftBrowserNative.wasm
3
- size: 50071156 bytes
4
- sha256: f976d6cb271c4fe729a1e960f0ddd5105bc868d35c85a4c1eb8bf79d19ac5e93
3
+ size: 50264748 bytes
4
+ sha256: 7712613a6aed40822224c0e4042dab20c7de0e5708ff6ad8ce79f52013b76472
package/dist/native.d.ts DELETED
@@ -1,19 +0,0 @@
1
- /**
2
- * `<Native name props onEvent>`: the escape hatch (docs/native.md). The
3
- * element kind `native`, which only this producer emits, names a SwiftUI view
4
- * the app registered with `NativeRegistry.register(name)`; `props` reach it
5
- * as JSON, its children as SwiftUI views, and what it sends back arrives at
6
- * `onEvent`.
7
- */
8
- import { type ReactElement, type ReactNode } from 'react';
9
- export interface NativeProps {
10
- /** The name registered on the Swift side. */
11
- name: string;
12
- /** JSON-serializable props for the view. */
13
- props?: Record<string, unknown>;
14
- /** A value the native view sent (`context.send(value)`). */
15
- onEvent?: (value: unknown) => void;
16
- children?: ReactNode;
17
- }
18
- export declare function Native({ name, props, onEvent, children }: NativeProps): ReactElement;
19
- //# sourceMappingURL=native.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAiB,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEzE,MAAM,WAAW,WAAW;IAC1B,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,4DAA4D;IAC5D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAUD,wBAAgB,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,WAAW,GAAG,YAAY,CAMpF"}
package/dist/native.js DELETED
@@ -1,17 +0,0 @@
1
- /**
2
- * `<Native name props onEvent>`: the escape hatch (docs/native.md). The
3
- * element kind `native`, which only this producer emits, names a SwiftUI view
4
- * the app registered with `NativeRegistry.register(name)`; `props` reach it
5
- * as JSON, its children as SwiftUI views, and what it sends back arrives at
6
- * `onEvent`.
7
- */
8
- import { createElement } from 'react';
9
- export function Native({ name, props, onEvent, children }) {
10
- // The host element of @swiftbrowser/react: `sb` with the protocol kind and
11
- // props, and the handlers by event type (packages/react/src/host/instance.ts).
12
- const hostProps = { kind: 'native', props: { name, props: props ?? {} } };
13
- if (onEvent)
14
- hostProps['on'] = { native: (event) => onEvent(event.value) };
15
- return children === undefined ? createElement('sb', hostProps) : createElement('sb', hostProps, children);
16
- }
17
- //# sourceMappingURL=native.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,aAAa,EAAqC,MAAM,OAAO,CAAC;AAoBzE,MAAM,UAAU,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAe;IACpE,2EAA2E;IAC3E,+EAA+E;IAC/E,MAAM,SAAS,GAA4B,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,EAAE,EAAE,CAAC;IACnG,IAAI,OAAO;QAAE,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,KAAkB,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;IACxF,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAC5G,CAAC"}
package/src/native.ts DELETED
@@ -1,35 +0,0 @@
1
- /**
2
- * `<Native name props onEvent>`: the escape hatch (docs/native.md). The
3
- * element kind `native`, which only this producer emits, names a SwiftUI view
4
- * the app registered with `NativeRegistry.register(name)`; `props` reach it
5
- * as JSON, its children as SwiftUI views, and what it sends back arrives at
6
- * `onEvent`.
7
- */
8
-
9
- import { createElement, type ReactElement, type ReactNode } from 'react';
10
-
11
- export interface NativeProps {
12
- /** The name registered on the Swift side. */
13
- name: string;
14
- /** JSON-serializable props for the view. */
15
- props?: Record<string, unknown>;
16
- /** A value the native view sent (`context.send(value)`). */
17
- onEvent?: (value: unknown) => void;
18
- children?: ReactNode;
19
- }
20
-
21
- /** The shape of a `native` event (`RendererEvent` has no such member). */
22
- interface NativeEvent {
23
- type: 'native';
24
- id: number;
25
- name: string;
26
- value: unknown;
27
- }
28
-
29
- export function Native({ name, props, onEvent, children }: NativeProps): ReactElement {
30
- // The host element of @swiftbrowser/react: `sb` with the protocol kind and
31
- // props, and the handlers by event type (packages/react/src/host/instance.ts).
32
- const hostProps: Record<string, unknown> = { kind: 'native', props: { name, props: props ?? {} } };
33
- if (onEvent) hostProps['on'] = { native: (event: NativeEvent) => onEvent(event.value) };
34
- return children === undefined ? createElement('sb', hostProps) : createElement('sb', hostProps, children);
35
- }
@@ -1,53 +0,0 @@
1
- #if canImport(SwiftUI)
2
- import Foundation
3
- import SwiftBrowserProtocol
4
- import SwiftBrowserTree
5
- import SwiftUI
6
-
7
- /// What a registered native view gets: the element's `props` as the app
8
- /// wrote them, its children (SwiftUI views the app put inside it) and a way
9
- /// to send a value back, which reaches the element's `onEvent` in JavaScript.
10
- public struct NativeViewContext {
11
- public let id: Int
12
- public let props: JSONValue
13
- public let children: [ElementNode]
14
- public let send: (JSONValue) -> Void
15
-
16
- /// The children as a view (built on the main actor, as views are).
17
- @MainActor public var content: some View {
18
- ForEach(children) { ElementView(node: $0) }
19
- }
20
- }
21
-
22
- /// The escape hatch (docs/native.md): the `native` element kind, which only
23
- /// a JavaScript producer emits, names a view the Swift app registered.
24
- ///
25
- /// ```swift
26
- /// NativeRegistry.register("MapView") { context in
27
- /// MapView(region: context.props["region"]) { context.send(["tapped": true]) }
28
- /// }
29
- /// ```
30
- ///
31
- /// ```tsx
32
- /// <Native name="MapView" props={{ region }} onEvent={(value) => …} />
33
- /// ```
34
- @MainActor
35
- public enum NativeRegistry {
36
- public typealias Builder = (NativeViewContext) -> AnyView
37
- private static var builders: [String: Builder] = [:]
38
-
39
- public static func register<V: View>(_ name: String, @ViewBuilder _ builder: @escaping (NativeViewContext) -> V) {
40
- builders[name] = { AnyView(builder($0)) }
41
- }
42
-
43
- public static func unregister(_ name: String) {
44
- builders[name] = nil
45
- }
46
-
47
- public static func isRegistered(_ name: String) -> Bool { builders[name] != nil }
48
-
49
- static func view(name: String, context: NativeViewContext) -> AnyView? {
50
- builders[name]?(context)
51
- }
52
- }
53
- #endif