@sap-ux/ui-components
Version:
SAP UI Components Library
249 lines • 12.7 kB
JavaScript
import React, { useEffect, useRef, useState } from 'react';
import { List } from 'react-movable';
import { UIDefaultButton, UILoader } from '../index.js';
import { renderTitleRow, UIFlexibleTableRow } from './UIFlexibleTableRow.js';
import { UIFlexibleTableRowNoData } from './UIFlexibleTableRowNoData.js';
import { UIFlexibleTableLayout } from './types.js';
import './UIFlexibleTable.scss';
import { composeClassNames, getRowActionButtonId, getTableActionButtonId } from './utils.js';
import { RowActions } from './RowActions.js';
import { RowDataCells } from './RowData.js';
/**
* UIFlexibleTable component.
*
* @exports
* @param {UIFlexibleTableProps<T>} props
* @returns {React.ReactElement}
*/
export function UIFlexibleTable(props) {
const [currentFocusedRowIndex, setCurrentFocusedRowIndex] = useState();
const [currentFocusedRowAction, setCurrentFocusedRowAction] = useState('');
const [isInRowLayout, setIsInRowLayout] = useState(props.layout === UIFlexibleTableLayout.InlineFlex);
const [titleRowRightPadding, setTitleRowRightPadding] = useState(0);
const [rowToNavigate, setRowToNavigate] = useState();
const scrollableElement = useRef(null);
const tableRootElementRef = useRef(null);
const contentElementRef = useRef(null);
const tableBodyElementRef = useRef(null);
const scrollTargetRef = useRef(null);
const onResize = (element) => {
setIsInRowLayout(props.layout === UIFlexibleTableLayout.InlineFlex &&
isRowFitsContainer(props.inRowLayoutMinWidth, tableRootElementRef));
const rootContainer = tableRootElementRef.current;
if (element && props.onContentSizeChange && rootContainer) {
props.onContentSizeChange({ height: rootContainer.clientHeight, width: rootContainer.clientWidth });
}
};
const onScrollBarStateChange = () => {
const content = contentElementRef
.current;
const scrollableContent = scrollableElement.current;
const scrollBarSize = content && scrollableContent ? content.clientWidth - scrollableContent.clientWidth : 0;
setTitleRowRightPadding(scrollBarSize);
};
const [resizeObserver] = useState(new ResizeObserver(onResize));
const scrollBarObserver = useRef(new ResizeObserver(onScrollBarStateChange));
useEffect(() => {
if (tableRootElementRef.current) {
resizeObserver.observe(tableRootElementRef.current);
onResize();
}
}, [tableRootElementRef.current]);
useEffect(() => {
if (currentFocusedRowIndex !== undefined) {
restoreFocus(currentFocusedRowAction, currentFocusedRowIndex, props.id);
}
if (!props.isContentLoading && scrollTargetRef.current) {
scrollTargetRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
setRowToNavigate(undefined);
}
}, [props.isContentLoading, props.rows]);
useEffect(() => {
return () => {
resizeObserver.disconnect();
};
}, []);
useEffect(() => {
onResize();
}, [props.layout, props.maxWidth]);
const reorderTable = (oldIndex, newIndex) => {
if (props.onTableReorder) {
const result = props.onTableReorder({ oldIndex, newIndex });
if (typeof result === 'object' && result.isDropDisabled) {
return;
}
setCurrentFocusedRowIndex(newIndex);
}
};
const addNewRow = () => {
if (props.addRowButton?.onClick) {
const result = props.addRowButton.onClick();
if (result instanceof Promise) {
result.then((data) => setRowToNavigate(data?.scrollToRow)).catch(() => undefined);
}
else {
setRowToNavigate(result?.scrollToRow);
}
}
};
const onFocusRowAction = (rowIndex, actionName = '') => {
setCurrentFocusedRowIndex(rowIndex);
setCurrentFocusedRowAction(actionName);
};
const renderRowActions = (rowIndex) => {
return (React.createElement(RowActions, { rowIndex: rowIndex, tableProps: props, onFocusRowAction: (actionName) => onFocusRowAction(rowIndex, actionName), onMoveDownClick: () => reorderTable(rowIndex, rowIndex + 1), onMoveUpClick: () => reorderTable(rowIndex, rowIndex - 1) }));
};
const renderRowData = (params) => (React.createElement(RowDataCells, { key: params.index ?? 0, isInRowLayout: isInRowLayout, row: params.value, rowIndex: params.index ?? 0, tableProps: props }));
/**
* Renders row.
*
* @param {NodeDragAndDropSortingParams} params
* @returns {React.ReactElement<UIFlexibleTableRowProps<T>, 'UIFlexibleTableRow'>}
*/
function renderRow(params) {
const rowIndex = params.index;
const { isDropWarning } = props.onRenderRowContainer
? props.onRenderRowContainer({
readonly: !!props.readonly,
rowIndex,
isDragged: params.isDragged
})
: { isDropWarning: false };
return (React.createElement(UIFlexibleTableRow, { key: `row-${rowIndex}`, dragAndDropParams: params, rowActions: renderRowActions(rowIndex ?? 0), rowData: renderRowData(params), tableProps: props, rowRef: typeof rowToNavigate === 'number' && rowIndex === rowToNavigate ? scrollTargetRef : undefined, className: isDropWarning ? 'highlight-drop-warning' : '' }));
}
const renderTable = (params) => {
const ulElement = params.props.ref?.current;
let { children } = params;
if (ulElement && scrollableElement.current !== ulElement) {
scrollableElement.current = ulElement;
scrollBarObserver.current.observe(ulElement);
}
if (props.rows.length === 0 && props.noDataText) {
children = (React.createElement(UIFlexibleTableRowNoData, { noRowBackground: props.noRowBackground, reverseBackground: props.reverseBackground }, props.noDataText));
}
const getCursorStyle = () => {
let cursorIcon = 'default';
if (props.onTableReorder) {
cursorIcon = params.isDragged ? 'grabbing' : 'grab';
}
return cursorIcon;
};
return (React.createElement("ul", { ref: params.props.ref, className: `flexible-table-content-table${params.isDragged ? ' dragged' : ''}`, style: {
cursor: getCursorStyle(),
maxHeight: props.maxScrollableContentHeight ? `${props.maxScrollableContentHeight}px` : undefined
} }, children));
};
const tableBody = getTableBody(props, renderTable, renderRow, reorderTable, tableBodyElementRef);
const tableClasses = composeClassNames('flexible-table', [
props.layout === UIFlexibleTableLayout.InlineFlex ? 'inline-layout' : 'wrapping-layout',
props.readonly ? 'readonly' : ''
]);
const showTitleRow = props.showColumnTitles && isInRowLayout && !props.showColumnTitlesInCells;
const tableRootElementStyle = {
maxWidth: props.maxWidth ? `${props.maxWidth}px` : '100%'
};
const getCustomTableActions = (actionsGenerator) => {
if (actionsGenerator) {
const customActions = actionsGenerator({ readonly: !!props.readonly });
return customActions.map((actionElement) => (React.createElement(React.Fragment, { key: `table-action-${actionElement.key}` },
React.createElement("div", { className: "flexible-table-header-action" }, actionElement))));
}
return [];
};
const primaryTableActions = getCustomTableActions(props.onRenderPrimaryTableActions);
const secondaryTableActions = getCustomTableActions(props.onRenderSecondaryTableActions);
const isEmptyHeader = !props.addRowButton && primaryTableActions.length + secondaryTableActions.length === 0;
const contentClasses = composeClassNames('flexible-table-content', [
props.rows.length === 0 && !props.noDataText ? 'empty-table' : '',
props.isContentLoading ? 'loading' : '',
isEmptyHeader ? 'empty-table-header' : ''
]);
return (React.createElement("div", { className: tableClasses, ref: tableRootElementRef, id: props.id, style: tableRootElementStyle, onBlur: () => {
onFocusRowAction();
} },
isEmptyHeader ? (React.createElement(React.Fragment, null)) : (React.createElement("div", { className: "flexible-table-header" },
React.createElement("div", { className: "flexible-table-header-primary-actions" },
props.addRowButton && (React.createElement("div", { className: "flexible-table-header-action" },
React.createElement(UIDefaultButton, { iconProps: { iconName: 'Add' }, className: "flexible-table-btn-add", id: getTableActionButtonId(props.id, 'add-row'), primary: true, disabled: props.isAddItemDisabled || props.isContentLoading || props.readonly, onClick: addNewRow, title: props.addRowButton.title, "aria-label": props.addRowButton.ariaLabel ?? props.addRowButton.label }, props.addRowButton.label))),
primaryTableActions),
React.createElement("div", { className: "flexible-table-header-secondary-actions" }, secondaryTableActions))),
React.createElement("div", { className: contentClasses, ref: contentElementRef },
showTitleRow && renderTitleRow(props, titleRowRightPadding),
tableBody,
props.isContentLoading && (React.createElement(UILoader, { className: 'uiLoaderXLarge flexible-table-overlay-loader', blockDOM: true })))));
}
/**
* Gets table body.
*
* @param {UIFlexibleTableProps<T>} props
* @param {(params: { children: React.ReactNode; isDragged: boolean; props: { ref?: React.RefObject<any>; }; }) => React.ReactNode} renderTable
* @param {( params: NodeDragAndDropSortingParams) => React.ReactElement<UIFlexibleTableRowProps<T>, 'UIFlexibleTableRow'>} renderRow
* @param { (oldIndex: number, newIndex: number) => void} reorderTable
* @param {React.MutableRefObject<any>} tableBodyElementRef
* @returns {React.ReactNode}
*/
function getTableBody(props, renderTable, renderRow, reorderTable, tableBodyElementRef) {
let tableBody;
const { rows } = props.onBeforeTableRender ? props.onBeforeTableRender({ rows: props.rows }) : { rows: props.rows };
if (rows.length > 0 && props.onTableReorder && !props.readonly) {
tableBody = (React.createElement(List, { values: rows, lockVertically: props.lockVertically, onChange: (params) => reorderTable(params.oldIndex, params.newIndex), renderList: (params) => renderTable(params), renderItem: (params) => renderRow(params), removableByMove: true, beforeDrag: props.onStartDragging }));
}
else {
const children = props.rows.map((row, index) => {
const rowProps = {
key: index
};
const dragAndDropParams = {
index,
isDragged: false,
isOutOfBounds: false,
isSelected: false,
props: rowProps,
value: row
};
return renderRow(dragAndDropParams);
});
tableBody = renderTable({ children, isDragged: false, props: { ...props, ref: tableBodyElementRef } });
}
return tableBody;
}
/**
* Restores focus.
*
* @param {string} currentFocusedRowAction
* @param {number | undefined} currentFocusedRowIndex
* @param {string} tableId
*/
function restoreFocus(currentFocusedRowAction, currentFocusedRowIndex, tableId) {
if (!currentFocusedRowAction) {
return;
}
let actionElement = document.getElementById(getRowActionButtonId(tableId, currentFocusedRowIndex, currentFocusedRowAction));
if (!actionElement) {
return;
}
if (!actionElement.hasAttribute('disabled')) {
actionElement.focus();
return;
}
const fallbackAction = currentFocusedRowAction === 'up' ? 'down' : 'up';
actionElement = document.getElementById(getRowActionButtonId(tableId, currentFocusedRowIndex, fallbackAction));
actionElement?.focus();
}
/**
* Check row fit container.
*
* @param {number | undefined} propThreshold
* @param {React.MutableRefObject<HTMLDivElement | null>} containerRef
* @returns {boolean}
*/
function isRowFitsContainer(propThreshold, containerRef) {
let result = true;
const inRowMinWidth = propThreshold ?? 600;
const containerWidth = containerRef?.current?.clientWidth ?? 0;
if (containerWidth) {
result = containerWidth >= inRowMinWidth;
}
return result;
}
//# sourceMappingURL=UIFlexibleTable.js.map