@adaptabletools/adaptable-cjs
Version:
Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
273 lines (272 loc) • 13 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TreeDropdown = exports.toDisplayValueDefault = void 0;
const tslib_1 = require("tslib");
const React = tslib_1.__importStar(require("react"));
const FieldWrap_1 = tslib_1.__importDefault(require("../../FieldWrap"));
const TreeList_1 = require("../TreeList");
const react_1 = require("react");
const OverlayTrigger_1 = tslib_1.__importDefault(require("../../OverlayTrigger"));
const rebass_1 = require("rebass");
const NotifyResize_1 = tslib_1.__importDefault(require("../../NotifyResize"));
const Input_1 = tslib_1.__importDefault(require("../../Input"));
const InfiniteTable_1 = require("../../InfiniteTable");
const SimpleButton_1 = tslib_1.__importDefault(require("../../SimpleButton"));
const CheckBox_1 = require("../../CheckBox");
const useLatest_1 = require("../../utils/useLatest");
const re_resizable_1 = require("re-resizable");
const AdaptableComputedCSSVarsContext_1 = require("../../../View/AdaptableComputedCSSVarsContext");
const resizableDirections = {
right: true,
bottom: true,
bottomRight: true,
};
function toDisplayValueDefault(value) {
if (!Array.isArray(value)) {
return `${value}`;
}
return value.map((v) => (Array.isArray(v) ? v.join('-') : v)).join(', ');
}
exports.toDisplayValueDefault = toDisplayValueDefault;
const getLabelColumn = (field, { includeExpandCollapseButton }) => {
return {
field,
defaultFlex: 1,
renderTreeIcon: true,
defaultSortable: false,
resizable: false,
renderSelectionCheckBox: ({ rowInfo, dataSourceApi, api }) => {
if (!rowInfo.isTreeNode) {
return null;
}
return (React.createElement(CheckBox_1.CheckBox, { mx: 1, checked: rowInfo?.rowSelected, onChange: (checked) => {
dataSourceApi.treeApi.setNodeSelection(rowInfo.nodePath, checked);
api.focus();
} }));
},
renderHeader: ({ dataSourceApi, api, allRowsSelected, someRowsSelected }) => {
const { treeApi } = dataSourceApi;
const allFirstLevelCollapsed = dataSourceApi
.getOriginalDataArray()
.every((item) => !treeApi.isNodeExpanded([item.id]));
return (React.createElement(rebass_1.Flex, { flexDirection: 'row', alignItems: 'center', width: '100%', onMouseDown: (e) => {
// so we can keep the focus on the Grid
e.preventDefault();
} },
React.createElement(CheckBox_1.CheckBox, { checked: someRowsSelected && !allRowsSelected ? null : allRowsSelected, mr: 2, onChange: () => {
if (allRowsSelected) {
dataSourceApi.treeApi.deselectAll();
}
else {
dataSourceApi.treeApi.selectAll();
}
api.focus();
} }, allRowsSelected ? '(Deselect All)' : '(Select All)'),
React.createElement(rebass_1.Flex, { flex: 1 }),
includeExpandCollapseButton ? (React.createElement(SimpleButton_1.default, { label: "toggle-expand-collapse", icon: allFirstLevelCollapsed ? 'expand-all' : 'collapse-all', onMouseDown: () => {
if (allFirstLevelCollapsed) {
dataSourceApi.treeApi.expandAll();
}
else {
dataSourceApi.treeApi.collapseAll();
}
}, iconPosition: "end" })) : null));
},
};
};
const sizeFull = {
width: '100%',
height: '100%',
};
function getRowCount(options) {
return options.reduce((acc, option) => {
if (Array.isArray(option.children)) {
return acc + getRowCount(option.children) + 1;
}
return acc + 1;
}, 0);
}
function TreeDropdown(props) {
const [visible, doSetVisible] = (0, react_1.useState)(false);
const overlayDOMRef = (0, react_1.useRef)(null);
const getProps = (0, useLatest_1.useLatest)(props);
const computedCSSVars = (0, AdaptableComputedCSSVarsContext_1.useAdaptableComputedCSSVars)();
const [treeExpandState, setTreeExpandState] = (0, react_1.useState)(undefined);
const [searchValue, setSearchValue] = (0, react_1.useState)('');
const labelField = props.labelField ?? 'label';
const [stateValue, setStateValue] = (0, react_1.useState)(props.value !== undefined ? props.value : props.defaultValue || []);
const onChange = (0, react_1.useCallback)((value) => {
const paths = value instanceof InfiniteTable_1.TreeSelectionState
? value.getState().selectedPaths
: value.selectedPaths || [];
if (props.value === undefined) {
setStateValue(paths);
}
props.onChange?.(paths);
}, [props.onChange, props.value]);
const value = props.value !== undefined ? props.value : stateValue;
const treeSelection = (0, react_1.useMemo)(() => {
const selection = {
defaultSelection: false,
selectedPaths: value,
};
return selection;
}, [value]);
const rowCount = (0, react_1.useMemo)(() => {
return getRowCount(props.options);
}, [props.options]);
const hasChildren = rowCount > props.options.length;
const columns = (0, react_1.useMemo)(() => {
return {
label: getLabelColumn(labelField, {
includeExpandCollapseButton: hasChildren,
}),
};
}, [labelField, hasChildren]);
const [size, setSize] = (0, react_1.useState)({
width: 0,
height: rowCount * 35,
});
const setHeight = (0, react_1.useCallback)((height) => {
setSize((s) => {
return {
...s,
height,
};
});
}, []);
const setWidth = (0, react_1.useCallback)((width) => {
setSize((s) => {
return {
...s,
width,
};
});
}, []);
const getSize = (0, useLatest_1.useLatest)(size);
(0, react_1.useEffect)(() => {
if (!getSize().height) {
setHeight(rowCount * 35);
}
}, [rowCount]);
const setVisible = (visible) => {
if (visible) {
const { onMenuOpen } = getProps();
if (onMenuOpen) {
onMenuOpen();
}
requestAnimationFrame(() => {
doSetVisible(visible);
});
}
else {
const { onMenuClose } = getProps();
if (onMenuClose) {
onMenuClose();
}
doSetVisible(visible);
}
};
const [treeListApi, setTreeListApi] = (0, react_1.useState)(null);
const { listSizeConstraints } = props;
const nodeMatches = (0, react_1.useCallback)(({ data }) => {
return !searchValue
? data
: `${data[labelField]}`.toLowerCase().includes(searchValue.toLowerCase());
}, [searchValue]);
const filterFunction = (0, react_1.useCallback)(({ data, filterTreeNode }) => {
if (!Array.isArray(data.children)) {
return nodeMatches({ data });
}
// allow non-leaf nodes to match
if (nodeMatches({ data })) {
return data;
}
return filterTreeNode(data);
}, [nodeMatches]);
return (React.createElement(rebass_1.Flex, { flexDirection: 'row', className: "ab-TreeDropdown", style: {
width: '100%',
...props.style,
}, onMouseDown: props.onMouseDown, onBlur: (e) => {
const { relatedTarget } = e;
const overlayDOMNode = overlayDOMRef.current;
if ((overlayDOMNode && relatedTarget == overlayDOMNode) ||
overlayDOMNode?.contains(relatedTarget)) {
return;
}
setVisible(false);
} },
React.createElement(NotifyResize_1.default, { onResize: (newSize) => {
setWidth(newSize.width);
} }),
React.createElement(OverlayTrigger_1.default, { visible: visible, targetOffset: 20, alignPosition: [
// overlay - target
['TopLeft', 'BottomLeft'],
['TopRight', 'BottomRight'],
['BottomLeft', 'TopLeft'],
['BottomRight', 'TopRight'],
], render: () => {
const minWidth = listSizeConstraints?.minWidth ||
computedCSSVars['--ab-cmp-select-menu__min-width'] ||
240;
const maxWidth = listSizeConstraints?.maxWidth ||
computedCSSVars['--ab-cmp-select-menu__max-width'] ||
'60vw';
const minHeight = listSizeConstraints?.minHeight || 200;
const maxHeight = listSizeConstraints?.maxHeight ||
computedCSSVars['--ab-cmp-select-menu__max-height'] ||
'50vh';
const resizable = getProps().resizable;
const treeListStyle = resizable
? { ...sizeFull }
: {
width: size.width,
height: size.height,
maxWidth,
minHeight,
maxHeight,
minWidth,
};
if (!hasChildren) {
// @ts-ignore - don't leave any space for the > expand icon, as there are no children
treeListStyle['--infinite-group-row-column-nesting'] = 'var(--ab-space-2)';
}
const treeList = (React.createElement(TreeList_1.TreeList, { primaryKey: props.primaryKey ?? 'id', treeFilterFunction: filterFunction, columnHeaderHeight: 30, onReady: ({ api }) => {
setTreeListApi(api);
api.focus();
}, defaultTreeExpandState: treeExpandState, onTreeExpandStateChange: setTreeExpandState, columns: columns, options: props.options, treeSelection: treeSelection, onTreeSelectionChange: (0, InfiniteTable_1.withSelectedLeafNodesOnly)(onChange), style: treeListStyle }));
let children = (React.createElement(rebass_1.Flex, { flexDirection: 'column', height: '100%' },
React.createElement(rebass_1.Flex, { backgroundColor: 'defaultbackground', p: 1, alignItems: 'center', justifyContent: 'stretch', justifyItems: 'stretch' },
React.createElement(Input_1.default, { "data-name": "menulist-search-input", placeholder: "Search...", style: { width: '100%' }, value: searchValue, onChange: (e) => setSearchValue(e.target.value) })),
treeList));
if (resizable) {
const onResizeStop = (_e, _direction, ref) => {
const newSize = {
width: ref.style.width,
height: ref.style.height,
};
setSize(newSize);
};
children = (React.createElement(re_resizable_1.Resizable, { enable: resizableDirections, minWidth: minWidth, maxWidth: maxWidth, minHeight: minHeight, maxHeight: maxHeight, defaultSize: size, onResizeStop: onResizeStop, onResizeStart: (e) => {
// in order to prevent focus from being lost
e.preventDefault();
} }, children));
}
return (React.createElement(rebass_1.Box, { ref: overlayDOMRef, className: "ab-TreeDropdownOverlay", "data-name": "menu-container", style: {
fontSize: 'var(--ab-cmp-select__font-size)',
} }, children));
} },
React.createElement(FieldWrap_1.default, { style: { width: '100%', ...props.fieldStyle } },
React.createElement(Input_1.default, { type: "text", readOnly: true, "data-name": "Select Values", placeholder: props.placeholder ?? 'Select a value', style: {
width: '100%',
}, pr: props.clearable ? 0 : undefined, value: props.toDisplayValue ? props.toDisplayValue(value) : toDisplayValueDefault(value), onFocus: () => {
if (!visible) {
setVisible(true);
}
treeListApi?.focus();
} }),
props.clearable && (React.createElement(SimpleButton_1.default, { style: {
visibility: Array.isArray(value) && value.length > 0 ? 'visible' : 'hidden',
}, variant: "text", icon: "close", onClick: () => onChange({ selectedPaths: [] }) }))))));
}
exports.TreeDropdown = TreeDropdown;