Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add prefers_default_focus and focus_scope modifiers #940

Merged
merged 4 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,20 @@ struct NamespaceContext<R: RootRegistry>: 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
}
}
Original file line number Diff line number Diff line change
@@ -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
/// <NamespaceContext id={:my_namespace}>
/// <VStack modifiers={focus_scope(@native, namespace: :my_namespace)}>
/// <TextField>Username</TextField>
/// <TextField modifiers={prefers_default_focus(@native, namespace: :my_namespace)}>Password</TextField>
/// </VStack>
/// </NamespaceContext>
/// ```
///
/// ## Arguments
/// * ``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 namespace
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// 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
/// <NamespaceContext id={:my_namespace}>
/// <VStack modifiers={focus_scope(@native, :my_namespace)}>
/// <TextField>Username</TextField>
/// <TextField modifiers={prefers_default_focus(@native, namespace: :my_namespace)}>Password</TextField>
/// <Button phx-click="reset_focus" phx-value-namespace={:my_namespace}>Reset Focus</Button>
/// </VStack>
/// </NamespaceContext>
/// ```
///
/// 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``
#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
}
}
9 changes: 9 additions & 0 deletions lib/live_view_native_swift_ui/modifiers/focus/focus_scope.ex
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Loading