From 9313bf16d2a91620df41163a5e70bde2968ce33d Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 30 Jun 2020 23:39:55 +0100 Subject: [PATCH 01/32] Add ColorScheme environment --- Sources/TokamakCore/Tokens/ColorScheme.swift | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Sources/TokamakCore/Tokens/ColorScheme.swift diff --git a/Sources/TokamakCore/Tokens/ColorScheme.swift b/Sources/TokamakCore/Tokens/ColorScheme.swift new file mode 100644 index 000000000..bee96e155 --- /dev/null +++ b/Sources/TokamakCore/Tokens/ColorScheme.swift @@ -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. + +public enum ColorScheme: CaseIterable { + case dark + case light +} + +struct ColorSchemeKey: EnvironmentKey { + static let defaultValue: ColorScheme = .light +} + +public extension EnvironmentValues { + var colorScheme: ColorScheme? { + get { + self[ColorSchemeKey.self] + } + set { + self[ColorSchemeKey.self] = newValue + } + } +} From ce432b7b489a8a94668795dffae10affe4aada51 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 1 Jul 2020 00:43:48 +0100 Subject: [PATCH 02/32] Fix wrong colorScheme computed property type --- Sources/TokamakCore/Tokens/ColorScheme.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/TokamakCore/Tokens/ColorScheme.swift b/Sources/TokamakCore/Tokens/ColorScheme.swift index bee96e155..51d605825 100644 --- a/Sources/TokamakCore/Tokens/ColorScheme.swift +++ b/Sources/TokamakCore/Tokens/ColorScheme.swift @@ -22,7 +22,7 @@ struct ColorSchemeKey: EnvironmentKey { } public extension EnvironmentValues { - var colorScheme: ColorScheme? { + var colorScheme: ColorScheme { get { self[ColorSchemeKey.self] } From a3c69b64e76a7dd8d83ca2decac850764be485df Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 1 Jul 2020 16:10:21 +0100 Subject: [PATCH 03/32] Add DOMEnvironment view with colorScheme state --- Sources/TokamakCore/Tokens/ColorScheme.swift | 6 +++++ Sources/TokamakDOM/DOMRenderer.swift | 6 +++-- .../Environment/DOMEnvironment.swift | 25 +++++++++++++++++++ Sources/TokamakDOM/Tokens/Tokens.swift | 1 + Sources/TokamakDemo/EnvironmentDemo.swift | 4 ++- 5 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 Sources/TokamakDOM/Environment/DOMEnvironment.swift diff --git a/Sources/TokamakCore/Tokens/ColorScheme.swift b/Sources/TokamakCore/Tokens/ColorScheme.swift index 51d605825..29aa7401f 100644 --- a/Sources/TokamakCore/Tokens/ColorScheme.swift +++ b/Sources/TokamakCore/Tokens/ColorScheme.swift @@ -31,3 +31,9 @@ public extension EnvironmentValues { } } } + +public extension View { + func colorScheme(_ colorScheme: ColorScheme) -> some View { + environment(\.colorScheme, colorScheme) + } +} diff --git a/Sources/TokamakDOM/DOMRenderer.swift b/Sources/TokamakDOM/DOMRenderer.swift index afc29a072..928fd1c50 100644 --- a/Sources/TokamakDOM/DOMRenderer.swift +++ b/Sources/TokamakDOM/DOMRenderer.swift @@ -65,9 +65,11 @@ public final class DOMRenderer: Renderer { rootStyle.innerHTML = .string(tokamakStyles) _ = head.appendChild!(rootStyle) + var environmentView = DOMEnvironment(scheme: .light, content: view) + reconciler = StackReconciler( - view: view, - target: DOMNode(view, ref), + view: environmentView, + target: DOMNode(environmentView, ref), renderer: self ) { closure in let fn = JSClosure { _ in diff --git a/Sources/TokamakDOM/Environment/DOMEnvironment.swift b/Sources/TokamakDOM/Environment/DOMEnvironment.swift new file mode 100644 index 000000000..862057a66 --- /dev/null +++ b/Sources/TokamakDOM/Environment/DOMEnvironment.swift @@ -0,0 +1,25 @@ +// 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. + +import TokamakCore + +struct DOMEnvironment: View { + @State var scheme: ColorScheme + + let content: V + + var body: some View { + content.colorScheme(scheme) + } +} diff --git a/Sources/TokamakDOM/Tokens/Tokens.swift b/Sources/TokamakDOM/Tokens/Tokens.swift index bdf90fef6..7acf461cf 100644 --- a/Sources/TokamakDOM/Tokens/Tokens.swift +++ b/Sources/TokamakDOM/Tokens/Tokens.swift @@ -16,6 +16,7 @@ import TokamakCore public typealias Font = TokamakCore.Font public typealias Color = TokamakCore.Color +public typealias ColorScheme = TokamakCore.ColorScheme extension Color: CustomStringConvertible { public var description: String { diff --git a/Sources/TokamakDemo/EnvironmentDemo.swift b/Sources/TokamakDemo/EnvironmentDemo.swift index 65e6d38ea..b2225d63f 100644 --- a/Sources/TokamakDemo/EnvironmentDemo.swift +++ b/Sources/TokamakDemo/EnvironmentDemo.swift @@ -18,11 +18,13 @@ import TokamakDOM struct EnvironmentDemo: View { + @Environment(\.colorScheme) var scheme: ColorScheme + @Environment(\.font) var font: Font? var body: some View { if let font = font { - return Text("\(font)") + return Text("ColorScheme is \(scheme), font is \(font)") } else { return Text("`font` environment not set.") } From 17764340b7981c98ec24680a3d45f543a5ca9b21 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 1 Jul 2020 16:30:57 +0100 Subject: [PATCH 04/32] Fix linter line length warning --- Sources/TokamakDOM/DOMRenderer.swift | 4 ++-- Sources/TokamakDOM/Resources/TokamakStyles.swift | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Sources/TokamakDOM/DOMRenderer.swift b/Sources/TokamakDOM/DOMRenderer.swift index 928fd1c50..e0ae51af0 100644 --- a/Sources/TokamakDOM/DOMRenderer.swift +++ b/Sources/TokamakDOM/DOMRenderer.swift @@ -59,13 +59,13 @@ public final class DOMRenderer: Renderer { public init(_ view: V, _ ref: JSObjectRef) { rootRef = ref - rootRef.style = "display: flex; width: 100%; height: 100%; justify-content: center; align-items: center; overflow: hidden;" + rootRef.style = .string(rootNodeStyles) let rootStyle = document.createElement!("style").object! rootStyle.innerHTML = .string(tokamakStyles) _ = head.appendChild!(rootStyle) - var environmentView = DOMEnvironment(scheme: .light, content: view) + let environmentView = DOMEnvironment(scheme: .light, content: view) reconciler = StackReconciler( view: environmentView, diff --git a/Sources/TokamakDOM/Resources/TokamakStyles.swift b/Sources/TokamakDOM/Resources/TokamakStyles.swift index 57066b8de..027f8a35a 100644 --- a/Sources/TokamakDOM/Resources/TokamakStyles.swift +++ b/Sources/TokamakDOM/Resources/TokamakStyles.swift @@ -25,3 +25,12 @@ let tokamakStyles = """ height: 0; } """ + +let rootNodeStyles = """ +display: flex; +width: 100%; +height: 100%; +justify-content: center; +align-items: center; +overflow: hidden; +""" From af498ac415e360f17217ac46ad764286b07dc1e0 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 2 Jul 2020 17:20:26 +0100 Subject: [PATCH 05/32] Use onAppear/onDisappear in DOMEnvironment --- Sources/TokamakCore/Views/View.swift | 2 +- .../Environment/DOMEnvironment.swift | 21 ++++++++++++++++++- Sources/TokamakDemo/EnvironmentDemo.swift | 2 +- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Sources/TokamakCore/Views/View.swift b/Sources/TokamakCore/Views/View.swift index 51e49e509..12b734af5 100644 --- a/Sources/TokamakCore/Views/View.swift +++ b/Sources/TokamakCore/Views/View.swift @@ -39,7 +39,7 @@ protocol GroupView: ParentView {} views is made in the reconciler in `TokamakCore` based on their `body` type, host views have body type `Never`. `ViewDeferredToRenderer` allows renderers to override that per-platform and render host views as composite by providing their own `deferredBody` implementation. -*/ + */ public protocol ViewDeferredToRenderer { var deferredBody: AnyView { get } } diff --git a/Sources/TokamakDOM/Environment/DOMEnvironment.swift b/Sources/TokamakDOM/Environment/DOMEnvironment.swift index 862057a66..4ab785c4f 100644 --- a/Sources/TokamakDOM/Environment/DOMEnvironment.swift +++ b/Sources/TokamakDOM/Environment/DOMEnvironment.swift @@ -12,14 +12,33 @@ // See the License for the specific language governing permissions and // limitations under the License. +import JavaScriptKit import TokamakCore +/// This is effectively a singleton, but a mounted `DOMEnvironment` is assumed to be a singleton +/// too. It can't be declared as a property of `DOMEnvironment` as you can't modify it from within +/// `body` callbacks. +private var colorSchemeListener: JSClosure? + struct DOMEnvironment: View { @State var scheme: ColorScheme let content: V + private let matchMedia = + JSObjectRef.global.window.object!.matchMedia!("(prefers-color-scheme: dark)").object! + var body: some View { - content.colorScheme(scheme) + content + .colorScheme(scheme) + .onAppear { + colorSchemeListener = JSClosure { + scheme = $0[0].object!.matches.boolean == true ? .dark : .light + return .undefined + } + _ = matchMedia.addEventListener!("change", colorSchemeListener!) + }.onDisappear { + _ = matchMedia.removeEventListener!("change", colorSchemeListener!) + } } } diff --git a/Sources/TokamakDemo/EnvironmentDemo.swift b/Sources/TokamakDemo/EnvironmentDemo.swift index b2225d63f..48521b0f0 100644 --- a/Sources/TokamakDemo/EnvironmentDemo.swift +++ b/Sources/TokamakDemo/EnvironmentDemo.swift @@ -26,7 +26,7 @@ struct EnvironmentDemo: View { if let font = font { return Text("ColorScheme is \(scheme), font is \(font)") } else { - return Text("`font` environment not set.") + return Text("ColorScheme is \(scheme), `font` environment not set.") } } } From f792a9f8a91ab8258ce47dcefbe03d5b9830073a Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 21 Jul 2020 12:53:57 +0100 Subject: [PATCH 06/32] Generalize environment updates --- .../TokamakCore/Environment/Environment.swift | 6 ++-- .../MountedViews/MountedCompositeView.swift | 4 +-- .../MountedViews/MountedHostView.swift | 16 +--------- .../MountedViews/MountedView.swift | 32 ++++++++++++------- Sources/TokamakCore/StackReconciler.swift | 4 ++- Sources/TokamakCore/Styles/ToggleStyle.swift | 6 ++-- Sources/TokamakCore/Tokens/Color.swift | 2 +- Sources/TokamakCore/Tokens/ColorScheme.swift | 10 +++--- .../Views/Containers/DisclosureGroup.swift | 2 +- .../TokamakCore/Views/Containers/List.swift | 2 +- .../TokamakCore/Views/Selectors/Picker.swift | 4 +-- Sources/TokamakCore/Views/Text/Text.swift | 2 +- .../TokamakCore/Views/Text/TextField.swift | 2 +- Sources/TokamakCore/Views/Toggle.swift | 2 +- Sources/TokamakDOM/DOMEnvironment.swift | 17 +++++----- Sources/TokamakDOM/DOMRenderer.swift | 18 ++++++++--- Sources/TokamakDOM/Styles/ColorScheme.swift | 25 +++++++++++++++ Sources/TokamakDemo/EnvironmentDemo.swift | 17 ++++++---- 18 files changed, 105 insertions(+), 66 deletions(-) create mode 100644 Sources/TokamakDOM/Styles/ColorScheme.swift diff --git a/Sources/TokamakCore/Environment/Environment.swift b/Sources/TokamakCore/Environment/Environment.swift index 2e8511a7b..00f9b19f2 100644 --- a/Sources/TokamakCore/Environment/Environment.swift +++ b/Sources/TokamakCore/Environment/Environment.swift @@ -29,14 +29,15 @@ protocol EnvironmentReader { case value(Value) } - var content: Content - let keyPath: KeyPath + private var content: Content + private let keyPath: KeyPath public init(_ keyPath: KeyPath) { content = .keyPath(keyPath) self.keyPath = keyPath } mutating func setContent(from values: EnvironmentValues) { + print("content set for \(keyPath),\nvalues is \(values)") content = .value(values[keyPath: keyPath]) } @@ -45,6 +46,7 @@ protocol EnvironmentReader { case let .value(value): return value case let .keyPath(keyPath): + print("EnvironmentValues() called for keyPath \(keyPath)") // not bound to a view, return the default value. return EnvironmentValues()[keyPath: keyPath] } diff --git a/Sources/TokamakCore/MountedViews/MountedCompositeView.swift b/Sources/TokamakCore/MountedViews/MountedCompositeView.swift index 0d5bc66a5..ec98caeb2 100644 --- a/Sources/TokamakCore/MountedViews/MountedCompositeView.swift +++ b/Sources/TokamakCore/MountedViews/MountedCompositeView.swift @@ -33,14 +33,12 @@ final class MountedCompositeView: MountedView, Hashable { var state = [Any]() var subscriptions = [AnyCancellable]() - var environmentValues: EnvironmentValues init(_ view: AnyView, _ parentTarget: R.TargetType, _ environmentValues: EnvironmentValues) { self.parentTarget = parentTarget - self.environmentValues = environmentValues - super.init(view) + super.init(view, environmentValues) } override func mount(with reconciler: StackReconciler) { diff --git a/Sources/TokamakCore/MountedViews/MountedHostView.swift b/Sources/TokamakCore/MountedViews/MountedHostView.swift index 0ad4ece40..eaaa70d15 100644 --- a/Sources/TokamakCore/MountedViews/MountedHostView.swift +++ b/Sources/TokamakCore/MountedViews/MountedHostView.swift @@ -33,15 +33,12 @@ public final class MountedHostView: MountedView { /// Target of this host view supplied by a renderer after mounting has completed. private var target: R.TargetType? - private let environmentValues: EnvironmentValues - init(_ view: AnyView, _ parentTarget: R.TargetType, _ environmentValues: EnvironmentValues) { self.parentTarget = parentTarget - self.environmentValues = environmentValues - super.init(view) + super.init(view, environmentValues) } override func mount(with reconciler: StackReconciler) { @@ -105,17 +102,6 @@ public final class MountedHostView: MountedView { let newChild: MountedView if firstChild.typeConstructorName == mountedChildren[0].view.typeConstructorName { child.view = firstChild - // Inject Environment - // swiftlint:disable force_try - let viewInfo = try! typeInfo(of: child.view.type) - for prop in viewInfo.properties.filter({ $0.type is EnvironmentReader.Type }) { - // swiftlint:disable force_cast - var wrapper = try! prop.get(from: child.view.view) as! EnvironmentReader - wrapper.setContent(from: environmentValues) - try! prop.set(value: wrapper, on: &child.view.view) - // swiftlint:enable force_cast - } - // swiftlint:enable force_try child.update(with: reconciler) newChild = child } else { diff --git a/Sources/TokamakCore/MountedViews/MountedView.swift b/Sources/TokamakCore/MountedViews/MountedView.swift index 96d4bc775..7cbd18bb5 100644 --- a/Sources/TokamakCore/MountedViews/MountedView.swift +++ b/Sources/TokamakCore/MountedViews/MountedView.swift @@ -19,9 +19,11 @@ import Runtime public class MountedView { public internal(set) var view: AnyView + let environmentValues: EnvironmentValues - init(_ view: AnyView) { + init(_ view: AnyView, _ environmentValues: EnvironmentValues) { self.view = view + self.environmentValues = environmentValues } func mount(with reconciler: StackReconciler) { @@ -33,7 +35,22 @@ public class MountedView { } func update(with reconciler: StackReconciler) { - fatalError("implement \(#function) in subclass") + // swiftlint:disable:next force_try + try! typeInfo(of: view.type).injectEnvironment(from: environmentValues, into: &view.view) + } +} + +extension TypeInfo { + func injectEnvironment(from environmentValues: EnvironmentValues, into view: inout Any) { + for prop in properties.filter({ $0.type is EnvironmentReader.Type }) { + // swiftlint:disable force_cast + // swiftlint:disable force_try + var wrapper = try! prop.get(from: view) as! EnvironmentReader + wrapper.setContent(from: environmentValues) + try! prop.set(value: wrapper, on: &view) + // swiftlint:enable force_cast + // swiftlint:enable force_try + } } } @@ -60,14 +77,7 @@ extension View { } } - // Inject @Environment values - for prop in viewInfo.properties.filter({ $0.type is EnvironmentReader.Type }) { - // swiftlint:disable force_cast - var wrapper = try! prop.get(from: any.view) as! EnvironmentReader - wrapper.setContent(from: modifiedEnv) - try! prop.set(value: wrapper, on: &extractedView) - // swiftlint:enable force_cast - } + viewInfo.injectEnvironment(from: modifiedEnv, into: &extractedView) // Set the extractedView back on the AnyView after modification let anyViewInfo = try! typeInfo(of: AnyView.self) @@ -77,7 +87,7 @@ extension View { // Make MountedView let anyView = injectableView as? AnyView ?? AnyView(injectableView) if anyView.type == EmptyView.self { - return MountedNull(anyView) + return MountedNull(anyView, modifiedEnv) } else if anyView.bodyType == Never.self && !(anyView.type is ViewDeferredToRenderer.Type) { return MountedHostView(anyView, parentTarget, modifiedEnv) } else { diff --git a/Sources/TokamakCore/StackReconciler.swift b/Sources/TokamakCore/StackReconciler.swift index 5197e07aa..90d134e65 100644 --- a/Sources/TokamakCore/StackReconciler.swift +++ b/Sources/TokamakCore/StackReconciler.swift @@ -29,13 +29,14 @@ public final class StackReconciler { view: V, target: R.TargetType, renderer: R, + environment: EnvironmentValues, scheduler: @escaping (@escaping () -> ()) -> () ) { self.renderer = renderer self.scheduler = scheduler rootTarget = target - rootView = view.makeMountedView(target, EnvironmentValues()) + rootView = view.makeMountedView(target, environment) rootView.mount(with: self) } @@ -122,6 +123,7 @@ public final class StackReconciler { } } + print("querying body of \(compositeView.view.type)") let result = compositeView.view.bodyClosure(compositeView.view.view) return result diff --git a/Sources/TokamakCore/Styles/ToggleStyle.swift b/Sources/TokamakCore/Styles/ToggleStyle.swift index 5fe18bb92..b34020dbd 100644 --- a/Sources/TokamakCore/Styles/ToggleStyle.swift +++ b/Sources/TokamakCore/Styles/ToggleStyle.swift @@ -52,7 +52,7 @@ public struct _AnyToggleStyle: ToggleStyle { } } -public enum ToggleStyleKey: EnvironmentKey { +public enum _ToggleStyleKey: EnvironmentKey { public static var defaultValue: _AnyToggleStyle { fatalError("\(self) must have a renderer-provided default value") } @@ -61,10 +61,10 @@ public enum ToggleStyleKey: EnvironmentKey { extension EnvironmentValues { var toggleStyle: _AnyToggleStyle { get { - self[ToggleStyleKey.self] + self[_ToggleStyleKey.self] } set { - self[ToggleStyleKey.self] = newValue + self[_ToggleStyleKey.self] = newValue } } } diff --git a/Sources/TokamakCore/Tokens/Color.swift b/Sources/TokamakCore/Tokens/Color.swift index 6170f9c0d..93c4d8905 100644 --- a/Sources/TokamakCore/Tokens/Color.swift +++ b/Sources/TokamakCore/Tokens/Color.swift @@ -17,7 +17,7 @@ public struct Color: Hashable, Equatable { // FIXME: This is not injected. - @Environment(\.accentColor) static var envAccentColor: Color? + @Environment(\.accentColor) static var envAccentColor public enum RGBColorSpace { case sRGB diff --git a/Sources/TokamakCore/Tokens/ColorScheme.swift b/Sources/TokamakCore/Tokens/ColorScheme.swift index 29aa7401f..87c2cb798 100644 --- a/Sources/TokamakCore/Tokens/ColorScheme.swift +++ b/Sources/TokamakCore/Tokens/ColorScheme.swift @@ -17,17 +17,19 @@ public enum ColorScheme: CaseIterable { case light } -struct ColorSchemeKey: EnvironmentKey { - static let defaultValue: ColorScheme = .light +public struct _ColorSchemeKey: EnvironmentKey { + public static var defaultValue: ColorScheme { + fatalError("\(self) must have a renderer-provided default value") + } } public extension EnvironmentValues { var colorScheme: ColorScheme { get { - self[ColorSchemeKey.self] + self[_ColorSchemeKey.self] } set { - self[ColorSchemeKey.self] = newValue + self[_ColorSchemeKey.self] = newValue } } } diff --git a/Sources/TokamakCore/Views/Containers/DisclosureGroup.swift b/Sources/TokamakCore/Views/Containers/DisclosureGroup.swift index 85cda3d77..7e5ff44ff 100644 --- a/Sources/TokamakCore/Views/Containers/DisclosureGroup.swift +++ b/Sources/TokamakCore/Views/Containers/DisclosureGroup.swift @@ -20,7 +20,7 @@ public struct DisclosureGroup: View @State var isExpanded: Bool = false let isExpandedBinding: Binding? - @Environment(\._outlineGroupStyle) var style: _OutlineGroupStyle + @Environment(\._outlineGroupStyle) var style let label: Label let content: () -> Content diff --git a/Sources/TokamakCore/Views/Containers/List.swift b/Sources/TokamakCore/Views/Containers/List.swift index 8e9af4fbf..277a60ba2 100644 --- a/Sources/TokamakCore/Views/Containers/List.swift +++ b/Sources/TokamakCore/Views/Containers/List.swift @@ -25,7 +25,7 @@ public struct List: View let selection: _Selection let content: Content - @Environment(\.listStyle) var style: ListStyle + @Environment(\.listStyle) var style public init(selection: Binding>?, @ViewBuilder content: () -> Content) { self.selection = .many(selection) diff --git a/Sources/TokamakCore/Views/Selectors/Picker.swift b/Sources/TokamakCore/Views/Selectors/Picker.swift index f270e4317..94faa6b09 100644 --- a/Sources/TokamakCore/Views/Selectors/Picker.swift +++ b/Sources/TokamakCore/Views/Selectors/Picker.swift @@ -16,7 +16,7 @@ public struct _PickerContainer, @@ -36,7 +36,7 @@ public struct _PickerContainer: View where Label: View { let textBinding: Binding let onEditingChanged: (Bool) -> () let onCommit: () -> () - @Environment(\.textFieldStyle) var style: TextFieldStyle + @Environment(\.textFieldStyle) var style public var body: Never { neverBody("TextField") diff --git a/Sources/TokamakCore/Views/Toggle.swift b/Sources/TokamakCore/Views/Toggle.swift index e0fcf5b44..725ba6125 100644 --- a/Sources/TokamakCore/Views/Toggle.swift +++ b/Sources/TokamakCore/Views/Toggle.swift @@ -18,7 +18,7 @@ public struct Toggle