mirror of
https://github.com/RetroShare/RSNewWebUI.git
synced 2026-09-12 19:50:04 +05:00
Fixed both mobile sheets using a shared dialog component with accessible names, Escape dismissal, focus trapping, and focus return.
This commit is contained in:
parent
4f7e0053f4
commit
ed1b7f1e3d
51
webui-src/app/dialog.js
Normal file
51
webui-src/app/dialog.js
Normal file
@ -0,0 +1,51 @@
|
||||
const m = require('mithril');
|
||||
|
||||
// Mount only while open. Native modal dialogs make the rest of the page inert
|
||||
// and provide dialog semantics; callers own the open state and sheet content.
|
||||
const Dialog = () => {
|
||||
let opener;
|
||||
return {
|
||||
oncreate: ({ dom }) => {
|
||||
opener = document.activeElement;
|
||||
dom.showModal();
|
||||
},
|
||||
onremove: ({ dom }) => {
|
||||
dom.close();
|
||||
if (opener && opener.isConnected) opener.focus();
|
||||
},
|
||||
view: ({ attrs, children }) => m('dialog.accessible-dialog', {
|
||||
class: attrs.overlayClass,
|
||||
'aria-label': attrs.label,
|
||||
'aria-modal': 'true',
|
||||
oncancel: (event) => {
|
||||
event.preventDefault();
|
||||
attrs.onclose();
|
||||
},
|
||||
onclick: (event) => {
|
||||
if (event.target === event.currentTarget) attrs.onclose();
|
||||
},
|
||||
onkeydown: (event) => {
|
||||
if (event.key !== 'Tab') return;
|
||||
const dialog = event.currentTarget;
|
||||
const controls = Array.from(dialog.querySelectorAll(
|
||||
'a[href], button, input, select, textarea, [tabindex], [contenteditable="true"]'
|
||||
)).filter((element) => element.tabIndex >= 0 &&
|
||||
!element.matches(':disabled') && !element.closest('[inert]') &&
|
||||
element.getClientRects().length > 0 && getComputedStyle(element).visibility !== 'hidden');
|
||||
const first = controls[0];
|
||||
const last = controls[controls.length - 1];
|
||||
if (!first) {
|
||||
event.preventDefault();
|
||||
} else if (event.shiftKey && (document.activeElement === first || !controls.includes(document.activeElement))) {
|
||||
event.preventDefault();
|
||||
last.focus();
|
||||
} else if (!event.shiftKey && (document.activeElement === last || !controls.includes(document.activeElement))) {
|
||||
event.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
},
|
||||
}, m('div', { class: attrs.sheetClass }, children)),
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = Dialog;
|
||||
@ -19,6 +19,7 @@ const config = require('config/config_resolver');
|
||||
const statistics = require('statistics/statistics');
|
||||
const debug = require('debug/debug');
|
||||
const statusbar = require('statusbar');
|
||||
const Dialog = require('dialog');
|
||||
const networkState = require('network/network_state');
|
||||
const peopleState = require('people/people_state');
|
||||
const { ChatRoomsModel, receiveLobbyChatMessage } = require('chat/chat_state');
|
||||
@ -245,6 +246,7 @@ const MobileStatus = () => {
|
||||
m('button.mobile-status-trigger[type=button]', {
|
||||
'aria-label': `Open connection status. ${summary.label}`,
|
||||
'aria-expanded': String(isOpen),
|
||||
'aria-haspopup': 'dialog',
|
||||
onclick: () => (isOpen = true),
|
||||
}, [
|
||||
m('span.mobile-status-trigger__dot', { style: { backgroundColor: summary.color } }),
|
||||
@ -252,11 +254,12 @@ const MobileStatus = () => {
|
||||
m('i.fas.fa-chevron-up'),
|
||||
]),
|
||||
]),
|
||||
isOpen && m('.mobile-status-overlay', {
|
||||
onclick: (event) => {
|
||||
if (event.target === event.currentTarget) isOpen = false;
|
||||
},
|
||||
}, m('.mobile-status-sheet', [
|
||||
isOpen && m(Dialog, {
|
||||
label: 'Connection status',
|
||||
overlayClass: 'mobile-status-overlay',
|
||||
sheetClass: 'mobile-status-sheet',
|
||||
onclose: () => (isOpen = false),
|
||||
}, [
|
||||
m('.mobile-status-sheet__handle'),
|
||||
m('.mobile-status-sheet__heading', [
|
||||
m('div', [
|
||||
@ -298,7 +301,7 @@ const MobileStatus = () => {
|
||||
onclick: () => window.location.reload(true),
|
||||
}, [m('i.fas.fa-sync-alt'), ' Reload']),
|
||||
]),
|
||||
])),
|
||||
]),
|
||||
];
|
||||
},
|
||||
};
|
||||
@ -315,24 +318,27 @@ const MobileNavigation = () => {
|
||||
|
||||
return {
|
||||
view: () => [
|
||||
isMoreOpen && m('.mobile-more-overlay', {
|
||||
onclick: (event) => {
|
||||
if (event.target === event.currentTarget) isMoreOpen = false;
|
||||
},
|
||||
}, m('.mobile-more-sheet', [
|
||||
isMoreOpen && m(Dialog, {
|
||||
label: 'More navigation',
|
||||
overlayClass: 'mobile-more-overlay',
|
||||
sheetClass: 'mobile-more-sheet',
|
||||
onclose: () => (isMoreOpen = false),
|
||||
}, [
|
||||
m('.mobile-more-sheet__handle'),
|
||||
m('h3', 'More'),
|
||||
m('.mobile-more-sheet__links', mobileMoreItems.map((item) => link(item))),
|
||||
m('.mobile-more-sheet__actions', [
|
||||
m('button[type=button]', { onclick: () => (isMoreOpen = false) }, 'Close'),
|
||||
m('button[type=button]', { onclick: () => window.location.reload(true) }, [m('i.fas.fa-sync-alt'), ' Reload']),
|
||||
m('button[type=button]', { onclick: () => rs.logout() }, [m('i.fas.fa-sign-out-alt'), ' Logout']),
|
||||
]),
|
||||
])),
|
||||
]),
|
||||
m('nav.mobile-bottom-nav[aria-label=Main navigation]', [
|
||||
mobileItems.map((item) => link(item, 'mobile-bottom-nav__item')),
|
||||
m('button.mobile-bottom-nav__item[type=button]', {
|
||||
class: isMoreOpen || mobileMoreItems.some((item) => item.name === routeName()) ? 'active' : '',
|
||||
'aria-expanded': String(isMoreOpen),
|
||||
'aria-haspopup': 'dialog',
|
||||
onclick: () => (isMoreOpen = !isMoreOpen),
|
||||
}, [m('i.fas.fa-bars.sidenav-icon'), m('span', 'More')]),
|
||||
]),
|
||||
|
||||
@ -645,3 +645,16 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset native dialog dimensions so mobile overlays still fill the viewport.
|
||||
dialog.accessible-dialog {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: none;
|
||||
max-height: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
dialog.accessible-dialog::backdrop { background: transparent; }
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user