UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

433 lines (432 loc) 16.9 kB
"use client"; import _extends from "@babel/runtime-corejs3/helpers/esm/extends"; var _br; import _pushInstanceProperty from "core-js-pure/stable/instance/push.js"; import React, { useMemo, useContext, useCallback, useRef, useReducer, useEffect } from 'react'; import classnames from 'classnames'; import FieldBlockContext from "./FieldBlockContext.js"; import DataContext from "../DataContext/Context.js"; import { Space, FormLabel, FormStatus } from "../../../components/index.js"; import { Ul, Li } from "../../../elements/index.js"; import { convertJsxToString, findElementInChildren } from "../../../shared/component-helper.js"; import useId from "../../../shared/helpers/useId.js"; import HelpButtonInline, { HelpButtonInlineContent } from "../../../components/help-button/HelpButtonInline.js"; import SubmitIndicator from "../Form/SubmitIndicator/SubmitIndicator.js"; import { createSharedState } from "../../../shared/helpers/useSharedState.js"; import useTranslation from "../hooks/useTranslation.js"; import { FormError } from "../utils/index.js"; import { useIterateItemNo } from "../Iterate/ItemNo/useIterateItemNo.js"; export const states = ['error', 'info', 'warning']; function FieldBlock(props) { var _props$id; const dataContext = useContext(DataContext); const fieldBlockContext = useContext(FieldBlockContext); const nestedFieldBlockContext = !(fieldBlockContext !== null && fieldBlockContext !== void 0 && fieldBlockContext.disableStatusSummary) ? fieldBlockContext : null; const id = useId((_props$id = props.id) !== null && _props$id !== void 0 ? _props$id : props.forId); const sharedData = createSharedState('field-block-props-' + id); const { className, forId, layout = 'vertical', layoutOptions, composition, label: labelProp, labelDescription, labelDescriptionInline, labelSuffix, labelSrOnly, labelSize, labelHeight, help, hideHelpButton, statusPosition, asFieldset, required, info, warning, error, disableStatusSummary, fieldState, disabled, width, contentWidth, align, contentClassName, fieldsetRole, children, ...rest } = Object.assign({}, sharedData.data, props); const hasCustomWidth = /\d(rem)$/.test(String(width)); const hasCustomContentWidth = /\d(rem)$/.test(String(contentWidth)); const infoRef = useRef(); const warningRef = useRef(); const errorRef = useRef(); const blockId = useId(props.id); const [salt, forceUpdate] = useReducer(() => ({}), {}); const mountedFieldsRef = useRef(new Map()); const fieldStateRef = useRef(null); const stateRecordRef = useRef({}); const fieldStateIdsRef = useRef(null); const contentsRef = useRef(null); const hasInitiallyErrorPropRef = useRef(Boolean(error)); const label = useIterateItemNo({ label: labelProp, labelSuffix, required }); const setInternalRecord = useCallback(props => { const { stateId, identifier, type } = props; if (!stateRecordRef.current[identifier]) { stateRecordRef.current[identifier] = []; } fieldStateIdsRef.current = { error: null, warning: null, info: null }; const existingIndex = stateRecordRef.current[identifier].findIndex(item => { return item.stateId === stateId && item.type === type; }); if (existingIndex > -1) { stateRecordRef.current[identifier][existingIndex] = { ...stateRecordRef.current[identifier][existingIndex], ...props }; } else { var _context; _pushInstanceProperty(_context = stateRecordRef.current[identifier]).call(_context, props); } }, []); const setBlockRecordNested = nestedFieldBlockContext === null || nestedFieldBlockContext === void 0 ? void 0 : nestedFieldBlockContext.setBlockRecord; const setBlockRecord = useCallback(props => { if (setBlockRecordNested) { setBlockRecordNested(props); return; } setInternalRecord(props); forceUpdate(); }, [setBlockRecordNested, setInternalRecord]); const setFieldState = useCallback((identifier, fieldState) => { if (fieldState !== fieldStateRef.current) { fieldStateRef.current = fieldState; forceUpdate(); } }, []); const showFieldError = useCallback((identifier, show) => { if (nestedFieldBlockContext) { nestedFieldBlockContext.showFieldError(identifier, show); return; } if (stateRecordRef.current[identifier]) { stateRecordRef.current[identifier] = stateRecordRef.current[identifier].map(item => { if (item.showInitially) { return item; } return { ...item, show }; }); forceUpdate(); } }, [nestedFieldBlockContext]); const statusContent = useMemo(() => { if (typeof error !== 'undefined' || errorRef.current && !error) { errorRef.current = error; setInternalRecord({ identifier: blockId, showInitially: hasInitiallyErrorPropRef.current, type: 'error', content: error }); } if (typeof warning !== 'undefined' || warningRef.current !== warning) { warningRef.current = warning; setInternalRecord({ identifier: blockId, showInitially: true, type: 'warning', content: warning }); } if (typeof info !== 'undefined' || infoRef.current !== info) { infoRef.current = info; setInternalRecord({ identifier: blockId, showInitially: true, type: 'info', content: info }); } const statesWithMessages = Object.entries(stateRecordRef.current).flatMap(([identifier, states]) => states.map(props => { return { identifier, ...props }; })).reduce((acc, cur) => { const existing = acc.find(item => { return item.type === cur.type; }); const messages = getMessagesFromError(cur).map(message => { return { ...cur, message }; }); if (existing) { var _context2; _pushInstanceProperty(_context2 = existing.messages).call(_context2, ...messages); } else { _pushInstanceProperty(acc).call(acc, { ...cur, content: undefined, messages }); } return acc; }, []); return states.reduce((acc, type) => { const id = `${props.id || forId || blockId}-form-status--${type}`; acc[type] = { id, label, state: type === 'warning' ? 'warn' : type, width_element: contentsRef, no_animation: process.env.NODE_ENV === 'test' ? true : typeof globalThis !== 'undefined' ? globalThis.IS_TEST === true : false }; const found = statesWithMessages.find(item => { return item.type === type; }); if (found !== null && found !== void 0 && found.messages) { const messages = found.messages.map(msg => { if (msg.type === 'error') { if (!msg.showInitially && !msg.show) { msg.message = null; } } return msg; }).filter(({ message }) => message).reduce((acc, msg, i, arr) => { const existingIndex = arr.findIndex(item => { return convertJsxToString(item.message) === convertJsxToString(msg.message); }); if (existingIndex === i) { _pushInstanceProperty(acc).call(acc, msg); } return acc; }, []); if (messages.length > 0) { acc[type] = { ...acc[type], children: React.createElement(CombineMessages, { type: type, messages: messages }) }; fieldStateIdsRef.current[type] = id; } else { fieldStateIdsRef.current[type] = undefined; } } return acc; }, salt); }, [error, warning, info, salt, setInternalRecord, blockId, props.id, forId, label]); useEffect(() => { if (!nestedFieldBlockContext) { showFieldError(blockId, Boolean(error)); } }, [error, blockId, showFieldError, nestedFieldBlockContext]); useEffect(() => () => { mountedFieldsRef.current = new Map(); stateRecordRef.current = {}; }, []); const mainClasses = classnames('dnb-forms-field-block', className, composition && `dnb-forms-field-block__composition dnb-forms-field-block__composition--${composition === true ? 'horizontal' : composition}`, width && `dnb-forms-field-block--width-${hasCustomWidth ? 'custom' : width}`, contentWidth && `dnb-forms-field-block--content-width-${hasCustomContentWidth ? 'custom' : contentWidth}`, labelHeight && `dnb-forms-field-block--label-height-${labelHeight}`, statusPosition === 'above' && 'dnb-forms-field-block--status-position-above'); const gridClasses = `dnb-forms-field-block__grid dnb-forms-field-block--layout-${layout}`; const enableFieldset = useEnableFieldset({ label, asFieldset, children, nestedFieldBlockContext }); const labelProps = { id: `${id}-label`, className: 'dnb-forms-field-block__label', element: enableFieldset ? 'legend' : 'label', forId: enableFieldset ? undefined : forId, srOnly: labelSrOnly, space: 0, size: labelSize, disabled }; const mainStyle = useMemo(() => { var _lO$minWidth, _lO$maxWidth; const style = {}; if (hasCustomWidth) { style['--dnb-forms-field-block-width'] = width; } if (hasCustomContentWidth) { style['--dnb-forms-field-block-content-width'] = contentWidth; } const lO = layoutOptions || {}; const min = getFieldWidth((_lO$minWidth = lO.minWidth) !== null && _lO$minWidth !== void 0 ? _lO$minWidth : lO.width); const max = getFieldWidth((_lO$maxWidth = lO.maxWidth) !== null && _lO$maxWidth !== void 0 ? _lO$maxWidth : lO.width); if (typeof min === 'string') { style['--dnb-forms-field-block-layout-width-min'] = min; } if (typeof max === 'string') { style['--dnb-forms-field-block-layout-width-max'] = max; } return style; }, [contentWidth, hasCustomContentWidth, hasCustomWidth, layoutOptions, width]); if (dataContext !== null && dataContext !== void 0 && dataContext.prerenderFieldProps) { return null; } const hasLabelDescription = isFragment(labelDescription) ? fragmentHasChildren(labelDescription) && !fragmentHasOnlyUndefinedChildren(labelDescription) : labelDescription; const hasHelp = (help === null || help === void 0 ? void 0 : help.title) || (help === null || help === void 0 ? void 0 : help.content); const hasOnlyLabelDescription = !label && hasLabelDescription; return React.createElement(FieldBlockContext.Provider, { value: { setBlockRecord, setFieldState, showFieldError, hasErrorProp: Boolean(error), fieldStateIdsRef, mountedFieldsRef, composition, disableStatusSummary } }, React.createElement(Space, _extends({ element: enableFieldset ? 'fieldset' : 'div', style: mainStyle, className: mainClasses, "aria-labelledby": enableFieldset ? labelProps.id : undefined, role: enableFieldset ? fieldsetRole : undefined }, rest), React.createElement("div", { className: gridClasses }, (label || labelDescription || hasHelp) && React.createElement(FormLabel, labelProps, React.createElement("span", null, label && React.createElement("span", { className: "dnb-forms-field-block__label__content" }, label), hasHelp && !hasOnlyLabelDescription && !hideHelpButton && React.createElement("span", { className: "dnb-help-button__word-joiner" }, React.createElement(HelpButtonInline, { contentId: `${id}-help`, help: help })), label && hasLabelDescription && !labelDescriptionInline && (_br || (_br = React.createElement("br", null))), hasLabelDescription && React.createElement("span", { className: "dnb-forms-field-block__label__description" }, labelDescription), hasHelp && hasOnlyLabelDescription && !hideHelpButton && React.createElement("span", { className: "dnb-help-button__word-joiner" }, React.createElement(HelpButtonInline, { contentId: `${id}-help`, help: help })))), hasHelp && React.createElement(HelpButtonInlineContent, { contentId: `${id}-help`, className: "dnb-forms-field-block__help", help: help, breakout: layout === 'vertical' && !(nestedFieldBlockContext !== null && nestedFieldBlockContext !== void 0 && nestedFieldBlockContext.composition), outset: layout !== 'horizontal' }), React.createElement("div", { className: 'dnb-forms-field-block__status' + (contentWidth && contentWidth !== 'small' && contentWidth !== 'medium' && !(parseFloat(contentWidth) <= 11) ? ` dnb-forms-field-block__contents--width-${hasCustomContentWidth ? 'custom' : contentWidth}` : "") }, React.createElement(FormStatus, statusContent === null || statusContent === void 0 ? void 0 : statusContent.error), React.createElement(FormStatus, statusContent === null || statusContent === void 0 ? void 0 : statusContent.warning), React.createElement(FormStatus, statusContent === null || statusContent === void 0 ? void 0 : statusContent.info)), React.createElement("div", { className: classnames('dnb-forms-field-block__contents', contentClassName, contentWidth && `dnb-forms-field-block__contents--width-${hasCustomContentWidth ? 'custom' : contentWidth}`, align && `dnb-forms-field-block__contents--align-${align}`), ref: contentsRef }, children), React.createElement(SubmitIndicator, { state: fieldState !== null && fieldState !== void 0 ? fieldState : fieldStateRef.current, className: "dnb-forms-field-block__indicator dnb-forms-submit-indicator--inline-wrap" })))); } function useEnableFieldset({ label, asFieldset, children, nestedFieldBlockContext }) { return useMemo(() => { if (asFieldset === false) { return false; } let result = asFieldset; if (label && !result && !nestedFieldBlockContext) { let count = 0; findElementInChildren(children, child => { var _child$props, _child$type; if (child !== null && child !== void 0 && (_child$props = child.props) !== null && _child$props !== void 0 && _child$props.label || (child === null || child === void 0 || (_child$type = child.type) === null || _child$type === void 0 ? void 0 : _child$type['_formElement']) === true) { count++; } if (count > 1) { return result = true; } }); } return Boolean(result); }, [asFieldset, children, label, nestedFieldBlockContext]); } function CombineMessages({ type, messages }) { const translations = useTranslation().Field; if (messages.length === 1) { return React.createElement(React.Fragment, null, messages[0].message); } return React.createElement(React.Fragment, null, type === 'error' ? translations.errorSummary : translations.stateSummary, React.createElement(Ul, null, messages.map(({ message }, i) => { return React.createElement(Li, { key: i }, message); }))); } function getMessage(error) { if (error instanceof FormError) { var _error$formattedMessa; return (_error$formattedMessa = error.formattedMessage) !== null && _error$formattedMessa !== void 0 ? _error$formattedMessa : error.message; } return error.message; } export function getMessagesFromError(item) { const { content } = item; if (content instanceof FormError && Array.isArray(content.errors)) { return content.errors.map(content => { return getMessage(content); }); } if (Array.isArray(content)) { return content.map(content => { return content instanceof FormError || content instanceof Error ? getMessage(content) : content; }); } if (content instanceof FormError || content instanceof Error) { return [getMessage(content)]; } return [(React.isValidElement(content) ? content : content === null || content === void 0 ? void 0 : content.toString()) || content]; } function isFragment(fragment) { return React.isValidElement(fragment) && fragment.type === React.Fragment; } function fragmentHasChildren(fragment) { return React.isValidElement(fragment) && React.Children.count(fragment.props.children) > 0; } function fragmentHasOnlyUndefinedChildren(fragment) { const isUndefined = child => child === undefined; return React.isValidElement(fragment) && React.Children.toArray(fragment.props.children).every(isUndefined); } FieldBlock._supportsSpacingProps = true; export default FieldBlock; function getFieldWidth(width) { switch (width) { case 'small': return 'var(--forms-field-width--small)'; case 'medium': return 'var(--forms-field-width--medium)'; case 'large': return 'var(--forms-field-width--large)'; } return width; } //# sourceMappingURL=FieldBlock.js.map