First step in making things focusable

This commit is contained in:
Lim Chee Aun 2022-12-30 20:37:57 +08:00
parent 30c529fe02
commit 9201f7a118
8 changed files with 62 additions and 9 deletions

View file

@ -298,6 +298,26 @@ export function App() {
}, []); }, []);
const [currentDeck, setCurrentDeck] = useState('home'); const [currentDeck, setCurrentDeck] = useState('home');
const focusDeck = () => {
let timer = setTimeout(() => {
const page = document.getElementById(`${currentDeck}-page`);
console.log('focus', currentDeck, page);
if (page) {
page.focus();
}
}, 100);
return () => clearTimeout(timer);
};
useEffect(focusDeck, [currentDeck]);
useEffect(() => {
if (
!snapStates.showCompose &&
!snapStates.showSettings &&
!snapStates.showAccount
) {
focusDeck();
}
}, [snapStates.showCompose, snapStates.showSettings, snapStates.showAccount]);
useEffect(() => { useEffect(() => {
// HACK: prevent this from running again due to HMR // HACK: prevent this from running again due to HMR
@ -324,7 +344,7 @@ export function App() {
return ( return (
<> <>
{isLoggedIn && currentDeck && ( {isLoggedIn && (
<> <>
<button <button
type="button" type="button"
@ -345,7 +365,7 @@ export function App() {
</button> </button>
<div class="decks"> <div class="decks">
{/* Home will never be unmounted */} {/* Home will never be unmounted */}
<Home hidden={currentDeck !== 'home'} /> <Home />
{/* Notifications can be unmounted */} {/* Notifications can be unmounted */}
{currentDeck === 'notifications' && <Notifications />} {currentDeck === 'notifications' && <Notifications />}
</div> </div>
@ -355,6 +375,7 @@ export function App() {
<Router <Router
history={createHashHistory()} history={createHashHistory()}
onChange={(e) => { onChange={(e) => {
console.log('router onChange', e);
// Special handling for Home and Notifications // Special handling for Home and Notifications
const { url } = e; const { url } = e;
if (/notifications/i.test(url)) { if (/notifications/i.test(url)) {
@ -362,7 +383,7 @@ export function App() {
} else if (url === '/') { } else if (url === '/') {
setCurrentDeck('home'); setCurrentDeck('home');
document.title = `Home / ${CLIENT_NAME}`; document.title = `Home / ${CLIENT_NAME}`;
} else if (url === '/login' || url === '/welcome') { } else {
setCurrentDeck(null); setCurrentDeck(null);
} }
states.history.push(url); states.history.push(url);

View file

@ -102,7 +102,6 @@ function Account({ account }) {
return ( return (
<div <div
id="account-container" id="account-container"
tabIndex="-1"
class={`sheet ${uiState === 'loading' ? 'skeleton' : ''}`} class={`sheet ${uiState === 'loading' ? 'skeleton' : ''}`}
> >
{!info || uiState === 'loading' ? ( {!info || uiState === 'loading' ? (
@ -129,7 +128,7 @@ function Account({ account }) {
<Avatar url={avatar} size="xxxl" /> <Avatar url={avatar} size="xxxl" />
<NameText account={info} showAcct external /> <NameText account={info} showAcct external />
</header> </header>
<main> <main tabIndex="-1">
{bot && ( {bot && (
<> <>
<span class="tag"> <span class="tag">

View file

@ -1,14 +1,26 @@
import './modal.css'; import './modal.css';
import { createPortal } from 'preact/compat'; import { createPortal } from 'preact/compat';
import { useEffect, useRef } from 'preact/hooks';
const $modalContainer = document.getElementById('modal-container'); const $modalContainer = document.getElementById('modal-container');
function Modal({ children, onClick, class: className }) { function Modal({ children, onClick, class: className }) {
if (!children) return null; if (!children) return null;
const modalRef = useRef();
useEffect(() => {
let timer = setTimeout(() => {
const focusElement = modalRef.current?.querySelector('[tabindex="-1"]');
if (focusElement) {
focusElement.focus();
}
}, 100);
return () => clearTimeout(timer);
}, []);
const Modal = ( const Modal = (
<div className={className} onClick={onClick}> <div ref={modalRef} className={className} onClick={onClick}>
{children} {children}
</div> </div>
); );

View file

@ -186,8 +186,12 @@ function Status({
}); });
const readMoreText = 'Read more →'; const readMoreText = 'Read more →';
const statusRef = useRef(null);
return ( return (
<article <article
ref={statusRef}
tabindex="-1"
class={`status ${ class={`status ${
!withinContext && inReplyToAccount ? 'status-reply-to' : '' !withinContext && inReplyToAccount ? 'status-reply-to' : ''
} visibility-${visibility} ${ } visibility-${visibility} ${
@ -653,6 +657,7 @@ function Status({
index={showMediaModal} index={showMediaModal}
onClose={() => { onClose={() => {
setShowMediaModal(false); setShowMediaModal(false);
statusRef.current?.focus();
}} }}
/> />
</Modal> </Modal>
@ -662,6 +667,7 @@ function Status({
onClick={(e) => { onClick={(e) => {
if (e.target === e.currentTarget) { if (e.target === e.currentTarget) {
setShowEdited(false); setShowEdited(false);
statusRef.current?.focus();
} }
}} }}
> >
@ -669,6 +675,7 @@ function Status({
statusID={showEdited} statusID={showEdited}
onClose={() => { onClose={() => {
setShowEdited(false); setShowEdited(false);
statusRef.current?.focus();
}} }}
/> />
</Modal> </Modal>
@ -1153,7 +1160,7 @@ function EditedAtModal({ statusID, onClose = () => {} }) {
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
return ( return (
<div id="edit-history" class="sheet" tabIndex="-1"> <div id="edit-history" class="sheet">
<header> <header>
{/* <button type="button" class="close-button plain large" onClick={onClose}> {/* <button type="button" class="close-button plain large" onClick={onClose}>
<Icon icon="x" alt="Close" /> <Icon icon="x" alt="Close" />
@ -1166,7 +1173,7 @@ function EditedAtModal({ statusID, onClose = () => {} }) {
</p> </p>
)} )}
</header> </header>
<main> <main tabIndex="-1">
{editHistory.length > 0 && ( {editHistory.length > 0 && (
<ol> <ol>
{editHistory.map((status) => { {editHistory.map((status) => {

View file

@ -242,6 +242,10 @@ code {
} }
} }
[tabindex='-1'] {
outline: 0;
}
/* UTILS */ /* UTILS */
.ib { .ib {

View file

@ -73,6 +73,7 @@ function Home({ hidden }) {
return ( return (
<div <div
id="home-page"
class="deck-container" class="deck-container"
hidden={hidden} hidden={hidden}
ref={scrollableRef} ref={scrollableRef}

View file

@ -290,7 +290,12 @@ function Notifications() {
); );
// console.log(groupedNotifications); // console.log(groupedNotifications);
return ( return (
<div class="deck-container" ref={scrollableRef} tabIndex="-1"> <div
id="notifications-page"
class="deck-container"
ref={scrollableRef}
tabIndex="-1"
>
<div class={`timeline-deck deck ${onlyMentions ? 'only-mentions' : ''}`}> <div class={`timeline-deck deck ${onlyMentions ? 'only-mentions' : ''}`}>
<header <header
onClick={() => { onClick={() => {

View file

@ -33,6 +33,9 @@ function StatusPage({ id }) {
const heroStatusRef = useRef(); const heroStatusRef = useRef();
const scrollableRef = useRef(); const scrollableRef = useRef();
useEffect(() => {
scrollableRef.current?.focus();
}, []);
useEffect(() => { useEffect(() => {
const onScroll = debounce(() => { const onScroll = debounce(() => {
// console.log('onScroll'); // console.log('onScroll');
@ -279,6 +282,7 @@ function StatusPage({ id }) {
<div class="deck-backdrop"> <div class="deck-backdrop">
<Link href={closeLink}></Link> <Link href={closeLink}></Link>
<div <div
tabIndex="-1"
ref={scrollableRef} ref={scrollableRef}
class={`status-deck deck contained ${ class={`status-deck deck contained ${
statuses.length > 1 ? 'padded-bottom' : '' statuses.length > 1 ? 'padded-bottom' : ''