Skip to content

Commit 15ac0d4

Browse files
authored
Work around roving tabindex: make Retry focusable in loading_failure_story (#3832)
1 parent 7efbd32 commit 15ac0d4

File tree

2 files changed

+58
-1
lines changed

2 files changed

+58
-1
lines changed

.changeset/clever-cats-carry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/view-components": patch
3+
---
4+
5+
Make Retry button focusable in loading_failure_story

previews/primer/alpha/tree_view_preview/loading_failure.html.erb

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,60 @@
2929
const includeFragment = subject.querySelector('tree-view-include-fragment')
3030
if (!includeFragment) return
3131

32-
includeFragment.addEventListener('loadend', (event) => {
32+
function retryButton() {
33+
return subject.querySelector("[data-target='tree-view-sub-tree-node.retryButton']")
34+
}
35+
36+
function focusTreeItem() {
37+
const treeItem = subject.querySelector("[role='treeitem']")
38+
if (treeItem) treeItem.focus()
39+
}
40+
41+
function makeRetryTabbable() {
42+
const retry = retryButton()
43+
if (!retry) return
44+
// Remove tabindex="-1" so the button can be reached by keyboard.
45+
retry.removeAttribute('tabindex')
46+
}
47+
48+
function addRetryFocusHandler() {
49+
const retry = retryButton()
50+
if (!retry) return
51+
52+
if (retry.dataset.focusHandlerAttached === "true") return
53+
retry.dataset.focusHandlerAttached = "true"
54+
55+
retry.addEventListener('click', () => {
56+
focusTreeItem()
57+
})
58+
}
59+
60+
// When the failure UI loads, ensure Retry is tabbable
61+
includeFragment.addEventListener('loadend', (_event) => {
62+
makeRetryTabbable()
63+
addRetryFocusHandler()
3364
subject.setAttribute('data-ready', 'true')
3465
})
66+
67+
// If include-fragment swaps in new markup on re-render, re-apply tabindex removal.
68+
includeFragment.addEventListener('include-fragment-replaced', () => {
69+
makeRetryTabbable()
70+
addRetryFocusHandler()
71+
})
72+
73+
// If Retry exists and the user presses Tab while focused inside this TreeView,
74+
// redirect focus to Retry so it is reachable without requiring a mouse click.
75+
subject.addEventListener('keydown', (event) => {
76+
if (event.key !== 'Tab' || event.shiftKey) return
77+
const active = document.activeElement
78+
if (!(active instanceof HTMLElement)) return
79+
if (!subject.contains(active)) return
80+
if (active.getAttribute('role') !== 'treeitem') return
81+
const retry = retryButton()
82+
if (!retry) return
83+
makeRetryTabbable()
84+
event.preventDefault()
85+
retry.focus()
86+
})
3587
})
3688
</script>

0 commit comments

Comments
 (0)