diff --git a/src/accessibility/RovingTabIndex.tsx b/src/accessibility/RovingTabIndex.tsx index 2fb22e9f8f..d35a0291c3 100644 --- a/src/accessibility/RovingTabIndex.tsx +++ b/src/accessibility/RovingTabIndex.tsx @@ -10,7 +10,6 @@ import React, { createContext, useCallback, useContext, - useEffect, useMemo, useRef, useReducer, @@ -18,11 +17,12 @@ import React, { Dispatch, RefObject, ReactNode, + RefCallback, } from "react"; import { getKeyBindingsManager } from "../KeyBindingsManager"; import { KeyBindingAction } from "./KeyboardShortcuts"; -import { FocusHandler, Ref } from "./roving/types"; +import { FocusHandler } from "./roving/types"; /** * Module to simplify implementing the Roving TabIndex accessibility technique @@ -49,8 +49,8 @@ export function checkInputableElement(el: HTMLElement): boolean { } export interface IState { - activeRef?: Ref; - refs: Ref[]; + activeNode?: HTMLElement; + nodes: HTMLElement[]; } export interface IContext { @@ -60,7 +60,7 @@ export interface IContext { export const RovingTabIndexContext = createContext({ state: { - refs: [], // list of refs in DOM order + nodes: [], // list of nodes in DOM order }, dispatch: () => {}, }); @@ -76,7 +76,7 @@ export enum Type { export interface IAction { type: Exclude; payload: { - ref: Ref; + node: HTMLElement; }; } @@ -87,12 +87,12 @@ interface UpdateAction { type Action = IAction | UpdateAction; -const refSorter = (a: Ref, b: Ref): number => { +const nodeSorter = (a: HTMLElement, b: HTMLElement): number => { if (a === b) { return 0; } - const position = a.current!.compareDocumentPosition(b.current!); + const position = a.compareDocumentPosition(b); if (position & Node.DOCUMENT_POSITION_FOLLOWING || position & Node.DOCUMENT_POSITION_CONTAINED_BY) { return -1; @@ -106,54 +106,56 @@ const refSorter = (a: Ref, b: Ref): number => { export const reducer: Reducer = (state: IState, action: Action) => { switch (action.type) { case Type.Register: { - if (!state.activeRef) { - // Our list of refs was empty, set activeRef to this first item - state.activeRef = action.payload.ref; + if (!state.activeNode) { + // Our list of nodes was empty, set activeNode to this first item + state.activeNode = action.payload.node; } + if (state.nodes.includes(action.payload.node)) return state; + // Sadly due to the potential of DOM elements swapping order we can't do anything fancy like a binary insert - state.refs.push(action.payload.ref); - state.refs.sort(refSorter); + state.nodes.push(action.payload.node); + state.nodes.sort(nodeSorter); return { ...state }; } case Type.Unregister: { - const oldIndex = state.refs.findIndex((r) => r === action.payload.ref); + const oldIndex = state.nodes.findIndex((r) => r === action.payload.node); if (oldIndex === -1) { return state; // already removed, this should not happen } - if (state.refs.splice(oldIndex, 1)[0] === state.activeRef) { - // we just removed the active ref, need to replace it - // pick the ref closest to the index the old ref was in - if (oldIndex >= state.refs.length) { - state.activeRef = findSiblingElement(state.refs, state.refs.length - 1, true); + if (state.nodes.splice(oldIndex, 1)[0] === state.activeNode) { + // we just removed the active node, need to replace it + // pick the node closest to the index the old node was in + if (oldIndex >= state.nodes.length) { + state.activeNode = findSiblingElement(state.nodes, state.nodes.length - 1, true); } else { - state.activeRef = - findSiblingElement(state.refs, oldIndex) || findSiblingElement(state.refs, oldIndex, true); + state.activeNode = + findSiblingElement(state.nodes, oldIndex) || findSiblingElement(state.nodes, oldIndex, true); } if (document.activeElement === document.body) { // if the focus got reverted to the body then the user was likely focused on the unmounted element - setTimeout(() => state.activeRef?.current?.focus(), 0); + setTimeout(() => state.activeNode?.focus(), 0); } } - // update the refs list + // update the nodes list return { ...state }; } case Type.SetFocus: { - // if the ref doesn't change just return the same object reference to skip a re-render - if (state.activeRef === action.payload.ref) return state; - // update active ref - state.activeRef = action.payload.ref; + // if the node doesn't change just return the same object reference to skip a re-render + if (state.activeNode === action.payload.node) return state; + // update active node + state.activeNode = action.payload.node; return { ...state }; } case Type.Update: { - state.refs.sort(refSorter); + state.nodes.sort(nodeSorter); return { ...state }; } @@ -174,28 +176,28 @@ interface IProps { } export const findSiblingElement = ( - refs: RefObject[], + nodes: HTMLElement[], startIndex: number, backwards = false, loop = false, -): RefObject | undefined => { +): HTMLElement | undefined => { if (backwards) { - for (let i = startIndex; i < refs.length && i >= 0; i--) { - if (refs[i].current?.offsetParent !== null) { - return refs[i]; + for (let i = startIndex; i < nodes.length && i >= 0; i--) { + if (nodes[i]?.offsetParent !== null) { + return nodes[i]; } } if (loop) { - return findSiblingElement(refs.slice(startIndex + 1), refs.length - 1, true, false); + return findSiblingElement(nodes.slice(startIndex + 1), nodes.length - 1, true, false); } } else { - for (let i = startIndex; i < refs.length && i >= 0; i++) { - if (refs[i].current?.offsetParent !== null) { - return refs[i]; + for (let i = startIndex; i < nodes.length && i >= 0; i++) { + if (nodes[i]?.offsetParent !== null) { + return nodes[i]; } } if (loop) { - return findSiblingElement(refs.slice(0, startIndex), 0, false, false); + return findSiblingElement(nodes.slice(0, startIndex), 0, false, false); } } }; @@ -211,7 +213,7 @@ export const RovingTabIndexProvider: React.FC = ({ onKeyDown, }) => { const [state, dispatch] = useReducer>(reducer, { - refs: [], + nodes: [], }); const context = useMemo(() => ({ state, dispatch }), [state]); @@ -227,17 +229,17 @@ export const RovingTabIndexProvider: React.FC = ({ let handled = false; const action = getKeyBindingsManager().getAccessibilityAction(ev); - let focusRef: RefObject | undefined; + let focusNode: HTMLElement | undefined; // Don't interfere with input default keydown behaviour // but allow people to move focus from it with Tab. if (!handleInputFields && checkInputableElement(ev.target as HTMLElement)) { switch (action) { case KeyBindingAction.Tab: handled = true; - if (context.state.refs.length > 0) { - const idx = context.state.refs.indexOf(context.state.activeRef!); - focusRef = findSiblingElement( - context.state.refs, + if (context.state.nodes.length > 0) { + const idx = context.state.nodes.indexOf(context.state.activeNode!); + focusNode = findSiblingElement( + context.state.nodes, idx + (ev.shiftKey ? -1 : 1), ev.shiftKey, ); @@ -251,7 +253,7 @@ export const RovingTabIndexProvider: React.FC = ({ if (handleHomeEnd) { handled = true; // move focus to first (visible) item - focusRef = findSiblingElement(context.state.refs, 0); + focusNode = findSiblingElement(context.state.nodes, 0); } break; @@ -259,7 +261,7 @@ export const RovingTabIndexProvider: React.FC = ({ if (handleHomeEnd) { handled = true; // move focus to last (visible) item - focusRef = findSiblingElement(context.state.refs, context.state.refs.length - 1, true); + focusNode = findSiblingElement(context.state.nodes, context.state.nodes.length - 1, true); } break; @@ -270,9 +272,9 @@ export const RovingTabIndexProvider: React.FC = ({ (action === KeyBindingAction.ArrowRight && handleLeftRight) ) { handled = true; - if (context.state.refs.length > 0) { - const idx = context.state.refs.indexOf(context.state.activeRef!); - focusRef = findSiblingElement(context.state.refs, idx + 1, false, handleLoop); + if (context.state.nodes.length > 0) { + const idx = context.state.nodes.indexOf(context.state.activeNode!); + focusNode = findSiblingElement(context.state.nodes, idx + 1, false, handleLoop); } } break; @@ -284,9 +286,9 @@ export const RovingTabIndexProvider: React.FC = ({ (action === KeyBindingAction.ArrowLeft && handleLeftRight) ) { handled = true; - if (context.state.refs.length > 0) { - const idx = context.state.refs.indexOf(context.state.activeRef!); - focusRef = findSiblingElement(context.state.refs, idx - 1, true, handleLoop); + if (context.state.nodes.length > 0) { + const idx = context.state.nodes.indexOf(context.state.activeNode!); + focusNode = findSiblingElement(context.state.nodes, idx - 1, true, handleLoop); } } break; @@ -298,17 +300,17 @@ export const RovingTabIndexProvider: React.FC = ({ ev.stopPropagation(); } - if (focusRef) { - focusRef.current?.focus(); + if (focusNode) { + focusNode?.focus(); // programmatic focus doesn't fire the onFocus handler, so we must do the do ourselves dispatch({ type: Type.SetFocus, payload: { - ref: focusRef, + node: focusNode, }, }); if (scrollIntoView) { - focusRef.current?.scrollIntoView(scrollIntoView); + focusNode?.scrollIntoView(scrollIntoView); } } }, @@ -337,46 +339,61 @@ export const RovingTabIndexProvider: React.FC = ({ ); }; -// Hook to register a roving tab index -// inputRef parameter specifies the ref to use -// onFocus should be called when the index gained focus in any manner -// isActive should be used to set tabIndex in a manner such as `tabIndex={isActive ? 0 : -1}` -// ref should be passed to a DOM node which will be used for DOM compareDocumentPosition +/** + * Hook to register a roving tab index. + * + * inputRef is an optional argument; when passed this ref points to the DOM element + * to which the callback ref is attached. + * + * Returns: + * onFocus should be called when the index gained focus in any manner. + * isActive should be used to set tabIndex in a manner such as `tabIndex={isActive ? 0 : -1}`. + * ref is a callback ref that should be passed to a DOM node which will be used for DOM compareDocumentPosition. + * nodeRef is a ref that points to the DOM element to which the ref mentioned above is attached. + * + * nodeRef = inputRef when inputRef argument is provided. + */ export const useRovingTabIndex = ( inputRef?: RefObject, -): [FocusHandler, boolean, RefObject] => { +): [FocusHandler, boolean, RefCallback, RefObject] => { const context = useContext(RovingTabIndexContext); - let ref = useRef(null); + + let nodeRef = useRef(null); if (inputRef) { // if we are given a ref, use it instead of ours - ref = inputRef; + nodeRef = inputRef; } - // setup (after refs) - useEffect(() => { - context.dispatch({ - type: Type.Register, - payload: { ref }, - }); - // teardown - return () => { + const ref = useCallback((node: T | null) => { + if (node) { + nodeRef.current = node; + context.dispatch({ + type: Type.Register, + payload: { node }, + }); + } else { context.dispatch({ type: Type.Unregister, - payload: { ref }, + payload: { node: nodeRef.current! }, }); - }; + nodeRef.current = null; + } }, []); // eslint-disable-line react-hooks/exhaustive-deps const onFocus = useCallback(() => { + if (!nodeRef.current) { + console.warn("useRovingTabIndex.onFocus called but the react ref does not point to any DOM element!"); + return; + } context.dispatch({ type: Type.SetFocus, - payload: { ref }, + payload: { node: nodeRef.current }, }); }, []); // eslint-disable-line react-hooks/exhaustive-deps - const isActive = context.state.activeRef === ref; - return [onFocus, isActive, ref]; + const isActive = context.state.activeNode === nodeRef.current; + return [onFocus, isActive, ref, nodeRef]; }; // re-export the semantic helper components for simplicity diff --git a/src/accessibility/roving/RovingTabIndexWrapper.tsx b/src/accessibility/roving/RovingTabIndexWrapper.tsx index 93436ef4b5..b44f44b92f 100644 --- a/src/accessibility/roving/RovingTabIndexWrapper.tsx +++ b/src/accessibility/roving/RovingTabIndexWrapper.tsx @@ -6,14 +6,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ -import React, { ReactElement } from "react"; +import React, { ReactElement, RefCallback } from "react"; import { useRovingTabIndex } from "../RovingTabIndex"; import { FocusHandler, Ref } from "./types"; interface IProps { inputRef?: Ref; - children(renderProps: { onFocus: FocusHandler; isActive: boolean; ref: Ref }): ReactElement; + children(renderProps: { + onFocus: FocusHandler; + isActive: boolean; + ref: RefCallback; + }): ReactElement; } // Wrapper to allow use of useRovingTabIndex outside of React Functional Components. diff --git a/src/components/structures/SpaceHierarchy.tsx b/src/components/structures/SpaceHierarchy.tsx index d12f66e9c7..80193fd338 100644 --- a/src/components/structures/SpaceHierarchy.tsx +++ b/src/components/structures/SpaceHierarchy.tsx @@ -114,7 +114,7 @@ const Tile: React.FC = ({ (room.room_type === RoomType.Space ? _t("common|unnamed_space") : _t("common|unnamed_room")); const [showChildren, toggleShowChildren] = useStateToggle(true); - const [onFocus, isActive, ref] = useRovingTabIndex(); + const [onFocus, isActive, ref, nodeRef] = useRovingTabIndex(); const [busy, setBusy] = useState(false); const onPreviewClick = (ev: ButtonEvent): void => { @@ -288,7 +288,7 @@ const Tile: React.FC = ({ case KeyBindingAction.ArrowLeft: e.preventDefault(); e.stopPropagation(); - ref.current?.focus(); + nodeRef.current?.focus(); break; } }; @@ -315,7 +315,7 @@ const Tile: React.FC = ({ case KeyBindingAction.ArrowRight: handled = true; if (showChildren) { - const childSection = ref.current?.nextElementSibling; + const childSection = nodeRef.current?.nextElementSibling; childSection?.querySelector(".mx_SpaceHierarchy_roomTile")?.focus(); } else { toggleShowChildren(); @@ -790,7 +790,7 @@ const SpaceHierarchy: React.FC = ({ space, initialText = "", showRoom, a const onKeyDown = (ev: KeyboardEvent, state: IState): void => { const action = getKeyBindingsManager().getAccessibilityAction(ev); if (action === KeyBindingAction.ArrowDown && ev.currentTarget.classList.contains("mx_SpaceHierarchy_search")) { - state.refs[0]?.current?.focus(); + state.nodes[0]?.focus(); } }; diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 6e9df055cd..0a0a70d1b5 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -294,7 +294,7 @@ const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCr const action = getKeyBindingsManager().getAccessibilityAction(ev); switch (action) { case KeyBindingAction.Enter: { - state.activeRef?.current?.querySelector(".mx_ForwardList_sendButton")?.click(); + state.activeNode?.querySelector(".mx_ForwardList_sendButton")?.click(); break; } @@ -347,13 +347,13 @@ const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCr onSearch={(query: string): void => { setQuery(query); setTimeout(() => { - const ref = context.state.refs[0]; - if (ref) { + const node = context.state.nodes[0]; + if (node) { context.dispatch({ type: Type.SetFocus, - payload: { ref }, + payload: { node }, }); - ref.current?.scrollIntoView?.({ + node?.scrollIntoView?.({ block: "nearest", }); } @@ -361,7 +361,7 @@ const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCr }} autoFocus={true} onKeyDown={onKeyDownHandler} - aria-activedescendant={context.state.activeRef?.current?.id} + aria-activedescendant={context.state.activeNode?.id} aria-owns="mx_ForwardDialog_resultsList" /> )} diff --git a/src/components/views/dialogs/spotlight/Option.tsx b/src/components/views/dialogs/spotlight/Option.tsx index 4c7e7ac4f4..6de93d0512 100644 --- a/src/components/views/dialogs/spotlight/Option.tsx +++ b/src/components/views/dialogs/spotlight/Option.tsx @@ -7,13 +7,12 @@ Please see LICENSE files in the repository root for full details. */ import classNames from "classnames"; -import React, { ReactNode, RefObject } from "react"; +import React, { ReactNode } from "react"; import { useRovingTabIndex } from "../../../../accessibility/RovingTabIndex"; import AccessibleButton, { ButtonEvent } from "../../elements/AccessibleButton"; interface OptionProps { - inputRef?: RefObject; endAdornment?: ReactNode; id?: string; className?: string; @@ -21,8 +20,8 @@ interface OptionProps { children?: ReactNode; } -export const Option: React.FC = ({ inputRef, children, endAdornment, className, ...props }) => { - const [onFocus, isActive, ref] = useRovingTabIndex(inputRef); +export const Option: React.FC = ({ children, endAdornment, className, ...props }) => { + const [onFocus, isActive, ref] = useRovingTabIndex(); return ( ): boolean { - return ref?.current?.id?.startsWith("mx_SpotlightDialog_button_recentlyViewed_") === true; +function nodeIsForRecentlyViewed(node?: HTMLElement): boolean { + return node?.id?.startsWith("mx_SpotlightDialog_button_recentlyViewed_") === true; } function getRoomTypes(filter: Filter | null): Set { @@ -498,13 +498,13 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n }; useEffect(() => { setTimeout(() => { - const ref = rovingContext.state.refs[0]; - if (ref) { + const node = rovingContext.state.nodes[0]; + if (node) { rovingContext.dispatch({ type: Type.SetFocus, - payload: { ref }, + payload: { node }, }); - ref.current?.scrollIntoView?.({ + node?.scrollIntoView?.({ block: "nearest", }); } @@ -1128,7 +1128,7 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n break; } - let ref: RefObject | undefined; + let node: HTMLElement | undefined; const accessibilityAction = getKeyBindingsManager().getAccessibilityAction(ev); switch (accessibilityAction) { case KeyBindingAction.Escape: @@ -1141,20 +1141,20 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n ev.stopPropagation(); ev.preventDefault(); - if (rovingContext.state.activeRef && rovingContext.state.refs.length > 0) { - let refs = rovingContext.state.refs; + if (rovingContext.state.activeNode && rovingContext.state.nodes.length > 0) { + let nodes = rovingContext.state.nodes; if (!query && !filter !== null) { // If the current selection is not in the recently viewed row then only include the // first recently viewed so that is the target when the user is switching into recently viewed. - const keptRecentlyViewedRef = refIsForRecentlyViewed(rovingContext.state.activeRef) - ? rovingContext.state.activeRef - : refs.find(refIsForRecentlyViewed); + const keptRecentlyViewedRef = nodeIsForRecentlyViewed(rovingContext.state.activeNode) + ? rovingContext.state.activeNode + : nodes.find(nodeIsForRecentlyViewed); // exclude all other recently viewed items from the list so up/down arrows skip them - refs = refs.filter((ref) => ref === keptRecentlyViewedRef || !refIsForRecentlyViewed(ref)); + nodes = nodes.filter((ref) => ref === keptRecentlyViewedRef || !nodeIsForRecentlyViewed(ref)); } - const idx = refs.indexOf(rovingContext.state.activeRef); - ref = findSiblingElement(refs, idx + (accessibilityAction === KeyBindingAction.ArrowUp ? -1 : 1)); + const idx = nodes.indexOf(rovingContext.state.activeNode); + node = findSiblingElement(nodes, idx + (accessibilityAction === KeyBindingAction.ArrowUp ? -1 : 1)); } break; @@ -1164,27 +1164,30 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n if ( !query && !filter !== null && - rovingContext.state.activeRef && - rovingContext.state.refs.length > 0 && - refIsForRecentlyViewed(rovingContext.state.activeRef) + rovingContext.state.activeNode && + rovingContext.state.nodes.length > 0 && + nodeIsForRecentlyViewed(rovingContext.state.activeNode) ) { // we only intercept left/right arrows when the field is empty, and they'd do nothing anyway ev.stopPropagation(); ev.preventDefault(); - const refs = rovingContext.state.refs.filter(refIsForRecentlyViewed); - const idx = refs.indexOf(rovingContext.state.activeRef); - ref = findSiblingElement(refs, idx + (accessibilityAction === KeyBindingAction.ArrowLeft ? -1 : 1)); + const nodes = rovingContext.state.nodes.filter(nodeIsForRecentlyViewed); + const idx = nodes.indexOf(rovingContext.state.activeNode); + node = findSiblingElement( + nodes, + idx + (accessibilityAction === KeyBindingAction.ArrowLeft ? -1 : 1), + ); } break; } - if (ref) { + if (node) { rovingContext.dispatch({ type: Type.SetFocus, - payload: { ref }, + payload: { node }, }); - ref.current?.scrollIntoView({ + node?.scrollIntoView({ block: "nearest", }); } @@ -1204,12 +1207,12 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n case KeyBindingAction.Enter: ev.stopPropagation(); ev.preventDefault(); - rovingContext.state.activeRef?.current?.click(); + rovingContext.state.activeNode?.click(); break; } }; - const activeDescendant = rovingContext.state.activeRef?.current?.id; + const activeDescendant = rovingContext.state.activeNode?.id; return ( <> diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx index 540cf37cbe..26e76bb014 100644 --- a/src/components/views/elements/Field.tsx +++ b/src/components/views/elements/Field.tsx @@ -12,6 +12,9 @@ import React, { RefObject, createRef, ComponentProps, + MutableRefObject, + RefCallback, + Ref, } from "react"; import classNames from "classnames"; import { debounce } from "lodash"; @@ -75,7 +78,7 @@ interface IProps { export interface IInputProps extends IProps, InputHTMLAttributes { // The ref pass through to the input - inputRef?: RefObject; + inputRef?: Ref; // The element to create. Defaults to "input". element: "input"; // The input's value. This is a controlled component, so the value is required. @@ -84,7 +87,7 @@ export interface IInputProps extends IProps, InputHTMLAttributes { // The ref pass through to the select - inputRef?: RefObject; + inputRef?: Ref; // To define options for a select, use element: "select"; // The select's value. This is a controlled component, so the value is required. @@ -93,7 +96,7 @@ interface ISelectProps extends IProps, SelectHTMLAttributes { interface ITextareaProps extends IProps, TextareaHTMLAttributes { // The ref pass through to the textarea - inputRef?: RefObject; + inputRef?: Ref; element: "textarea"; // The textarea's value. This is a controlled component, so the value is required. value: string; @@ -101,7 +104,7 @@ interface ITextareaProps extends IProps, TextareaHTMLAttributes { // The ref pass through to the input - inputRef?: RefObject; + inputRef?: Ref; element: "input"; // The input's value. This is a controlled component, so the value is required. value: string; @@ -118,7 +121,17 @@ interface IState { export default class Field extends React.PureComponent { private readonly id: string; - private readonly _inputRef = createRef(); + private readonly _inputRef: MutableRefObject = + createRef(); + + /** + * When props.inputRef is a callback ref, we will pass callbackRef to the DOM element. + * This is so that other methods here can still access the DOM element via this._inputRef. + */ + private readonly callbackRef: RefCallback = (node) => { + this._inputRef.current = node; + (this.props.inputRef as RefCallback)(node); + }; public static readonly defaultProps = { element: "input", @@ -230,7 +243,12 @@ export default class Field extends React.PureComponent { } private get inputRef(): RefObject { - return this.props.inputRef ?? this._inputRef; + const inputRef = this.props.inputRef; + if (typeof inputRef === "function") { + // This is a callback ref, so return _inputRef which will point to the actual DOM element. + return this._inputRef; + } + return (inputRef ?? this._inputRef) as RefObject; } private onTooltipOpenChange = (open: boolean): void => { @@ -284,7 +302,7 @@ export default class Field extends React.PureComponent { const inputProps_: React.HTMLAttributes & React.ClassAttributes = { ...inputProps, - ref: this.inputRef, + ref: typeof this.props.inputRef === "function" ? this.callbackRef : this.inputRef, }; const fieldInput = React.createElement(this.props.element, inputProps_, children); diff --git a/src/components/views/elements/StyledCheckbox.tsx b/src/components/views/elements/StyledCheckbox.tsx index 388c3f0a3c..83409b8f68 100644 --- a/src/components/views/elements/StyledCheckbox.tsx +++ b/src/components/views/elements/StyledCheckbox.tsx @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ -import React from "react"; +import React, { Ref } from "react"; import { randomString } from "matrix-js-sdk/src/randomstring"; import classnames from "classnames"; @@ -16,7 +16,7 @@ export enum CheckboxStyle { } interface IProps extends React.InputHTMLAttributes { - inputRef?: React.RefObject; + inputRef?: Ref; kind?: CheckboxStyle; id?: string; } diff --git a/src/components/views/elements/StyledRadioButton.tsx b/src/components/views/elements/StyledRadioButton.tsx index 72cf3645b3..01c30c837b 100644 --- a/src/components/views/elements/StyledRadioButton.tsx +++ b/src/components/views/elements/StyledRadioButton.tsx @@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ -import React from "react"; +import React, { Ref } from "react"; import classnames from "classnames"; interface IProps extends React.InputHTMLAttributes { - inputRef?: React.RefObject; + inputRef?: Ref; outlined?: boolean; // If true (default), the children will be contained within a