feat(accessibility): enhance accessibility features across components and improve focus management (#725)

This commit is contained in:
Cell
2026-04-03 18:00:17 +08:00
committed by GitHub
parent b172fd416d
commit 29ece68332
12 changed files with 89 additions and 39 deletions
+16 -10
View File
@@ -70,7 +70,8 @@ header {
font-size: fixit-var(header-title-font-size);
font-family: fixit-var(header-title-font-family);
font-weight: bold;
margin-right: 0.5rem;
margin-inline-end: 0.25rem;
padding-inline: 0.25rem;
min-width: 10%;
overflow: hidden;
white-space: nowrap;
@@ -175,6 +176,7 @@ header {
#header-desktop.open & {
left: 1rem;
}
@include focus-visible-ring;
}
.search-loading {
@@ -210,6 +212,9 @@ header {
}
.theme-switch {
position: relative;
@include focus-visible-ring;
i {
display: inline-block;
transform: rotate(225deg) scale(1);
@@ -246,12 +251,14 @@ header {
}
.header-wrapper {
// use css native function to calculate max value
padding: 0 2rem 0 MAX(10vh, fixit-var(header-height));
padding: 0 2rem;
height: 100%;
&[data-github-corner='left'] {
padding-inline-start: fixit-var(header-height);
}
&[data-github-corner='right'] {
padding-right: fixit-var(header-height);
padding-inline-end: fixit-var(header-height);
}
.header-title {
@@ -311,11 +318,7 @@ header {
}
@include media('md', 'down') {
padding-inline-end: 1rem;
}
@include media('sm', 'down') {
padding-inline-start: 1rem;
padding-inline: 1rem;
}
}
@@ -358,13 +361,16 @@ header {
}
.menu-toggle {
position: relative;
@include focus-visible-ring;
span {
display: block;
background-color: fixit-var(global-font-color);
width: 20px;
height: 2px;
@include border-radius(full);
transition: all 0.3s ease-in-out;
@include border-radius(full);
}
span:not(:last-child) {
@@ -61,3 +61,22 @@
}
}
}
@mixin focus-visible-ring($size: 1.5em, $offset: 0.2rem) {
&:focus-visible {
outline: none;
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: $size;
height: $size;
transform: translate(-50%, -50%);
outline: -webkit-focus-ring-color auto 1px;
outline-offset: $offset;
pointer-events: none;
}
}
}
+2 -4
View File
@@ -23,10 +23,8 @@
cursor: pointer;
}
&:active,
&:focus,
&:hover {
outline: none;
&:focus-visible {
outline: -webkit-focus-ring-color auto 1px;
}
@include media('print') {
+28 -1
View File
@@ -68,6 +68,19 @@ class FixIt {
this.config.twemoji && twemoji.parse(target);
}
initRoleButtons(target = document) {
forEach(target.querySelectorAll('[role="button"]'), ($el) => {
if ($el.dataset.init) return;
$el.dataset.init = 'true';
$el.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
$el.click();
}
}, false);
});
}
initMenu() {
this.initMenuDesktop();
this.initMenuMobile();
@@ -87,10 +100,12 @@ class FixIt {
$mask.classList.toggle('blur');
$menuToggleMobile.classList.toggle('active');
$menuMobile.classList.toggle('active');
$menuToggleMobile.setAttribute('aria-expanded', $menuToggleMobile.classList.contains('active') ? 'true' : 'false');
}, false);
this._menuMobileOnClickMask = this._menuMobileOnClickMask || (() => {
$menuToggleMobile.classList.remove('active');
$menuMobile.classList.remove('active');
$menuToggleMobile.setAttribute('aria-expanded', 'false');
});
this.clickMaskEventSet.add(this._menuMobileOnClickMask);
// add nested menu toggler
@@ -166,6 +181,7 @@ class FixIt {
$searchLoading.style.display = 'none';
$searchClear.style.display = 'none';
searchInstance && searchInstance.autocomplete.setVal('');
document.getElementById(`search-toggle-${$header.id.replace('header-', '')}`)?.setAttribute('aria-expanded', 'false');
}
initSearch() {
@@ -198,6 +214,9 @@ class FixIt {
const $searchClear = document.getElementById(`search-clear-${suffix}`);
const $searchCancel = document.getElementById('search-cancel-mobile');
const $mask = document.getElementById('mask');
const setSearchExpanded = (expanded) => {
$searchToggle?.setAttribute('aria-expanded', expanded ? 'true' : 'false');
};
// goto the PostChat panel rather than search results
if (searchConfig.type === 'post-chat' && window.postChatUser) {
@@ -218,11 +237,13 @@ class FixIt {
$searchInput.addEventListener('focus', () => {
$mask.classList.add('blur');
$header.classList.add('open');
setSearchExpanded(true);
}, false);
$searchCancel.addEventListener('click', () => {
$mask.classList.remove('blur');
document.getElementById('menu-toggle-mobile').classList.remove('active');
document.getElementById('menu-mobile').classList.remove('active');
document.getElementById('menu-toggle-mobile').setAttribute('aria-expanded', 'false');
this._resetSearchUI($header, $searchLoading, $searchClear, this._searchMobile);
}, false);
$searchClear.addEventListener('click', () => {
@@ -239,6 +260,7 @@ class FixIt {
$mask.classList.add('blur');
$header.classList.add('open');
$searchInput.focus();
setSearchExpanded(true);
}, false);
$searchClear.addEventListener('click', () => {
$searchClear.style.display = 'none';
@@ -262,7 +284,7 @@ class FixIt {
dropdownMenuContainer: `#search-dropdown-${suffix}`,
clearOnSelected: true,
cssClasses: { noPrefix: true },
debug: true
debug: false
},
{
name: 'search',
@@ -908,11 +930,15 @@ class FixIt {
if (!dialog || !openButton) return;
openButton.addEventListener("click", () => {
dialog.showModal();
openButton.setAttribute('aria-expanded', 'true');
document.activeElement?.blur();
});
dialog.addEventListener("click", (e) => {
dialog.close();
});
dialog.addEventListener("close", () => {
openButton.setAttribute('aria-expanded', 'false');
});
}
/**
@@ -1447,6 +1473,7 @@ class FixIt {
this.initMathJax();
this.initJsonViewer();
this.initTabEvents(target);
this.initRoleButtons(target);
FileTree.init(target);
window.FixItMermaid?.init?.();
window.FixItAPlayer?.init?.();
+2 -2
View File
@@ -492,8 +492,8 @@ tileColor = "#da532c"
# FixIt 0.2.12 | CHANGED Android browser theme color
[params.app.themeColor]
light = "#f8f8f8"
dark = "#252627"
light = "#f6f8fa"
dark = "#151b23"
# Search config
[params.search]
@@ -4,7 +4,7 @@
{{- if $config.copyable -}}
{{- $copyIcon := dict "Class" "fa-regular fa-clone fa-width-auto" | partial "plugin/icon.html" -}}
{{- $copyBtn := printf `<div class="code-copy-btn" aria-label="%s" role="button" title="%s">%s</div>` (T "assets.copyToClipboard") (T "assets.copyToClipboard") $copyIcon -}}
{{- $copyBtn := printf `<div class="code-copy-btn" aria-label="%s" role="button" tabindex="0" title="%s">%s</div>` (T "assets.copyToClipboard") (T "assets.copyToClipboard") $copyIcon -}}
{{- $wrapper = replaceRE `(<div class="code-wrapper">[\s\S]*?<\/div>)` (printf `$1%s` $copyBtn) $wrapper -}}
{{- end -}}
@@ -1,7 +1,7 @@
{{- /* Code expand button for code blocks */ -}}
{{- $wrapper := .Wrapper -}}
{{- $expandIcon := dict "Class" "fa-solid fa-angles-down" | partial "plugin/icon.html" -}}
{{- $expandBtn := printf `<div class="code-expand-btn" aria-label="%s" role="button">%s</div>` (T "assets.expandCode") $expandIcon -}}
{{- $expandBtn := printf `<div class="code-expand-btn" aria-label="%s" role="button" tabindex="0">%s</div>` (T "assets.expandCode") $expandIcon -}}
{{- $wrapper = replaceRE `(<div class="code-wrapper">[\s\S]*?<\/div>)` (printf `$1%s` $expandBtn) $wrapper -}}
{{- return $wrapper -}}
+7 -7
View File
@@ -11,34 +11,34 @@
{{- $fullscreenBtn := "" -}}
{{- $foldIcon := dict "Class" "arrow fa-solid fa-chevron-down" | partial "plugin/icon.html" -}}
{{- $titleEl := printf `<span class="code-title">%s</span>` $foldIcon -}}
{{- $ellipsisBtn := printf `<span class="ellipses-btn" aria-label="Show more options" role="button">%s</span>` (dict "Class" "fa-solid fa-ellipsis-h" | partial "plugin/icon.html") -}}
{{- $ellipsisBtn := printf `<span class="ellipses-btn" aria-label="Show more options" role="button" tabindex="0">%s</span>` (dict "Class" "fa-solid fa-ellipsis-h" | partial "plugin/icon.html") -}}
{{- if $config.title -}}
{{- $titleEl = printf `<span class="code-title">%s<span class="title-inner">%s</span></span>` $foldIcon $config.title -}}
{{- end -}}
{{- if $config.linenostoggler -}}
{{- $lineNosIcon := dict "Class" "fa-solid fa-list-ol" | partial "plugin/icon.html" -}}
{{- $lineNosBtn = printf `<span class="action-btn line-nos-btn" aria-label="%s" role="button" title="%s">%s</span>` (T "assets.toggleLineNumbers") (T "assets.toggleLineNumbers") $lineNosIcon -}}
{{- $lineNosBtn = printf `<span class="action-btn line-nos-btn" aria-label="%s" role="button" tabindex="0" title="%s">%s</span>` (T "assets.toggleLineNumbers") (T "assets.toggleLineNumbers") $lineNosIcon -}}
{{- end -}}
{{- if $config.linewraptoggler -}}
{{- $lineWrapIcon := dict "Class" "fa-solid fa-right-left" | partial "plugin/icon.html" -}}
{{- $lineWrapBtn = printf `<span class="action-btn line-wrap-btn" aria-label="%s" role="button" title="%s">%s</span>` (T "assets.toggleLineWrap") (T "assets.toggleLineWrap") $lineWrapIcon -}}
{{- $lineWrapBtn = printf `<span class="action-btn line-wrap-btn" aria-label="%s" role="button" tabindex="0" title="%s">%s</span>` (T "assets.toggleLineWrap") (T "assets.toggleLineWrap") $lineWrapIcon -}}
{{- end -}}
{{- if $config.editable -}}
{{- $editIcon := dict "Class" "fa-solid fa-pen-to-square" | partial "plugin/icon.html" -}}
{{- $editBtn = printf `<span class="action-btn edit-btn" aria-label="%s" role="button" title="%s">%s</span>` (T "assets.toggleCodeEditable") (T "assets.toggleCodeEditable") $editIcon -}}
{{- $editBtn = printf `<span class="action-btn edit-btn" aria-label="%s" role="button" tabindex="0" title="%s">%s</span>` (T "assets.toggleCodeEditable") (T "assets.toggleCodeEditable") $editIcon -}}
{{- end -}}
{{- if $config.copyable -}}
{{- $copyIcon := dict "Class" "fa-regular fa-clone" | partial "plugin/icon.html" -}}
{{- $copyBtn = printf `<span class="action-btn copy-btn" aria-label="%s" role="button" title="%s" data-copied-text="%s">%s</span>` (T "assets.copyToClipboard") (T "assets.copyToClipboard") (T "assets.copiedText") $copyIcon -}}
{{- $copyBtn = printf `<span class="action-btn copy-btn" aria-label="%s" role="button" tabindex="0" title="%s" data-copied-text="%s">%s</span>` (T "assets.copyToClipboard") (T "assets.copyToClipboard") (T "assets.copiedText") $copyIcon -}}
{{- end -}}
{{- if $config.downloadable -}}
{{- $downloadIcon := dict "Class" "fa-solid fa-download" | partial "plugin/icon.html" -}}
{{- $downloadBtn = printf `<span class="action-btn download-btn" aria-label="%s" role="button" title="%s">%s</span>` (T "assets.downloadCode") (T "assets.downloadCode") $downloadIcon -}}
{{- $downloadBtn = printf `<span class="action-btn download-btn" aria-label="%s" role="button" tabindex="0" title="%s">%s</span>` (T "assets.downloadCode") (T "assets.downloadCode") $downloadIcon -}}
{{- end -}}
{{- if $config.fullscreen -}}
{{- $fullscreenIcon := dict "Class" "fa-solid fa-expand" | partial "plugin/icon.html" -}}
{{- $fullscreenBtn = printf `<span class="action-btn fullscreen-btn" aria-label="%s" role="button" title="%s">%s</span>` (T "assets.toggleCodeFullscreen") (T "assets.toggleCodeFullscreen") $fullscreenIcon -}}
{{- $fullscreenBtn = printf `<span class="action-btn fullscreen-btn" aria-label="%s" role="button" tabindex="0" title="%s">%s</span>` (T "assets.toggleCodeFullscreen") (T "assets.toggleCodeFullscreen") $fullscreenIcon -}}
{{- end -}}
{{- with $config.name -}}
{{- $titleEl = replace $titleEl `<span class="code-title">` (printf `<span class="code-title" data-name="%s">` .) -}}
+8 -8
View File
@@ -96,8 +96,8 @@
{{- end -}}
{{- if .Site.Params.search.enable -}}
<li class="menu-item search" id="search-desktop">
<input type="text" placeholder="{{ .Site.Params.search.placeholder | default (T `assets.searchPlaceholder`) }}" id="search-input-desktop">
<a href="javascript:void(0);" class="search-button search-toggle" id="search-toggle-desktop" title="{{ T `assets.search` }}">
<input type="text" placeholder="{{ .Site.Params.search.placeholder | default (T `assets.searchPlaceholder`) }}" id="search-input-desktop" tabindex="-1">
<a href="javascript:void(0);" class="search-button search-toggle" id="search-toggle-desktop" title="{{ T `assets.search` }}" aria-controls="search-dropdown-desktop" aria-expanded="false">
{{ dict "Class" "fa-solid fa-search" | partial "plugin/icon.html" }}
</a>
<a href="javascript:void(0);" class="search-button search-clear" id="search-clear-desktop" title="{{ T `assets.clear` }}">
@@ -108,12 +108,12 @@
</span>
</li>
{{- end -}}
<li class="menu-item theme-switch" role="button" aria-label="{{ T "header.switchTheme" }}" title="{{ T "header.switchTheme" }}">
<li class="menu-item theme-switch" role="button" tabindex="0" aria-label="{{ T "header.switchTheme" }}" title="{{ T "header.switchTheme" }}">
{{- dict "Class" "fa-solid fa-adjust" | partial "plugin/icon.html" -}}
</li>
{{- if hugo.IsMultilingual -}}
<li class="menu-item language-switch">
<span role="button" aria-label="{{ T "header.selectLanguage" }}" title="{{ T "header.selectLanguage" }}">
<span role="button" tabindex="0" aria-label="{{ T "header.selectLanguage" }}" title="{{ T "header.selectLanguage" }}">
{{- dict "Class" "fa-solid fa-language" | partial "plugin/icon.html" -}}
</span>
<ul class="sub-menu">
@@ -187,10 +187,10 @@
{{- end -}}
{{- end -}}
</div>
<div class="theme-switch" role="button" aria-label="{{ T "header.switchTheme" }}">
<div class="theme-switch" role="button" tabindex="0" aria-label="{{ T "header.switchTheme" }}">
{{- dict "Class" "fa-solid fa-adjust" | partial "plugin/icon.html" -}}
</div>
<div class="menu-toggle" id="menu-toggle-mobile" role="button" aria-labelledby="menu-mobile">
<div class="menu-toggle" id="menu-toggle-mobile" role="button" tabindex="0" aria-controls="menu-mobile" aria-expanded="false" aria-labelledby="menu-mobile">
<span></span><span></span><span></span>
</div>
</div>
@@ -200,7 +200,7 @@
<li class="search-wrapper">
<div class="search mobile" id="search-mobile">
<input type="text" placeholder="{{ .Site.Params.search.placeholder | default (T `assets.searchPlaceholder`) }}" id="search-input-mobile">
<a href="javascript:void(0);" class="search-button search-toggle" id="search-toggle-mobile" title="{{ T `assets.search` }}">
<a href="javascript:void(0);" class="search-button search-toggle" id="search-toggle-mobile" title="{{ T `assets.search` }}" aria-controls="search-dropdown-mobile" aria-expanded="false">
{{ dict "Class" "fa-solid fa-search" | partial "plugin/icon.html" }}
</a>
<a href="javascript:void(0);" class="search-button search-clear" id="search-clear-mobile" title="{{ T `assets.clear` }}">
@@ -286,7 +286,7 @@
<li class="menu-item menu-system">
{{- if hugo.IsMultilingual -}}
<span class="menu-system-item language-switch">
<span role="button" aria-label="{{ T "header.selectLanguage" }}" title="{{ T "header.selectLanguage" }}">
<span role="button" tabindex="0" aria-label="{{ T "header.selectLanguage" }}" title="{{ T "header.selectLanguage" }}">
{{- .Language.LanguageName -}}
{{- dict "Class" "dropdown-icon fa-solid fa-chevron-down" | partial "plugin/icon.html" -}}
</span>
+3 -3
View File
@@ -6,7 +6,7 @@
<div class="fixed-buttons">
{{- /* back to top button */ -}}
{{- if $backToTop.enable -}}
<div class="fixed-button back-to-top animate__faster d-none" role="button" aria-label="{{ T `baseof.backToTop` }}">
<div class="fixed-button back-to-top animate__faster d-none" role="button" tabindex="0" aria-label="{{ T `baseof.backToTop` }}">
{{- dict "Class" "fa-solid fa-arrow-up" | partial "plugin/icon.html" -}}
{{- if $backToTop.scrollpercent -}}
<svg viewBox="0 0 100 100">
@@ -18,13 +18,13 @@
{{- end -}}
{{- /* toc dialog button */ -}}
{{- if .Store.Get "showToc" -}}
<div id="toc-drawer-button" class="fixed-button toc-drawer-button d-none{{ with $params.password }} encrypted-hidden{{ end }}" role="button" aria-label="{{ T `single.contents` }}">
<div id="toc-drawer-button" class="fixed-button toc-drawer-button d-none{{ with $params.password }} encrypted-hidden{{ end }}" role="button" tabindex="0" aria-label="{{ T `single.contents` }}" aria-controls="toc-dialog" aria-expanded="false">
{{- dict "Class" "fa-solid fa-bars" | partial "plugin/icon.html" -}}
</div>
{{- end -}}
{{- /* comment button */ -}}
{{- if $comment.enable -}}
<div class="fixed-button view-comments d-none" role="button" aria-label="{{ T `baseof.viewComments` }}">
<div class="fixed-button view-comments d-none" role="button" tabindex="0" aria-label="{{ T `baseof.viewComments` }}">
{{- dict "Class" "fa-solid fa-comment" | partial "plugin/icon.html" -}}
</div>
{{- end -}}
@@ -1,6 +1,6 @@
{{- /* Diagram copy button */ -}}
{{- $label := .Label | default "Copy Diagram code" -}}
<div class="diagram-copy-btn" aria-label="{{ $label }}" role="button">
<div class="diagram-copy-btn" aria-label="{{ $label }}" role="button" tabindex="0">
{{- dict "Class" "fa-regular fa-clone fa-width-auto" | partial "plugin/icon.html" -}}
</div>
+1 -1
View File
@@ -241,7 +241,7 @@
{{- if $showToc -}}
<dialog id="toc-dialog" aria-labelledby="toc-dialog-title" role="dialog">
<div class="toc">
<h2 class="toc-title">{{ T "single.contents" }}</h2>
<h2 class="toc-title" id="toc-dialog-title">{{ T "single.contents" }}</h2>
<div class="toc-content" id="toc-content-drawer">
{{- replace $tableOfContents `id="TableOfContents"` "" | safeHTML -}}
</div>