Remove `DefaultApp`, make `DOMRenderer` internal (#227)
Removes the `View`-based initializer of `DOMRenderer` which no longer leaves any `public` initializers on it, means we can make it fully internal. `DOMNode` is now internal too, which is great as it was an implementation detail anyway. Corollary, `DefaultApp` is no longer needed. `Target` was cleaned up is it doesn't need to hold `App` or `Scene` values, now it's just a simple protocol. I've updated `README.md` to show usage of the `App` protocol in the basic example. Closes #224.
This commit is contained in:
parent
e37d13017c
commit
40804d4542
25
README.md
25
README.md
|
@ -48,23 +48,18 @@ struct Counter: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can then render your view in any DOM node captured with
|
||||
[JavaScriptKit](https://github.com/kateinoigakukun/JavaScriptKit/), just
|
||||
pass it as an argument to the `DOMRenderer` initializer together with your view:
|
||||
struct CounterApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup("Counter Demo") {
|
||||
Counter(count: 5, limit: 15)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```swift
|
||||
import JavaScriptKit
|
||||
import TokamakDOM
|
||||
|
||||
let document = JSObjectRef.global.document.object!
|
||||
|
||||
let divElement = document.createElement!("div").object!
|
||||
let renderer = DOMRenderer(Counter(count: 5, limit: 15), divElement)
|
||||
|
||||
let body = document.body.object!
|
||||
_ = body.appendChild!(divElement)
|
||||
// @main attribute is not supported in SwiftPM apps.
|
||||
// See https://bugs.swift.org/browse/SR-12683 for more details.
|
||||
CounterApp.main()
|
||||
```
|
||||
|
||||
### Arbitrary HTML
|
||||
|
|
|
@ -15,52 +15,6 @@
|
|||
// Created by Max Desiatov on 10/02/2019.
|
||||
//
|
||||
|
||||
open class Target {
|
||||
var element: MountedElementKind
|
||||
public internal(set) var app: _AnyApp {
|
||||
get {
|
||||
if case let .app(app) = element {
|
||||
return app
|
||||
} else {
|
||||
fatalError("`Target` has type \(element) not `App`")
|
||||
}
|
||||
}
|
||||
set {
|
||||
element = .app(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
public internal(set) var scene: _AnyScene {
|
||||
get {
|
||||
if case let .scene(scene) = element {
|
||||
return scene
|
||||
} else {
|
||||
fatalError("`Target` has type \(element) not `Scene`")
|
||||
}
|
||||
}
|
||||
set {
|
||||
element = .scene(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
public internal(set) var view: AnyView {
|
||||
get {
|
||||
if case let .view(view) = element {
|
||||
return view
|
||||
} else {
|
||||
fatalError("`Target` has type \(element) not `View`")
|
||||
}
|
||||
}
|
||||
set {
|
||||
element = .view(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
public init<V: View>(_ view: V) {
|
||||
element = .view(AnyView(view))
|
||||
}
|
||||
|
||||
public init<A: App>(_ app: A) {
|
||||
element = .app(_AnyApp(app))
|
||||
}
|
||||
public protocol Target: AnyObject {
|
||||
var view: AnyView { get set }
|
||||
}
|
||||
|
|
|
@ -72,13 +72,3 @@ extension App {
|
|||
ScenePhaseObserver.publisher
|
||||
}
|
||||
}
|
||||
|
||||
struct DefaultApp<V: View>: App {
|
||||
var content: V?
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,22 +15,22 @@
|
|||
import JavaScriptKit
|
||||
import TokamakCore
|
||||
|
||||
public final class DOMNode: Target {
|
||||
final class DOMNode: Target {
|
||||
let ref: JSObjectRef
|
||||
private var listeners: [String: JSClosure]
|
||||
var view: AnyView
|
||||
|
||||
init<V: View>(_ view: V, _ ref: JSObjectRef, _ listeners: [String: Listener] = [:]) {
|
||||
self.ref = ref
|
||||
self.listeners = [:]
|
||||
super.init(view)
|
||||
self.view = AnyView(view)
|
||||
reinstall(listeners)
|
||||
}
|
||||
|
||||
init<A: App>(_ app: A, _ ref: JSObjectRef, _ listeners: [String: Listener] = [:]) {
|
||||
init(_ ref: JSObjectRef) {
|
||||
self.ref = ref
|
||||
self.listeners = [:]
|
||||
super.init(app)
|
||||
reinstall(listeners)
|
||||
view = AnyView(EmptyView())
|
||||
listeners = [:]
|
||||
}
|
||||
|
||||
/// Removes all existing event listeners on this DOM node and install new ones from
|
||||
|
|
|
@ -74,33 +74,25 @@ func appendRootStyle(_ rootNode: JSObjectRef) {
|
|||
_ = head.appendChild!(rootStyle)
|
||||
}
|
||||
|
||||
public final class DOMRenderer: Renderer {
|
||||
public private(set) var reconciler: StackReconciler<DOMRenderer>?
|
||||
final class DOMRenderer: Renderer {
|
||||
private(set) var reconciler: StackReconciler<DOMRenderer>?
|
||||
|
||||
private let rootRef: JSObjectRef
|
||||
|
||||
public convenience init<V: View>(
|
||||
_ view: V,
|
||||
_ ref: JSObjectRef,
|
||||
_ rootEnvironment: EnvironmentValues? = nil
|
||||
) {
|
||||
self.init(DefaultApp(content: view), ref, rootEnvironment)
|
||||
}
|
||||
|
||||
init<A: App>(_ app: A, _ ref: JSObjectRef, _ rootEnvironment: EnvironmentValues? = nil) {
|
||||
rootRef = ref
|
||||
appendRootStyle(ref)
|
||||
|
||||
reconciler = StackReconciler(
|
||||
app: app,
|
||||
target: DOMNode(app, ref),
|
||||
target: DOMNode(ref),
|
||||
environment: .defaultEnvironment,
|
||||
renderer: self,
|
||||
scheduler: timeoutScheduler
|
||||
)
|
||||
}
|
||||
|
||||
public func mountTarget(to parent: DOMNode, with host: MountedHost) -> DOMNode? {
|
||||
func mountTarget(to parent: DOMNode, with host: MountedHost) -> DOMNode? {
|
||||
guard let (outerHTML, listeners) = mapAnyView(
|
||||
host.view,
|
||||
transform: { (html: AnyHTML) in (html.outerHTML, html.listeners) }
|
||||
|
@ -133,14 +125,14 @@ public final class DOMRenderer: Renderer {
|
|||
return DOMNode(host.view, lastChild, listeners)
|
||||
}
|
||||
|
||||
public func update(target: DOMNode, with host: MountedHost) {
|
||||
func update(target: DOMNode, with host: MountedHost) {
|
||||
guard let html = mapAnyView(host.view, transform: { (html: AnyHTML) in html })
|
||||
else { return }
|
||||
|
||||
html.update(dom: target)
|
||||
}
|
||||
|
||||
public func unmount(
|
||||
func unmount(
|
||||
target: DOMNode,
|
||||
from parent: DOMNode,
|
||||
with host: MountedHost,
|
||||
|
|
Loading…
Reference in New Issue