UNPKG

@carbon/ibm-products

Version:

Carbon for IBM Products

364 lines (355 loc) 15.6 kB
/** * 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 React__default, { useContext, useState, useRef, Fragment } from 'react'; import ConditionBlock from '../ConditionBlock/ConditionBlock.js'; import PropTypes from '../../../_virtual/index.js'; import cx from 'classnames'; import { ConditionBuilderItem } from '../ConditionBuilderItem/ConditionBuilderItem.js'; import { HIERARCHICAL_VARIANT, NON_HIERARCHICAL_VARIANT, blockClass, focusThisField, manageTabIndexAndFocus } from '../utils/util.js'; import ConditionConnector from '../ConditionBuilderConnector/ConditionConnector.js'; import { ConditionBuilderContext } from '../ConditionBuilderContext/ConditionBuilderProvider.js'; import uuidv4 from '../../../global/js/utils/uuidv4.js'; import ConditionPreview from '../ConditionPreview/ConditionPreview.js'; import { ItemOption } from '../ConditionBuilderItem/ConditionBuilderItemOption/ItemOption.js'; import { useTranslations } from '../utils/useTranslations.js'; import { useDataConfigs } from '../utils/useDataConfigs.js'; /** * * state - this is the current group that is being rendered . This can be a inner group or outer group * All the inner components of group are called from here. * @returns */ const ConditionGroupBuilder = _ref => { var _ConditionPreview, _ConditionPreview2; let { group, aria, onRemove, onChange, className } = _ref; const [conditionBuilderGroupText, conditionText, conditionBuilderText] = useTranslations(['conditionBuilderGroupText', 'conditionText', 'conditionBuilderText']); const { statementConfig } = useDataConfigs(); const { variant, conditionBuilderRef } = useContext(ConditionBuilderContext); const [showConditionPreview, setShowConditionPreview] = useState(-1); const [showConditionSubGroupPreview, setShowConditionSubGroupPreview] = useState(-1); useState(false); const conditionBuilderContentRef = useRef(null); const onRemoveHandler = (conditionId, evt, conditionIndex) => { if (group && group.conditions && group.conditions.length > 1) { if (variant == HIERARCHICAL_VARIANT) { handleFocusOnCloseHierarchical(evt); } else { handleFocusOnClose(evt, conditionIndex); } if (!checkGroupHaveCondition(group.conditions, conditionId)) { //when we delete the last condition of a group without deleting the subgroup, we need to restructure the group. //we will shift the group one level up. The subgroups will open up as conditions. //spreading out the condition inside the subgroup const allConditions = group?.conditions?.reduce((acc, condition) => { const _condition = condition; if (_condition.conditions) { //this is a subgroup return acc.concat(_condition.conditions); } return acc; }, []); onChange?.({ ...group, conditions: allConditions }); } else { const filteredConditions = group.conditions.filter(condition => conditionId !== condition.id); //This is to handle a edge case. //When a group has structure as 1 condition,1 subgroup, 1 condition and if we delete first condition, //the group will start with a subgroup. To avoid this,opening up that subgroup. if ((filteredConditions?.[0]).conditions) { const firstFilteredConditions = filteredConditions[0]; filteredConditions.splice(0, 1, ...firstFilteredConditions?.conditions); } onChange?.({ ...group, conditions: filteredConditions }); } } else { onRemove?.(evt); } }; //check to identify a group without a plain condition const checkGroupHaveCondition = (conditions, conditionId) => { return conditions.find(eachCondition => { return !eachCondition.conditions && eachCondition.id !== conditionId; }); }; const onChangeHandler = (updatedCondition, conditionIndex) => { const updatedConditions = [...(group?.conditions ? group.conditions.slice(0, conditionIndex) : []), updatedCondition, ...(group?.conditions ? group.conditions.slice(conditionIndex + 1) : [])]; onChange?.({ ...group, conditions: updatedConditions, id: group?.id ?? uuidv4() }); }; const addConditionHandler = conditionIndex => { const newCondition = { property: undefined, operator: '', value: '', popoverToOpen: 'propertyField', id: uuidv4() }; onChange?.({ ...group, conditions: [...(group.conditions ? group.conditions.slice(0, conditionIndex + 1) : []), newCondition, ...(group.conditions ? group.conditions.slice(conditionIndex + 1) : [])] }); }; const handleFocusOnClose = (e, conditionIndex) => { //get all close buttons. //if the last condition is closing, focus the second last one. //or focus the next one. const currentGroupCloseButtons = e.currentTarget.closest(`.${blockClass}__group`)?.querySelectorAll('[data-name="closeCondition"]'); if (conditionIndex == currentGroupCloseButtons.length - 1) { manageTabIndexAndFocus(currentGroupCloseButtons[conditionIndex - 1], conditionBuilderRef); } else { manageTabIndexAndFocus(currentGroupCloseButtons[conditionIndex + 1], conditionBuilderRef); } }; const handleFocusOnCloseHierarchical = evt => { //getting the current aria-level and aria-posinset. const currentLevel = evt.currentTarget?.closest('[role="row"]')?.getAttribute('aria-level'); const currentPos = evt.currentTarget?.closest('[role="row"]')?.getAttribute('aria-posinset'); //finding the next and previous items in same level const nextElement = conditionBuilderContentRef.current?.querySelector(`[aria-level="${currentLevel}"][aria-posinset="${Number(currentPos) + 1}"]`); const prevElement = conditionBuilderContentRef.current?.querySelector(`[aria-level="${currentLevel}"][aria-posinset="${Number(currentPos) - 1}"]`); //checking if next level is a valid condition. If then, focus the close button inside that condition //Otherwise , check the previous item is a valid condition if (nextElement?.classList.contains(`${blockClass}__condition-block`)) { manageTabIndexAndFocus(nextElement?.querySelector('[data-name="closeCondition"]'), conditionBuilderRef); } else if (prevElement?.classList.contains(`${blockClass}__condition-block`)) { manageTabIndexAndFocus(prevElement?.querySelector('[data-name="closeCondition"]'), conditionBuilderRef); } //If there are no valid condition in this group, focus next or previous row else { const prevRows = conditionBuilderContentRef.current?.querySelectorAll(`[aria-level="${Number(currentLevel) - 1}"][role="row"]`); const nextRow = conditionBuilderContentRef.current?.querySelector(`[aria-level="${Number(currentLevel) + 1}"][role="row"]`); if (nextRow) { //since there are no condition in current group, this group will move one level up const rowIdentity = { ariaLevel: Number(nextRow.ariaLevel) - 1, ariaPosInSet: nextRow.ariaPosInSet }; setTimeout(() => { const currentRowToFocus = conditionBuilderContentRef.current?.querySelector(`[role="row"][aria-level="${rowIdentity.ariaLevel}"][aria-posinset="${rowIdentity.ariaPosInSet}"]`); manageTabIndexAndFocus(currentRowToFocus?.querySelector('[data-name="closeCondition"]'), conditionBuilderRef); }, 0); } else if (prevRows?.length && prevRows.length > 1) { manageTabIndexAndFocus(prevRows[prevRows.length - 2]?.querySelector('[data-name="closeCondition"]'), conditionBuilderRef); } } }; const addConditionSubGroupHandler = conditionIndex => { onChange({ ...group, conditions: [...(group.conditions ? group.conditions.slice(0, conditionIndex + 1) : []), { statement: 'ifAll', groupOperator: 'and', conditions: [{ property: undefined, operator: '', value: '', popoverToOpen: 'propertyField', id: uuidv4() }], id: uuidv4() }, ...(group.conditions ? group.conditions.slice(conditionIndex + 1) : [])] }); }; const isLastCondition = (conditionIndex, conditionArr) => { return conditionIndex + 1 >= conditionArr.length || conditionArr.length - 1 != conditionIndex && conditionArr[conditionIndex + 1].conditions; }; const showConditionSubGroupPreviewHandler = conditionIndex => { setShowConditionSubGroupPreview(conditionIndex); }; const hideConditionSubGroupPreviewHandler = () => { setShowConditionSubGroupPreview(-1); }; const showConditionPreviewHandler = conditionIndex => { setShowConditionPreview(conditionIndex); }; const hideConditionPreviewHandler = () => { setShowConditionPreview(-1); }; const onStatementChangeHandler = updatedStatement => { const groupOperator = statementConfig.find(statement => statement.id == updatedStatement)?.connector; onChange({ ...group, groupOperator: groupOperator, statement: updatedStatement }); }; const onConnectorOperatorChange = op => { onChange({ ...group, groupOperator: op }); }; const getNonHierarchicalVariant = () => { return /*#__PURE__*/React__default.createElement("div", { className: `${className} eachGroup` }, /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__condition-wrapper`, role: "grid", "aria-label": conditionBuilderText }, group?.conditions?.map((eachCondition, conditionIndex) => /*#__PURE__*/React__default.createElement("div", { key: eachCondition.id, className: `${blockClass}__group-wrapper` }, /*#__PURE__*/React__default.createElement(ConditionBlock, { conjunction: conditionIndex > 0 ? group.groupOperator : undefined, aria: { level: aria.level + 1, posinset: conditionIndex + 1, setsize: group?.conditions?.length }, isStatement: conditionIndex == 0, condition: eachCondition, group: group, conditionIndex: conditionIndex, onChange: updatedConditions => { onChangeHandler(updatedConditions, conditionIndex); }, addConditionHandler: addConditionHandler, onRemove: e => { onRemoveHandler(eachCondition.id, e, conditionIndex); }, onConnectorOperatorChange: onConnectorOperatorChange, onStatementChange: onStatementChangeHandler, isLastCondition: isLastCondition }))))); }; const getHierarchicalVariant = () => { return /*#__PURE__*/React__default.createElement("div", { className: `${className} ${blockClass}__condition-wrapper`, role: aria.level === 1 ? 'rowgroup' : undefined, "aria-label": aria.level == 1 ? conditionBuilderGroupText : undefined, ref: conditionBuilderContentRef }, /*#__PURE__*/React__default.createElement("div", { tabIndex: 0, role: "row", "aria-level": aria.level, "aria-posinset": aria.posinset, "aria-setsize": aria.setsize }, /*#__PURE__*/React__default.createElement(ConditionBuilderItem, { label: group.statement, title: conditionText, "data-name": "connectorField", popOverClassName: `${blockClass}__gap`, className: `${blockClass}__statement-button` }, /*#__PURE__*/React__default.createElement(ItemOption, { conditionState: { value: group.statement, label: conditionText }, onChange: (v, evt) => { focusThisField(evt, conditionBuilderRef); onStatementChangeHandler(v); }, config: { options: statementConfig, isStatement: true } }))), group?.conditions?.map((eachCondition, conditionIndex) => /*#__PURE__*/React__default.createElement(Fragment, { key: eachCondition.id }, eachCondition.conditions ? /*#__PURE__*/React__default.createElement("div", { className: cx(`${blockClass}__condition-block subgroup ${blockClass}__gap`, { [`${blockClass}__gap-bottom`]: group && group.conditions && group.conditions.length < conditionIndex + 1 }, {}) }, /*#__PURE__*/React__default.createElement(ConditionConnector, { className: `${blockClass}__gap ${blockClass}__gap-bottom ${blockClass}__groupConnector`, operator: group.groupOperator, "aria-hidden": true }), /*#__PURE__*/React__default.createElement(ConditionGroupBuilder, { aria: { level: aria.level + 1, posinset: conditionIndex + 1, setsize: group.conditions?.length ?? 0 }, className: `${blockClass}__group`, group: eachCondition, onChange: updatedConditions => { onChangeHandler(updatedConditions, conditionIndex); }, onRemove: e => { onRemoveHandler(eachCondition.id, e, conditionIndex); } })) : /*#__PURE__*/React__default.createElement("div", null, /*#__PURE__*/React__default.createElement(ConditionBlock, { conjunction: conditionIndex > 0 ? group.groupOperator : undefined, aria: { level: aria.level + 1, posinset: conditionIndex + 1, setsize: group.conditions?.length ?? 0 }, isStatement: false, condition: eachCondition, group: group, conditionIndex: conditionIndex, addConditionHandler: addConditionHandler, addConditionSubGroupHandler: addConditionSubGroupHandler, onChange: updatedConditions => { onChangeHandler(updatedConditions, conditionIndex); }, onRemove: e => { onRemoveHandler(eachCondition.id, e, conditionIndex); }, onConnectorOperatorChange: onConnectorOperatorChange, onStatementChange: onStatementChangeHandler, showConditionSubGroupPreviewHandler: () => { showConditionSubGroupPreviewHandler(conditionIndex); }, hideConditionSubGroupPreviewHandler: hideConditionSubGroupPreviewHandler, showConditionPreviewHandler: () => { showConditionPreviewHandler(conditionIndex); }, hideConditionPreviewHandler: hideConditionPreviewHandler, isLastCondition: isLastCondition })), conditionIndex == showConditionSubGroupPreview && (_ConditionPreview || (_ConditionPreview = /*#__PURE__*/React__default.createElement(ConditionPreview, { previewType: "subGroup", group: group }))), conditionIndex == showConditionPreview && (_ConditionPreview2 || (_ConditionPreview2 = /*#__PURE__*/React__default.createElement(ConditionPreview, { previewType: "condition", group: group })))))); }; return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, variant == HIERARCHICAL_VARIANT && getHierarchicalVariant(), variant == NON_HIERARCHICAL_VARIANT && getNonHierarchicalVariant()); }; ConditionGroupBuilder.propTypes = { /** * object contains the aria attributes */ aria: PropTypes.object, /** * state defines the current group */ /** * Provide an optional class to be applied to the containing node. */ className: PropTypes.string, group: PropTypes.object, /** * callback to update the current condition of the state tree */ onChange: PropTypes.func, /** * call back to remove the particular group from the state tree */ onRemove: PropTypes.func }; export { ConditionGroupBuilder as default };