mirror of
https://github.com/flarum/core.git
synced 2025-08-06 16:36:47 +02:00
feat: skippable discussion list pane
This commit is contained in:
@@ -39,6 +39,7 @@ import IExtender from './extenders/IExtender';
|
||||
import AccessToken from './models/AccessToken';
|
||||
import SearchManager from './SearchManager';
|
||||
import { ColorScheme } from './components/ThemeMode';
|
||||
import { prepareSkipLinks } from './utils/a11y';
|
||||
|
||||
export type FlarumScreens = 'phone' | 'tablet' | 'desktop' | 'desktop-hd';
|
||||
|
||||
@@ -394,6 +395,8 @@ export default class Application {
|
||||
this.initColorScheme();
|
||||
|
||||
liveHumanTimes();
|
||||
|
||||
prepareSkipLinks();
|
||||
}
|
||||
|
||||
private initColorScheme(forumDefault: string | null = null): void {
|
||||
|
@@ -124,12 +124,13 @@ export default class Dropdown<CustomAttrs extends IDropdownAttrs = IDropdownAttr
|
||||
m.redraw();
|
||||
});
|
||||
|
||||
// Focusing out of the dropdown should close it.
|
||||
this.$().on('focusout', (e) => {
|
||||
if (e.relatedTarget && !this.$().has(e.relatedTarget).length) {
|
||||
this.$().on('focusout', (e: JQuery.FocusOutEvent) => {
|
||||
// Check if the new focused element is outside of this dropdown
|
||||
if (!this.$().has(e.relatedTarget as Element).length) {
|
||||
this.$().trigger('hidden.bs.dropdown');
|
||||
}
|
||||
});
|
||||
// Focusing out of the dropdown should close it.
|
||||
}
|
||||
|
||||
/**
|
||||
|
22
framework/core/js/src/common/utils/a11y.ts
Normal file
22
framework/core/js/src/common/utils/a11y.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Fix a11y skip links by manually focusing on the href target element.
|
||||
* This prevents unwanted/unexpected reloads of the page.
|
||||
*/
|
||||
export function prepareSkipLinks() {
|
||||
document.querySelectorAll('.sr-only-focusable-custom:not([data-prepared])').forEach((el) => {
|
||||
el.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
console.log('intercepted');
|
||||
const target = el.getAttribute('href')!;
|
||||
const $target = document.querySelector(target) as HTMLElement;
|
||||
|
||||
if ($target) {
|
||||
$target.setAttribute('tabindex', '-1');
|
||||
$target.focus();
|
||||
$target.removeAttribute('tabindex');
|
||||
|
||||
$target.dataset.prepared = 'true';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@@ -2,6 +2,7 @@ import app from '../../forum/app';
|
||||
import DiscussionList from './DiscussionList';
|
||||
import Component from '../../common/Component';
|
||||
import DiscussionPage from './DiscussionPage';
|
||||
import { prepareSkipLinks } from '../../common/utils/a11y';
|
||||
|
||||
const hotEdge = (e) => {
|
||||
if (e.pageX < 10) app.pane.show();
|
||||
@@ -22,7 +23,14 @@ export default class DiscussionListPane extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
return <aside className="DiscussionListPane">{this.enoughSpace() && <DiscussionList state={this.attrs.state} />}</aside>;
|
||||
return (
|
||||
<aside className="DiscussionListPane">
|
||||
<a href="#page-main" class="sr-only sr-only-focusable-custom" oncreate={() => prepareSkipLinks()}>
|
||||
{app.translator.trans('core.forum.discussion_list.skip_discussion_list_pane')}
|
||||
</a>
|
||||
{this.enoughSpace() && <DiscussionList state={this.attrs.state} />}
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
oncreate(vnode) {
|
||||
@@ -34,7 +42,12 @@ export default class DiscussionListPane extends Component {
|
||||
// and hide the pane respectively. We also create a 10px 'hot edge' on the
|
||||
// left of the screen to activate the pane.
|
||||
const pane = app.pane;
|
||||
$list.hover(pane.show.bind(pane), pane.onmouseleave.bind(pane));
|
||||
$list.on('mouseenter', pane.show.bind(pane));
|
||||
$list.on('mouseleave', pane.onmouseleave.bind(pane));
|
||||
// a11y: when tabbing into the pane (focus) we should also show the pane.
|
||||
// and when tabbing out, we should hide the pane.
|
||||
$list.on('focus', 'a, .Button', pane.show.bind(pane));
|
||||
$list.on('blur', 'a, .Button', pane.onmouseleave.bind(pane));
|
||||
|
||||
$(document).on('mousemove', hotEdge);
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import type Mithril from 'mithril';
|
||||
import classList from '../../common/utils/classList';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import LoadingIndicator from '../../common/components/LoadingIndicator';
|
||||
import { prepareSkipLinks } from '../../common/utils/a11y';
|
||||
|
||||
export interface PageStructureAttrs extends ComponentAttrs {
|
||||
hero?: () => Mithril.Children;
|
||||
@@ -52,7 +53,11 @@ export default class PageStructure<CustomAttrs extends PageStructureAttrs = Page
|
||||
}
|
||||
|
||||
main(): Mithril.Children {
|
||||
return <div className="Page-main">{this.attrs.loading ? this.loadingItems().toArray() : this.mainItems().toArray()}</div>;
|
||||
return (
|
||||
<div className="Page-main" id="page-main">
|
||||
{this.attrs.loading ? this.loadingItems().toArray() : this.mainItems().toArray()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
containerItems(): ItemList<Mithril.Children> {
|
||||
@@ -73,7 +78,7 @@ export default class PageStructure<CustomAttrs extends PageStructureAttrs = Page
|
||||
|
||||
items.add(
|
||||
'skipToMainContent',
|
||||
<a href="#main-content" className="sr-only sr-only-focusable-custom">
|
||||
<a href="#main-content" className="sr-only sr-only-focusable-custom" oncreate={() => prepareSkipLinks()}>
|
||||
{app.translator.trans('core.forum.index.skip_to_main_content')}
|
||||
</a>,
|
||||
200
|
||||
|
@@ -459,6 +459,7 @@ core:
|
||||
empty_text: It looks as though there are no discussions here.
|
||||
load_more_button: => core.ref.load_more
|
||||
replied_text: "{username} replied {ago}"
|
||||
skip_discussion_list_pane: Skip the discussion list pane
|
||||
started_text: "{username} started {ago}"
|
||||
total_replies_a11y_label: "{count, plural, one {# reply} other {# replies}}"
|
||||
unread_replies_a11y_label: "{count, plural, one {# unread reply} other {# unread replies}}. Mark unread {count, plural, one {reply} other {replies}} as read."
|
||||
|
Reference in New Issue
Block a user