@carbon/ibm-products
Version:
Carbon for IBM Products
236 lines (231 loc) • 8.18 kB
JavaScript
/**
* Copyright IBM Corp. 2020, 2025
*
* 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 { extends as _extends } from '../../../_virtual/_rollupPluginBabelHelpers.js';
import React__default, { useRef, useState, useContext, useEffect } from 'react';
import { Popover, PopoverContent, Layer, Section, Heading } from '@carbon/react';
import PropTypes from '../../../_virtual/index.js';
import { Add } from '@carbon/react/icons';
import { ConditionBuilderButton } from '../ConditionBuilderButton/ConditionBuilderButton.js';
import { useTranslations } from '../utils/useTranslations.js';
import { ConditionBuilderContext } from '../ConditionBuilderContext/ConditionBuilderProvider.js';
import { handleKeyDownForPopover } from '../utils/handleKeyboardEvents.js';
import { checkForMultiSelectOperator, blockClass, getValue } from '../utils/util.js';
import { translationsObject } from '../ConditionBuilderContext/translationObject.js';
const ConditionBuilderItem = _ref => {
let {
children,
className,
label,
renderIcon,
title,
type,
showToolTip,
condition,
popOverClassName,
config,
renderChildren,
onChange,
description,
...rest
} = _ref;
const popoverRef = useRef(null);
const [open, setOpen] = useState(false);
const {
conditionBuilderRef,
statementConfigCustom
} = useContext(ConditionBuilderContext);
const statementIdMap = {
ifAll: translationsObject.ifText,
ifAny: translationsObject.ifText,
unlessAll: translationsObject.unlessText,
unlessAny: translationsObject.unlessText
};
//Appending statements from custom statement configuration if present
statementConfigCustom?.forEach(statement => {
statementIdMap[statement.id] = statement.label;
});
const [invalidText, addConditionText, addPropertyText, addOperatorText, addValueText, labelText] = useTranslations(['invalidText', 'addConditionText', 'addPropertyText', 'addOperatorText', 'addValueText', label], statementIdMap);
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
};
}
const propertyId = rest['data-name'] == 'valueField' && type ? getValue(type, label, config) : labelText;
return {
isInvalid: false,
propertyLabel: propertyId
};
};
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 any condition is changed, state prop is triggered
if (condition.popoverToOpen && currentField !== condition.popoverToOpen) {
// close the previous popover
closePopover();
} else if (currentField == 'valueField' && type === 'option' && !checkForMultiSelectOperator(condition, config)) {
//close the current popover if the field is valueField and is a single select dropdown. For all other inputs ,popover need to be open on value changes.
closePopover();
}
if (condition.popoverToOpen == currentField) {
//current popover need to be opened
openPopOver();
}
} else {
// when we change any statement(if/ excl.if) which is not part of condition state, label change is triggered.
//close popOver when statement is changed.
closePopover();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [condition, label]);
useEffect(() => {
//this will focus the first input field in the popover
if (open && popoverRef.current) {
const firstFocusableElement = popoverRef?.current?.querySelector('input,textarea');
if (firstFocusableElement) {
setTimeout(() => firstFocusableElement.focus(), 0);
}
}
}, [popoverRef, open]);
const manageInvalidSelection = () => {
//when the user didn't select any value , we need to show as incomplete
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 = () => setOpen(true);
const togglePopover = () => {
if (children || renderChildren) {
setOpen(!open);
}
};
const handleKeyDownHandler = evt => {
handleKeyDownForPopover(evt, conditionBuilderRef, popoverRef);
if (evt.key === 'Escape') {
manageInvalidSelection();
}
};
const getCustomOperatorLabel = propertyLabel => {
return propertyLabel && config?.operators?.find(operator => {
return operator.id === propertyLabel;
});
};
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__default.createElement(Popover, {
open: open,
isTabTip: true,
role: "gridcell",
className: `${popOverClassName} ${blockClass}__popover`,
ref: popoverRef,
onRequestClose: closePopover
}, /*#__PURE__*/React__default.createElement(ConditionBuilderButton, _extends({
label: getLabel(),
hideLabel: !label ? true : false,
onClick: togglePopover,
className: className,
"aria-haspopup": true,
"aria-expanded": open,
renderIcon: renderIcon ? renderIcon : label == undefined ? Add : undefined,
showToolTip: showToolTip,
isInvalid: isInvalid,
description: description
}, rest)), open && /*#__PURE__*/React__default.createElement(PopoverContent, {
className: `${blockClass}__popover-content-wrapper`,
role: "dialog",
"aria-label": title,
onKeyDown: handleKeyDownHandler
}, /*#__PURE__*/React__default.createElement(Layer, null, /*#__PURE__*/React__default.createElement(Section, null, /*#__PURE__*/React__default.createElement(Heading, {
className: `${blockClass}__item__title`
}, title), /*#__PURE__*/React__default.createElement("div", {
className: `${blockClass}__popover-content`
}, renderChildren ? renderChildren(popoverRef) : 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
};
export { ConditionBuilderItem };