From 80a5f4594eefbf113a1c55c7cd3f52db9478513f Mon Sep 17 00:00:00 2001 From: Tim Down <158919+timdown@users.noreply.github.com> Date: Thu, 25 May 2023 10:59:55 +0100 Subject: [PATCH] Merge pull request #13130 from overleaf/td-history-add-label-autofocus History migration: Autofocus input in add label dialog GitOrigin-RevId: b51187e735414cd4b027413dfe57a0ba332c7b1a --- .../change-list/add-label-modal.tsx | 19 ++++++++++--- .../shared/hooks/use-ref-with-auto-focus.ts | 27 ++++++++++++++++--- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/services/web/frontend/js/features/history/components/change-list/add-label-modal.tsx b/services/web/frontend/js/features/history/components/change-list/add-label-modal.tsx index 409fbee428..15196c8b7a 100644 --- a/services/web/frontend/js/features/history/components/change-list/add-label-modal.tsx +++ b/services/web/frontend/js/features/history/components/change-list/add-label-modal.tsx @@ -1,5 +1,5 @@ import { useTranslation } from 'react-i18next' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { Modal, FormGroup, FormControl } from 'react-bootstrap' import ModalError from './modal-error' import AccessibleModal from '../../../../shared/components/accessible-modal' @@ -9,6 +9,7 @@ import useAddOrRemoveLabels from '../../hooks/use-add-or-remove-labels' import { useHistoryContext } from '../../context/history-context' import { addLabel } from '../../services/api' import { Label } from '../../services/types/label' +import { useRefWithAutoFocus } from '../../../../shared/hooks/use-ref-with-auto-focus' type AddLabelModalProps = { show: boolean @@ -32,6 +33,17 @@ function AddLabelModal({ show, setShow, version }: AddLabelModalProps) { const { signal } = useAbortController() const { addUpdateLabel } = useAddOrRemoveLabels() + const { autoFocusedRef, resetAutoFocus } = + useRefWithAutoFocus() + + // Reset the autofocus when `show` changes so that autofocus still happens if + // the dialog is shown, hidden and then shown again + useEffect(() => { + if (show) { + resetAutoFocus() + } + }, [resetAutoFocus, show]) + const handleModalExited = () => { setComment('') @@ -71,7 +83,9 @@ function AddLabelModal({ show, setShow, version }: AddLabelModalProps) { {isError && } - ) => setComment(e.target.value)} - autoFocus // eslint-disable-line jsx-a11y/no-autofocus /> diff --git a/services/web/frontend/js/shared/hooks/use-ref-with-auto-focus.ts b/services/web/frontend/js/shared/hooks/use-ref-with-auto-focus.ts index e01c71c7e9..f37acd5b80 100644 --- a/services/web/frontend/js/shared/hooks/use-ref-with-auto-focus.ts +++ b/services/web/frontend/js/shared/hooks/use-ref-with-auto-focus.ts @@ -1,17 +1,36 @@ -import { useRef, useEffect } from 'react' +import { useRef, useEffect, useCallback, useState } from 'react' export function useRefWithAutoFocus() { const autoFocusedRef = useRef(null) + const [hasFocused, setHasFocused] = useState(false) + const resetAutoFocus = useCallback(() => setHasFocused(false), []) + // Run on every render but use hasFocused to ensure that the autofocus only + // happens once useEffect(() => { + if (hasFocused) { + return + } + + let request: number | null = null if (autoFocusedRef.current) { - window.requestAnimationFrame(() => { + request = window.requestAnimationFrame(() => { if (autoFocusedRef.current) { autoFocusedRef.current.focus() + setHasFocused(true) + request = null } }) } - }, []) - return { autoFocusedRef } + // Cancel a pending autofocus prior to autofocus actually happening on + // render, and on unmount + return () => { + if (request !== null) { + window.cancelAnimationFrame(request) + } + } + }) + + return { autoFocusedRef, resetAutoFocus } }