From 178d0253191e607b5e207671ae1e328263d632a9 Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Thu, 18 May 2023 13:50:48 -0400 Subject: [PATCH 1/4] Add `prefers_default_focus` modifier --- .../NamespaceContext.swift | 8 +++ .../TransformEffectModifier.swift | 0 .../Focus Modifiers/FocusScopeModifier.swift | 60 ++++++++++++++++ .../PrefersDefaultFocusModifier.swift | 69 +++++++++++++++++++ .../modifiers/focus/focus_scope.ex | 9 +++ .../modifiers/focus/prefers_default_focus.ex | 10 +++ 6 files changed, 156 insertions(+) rename Sources/LiveViewNative/Modifiers/{ => Drawing and Graphics Modifiers}/TransformEffectModifier.swift (100%) create mode 100644 Sources/LiveViewNative/Modifiers/Focus Modifiers/FocusScopeModifier.swift create mode 100644 Sources/LiveViewNative/Modifiers/Focus Modifiers/PrefersDefaultFocusModifier.swift create mode 100644 lib/live_view_native_swift_ui/modifiers/focus/focus_scope.ex create mode 100644 lib/live_view_native_swift_ui/modifiers/focus/prefers_default_focus.ex diff --git a/Sources/LiveViewNative/Modifiers/Animations Modifiers/NamespaceContext.swift b/Sources/LiveViewNative/Modifiers/Animations Modifiers/NamespaceContext.swift index d52f85e04..ad4a29d4a 100644 --- a/Sources/LiveViewNative/Modifiers/Animations Modifiers/NamespaceContext.swift +++ b/Sources/LiveViewNative/Modifiers/Animations Modifiers/NamespaceContext.swift @@ -30,8 +30,16 @@ struct NamespaceContext: View { @Namespace private var namespace @Environment(\.namespaces) private var namespaces + @Environment(\.resetFocus) private var resetFocus + var body: some View { context.buildChildren(of: element) .environment(\.namespaces, namespaces.merging([id: namespace], uniquingKeysWith: { $1 })) + .onReceive(context.coordinator.receiveEvent("reset_focus")) { event in + guard let namespace = event["namespace"] as? String, + namespace == id + else { return } + resetFocus(in: self.namespace) + } } } diff --git a/Sources/LiveViewNative/Modifiers/TransformEffectModifier.swift b/Sources/LiveViewNative/Modifiers/Drawing and Graphics Modifiers/TransformEffectModifier.swift similarity index 100% rename from Sources/LiveViewNative/Modifiers/TransformEffectModifier.swift rename to Sources/LiveViewNative/Modifiers/Drawing and Graphics Modifiers/TransformEffectModifier.swift diff --git a/Sources/LiveViewNative/Modifiers/Focus Modifiers/FocusScopeModifier.swift b/Sources/LiveViewNative/Modifiers/Focus Modifiers/FocusScopeModifier.swift new file mode 100644 index 000000000..8168d7eb3 --- /dev/null +++ b/Sources/LiveViewNative/Modifiers/Focus Modifiers/FocusScopeModifier.swift @@ -0,0 +1,60 @@ +// +// FocusScopeModifier.swift +// LiveViewNative +// +// Created by Carson Katri on 5/18/2023. +// +import SwiftUI + +/// Associates an element hierarchy with a namespace. +/// +/// Use a ``NamespaceContext`` element to create a focus namespace. +/// Provide the `id` of the namespace to the ``namespace`` argument. +/// +/// ```html +/// +/// Username +/// Password +/// +/// ``` +/// +/// ## Arguments +/// * ``prefersDefaultFocus`` +/// * ``namespace`` +#if swift(>=5.8) +@_documentation(visibility: public) +#endif +@available(macOS 13.0, tvOS 14.0, watchOS 7.0, *) +struct FocusScopeModifier: ViewModifier, Decodable { + /// The namespace to associate this hierarchy with. + /// + /// Use a ``NamespaceContext`` element to create a namespace. + #if swift(>=5.8) + @_documentation(visibility: public) + #endif + private let namespace: String + + @Environment(\.namespaces) private var namespaces + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.namespace = try container.decode(String.self, forKey: .namespace) + } + + func body(content: Content) -> some View { + if let namespace = namespaces[namespace] { + content + #if !os(iOS) + .focusScope(namespace) + #endif + } else { + content + } + } + + enum CodingKeys: CodingKey { + case prefersDefaultFocus + case namespace + } +} diff --git a/Sources/LiveViewNative/Modifiers/Focus Modifiers/PrefersDefaultFocusModifier.swift b/Sources/LiveViewNative/Modifiers/Focus Modifiers/PrefersDefaultFocusModifier.swift new file mode 100644 index 000000000..a8b95ee60 --- /dev/null +++ b/Sources/LiveViewNative/Modifiers/Focus Modifiers/PrefersDefaultFocusModifier.swift @@ -0,0 +1,69 @@ +// +// PrefersDefaultFocusModifier.swift +// LiveViewNative +// +// Created by Carson Katri on 5/16/2023. +// +import SwiftUI + +/// Gives default focus to the element in a ``NamespaceContext``. +/// +/// Use a ``NamespaceContext`` element to create a focus namespace. +/// Provide the `id` of the namespace to the ``namespace`` argument. +/// +/// ```html +/// +/// +/// Username +/// Password +/// +/// +/// ``` +/// +/// ## Arguments +/// * ``prefersDefaultFocus`` +/// * ``namespace`` +#if swift(>=5.8) +@_documentation(visibility: public) +#endif +@available(macOS 13.0, tvOS 14.0, watchOS 7.0, *) +struct PrefersDefaultFocusModifier: ViewModifier, Decodable { + /// Enables/disables the effect of this modifier. Defaults to `true`. + #if swift(>=5.8) + @_documentation(visibility: public) + #endif + private let prefersDefaultFocus: Bool + + /// The namespace where this element prefers default focus. + /// + /// Use a ``NamespaceContext`` element to create a namespace. + #if swift(>=5.8) + @_documentation(visibility: public) + #endif + private let namespace: String + + @Environment(\.namespaces) private var namespaces + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.prefersDefaultFocus = try container.decode(Bool.self, forKey: .prefersDefaultFocus) + self.namespace = try container.decode(String.self, forKey: .namespace) + } + + func body(content: Content) -> some View { + if let namespace = namespaces[namespace] { + content + #if !os(iOS) + .prefersDefaultFocus(prefersDefaultFocus, in: namespace) + #endif + } else { + content + } + } + + enum CodingKeys: CodingKey { + case prefersDefaultFocus + case namespace + } +} diff --git a/lib/live_view_native_swift_ui/modifiers/focus/focus_scope.ex b/lib/live_view_native_swift_ui/modifiers/focus/focus_scope.ex new file mode 100644 index 000000000..9d16dbd3e --- /dev/null +++ b/lib/live_view_native_swift_ui/modifiers/focus/focus_scope.ex @@ -0,0 +1,9 @@ +defmodule LiveViewNativeSwiftUi.Modifiers.FocusScope do + use LiveViewNativePlatform.Modifier + + alias LiveViewNativeSwiftUi.Types.Namespace + + modifier_schema "focus_scope" do + field :namespace, Namespace + end +end diff --git a/lib/live_view_native_swift_ui/modifiers/focus/prefers_default_focus.ex b/lib/live_view_native_swift_ui/modifiers/focus/prefers_default_focus.ex new file mode 100644 index 000000000..be261260d --- /dev/null +++ b/lib/live_view_native_swift_ui/modifiers/focus/prefers_default_focus.ex @@ -0,0 +1,10 @@ +defmodule LiveViewNativeSwiftUi.Modifiers.PrefersDefaultFocus do + use LiveViewNativePlatform.Modifier + + alias LiveViewNativeSwiftUi.Types.Namespace + + modifier_schema "prefers_default_focus" do + field :prefers_default_focus, :boolean, default: true + field :namespace, Namespace + end +end From 5bdb3edf418d59a48218cb53bd71e05435fc7457 Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Thu, 18 May 2023 14:30:40 -0400 Subject: [PATCH 2/4] Cleanup focus_scope modifier --- .../Modifiers/Focus Modifiers/FocusScopeModifier.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/LiveViewNative/Modifiers/Focus Modifiers/FocusScopeModifier.swift b/Sources/LiveViewNative/Modifiers/Focus Modifiers/FocusScopeModifier.swift index 8168d7eb3..6ec0c61be 100644 --- a/Sources/LiveViewNative/Modifiers/Focus Modifiers/FocusScopeModifier.swift +++ b/Sources/LiveViewNative/Modifiers/Focus Modifiers/FocusScopeModifier.swift @@ -13,13 +13,14 @@ import SwiftUI /// /// ```html /// -/// Username -/// Password +/// +/// Username +/// Password +/// /// /// ``` /// /// ## Arguments -/// * ``prefersDefaultFocus`` /// * ``namespace`` #if swift(>=5.8) @_documentation(visibility: public) @@ -54,7 +55,6 @@ struct FocusScopeModifier: ViewModifier, Decodable { } enum CodingKeys: CodingKey { - case prefersDefaultFocus case namespace } } From 1d94e102448692b3e4a5c12c2f078d454dc9830f Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Thu, 18 May 2023 14:34:15 -0400 Subject: [PATCH 3/4] Document reset_focus --- .../Focus Modifiers/PrefersDefaultFocusModifier.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Sources/LiveViewNative/Modifiers/Focus Modifiers/PrefersDefaultFocusModifier.swift b/Sources/LiveViewNative/Modifiers/Focus Modifiers/PrefersDefaultFocusModifier.swift index a8b95ee60..4ecac4681 100644 --- a/Sources/LiveViewNative/Modifiers/Focus Modifiers/PrefersDefaultFocusModifier.swift +++ b/Sources/LiveViewNative/Modifiers/Focus Modifiers/PrefersDefaultFocusModifier.swift @@ -16,10 +16,19 @@ import SwiftUI /// /// Username /// Password +/// /// /// /// ``` /// +/// Push the `reset_focus` event to set focus back to the preferred defaults. +/// +/// ```elixir +/// def handle_event("reset_focus", %{ "namespace" => namespace }, socket) do +/// {:noreply, push_event(socket, :reset_focus, %{ namespace: namespace })} +/// end +/// ``` +/// /// ## Arguments /// * ``prefersDefaultFocus`` /// * ``namespace`` From 5e2ff3610fe8ddfc8b9da9d8db3b26690e6c95c1 Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Thu, 18 May 2023 14:37:00 -0400 Subject: [PATCH 4/4] Disable reset_focus on iOS --- .../Modifiers/Animations Modifiers/NamespaceContext.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/LiveViewNative/Modifiers/Animations Modifiers/NamespaceContext.swift b/Sources/LiveViewNative/Modifiers/Animations Modifiers/NamespaceContext.swift index ad4a29d4a..bf862b1f9 100644 --- a/Sources/LiveViewNative/Modifiers/Animations Modifiers/NamespaceContext.swift +++ b/Sources/LiveViewNative/Modifiers/Animations Modifiers/NamespaceContext.swift @@ -30,16 +30,20 @@ struct NamespaceContext: View { @Namespace private var namespace @Environment(\.namespaces) private var namespaces + #if !os(iOS) @Environment(\.resetFocus) private var resetFocus + #endif var body: some View { context.buildChildren(of: element) .environment(\.namespaces, namespaces.merging([id: namespace], uniquingKeysWith: { $1 })) + #if !os(iOS) .onReceive(context.coordinator.receiveEvent("reset_focus")) { event in guard let namespace = event["namespace"] as? String, namespace == id else { return } resetFocus(in: self.namespace) } + #endif } }