mirror of
https://github.com/hugo-fixit/FixIt.git
synced 2026-08-24 15:28:57 +00:00
feat(accessibility): enhance accessibility features across components and improve focus management (#725)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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?.();
|
||||
|
||||
@@ -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 -}}
|
||||
|
||||
@@ -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">` .) -}}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user