/**
* WordPress dependencies
*/
import {
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { Stack } from '@wordpress/ui';
/**
* Internal dependencies
*/
import type {
NormalizedForm,
NormalizedDetailsLayout,
FieldLayoutProps,
} from '../../../types';
import DataFormContext from '../../dataform-context';
import { DataFormLayout } from '../data-form-layout';
import { DEFAULT_LAYOUT } from '../normalize-form';
import useReportValidity from '../../../hooks/use-report-validity';
import ValidationBadge from '../validation-badge';
export default function FormDetailsField< Item >( {
data,
field,
onChange,
validity,
}: FieldLayoutProps< Item > ) {
const { fields } = useContext( DataFormContext );
const detailsRef = useRef< HTMLDetailsElement >( null );
const contentRef = useRef< HTMLDivElement >( null );
const [ touched, setTouched ] = useState( false );
const [ isOpen, setIsOpen ] = useState( false );
const form: NormalizedForm = useMemo(
() => ( {
layout: DEFAULT_LAYOUT,
fields: field.children ?? [],
} ),
[ field ]
);
// Track the open/close state of the native details element.
useEffect( () => {
const details = detailsRef.current;
if ( ! details ) {
return;
}
const handleToggle = () => {
const nowOpen = details.open;
// Mark as touched when collapsing (going from open to closed).
if ( ! nowOpen ) {
setTouched( true );
}
setIsOpen( nowOpen );
};
details.addEventListener( 'toggle', handleToggle );
return () => {
details.removeEventListener( 'toggle', handleToggle );
};
}, [] );
// When expanded after being touched, trigger reportValidity to show
// field-level errors.
useReportValidity( contentRef, isOpen && touched );
// Mark as touched when any field inside is blurred.
const handleBlur = useCallback( () => {
setTouched( true );
}, [] );
if ( ! field.children ) {
return null;
}
// Find the summary field definition if specified
const summaryFieldId =
( field.layout as NormalizedDetailsLayout ).summary ?? '';
const summaryField = summaryFieldId
? fields.find( ( fieldDef ) => fieldDef.id === summaryFieldId )
: undefined;
// Render the summary content
let summaryContent;
if ( summaryField && summaryField.render ) {
// Use the field's render function to display the current value
summaryContent = (
);
} else {
// Fall back to the label
summaryContent = field.label || __( 'More details' );
}
return (
{ summaryContent }
{ touched && }
);
}