UNPKG

@carbon/ibm-products

Version:
209 lines (207 loc) 7.81 kB
/** * Copyright IBM Corp. 2020, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import { blockClass, checkForMultiSelectOperator, getValue } from "../utils/util.js"; import { ConditionBuilderContext } from "../ConditionBuilderContext/ConditionBuilderProvider.js"; import { ConditionBuilderButton } from "../ConditionBuilderButton/ConditionBuilderButton.js"; import { translationsObject } from "../ConditionBuilderContext/translationObject.js"; import { useTranslations } from "../utils/useTranslations.js"; import { handleKeyDownForPopover } from "../utils/handleKeyboardEvents.js"; import { useEvent } from "../utils/useEvent.js"; import React, { useContext, useEffect, useRef, useState } from "react"; import PropTypes from "prop-types"; import { Heading, Layer, Popover, PopoverContent, Section } from "@carbon/react"; import { Add } from "@carbon/react/icons"; //#region src/components/ConditionBuilder/ConditionBuilderItem/ConditionBuilderItem.tsx /** * Copyright IBM Corp. 2024 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ const ConditionBuilderItem = ({ children, className, label, renderIcon, title, type, showToolTip, condition, popOverClassName, config, renderChildren, onChange, description, ...rest }) => { const popoverRef = useRef(null); const [open, setOpen] = useState(false); const { conditionBuilderRef, statementConfigCustom, readOnly } = useContext(ConditionBuilderContext); const statementIdMap = { ifAll: translationsObject.ifText, ifAny: translationsObject.ifText, unlessAll: translationsObject.unlessText, unlessAny: translationsObject.unlessText }; statementConfigCustom?.forEach((statement) => { statementIdMap[statement.id] = statement.label; }); const [invalidText, addConditionText, addPropertyText, addOperatorText, addValueText, labelText] = useTranslations([ "invalidText", "addConditionText", "addPropertyText", "addOperatorText", "addValueText", label ], statementIdMap); const getCustomOperatorLabel = (propertyLabel) => { return propertyLabel && config?.operators?.find((operator) => { return operator.id === propertyLabel; }); }; const getPropertyDetails = () => { const { property, operator } = condition || {}; if (label === "INVALID" || rest["data-name"] === "propertyField" && property === "INVALID" || rest["data-name"] === "operatorField" && operator === "INVALID") return { propertyLabel: invalidText, isInvalid: true }; if (rest["data-name"] == "operatorField" && type == "custom") return { isInvalid: false, propertyLabel: getCustomOperatorLabel(label)?.id }; return { isInvalid: false, propertyLabel: rest["data-name"] == "valueField" && type ? getValue(type, label, config) : labelText }; }; const { propertyLabel, isInvalid } = getPropertyDetails(); useEffect(() => { /** * rest['data-name'] holds the current field name * popoverToOpen hold the next popover to be opened if required */ if (condition) { const currentField = rest["data-name"]; if (condition.popoverToOpen && currentField !== condition.popoverToOpen) closePopover(); else if (currentField == "valueField" && type === "option" && !checkForMultiSelectOperator(condition, config)) closePopover(); if (condition.popoverToOpen == currentField) setTimeout(() => { openPopOver(); }); } else closePopover(); }, [condition, label]); useEffect(() => { if (open && popoverRef.current) { const firstFocusableElement = popoverRef?.current?.querySelector("input,textarea"); if (firstFocusableElement) setTimeout(() => firstFocusableElement.focus(), 0); } }, [popoverRef, open]); useEvent(popoverRef, "focusout", (event) => { const focusEvent = event; const relatedTarget = focusEvent.relatedTarget; const popoverEl = popoverRef.current; if (!popoverEl) return; const focusLeftPopover = relatedTarget && !popoverEl.contains(relatedTarget); const targetInsidePopover = popoverEl.contains(focusEvent.target); const focusMovedToDatePicker = focusEvent.target?.closest(".flatpickr-calendar"); if ((focusLeftPopover || !targetInsidePopover) && !focusMovedToDatePicker) closePopover(); }); const manageInvalidSelection = () => { if (rest["data-name"] === "propertyField" && !condition?.property || rest["data-name"] === "operatorField" && !condition?.operator || rest["data-name"] === "valueField" && !condition?.value) onChange?.("INVALID"); }; const closePopover = () => { if (open) manageInvalidSelection(); setOpen(false); }; const openPopOver = () => { if (readOnly) return; setOpen(true); }; const togglePopover = () => { if (children || renderChildren) setOpen(!open); }; const handleKeyDownHandler = (evt) => { handleKeyDownForPopover(evt, conditionBuilderRef, popoverRef, closePopover); if (evt.key === "Escape") manageInvalidSelection(); }; const getLabel = () => { if (config?.operators && rest["data-name"] === "operatorField") return getCustomOperatorLabel(propertyLabel)?.label ?? addOperatorText; else if (propertyLabel) return propertyLabel; else if (rest["data-name"] === "propertyField") return addPropertyText; else if (rest["data-name"] === "operatorField") return addOperatorText; else if (rest["data-name"] === "valueField") return addValueText; else return addConditionText; }; return /* @__PURE__ */ React.createElement(Popover, { open, isTabTip: true, role: "gridcell", className: `${popOverClassName} ${blockClass}__popover`, ref: popoverRef, onRequestClose: () => { if ((event?.target)?.closest(".flatpickr-calendar")) return; closePopover(); } }, /* @__PURE__ */ React.createElement(ConditionBuilderButton, { label: getLabel(), hideLabel: !label ? true : false, onClick: togglePopover, className, "aria-haspopup": true, "aria-expanded": open, renderIcon: renderIcon ? renderIcon : label == void 0 ? Add : void 0, showToolTip, isInvalid, description, ...rest }), open && /* @__PURE__ */ React.createElement(PopoverContent, { className: `${blockClass}__popover-content-wrapper`, role: "dialog", "aria-label": title, onKeyDown: handleKeyDownHandler }, /* @__PURE__ */ React.createElement(Layer, null, /* @__PURE__ */ React.createElement(Section, null, /* @__PURE__ */ React.createElement(Heading, { className: `${blockClass}__item__title` }, title), /* @__PURE__ */ React.createElement("div", { className: `${blockClass}__popover-content` }, renderChildren ? renderChildren(popoverRef, closePopover) : children))))); }; ConditionBuilderItem.propTypes = { /** * provide the contents of the popover */ children: PropTypes.node, /** * Provide an optional class to be applied to the containing node. */ className: PropTypes.string, /** * current condition state object */ condition: PropTypes.object, /** * this is the config object again the current property from inputConfig */ config: PropTypes.object, /** * text to be displayed in the field */ label: PropTypes.oneOfType([ PropTypes.string, PropTypes.array, PropTypes.object ]), /** * callback to update the current condition of the state tree */ onChange: PropTypes.func, /** * class name for popover */ popOverClassName: PropTypes.string, /** * callback prop that returns the jsx for children */ renderChildren: PropTypes.func, /** * Optional prop to allow overriding the icon rendering. */ renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), /** * show tool tip */ showToolTip: PropTypes.bool, /** * title of the popover */ title: PropTypes.string, /** * input type */ type: PropTypes.string }; //#endregion export { ConditionBuilderItem };