Add `ColorScheme` environment (#136)
You can see the dark scheme environment text representation updated in `EnvironmentDemo`. I suggest adding default dark mode styles in a separate PR, I've created #237 as a reminder for that.
This commit is contained in:
parent
70d31b2e5b
commit
c7b5e75e1a
|
@ -32,7 +32,10 @@ public protocol App: _TitledApp {
|
||||||
static func _launch(_ app: Self, _ rootEnvironment: EnvironmentValues)
|
static func _launch(_ app: Self, _ rootEnvironment: EnvironmentValues)
|
||||||
|
|
||||||
/// Implemented by the renderer to update the `App` on `ScenePhase` changes
|
/// Implemented by the renderer to update the `App` on `ScenePhase` changes
|
||||||
var _phasePublisher: CurrentValueSubject<ScenePhase, Never> { get }
|
var _phasePublisher: AnyPublisher<ScenePhase, Never> { get }
|
||||||
|
|
||||||
|
/// Implemented by the renderer to update the `App` on `ColorScheme` changes
|
||||||
|
var _colorSchemePublisher: AnyPublisher<ColorScheme, Never> { get }
|
||||||
|
|
||||||
static func main()
|
static func main()
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,11 @@ public struct _AnyApp: App {
|
||||||
fatalError("`title` cannot be set for `AnyApp`. Access underlying `app` value.")
|
fatalError("`title` cannot be set for `AnyApp`. Access underlying `app` value.")
|
||||||
}
|
}
|
||||||
|
|
||||||
public var _phasePublisher: CurrentValueSubject<ScenePhase, Never> {
|
public var _phasePublisher: AnyPublisher<ScenePhase, Never> {
|
||||||
fatalError("`_AnyApp` cannot monitor scenePhase. Access underlying `app` value.")
|
fatalError("`_AnyApp` cannot monitor scenePhase. Access underlying `app` value.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var _colorSchemePublisher: AnyPublisher<ColorScheme, Never> {
|
||||||
|
fatalError("`_AnyApp` cannot monitor colorScheme. Access underlying `app` value.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,8 @@ protocol EnvironmentReader {
|
||||||
case value(Value)
|
case value(Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
var content: Content
|
private var content: Content
|
||||||
let keyPath: KeyPath<EnvironmentValues, Value>
|
private let keyPath: KeyPath<EnvironmentValues, Value>
|
||||||
public init(_ keyPath: KeyPath<EnvironmentValues, Value>) {
|
public init(_ keyPath: KeyPath<EnvironmentValues, Value>) {
|
||||||
content = .keyPath(keyPath)
|
content = .keyPath(keyPath)
|
||||||
self.keyPath = keyPath
|
self.keyPath = keyPath
|
||||||
|
|
|
@ -94,6 +94,7 @@ public final class MountedHostView<R: Renderer>: MountedElement<R> {
|
||||||
while let child = mountedChildren.first, let firstChild = childrenViews.first {
|
while let child = mountedChildren.first, let firstChild = childrenViews.first {
|
||||||
let newChild: MountedElement<R>
|
let newChild: MountedElement<R>
|
||||||
if firstChild.typeConstructorName == mountedChildren[0].view.typeConstructorName {
|
if firstChild.typeConstructorName == mountedChildren[0].view.typeConstructorName {
|
||||||
|
child.environmentValues = environmentValues
|
||||||
child.view = firstChild
|
child.view = firstChild
|
||||||
child.updateEnvironment()
|
child.updateEnvironment()
|
||||||
child.update(with: reconciler)
|
child.update(with: reconciler)
|
||||||
|
|
|
@ -94,12 +94,8 @@ public final class StackReconciler<R: Renderer> {
|
||||||
|
|
||||||
rootElement.mount(with: self)
|
rootElement.mount(with: self)
|
||||||
if let mountedApp = rootElement as? MountedApp<R> {
|
if let mountedApp = rootElement as? MountedApp<R> {
|
||||||
app._phasePublisher.sink { [weak self] phase in
|
setupSubscription(for: app._phasePublisher, to: \.scenePhase, of: mountedApp)
|
||||||
if mountedApp.environmentValues.scenePhase != phase {
|
setupSubscription(for: app._colorSchemePublisher, to: \.colorScheme, of: mountedApp)
|
||||||
mountedApp.environmentValues.scenePhase = phase
|
|
||||||
self?.queueUpdate(for: mountedApp)
|
|
||||||
}
|
|
||||||
}.store(in: &mountedApp.subscriptions)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,6 +170,22 @@ public final class StackReconciler<R: Renderer> {
|
||||||
}.store(in: &compositeElement.subscriptions)
|
}.store(in: &compositeElement.subscriptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func setupSubscription<T: Equatable>(
|
||||||
|
for publisher: AnyPublisher<T, Never>,
|
||||||
|
to keyPath: WritableKeyPath<EnvironmentValues, T>,
|
||||||
|
of mountedApp: MountedApp<R>
|
||||||
|
) {
|
||||||
|
publisher.sink { [weak self, weak mountedApp] value in
|
||||||
|
guard
|
||||||
|
let mountedApp = mountedApp,
|
||||||
|
mountedApp.environmentValues[keyPath: keyPath] != value
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
mountedApp.environmentValues[keyPath: keyPath] = value
|
||||||
|
self?.queueUpdate(for: mountedApp)
|
||||||
|
}.store(in: &mountedApp.subscriptions)
|
||||||
|
}
|
||||||
|
|
||||||
func render<T>(compositeElement: MountedCompositeElement<R>,
|
func render<T>(compositeElement: MountedCompositeElement<R>,
|
||||||
body bodyKeypath: ReferenceWritableKeyPath<MountedCompositeElement<R>, Any>,
|
body bodyKeypath: ReferenceWritableKeyPath<MountedCompositeElement<R>, Any>,
|
||||||
result: KeyPath<MountedCompositeElement<R>, (Any) -> T>) -> T {
|
result: KeyPath<MountedCompositeElement<R>, (Any) -> T>) -> T {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
public struct Color: Hashable, Equatable {
|
public struct Color: Hashable, Equatable {
|
||||||
// FIXME: This is not injected.
|
// FIXME: This is not injected.
|
||||||
@Environment(\.accentColor) static var envAccentColor: Color?
|
@Environment(\.accentColor) static var envAccentColor
|
||||||
|
|
||||||
public enum RGBColorSpace {
|
public enum RGBColorSpace {
|
||||||
case sRGB
|
case sRGB
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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] }
|
||||||
|
set { self[_ColorSchemeKey.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension View {
|
||||||
|
func colorScheme(_ colorScheme: ColorScheme) -> some View {
|
||||||
|
environment(\.colorScheme, colorScheme)
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ public struct DisclosureGroup<Label, Content>: View
|
||||||
@State var isExpanded: Bool = false
|
@State var isExpanded: Bool = false
|
||||||
let isExpandedBinding: Binding<Bool>?
|
let isExpandedBinding: Binding<Bool>?
|
||||||
|
|
||||||
@Environment(\._outlineGroupStyle) var style: _OutlineGroupStyle
|
@Environment(\._outlineGroupStyle) var style
|
||||||
|
|
||||||
let label: Label
|
let label: Label
|
||||||
let content: () -> Content
|
let content: () -> Content
|
||||||
|
|
|
@ -25,7 +25,7 @@ public struct List<SelectionValue, Content>: View
|
||||||
let selection: _Selection
|
let selection: _Selection
|
||||||
let content: Content
|
let content: Content
|
||||||
|
|
||||||
@Environment(\.listStyle) var style: ListStyle
|
@Environment(\.listStyle) var style
|
||||||
|
|
||||||
public init(selection: Binding<Set<SelectionValue>>?, @ViewBuilder content: () -> Content) {
|
public init(selection: Binding<Set<SelectionValue>>?, @ViewBuilder content: () -> Content) {
|
||||||
self.selection = .many(selection)
|
self.selection = .many(selection)
|
||||||
|
|
|
@ -30,7 +30,7 @@ public struct NavigationView<Content>: View where Content: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is a helper class that works around absence of "package private" access control in Swift
|
/// This is a helper class that works around absence of "package private" access control in Swift
|
||||||
public struct _NavigationViewProxy<Content: View> {
|
public struct _NavigationViewProxy<Content: View>: View {
|
||||||
public let subject: NavigationView<Content>
|
public let subject: NavigationView<Content>
|
||||||
|
|
||||||
public init(_ subject: NavigationView<Content>) { self.subject = subject }
|
public init(_ subject: NavigationView<Content>) { self.subject = subject }
|
||||||
|
|
|
@ -16,7 +16,7 @@ public struct _PickerContainer<Label: View, SelectionValue: Hashable, Content: V
|
||||||
@Binding public var selection: SelectionValue
|
@Binding public var selection: SelectionValue
|
||||||
public let label: Label
|
public let label: Label
|
||||||
public let content: Content
|
public let content: Content
|
||||||
@Environment(\.pickerStyle) public var style: PickerStyle
|
@Environment(\.pickerStyle) public var style
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
selection: Binding<SelectionValue>,
|
selection: Binding<SelectionValue>,
|
||||||
|
@ -36,7 +36,7 @@ public struct _PickerContainer<Label: View, SelectionValue: Hashable, Content: V
|
||||||
public struct _PickerElement: View {
|
public struct _PickerElement: View {
|
||||||
public let valueIndex: Int?
|
public let valueIndex: Int?
|
||||||
public let content: AnyView
|
public let content: AnyView
|
||||||
@Environment(\.pickerStyle) public var style: PickerStyle
|
@Environment(\.pickerStyle) public var style
|
||||||
|
|
||||||
public var body: Never {
|
public var body: Never {
|
||||||
neverBody("_PickerElement")
|
neverBody("_PickerElement")
|
||||||
|
|
|
@ -39,7 +39,7 @@ public struct TextField<Label>: View where Label: View {
|
||||||
let textBinding: Binding<String>
|
let textBinding: Binding<String>
|
||||||
let onEditingChanged: (Bool) -> ()
|
let onEditingChanged: (Bool) -> ()
|
||||||
let onCommit: () -> ()
|
let onCommit: () -> ()
|
||||||
@Environment(\.textFieldStyle) var style: TextFieldStyle
|
@Environment(\.textFieldStyle) var style
|
||||||
|
|
||||||
public var body: Never {
|
public var body: Never {
|
||||||
neverBody("TextField")
|
neverBody("TextField")
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
public struct Toggle<Label>: View where Label: View {
|
public struct Toggle<Label>: View where Label: View {
|
||||||
@Binding var isOn: Bool
|
@Binding var isOn: Bool
|
||||||
var label: Label
|
var label: Label
|
||||||
@Environment(\.toggleStyle) var toggleStyle: _AnyToggleStyle
|
@Environment(\.toggleStyle) var toggleStyle
|
||||||
|
|
||||||
public init(isOn: Binding<Bool>, label: () -> Label) {
|
public init(isOn: Binding<Bool>, label: () -> Label) {
|
||||||
_isOn = isOn
|
_isOn = isOn
|
||||||
|
|
|
@ -20,22 +20,6 @@ import JavaScriptKit
|
||||||
import TokamakCore
|
import TokamakCore
|
||||||
import TokamakStaticHTML
|
import TokamakStaticHTML
|
||||||
|
|
||||||
private enum ScenePhaseObserver {
|
|
||||||
static var publisher = CurrentValueSubject<ScenePhase, Never>(.active)
|
|
||||||
|
|
||||||
static func observe() {
|
|
||||||
_ = document.addEventListener!("visibilitychange", JSClosure { _ in
|
|
||||||
let visibilityState = document.visibilityState.string
|
|
||||||
if visibilityState == "visible" {
|
|
||||||
publisher.send(.active)
|
|
||||||
} else if visibilityState == "hidden" {
|
|
||||||
publisher.send(.background)
|
|
||||||
}
|
|
||||||
return .undefined
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension App {
|
extension App {
|
||||||
/// The default implementation of `launch` for a `TokamakDOM` app.
|
/// The default implementation of `launch` for a `TokamakDOM` app.
|
||||||
///
|
///
|
||||||
|
@ -60,6 +44,7 @@ extension App {
|
||||||
_ = body.appendChild!(div)
|
_ = body.appendChild!(div)
|
||||||
|
|
||||||
ScenePhaseObserver.observe()
|
ScenePhaseObserver.observe()
|
||||||
|
ColorSchemeObserver.observe()
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func _setTitle(_ title: String) {
|
public static func _setTitle(_ title: String) {
|
||||||
|
@ -69,7 +54,11 @@ extension App {
|
||||||
_ = head.appendChild!(titleTag)
|
_ = head.appendChild!(titleTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var _phasePublisher: CurrentValueSubject<ScenePhase, Never> {
|
public var _phasePublisher: AnyPublisher<ScenePhase, Never> {
|
||||||
ScenePhaseObserver.publisher
|
ScenePhaseObserver.publisher.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
public var _colorSchemePublisher: AnyPublisher<ColorScheme, Never> {
|
||||||
|
ColorSchemeObserver.publisher.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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.
|
||||||
|
|
||||||
|
import CombineShim
|
||||||
|
import JavaScriptKit
|
||||||
|
|
||||||
|
enum ColorSchemeObserver {
|
||||||
|
static var publisher = CurrentValueSubject<ColorScheme, Never>(
|
||||||
|
.init(matchMediaDarkScheme: matchMediaDarkScheme)
|
||||||
|
)
|
||||||
|
|
||||||
|
private static var closure: JSClosure?
|
||||||
|
|
||||||
|
static func observe() {
|
||||||
|
let closure = JSClosure {
|
||||||
|
publisher.value = .init(matchMediaDarkScheme: $0[0].object!)
|
||||||
|
return .undefined
|
||||||
|
}
|
||||||
|
_ = matchMediaDarkScheme.addEventListener!("change", closure)
|
||||||
|
Self.closure = closure
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
// 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 CombineShim
|
||||||
|
import JavaScriptKit
|
||||||
|
|
||||||
|
enum ScenePhaseObserver {
|
||||||
|
static var publisher = CurrentValueSubject<ScenePhase, Never>(.active)
|
||||||
|
|
||||||
|
private static var closure: JSClosure?
|
||||||
|
|
||||||
|
static func observe() {
|
||||||
|
let closure = JSClosure { _ in
|
||||||
|
let visibilityState = document.visibilityState.string
|
||||||
|
if visibilityState == "visible" {
|
||||||
|
publisher.send(.active)
|
||||||
|
} else if visibilityState == "hidden" {
|
||||||
|
publisher.send(.background)
|
||||||
|
}
|
||||||
|
return .undefined
|
||||||
|
}
|
||||||
|
_ = document.addEventListener!("visibilitychange", closure)
|
||||||
|
Self.closure = closure
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,8 +51,14 @@ public typealias RadioGroupPickerStyle = TokamakCore.RadioGroupPickerStyle
|
||||||
public typealias SegmentedPickerStyle = TokamakCore.SegmentedPickerStyle
|
public typealias SegmentedPickerStyle = TokamakCore.SegmentedPickerStyle
|
||||||
public typealias WheelPickerStyle = TokamakCore.WheelPickerStyle
|
public typealias WheelPickerStyle = TokamakCore.WheelPickerStyle
|
||||||
|
|
||||||
|
public typealias ToggleStyle = TokamakCore.ToggleStyle
|
||||||
|
public typealias ToggleStyleConfiguration = TokamakCore.ToggleStyleConfiguration
|
||||||
|
|
||||||
public typealias ButtonStyle = TokamakCore.ButtonStyle
|
public typealias ButtonStyle = TokamakCore.ButtonStyle
|
||||||
public typealias ButtonStyleConfiguration = TokamakCore.ButtonStyleConfiguration
|
public typealias ButtonStyleConfiguration = TokamakCore.ButtonStyleConfiguration
|
||||||
|
public typealias DefaultButtonStyle = TokamakCore.DefaultButtonStyle
|
||||||
|
|
||||||
|
public typealias ColorScheme = TokamakCore.ColorScheme
|
||||||
|
|
||||||
// MARK: Shapes
|
// MARK: Shapes
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ extension EnvironmentValues {
|
||||||
static var defaultEnvironment: Self {
|
static var defaultEnvironment: Self {
|
||||||
var environment = EnvironmentValues()
|
var environment = EnvironmentValues()
|
||||||
environment[_ToggleStyleKey] = _AnyToggleStyle(DefaultToggleStyle())
|
environment[_ToggleStyleKey] = _AnyToggleStyle(DefaultToggleStyle())
|
||||||
|
environment[_ColorSchemeKey] = .init(matchMediaDarkScheme: matchMediaDarkScheme)
|
||||||
environment._defaultAppStorage = LocalStorage.standard
|
environment._defaultAppStorage = LocalStorage.standard
|
||||||
_DefaultSceneStorageProvider.default = SessionStorage.standard
|
_DefaultSceneStorageProvider.default = SessionStorage.standard
|
||||||
|
|
||||||
|
@ -54,8 +55,11 @@ private extension AnyView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let log = JSObjectRef.global.console.object!.log.function!
|
let global = JSObjectRef.global
|
||||||
let document = JSObjectRef.global.document.object!
|
let window = global.window.object!
|
||||||
|
let matchMediaDarkScheme = window.matchMedia!("(prefers-color-scheme: dark)").object!
|
||||||
|
let log = global.console.object!.log.function!
|
||||||
|
let document = global.document.object!
|
||||||
let body = document.body.object!
|
let body = document.body.object!
|
||||||
let head = document.head.object!
|
let head = document.head.object!
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
// 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 JavaScriptKit
|
||||||
|
|
||||||
|
extension ColorScheme {
|
||||||
|
init(matchMediaDarkScheme: JSObjectRef) {
|
||||||
|
self = matchMediaDarkScheme.matches.boolean == true ? .dark : .light
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,13 +32,28 @@ struct EnvironmentObjectDemo: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ColorScheme: CustomStringConvertible {
|
||||||
|
public var description: String {
|
||||||
|
switch self {
|
||||||
|
case .dark: return "dark"
|
||||||
|
case .light: return "light"
|
||||||
|
@unknown default: return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct EnvironmentDemo: View {
|
struct EnvironmentDemo: View {
|
||||||
@Environment(\.font) var font: Font?
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
|
@Environment(\.font) var font
|
||||||
@EnvironmentObject var testEnv: TestEnvironment
|
@EnvironmentObject var testEnv: TestEnvironment
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
Text(font == nil ? "`font` environment not set." : "\(String(describing: font!))")
|
Text("`colorScheme` is \(colorScheme.description)")
|
||||||
|
if let font = font {
|
||||||
|
Text("`font` environment is \(String(describing: font))")
|
||||||
|
}
|
||||||
Text(testEnv.envTest)
|
Text(testEnv.envTest)
|
||||||
EnvironmentObjectDemo()
|
EnvironmentObjectDemo()
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,11 @@ extension App {
|
||||||
StaticHTMLRenderer.title = title
|
StaticHTMLRenderer.title = title
|
||||||
}
|
}
|
||||||
|
|
||||||
public var _phasePublisher: CurrentValueSubject<ScenePhase, Never> {
|
public var _phasePublisher: AnyPublisher<ScenePhase, Never> {
|
||||||
CurrentValueSubject<ScenePhase, Never>(.active)
|
CurrentValueSubject<ScenePhase, Never>(.active).eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
public var _colorSchemePublisher: AnyPublisher<ColorScheme, Never> {
|
||||||
|
CurrentValueSubject<ColorScheme, Never>(.light).eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,16 @@
|
||||||
|
|
||||||
import TokamakCore
|
import TokamakCore
|
||||||
|
|
||||||
|
extension EnvironmentValues {
|
||||||
|
/// Returns default settings for the static HTML environment
|
||||||
|
static var defaultEnvironment: Self {
|
||||||
|
var environment = EnvironmentValues()
|
||||||
|
environment[_ColorSchemeKey] = .light
|
||||||
|
|
||||||
|
return environment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public final class HTMLTarget: Target {
|
public final class HTMLTarget: Target {
|
||||||
var html: AnyHTML
|
var html: AnyHTML
|
||||||
var children: [HTMLTarget] = []
|
var children: [HTMLTarget] = []
|
||||||
|
@ -80,7 +90,7 @@ public final class StaticHTMLRenderer: Renderer {
|
||||||
reconciler = StackReconciler(
|
reconciler = StackReconciler(
|
||||||
view: view,
|
view: view,
|
||||||
target: rootTarget,
|
target: rootTarget,
|
||||||
environment: EnvironmentValues(),
|
environment: .defaultEnvironment,
|
||||||
renderer: self,
|
renderer: self,
|
||||||
scheduler: { _ in
|
scheduler: { _ in
|
||||||
fatalError("Stateful apps cannot be created with TokamakStaticHTML")
|
fatalError("Stateful apps cannot be created with TokamakStaticHTML")
|
||||||
|
@ -94,7 +104,7 @@ public final class StaticHTMLRenderer: Renderer {
|
||||||
reconciler = StackReconciler(
|
reconciler = StackReconciler(
|
||||||
app: app,
|
app: app,
|
||||||
target: rootTarget,
|
target: rootTarget,
|
||||||
environment: EnvironmentValues(),
|
environment: .defaultEnvironment,
|
||||||
renderer: self,
|
renderer: self,
|
||||||
scheduler: { _ in
|
scheduler: { _ in
|
||||||
fatalError("Stateful apps cannot be created with TokamakStaticHTML")
|
fatalError("Stateful apps cannot be created with TokamakStaticHTML")
|
||||||
|
|
|
@ -22,7 +22,7 @@ extension NavigationView: ViewDeferredToRenderer {
|
||||||
width: 100%; height: 100%;
|
width: 100%; height: 100%;
|
||||||
""",
|
""",
|
||||||
]) {
|
]) {
|
||||||
_NavigationViewProxy(self).body
|
_NavigationViewProxy(self)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue