Add ViewTree, onHover modifier, and various other debug changes
This commit is contained in:
parent
7ab7178004
commit
bc5470d05a
|
@ -19,6 +19,15 @@
|
|||
"version": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "pure-swift-json",
|
||||
"repositoryURL": "https://github.com/fabianfett/pure-swift-json.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "5dc8ec1d857f3b56e3a718a01165ae80c7677d48",
|
||||
"version": "0.4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Runtime",
|
||||
"repositoryURL": "https://github.com/MaxDesiatov/Runtime.git",
|
||||
|
|
|
@ -40,6 +40,7 @@ let package = Package(
|
|||
.package(url: "https://github.com/kateinoigakukun/JavaScriptKit.git", .revision("c90e82f")),
|
||||
.package(url: "https://github.com/MaxDesiatov/Runtime.git", .branch("wasi-build")),
|
||||
.package(url: "https://github.com/MaxDesiatov/OpenCombine.git", .branch("observable-object")),
|
||||
.package(url: "https://github.com/fabianfett/pure-swift-json.git", from: "0.4.0"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define
|
||||
|
@ -66,7 +67,13 @@ let package = Package(
|
|||
),
|
||||
.target(
|
||||
name: "TokamakDOM",
|
||||
dependencies: ["CombineShim", "JavaScriptKit", "TokamakCore", "TokamakStaticHTML"]
|
||||
dependencies: [
|
||||
"CombineShim",
|
||||
"JavaScriptKit",
|
||||
"TokamakCore",
|
||||
"TokamakStaticHTML",
|
||||
.product(name: "PureSwiftJSON", package: "pure-swift-json"),
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "TokamakShim",
|
||||
|
|
|
@ -22,8 +22,8 @@ public struct _ViewModifier_Content<Modifier>: View where Modifier: ViewModifier
|
|||
public let modifier: Modifier
|
||||
public let view: AnyView
|
||||
|
||||
public var body: Never {
|
||||
neverBody("_ViewModifier_Content")
|
||||
public var body: AnyView {
|
||||
view
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,3 +38,17 @@ extension ViewModifier where Body == Never {
|
|||
fatalError("\(self) is a primitive `ViewModifier`, you're not supposed to run `body(content:)`")
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ViewModifierDeferredToRenderer {
|
||||
func deferredBody<Content>(content: Content) -> AnyView where Content: View
|
||||
}
|
||||
|
||||
public struct _ViewModifierProxy<Modifier: ViewModifier> {
|
||||
let subject: Modifier
|
||||
|
||||
public init(_ subject: Modifier) { self.subject = subject }
|
||||
|
||||
public func body<Content: View>(content: Content) -> Modifier.Body {
|
||||
subject.body(content: .init(modifier: subject, view: AnyView(content)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2020 Tokamak contributors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Created by Carson Katri on 8/6/20.
|
||||
//
|
||||
|
||||
extension View {
|
||||
public func onHover(perform action: @escaping (Bool) -> ()) -> some View {
|
||||
modifier(_HoverRegionModifier(action))
|
||||
}
|
||||
}
|
||||
|
||||
public struct _HoverRegionModifier: ViewModifier {
|
||||
public let callback: (Bool) -> ()
|
||||
public init(_ callback: @escaping (Bool) -> ()) {
|
||||
self.callback = callback
|
||||
}
|
||||
|
||||
public func body(content: Content) -> some View {
|
||||
content
|
||||
}
|
||||
}
|
|
@ -28,10 +28,18 @@ final class MountedApp<R: Renderer>: MountedCompositeElement<R> {
|
|||
let child: MountedElement<R> = mountChild(childBody)
|
||||
mountedChildren = [child]
|
||||
child.mount(with: reconciler)
|
||||
#if DEBUG
|
||||
reconciler.debugTree[ObjectIdentifier(child)] = child.debugNode(parent: self)
|
||||
#endif
|
||||
}
|
||||
|
||||
override func unmount(with reconciler: StackReconciler<R>) {
|
||||
mountedChildren.forEach { $0.unmount(with: reconciler) }
|
||||
mountedChildren.forEach {
|
||||
$0.unmount(with: reconciler)
|
||||
#if DEBUG
|
||||
reconciler.debugTree[ObjectIdentifier($0)] = nil
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private func mountChild(_ childBody: _AnyScene) -> MountedElement<R> {
|
||||
|
@ -56,4 +64,15 @@ final class MountedApp<R: Renderer>: MountedCompositeElement<R> {
|
|||
mountChild: { mountChild($0) }
|
||||
)
|
||||
}
|
||||
|
||||
override func debugNode(parent: MountedElement<R>? = nil) -> ViewTree<R>.Node {
|
||||
// swiftlint:disable:next force_try
|
||||
let info = try! typeInfo(of: app.type)
|
||||
return .init(type: app.type,
|
||||
isPrimitive: false,
|
||||
isHost: false,
|
||||
dynamicProperties: info.properties.filter { $0.type is DynamicProperty.Type }.map(\.name),
|
||||
object: self,
|
||||
parent: parent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,9 @@ final class MountedCompositeView<R: Renderer>: MountedCompositeElement<R> {
|
|||
let child: MountedElement<R> = childBody.makeMountedView(parentTarget, environmentValues)
|
||||
mountedChildren = [child]
|
||||
child.mount(with: reconciler)
|
||||
#if DEBUG
|
||||
reconciler.debugTree[ObjectIdentifier(child)] = child.debugNode(parent: self)
|
||||
#endif
|
||||
|
||||
// `_TargetRef` is a composite view, so it's enough to check for it only here
|
||||
if var targetRef = view.view as? TargetRefType {
|
||||
|
@ -47,7 +50,12 @@ final class MountedCompositeView<R: Renderer>: MountedCompositeElement<R> {
|
|||
}
|
||||
|
||||
override func unmount(with reconciler: StackReconciler<R>) {
|
||||
mountedChildren.forEach { $0.unmount(with: reconciler) }
|
||||
mountedChildren.forEach {
|
||||
$0.unmount(with: reconciler)
|
||||
#if DEBUG
|
||||
reconciler.debugTree[ObjectIdentifier($0)] = nil
|
||||
#endif
|
||||
}
|
||||
|
||||
if let appearanceAction = view.view as? AppearanceActionType {
|
||||
appearanceAction.disappear?()
|
||||
|
@ -67,4 +75,15 @@ final class MountedCompositeView<R: Renderer>: MountedCompositeElement<R> {
|
|||
mountChild: { $0.makeMountedView(parentTarget, environmentValues) }
|
||||
)
|
||||
}
|
||||
|
||||
override func debugNode(parent: MountedElement<R>? = nil) -> ViewTree<R>.Node {
|
||||
// swiftlint:disable:next force_try
|
||||
let info = try! typeInfo(of: view.type)
|
||||
return .init(type: view.type,
|
||||
isPrimitive: view.bodyType is Never.Type,
|
||||
isHost: false,
|
||||
dynamicProperties: info.properties.filter { $0.type is DynamicProperty.Type }.map(\.name),
|
||||
object: self,
|
||||
parent: parent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ enum MountedElementKind {
|
|||
}
|
||||
|
||||
public class MountedElement<R: Renderer> {
|
||||
private var element: MountedElementKind
|
||||
var element: MountedElementKind
|
||||
|
||||
public internal(set) var app: _AnyApp {
|
||||
get {
|
||||
|
@ -132,6 +132,10 @@ public class MountedElement<R: Renderer> {
|
|||
func update(with reconciler: StackReconciler<R>) {
|
||||
fatalError("implement \(#function) in subclass")
|
||||
}
|
||||
|
||||
func debugNode(parent: MountedElement<R>? = nil) -> ViewTree<R>.Node {
|
||||
fatalError("implement \(#function) in subclass")
|
||||
}
|
||||
}
|
||||
|
||||
extension TypeInfo {
|
||||
|
|
|
@ -21,4 +21,12 @@ final class MountedEmptyView<R: Renderer>: MountedElement<R> {
|
|||
override func unmount(with reconciler: StackReconciler<R>) {}
|
||||
|
||||
override func update(with reconciler: StackReconciler<R>) {}
|
||||
|
||||
override func debugNode(parent: MountedElement<R>? = nil) -> ViewTree<R>.Node {
|
||||
.init(type: EmptyView.self,
|
||||
isPrimitive: true,
|
||||
isHost: false,
|
||||
object: self,
|
||||
parent: parent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,12 @@ public final class MountedHostView<R: Renderer>: MountedElement<R> {
|
|||
mountedChildren = view.children.map {
|
||||
$0.makeMountedView(target, environmentValues)
|
||||
}
|
||||
mountedChildren.forEach { $0.mount(with: reconciler) }
|
||||
mountedChildren.forEach {
|
||||
$0.mount(with: reconciler)
|
||||
#if DEBUG
|
||||
reconciler.debugTree[ObjectIdentifier($0)] = $0.debugNode(parent: self)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
override func unmount(with reconciler: StackReconciler<R>) {
|
||||
|
@ -59,7 +64,12 @@ public final class MountedHostView<R: Renderer>: MountedElement<R> {
|
|||
from: parentTarget,
|
||||
with: self
|
||||
) {
|
||||
self.mountedChildren.forEach { $0.unmount(with: reconciler) }
|
||||
self.mountedChildren.forEach {
|
||||
$0.unmount(with: reconciler)
|
||||
#if DEBUG
|
||||
reconciler.debugTree[ObjectIdentifier($0)] = nil
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,4 +142,13 @@ public final class MountedHostView<R: Renderer>: MountedElement<R> {
|
|||
case (true, true): ()
|
||||
}
|
||||
}
|
||||
|
||||
override func debugNode(parent: MountedElement<R>? = nil) -> ViewTree<R>.Node {
|
||||
.init(type: view.type,
|
||||
isPrimitive: view.bodyType is Never.Type,
|
||||
isHost: true,
|
||||
target: target,
|
||||
object: self,
|
||||
parent: parent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,10 +35,18 @@ final class MountedScene<R: Renderer>: MountedCompositeElement<R> {
|
|||
let child: MountedElement<R> = childBody.makeMountedElement(parentTarget, environmentValues)
|
||||
mountedChildren = [child]
|
||||
child.mount(with: reconciler)
|
||||
#if DEBUG
|
||||
reconciler.debugTree[ObjectIdentifier(child)] = child.debugNode(parent: self)
|
||||
#endif
|
||||
}
|
||||
|
||||
override func unmount(with reconciler: StackReconciler<R>) {
|
||||
mountedChildren.forEach { $0.unmount(with: reconciler) }
|
||||
mountedChildren.forEach {
|
||||
$0.unmount(with: reconciler)
|
||||
#if DEBUG
|
||||
reconciler.debugTree[ObjectIdentifier($0)] = nil
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
override func update(with reconciler: StackReconciler<R>) {
|
||||
|
@ -59,6 +67,17 @@ final class MountedScene<R: Renderer>: MountedCompositeElement<R> {
|
|||
mountChild: { $0.makeMountedElement(parentTarget, environmentValues) }
|
||||
)
|
||||
}
|
||||
|
||||
override func debugNode(parent: MountedElement<R>? = nil) -> ViewTree<R>.Node {
|
||||
// swiftlint:disable:next force_try
|
||||
let info = try! typeInfo(of: scene.type)
|
||||
return .init(type: scene.type,
|
||||
isPrimitive: scene.bodyType is Never.Type,
|
||||
isHost: false,
|
||||
dynamicProperties: info.properties.filter { $0.type is DynamicProperty.Type }.map(\.name),
|
||||
object: self,
|
||||
parent: parent)
|
||||
}
|
||||
}
|
||||
|
||||
extension _AnyScene.BodyResult {
|
||||
|
|
|
@ -68,4 +68,14 @@ public protocol Renderer: AnyObject {
|
|||
with host: MountedHost,
|
||||
completion: @escaping () -> ()
|
||||
)
|
||||
|
||||
func debugTree(
|
||||
_ tree: Set<ViewTree<Self>.Node>,
|
||||
context: ViewTree<Self>.Context
|
||||
)
|
||||
}
|
||||
|
||||
extension Renderer {
|
||||
public func debugTree(_ tree: Set<ViewTree<Self>.Node>,
|
||||
context: ViewTree<Self>.Context) {}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,11 @@ public final class StackReconciler<R: Renderer> {
|
|||
*/
|
||||
private let scheduler: (@escaping () -> ()) -> ()
|
||||
|
||||
#if DEBUG
|
||||
typealias Tree = ViewTree<R>
|
||||
var debugTree: [ObjectIdentifier: Tree.Node]
|
||||
#endif
|
||||
|
||||
public init<V: View>(
|
||||
view: V,
|
||||
target: R.TargetType,
|
||||
|
@ -76,7 +81,15 @@ public final class StackReconciler<R: Renderer> {
|
|||
|
||||
rootElement = AnyView(view).makeMountedView(target, environment)
|
||||
|
||||
#if DEBUG
|
||||
debugTree = [ObjectIdentifier(rootElement): rootElement.debugNode(parent: nil)]
|
||||
#endif
|
||||
|
||||
rootElement.mount(with: self)
|
||||
|
||||
#if DEBUG
|
||||
renderer.debugTree(Set(debugTree.values), context: .firstRender)
|
||||
#endif
|
||||
}
|
||||
|
||||
public init<A: App>(
|
||||
|
@ -92,11 +105,19 @@ public final class StackReconciler<R: Renderer> {
|
|||
|
||||
rootElement = MountedApp(app, target, environment)
|
||||
|
||||
#if DEBUG
|
||||
debugTree = [ObjectIdentifier(rootElement): rootElement.debugNode(parent: nil)]
|
||||
#endif
|
||||
|
||||
rootElement.mount(with: self)
|
||||
if let mountedApp = rootElement as? MountedApp<R> {
|
||||
setupSubscription(for: app._phasePublisher, to: \.scenePhase, of: mountedApp)
|
||||
setupSubscription(for: app._colorSchemePublisher, to: \.colorScheme, of: mountedApp)
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
renderer.debugTree(Set(debugTree.values), context: .firstRender)
|
||||
#endif
|
||||
}
|
||||
|
||||
private func queueStateUpdate(
|
||||
|
@ -118,10 +139,20 @@ public final class StackReconciler<R: Renderer> {
|
|||
}
|
||||
|
||||
private func updateStateAndReconcile() {
|
||||
#if DEBUG
|
||||
let preValues = Set(debugTree.values)
|
||||
#endif
|
||||
|
||||
for mountedView in queuedRerenders {
|
||||
mountedView.update(with: self)
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
// Only send the difference for performance reasons
|
||||
renderer?.debugTree(Set(debugTree.values).subtracting(preValues),
|
||||
context: .update)
|
||||
#endif
|
||||
|
||||
queuedRerenders.removeAll()
|
||||
}
|
||||
|
||||
|
|
|
@ -17,4 +17,10 @@
|
|||
|
||||
public protocol Target: AnyObject {
|
||||
var view: AnyView { get set }
|
||||
/// Provide a way to identify this Target from the debug tree.
|
||||
var debugIdentifier: String? { get }
|
||||
}
|
||||
|
||||
extension Target {
|
||||
public var debugIdentifier: String? { nil }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright 2018-2020 Tokamak contributors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Created by Carson Katri on 8/5/20.
|
||||
//
|
||||
|
||||
import Runtime
|
||||
|
||||
extension ObjectIdentifier: Encodable {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(hashValue)
|
||||
}
|
||||
}
|
||||
|
||||
public final class ViewTree<R: Renderer> {
|
||||
public enum Context {
|
||||
case firstRender
|
||||
case update
|
||||
}
|
||||
|
||||
public struct Node: Encodable, Hashable {
|
||||
let type: String
|
||||
let isPrimitive: Bool
|
||||
let isHost: Bool
|
||||
let dynamicProperties: [String]
|
||||
let target: String?
|
||||
var id: ObjectIdentifier
|
||||
var parent: ObjectIdentifier?
|
||||
|
||||
public static func == (lhs: Self, rhs: Self) -> Bool {
|
||||
lhs.id == rhs.id
|
||||
}
|
||||
|
||||
init(type: Any.Type,
|
||||
isPrimitive: Bool,
|
||||
isHost: Bool,
|
||||
dynamicProperties: [String] = [],
|
||||
target: R.TargetType? = nil,
|
||||
object: MountedElement<R>,
|
||||
parent: MountedElement<R>? = nil) {
|
||||
self.type = "\(type)"
|
||||
id = ObjectIdentifier(object)
|
||||
self.isPrimitive = isPrimitive
|
||||
self.isHost = isHost
|
||||
self.dynamicProperties = dynamicProperties
|
||||
self.target = target?.debugIdentifier
|
||||
if let parent = parent {
|
||||
self.parent = ObjectIdentifier(parent)
|
||||
} else {
|
||||
self.parent = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,10 +42,15 @@ final class DOMNode: Target {
|
|||
private var listeners: [String: JSClosure]
|
||||
var view: AnyView
|
||||
|
||||
let debugIdentifier: String?
|
||||
|
||||
init<V: View>(_ view: V, _ ref: JSObjectRef, _ listeners: [String: Listener] = [:]) {
|
||||
self.ref = ref
|
||||
self.listeners = [:]
|
||||
self.view = AnyView(view)
|
||||
|
||||
debugIdentifier = ref.id.string
|
||||
|
||||
reinstall(listeners)
|
||||
}
|
||||
|
||||
|
@ -53,6 +58,8 @@ final class DOMNode: Target {
|
|||
self.ref = ref
|
||||
view = AnyView(EmptyView())
|
||||
listeners = [:]
|
||||
|
||||
debugIdentifier = ref.id.string
|
||||
}
|
||||
|
||||
/// Removes all existing event listeners on this DOM node and install new ones from
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
//
|
||||
|
||||
import JavaScriptKit
|
||||
import PureSwiftJSON
|
||||
import TokamakCore
|
||||
import TokamakStaticHTML
|
||||
|
||||
|
@ -126,6 +127,8 @@ final class DOMRenderer: Renderer {
|
|||
lastChild.style.object!.height = "100%"
|
||||
}
|
||||
|
||||
lastChild.id = "\(ObjectIdentifier(host).hashValue)".jsValue()
|
||||
|
||||
if let dynamicHTML = anyHTML as? AnyDynamicHTML {
|
||||
return DOMNode(host.view, lastChild, dynamicHTML.listeners)
|
||||
} else {
|
||||
|
@ -153,4 +156,23 @@ final class DOMRenderer: Renderer {
|
|||
|
||||
_ = parent.ref.removeChild!(target.ref)
|
||||
}
|
||||
|
||||
public func debugTree(
|
||||
_ tree: Set<ViewTree<DOMRenderer>.Node>,
|
||||
context: ViewTree<DOMRenderer>.Context
|
||||
) {
|
||||
if tree.count > 0,
|
||||
let json = try? PSJSONEncoder().encode(tree) {
|
||||
switch context {
|
||||
case .firstRender:
|
||||
JSObjectRef.global.window.object!._tokamak_debug_tree = json.jsValue()
|
||||
case .update:
|
||||
let Object = JSObjectRef.global.Object.function!
|
||||
let details = Object.new()
|
||||
details.detail = json.jsValue()
|
||||
let event = window.CustomEvent.function!.new("_tokamak_debug_tree_update", details)
|
||||
_ = document.body.object!.dispatchEvent!(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2020 Tokamak contributors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Created by Carson Katri on 8/6/20.
|
||||
//
|
||||
|
||||
import TokamakCore
|
||||
|
||||
extension _HoverRegionModifier: ViewModifierDeferredToRenderer {
|
||||
public func deferredBody<Content>(content: Content) -> AnyView where Content: View {
|
||||
AnyView(DynamicHTML("div", listeners: [
|
||||
"mouseover": { _ in callback(true) },
|
||||
"mouseout": { _ in callback(false) },
|
||||
]) { _ViewModifierProxy(self).body(content: content) })
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Carson Katri on 8/7/20.
|
||||
//
|
||||
|
||||
import TokamakShim
|
||||
|
||||
struct HoverDemo: View {
|
||||
@State private var isHovering = false
|
||||
var body: some View {
|
||||
Text(isHovering ? "Hovering" : "Not Hovering")
|
||||
.onHover {
|
||||
isHovering = $0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -121,6 +121,9 @@ struct TokamakDemoView: View {
|
|||
NavItem("Text", destination: TextDemo())
|
||||
NavItem("TextField", destination: TextFieldDemo())
|
||||
}
|
||||
Section(header: Text("Actions")) {
|
||||
NavItem("onHover", destination: HoverDemo())
|
||||
}
|
||||
Section(header: Text("Misc")) {
|
||||
NavItem("Path", destination: PathDemo())
|
||||
NavItem("Environment", destination: EnvironmentDemo().font(.system(size: 8)))
|
||||
|
|
|
@ -50,6 +50,8 @@ extension ModifiedContent: ViewDeferredToRenderer where Content: View {
|
|||
content
|
||||
})
|
||||
}
|
||||
} else if let viewModifier = modifier as? ViewModifierDeferredToRenderer {
|
||||
return viewModifier.deferredBody(content: content)
|
||||
} else {
|
||||
return AnyView(content)
|
||||
}
|
||||
|
|
|
@ -109,6 +109,22 @@ public let tokamakStyles = """
|
|||
border-top-color: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
._tokamak-debug-hover {
|
||||
position: relative;
|
||||
}
|
||||
._tokamak-debug-hover::after {
|
||||
background-color: rgb(100, 178, 200);
|
||||
opacity: 0.6;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
content: '';
|
||||
z-index: 999;
|
||||
}
|
||||
"""
|
||||
|
||||
public let rootNodeStyles = """
|
||||
|
|
Loading…
Reference in New Issue