@carbon/ibm-products
Version:
Carbon for IBM Products
209 lines (207 loc) • 7.81 kB
JavaScript
/**
* 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 };