@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
JavaScript
/**
* @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
};