Support <meta> and <title> in DOMFiberRenderer
This commit is contained in:
parent
4e8b84e4a1
commit
1c3603ae40
|
@ -15,6 +15,11 @@
|
|||
// Created by Carson Katri on 7/16/20.
|
||||
//
|
||||
|
||||
@_spi(TokamakCore)
|
||||
public struct _WindowGroupTitle: _PrimitiveView {
|
||||
public let title: Text?
|
||||
}
|
||||
|
||||
public struct WindowGroup<Content>: Scene, TitledScene where Content: View {
|
||||
public let id: String
|
||||
public let title: Text?
|
||||
|
@ -77,6 +82,10 @@ public struct WindowGroup<Content>: Scene, TitledScene where Content: View {
|
|||
// }
|
||||
|
||||
public func _visitChildren<V>(_ visitor: V) where V: SceneVisitor {
|
||||
visitor.visit(content)
|
||||
print("Visiting scene")
|
||||
visitor.visit(Group {
|
||||
_WindowGroupTitle(title: self.title)
|
||||
content
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -423,7 +423,8 @@ public extension FiberReconciler {
|
|||
environment: .init(rootEnvironment),
|
||||
traits: .init(),
|
||||
preferenceStore: preferences
|
||||
)
|
||||
),
|
||||
preferenceStore: preferences ?? .init()
|
||||
)
|
||||
if let preferenceStore = outputs.preferenceStore {
|
||||
preferences = preferenceStore
|
||||
|
|
|
@ -69,6 +69,10 @@ public final class FiberReconciler<Renderer: FiberRenderer> {
|
|||
.environmentValues(environment)
|
||||
}
|
||||
}
|
||||
|
||||
static func _makeView(_ inputs: ViewInputs<Self>) -> ViewOutputs {
|
||||
.init(inputs: inputs, preferenceStore: inputs.preferenceStore ?? .init())
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Layout` container for the root of a `View` hierarchy.
|
||||
|
@ -275,6 +279,10 @@ public final class FiberReconciler<Renderer: FiberRenderer> {
|
|||
for action in afterReconcileActions {
|
||||
action()
|
||||
}
|
||||
|
||||
if let preferences = current.preferences {
|
||||
renderer.preferencesChanged(preferences)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -88,6 +88,9 @@ public protocol FiberRenderer {
|
|||
/// (in this case just `DuelOfTheStates` as both properties were on it),
|
||||
/// and reconcile after all changes have been collected.
|
||||
func schedule(_ action: @escaping () -> ())
|
||||
|
||||
/// Called by the reconciler when the preferences of the topmost `Fiber` changed.
|
||||
func preferencesChanged(_ preferenceStore: _PreferenceStore)
|
||||
}
|
||||
|
||||
public extension FiberRenderer {
|
||||
|
@ -107,6 +110,8 @@ public extension FiberRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
func preferencesChanged(_ preferenceStore: _PreferenceStore) {}
|
||||
|
||||
@discardableResult
|
||||
@_disfavoredOverload
|
||||
func render<V: View>(_ view: V) -> FiberReconciler<Self> {
|
||||
|
|
|
@ -118,6 +118,19 @@ public final class _PreferenceStore: CustomDebugStringConvertible {
|
|||
_PreferenceValue(storage: previousValues[ObjectIdentifier(key)] ?? .init(key))
|
||||
}
|
||||
|
||||
/// Returns the new value for `Key`, or `nil` if the value did not change.
|
||||
public func newValue<Key>(forKey key: Key.Type = Key.self) -> Key.Value?
|
||||
where Key: PreferenceKey, Key.Value: Equatable
|
||||
{
|
||||
let value = value(forKey: key).value
|
||||
let previousValue = previousValue(forKey: key).value
|
||||
if value != previousValue {
|
||||
return value
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func insert<Key>(_ value: Key.Value, forKey key: Key.Type = Key.self)
|
||||
where Key: PreferenceKey
|
||||
{
|
||||
|
|
|
@ -323,6 +323,38 @@ public struct DOMFiberRenderer: FiberRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
final class Head {
|
||||
let head = document.head.object!
|
||||
var metaTags = [JSObject]()
|
||||
var title: JSObject?
|
||||
}
|
||||
|
||||
private let head = Head()
|
||||
public func preferencesChanged(_ preferenceStore: _PreferenceStore) {
|
||||
if let newMetaTags = preferenceStore.newValue(forKey: HTMLMetaPreferenceKey.self) {
|
||||
for oldTag in head.metaTags {
|
||||
_ = head.head.removeChild!(oldTag)
|
||||
}
|
||||
head.metaTags = newMetaTags.map {
|
||||
let template = document.createElement!("template").object!
|
||||
template.innerHTML = .string($0.outerHTML())
|
||||
let meta = template.content.firstChild.object!
|
||||
_ = head.head.appendChild!(meta)
|
||||
return meta
|
||||
}
|
||||
}
|
||||
if let newTitle = preferenceStore.newValue(forKey: HTMLTitlePreferenceKey.self) {
|
||||
if let title = head.title {
|
||||
title.innerHTML = .string(newTitle)
|
||||
} else {
|
||||
let node = document.createElement!("title").object!
|
||||
_ = head.head.appendChild!(node)
|
||||
node.innerHTML = .string(newTitle)
|
||||
head.title = node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let scheduler = JSScheduler()
|
||||
public func schedule(_ action: @escaping () -> ()) {
|
||||
scheduler.schedule(options: nil, action)
|
||||
|
|
|
@ -15,10 +15,26 @@
|
|||
// Created by Carson Katri on 7/19/20.
|
||||
//
|
||||
|
||||
import TokamakCore
|
||||
@_spi(TokamakCore) import TokamakCore
|
||||
|
||||
extension WindowGroup: SceneDeferredToRenderer {
|
||||
public var deferredBody: AnyView {
|
||||
AnyView(content)
|
||||
}
|
||||
}
|
||||
|
||||
extension _WindowGroupTitle: HTMLConvertible {
|
||||
public var tag: String { "div" }
|
||||
public func attributes(useDynamicLayout: Bool) -> [HTMLAttribute: String] {
|
||||
guard !useDynamicLayout else { return [:] }
|
||||
return ["style": "position: absolute; width: 0; height: 0; top: 0; left: 0;"]
|
||||
}
|
||||
|
||||
public func primitiveVisitor<V>(useDynamicLayout: Bool) -> ((V) -> ())? where V: ViewVisitor {
|
||||
{
|
||||
if let title = self.title {
|
||||
$0.visit(HTMLTitle(_TextProxy(title).rawText))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ struct HTMLBody: AnyHTML {
|
|||
]
|
||||
}
|
||||
|
||||
extension HTMLMeta.MetaTag {
|
||||
public extension HTMLMeta.MetaTag {
|
||||
func outerHTML() -> String {
|
||||
switch self {
|
||||
case let .charset(charset):
|
||||
|
|
Loading…
Reference in New Issue