diff --git a/lib/RepeatableField/RepeatableField.js b/lib/RepeatableField/RepeatableField.js index c3ace1b0b..db134d2dc 100644 --- a/lib/RepeatableField/RepeatableField.js +++ b/lib/RepeatableField/RepeatableField.js @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useRef, useEffect } from 'react'; import classnames from 'classnames'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; @@ -12,7 +12,7 @@ import { RepeatableFieldContent } from "./RepeatableFieldContent"; import css from './RepeatableField.css'; import { useFocusedIndex } from "./hooks/useFocusedIndex"; import { useIsElementFocused } from "./hooks/useIsElementFocused"; - +import { getFirstFocusable } from '../../util/getFocusableElements'; const RepeatableField = ({ canAdd = true, @@ -35,6 +35,51 @@ const RepeatableField = ({ const fieldsLength = fields.length; const isSomeChildElementFocused = useIsElementFocused(rootRef); const focusedIndex = useFocusedIndex(fieldsLength); + + // use mutation observers to handle focus-management since we only have internal state. + useEffect(() => { + const observer = new MutationObserver(mutations => { + let rowElem; + // field additions - we only manage focus when focus is + // within the container... + if (rootRef.current.matches(':focus-within')) { + mutations.forEach((m) => { + if (m.type === 'childList') { + if (m.addedNodes?.length === 1) { + rowElem = m.addedNodes[0]; + if (rowElem) { + getFirstFocusable(rowElem)?.focus(); + } + } + } + }); + } + // removals may or may not keep focus within the field list... + mutations.forEach((m) => { + if (m.type === 'childList') { + if (m.removedNodes.length === 1) { + rowElem = m.previousSibling; + if (rowElem) { + getFirstFocusable(rowElem)?.focus(); + } + } + } + }); + }); + + if (rootRef.current) { + // observe for item additions/removals from list. + observer.observe(rootRef.current, { + childList: true, + subtree: true, + }); + } + + return () => { + observer.disconnect(); + }; + }, []) + const hasToBeFocused = (index) => isSomeChildElementFocused && focusedIndex === index; return ( diff --git a/lib/RepeatableField/RepeatableFieldContent.js b/lib/RepeatableField/RepeatableFieldContent.js index 854cdb341..dda59c841 100644 --- a/lib/RepeatableField/RepeatableFieldContent.js +++ b/lib/RepeatableField/RepeatableFieldContent.js @@ -1,31 +1,17 @@ import React, { useCallback } from "react"; import PropTypes from "prop-types"; -import { getFirstFocusable } from "../../util/getFocusableElements"; - import css from "./RepeatableField.css"; -export const RepeatableFieldContent = ({ children, isFocused }) => { - - const callbackRef = useCallback((node) => { - if (node && isFocused) { - const elem = getFirstFocusable(node, true, true); - - elem?.focus(); - } - }, [isFocused]) - - return ( -
- {children} -
- ); -} +export const RepeatableFieldContent = ({ children }) => ( +
+ {children} +
+); RepeatableFieldContent.propTypes = { children: PropTypes.oneOfType([ PropTypes.node, PropTypes.func, ]).isRequired, - isFocused: PropTypes.bool.isRequired, }