diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx index b78acd0001b..ea8ff99beaa 100644 --- a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx @@ -46,11 +46,13 @@ const codeList: CodeList = [ helpText: 'Test 3 help text', }, ]; +const onBlurAny = jest.fn(); const onChange = jest.fn(); const onInvalid = jest.fn(); const defaultProps: StudioCodeListEditorProps = { codeList, texts, + onBlurAny, onChange, onInvalid, }; @@ -119,8 +121,7 @@ describe('StudioCodeListEditor', () => { const labelInput = screen.getByRole('textbox', { name: texts.itemLabel(1) }); const newValue = 'new text'; await user.type(labelInput, newValue); - await user.tab(); - expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledTimes(newValue.length); expect(onChange).toHaveBeenLastCalledWith([ { ...codeList[0], label: newValue }, codeList[1], @@ -134,8 +135,7 @@ describe('StudioCodeListEditor', () => { const valueInput = screen.getByRole('textbox', { name: texts.itemValue(1) }); const newValue = 'new text'; await user.type(valueInput, newValue); - await user.tab(); - expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledTimes(newValue.length); expect(onChange).toHaveBeenLastCalledWith([ { ...codeList[0], value: newValue }, codeList[1], @@ -149,8 +149,7 @@ describe('StudioCodeListEditor', () => { const descriptionInput = screen.getByRole('textbox', { name: texts.itemDescription(1) }); const newValue = 'new text'; await user.type(descriptionInput, newValue); - await user.tab(); - expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledTimes(newValue.length); expect(onChange).toHaveBeenLastCalledWith([ { ...codeList[0], description: newValue }, codeList[1], @@ -164,8 +163,7 @@ describe('StudioCodeListEditor', () => { const helpTextInput = screen.getByRole('textbox', { name: texts.itemHelpText(1) }); const newValue = 'new text'; await user.type(helpTextInput, newValue); - await user.tab(); - expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledTimes(newValue.length); expect(onChange).toHaveBeenLastCalledWith([ { ...codeList[0], helpText: newValue }, codeList[1], @@ -196,6 +194,21 @@ describe('StudioCodeListEditor', () => { ]); }); + it('Calls the onBlurAny callback with the current code list when an item in the table is blurred', async () => { + const user = userEvent.setup(); + renderCodeListEditor(); + const valueInput = screen.getByRole('textbox', { name: texts.itemValue(1) }); + const newValue = 'new text'; + await user.type(valueInput, newValue); + await user.tab(); + expect(onBlurAny).toHaveBeenCalledTimes(1); + expect(onBlurAny).toHaveBeenLastCalledWith([ + { ...codeList[0], value: newValue }, + codeList[1], + codeList[2], + ]); + }); + it('Updates itself when the user changes something', async () => { const user = userEvent.setup(); renderCodeListEditor(); @@ -267,8 +280,7 @@ describe('StudioCodeListEditor', () => { const validValueInput = screen.getByRole('textbox', { name: texts.itemValue(3) }); const newValue = 'new value'; await user.type(validValueInput, newValue); - await user.tab(); - expect(onInvalid).toHaveBeenCalledTimes(1); + expect(onInvalid).toHaveBeenCalledTimes(newValue.length); }); it('Does not trigger onInvalid if an invalid code list is changed to a valid state', async () => { diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.tsx b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.tsx index a4cff5e76da..d1da350578a 100644 --- a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.tsx +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.tsx @@ -23,23 +23,32 @@ import { areThereCodeListErrors, findCodeListErrors, isCodeListValid } from './v import type { ValueErrorMap } from './types/ValueErrorMap'; import { StudioFieldset } from '../StudioFieldset'; import { StudioErrorMessage } from '../StudioErrorMessage'; +import type { Override } from '../../types/Override'; +import type { StudioInputTableProps } from '../StudioInputTable/StudioInputTable'; export type StudioCodeListEditorProps = { codeList: CodeList; - onChange: (codeList: CodeList) => void; + onBlurAny?: (codeList: CodeList) => void; + onChange?: (codeList: CodeList) => void; onInvalid?: () => void; texts: CodeListEditorTexts; }; export function StudioCodeListEditor({ codeList, + onBlurAny, onChange, onInvalid, texts, }: StudioCodeListEditorProps): ReactElement { return ( - + ); } @@ -48,6 +57,7 @@ type StatefulCodeListEditorProps = Omit; function StatefulCodeListEditor({ codeList: defaultCodeList, + onBlurAny, onChange, onInvalid, }: StatefulCodeListEditorProps): ReactElement { @@ -60,18 +70,32 @@ function StatefulCodeListEditor({ const handleChange = useCallback( (newCodeList: CodeList) => { setCodeList(newCodeList); - isCodeListValid(newCodeList) ? onChange(newCodeList) : onInvalid?.(); + isCodeListValid(newCodeList) ? onChange?.(newCodeList) : onInvalid?.(); }, [onChange, onInvalid], ); - return ; + const handleBlurAny = useCallback(() => { + onBlurAny?.(codeList); + }, [onBlurAny, codeList]); + + return ( + + ); } -type InternalCodeListEditorProps = Omit; +type InternalCodeListEditorProps = Override< + Pick, + Omit +>; function ControlledCodeListEditor({ codeList, + onBlurAny, onChange, }: InternalCodeListEditorProps): ReactElement { const { texts } = useStudioCodeListEditorContext(); @@ -86,12 +110,18 @@ function ControlledCodeListEditor({ return ( - + ); } + type InternalCodeListEditorWithErrorsProps = InternalCodeListEditorProps & ErrorsProps; function CodeListTable(props: InternalCodeListEditorWithErrorsProps): ReactElement { @@ -107,11 +137,14 @@ function EmptyCodeListTable(): ReactElement { return {texts.emptyCodeList}; } -function CodeListTableWithContent(props: InternalCodeListEditorWithErrorsProps): ReactElement { +function CodeListTableWithContent({ + onBlurAny, + ...rest +}: InternalCodeListEditorWithErrorsProps): ReactElement { return ( - + - + ); } @@ -145,7 +178,7 @@ function TableBody({ [codeList, onChange], ); - const handleBlur = useCallback( + const handleChange = useCallback( (index: number, newItem: CodeListItem) => { const updatedCodeList = changeCodeListItem(codeList, index, newItem); onChange(updatedCodeList); @@ -161,7 +194,7 @@ function TableBody({ item={item} key={index} number={index + 1} - onBlur={(newItem) => handleBlur(index, newItem)} + onChange={(newItem) => handleChange(index, newItem)} onDeleteButtonClick={() => handleDeleteButtonClick(index)} /> ))} diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/StudioCodeListEditorRow.tsx b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/StudioCodeListEditorRow.tsx index 161b6d04264..501f9f4fcac 100644 --- a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/StudioCodeListEditorRow.tsx +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/StudioCodeListEditorRow.tsx @@ -13,7 +13,7 @@ type StudioCodeListEditorRowProps = { error: ValueError | null; item: CodeListItem; number: number; - onBlur: (newItem: CodeListItem) => void; + onChange: (newItem: CodeListItem) => void; onDeleteButtonClick: () => void; }; @@ -21,7 +21,7 @@ export function StudioCodeListEditorRow({ error, item, number, - onBlur, + onChange, onDeleteButtonClick, }: StudioCodeListEditorRowProps) { const { texts } = useStudioCodeListEditorContext(); @@ -29,33 +29,33 @@ export function StudioCodeListEditorRow({ const handleLabelChange = useCallback( (label: string) => { const updatedItem = changeLabel(item, label); - onBlur(updatedItem); + onChange(updatedItem); }, - [item, onBlur], + [item, onChange], ); const handleDescriptionChange = useCallback( (description: string) => { const updatedItem = changeDescription(item, description); - onBlur(updatedItem); + onChange(updatedItem); }, - [item, onBlur], + [item, onChange], ); const handleValueChange = useCallback( (value: string) => { const updatedItem = changeValue(item, value); - onBlur(updatedItem); + onChange(updatedItem); }, - [item, onBlur], + [item, onChange], ); const handleHelpTextChange = useCallback( (helpText: string) => { const updatedItem = changeHelpText(item, helpText); - onBlur(updatedItem); + onChange(updatedItem); }, - [item, onBlur], + [item, onChange], ); return ( @@ -64,22 +64,22 @@ export function StudioCodeListEditorRow({ autoComplete='off' error={error && texts.valueErrors[error]} label={texts.itemValue(number)} - onBlur={handleValueChange} + onChange={handleValueChange} value={item.value} /> @@ -90,23 +90,23 @@ export function StudioCodeListEditorRow({ type TextfieldCellProps = { error?: string; label: string; - onBlur: (newString: string) => void; + onChange: (newString: string) => void; value: CodeListItemValue; autoComplete?: HTMLInputAutoCompleteAttribute; }; -function TextfieldCell({ error, label, value, onBlur, autoComplete }: TextfieldCellProps) { +function TextfieldCell({ error, label, value, onChange, autoComplete }: TextfieldCellProps) { const ref = useRef(null); useEffect((): void => { ref.current?.setCustomValidity(error || ''); }, [error]); - const handleBlur = useCallback( + const handleChange = useCallback( (event: React.ChangeEvent): void => { - onBlur(event.target.value); + onChange(event.target.value); }, - [onBlur], + [onChange], ); const handleFocus = useCallback((event: FocusEvent): void => { @@ -118,7 +118,7 @@ function TextfieldCell({ error, label, value, onBlur, autoComplete }: TextfieldC aria-label={label} autoComplete={autoComplete} className={classes.textfieldCell} - onBlur={handleBlur} + onChange={handleChange} onFocus={handleFocus} ref={ref} value={(value as string) ?? ''} diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeLists/CodeLists.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeLists/CodeLists.tsx index b2a4c57cfb4..c2553e24db5 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeLists/CodeLists.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeLists/CodeLists.tsx @@ -24,7 +24,7 @@ type CodeListProps = { function CodeList({ codeList, onUpdateCodeList }: CodeListProps) { const editorTexts: CodeListEditorTexts = useOptionListEditorTexts(); - const handleUpdateCodeList = (updatedCodeList: StudioComponentsCodeList): void => { + const handleBlurAny = (updatedCodeList: StudioComponentsCodeList): void => { const updatedCodeListWithMetadata = updateCodeListWithMetadata(codeList, updatedCodeList); onUpdateCodeList(updatedCodeListWithMetadata); }; @@ -36,7 +36,7 @@ function CodeList({ codeList, onUpdateCodeList }: CodeListProps) { diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListEditor.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListEditor.tsx index 2f581ec6407..767d9861a24 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListEditor.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListEditor.tsx @@ -90,7 +90,7 @@ function EditLibraryOptionListEditorModal({ const optionListHasChanged = (options: Option[]): boolean => JSON.stringify(options) !== JSON.stringify(localOptionList); - const handleOptionsChange = (options: Option[]) => { + const handleBlurAny = (options: Option[]) => { if (optionListHasChanged(options)) { updateOptionList({ optionListId: optionsId, optionsList: options }); setLocalOptionList(options); @@ -126,7 +126,7 @@ function EditLibraryOptionListEditorModal({ > @@ -147,7 +147,7 @@ function EditManualOptionListEditorModal({ const modalRef = useRef(null); const editorTexts = useOptionListEditorTexts(); - const handleOptionsChange = (options: Option[]) => { + const handleBlurAny = (options: Option[]) => { if (component.optionsId) { delete component.optionsId; } @@ -175,7 +175,7 @@ function EditManualOptionListEditorModal({ >