UNPKG

@progress/kendo-react-treelist

Version:

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

587 lines (586 loc) 22.6 kB
/** * @license *------------------------------------------------------------------------------------------- * Copyright © 2025 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 H, getNestedValue as x, validatePackage as Y, getLicenseMessage as Z, noop as A, canUseDOM as ee, getter as te, classNames as oe, WatermarkOverlay as se } from "@progress/kendo-react-common"; import { readColumns as ne, mapColumns as ae, tableKeyboardNavigation as R, getSelectionOptions as K, CommonDragLogic as ie, ColumnResize as le, TableKeyboardNavigationContext as O, tableKeyboardNavigationTools as P, Header as re, HeaderRow as he, tableColumnsVirtualization as de, tableKeyboardNavigationScopeAttributes as ce, TableSelection as pe, tableKeyboardNavigationBodyAttributes as ge, DropClue as me, DragClue as ue, flatData as fe, FilterRow as Ce } from "@progress/kendo-react-data-tools"; import { setHeaderRowsTop as ve, tableRowsVirtualization as be } from "./utils/index.mjs"; import { TreeListCell as Se } from "./cells/TreeListCell.mjs"; import { TreeListNoRecords as Re } from "./TreeListNoRecords.mjs"; import { TreeListRow as we } from "./rows/TreeListRow.mjs"; import { packageMetadata as B } from "./package-metadata.mjs"; const k = class k 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 = H((e, t) => ne(e, { prevId: 0, idPrefix: t })), this.getColumnsMap = H((e, t) => ae(e, t)), this.onKeyDown = (e) => { if (R.onKeyDown(e, { navigatable: !1, contextStateRef: this.contextStateRef, navigationStateRef: this.navigationStateRef }), this.props.onKeyDown) { const { mode: t, cell: o } = K(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) => { R.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 = (c) => { do c++; while (c < this.extendedColumn.length && this.extendedColumn[c].depth > s); return c; }, i = this.extendedColumn.splice(e, l(e) - e); if (this.extendedColumn.splice(e < t ? l(t - i.length) : t, 0, ...i), this.extendedColumn.filter((c) => c.declarationIndex >= 0).forEach((c, g) => c.orderIndex = g), this.props.onColumnReorder) { const c = { target: this, columns: this.columns, nativeEvent: o }; this.props.onColumnReorder.call(void 0, c); } }, 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), c = { columns: this.columns, totalWidth: i, index: e, nativeEvent: s, newWidth: t, oldWidth: o, end: l, target: this }; this.props.onColumnResize.call(void 0, c); } }, this.handleOnScroll = (e) => { const t = e.currentTarget.scrollLeft, o = e.currentTarget.scrollTop, { columnVirtualization: s, scrollable: l, rowHeight: i = 0 } = this.props, c = 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) > c && (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, ve(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) 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(...x(this.props.subItemsField, e)), t; }, this.getLeafDataItems = () => this.flatData.map((e) => e.dataItem), this.expanded = (e) => !!x(this.props.expandField, e), this.hasChildren = (e) => !!x(this.props.subItemsField, e), this.showLicenseWatermark = !Y(B, { component: "TreeList" }), this.licenseMessage = Z(B), this.dragLogic = new ie(this.columnReorder, A, A), this.columnResize = new le(this.onResize.bind(this)), R.onConstructor({ navigatable: !!h.navigatable, contextStateRef: this.contextStateRef, navigationStateRef: this.navigationStateRef }); } get _treeListId() { return this.props.id + "-treelist"; } get document() { if (ee) return this.element && this.element.ownerDocument || document; } /** * @hidden */ componentDidMount() { this.calculateSizes(this.element), R.onComponentDidMount({ scope: this.element || void 0, contextStateRef: this.contextStateRef, navigationStateRef: this.navigationStateRef }); } /** * @hidden */ getSnapshotBeforeUpdate() { return R.onGetSnapshotBeforeUpdate({ document: this.document, contextStateRef: this.contextStateRef, navigationStateRef: this.navigationStateRef }), null; } /** * @hidden */ componentDidUpdate(h) { h.columns !== this.props.columns && this.calculateSizes(this.element), R.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, c = h.some((a) => !!a.filter || !!a.filterCell) || e !== void 0, g = e || Ce, u = P.getIdPrefix(this.navigationStateRef), F = this.getExtendedColumn(h, u), z = F.length !== this.extendedColumn.length; this.extendedColumn = F, this.columnsMap = this.getColumnsMap(this.extendedColumn, z); const f = 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 y = /* @__PURE__ */ r.createElement( re, { headerRow: /* @__PURE__ */ r.createElement( he, { 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: c && /* @__PURE__ */ r.createElement( g, { columns: f, filter: this.props.filter, filterChange: this.filterChange, sort: this.props.sort, ariaRowIndex: this.columnsMap.length + 1 } ) || void 0, columnResize: this.columnResize } ), M = this.props.style || {}, { colSpans: U, hiddenColumns: W } = de({ enabled: this.props.columnVirtualization, columns: f, scrollLeft: this.wrapperScrollLeft, tableViewPortWidth: parseFloat((M.width || "").toString()) }), $ = (a, m, D, I, E, w) => f.map((d, p) => { if (W[p]) return null; const b = d.id ? d.id : p, L = `${d.className ? d.className + " " : ""}${d.locked ? "k-grid-content-sticky" : ""}`, S = { id: P.generateNavigatableId(`${D}-${String(p)}`, u), colSpan: U[p], dataItem: a.dataItem, field: d.field, format: d.format, className: L || void 0, render: this.props.cellRender, onChange: this.itemChange, selectionChange: this.props.onSelectionChange ? (v) => { this.selectionChange({ event: v, item: a, columnIndex: p, dataIndex: E }); } : void 0, level: a.level, expandable: d.expandable, expanded: I, hasChildren: this.hasChildren(a.dataItem), onExpandChange: this.expandChange, colIndex: p, ariaColumnIndex: d.ariaColumnIndex, style: d.left !== void 0 && { left: d.left, right: d.right, borderRightWidth: d.rightBorder ? "1px" : "" } || {}, isSelected: Array.isArray(w) && w.indexOf(p) > -1 }; return m && d.editCell ? /* @__PURE__ */ r.createElement(d.editCell, { key: b, ...S, onChange: this.itemChange }) : d.cell ? /* @__PURE__ */ r.createElement(d.cell, { key: b, ...S }) : /* @__PURE__ */ r.createElement(Se, { key: b, ...S }); }); let C = this.flatData; const T = C.length; l !== void 0 && i !== void 0 && (C = C.slice(l, l + i)), t === "virtual" && (C = be({ rows: C, tableViewPortHeight: parseFloat((M.height || M.maxHeight || "").toString()), scrollTop: this.wrapperScrollTop }), this.updateOnScroll = !1); const j = C.map((a) => a.level), G = this.columnsMap.length + (c ? 1 : 0) + 1, _ = C.length > 0 && C.map((a, m) => { const D = x(this.props.editField, a.dataItem), I = this.props.dataItemKey && te(this.props.dataItemKey)(a.dataItem), E = String(I || a.level.join(".")), w = this.expanded(a.dataItem), d = this.props.selectedField ? x(this.props.selectedField, a.dataItem) : void 0, p = { key: E, level: a.level, levels: j, 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: (v) => this.rowClick(v, a), onDoubleClick: (v) => this.rowDoubleClick(v, a), onContextMenu: (v) => this.rowContextMenu(v, a), isAltRow: m % 2 !== 0, expanded: w, rowIndex: m, ariaRowIndex: G + m, ariaSetSize: a.levelCount, ariaPosInSet: a.level[a.level.length - 1] + 1, isSelected: typeof d == "boolean" && d }, b = this.props.editRow, L = this.props.row || we, S = $(a, D, E, w, m, d); return D && b ? /* @__PURE__ */ r.createElement(b, { ...p, key: p.key }, S) : /* @__PURE__ */ r.createElement(L, { ...p, key: p.key }, S); }) || /* @__PURE__ */ r.createElement("tr", { className: "k-table-row k-grid-norecords" }, /* @__PURE__ */ r.createElement("td", { colSpan: f.length }, this.props.noRecords || /* @__PURE__ */ r.createElement(Re, null))), q = (a) => this.props.sort && this.props.sort.some((m) => m.field === a), J = /* @__PURE__ */ r.createElement( "colgroup", { ref: (a) => { this.columnResize.colGroupMain = a; } }, f.map((a, m) => /* @__PURE__ */ r.createElement( "col", { key: m.toString(), className: q(a.field) ? "k-sorted" : void 0, style: a.width !== void 0 ? { width: a.width } : void 0 } )) ), Q = this.props.columnVirtualization || this.props.scrollable === "virtual", X = this.props.selectable && this.props.selectable.drag ? "none" : void 0, N = this.props.tableProps || {}; return /* @__PURE__ */ r.createElement(O.Provider, { value: this.contextStateRef.current }, /* @__PURE__ */ r.createElement( "div", { id: this.props.id, style: this.props.style, className: oe("k-grid k-grid-md", "k-treelist", this.props.className, { "k-treelist-scrollable": t !== "none" }), ref: (a) => { this.element = a; }, onScroll: Q ? this.handleOnScroll : void 0, onKeyDown: this.onKeyDown, onFocus: this.onFocus, "aria-rowcount": T, "aria-colcount": f.length, role: "treegrid", ...ce }, this.props.toolbar, /* @__PURE__ */ r.createElement(pe, { selectable: this.props.selectable, onRelease: this.selectionRelease }, /* @__PURE__ */ r.createElement( "table", { className: "k-table k-table-md k-grid-table", ...N, style: { ...N.style || {}, userSelect: X }, role: "presentation" }, J, y, /* @__PURE__ */ r.createElement( "tbody", { className: "k-table-tbody", ...ge, role: "presentation" }, _ ) )), this.props.pager && /* @__PURE__ */ r.createElement( this.props.pager, { className: "k-grid-pager", total: T, skip: l, take: i, onPageChange: this.onPageChange } ), s && /* @__PURE__ */ r.createElement(r.Fragment, null, /* @__PURE__ */ r.createElement(me, { ref: this.dragLogic.refDropElementClue }), /* @__PURE__ */ r.createElement(ue, { ref: this.dragLogic.refDragElementClue })), this.showLicenseWatermark && /* @__PURE__ */ r.createElement(se, { 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: c, rowSpan: g, index: u, kFirst: F, groupable: z, children: f, ...y } = o; return f.length ? { children: e(f), ...y } : y; })); 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 : fe(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 } = K(this.props.selectable), c = { ...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, c); } } 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 }; } }; k.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 }, k.contextType = O; let V = k; export { V as TreeList };