@carbon/ibm-products
Version:
Carbon for IBM Products
368 lines (357 loc) • 15.7 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.
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var React = require('react');
var ConditionBlock = require('../ConditionBlock/ConditionBlock.js');
var index = require('../../../_virtual/index.js');
var cx = require('classnames');
var ConditionBuilderItem = require('../ConditionBuilderItem/ConditionBuilderItem.js');
var util = require('../utils/util.js');
var ConditionConnector = require('../ConditionBuilderConnector/ConditionConnector.js');
var ConditionBuilderProvider = require('../ConditionBuilderContext/ConditionBuilderProvider.js');
var uuidv4 = require('../../../global/js/utils/uuidv4.js');
var ConditionPreview = require('../ConditionPreview/ConditionPreview.js');
var ItemOption = require('../ConditionBuilderItem/ConditionBuilderItemOption/ItemOption.js');
var useTranslations = require('../utils/useTranslations.js');
var useDataConfigs = require('../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.useTranslations(['conditionBuilderGroupText', 'conditionText', 'conditionBuilderText']);
const {
statementConfig
} = useDataConfigs.useDataConfigs();
const {
variant,
conditionBuilderRef
} = React.useContext(ConditionBuilderProvider.ConditionBuilderContext);
const [showConditionPreview, setShowConditionPreview] = React.useState(-1);
const [showConditionSubGroupPreview, setShowConditionSubGroupPreview] = React.useState(-1);
React.useState(false);
const conditionBuilderContentRef = React.useRef(null);
const onRemoveHandler = (conditionId, evt, conditionIndex) => {
if (group && group.conditions && group.conditions.length > 1) {
if (variant == util.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.default()
});
};
const addConditionHandler = conditionIndex => {
const newCondition = {
property: undefined,
operator: '',
value: '',
popoverToOpen: 'propertyField',
id: uuidv4.default()
};
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(`.${util.blockClass}__group`)?.querySelectorAll('[data-name="closeCondition"]');
if (conditionIndex == currentGroupCloseButtons.length - 1) {
util.manageTabIndexAndFocus(currentGroupCloseButtons[conditionIndex - 1], conditionBuilderRef);
} else {
util.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(`${util.blockClass}__condition-block`)) {
util.manageTabIndexAndFocus(nextElement?.querySelector('[data-name="closeCondition"]'), conditionBuilderRef);
} else if (prevElement?.classList.contains(`${util.blockClass}__condition-block`)) {
util.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}"]`);
util.manageTabIndexAndFocus(currentRowToFocus?.querySelector('[data-name="closeCondition"]'), conditionBuilderRef);
}, 0);
} else if (prevRows?.length && prevRows.length > 1) {
util.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.default()
}],
id: uuidv4.default()
}, ...(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.createElement("div", {
className: `${className} eachGroup`
}, /*#__PURE__*/React.createElement("div", {
className: `${util.blockClass}__condition-wrapper`,
role: "grid",
"aria-label": conditionBuilderText
}, group?.conditions?.map((eachCondition, conditionIndex) => /*#__PURE__*/React.createElement("div", {
key: eachCondition.id,
className: `${util.blockClass}__group-wrapper`
}, /*#__PURE__*/React.createElement(ConditionBlock.default, {
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.createElement("div", {
className: `${className} ${util.blockClass}__condition-wrapper`,
role: aria.level === 1 ? 'rowgroup' : undefined,
"aria-label": aria.level == 1 ? conditionBuilderGroupText : undefined,
ref: conditionBuilderContentRef
}, /*#__PURE__*/React.createElement("div", {
tabIndex: 0,
role: "row",
"aria-level": aria.level,
"aria-posinset": aria.posinset,
"aria-setsize": aria.setsize
}, /*#__PURE__*/React.createElement(ConditionBuilderItem.ConditionBuilderItem, {
label: group.statement,
title: conditionText,
"data-name": "connectorField",
popOverClassName: `${util.blockClass}__gap`,
className: `${util.blockClass}__statement-button`
}, /*#__PURE__*/React.createElement(ItemOption.ItemOption, {
conditionState: {
value: group.statement,
label: conditionText
},
onChange: (v, evt) => {
util.focusThisField(evt, conditionBuilderRef);
onStatementChangeHandler(v);
},
config: {
options: statementConfig,
isStatement: true
}
}))), group?.conditions?.map((eachCondition, conditionIndex) => /*#__PURE__*/React.createElement(React.Fragment, {
key: eachCondition.id
}, eachCondition.conditions ? /*#__PURE__*/React.createElement("div", {
className: cx(`${util.blockClass}__condition-block subgroup ${util.blockClass}__gap`, {
[`${util.blockClass}__gap-bottom`]: group && group.conditions && group.conditions.length < conditionIndex + 1
}, {})
}, /*#__PURE__*/React.createElement(ConditionConnector.default, {
className: `${util.blockClass}__gap ${util.blockClass}__gap-bottom ${util.blockClass}__groupConnector`,
operator: group.groupOperator,
"aria-hidden": true
}), /*#__PURE__*/React.createElement(ConditionGroupBuilder, {
aria: {
level: aria.level + 1,
posinset: conditionIndex + 1,
setsize: group.conditions?.length ?? 0
},
className: `${util.blockClass}__group`,
group: eachCondition,
onChange: updatedConditions => {
onChangeHandler(updatedConditions, conditionIndex);
},
onRemove: e => {
onRemoveHandler(eachCondition.id, e, conditionIndex);
}
})) : /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(ConditionBlock.default, {
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.createElement(ConditionPreview.default, {
previewType: "subGroup",
group: group
}))), conditionIndex == showConditionPreview && (_ConditionPreview2 || (_ConditionPreview2 = /*#__PURE__*/React.createElement(ConditionPreview.default, {
previewType: "condition",
group: group
}))))));
};
return /*#__PURE__*/React.createElement(React.Fragment, null, variant == util.HIERARCHICAL_VARIANT && getHierarchicalVariant(), variant == util.NON_HIERARCHICAL_VARIANT && getNonHierarchicalVariant());
};
ConditionGroupBuilder.propTypes = {
/**
* object contains the aria attributes
*/
aria: index.default.object,
/**
* state defines the current group
*/
/**
* Provide an optional class to be applied to the containing node.
*/
className: index.default.string,
group: index.default.object,
/**
* callback to update the current condition of the state tree
*/
onChange: index.default.func,
/**
* call back to remove the particular group from the state tree
*/
onRemove: index.default.func
};
exports.default = ConditionGroupBuilder;