UNPKG

@progress/kendo-react-treelist

Version:

React TreeList enables the display of self-referencing tabular data. KendoReact TreeList package

587 lines (586 loc) 22.7 kB
/** * @license *------------------------------------------------------------------------------------------- * Copyright © 2026 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the package root for more information *------------------------------------------------------------------------------------------- */ import * as r from "react"; import n from "prop-types"; import { memoizeOne as A, getNestedValue as y, validatePackage as Z, getLicenseMessage as ee, noop as K, canUseDOM as te, getAbsoluteVirtualRowIndex as oe, getter as se, classNames as ne, WatermarkOverlay as ae } from "@progress/kendo-react-common"; import { readColumns as ie, mapColumns as le, tableKeyboardNavigation as S, getSelectionOptions as O, CommonDragLogic as re, ColumnResize as he, TableKeyboardNavigationContext as P, tableKeyboardNavigationTools as V, Header as de, FilterRow as ce, HeaderRow as pe, tableColumnsVirtualization as ge, tableKeyboardNavigationScopeAttributes as ue, TableSelection as me, tableKeyboardNavigationBodyAttributes as fe, DropClue as Ce, DragClue as ve, flatData as be } from "@progress/kendo-react-data-tools"; import { setHeaderRowsTop as Se, tableRowsVirtualization as we } from "./utils/index.mjs"; import { TreeListCell as Re } from "./cells/TreeListCell.mjs"; import { TreeListNoRecords as xe } from "./TreeListNoRecords.mjs"; import { TreeListRow as ye } from "./rows/TreeListRow.mjs"; import { packageMetadata as B } from "./package-metadata.mjs"; const M = class M extends r.Component { constructor(h) { super(h), this.element = null, this.wrapperScrollLeft = 0, this.wrapperScrollTop = 0, this.updateOnScroll = !1, this.tbodyOffsetTop = 0, this.prevData = [], this.flattedData = [], this.extendedColumn = [], this.columnsMap = [], this.contextStateRef = { current: void 0 }, this.navigationStateRef = { current: void 0 }, this.showLicenseWatermark = !1, this.licenseMessage = void 0, this.scrollIntoView = (e) => { if (!this.element) return; const { rowIndex: t = 0 } = e, { scrollable: o, rowHeight: s = 0 } = this.props; if (o === "virtual" || s) this.element.scroll(0, (t - 1) * s); else { const l = this.element.querySelector(`tbody > tr:nth-child(${t})`); if (l) { const i = l.offsetTop - this.tbodyOffsetTop; this.element.scroll(0, i); } } }, this.getExtendedColumn = A((e, t) => ie(e, { prevId: 0, idPrefix: t })), this.getColumnsMap = A((e, t) => le(e, t)), this.onKeyDown = (e) => { if (S.onKeyDown(e, { navigatable: !1, contextStateRef: this.contextStateRef, navigationStateRef: this.navigationStateRef }), this.props.onKeyDown) { const { mode: t, cell: o } = O(this.props.selectable), s = { dataItems: this.getLeafDataItems(), mode: t, cell: o, componentId: this._treeListId, selectedField: this.props.selectedField || "", ...this.getArguments(e) }; this.props.onKeyDown.call(void 0, s); } }, this.onFocus = (e) => { S.onFocus(e, { contextStateRef: this.contextStateRef }); }, this.onRowDrag = (e) => { this.props.onRowDrag && this.props.onRowDrag.call(void 0, { ...e, target: this }); }, this.onRowDrop = (e) => { this.props.onRowDrop && this.props.onRowDrop.call(void 0, { ...e, target: this }); }, this.columnReorder = (e, t, o) => { const s = this.extendedColumn[e].depth, l = (d) => { do d++; while (d < this.extendedColumn.length && this.extendedColumn[d].depth > s); return d; }, i = this.extendedColumn.splice(e, l(e) - e); if (this.extendedColumn.splice(e < t ? l(t - i.length) : t, 0, ...i), this.extendedColumn.filter((d) => d.declarationIndex >= 0).forEach((d, g) => d.orderIndex = g), this.props.onColumnReorder) { const d = { target: this, columns: this.columns, nativeEvent: o }; this.props.onColumnReorder.call(void 0, d); } }, this.onResize = (e, t, o, s, l) => { if (this.props.onColumnResize) { const i = this.extendedColumn.filter((g) => g.children.length === 0).reduce((g, u) => g += parseFloat(String(u.width)), 0), d = { columns: this.columns, totalWidth: i, index: e, nativeEvent: s, newWidth: t, oldWidth: o, end: l, target: this }; this.props.onColumnResize.call(void 0, d); } }, this.handleOnScroll = (e) => { const t = e.currentTarget.scrollLeft, o = e.currentTarget.scrollTop, { columnVirtualization: s, scrollable: l, rowHeight: i = 0 } = this.props, d = i, g = 0; let u = !1; s && Math.abs(this.wrapperScrollLeft - t) > g && (this.wrapperScrollLeft = t, u = !0), l === "virtual" && Math.abs(this.wrapperScrollTop - o) > d && (this.wrapperScrollTop = o, u = !0), u && (this.updateOnScroll = !0, this.forceUpdate()); }, this.calculateSizes = (e) => { if (!e || this.props.scrollable === "none") return; const t = Array.from(e.childNodes), o = t.find((i) => i.nodeName === "TABLE"), s = this.props.toolbar && t.find( (i) => i.nodeType === 1 && i.classList.contains("k-grid-toolbar") ); let l = 0; if (s) { const i = s.style.boxSizing; s.style.boxSizing = "border-box", l = parseFloat(String(window.getComputedStyle(s).height)) || s.offsetHeight, s.style.boxSizing = i, s.getAttribute("style") || s.removeAttribute("style"); } this.tbodyOffsetTop = o.tBodies[0].offsetTop, Se(o, l); }, this.itemChange = (e) => { const t = this.props.onItemChange; if (e.field === this.props.expandField) { const o = this.props.onExpandChange; if (o) { const s = { ...this.getArguments(e.syntheticEvent), dataItem: e.dataItem, level: e.level, value: e.value }; o.call(void 0, s); } return; } if (t) { const o = { ...this.getArguments(e.syntheticEvent), dataItem: e.dataItem, level: e.level, field: e.field, value: e.value }; t.call(void 0, o); } }, this.onHeaderSelectionChange = (e) => { if (this.props.onHeaderSelectionChange) { const t = { field: e.field, nativeEvent: e.syntheticEvent && e.syntheticEvent.nativeEvent, syntheticEvent: e.syntheticEvent, target: this, dataItems: this.getLeafDataItems() }; this.props.onHeaderSelectionChange.call(void 0, t); } }, this.selectionRelease = (e) => { if (this.props.onSelectionChange) { const t = { syntheticEvent: void 0, target: this, selectedField: this.props.selectedField || "", componentId: this._treeListId, dataItems: this.getLeafDataItems(), dataItem: null, level: [], ...e }; this.props.onSelectionChange.call(void 0, t); } }, this.sortChange = (e, t, o) => { this.raiseDataEvent(this.props.onSortChange, { sort: t, field: o }, e); }, this.headerFilterChange = (e, t, o) => { this.raiseDataEvent(this.props.onFilterChange, { filter: t, field: o }, e); }, this.filterChange = (e) => { const { filter: t, field: o } = e; this.raiseDataEvent(this.props.onFilterChange, { filter: t, field: o }, e.syntheticEvent); }, this.columnMenuFilterChange = (e, t, o) => { const { onColumnMenuFilterChange: s } = this.props; if (!s || !o) return; const l = { syntheticEvent: e, filter: t, field: o, target: this, nativeEvent: e.nativeEvent }; s.call(void 0, l); }, this.expandChange = (e, t, o) => { const { expandField: s, onExpandChange: l } = this.props; if (s && l) { const i = { ...this.getArguments(e), dataItem: t, level: o, value: this.expanded(t) }; l.call(void 0, i); } }, this.rowClick = (e, t) => { if (this.props.onRowClick && e.target.nodeName === "TD") { const o = { dataItem: t.dataItem, level: t.level, ...this.getArguments(e) }; this.props.onRowClick.call(void 0, o); } }, this.rowDoubleClick = (e, t) => { if (this.props.onRowDoubleClick && e.target.nodeName === "TD") { const o = { dataItem: t.dataItem, level: t.level, ...this.getArguments(e) }; this.props.onRowDoubleClick.call(void 0, o); } }, this.rowContextMenu = (e, t) => { if (this.props.onRowContextMenu && e.target.nodeName === "TD") { const o = { dataItem: t.dataItem, level: t.level, ...this.getArguments(e) }; this.props.onRowContextMenu.call(void 0, o); } }, this.onPageChange = (e) => { if (this.props.onPageChange) { const t = { ...this.getArguments(e.syntheticEvent), skip: e.skip, take: e.take }; this.props.onPageChange.call(void 0, t); } }, this.expandedSubItems = (e) => { const t = []; return this.expanded(e) && this.hasChildren(e) && t.push(...y(this.props.subItemsField, e)), t; }, this.getLeafDataItems = () => this.flatData.map((e) => e.dataItem), this.expanded = (e) => !!y(this.props.expandField, e), this.hasChildren = (e) => !!y(this.props.subItemsField, e), this.showLicenseWatermark = !Z(B, { component: "TreeList" }), this.licenseMessage = ee(B), this.dragLogic = new re(this.columnReorder, K, K), this.columnResize = new he(this.onResize.bind(this)), S.onConstructor({ navigatable: !!h.navigatable, contextStateRef: this.contextStateRef, navigationStateRef: this.navigationStateRef }); } get _treeListId() { return this.props.id + "-treelist"; } get document() { if (te) return this.element && this.element.ownerDocument || document; } /** * @hidden */ componentDidMount() { this.calculateSizes(this.element), S.onComponentDidMount({ scope: this.element || void 0, contextStateRef: this.contextStateRef, navigationStateRef: this.navigationStateRef }); } /** * @hidden */ getSnapshotBeforeUpdate() { return S.onGetSnapshotBeforeUpdate({ document: this.document, contextStateRef: this.contextStateRef, navigationStateRef: this.navigationStateRef }), null; } /** * @hidden */ componentDidUpdate(h) { h.columns !== this.props.columns && this.calculateSizes(this.element), S.onComponentDidUpdate({ scope: this.element || void 0, contextStateRef: this.contextStateRef, navigationStateRef: this.navigationStateRef }); } /** * @hidden */ componentWillUnmount() { this.columnsMap = [], this.prevData = [], this.flattedData = [], this.updateOnScroll = !1, this.getExtendedColumn.clear(), this.getColumnsMap.clear(); } /** * @hidden */ render() { const { columns: h = [], filterRow: e, scrollable: t = "scrollable", resizable: o = !1, reorderable: s = !1, skip: l, take: i } = this.props, d = h.some((a) => !!a.filter || !!a.filterCell) || e !== void 0, g = e || ce, u = V.getIdPrefix(this.navigationStateRef), L = this.getExtendedColumn(h, u), T = L.length !== this.extendedColumn.length; this.extendedColumn = L, this.columnsMap = this.getColumnsMap(this.extendedColumn, T); const m = this.extendedColumn.filter((a) => a.children.length === 0); this.columnResize.columns = this.extendedColumn, this.columnResize.resizable = o, this.dragLogic.columns = this.extendedColumn, this.dragLogic.reorderable = s, this.dragLogic.groupable = !1; const I = /* @__PURE__ */ r.createElement( de, { headerRow: /* @__PURE__ */ r.createElement( pe, { sort: this.props.sort, sortable: this.props.sortable, sortChange: this.sortChange, selectionChange: this.onHeaderSelectionChange, columns: this.extendedColumn, columnsMap: this.columnsMap, cellRender: this.props.headerCellRender, columnResize: this.columnResize, columnMenu: this.props.columnMenu, columnMenuFilter: this.props.columnMenuFilter, columnMenuFilterChange: this.columnMenuFilterChange, pressHandler: this.dragLogic.pressHandler, dragHandler: this.dragLogic.dragHandler, releaseHandler: this.dragLogic.releaseHandler, filterChange: this.headerFilterChange } ), reorderable: this.props.reorderable, filterRow: d && /* @__PURE__ */ r.createElement( g, { columns: m, filter: this.props.filter, filterChange: this.filterChange, sort: this.props.sort, ariaRowIndex: this.columnsMap.length + 1 } ) || void 0, columnResize: this.columnResize } ), z = this.props.style || {}, { colSpans: W, hiddenColumns: $ } = ge({ enabled: this.props.columnVirtualization, columns: m, scrollLeft: this.wrapperScrollLeft, tableViewPortWidth: parseFloat((z.width || "").toString()) }), j = (a, v, b, D, E, w) => m.map((c, p) => { if ($[p]) return null; const C = c.id ? c.id : p, k = `${c.className ? c.className + " " : ""}${c.locked ? "k-grid-content-sticky" : ""}`, R = { id: V.generateNavigatableId(`${b}-${String(p)}`, u), colSpan: W[p], dataItem: a.dataItem, field: c.field, format: c.format, className: k || void 0, render: this.props.cellRender, onChange: this.itemChange, selectionChange: this.props.onSelectionChange ? (F) => { this.selectionChange({ event: F, item: a, columnIndex: p, dataIndex: E }); } : void 0, level: a.level, expandable: c.expandable, expanded: D, hasChildren: this.hasChildren(a.dataItem), onExpandChange: this.expandChange, colIndex: p, ariaColumnIndex: c.ariaColumnIndex, style: c.left !== void 0 && { left: c.left, right: c.right, borderRightWidth: c.rightBorder ? "1px" : "" } || {}, isSelected: Array.isArray(w) && w.indexOf(p) > -1 }; return v && c.editCell ? /* @__PURE__ */ r.createElement(c.editCell, { key: C, ...R, onChange: this.itemChange }) : c.cell ? /* @__PURE__ */ r.createElement(c.cell, { key: C, ...R }) : /* @__PURE__ */ r.createElement(Re, { key: C, ...R }); }); let f = this.flatData; const N = f.length; l !== void 0 && i !== void 0 && (f = f.slice(l, l + i)), t === "virtual" && (f = we({ rows: f, tableViewPortHeight: parseFloat((z.height || z.maxHeight || "").toString()), scrollTop: this.wrapperScrollTop }), this.updateOnScroll = !1); const G = f.map((a) => a.level), _ = this.columnsMap.length + (d ? 1 : 0) + 1, q = f.length > 0 && f.map((a, v) => { const b = oe(t, a, v), D = y(this.props.editField, a.dataItem), E = this.props.dataItemKey && se(this.props.dataItemKey)(a.dataItem), w = String(E || a.level.join(".")), c = this.expanded(a.dataItem), p = this.props.selectedField ? y(this.props.selectedField, a.dataItem) : void 0, C = { key: w, level: a.level, levels: G, dataItem: a.dataItem, selectedField: this.props.selectedField, rowHeight: t === "virtual" ? a.height : this.props.rowHeight, render: this.props.rowRender, onDrop: this.onRowDrop, onDrag: this.onRowDrag, onClick: (x) => this.rowClick(x, a), onDoubleClick: (x) => this.rowDoubleClick(x, a), onContextMenu: (x) => this.rowContextMenu(x, a), isAltRow: b % 2 !== 0, expanded: c, rowIndex: b, ariaRowIndex: _ + b, ariaSetSize: a.levelCount, ariaPosInSet: a.level[a.level.length - 1] + 1, isSelected: typeof p == "boolean" && p }, k = this.props.editRow, R = this.props.row || ye, F = j(a, D, w, c, b, p); return D && k ? /* @__PURE__ */ r.createElement(k, { ...C, key: C.key }, F) : /* @__PURE__ */ r.createElement(R, { ...C, key: C.key }, F); }) || /* @__PURE__ */ r.createElement("tr", { className: "k-table-row k-grid-norecords" }, /* @__PURE__ */ r.createElement("td", { colSpan: m.length }, this.props.noRecords || /* @__PURE__ */ r.createElement(xe, null))), J = (a) => this.props.sort && this.props.sort.some((v) => v.field === a), Q = /* @__PURE__ */ r.createElement( "colgroup", { ref: (a) => { this.columnResize.colGroupMain = a; } }, m.map((a, v) => /* @__PURE__ */ r.createElement( "col", { key: v.toString(), className: J(a.field) ? "k-sorted" : void 0, style: a.width !== void 0 ? { width: a.width } : void 0 } )) ), X = this.props.columnVirtualization || this.props.scrollable === "virtual", Y = this.props.selectable && this.props.selectable.drag ? "none" : void 0, H = this.props.tableProps || {}; return /* @__PURE__ */ r.createElement(P.Provider, { value: this.contextStateRef.current }, /* @__PURE__ */ r.createElement( "div", { id: this.props.id, style: this.props.style, className: ne("k-grid", "k-treelist", this.props.className, { "k-treelist-scrollable": t !== "none" }), ref: (a) => { this.element = a; }, onScroll: X ? this.handleOnScroll : void 0, onKeyDown: this.onKeyDown, onFocus: this.onFocus, "aria-rowcount": N, "aria-colcount": m.length, role: "treegrid", ...ue }, this.props.toolbar, /* @__PURE__ */ r.createElement(me, { selectable: this.props.selectable, onRelease: this.selectionRelease }, /* @__PURE__ */ r.createElement( "table", { className: "k-table k-grid-table", ...H, style: { ...H.style || {}, userSelect: Y }, role: "presentation" }, Q, I, /* @__PURE__ */ r.createElement( "tbody", { className: "k-table-tbody", ...fe, role: "presentation" }, q ) )), this.props.pager && /* @__PURE__ */ r.createElement( this.props.pager, { className: "k-grid-pager", total: N, skip: l, take: i, onPageChange: this.onPageChange } ), s && /* @__PURE__ */ r.createElement(r.Fragment, null, /* @__PURE__ */ r.createElement(Ce, { ref: this.dragLogic.refDropElementClue }), /* @__PURE__ */ r.createElement(ve, { ref: this.dragLogic.refDragElementClue })), this.showLicenseWatermark && /* @__PURE__ */ r.createElement(ae, { message: this.licenseMessage }) )); } get columns() { const h = this.extendedColumn.filter((t) => t.declarationIndex >= 0 && t.parentIndex === -1), e = (t) => (t.sort((o, s) => o.declarationIndex - s.declarationIndex), t.map((o) => { const { declarationIndex: s, parentIndex: l, depth: i, colSpan: d, rowSpan: g, index: u, kFirst: L, groupable: T, children: m, ...I } = o; return m.length ? { children: e(m), ...I } : I; })); return e(h); } get flatData() { const { data: h = [], rowHeight: e = 0 } = this.props; let t = 0; const o = (l) => { const i = { height: e, offsetTop: t }; return t += i.height, i; }, s = this.updateOnScroll && this.prevData === h && this.tbodyOffsetTop > 0 && this.flattedData.length ? this.flattedData : be(h, this.expandedSubItems, o); return this.prevData = h, this.flattedData = s, s; } selectionChange(h) { if (this.props.onSelectionChange) { const { event: e, item: t, dataIndex: o, columnIndex: s } = h, { mode: l, cell: i } = O(this.props.selectable), d = { ...this.getArguments(e.syntheticEvent), dataItem: t.dataItem, level: t.level, startColIndex: s, endColIndex: s, startRowIndex: o, endRowIndex: o, dataItems: this.getLeafDataItems(), altKey: !1, ctrlKey: !1, shiftKey: !1, metaKey: !1, mode: l, cell: i, isDrag: !1, componentId: this._treeListId, selectedField: this.props.selectedField || "" }; this.props.onSelectionChange.call(void 0, d); } } raiseDataEvent(h, e, t) { const o = this.props.onDataStateChange; if (h) h.call(void 0, { ...this.getArguments(t), ...e }); else if (o) { const s = { ...this.getArguments(t), dataState: { ...this.getDataState(), ...e } }; o.call(void 0, s); } } getDataState() { return { filter: this.props.filter, sort: this.props.sort }; } getArguments(h) { return { nativeEvent: h && h.nativeEvent, syntheticEvent: h, target: this }; } }; M.propTypes = { data: n.array, resizable: n.bool, reorderable: n.bool, sortable: n.oneOfType([ n.bool, n.shape({ mode: n.oneOf(["single", "multiple"]), allowUnsort: n.bool }) ]), onSortChange: n.func, sort: n.array, columns: n.arrayOf(n.object), columnVirtualization: n.bool, filter: n.array, onFilterChange: n.func, filterRow: n.any, toolbar: n.any, noRecords: n.any, onExpandChange: n.func, expandField: n.string, subItemsField: n.string, selectedField: n.string, onSelectionChange: n.func, onHeaderSelectionChange: n.func, onRowClick: n.func, onItemChange: n.func, editField: n.string, scrollable: n.oneOf(["none", "scrollable", "virtual"]), rowHeight: n.number, style: n.object, tableProps: n.object, pager: n.any, skip: n.number, take: n.number, onPageChange: n.func, onDataStateChange: n.func, onColumnResize: n.func, onColumnReorder: n.func, dataItemKey: n.string, navigatable: n.bool }, M.contextType = P; let U = M; export { U as TreeList };