diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Iterate/PushContainer/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Iterate/PushContainer/properties.mdx index f1664abf510..516cfa1f8b9 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Iterate/PushContainer/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Iterate/PushContainer/properties.mdx @@ -5,12 +5,19 @@ hideInMenu: true import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' -import { PushContainerProperties } from '@dnb/eufemia/src/extensions/forms/Iterate/PushContainer/PushContainerDocs' +import { + PushContainerProperties, + PushContainerEvents, +} from '@dnb/eufemia/src/extensions/forms/Iterate/PushContainer/PushContainerDocs' ## Properties +## Events + + + ## Translations diff --git a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx index 4eef3be8333..325927e8339 100644 --- a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx @@ -303,6 +303,7 @@ export default function Provider( // eslint-disable-next-line react-hooks/exhaustive-deps -- Avoid triggering code that should only run initially }, []) const internalDataRef = useRef(initialData) + const isEmptyDataRef = useRef(false) // - Validator const ajvValidatorRef = useRef() @@ -658,8 +659,10 @@ export default function Provider( ) { cacheRef.current.shared = sharedData.data - if (internalDataRef.current === clearedData) { - return clearedData as Data + if (isEmptyDataRef.current) { + return ( + Array.isArray(internalDataRef.current) ? [] : clearedData + ) as Data } return { @@ -682,10 +685,12 @@ export default function Provider( ? pointer.get(internalData, props.path) : internalData - const isEmptyDataRef = useRef(false) const clearData = useCallback(() => { isEmptyDataRef.current = true - internalDataRef.current = (emptyData ?? clearedData) as Data + internalDataRef.current = ((typeof emptyData === 'function' + ? emptyData(internalDataRef.current) + : emptyData) ?? + (Array.isArray(internalDataRef.current) ? [] : clearedData)) as Data if (id) { setSharedData?.(internalDataRef.current) @@ -1002,7 +1007,6 @@ export default function Provider( if (isolate) { submitResult = await onCommit?.(internalDataRef.current, { clearData, - setData, }) } else { submitResult = await onSubmit() @@ -1075,7 +1079,6 @@ export default function Provider( setFormState, setShowAllErrors, setSubmitState, - setData, ] ) @@ -1490,9 +1493,3 @@ function useFormStatusBuffer(props: FormStatusBufferProps) { } export const clearedData = Object.freeze({}) -export const getClearedData = (mergeData?: Record) => { - if (mergeData) { - return Object.assign(clearedData, mergeData) - } - return clearedData -} diff --git a/packages/dnb-eufemia/src/extensions/forms/Iterate/PushContainer/PushContainer.tsx b/packages/dnb-eufemia/src/extensions/forms/Iterate/PushContainer/PushContainer.tsx index b11ea93dde0..a9050f987c8 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Iterate/PushContainer/PushContainer.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Iterate/PushContainer/PushContainer.tsx @@ -8,7 +8,7 @@ import EditContainer, { CancelButton, DoneButton } from '../EditContainer' import IterateArray, { ContainerMode } from '../Array' import OpenButton from './OpenButton' import { Flex, HeightAnimation } from '../../../../components' -import { Path } from '../../types' +import { OnCommit, Path } from '../../types' import { SpacingProps } from '../../../../shared/types' import { useArrayLimit, useSwitchContainerMode } from '../hooks' import Toolbar from '../Toolbar' @@ -58,6 +58,11 @@ export type Props = { */ toolbar?: React.ReactNode + /** + * Will be called when the user clicks on the "Done" button. + */ + onCommit?: OnCommit + /** * The container contents. */ @@ -76,6 +81,7 @@ function PushContainer(props: AllProps) { children, openButton, showOpenButtonWhen, + onCommit, ...rest } = props @@ -120,16 +126,31 @@ function PushContainer(props: AllProps) { } }, [dataProp, defaultDataProp, isolatedData]) + const emptyData = useCallback( + (data: { pushContainerItems: unknown[] }) => { + const firstItem = data.pushContainerItems?.[0] + if (firstItem === null || typeof firstItem !== 'object') { + return { + ...isolatedData, + pushContainerItems: [null], + } + } + return defaultData + }, + [defaultData, isolatedData] + ) + return ( { return moveValueToPath(path, [...entries, ...pushContainerItems]) }} - onCommit={(data, { clearData, setData, preventCommit }) => { + onCommit={(data, options) => { + const { clearData, preventCommit } = options if (hasReachedLimit) { preventCommit() setShowStatus(true) @@ -137,13 +158,8 @@ function PushContainer(props: AllProps) { setNextContainerMode('view') switchContainerModeRef.current?.('view') clearData() - if (isolatedData) { - setData({ - ...isolatedData, - pushContainerItems: [clearedData], - }) - } } + onCommit?.(data, options) }} > diff --git a/packages/dnb-eufemia/src/extensions/forms/Iterate/PushContainer/PushContainerDocs.ts b/packages/dnb-eufemia/src/extensions/forms/Iterate/PushContainer/PushContainerDocs.ts index e8f56900dd5..54ac7f6b155 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Iterate/PushContainer/PushContainerDocs.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Iterate/PushContainer/PushContainerDocs.ts @@ -1,4 +1,5 @@ import { PropertiesTableProps } from '../../../../shared/types' +import { IsolationEvents } from '../../Form/Isolation/IsolationDocs' export const PushContainerProperties: PropertiesTableProps = { path: { @@ -58,4 +59,6 @@ export const PushContainerProperties: PropertiesTableProps = { }, } -export const PushContainerEvents: PropertiesTableProps = {} +export const PushContainerEvents: PropertiesTableProps = { + onCommit: IsolationEvents.onCommit, +} diff --git a/packages/dnb-eufemia/src/extensions/forms/Iterate/PushContainer/__tests__/PushContainer.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Iterate/PushContainer/__tests__/PushContainer.test.tsx index 40f2d19207f..9a39718fdb1 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Iterate/PushContainer/__tests__/PushContainer.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Iterate/PushContainer/__tests__/PushContainer.test.tsx @@ -1,10 +1,11 @@ -import React from 'react' +import React, { useContext } from 'react' import { render, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { Field, Form, Iterate } from '../../..' -import nbNO from '../../../constants/locales/nb-NO' import { Div } from '../../../../../elements' +import DataContext from '../../../DataContext/Context' +import nbNO from '../../../constants/locales/nb-NO' const nb = nbNO['nb-NO'] describe('PushContainer', () => { @@ -548,7 +549,7 @@ describe('PushContainer', () => { ) }) - it('should keep the defaultValue after clearing', async () => { + it('should not show error message after clearing', async () => { const onChange = jest.fn() render( @@ -643,6 +644,68 @@ describe('PushContainer', () => { expect(document.querySelector('.dnb-form-status')).toBeNull() }) }) + + it('should keep the defaultValue after clearing', async () => { + const onChange = jest.fn() + const onCommit = jest.fn() + + let internalContext = null + const CollectInternalData = () => { + internalContext = useContext(DataContext) + return null + } + + render( + + + + + + + ) + + expect(internalContext).toMatchObject({ + data: { + pushContainerItems: ['default value'], + }, + }) + + const input = document.querySelector('input') + + await userEvent.type(input, ' changed') + + const button = document.querySelector('button') + + await userEvent.click(button) + expect(internalContext.internalDataRef.current).toEqual({ + pushContainerItems: ['default value'], + }) + expect(onChange).toHaveBeenCalledTimes(1) + expect(onChange).toHaveBeenLastCalledWith( + ['default value changed'], + expect.anything() + ) + expect(onCommit).toHaveBeenCalledTimes(1) + expect(onCommit).toHaveBeenLastCalledWith( + ['default value changed'], + expect.anything() + ) + + await userEvent.click(button) + expect(internalContext.internalDataRef.current).toEqual({ + pushContainerItems: ['default value'], + }) + expect(onChange).toHaveBeenCalledTimes(2) + expect(onChange).toHaveBeenLastCalledWith( + ['default value changed', 'default value'], + expect.anything() + ) + expect(onCommit).toHaveBeenCalledTimes(2) + expect(onCommit).toHaveBeenLastCalledWith( + ['default value changed', 'default value'], + expect.anything() + ) + }) }) it('should support initial data as a string', async () => { diff --git a/packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts b/packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts index fb02d0138ef..973ebea13cd 100644 --- a/packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts +++ b/packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts @@ -1823,11 +1823,8 @@ export default function useFieldProps( useEffect(() => { if (isEmptyData()) { - // Fill the data context with the default value after it has been cleared - requestAnimationFrame(() => { - setContextData() - validateValue() - }) + setContextData() + validateValue() } }, [isEmptyData, setContextData, validateValue]) diff --git a/packages/dnb-eufemia/src/extensions/forms/types.ts b/packages/dnb-eufemia/src/extensions/forms/types.ts index c84c6a1606d..6ec8f27a635 100644 --- a/packages/dnb-eufemia/src/extensions/forms/types.ts +++ b/packages/dnb-eufemia/src/extensions/forms/types.ts @@ -602,7 +602,6 @@ export type OnCommit = ( preventCommit, }: { clearData: () => void - setData?: (data: Data) => void preventCommit?: () => void } ) =>