diff --git a/.changeset/clever-cats-carry.md b/.changeset/clever-cats-carry.md new file mode 100644 index 0000000000..a9cb6bdeed --- /dev/null +++ b/.changeset/clever-cats-carry.md @@ -0,0 +1,5 @@ +--- +"@primer/view-components": patch +--- + +Make Retry button focusable in loading_failure_story diff --git a/previews/primer/alpha/tree_view_preview/loading_failure.html.erb b/previews/primer/alpha/tree_view_preview/loading_failure.html.erb index d324e8e703..7810d52b3d 100644 --- a/previews/primer/alpha/tree_view_preview/loading_failure.html.erb +++ b/previews/primer/alpha/tree_view_preview/loading_failure.html.erb @@ -29,8 +29,60 @@ const includeFragment = subject.querySelector('tree-view-include-fragment') if (!includeFragment) return - includeFragment.addEventListener('loadend', (event) => { + function retryButton() { + return subject.querySelector("[data-target='tree-view-sub-tree-node.retryButton']") + } + + function focusTreeItem() { + const treeItem = subject.querySelector("[role='treeitem']") + if (treeItem) treeItem.focus() + } + + function makeRetryTabbable() { + const retry = retryButton() + if (!retry) return + // Remove tabindex="-1" so the button can be reached by keyboard. + retry.removeAttribute('tabindex') + } + + function addRetryFocusHandler() { + const retry = retryButton() + if (!retry) return + + if (retry.dataset.focusHandlerAttached === "true") return + retry.dataset.focusHandlerAttached = "true" + + retry.addEventListener('click', () => { + focusTreeItem() + }) + } + + // When the failure UI loads, ensure Retry is tabbable + includeFragment.addEventListener('loadend', (_event) => { + makeRetryTabbable() + addRetryFocusHandler() subject.setAttribute('data-ready', 'true') }) + + // If include-fragment swaps in new markup on re-render, re-apply tabindex removal. + includeFragment.addEventListener('include-fragment-replaced', () => { + makeRetryTabbable() + addRetryFocusHandler() + }) + + // If Retry exists and the user presses Tab while focused inside this TreeView, + // redirect focus to Retry so it is reachable without requiring a mouse click. + subject.addEventListener('keydown', (event) => { + if (event.key !== 'Tab' || event.shiftKey) return + const active = document.activeElement + if (!(active instanceof HTMLElement)) return + if (!subject.contains(active)) return + if (active.getAttribute('role') !== 'treeitem') return + const retry = retryButton() + if (!retry) return + makeRetryTabbable() + event.preventDefault() + retry.focus() + }) })