From 90ebbb5cb7121f85a803d7b60b2ded63f48e118a Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Sat, 18 Sep 2021 15:34:50 -0700 Subject: [PATCH] onHover. Initial implementation of action modifiers --- .../Modifiers/HoverActionModifier.swift | 31 +++++++++++++++ .../TokamakDOM/Modifiers/ActionModifier.swift | 38 +++++++++++++++++++ Sources/TokamakDOM/Modifiers/Hover.swift | 26 +++++++++++++ .../Modifiers/ModifiedContent.swift | 25 ++++++++++++ .../TokamakStaticHTMLTests/HTMLStrategy.swift | 4 +- 5 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 Sources/TokamakCore/Modifiers/HoverActionModifier.swift create mode 100644 Sources/TokamakDOM/Modifiers/ActionModifier.swift create mode 100644 Sources/TokamakDOM/Modifiers/Hover.swift create mode 100644 Sources/TokamakDOM/Modifiers/ModifiedContent.swift diff --git a/Sources/TokamakCore/Modifiers/HoverActionModifier.swift b/Sources/TokamakCore/Modifiers/HoverActionModifier.swift new file mode 100644 index 000000000..190d3f74a --- /dev/null +++ b/Sources/TokamakCore/Modifiers/HoverActionModifier.swift @@ -0,0 +1,31 @@ +// Copyright 2021 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. +/// Underscore is present in the name for SwiftUI compatibility. +public struct _HoverActionModifier: ViewModifier { + public var hover: ((Bool) -> ())? + + public typealias Body = Never +} + +extension ModifiedContent + where Content: View, Modifier == _HoverActionModifier +{ + var hover: ((Bool) -> ())? { modifier.hover } +} + +public extension View { + func onHover(perform action: ((Bool) -> ())?) -> some View { + modifier(_HoverActionModifier(hover: action)) + } +} diff --git a/Sources/TokamakDOM/Modifiers/ActionModifier.swift b/Sources/TokamakDOM/Modifiers/ActionModifier.swift new file mode 100644 index 000000000..4b1b477cf --- /dev/null +++ b/Sources/TokamakDOM/Modifiers/ActionModifier.swift @@ -0,0 +1,38 @@ +// Copyright 2021 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. + +import TokamakCore + +public protocol DOMActionModifier { + var listeners: [String: Listener] { get } +} + +extension ModifiedContent + where Content: AnyDynamicHTML, Modifier: DOMActionModifier +{ + // Merge listeners + var listeners: [String: Listener] { + var attr = content.listeners + for (key, val) in modifier.listeners { + if let prev = attr[key] { + attr[key] = { input in + val(input) + prev(input) + } + } + } + + return attr + } +} diff --git a/Sources/TokamakDOM/Modifiers/Hover.swift b/Sources/TokamakDOM/Modifiers/Hover.swift new file mode 100644 index 000000000..899cb9cde --- /dev/null +++ b/Sources/TokamakDOM/Modifiers/Hover.swift @@ -0,0 +1,26 @@ +// Copyright 2021 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. + +import TokamakCore + +extension _HoverActionModifier: DOMActionModifier { + public var listeners: [String: Listener] { + [ + "mouseover": + { _ in hover?(true) }, + "mouseout": + { _ in hover?(false) }, + ] + } +} diff --git a/Sources/TokamakDOM/Modifiers/ModifiedContent.swift b/Sources/TokamakDOM/Modifiers/ModifiedContent.swift new file mode 100644 index 000000000..4624ce213 --- /dev/null +++ b/Sources/TokamakDOM/Modifiers/ModifiedContent.swift @@ -0,0 +1,25 @@ +// Copyright 2021 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. + +import TokamakCore + +// TOOD: Add _AnyModifiedActionContent similar to TokamakStaticHTML/ModifiedContent.swift? +extension ModifiedContent: DOMPrimitive where Content: View, Modifier: DOMActionModifier { + public var renderedBody: AnyView { + // TODO: Combine DOM nodes when possible, rather than generating arbitrary new ones + AnyView(DynamicHTML("div", listeners: modifier.listeners) { + content + }) + } +} diff --git a/Tests/TokamakStaticHTMLTests/HTMLStrategy.swift b/Tests/TokamakStaticHTMLTests/HTMLStrategy.swift index 4714eb79a..8cba5368d 100644 --- a/Tests/TokamakStaticHTMLTests/HTMLStrategy.swift +++ b/Tests/TokamakStaticHTMLTests/HTMLStrategy.swift @@ -15,8 +15,8 @@ #if canImport(SnapshotTesting) import SnapshotTesting -extension Snapshotting where Value == String, Format == String { - public static let html = Snapshotting(pathExtension: "html", diffing: .lines) +public extension Snapshotting where Value == String, Format == String { + static let html = Snapshotting(pathExtension: "html", diffing: .lines) } #endif