diff --git a/src/app/content/highlights/components/EditCard.spec.tsx b/src/app/content/highlights/components/EditCard.spec.tsx index bde834dfa5..4726a95479 100644 --- a/src/app/content/highlights/components/EditCard.spec.tsx +++ b/src/app/content/highlights/components/EditCard.spec.tsx @@ -17,6 +17,8 @@ import ColorPicker from './ColorPicker'; import EditCard, { EditCardProps } from './EditCard'; import Note from './Note'; import * as onClickOutsideModule from './utils/onClickOutside'; +import { MAIN_CONTENT_ID } from '../../../context/constants'; +import { renderToDom } from '../../../../test/reactutils'; jest.mock('./ColorPicker', () => (props: any) =>
); jest.mock('./Note', () => (props: any) =>
); @@ -576,6 +578,65 @@ describe('EditCard', () => { mockSpyUser.mockClear(); }); + it('blurs and removes selections when navigating to different elements', () => { + const mockSpyUser = jest.spyOn(selectAuth, 'user') + .mockReturnValue(formatUser(testAccountsUser)); + const onHeightChange = jest.fn(); + + renderToDom( +
+ + text + + +
+ ); + + document?.getElementById(MAIN_CONTENT_ID)?.focus(); + document?.querySelector('a')?.focus(); + document?.getElementById(MAIN_CONTENT_ID)?.focus(); + expect(editCardProps.onBlur).toHaveBeenCalledTimes(3); + mockSpyUser.mockClear(); + jest.resetAllMocks(); + }); + + it('doesn\'t blur when there is data (existing highlight)', () => { + const mockSpyUser = jest.spyOn(selectAuth, 'user') + .mockReturnValue(formatUser(testAccountsUser)); + const onHeightChange = jest.fn(); + const data = { + color: highlightStyles[0].label, + ...highlightData, + }; + + renderToDom( +
+ + text + + +
+ ); + + document?.getElementById(MAIN_CONTENT_ID)?.focus(); + document?.querySelector('a')?.focus(); + document?.getElementById(MAIN_CONTENT_ID)?.focus(); + expect(editCardProps.onBlur).not.toHaveBeenCalled(); + mockSpyUser.mockClear(); + }); + it('doesn\'t blur when clicking outside and editing', () => { highlight.getStyle.mockReturnValue('red'); diff --git a/src/app/content/highlights/components/EditCard.tsx b/src/app/content/highlights/components/EditCard.tsx index 043959d374..b78d21ed25 100644 --- a/src/app/content/highlights/components/EditCard.tsx +++ b/src/app/content/highlights/components/EditCard.tsx @@ -1,6 +1,6 @@ import { Highlight } from '@openstax/highlighter'; import { HighlightColorEnum } from '@openstax/highlighter/dist/api'; -import { HTMLElement, HTMLTextAreaElement } from '@openstax/types/lib.dom'; +import { HTMLElement, HTMLTextAreaElement, FocusEvent } from '@openstax/types/lib.dom'; import React from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; @@ -29,6 +29,7 @@ import { useOnClickOutside } from './utils/onClickOutside'; import scrollHighlightIntoView from './utils/scrollHighlightIntoView'; +import { MAIN_CONTENT_ID } from '../../../context/constants'; export interface EditCardProps { isActive: boolean; @@ -134,24 +135,45 @@ function ActiveEditCard({ const resetAnnotation = React.useCallback(() => { setPendingAnnotation(defaultAnnotation); }, [defaultAnnotation]); - const [editingAnnotation, setEditing] = React.useState( - !!props.data && !!props.data.annotation + const [editingAnnotation, setEditing] = React.useState( + Boolean(props?.data?.annotation) ); const [confirmingDelete, setConfirmingDelete] = React.useState( false ); - const onBlur = props.onBlur; + const {onBlur, hasUnsavedHighlight} = props; const blurIfNotEditing = React.useCallback(() => { - if (!props.hasUnsavedHighlight && !editingAnnotation) { + if (!hasUnsavedHighlight && !editingAnnotation) { onBlur(); } - }, [props.hasUnsavedHighlight, editingAnnotation, onBlur]); + }, [onBlur, hasUnsavedHighlight, editingAnnotation]); + + const deselectRange = React.useCallback( + ({target}: FocusEvent) => { + const targetAsNode = target as HTMLElement; + const mainEl = document?.getElementById(MAIN_CONTENT_ID); + + if (!props.data?.color && mainEl?.contains(targetAsNode)) { + blurIfNotEditing(); + document?.getSelection()?.removeAllRanges(); + } + }, + [blurIfNotEditing, props.data?.color] + ); const elements = [element, ...props.highlight.elements].filter( isElementForOnClickOutside ); + React.useEffect( + () => { + document?.addEventListener('focusin', deselectRange); + return () => document?.removeEventListener('focusin', deselectRange); + }, + [deselectRange] + ); + useOnClickOutside(elements, props.isActive, blurIfNotEditing, { capture: true, });