UNPKG

@gravity-ui/uikit

Version:

Gravity UI base styling and components

291 lines (290 loc) 14.2 kB
'use client'; "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Table = void 0; const tslib_1 = require("tslib"); const jsx_runtime_1 = require("react/jsx-runtime"); const React = tslib_1.__importStar(require("react")); const get_1 = tslib_1.__importDefault(require("lodash/get.js")); const has_1 = tslib_1.__importDefault(require("lodash/has.js")); const isNumber_1 = tslib_1.__importDefault(require("lodash/isNumber.js")); const cn_1 = require("../utils/cn.js"); const filterDOMProps_1 = require("../utils/filterDOMProps.js"); const warn_1 = require("../utils/warn.js"); const i18n_1 = tslib_1.__importDefault(require("./i18n/index.js")); require("./Table.css"); const DASH = '\u2014'; function warnAboutPhysicalValues(propName) { (0, warn_1.warnOnce)(`[Table] Physical values (left, right) of "${propName}" property are deprecated. Use logical values (start, end) instead.`); } function normalizeSides(side, propName) { if (side === 'left') { warnAboutPhysicalValues(propName); return 'start'; } if (side === 'right') { warnAboutPhysicalValues(propName); return 'end'; } return side; } const b = (0, cn_1.block)('table'); class Table extends React.Component { static defaultProps = { edgePadding: true, }; // Static methods may be used by HOCs static getRowId(props, item, rowIndex) { const { data, getRowId, getRowDescriptor } = props; const index = rowIndex ?? data.indexOf(item); const descriptor = getRowDescriptor?.(item, index); if (descriptor?.id !== undefined) { return descriptor.id; } if (typeof getRowId === 'function') { return getRowId(item, index); } if (getRowId && getRowId in item) { return String(item[getRowId]); } return String(index); } static getHeadCellContent(column) { const { id, name } = column; let content; if (typeof name === 'function') { content = name(); } else if (typeof name === 'string') { content = name; } else { content = id; } return content; } static getBodyCellContent(column, item, rowIndex) { const { id, template, placeholder } = column; let placeholderValue; if (typeof placeholder === 'function') { placeholderValue = placeholder(item, rowIndex); } else { placeholderValue = placeholder ?? DASH; } let value; if (typeof template === 'function') { value = template(item, rowIndex); } else if (typeof template === 'string') { value = (0, get_1.default)(item, template); } else if ((0, has_1.default)(item, id)) { value = (0, get_1.default)(item, id); } if ([undefined, null, ''].includes(value) && placeholderValue) { return placeholderValue; } return value; } static getDerivedStateFromProps(props, state) { if (props.columns.length === state.columnHeaderRefs.length) { return null; } return { columnHeaderRefs: Array.from(props.columns, () => React.createRef()), }; } state = { activeScrollElement: 'scrollContainer', columnsStyles: Array.from(this.props.columns, () => ({})), columnHeaderRefs: Array.from(this.props.columns, () => React.createRef()), }; tableRef = React.createRef(); scrollContainerRef = React.createRef(); horizontalScrollBarRef = React.createRef(); horizontalScrollBarInnerRef = React.createRef(); tableResizeObserver; columnsResizeObserver; componentDidMount() { if (this.props.stickyHorizontalScroll) { this.tableResizeObserver = new ResizeObserver((entries) => { const { contentRect } = entries[0]; // Sync scrollbar width with table width this.horizontalScrollBarInnerRef.current?.style.setProperty('width', `${contentRect.width}px`); }); if (this.tableRef.current) { this.tableResizeObserver.observe(this.tableRef.current); } if (this.scrollContainerRef.current) { this.scrollContainerRef.current.addEventListener('scroll', this.handleScrollContainerScroll); this.scrollContainerRef.current.addEventListener('mouseenter', this.handleScrollContainerMouseenter); } if (this.horizontalScrollBarRef.current) { this.horizontalScrollBarRef.current.addEventListener('scroll', this.handleHorizontalScrollBarScroll); this.horizontalScrollBarRef.current.addEventListener('mouseenter', this.handleHorizontalScrollBarMouseenter); } } this.columnsResizeObserver = new ResizeObserver((entries) => { // fix ResizeObserver loop error window.requestAnimationFrame(() => { if (!Array.isArray(entries) || !entries.length) { return; } this.updateColumnStyles(); }); }); if (this.tableRef.current) { this.columnsResizeObserver.observe(this.tableRef.current); } this.updateColumnStyles(); } componentDidUpdate(prevProps) { if (this.props.columns !== prevProps.columns) { this.updateColumnStyles(); } } componentWillUnmount() { if (this.props.stickyHorizontalScroll) { if (this.tableResizeObserver) { this.tableResizeObserver.disconnect(); } if (this.scrollContainerRef.current) { this.scrollContainerRef.current.removeEventListener('scroll', this.handleScrollContainerScroll); this.scrollContainerRef.current.removeEventListener('mouseenter', this.handleScrollContainerMouseenter); } if (this.horizontalScrollBarRef.current) { this.horizontalScrollBarRef.current.removeEventListener('scroll', this.handleHorizontalScrollBarScroll); this.horizontalScrollBarRef.current.removeEventListener('mouseenter', this.handleHorizontalScrollBarMouseenter); } } if (this.columnsResizeObserver) { this.columnsResizeObserver.disconnect(); } } render() { const { columns, stickyHorizontalScroll, className, qa } = this.props; const withPrimary = columns.some(({ primary }) => primary); return ((0, jsx_runtime_1.jsx)("div", { className: b({ 'with-primary': withPrimary, 'with-sticky-scroll': stickyHorizontalScroll, }, className), "data-qa": qa, children: stickyHorizontalScroll ? ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { ref: this.scrollContainerRef, className: b('scroll-container'), children: this.renderTable() }), this.renderHorizontalScrollBar()] })) : (this.renderTable()) })); } renderHead() { const { columns, edgePadding, wordWrap } = this.props; const { columnsStyles } = this.state; return ((0, jsx_runtime_1.jsx)("thead", { className: b('head'), children: (0, jsx_runtime_1.jsx)("tr", { className: b('row'), children: columns.map((column, index) => { const { id, align: rawAlign, primary, sticky: rawSticky, className } = column; const align = normalizeSides(rawAlign, 'column.align'); const sticky = normalizeSides(rawSticky, 'column.sticky'); const content = Table.getHeadCellContent(column); return ((0, jsx_runtime_1.jsx)("th", { ref: this.state.columnHeaderRefs[index], style: columnsStyles[index], className: b('cell', { align, primary, sticky, ['edge-padding']: edgePadding, ['word-wrap']: wordWrap, }, className), children: content }, id)); }) }) })); } renderBody() { const { data } = this.props; return ((0, jsx_runtime_1.jsx)("tbody", { className: b('body'), children: data.length > 0 ? data.map(this.renderRow) : this.renderEmptyRow() })); } renderTable() { const { width = 'auto' } = this.props; return ((0, jsx_runtime_1.jsxs)("table", { ...(0, filterDOMProps_1.filterDOMProps)(this.props, { labelable: true }), ref: this.tableRef, className: b('table', { width }), children: [this.renderHead(), this.renderBody()] })); } renderRow = (item, rowIndex) => { const { columns, isRowDisabled, onRowClick, onRowMouseEnter, onRowMouseLeave, onRowMouseDown, getRowClassNames, verticalAlign, edgePadding, wordWrap, getRowDescriptor, qa, } = this.props; const { columnsStyles } = this.state; const descriptor = getRowDescriptor?.(item, rowIndex); const disabled = descriptor?.disabled || isRowDisabled?.(item, rowIndex) || false; const additionalClassNames = descriptor?.classNames || getRowClassNames?.(item, rowIndex) || []; const interactive = Boolean(!disabled && (descriptor?.interactive || onRowClick)); return ((0, jsx_runtime_1.jsx)("tr", { onClick: !disabled && onRowClick ? onRowClick.bind(null, item, rowIndex) : undefined, onMouseEnter: !disabled && onRowMouseEnter ? onRowMouseEnter.bind(null, item, rowIndex) : undefined, onMouseLeave: !disabled && onRowMouseLeave ? onRowMouseLeave.bind(null, item, rowIndex) : undefined, onMouseDown: !disabled && onRowMouseDown ? onRowMouseDown.bind(null, item, rowIndex) : undefined, className: b('row', { disabled, interactive, 'vertical-align': verticalAlign }, additionalClassNames.join(' ')), "data-qa": qa && `${qa}-row-${rowIndex}`, children: columns.map((column, colIndex) => { const { id, align: rawAlign, primary, className, sticky: rawSticky } = column; const content = Table.getBodyCellContent(column, item, rowIndex); const align = normalizeSides(rawAlign, 'column.align'); const sticky = normalizeSides(rawSticky, 'column.sticky'); return ((0, jsx_runtime_1.jsx)("td", { style: columnsStyles[colIndex], className: b('cell', { align, primary, sticky, ['edge-padding']: edgePadding, ['word-wrap']: wordWrap, }, className), children: content }, id)); }) }, Table.getRowId(this.props, item, rowIndex))); }; renderEmptyRow() { const { columns, emptyMessage } = this.props; return ((0, jsx_runtime_1.jsx)("tr", { className: b('row', { empty: true }), children: (0, jsx_runtime_1.jsx)("td", { className: b('cell'), colSpan: columns.length, children: emptyMessage ? emptyMessage : (0, i18n_1.default)('label_empty') }) })); } renderHorizontalScrollBar() { const { stickyHorizontalScroll, stickyHorizontalScrollBreakpoint = 0 } = this.props; return ((0, jsx_runtime_1.jsx)("div", { ref: this.horizontalScrollBarRef, className: b('horizontal-scroll-bar', { 'sticky-horizontal-scroll': stickyHorizontalScroll, }), style: { bottom: `${stickyHorizontalScrollBreakpoint}px` }, "data-qa": "sticky-horizontal-scroll-breakpoint-qa", children: (0, jsx_runtime_1.jsx)("div", { ref: this.horizontalScrollBarInnerRef, className: b('horizontal-scroll-bar-inner') }) })); } updateColumnStyles() { this.setState((prevState) => { const columnsWidth = prevState.columnHeaderRefs.map((ref) => ref.current === null ? undefined : ref.current.getBoundingClientRect().width); const columnsStyles = this.props.columns.map((_, index) => this.getColumnStyles(index, columnsWidth)); return { columnsStyles }; }); } getColumnStyles(index, columnsWidth) { const { columns } = this.props; const column = columns[index]; const style = {}; if (typeof column.width === 'string') { return { maxWidth: 0, width: column.width }; } if (typeof column.width !== 'undefined') { style.width = column.width; } if (!column.sticky) { return style; } const filteredColumns = column.sticky === 'left' || column.sticky === 'start' ? columnsWidth.slice(0, index) : columnsWidth.slice(index + 1); const styleName = column.sticky === 'left' || column.sticky === 'start' ? 'insetInlineStart' : 'insetInlineEnd'; style[styleName] = filteredColumns.reduce((start, width) => { return (0, isNumber_1.default)(width) ? start + width : start; }, 0); return style; } handleScrollContainerMouseenter = () => { this.setState({ activeScrollElement: 'scrollContainer' }); }; handleScrollContainerScroll = () => { if (this.state.activeScrollElement === 'scrollContainer' && this.horizontalScrollBarRef.current && this.scrollContainerRef.current) { this.horizontalScrollBarRef.current.scrollLeft = this.scrollContainerRef.current.scrollLeft; } }; handleHorizontalScrollBarMouseenter = () => { this.setState({ activeScrollElement: 'scrollBar' }); }; handleHorizontalScrollBarScroll = () => { if (this.state.activeScrollElement === 'scrollBar' && this.horizontalScrollBarRef.current && this.scrollContainerRef.current) { this.scrollContainerRef.current.scrollLeft = this.horizontalScrollBarRef.current.scrollLeft; } }; } exports.Table = Table; //# sourceMappingURL=Table.js.map