@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
433 lines (432 loc) • 16.9 kB
JavaScript
"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