@semcore/data-table 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/lib/cjs/Body.js +240 -80
- package/lib/cjs/Body.js.map +1 -1
- package/lib/cjs/DataTable.js +52 -56
- package/lib/cjs/DataTable.js.map +1 -1
- package/lib/cjs/Head.js +2 -2
- package/lib/cjs/Head.js.map +1 -1
- package/lib/cjs/style/data-table.shadow.css +11 -0
- package/lib/cjs/types.js.map +1 -1
- package/lib/es6/Body.js +234 -76
- package/lib/es6/Body.js.map +1 -1
- package/lib/es6/DataTable.js +52 -56
- package/lib/es6/DataTable.js.map +1 -1
- package/lib/es6/Head.js +2 -2
- package/lib/es6/Head.js.map +1 -1
- package/lib/es6/style/data-table.shadow.css +11 -0
- package/lib/es6/types.js.map +1 -1
- package/package.json +3 -2
- package/src/Body.tsx +152 -31
- package/src/DataTable.tsx +17 -2
- package/src/style/data-table.shadow.css +11 -0
- package/src/types.ts +0 -1
package/src/Body.tsx
CHANGED
|
@@ -4,11 +4,11 @@ import { Box, Flex, IBoxProps } from '@semcore/flex-box';
|
|
|
4
4
|
import ScrollArea from '@semcore/scroll-area';
|
|
5
5
|
import { getFixedStyle, getScrollOffsetValue } from './utils';
|
|
6
6
|
import { RowData, Column, NestedCells, PropsLayer, Cell } from './types';
|
|
7
|
-
import assignProps from '@semcore/utils/lib/assignProps';
|
|
8
|
-
import
|
|
9
|
-
|
|
7
|
+
import assignProps, { callAllEventHandlers } from '@semcore/utils/lib/assignProps';
|
|
8
|
+
import ResizeObserver from 'resize-observer-polyfill';
|
|
10
9
|
import scrollStyles from './style/scroll-area.shadow.css';
|
|
11
10
|
import syncScroll from '@semcore/utils/lib/syncScroll';
|
|
11
|
+
import trottle from '@semcore/utils/lib/rafTrottle';
|
|
12
12
|
|
|
13
13
|
const testEnv = process.env.NODE_ENV === 'test';
|
|
14
14
|
|
|
@@ -24,35 +24,33 @@ type AsProps = {
|
|
|
24
24
|
onResize: ResizeObserverCallback;
|
|
25
25
|
rowPropsLayers: PropsLayer[];
|
|
26
26
|
use: 'primary' | 'secondary';
|
|
27
|
+
uniqueKey: string;
|
|
28
|
+
virtualScroll?: boolean | { tollerance?: number; rowHeight?: number };
|
|
27
29
|
};
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const cellsByColumn = cells.flatRowData || getCellsByColumn(cells);
|
|
31
|
+
type State = {
|
|
32
|
+
rowHeight: number | undefined;
|
|
33
|
+
scrollAreaHeight: undefined | number;
|
|
34
|
+
scrollOffset: number;
|
|
35
|
+
};
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
class Body extends Component<AsProps, State> {
|
|
38
|
+
state: State = {
|
|
39
|
+
rowHeight: undefined,
|
|
40
|
+
scrollAreaHeight: undefined,
|
|
41
|
+
scrollOffset: 0,
|
|
42
|
+
};
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const propsRow = assignProps(other, props);
|
|
45
|
-
props = assignProps(childrenPropsGetter(propsRow, cellsByColumn, index), propsRow);
|
|
46
|
-
}
|
|
44
|
+
firstRowRef = React.createRef<HTMLElement>();
|
|
45
|
+
firstRowResizeObserver: ResizeObserver | null = null;
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
getRowHeight = () => {
|
|
48
|
+
const { virtualScroll } = this.asProps;
|
|
49
|
+
const rowHeightFromProps = typeof virtualScroll === 'object' && virtualScroll?.rowHeight;
|
|
50
|
+
return rowHeightFromProps || this.state.rowHeight;
|
|
51
|
+
};
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
return rows.map((cells, index) => this.renderRow(cells, index));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
renderCells(cells: NestedCells, cellsByColumn: RowData, index: number) {
|
|
53
|
+
renderCells(cells: NestedCells, rowData: RowData, index: number) {
|
|
56
54
|
const SCell = Flex;
|
|
57
55
|
const { styles, columns, use } = this.asProps;
|
|
58
56
|
return cells.map((cell) => {
|
|
@@ -73,7 +71,6 @@ class Body extends Component<AsProps> {
|
|
|
73
71
|
let props: CellProps = {
|
|
74
72
|
name: cell.name,
|
|
75
73
|
children: <>{cell.data}</>,
|
|
76
|
-
['data-data']: JSON.stringify(cell.data),
|
|
77
74
|
justifyContent: column?.props?.justifyContent,
|
|
78
75
|
style: {
|
|
79
76
|
width: vars.length === 1 ? vars[0] : `calc(${vars.join(' + ')})`,
|
|
@@ -86,7 +83,7 @@ class Body extends Component<AsProps> {
|
|
|
86
83
|
for (const cellPropLayer of cell.cellPropsLayers || []) {
|
|
87
84
|
const { childrenPropsGetter = (p) => p, ...other } = cellPropLayer;
|
|
88
85
|
const propsCell = assignProps(other, props);
|
|
89
|
-
props = assignProps(childrenPropsGetter(propsCell,
|
|
86
|
+
props = assignProps(childrenPropsGetter(propsCell, rowData, index), propsCell);
|
|
90
87
|
}
|
|
91
88
|
|
|
92
89
|
return sstyled(styles)(
|
|
@@ -96,17 +93,136 @@ class Body extends Component<AsProps> {
|
|
|
96
93
|
}, [] as React.ReactElement[]);
|
|
97
94
|
}
|
|
98
95
|
|
|
96
|
+
renderRow(
|
|
97
|
+
cells: NestedCells,
|
|
98
|
+
{ dataIndex, topOffset, nested }: { dataIndex: number; topOffset?: number; nested: boolean },
|
|
99
|
+
) {
|
|
100
|
+
const SRow = Box;
|
|
101
|
+
const { styles, rowPropsLayers, uniqueKey, virtualScroll } = this.asProps;
|
|
102
|
+
const rowHeightFromProps = typeof virtualScroll === 'object' && virtualScroll?.rowHeight;
|
|
103
|
+
|
|
104
|
+
const rowData = cells.flatRowData || getCellsByColumn(cells);
|
|
105
|
+
const key = rowData[uniqueKey] ? String(rowData[uniqueKey]) : `row_${dataIndex}`;
|
|
106
|
+
const needToMeasureHeight = dataIndex === 0 && !nested && !rowHeightFromProps;
|
|
107
|
+
|
|
108
|
+
let props = {
|
|
109
|
+
children: this.renderCells(cells, rowData, dataIndex),
|
|
110
|
+
theme: undefined,
|
|
111
|
+
active: undefined,
|
|
112
|
+
positioned: topOffset !== undefined,
|
|
113
|
+
top: topOffset,
|
|
114
|
+
ref: needToMeasureHeight ? this.firstRowRef : undefined,
|
|
115
|
+
key,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
for (const rowPropsLayer of rowPropsLayers) {
|
|
119
|
+
const { childrenPropsGetter = (p) => p, ...other } = rowPropsLayer;
|
|
120
|
+
const propsRow = assignProps(other, props);
|
|
121
|
+
props = assignProps(childrenPropsGetter(propsRow, rowData, dataIndex), propsRow);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return sstyled(styles)(<SRow {...props} />);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
renderRows(rows: NestedCells[]) {
|
|
128
|
+
return rows.map((cells, dataIndex) => this.renderRow(cells, { dataIndex, nested: false }));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
renderVirtualizedRows(rows: NestedCells[]) {
|
|
132
|
+
if (rows.length === 0) return [];
|
|
133
|
+
|
|
134
|
+
const { virtualScroll } = this.asProps;
|
|
135
|
+
const { scrollOffset, scrollAreaHeight } = this.state;
|
|
136
|
+
const rowHeight = this.getRowHeight();
|
|
137
|
+
|
|
138
|
+
const tollerance = (typeof virtualScroll === 'object' ? virtualScroll?.tollerance : 2) ?? 2;
|
|
139
|
+
const startIndex = Math.max(Math.floor(scrollOffset / rowHeight!) - tollerance, 0);
|
|
140
|
+
const lastIndex = Math.min(
|
|
141
|
+
Math.ceil((scrollOffset + scrollAreaHeight!) / rowHeight!) + tollerance,
|
|
142
|
+
rows.length,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const rowHeightFromProps = typeof virtualScroll === 'object' && virtualScroll?.rowHeight;
|
|
146
|
+
const needToMeasureFirstRowHeight = !rowHeightFromProps;
|
|
147
|
+
|
|
148
|
+
const firstRow = { cells: rows[0], dataIndex: 0, topOffset: 0 };
|
|
149
|
+
const visibleRows = rowHeight !== undefined ? rows.slice(startIndex, lastIndex) : [];
|
|
150
|
+
const processedVisibleRows = visibleRows.map((cells, index) => ({
|
|
151
|
+
cells,
|
|
152
|
+
dataIndex: startIndex + index,
|
|
153
|
+
topOffset: rowHeight! * (startIndex + index),
|
|
154
|
+
}));
|
|
155
|
+
if (needToMeasureFirstRowHeight && startIndex !== 0) {
|
|
156
|
+
processedVisibleRows.unshift(firstRow);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return processedVisibleRows.map(({ cells, dataIndex, topOffset }) =>
|
|
160
|
+
this.renderRow(cells, { dataIndex, topOffset, nested: false }),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
handleFirstRowResize = trottle((entries: ResizeObserverEntry[]) => {
|
|
165
|
+
const { contentRect } = entries[0];
|
|
166
|
+
const { height } = contentRect;
|
|
167
|
+
this.setState((oldState: State) => {
|
|
168
|
+
if (oldState.rowHeight === height) return oldState;
|
|
169
|
+
return { rowHeight: height };
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
handleScrollAreaResize = trottle((entries: ResizeObserverEntry[]) => {
|
|
174
|
+
const { virtualScroll } = this.asProps;
|
|
175
|
+
if (!virtualScroll) return;
|
|
176
|
+
const { contentRect } = entries[0];
|
|
177
|
+
const { height } = contentRect;
|
|
178
|
+
this.setState((oldState: State) => {
|
|
179
|
+
if (oldState.scrollAreaHeight === height) return oldState;
|
|
180
|
+
return { scrollAreaHeight: height };
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
handleScrollAreaScroll = (event: React.SyntheticEvent<HTMLElement>) => {
|
|
185
|
+
const { scrollTop } = event.target as HTMLElement;
|
|
186
|
+
const { virtualScroll } = this.asProps;
|
|
187
|
+
if (virtualScroll) {
|
|
188
|
+
this.setState((oldState: State) => {
|
|
189
|
+
if (oldState.scrollOffset === scrollTop) return oldState;
|
|
190
|
+
return { scrollOffset: scrollTop };
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
setupRowSizeObserver = () => {
|
|
196
|
+
if (!this.firstRowRef.current) return;
|
|
197
|
+
if (!this.asProps.virtualScroll) return;
|
|
198
|
+
this.firstRowResizeObserver = new ResizeObserver(this.handleFirstRowResize);
|
|
199
|
+
this.firstRowResizeObserver.observe(this.firstRowRef.current);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
componentWillUnmount() {
|
|
203
|
+
this.firstRowResizeObserver?.disconnect();
|
|
204
|
+
}
|
|
205
|
+
|
|
99
206
|
render() {
|
|
100
207
|
const SBody = Root;
|
|
101
208
|
const SBodyWrapper = Box;
|
|
102
209
|
const SScrollAreaBar = ScrollArea.Bar;
|
|
103
|
-
const
|
|
210
|
+
const SHeightHold = Box;
|
|
211
|
+
const { Children, styles, rows, columns, $scrollRef, virtualScroll, onResize } = this.asProps;
|
|
104
212
|
|
|
105
213
|
const columnsInitialized = columns.reduce((sum, { width }) => sum + width, 0) > 0 || testEnv;
|
|
106
214
|
|
|
107
215
|
const [offsetLeftSum, offsetRightSum] = getScrollOffsetValue(columns);
|
|
108
216
|
const offsetSum = offsetLeftSum + offsetRightSum;
|
|
109
217
|
|
|
218
|
+
const rowHeight = this.getRowHeight();
|
|
219
|
+
const holdHeight =
|
|
220
|
+
rowHeight !== undefined && virtualScroll ? rowHeight * rows.length : undefined;
|
|
221
|
+
|
|
222
|
+
if (virtualScroll && columnsInitialized && !rowHeight) {
|
|
223
|
+
new Promise(() => this.setupRowSizeObserver());
|
|
224
|
+
}
|
|
225
|
+
|
|
110
226
|
return sstyled(styles)(
|
|
111
227
|
<SBodyWrapper>
|
|
112
228
|
<ScrollArea
|
|
@@ -114,10 +230,15 @@ class Body extends Component<AsProps> {
|
|
|
114
230
|
styles={scrollStyles}
|
|
115
231
|
use:left={`${offsetLeftSum}px`}
|
|
116
232
|
use:right={`${offsetRightSum}px`}
|
|
117
|
-
onResize={onResize}
|
|
233
|
+
onResize={callAllEventHandlers(onResize, this.handleScrollAreaResize)}
|
|
234
|
+
onScroll={this.handleScrollAreaScroll}
|
|
118
235
|
>
|
|
119
236
|
<ScrollArea.Container ref={$scrollRef}>
|
|
120
|
-
<SBody render={Box}>
|
|
237
|
+
<SBody render={Box}>
|
|
238
|
+
{holdHeight && <SHeightHold hMin={holdHeight} aria-hidden={true} />}
|
|
239
|
+
{columnsInitialized && !virtualScroll ? this.renderRows(rows) : null}
|
|
240
|
+
{columnsInitialized && virtualScroll ? this.renderVirtualizedRows(rows) : null}
|
|
241
|
+
</SBody>
|
|
121
242
|
</ScrollArea.Container>
|
|
122
243
|
<SScrollAreaBar
|
|
123
244
|
orientation="horizontal"
|
package/src/DataTable.tsx
CHANGED
|
@@ -36,6 +36,7 @@ type AsProps = {
|
|
|
36
36
|
use: 'primary' | 'secondary';
|
|
37
37
|
sort: SortDirection[];
|
|
38
38
|
data: RowData[];
|
|
39
|
+
uniqueKey: string;
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
type HeadAsProps = {
|
|
@@ -79,6 +80,10 @@ export interface IDataTableProps extends IBoxProps {
|
|
|
79
80
|
sort?: DataTableSort;
|
|
80
81
|
/** Handler call when will request change sort */
|
|
81
82
|
onSortChange?: (sort: DataTableSort, e?: React.SyntheticEvent) => void;
|
|
83
|
+
/** Field name in one data entity that is unique accross all set of data
|
|
84
|
+
* @default id
|
|
85
|
+
*/
|
|
86
|
+
uniqueKey?: string;
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
export interface IDataTableHeadProps extends IBoxProps {
|
|
@@ -106,6 +111,12 @@ export interface IDataTableColumnProps extends IFlexProps {
|
|
|
106
111
|
export interface IDataTableBodyProps extends IBoxProps {
|
|
107
112
|
/** Rows table */
|
|
108
113
|
rows?: DataTableRow[];
|
|
114
|
+
/** When enabled, only visually acessable rows are rendered.
|
|
115
|
+
* `tollerance` property controls how many rows outside of viewport are render.
|
|
116
|
+
* `rowHeight` fixes the rows height if it known. If not provided, first row node height is measured.
|
|
117
|
+
* @default { tollerance: 2 }
|
|
118
|
+
*/
|
|
119
|
+
virtualScroll?: boolean | { tollerance?: number; rowHeight?: number };
|
|
109
120
|
}
|
|
110
121
|
|
|
111
122
|
export interface IDataTableRowProps extends IBoxProps {
|
|
@@ -129,6 +140,7 @@ class RootDefinitionTable extends Component<AsProps> {
|
|
|
129
140
|
|
|
130
141
|
static defaultProps = {
|
|
131
142
|
use: 'primary',
|
|
143
|
+
uniqueKey: 'id',
|
|
132
144
|
sort: [],
|
|
133
145
|
data: [],
|
|
134
146
|
} as AsProps;
|
|
@@ -260,7 +272,7 @@ class RootDefinitionTable extends Component<AsProps> {
|
|
|
260
272
|
}
|
|
261
273
|
|
|
262
274
|
getBodyProps(props: BodyAsProps) {
|
|
263
|
-
const { data, use } = this.asProps;
|
|
275
|
+
const { data, use, uniqueKey } = this.asProps;
|
|
264
276
|
|
|
265
277
|
const cellPropsLayers: { [columnName: string]: PropsLayer[] } = {};
|
|
266
278
|
const rowPropsLayers: PropsLayer[] = [];
|
|
@@ -289,13 +301,16 @@ class RootDefinitionTable extends Component<AsProps> {
|
|
|
289
301
|
}
|
|
290
302
|
});
|
|
291
303
|
|
|
292
|
-
|
|
304
|
+
const result = {
|
|
293
305
|
columns: this.columns,
|
|
294
306
|
rows: this.dataToRows(data, cellPropsLayers),
|
|
307
|
+
uniqueKey,
|
|
295
308
|
use,
|
|
296
309
|
rowPropsLayers,
|
|
297
310
|
$scrollRef: this.scrollBodyRef,
|
|
298
311
|
};
|
|
312
|
+
|
|
313
|
+
return result;
|
|
299
314
|
}
|
|
300
315
|
|
|
301
316
|
dataToRows(data: RowData[], cellPropsLayers: { [columnName: string]: PropsLayer[] }) {
|
|
@@ -209,6 +209,10 @@ SRow[theme='danger']:hover SCell:not([theme]) {
|
|
|
209
209
|
background-color: color-mod(var(--red) blend(#fff 85%));
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
SRow[positioned] {
|
|
213
|
+
position: absolute;
|
|
214
|
+
}
|
|
215
|
+
|
|
212
216
|
SCell {
|
|
213
217
|
display: flex;
|
|
214
218
|
flex: 1;
|
|
@@ -275,3 +279,10 @@ SScrollAreaBar[orientation='horizontal'] {
|
|
|
275
279
|
margin-right: calc(var(--right) + 4px);
|
|
276
280
|
width: calc(100% - var(--offsetSum) - 8px);
|
|
277
281
|
}
|
|
282
|
+
|
|
283
|
+
SHeightHold {
|
|
284
|
+
position: absolute;
|
|
285
|
+
top: 0;
|
|
286
|
+
width: 100px;
|
|
287
|
+
/* pointer-events: none; */
|
|
288
|
+
}
|