diff --git a/packages/dnb-eufemia/src/extensions/forms/DataContext/FieldBoundary/FieldBoundaryProvider.tsx b/packages/dnb-eufemia/src/extensions/forms/DataContext/FieldBoundary/FieldBoundaryProvider.tsx index 803b8a47dea..ef44b2739fd 100644 --- a/packages/dnb-eufemia/src/extensions/forms/DataContext/FieldBoundary/FieldBoundaryProvider.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/DataContext/FieldBoundary/FieldBoundaryProvider.tsx @@ -5,22 +5,33 @@ import FieldBoundaryContext, { import DataContext from '../Context' import { Path } from '../../types' -export default function FieldBoundaryProvider({ children }) { +export type Props = { + showErrors?: boolean + onPathError?: (path: Path, error: Error) => void + children: React.ReactNode +} + +export default function FieldBoundaryProvider(props: Props) { + const { showErrors = false, onPathError = null, children } = props const [, forceUpdate] = useReducer(() => ({}), {}) const { showAllErrors, hasVisibleError } = useContext(DataContext) const errorsRef = useRef>({}) - const showBoundaryErrorsRef = useRef(false) + const showBoundaryErrorsRef = useRef(showErrors) const hasError = Object.keys(errorsRef.current || {}).length > 0 const hasSubmitError = showAllErrors && hasError - const setFieldError = useCallback((path: Path, error: Error) => { - if (error) { - errorsRef.current[path] = !!error - } else { - delete errorsRef.current?.[path] - } - }, []) + const setFieldError = useCallback( + (path: Path, error: Error) => { + onPathError?.(path, error) + if (error) { + errorsRef.current[path] = !!error + } else { + delete errorsRef.current?.[path] + } + }, + [onPathError] + ) const setShowBoundaryErrors = useCallback( (showBoundaryErrors: boolean) => { diff --git a/packages/dnb-eufemia/src/extensions/forms/DataContext/FieldBoundary/__tests__/FieldBoundaryProvider.test.tsx b/packages/dnb-eufemia/src/extensions/forms/DataContext/FieldBoundary/__tests__/FieldBoundaryProvider.test.tsx index 7055e26218b..b36c4b58feb 100644 --- a/packages/dnb-eufemia/src/extensions/forms/DataContext/FieldBoundary/__tests__/FieldBoundaryProvider.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/DataContext/FieldBoundary/__tests__/FieldBoundaryProvider.test.tsx @@ -124,6 +124,27 @@ describe('FieldBoundaryProvider', () => { }) }) + it('should set showBoundaryErrorsRef to true when showErrors is true', async () => { + const contextRef: React.MutableRefObject = + React.createRef() + + const ContextConsumer = () => { + contextRef.current = useContext(FieldBoundaryContext) + return null + } + + render( + + + + + + + ) + + expect(contextRef.current.showBoundaryErrors).toBe(true) + }) + it('should set error in boundary context', async () => { const contextRef: React.MutableRefObject = React.createRef() diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Section/EditContainer/EditContainer.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Section/EditContainer/EditContainer.tsx index 22f3178c638..8aa6aa92e2f 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Section/EditContainer/EditContainer.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Section/EditContainer/EditContainer.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react' +import React, { useCallback, useContext, useMemo } from 'react' import classnames from 'classnames' import { convertJsxToString } from '../../../../../shared/component-helper' import { Flex } from '../../../../../components' @@ -10,6 +10,9 @@ import SectionContainer, { SectionContainerProps, } from '../containers/SectionContainer' import Toolbar from '../containers/Toolbar' +import SectionContext from '../SectionContext' +import { Path } from '../../../types' +import SectionContainerContext from '../containers/SectionContainerContext' export type Props = { title?: React.ReactNode @@ -20,9 +23,24 @@ export type AllProps = Props & SectionContainerProps & FlexContainerProps function EditContainer(props: AllProps) { const { children, className, title, ...restProps } = props || {} const ariaLabel = useMemo(() => convertJsxToString(title), [title]) + const { switchContainerMode } = useContext(SectionContainerContext) || {} + const sectionContext = useContext(SectionContext) + const { validateFieldsInitially } = sectionContext?.props || {} + + const onPathError = useCallback( + (path: Path, error: Error) => { + if (validateFieldsInitially && error instanceof Error) { + switchContainerMode?.('edit') + } + }, + [switchContainerMode, validateFieldsInitially] + ) return ( - + { expect(input).toHaveValue('bar') }) + it('when "validateFieldsInitially" is set to true, fields should show their errors and the mode should be "edit"', async () => { + let containerMode = null + + const ContextConsumer = () => { + const context = React.useContext(SectionContainerContext) + containerMode = context.containerMode + + return null + } + + render( + + + + + + content + + + + ) + + expect(containerMode).toBe('edit') + expect(document.querySelector('.dnb-form-status')).toBeInTheDocument() + expect(document.querySelector('.dnb-form-status')).toHaveTextContent( + nb.Field.errorRequired + ) + + const input = document.querySelector('input') + expect(input).toHaveValue('') + + await userEvent.type(input, 'something') + + expect( + document.querySelector('.dnb-form-status') + ).not.toBeInTheDocument() + + const [doneButton] = Array.from(document.querySelectorAll('button')) + await userEvent.click(doneButton) + + expect(containerMode).toBe('view') + }) + it('should reset entered data on Cancel press when path is set', async () => { let containerMode = null diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Section/Section.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Section/Section.tsx index 2a132dd9fd0..92e9244f175 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Section/Section.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Section/Section.tsx @@ -42,6 +42,13 @@ export type SectionProps = { */ containerMode?: ContainerMode + /** + * When set to `true`, the mode will initially be "edit" if fields contain errors. + * Fields will automatically get `validateInitially` and show their error messages. + * Defaults to `false`. + */ + validateFieldsInitially?: boolean + /** * Only for internal use and undocumented for now. * Prioritize error techniques for the section. diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Section/SectionDocs.ts b/packages/dnb-eufemia/src/extensions/forms/Form/Section/SectionDocs.ts index deb4e1129cd..aca839bfa0f 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Section/SectionDocs.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Section/SectionDocs.ts @@ -36,6 +36,11 @@ export const SectionProperties: PropertiesTableProps = { type: 'string', status: 'optional', }, + validateFieldsInitially: { + doc: 'When set to `true`, the mode will initially be "edit" if fields contain errors. Fields will automatically get `validateInitially` and show their error messages. Defaults to `false`.', + type: 'boolean', + status: 'optional', + }, children: { doc: 'All the fields and values inside the section.', type: 'React.Node', diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Section/containers/SectionContainer.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Section/containers/SectionContainer.tsx index 98955ee61a4..6e7d244affc 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Section/containers/SectionContainer.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Section/containers/SectionContainer.tsx @@ -102,11 +102,21 @@ function SectionContainer(props: Props & FlexContainerProps) { [onAnimationEnd, switchContainerMode] ) + const preventAnimationRef = useRef(true) + useEffect(() => { + setTimeout(() => { + preventAnimationRef.current = false + forceUpdate() + }, 1000) // Initially, we don't want to animate + }, []) + return ( ( // something else that should lead to showing the user all errors. revealError() } - } else if (showBoundaryErrors === false) { + } else if (showBoundaryErrors === false && !validateInitially) { hideError() } - }, [hideError, revealError, showAllErrors, showBoundaryErrors]) + }, [ + hideError, + revealError, + showAllErrors, + showBoundaryErrors, + validateInitially, + ]) useEffect(() => { if (