Skip to content

Commit

Permalink
feat(block-dialog): focus the block button by default
Browse files Browse the repository at this point in the history
fix #192
  • Loading branch information
iorate committed Jan 25, 2022
1 parent e20c029 commit da0d934
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 10 deletions.
4 changes: 2 additions & 2 deletions src/scripts/block-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React, { useState } from 'react';
import icon from '../icons/icon.svg';
import { ScopedBaseline } from './components/baseline';
import { Button, LinkButton } from './components/button';
import { FOCUS_END_CLASS, FOCUS_START_CLASS } from './components/constants';
import { FOCUS_DEFAULT_CLASS, FOCUS_END_CLASS, FOCUS_START_CLASS } from './components/constants';
import { Details, DetailsBody, DetailsSummary } from './components/details';
import {
Dialog,
Expand Down Expand Up @@ -253,7 +253,7 @@ const BlockDialogContent: React.VFC<BlockDialogContentProps> = ({
</RowItem>
<RowItem>
<Button
className={ok ? FOCUS_END_CLASS : ''}
className={ok ? `${FOCUS_END_CLASS} ${FOCUS_DEFAULT_CLASS}` : FOCUS_DEFAULT_CLASS}
disabled={!ok}
primary
onClick={async () => {
Expand Down
1 change: 1 addition & 0 deletions src/scripts/components/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Classes
export const FOCUS_START_CLASS = 'js-focus-start';
export const FOCUS_END_CLASS = 'js-focus-end';
export const FOCUS_DEFAULT_CLASS = 'js-focus-default';
export const MENU_ITEM_CLASS = 'js-menu-item';

// Opacity
Expand Down
41 changes: 36 additions & 5 deletions src/scripts/components/dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
import * as Goober from 'goober';
import React, { useLayoutEffect, useMemo, useRef } from 'react';
import { DIALOG_Z_INDEX, FOCUS_END_CLASS, FOCUS_START_CLASS } from './constants';
import {
DIALOG_Z_INDEX,
FOCUS_DEFAULT_CLASS,
FOCUS_END_CLASS,
FOCUS_START_CLASS,
} from './constants';
import { applyClassName, useInnerRef } from './helpers';
import { useClassName } from './utilities';

function getFocusedElement(dialog: HTMLElement): Element | null {
return (dialog.getRootNode() as Document | ShadowRoot).activeElement;
}

function focusDefaultOrStart(dialog: HTMLDivElement): void {
const defaultOrStart =
dialog.querySelector(`.${FOCUS_DEFAULT_CLASS}`) ||
dialog.querySelector(`.${FOCUS_START_CLASS}`);
if (defaultOrStart instanceof HTMLElement || defaultOrStart instanceof SVGElement) {
defaultOrStart.focus();
if (getFocusedElement(dialog) !== defaultOrStart) {
dialog.focus();
}
} else {
dialog.focus();
}
}

function handleKeyDown(
e: React.KeyboardEvent<HTMLDivElement>,
dialog: HTMLDivElement,
Expand Down Expand Up @@ -55,9 +78,11 @@ export const Dialog = React.forwardRef<HTMLDivElement, DialogProps>(function Dia

useLayoutEffect(() => {
if (open) {
prevFocus.current = document.activeElement;
innerRef.current?.querySelector<HTMLElement>(`.${FOCUS_START_CLASS}`)?.focus();
document.documentElement.classList.add(rootClassName);
prevFocus.current = document.activeElement;
if (innerRef.current) {
focusDefaultOrStart(innerRef.current);
}
} else {
if (prevFocus.current instanceof HTMLElement || prevFocus.current instanceof SVGElement) {
prevFocus.current.focus();
Expand All @@ -69,8 +94,7 @@ export const Dialog = React.forwardRef<HTMLDivElement, DialogProps>(function Dia
}, [open]);
useLayoutEffect(() => {
if (open && innerRef.current) {
const currFocus = (innerRef.current.getRootNode() as Document | ShadowRoot).activeElement;
if (!innerRef.current.contains(currFocus)) {
if (!innerRef.current.contains(getFocusedElement(innerRef.current))) {
innerRef.current.focus();
}
}
Expand Down Expand Up @@ -202,6 +226,13 @@ export type EmbeddedDialogProps = JSX.IntrinsicElements['div'] & {
export const EmbeddedDialog = React.forwardRef<HTMLDivElement, EmbeddedDialogProps>(
function EmbeddedDialog({ close, width, ...props }, ref) {
const innerRef = useInnerRef(ref);
useLayoutEffect(() => {
if (innerRef.current) {
focusDefaultOrStart(innerRef.current);
}
// 'innerRef' does not change between renders.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const className = useClassName(
theme => ({
Expand Down
10 changes: 7 additions & 3 deletions src/scripts/popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { apis } from './apis';
import { BlockEmbeddedDialog, BlockEmbeddedDialogProps } from './block-dialog';
import { Baseline } from './components/baseline';
import { Button, LinkButton } from './components/button';
import { FOCUS_END_CLASS, FOCUS_START_CLASS } from './components/constants';
import { FOCUS_DEFAULT_CLASS, FOCUS_END_CLASS, FOCUS_START_CLASS } from './components/constants';
import { DialogFooter, DialogHeader, DialogTitle, EmbeddedDialog } from './components/dialog';
import { Icon } from './components/icon';
import { Row, RowItem } from './components/row';
Expand Down Expand Up @@ -66,7 +66,11 @@ const ActivateEmbeddedDialog: React.VFC<ActivateEmbeddedDialogProps> = ({
{active ? (
<Row>
<RowItem>
<Button className={FOCUS_END_CLASS} primary onClick={() => window.close()}>
<Button
className={`${FOCUS_END_CLASS} ${FOCUS_DEFAULT_CLASS}`}
primary
onClick={() => window.close()}
>
{translate('okButton')}
</Button>
</RowItem>
Expand All @@ -78,7 +82,7 @@ const ActivateEmbeddedDialog: React.VFC<ActivateEmbeddedDialogProps> = ({
</RowItem>
<RowItem>
<Button
className={FOCUS_END_CLASS}
className={`${FOCUS_END_CLASS} ${FOCUS_DEFAULT_CLASS}`}
primary
onClick={async () => {
// In Chrome, the popup is closed immediately after 'permissions.request'!
Expand Down

0 comments on commit da0d934

Please sign in to comment.