UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

307 lines (306 loc) 10.8 kB
"use client"; import React, { useCallback, useContext, useEffect, useMemo, useReducer, useRef } from 'react'; import clsx from 'clsx'; import Isolation from "../../Form/Isolation/index.js"; import { extractZodSubSchema } from "../../Form/Isolation/extractZodSubSchema.js"; import * as z from 'zod'; import { isZodSchema } from "../../utils/zod.js"; import pointer from "../../utils/json-pointer/index.js"; import useHandleStatus from "../../Form/Isolation/useHandleStatus.js"; import PushContainerContext from "./PushContainerContext.js"; import IterateItemContext from "../IterateItemContext.js"; import DataContext from "../../DataContext/Context.js"; import VisibilityContext from "../../Form/Visibility/VisibilityContext.js"; import useDataValue from "../../hooks/useDataValue.js"; import EditContainer, { DoneButton, CancelButton, ResetButton } from "../EditContainer/index.js"; import IterateArray from "../Array/index.js"; import OpenButton from "./OpenButton.js"; import { Flex, FormStatus, HeightAnimation } from "../../../../components/index.js"; import { useArrayLimit, useItemPath, useSwitchContainerMode } from "../hooks/index.js"; import Toolbar from "../Toolbar/index.js"; import { usePath, useTranslation } from "../../hooks/index.js"; import { clearedData } from "../../DataContext/Provider/index.js"; import { structuredClone } from "../../../../shared/helpers/structuredClone.js"; import withComponentMarkers from "../../../../shared/helpers/withComponentMarkers.js"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; function PushContainer(props) { var _outerContext$props2; const [, forceUpdate] = useReducer(() => ({}), {}); const outerContext = useContext(DataContext); const { data: outerData, required: requiredInherited } = outerContext; const { data: dataProp, defaultData: defaultDataProp, isolatedData, bubbleValidation, preventUncommittedChanges, dataReference, showResetButton, path, itemPath, insertAt, title, required = requiredInherited, children, openButton, showOpenButtonWhen, onCommit, ...rest } = props; const { absolutePath } = useItemPath(itemPath); const { path: relativePath } = usePath({ path, itemPath }); const commitHandleRef = useRef(undefined); const switchContainerModeRef = useRef(undefined); const containerModeRef = useRef(undefined); const { value: entries = [], moveValueToPath, getValueByPath } = useDataValue(path || itemPath); const { setNextContainerMode } = useSwitchContainerMode(path || absolutePath); const { hasReachedLimit, setShowStatus } = useArrayLimit(path || absolutePath); const cancelHandler = useCallback(() => { if (hasReachedLimit) { setShowStatus(false); } }, [hasReachedLimit, setShowStatus]); const showOpenButton = showOpenButtonWhen === null || showOpenButtonWhen === void 0 ? void 0 : showOpenButtonWhen(entries); const newItemContextProps = { path, itemPath, entries, commitHandleRef, switchContainerMode: switchContainerModeRef.current }; const data = useMemo(() => { var _Object$freeze; if (defaultDataProp) { return undefined; } return { ...isolatedData, pushContainerItems: [(_Object$freeze = Object.freeze(dataProp)) !== null && _Object$freeze !== void 0 ? _Object$freeze : clearedData] }; }, [dataProp, defaultDataProp, isolatedData]); if (outerData) { if (!Object.isFrozen(data)) { Object.assign(data || {}, outerData); } } const defaultData = useMemo(() => { return { ...(!dataProp ? isolatedData : null), pushContainerItems: [Object.freeze(defaultDataProp !== null && defaultDataProp !== void 0 ? defaultDataProp : clearedData)] }; }, [dataProp, defaultDataProp, isolatedData]); const emptyData = useCallback(data => { var _data$pushContainerIt, _ref; const firstItem = (_data$pushContainerIt = data.pushContainerItems) === null || _data$pushContainerIt === void 0 ? void 0 : _data$pushContainerIt[0]; if (firstItem === null || typeof firstItem !== 'object') { return { ...isolatedData, pushContainerItems: [null] }; } return { ...isolatedData, pushContainerItems: [(_ref = dataProp !== null && dataProp !== void 0 ? dataProp : defaultDataProp) !== null && _ref !== void 0 ? _ref : clearedData] }; }, [dataProp, defaultDataProp, isolatedData]); const isolationSchema = useMemo(() => { var _outerContext$props; const parentSchema = outerContext === null || outerContext === void 0 || (_outerContext$props = outerContext.props) === null || _outerContext$props === void 0 ? void 0 : _outerContext$props.schema; const targetPath = absolutePath || relativePath; if (!parentSchema || !targetPath) { return undefined; } if (isZodSchema(parentSchema)) { const element = extractZodSubSchema(parentSchema, `${targetPath}/0`); return z.object({ pushContainerItems: z.array(element) }); } else { const segments = String(targetPath).split('/').filter(Boolean); const schemaPointer = `/properties/${segments.join('/properties/')}/items`; const itemsSchema = pointer.has(parentSchema, schemaPointer) ? pointer.get(parentSchema, schemaPointer) : undefined; if (!itemsSchema) { return undefined; } return { type: 'object', properties: { pushContainerItems: { type: 'array', items: itemsSchema } } }; } }, [outerContext === null || outerContext === void 0 || (_outerContext$props2 = outerContext.props) === null || _outerContext$props2 === void 0 ? void 0 : _outerContext$props2.schema, absolutePath, relativePath]); return _jsx(Isolation, { data: data, defaultData: defaultData, required: required, dataReference: dataReference, emptyData: emptyData, bubbleValidation: containerModeRef.current === 'view' ? false : bubbleValidation, commitHandleRef: commitHandleRef, schema: isolationSchema, transformOnCommit: ({ pushContainerItems }) => { return moveValueToPath(absolutePath || relativePath, typeof insertAt === 'number' ? [...entries.slice(0, insertAt), ...pushContainerItems, ...entries.slice(insertAt)] : [...entries, ...pushContainerItems], absolutePath ? structuredClone(getValueByPath('/')) : {}); }, onCommit: (data, options) => { const { clearData, preventCommit } = options; if (hasReachedLimit) { preventCommit(); setShowStatus(true); } else { var _switchContainerModeR; setNextContainerMode('view'); (_switchContainerModeR = switchContainerModeRef.current) === null || _switchContainerModeR === void 0 || _switchContainerModeR.call(switchContainerModeRef, 'view'); clearData(); } onCommit === null || onCommit === void 0 || onCommit(data, options); }, children: _jsx(PushContainerContext, { value: newItemContextProps, children: _jsx(IterateArray, { path: "/pushContainerItems", containerMode: showOpenButton ? 'view' : 'edit', withoutFlex: true, omitSectionPath: true, children: _jsx(NewContainer, { title: title, openButton: openButton, switchContainerModeRef: switchContainerModeRef, showOpenButton: showOpenButton, cancelHandler: cancelHandler, containerModeRef: containerModeRef, rerenderPushContainer: forceUpdate, preventUncommittedChanges: preventUncommittedChanges, showResetButton: showResetButton, outerContext: outerContext, required: required, ...rest, children: children }) }) }) }); } function NewContainer({ title, openButton, showOpenButton, showResetButton, switchContainerModeRef, cancelHandler, containerModeRef, rerenderPushContainer, preventUncommittedChanges, outerContext, required, children, ...rest }) { const iterateItemContext = useContext(IterateItemContext); const { containerMode, switchContainerMode } = iterateItemContext || {}; containerModeRef.current = containerMode; const { hasContentChanged, showStatus } = useHandleStatus({ outerContext, preventUncommittedChanges, error: pushContainerError, name: 'push-container' }); useEffect(() => { rerenderPushContainer(); }, [containerMode, rerenderPushContainer]); const visibilityContext = useContext(VisibilityContext); switchContainerModeRef.current = switchContainerMode; const isVisible = (visibilityContext === null || visibilityContext === void 0 ? void 0 : visibilityContext.isVisible) === false ? false : Boolean(!showOpenButton || containerMode === 'edit' || (required || hasContentChanged) && showStatus); const { preventUncommittedChangesText } = useTranslation().Isolation; const { createButton } = useTranslation().IteratePushContainer; const { clearData } = useContext(DataContext) || {}; const restoreOriginalValue = useCallback(() => { clearData === null || clearData === void 0 || clearData(); }, [clearData]); const newItemContextProps = { ...iterateItemContext, restoreOriginalValue }; const toolbar = _jsx(Toolbar, { children: _jsxs(IterateItemContext, { value: newItemContextProps, children: [_jsxs(Flex.Horizontal, { gap: "large", children: [_jsx(DoneButton, { text: createButton }), showOpenButton && _jsx(CancelButton, { onClick: cancelHandler }), (preventUncommittedChanges || showResetButton) && _jsx(ResetButton, { hidden: !(showResetButton || showStatus) })] }), preventUncommittedChanges && showStatus && _jsx(FormStatus, { noAnimation: false, show: hasContentChanged, children: preventUncommittedChangesText })] }) }); return _jsxs(VisibilityContext, { value: { isVisible, keepInDOM: false }, children: [_jsx(EditContainer, { open: isVisible, title: title, toolbar: toolbar, ...rest, className: clsx('dnb-forms-section-block--error', rest.className), children: children }), openButton && typeof showOpenButton === 'boolean' && _jsx(HeightAnimation, { open: showOpenButton && containerMode === 'view', children: openButton })] }); } const pushContainerError = new Error('Iterate.PushContainer'); PushContainer.OpenButton = OpenButton; withComponentMarkers(PushContainer, { _supportsSpacingProps: true }); export default PushContainer; //# sourceMappingURL=PushContainer.js.map