fix(overview): make filter-builder toggles keyboard-operable
This commit is contained in:
parent
8303a39041
commit
ce877463d6
2 changed files with 37 additions and 3 deletions
|
|
@ -1190,13 +1190,20 @@ defmodule MvWeb.Components.AddFilterBuilderComponent do
|
||||||
slot :inner_block, required: true
|
slot :inner_block, required: true
|
||||||
|
|
||||||
# Shared two-state join toggle used by the boolean and is/is-not controls
|
# Shared two-state join toggle used by the boolean and is/is-not controls
|
||||||
# (Task 5): a `join-item` button label wrapping a hidden radio, with a visible
|
# (Task 5): a `join-item` button label wrapping a native radio, with a visible
|
||||||
# selected state (`btn-primary` when the radio is checked) and an optional
|
# selected state (`btn-primary` when the radio is checked) and an optional
|
||||||
# leading icon. Consistent look across every two-state selector.
|
# leading icon. Consistent look across every two-state selector.
|
||||||
|
#
|
||||||
|
# The radio is `sr-only` (visually hidden) rather than `hidden`/`display:none`
|
||||||
|
# so it stays keyboard-focusable and in the tab order: Tab reaches the group,
|
||||||
|
# native same-`name` radio semantics move the selection with the arrow keys
|
||||||
|
# (WCAG 2.1.1). `has-[:focus-visible]:*` surfaces a visible ring on the styled
|
||||||
|
# label while its input has keyboard focus (WCAG 2.4.7); `z-10` lifts the
|
||||||
|
# focused item so the ring is not clipped by adjacent join-items.
|
||||||
defp toggle_option(assigns) do
|
defp toggle_option(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<label class="join-item btn btn-sm gap-1 has-[:checked]:btn-primary">
|
<label class="join-item btn btn-sm gap-1 has-[:checked]:btn-primary has-[:focus-visible]:z-10 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-offset-2 has-[:focus-visible]:ring-offset-base-100 has-[:focus-visible]:ring-base-content/60">
|
||||||
<input type="radio" name={@name} value={@value} class="hidden" checked={@checked} />
|
<input type="radio" name={@name} value={@value} class="sr-only" checked={@checked} />
|
||||||
<.icon :if={@icon} name={@icon} class="size-3.5" />
|
<.icon :if={@icon} name={@icon} class="size-3.5" />
|
||||||
{render_slot(@inner_block)}
|
{render_slot(@inner_block)}
|
||||||
</label>
|
</label>
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,33 @@ defmodule MvWeb.MemberLive.IndexAddFilterFlowTest do
|
||||||
refute has_element?(view, "[data-testid='filter-chip']")
|
refute has_element?(view, "[data-testid='filter-chip']")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "§3.7 join-toggles are keyboard-operable (WCAG 2.1.1 / 2.4.7)" do
|
||||||
|
test "the shared toggle radios are focusable (not display:none) with a visible focus ring",
|
||||||
|
%{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, ~p"/members")
|
||||||
|
|
||||||
|
pick_field(view, "group")
|
||||||
|
|
||||||
|
scope = "[data-testid='value-control-group']"
|
||||||
|
|
||||||
|
assert has_element?(view, "#{scope} input[type='radio']"),
|
||||||
|
"expected the join-toggle to render native radio inputs"
|
||||||
|
|
||||||
|
# A `hidden`/`display:none` (or negative-tabindex) input is skipped by Tab
|
||||||
|
# and cannot be reached with the keyboard — the WCAG 2.1.1 failure.
|
||||||
|
refute has_element?(view, "#{scope} input[type='radio'].hidden"),
|
||||||
|
"toggle radio must not be display:none-hidden (keyboard-unreachable)"
|
||||||
|
|
||||||
|
refute has_element?(view, "#{scope} input[type='radio'][tabindex='-1']"),
|
||||||
|
"toggle radio must stay in the natural tab order (no tabindex=-1)"
|
||||||
|
|
||||||
|
# WCAG 2.4.7: the styled label must surface a visible focus indicator when
|
||||||
|
# its (visually-hidden) input is keyboard-focused.
|
||||||
|
assert render(element(view, scope)) =~ "focus-visible",
|
||||||
|
"toggle label must carry a :focus-visible ring indicator"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp refute_patched(view) do
|
defp refute_patched(view) do
|
||||||
assert_raise ArgumentError, fn -> assert_patch(view, 50) end
|
assert_raise ArgumentError, fn -> assert_patch(view, 50) end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue