@semcore/data-table 2.0.0 → 2.1.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 +101 -62
- package/lib/cjs/Body.js.map +1 -1
- package/lib/cjs/DataTable.js +148 -109
- package/lib/cjs/DataTable.js.map +1 -1
- package/lib/cjs/Head.js +15 -8
- package/lib/cjs/Head.js.map +1 -1
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/types.js +4 -0
- package/lib/cjs/types.js.map +1 -0
- package/lib/cjs/utils.js +26 -26
- package/lib/cjs/utils.js.map +1 -1
- package/lib/es6/Body.js +104 -63
- package/lib/es6/Body.js.map +1 -1
- package/lib/es6/DataTable.js +150 -107
- package/lib/es6/DataTable.js.map +1 -1
- package/lib/es6/Head.js +16 -8
- package/lib/es6/Head.js.map +1 -1
- package/lib/es6/index.js.map +1 -1
- package/lib/es6/types.js +2 -0
- package/lib/es6/types.js.map +1 -0
- package/lib/es6/utils.js +20 -24
- package/lib/es6/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/Body.tsx +136 -0
- package/src/DataTable.tsx +428 -0
- package/src/{Head.jsx → Head.tsx} +29 -11
- package/src/{index.js → index.ts} +0 -0
- package/src/types.ts +54 -0
- package/src/utils.ts +55 -0
- package/lib/types/index.d.ts +0 -99
- package/src/Body.jsx +0 -119
- package/src/DataTable.jsx +0 -283
- package/src/index.d.ts +0 -99
- package/src/utils.js +0 -54
package/lib/es6/utils.js
CHANGED
|
@@ -1,39 +1,35 @@
|
|
|
1
|
-
export function getScrollOffsetValue(columns) {
|
|
2
|
-
return columns.reduce(function (acc,
|
|
3
|
-
if (
|
|
4
|
-
acc[0] +=
|
|
1
|
+
export var getScrollOffsetValue = function getScrollOffsetValue(columns) {
|
|
2
|
+
return columns.reduce(function (acc, column) {
|
|
3
|
+
if (column.fixed === 'left') {
|
|
4
|
+
acc[0] += column.width;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
if (
|
|
8
|
-
acc[1] +=
|
|
7
|
+
if (column.fixed === 'right') {
|
|
8
|
+
acc[1] += column.width;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
return acc;
|
|
12
12
|
}, [0, 0]);
|
|
13
|
-
}
|
|
14
|
-
export function flattenColumns(columns) {
|
|
15
|
-
return columns.reduce(function (acc,
|
|
16
|
-
var
|
|
17
|
-
|
|
18
|
-
if (c.columns) {
|
|
19
|
-
columns = flattenColumns(c.columns);
|
|
20
|
-
}
|
|
21
|
-
|
|
13
|
+
};
|
|
14
|
+
export var flattenColumns = function flattenColumns(columns) {
|
|
15
|
+
return columns.reduce(function (acc, column) {
|
|
16
|
+
var hasNestedColumns = 'columns' in column && column.columns.length > 0;
|
|
17
|
+
var columns = hasNestedColumns ? flattenColumns(column.columns) : [column];
|
|
22
18
|
acc = acc.concat(columns);
|
|
23
19
|
return acc;
|
|
24
20
|
}, []);
|
|
25
|
-
}
|
|
26
|
-
export function getFixedStyle(
|
|
27
|
-
var side =
|
|
21
|
+
};
|
|
22
|
+
export var getFixedStyle = function getFixedStyle(cell, columns) {
|
|
23
|
+
var side = cell.fixed;
|
|
28
24
|
if (!side) return [undefined, undefined];
|
|
29
|
-
var names =
|
|
25
|
+
var names = cell.name.split('/');
|
|
30
26
|
var nameSideMap = {
|
|
31
27
|
left: names[0],
|
|
32
28
|
right: names[names.length - 1]
|
|
33
29
|
};
|
|
34
30
|
var name = nameSideMap[side];
|
|
35
|
-
var index = columns.findIndex(function (
|
|
36
|
-
return
|
|
31
|
+
var index = columns.findIndex(function (column) {
|
|
32
|
+
return column.name === name;
|
|
37
33
|
});
|
|
38
34
|
if (index === -1) return [undefined, undefined];
|
|
39
35
|
var startIndexSideMap = {
|
|
@@ -46,9 +42,9 @@ export function getFixedStyle(column, columns) {
|
|
|
46
42
|
};
|
|
47
43
|
var columnsFixed = columns.slice(startIndexSideMap[side], endIndexSideMap[side]);
|
|
48
44
|
if (columnsFixed.length < 1) return [side, 0];
|
|
49
|
-
var vars = columnsFixed.map(function (
|
|
50
|
-
return "var(--".concat(
|
|
45
|
+
var vars = columnsFixed.map(function (column) {
|
|
46
|
+
return "var(--".concat(column.name, "_width)");
|
|
51
47
|
});
|
|
52
48
|
return [side, vars.length === 1 ? vars[0] : "calc(".concat(vars.join(' + '), ")")];
|
|
53
|
-
}
|
|
49
|
+
};
|
|
54
50
|
//# sourceMappingURL=utils.js.map
|
package/lib/es6/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils.
|
|
1
|
+
{"version":3,"sources":["../../src/utils.ts"],"names":["getScrollOffsetValue","columns","reduce","acc","column","fixed","width","flattenColumns","hasNestedColumns","length","concat","getFixedStyle","cell","side","undefined","names","name","split","nameSideMap","left","right","index","findIndex","startIndexSideMap","endIndexSideMap","columnsFixed","slice","vars","map","join"],"mappings":"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","sourcesContent":["import type { Column } from './types';\n\nexport const getScrollOffsetValue = (columns: Column[]) =>\n columns.reduce(\n (acc, column) => {\n if (column.fixed === 'left') {\n acc[0] += column.width;\n }\n if (column.fixed === 'right') {\n acc[1] += column.width;\n }\n return acc;\n },\n [0, 0] as [leftOffset: number, rightOffset: number],\n );\n\nexport const flattenColumns = (columns: Column[]) =>\n columns.reduce((acc, column) => {\n const hasNestedColumns = 'columns' in column && column.columns.length > 0;\n const columns: Column[] = hasNestedColumns ? flattenColumns(column.columns) : [column];\n acc = acc.concat(columns);\n return acc;\n }, [] as Column[]);\n\nexport const getFixedStyle = (\n cell: Pick<Column, 'name' | 'fixed'>,\n columns: Column[],\n): [side: 'left' | 'right', style: string | number] | [side: undefined, style: undefined] => {\n const side = cell.fixed;\n if (!side) return [undefined, undefined];\n const names = cell.name.split('/');\n const nameSideMap = {\n left: names[0],\n right: names[names.length - 1],\n };\n const name = nameSideMap[side];\n const index = columns.findIndex((column) => column.name === name);\n\n if (index === -1) return [undefined, undefined];\n\n const startIndexSideMap = {\n left: 0,\n right: index,\n };\n const endIndexSideMap = {\n left: index,\n right: columns.length - 1,\n };\n const columnsFixed = columns.slice(startIndexSideMap[side], endIndexSideMap[side]);\n\n if (columnsFixed.length < 1) return [side, 0];\n\n const vars = columnsFixed.map((column) => `var(--${column.name}_width)`);\n return [side, vars.length === 1 ? vars[0] : `calc(${vars.join(' + ')})`];\n};\n"],"file":"utils.js"}
|
package/package.json
CHANGED
package/src/Body.tsx
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Component, sstyled, Root } from '@semcore/core';
|
|
3
|
+
import { Box, Flex, IBoxProps } from '@semcore/flex-box';
|
|
4
|
+
import ScrollArea from '@semcore/scroll-area';
|
|
5
|
+
import { getFixedStyle, getScrollOffsetValue } from './utils';
|
|
6
|
+
import { RowData, Column, NestedCells, PropsLayer, Cell } from './types';
|
|
7
|
+
import assignProps from '@semcore/utils/lib/assignProps';
|
|
8
|
+
import type ResizeObserverCallback from 'resize-observer-polyfill';
|
|
9
|
+
|
|
10
|
+
import scrollStyles from './style/scroll-area.shadow.css';
|
|
11
|
+
import syncScroll from '@semcore/utils/lib/syncScroll';
|
|
12
|
+
|
|
13
|
+
const testEnv = process.env.NODE_ENV === 'test';
|
|
14
|
+
|
|
15
|
+
const getCellsByColumn = (cells: NestedCells): RowData => {
|
|
16
|
+
const flattenCells = cells.flat(20) as Cell[];
|
|
17
|
+
return Object.fromEntries(flattenCells.map((cell) => [cell.name, cell.data]));
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type AsProps = {
|
|
21
|
+
rows: NestedCells[];
|
|
22
|
+
columns: Column[];
|
|
23
|
+
$scrollRef: ReturnType<ReturnType<typeof syncScroll>>;
|
|
24
|
+
onResize: ResizeObserverCallback;
|
|
25
|
+
rowPropsLayers: PropsLayer[];
|
|
26
|
+
use: 'primary' | 'secondary';
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
class Body extends Component<AsProps> {
|
|
30
|
+
renderRow(cells: NestedCells, index: number) {
|
|
31
|
+
const SRow = Box;
|
|
32
|
+
const { styles, rowPropsLayers } = this.asProps;
|
|
33
|
+
|
|
34
|
+
const cellsByColumn = cells.flatRowData || getCellsByColumn(cells);
|
|
35
|
+
|
|
36
|
+
let props = {
|
|
37
|
+
children: this.renderCells(cells, cellsByColumn, index),
|
|
38
|
+
theme: undefined,
|
|
39
|
+
active: undefined,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
for (const rowPropsLayer of rowPropsLayers) {
|
|
43
|
+
const { childrenPropsGetter = (p) => p, ...other } = rowPropsLayer;
|
|
44
|
+
const propsRow = assignProps(other, props);
|
|
45
|
+
props = assignProps(childrenPropsGetter(propsRow, cellsByColumn, index), propsRow);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return sstyled(styles)(<SRow key={index} {...props} />);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
renderRows(rows: NestedCells[]) {
|
|
52
|
+
return rows.map((cells, index) => this.renderRow(cells, index));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
renderCells(cells: NestedCells, cellsByColumn: RowData, index: number) {
|
|
56
|
+
const SCell = Flex;
|
|
57
|
+
const { styles, columns, use } = this.asProps;
|
|
58
|
+
return cells.map((cell) => {
|
|
59
|
+
if (Array.isArray(cell)) {
|
|
60
|
+
return <div>{this.renderRows(cell as NestedCells[])}</div>;
|
|
61
|
+
} else {
|
|
62
|
+
const column = columns.find((c) => c.name === cell.name);
|
|
63
|
+
const [name, value] = getFixedStyle(cell, columns);
|
|
64
|
+
const vars = (Array.isArray(cell.cssVar) ? cell.cssVar : [cell.cssVar]).map(
|
|
65
|
+
(name) => `var(${name})`,
|
|
66
|
+
);
|
|
67
|
+
type CellProps = IBoxProps & {
|
|
68
|
+
name: string;
|
|
69
|
+
children: React.ReactNode;
|
|
70
|
+
style: React.CSSProperties;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
let props: CellProps = {
|
|
74
|
+
name: cell.name,
|
|
75
|
+
children: <>{cell.data}</>,
|
|
76
|
+
['data-data']: JSON.stringify(cell.data),
|
|
77
|
+
justifyContent: column?.props?.justifyContent,
|
|
78
|
+
style: {
|
|
79
|
+
width: vars.length === 1 ? vars[0] : `calc(${vars.join(' + ')})`,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
if (name !== undefined && value !== undefined) {
|
|
83
|
+
props.style[name] = value;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const cellPropLayer of cell.cellPropsLayers || []) {
|
|
87
|
+
const { childrenPropsGetter = (p) => p, ...other } = cellPropLayer;
|
|
88
|
+
const propsCell = assignProps(other, props);
|
|
89
|
+
props = assignProps(childrenPropsGetter(propsCell, cellsByColumn, index), propsCell);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return sstyled(styles)(
|
|
93
|
+
<SCell key={cell.name} {...props} fixed={cell.fixed} theme={props.theme} use={use} />,
|
|
94
|
+
) as React.ReactElement;
|
|
95
|
+
}
|
|
96
|
+
}, [] as React.ReactElement[]);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
render() {
|
|
100
|
+
const SBody = Root;
|
|
101
|
+
const SBodyWrapper = Box;
|
|
102
|
+
const SScrollAreaBar = ScrollArea.Bar;
|
|
103
|
+
const { Children, styles, rows, columns, onResize, $scrollRef } = this.asProps;
|
|
104
|
+
|
|
105
|
+
const columnsInitialized = columns.reduce((sum, { width }) => sum + width, 0) > 0 || testEnv;
|
|
106
|
+
|
|
107
|
+
const [offsetLeftSum, offsetRightSum] = getScrollOffsetValue(columns);
|
|
108
|
+
const offsetSum = offsetLeftSum + offsetRightSum;
|
|
109
|
+
|
|
110
|
+
return sstyled(styles)(
|
|
111
|
+
<SBodyWrapper>
|
|
112
|
+
<ScrollArea
|
|
113
|
+
shadow
|
|
114
|
+
styles={scrollStyles}
|
|
115
|
+
use:left={`${offsetLeftSum}px`}
|
|
116
|
+
use:right={`${offsetRightSum}px`}
|
|
117
|
+
onResize={onResize}
|
|
118
|
+
>
|
|
119
|
+
<ScrollArea.Container ref={$scrollRef}>
|
|
120
|
+
<SBody render={Box}>{columnsInitialized ? this.renderRows(rows) : null}</SBody>
|
|
121
|
+
</ScrollArea.Container>
|
|
122
|
+
<SScrollAreaBar
|
|
123
|
+
orientation="horizontal"
|
|
124
|
+
left={`${offsetLeftSum}px`}
|
|
125
|
+
right={`${offsetRightSum}px`}
|
|
126
|
+
offsetSum={`${offsetSum}px`}
|
|
127
|
+
/>
|
|
128
|
+
<SScrollAreaBar orientation="vertical" />
|
|
129
|
+
</ScrollArea>
|
|
130
|
+
{Children.origin}
|
|
131
|
+
</SBodyWrapper>,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default Body;
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import createComponent, { Component, sstyled, Root, PropGetterFn } from '@semcore/core';
|
|
3
|
+
import { Box, IBoxProps, IFlexProps } from '@semcore/flex-box';
|
|
4
|
+
import syncScroll from '@semcore/utils/lib/syncScroll';
|
|
5
|
+
import { callAllEventHandlers } from '@semcore/utils/lib/assignProps';
|
|
6
|
+
import fire from '@semcore/utils/lib/fire';
|
|
7
|
+
import { flattenColumns } from './utils';
|
|
8
|
+
import type {
|
|
9
|
+
RowData,
|
|
10
|
+
SortDirection,
|
|
11
|
+
PseudoChildPropsGetter,
|
|
12
|
+
PropsLayer,
|
|
13
|
+
NestedCells,
|
|
14
|
+
Column,
|
|
15
|
+
} from './types';
|
|
16
|
+
import Head from './Head';
|
|
17
|
+
import Body from './Body';
|
|
18
|
+
|
|
19
|
+
import style from './style/data-table.shadow.css';
|
|
20
|
+
|
|
21
|
+
const REVERSED_SORT_DIRECTION: { [direction in SortDirection]: SortDirection } = {
|
|
22
|
+
desc: 'asc',
|
|
23
|
+
asc: 'desc',
|
|
24
|
+
};
|
|
25
|
+
const DEFAULT_SORT_DIRECTION: SortDirection = 'desc';
|
|
26
|
+
|
|
27
|
+
const ROW_GROUP = Symbol('ROW_GROUP');
|
|
28
|
+
|
|
29
|
+
const cssVarReg = /[:;]/g;
|
|
30
|
+
|
|
31
|
+
const createCssVarForWidth = (name: string) => {
|
|
32
|
+
return `--${name.replace(cssVarReg, '_')}_width`;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type AsProps = {
|
|
36
|
+
use: 'primary' | 'secondary';
|
|
37
|
+
sort: SortDirection[];
|
|
38
|
+
data: RowData[];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type HeadAsProps = {
|
|
42
|
+
children: React.ReactChild;
|
|
43
|
+
};
|
|
44
|
+
type BodyAsProps = {
|
|
45
|
+
children: React.ReactChild;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/* utils type */
|
|
49
|
+
type CProps<Props, Ctx = {}, UCProps = {}> = Props & {
|
|
50
|
+
children?: ((props: Props & Ctx, handlers: UCProps) => React.ReactNode) | React.ReactNode;
|
|
51
|
+
};
|
|
52
|
+
type ReturnEl = React.ReactElement | null;
|
|
53
|
+
type ChildRenderFn<Props> = Props & {
|
|
54
|
+
children?: (props: Props, column: DataTableData, index: number) => { [key: string]: unknown };
|
|
55
|
+
};
|
|
56
|
+
/* utils type */
|
|
57
|
+
|
|
58
|
+
export type DataTableData = { [key: string]: unknown };
|
|
59
|
+
export type DataTableSort = [string, 'desc' | 'asc'];
|
|
60
|
+
export type DataTableTheme = 'muted' | 'info' | 'success' | 'warning' | 'danger';
|
|
61
|
+
export type DataTableUse = 'primary' | 'secondary';
|
|
62
|
+
export type DataTableRow = DataTableCell[];
|
|
63
|
+
export type DataTableCell = {
|
|
64
|
+
/** Name of column */
|
|
65
|
+
name: string;
|
|
66
|
+
/** Data of column */
|
|
67
|
+
data: React.ReactNode;
|
|
68
|
+
[key: string]: unknown;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export interface IDataTableProps extends IBoxProps {
|
|
72
|
+
/** Theme for table
|
|
73
|
+
* @default primary
|
|
74
|
+
* */
|
|
75
|
+
use?: DataTableUse;
|
|
76
|
+
/** Data for table */
|
|
77
|
+
data?: DataTableData[];
|
|
78
|
+
/** Active sort object */
|
|
79
|
+
sort?: DataTableSort;
|
|
80
|
+
/** Handler call when will request change sort */
|
|
81
|
+
onSortChange?: (sort: DataTableSort, e?: React.SyntheticEvent) => void;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface IDataTableHeadProps extends IBoxProps {
|
|
85
|
+
/** Sticky header table
|
|
86
|
+
* @deprecated
|
|
87
|
+
* */
|
|
88
|
+
sticky?: boolean;
|
|
89
|
+
|
|
90
|
+
/** Hidden header */
|
|
91
|
+
hidden?: boolean;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface IDataTableColumnProps extends IFlexProps {
|
|
95
|
+
/** Unique name column */
|
|
96
|
+
name?: string;
|
|
97
|
+
/** Enable sort for column also if you pass string you can set default sort */
|
|
98
|
+
sortable?: boolean | 'desc' | 'asc';
|
|
99
|
+
/** Enable resize for column
|
|
100
|
+
* @ignore */
|
|
101
|
+
resizable?: boolean;
|
|
102
|
+
/** Fixed column on the left/right */
|
|
103
|
+
fixed?: 'left' | 'right';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface IDataTableBodyProps extends IBoxProps {
|
|
107
|
+
/** Rows table */
|
|
108
|
+
rows?: DataTableRow[];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface IDataTableRowProps extends IBoxProps {
|
|
112
|
+
/** Theme for row */
|
|
113
|
+
theme?: DataTableTheme;
|
|
114
|
+
/** Displays row as active/hover */
|
|
115
|
+
active?: boolean;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface IDataTableCellProps extends IFlexProps {
|
|
119
|
+
/** Unique name column or columns separated by / */
|
|
120
|
+
name: string;
|
|
121
|
+
/** Theme for cell */
|
|
122
|
+
theme?: DataTableTheme;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
class RootDefinitionTable extends Component<AsProps> {
|
|
126
|
+
static displayName = 'DefinitionTable';
|
|
127
|
+
|
|
128
|
+
static style = style;
|
|
129
|
+
|
|
130
|
+
static defaultProps = {
|
|
131
|
+
use: 'primary',
|
|
132
|
+
sort: [],
|
|
133
|
+
data: [],
|
|
134
|
+
} as AsProps;
|
|
135
|
+
|
|
136
|
+
columns: Column[] = [];
|
|
137
|
+
|
|
138
|
+
tableRef = React.createRef<HTMLElement>();
|
|
139
|
+
scrollBodyRef: null | ReturnType<ReturnType<typeof syncScroll>> = null;
|
|
140
|
+
scrollHeadRef: null | ReturnType<ReturnType<typeof syncScroll>> = null;
|
|
141
|
+
|
|
142
|
+
constructor(props: AsProps) {
|
|
143
|
+
super(props);
|
|
144
|
+
|
|
145
|
+
const createRef = syncScroll();
|
|
146
|
+
// first create body ref for master scroll
|
|
147
|
+
this.scrollBodyRef = createRef('body');
|
|
148
|
+
this.scrollHeadRef = createRef('head');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
handlerSortClick = (name: string, event: React.MouseEvent) => {
|
|
152
|
+
const column = this.columns.find((column) => column.name === name)!;
|
|
153
|
+
return fire(
|
|
154
|
+
this,
|
|
155
|
+
'onSortChange',
|
|
156
|
+
[
|
|
157
|
+
column.name,
|
|
158
|
+
column.active ? REVERSED_SORT_DIRECTION[column.sortDirection] : column.sortDirection,
|
|
159
|
+
],
|
|
160
|
+
event,
|
|
161
|
+
);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
handlerResize = () => {
|
|
165
|
+
this.forceUpdate();
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
scrollToUp = () => {
|
|
169
|
+
this.tableRef?.current?.scrollIntoView({
|
|
170
|
+
block: 'nearest',
|
|
171
|
+
inline: 'nearest',
|
|
172
|
+
behavior: 'smooth',
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
setVarStyle(columns: Column[]) {
|
|
177
|
+
for (const column of columns) {
|
|
178
|
+
if (Array.isArray(column.cssVar)) {
|
|
179
|
+
for (const cssVar of column.cssVar) {
|
|
180
|
+
this.tableRef.current?.style.setProperty(cssVar, `${column.width}px`);
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
this.tableRef.current?.style.setProperty(column.cssVar, `${column.width}px`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
childrenToColumns(
|
|
189
|
+
children: React.ReactNode,
|
|
190
|
+
options: { fixed?: 'left' | 'right' } = { fixed: undefined },
|
|
191
|
+
) {
|
|
192
|
+
const { sort } = this.asProps;
|
|
193
|
+
const columnsChildren: Column[] = [];
|
|
194
|
+
React.Children.forEach(children, (child) => {
|
|
195
|
+
if (!React.isValidElement(child)) return;
|
|
196
|
+
if (child.type !== DefinitionTable.Column) return;
|
|
197
|
+
|
|
198
|
+
let {
|
|
199
|
+
children,
|
|
200
|
+
name,
|
|
201
|
+
fixed = options.fixed,
|
|
202
|
+
resizable,
|
|
203
|
+
sortable,
|
|
204
|
+
...props
|
|
205
|
+
} = child.props as Column['props'];
|
|
206
|
+
const isGroup = !name;
|
|
207
|
+
let columns: Column[] = [];
|
|
208
|
+
|
|
209
|
+
if (isGroup) {
|
|
210
|
+
columns = this.childrenToColumns(children, { fixed });
|
|
211
|
+
name = flattenColumns(columns)
|
|
212
|
+
.map(({ name }) => name)
|
|
213
|
+
.join('/');
|
|
214
|
+
if (!columns.length) return;
|
|
215
|
+
children = React.Children.toArray(children).filter(
|
|
216
|
+
(child) => !(React.isValidElement(child) && child.type === DefinitionTable.Column),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const column = this.columns.find((column) => column.name === name);
|
|
221
|
+
|
|
222
|
+
columnsChildren.push({
|
|
223
|
+
get width() {
|
|
224
|
+
return this.props.ref.current?.getBoundingClientRect().width || 0;
|
|
225
|
+
},
|
|
226
|
+
name,
|
|
227
|
+
cssVar: createCssVarForWidth(name),
|
|
228
|
+
fixed,
|
|
229
|
+
resizable,
|
|
230
|
+
active: sort[0] === name,
|
|
231
|
+
sortable,
|
|
232
|
+
sortDirection:
|
|
233
|
+
sort[0] === name
|
|
234
|
+
? sort[1]
|
|
235
|
+
: column?.sortDirection ||
|
|
236
|
+
(typeof sortable == 'string' ? sortable : DEFAULT_SORT_DIRECTION),
|
|
237
|
+
columns,
|
|
238
|
+
props: {
|
|
239
|
+
name,
|
|
240
|
+
ref: column?.props?.ref || React.createRef(),
|
|
241
|
+
children,
|
|
242
|
+
...props,
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
return columnsChildren;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
getHeadProps(props: HeadAsProps) {
|
|
250
|
+
const { use } = this.asProps;
|
|
251
|
+
const columnsChildren = this.childrenToColumns(props.children);
|
|
252
|
+
this.columns = flattenColumns(columnsChildren);
|
|
253
|
+
return {
|
|
254
|
+
$onSortClick: callAllEventHandlers(this.handlerSortClick, this.scrollToUp),
|
|
255
|
+
columnsChildren,
|
|
256
|
+
use,
|
|
257
|
+
onResize: this.handlerResize,
|
|
258
|
+
$scrollRef: this.scrollHeadRef,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
getBodyProps(props: BodyAsProps) {
|
|
263
|
+
const { data, use } = this.asProps;
|
|
264
|
+
|
|
265
|
+
const cellPropsLayers: { [columnName: string]: PropsLayer[] } = {};
|
|
266
|
+
const rowPropsLayers: PropsLayer[] = [];
|
|
267
|
+
|
|
268
|
+
React.Children.forEach(props.children, (child) => {
|
|
269
|
+
if (React.isValidElement(child)) {
|
|
270
|
+
const { name, children, ...other } = child.props as {
|
|
271
|
+
name?: string;
|
|
272
|
+
children?: PseudoChildPropsGetter;
|
|
273
|
+
} & { [propName: string]: unknown };
|
|
274
|
+
if (child.type === DefinitionTable.Cell && name) {
|
|
275
|
+
name.split('/').forEach((name) => {
|
|
276
|
+
cellPropsLayers[name] = cellPropsLayers[name] || [];
|
|
277
|
+
cellPropsLayers[name].push({
|
|
278
|
+
...other,
|
|
279
|
+
childrenPropsGetter: children,
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
if (child.type === DefinitionTable.Row) {
|
|
284
|
+
rowPropsLayers.push({
|
|
285
|
+
...other,
|
|
286
|
+
childrenPropsGetter: children,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
columns: this.columns,
|
|
294
|
+
rows: this.dataToRows(data, cellPropsLayers),
|
|
295
|
+
use,
|
|
296
|
+
rowPropsLayers,
|
|
297
|
+
$scrollRef: this.scrollBodyRef,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
dataToRows(data: RowData[], cellPropsLayers: { [columnName: string]: PropsLayer[] }) {
|
|
302
|
+
const parseData = (data: RowData[], exclude: { [columnName: string]: true }) =>
|
|
303
|
+
data.map((row) => {
|
|
304
|
+
const groupByName: {
|
|
305
|
+
[columnName: string]: {
|
|
306
|
+
groupedColumns: string[];
|
|
307
|
+
groupData: { [columnName: string]: unknown };
|
|
308
|
+
};
|
|
309
|
+
} = {};
|
|
310
|
+
const groupedColumns: { [columnname: string]: true } = {};
|
|
311
|
+
const ungroupedColumns: { [columnname: string]: true } = {};
|
|
312
|
+
for (const rowKey in row) {
|
|
313
|
+
const columnNames = rowKey.split('/');
|
|
314
|
+
if (columnNames.length >= 2) {
|
|
315
|
+
for (const column of columnNames) {
|
|
316
|
+
groupByName[column] = {
|
|
317
|
+
groupedColumns: columnNames,
|
|
318
|
+
groupData: row[rowKey] as { [columnName: string]: unknown },
|
|
319
|
+
};
|
|
320
|
+
groupedColumns[rowKey] = true;
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
ungroupedColumns[rowKey] = true;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const rowsGroup = row[ROW_GROUP] || [];
|
|
327
|
+
const rowsGroupedNames = Object.fromEntries(
|
|
328
|
+
rowsGroup
|
|
329
|
+
.map((subRow) => Object.keys(subRow))
|
|
330
|
+
.flat()
|
|
331
|
+
.map((key) => [key, true]),
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
let isGroup = false;
|
|
335
|
+
|
|
336
|
+
const cells: NestedCells = this.columns
|
|
337
|
+
.map((column) => {
|
|
338
|
+
if (groupByName[column.name]) {
|
|
339
|
+
const { groupedColumns, groupData } = groupByName[column.name];
|
|
340
|
+
if (groupedColumns[0] === column.name) {
|
|
341
|
+
return {
|
|
342
|
+
name: groupedColumns.join('/'),
|
|
343
|
+
cssVar: groupedColumns.map(createCssVarForWidth),
|
|
344
|
+
fixed: column.fixed,
|
|
345
|
+
data: groupData,
|
|
346
|
+
cellPropsLayers: cellPropsLayers[column.name] || [],
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
} else if (column.name in row) {
|
|
350
|
+
return {
|
|
351
|
+
name: column.name,
|
|
352
|
+
cssVar: column.cssVar,
|
|
353
|
+
fixed: column.fixed,
|
|
354
|
+
data: row[column.name],
|
|
355
|
+
cellPropsLayers: cellPropsLayers[column.name] || [],
|
|
356
|
+
};
|
|
357
|
+
} else if (!isGroup && rowsGroupedNames[column.name]) {
|
|
358
|
+
// TODO: make it work not only with first group
|
|
359
|
+
isGroup = true;
|
|
360
|
+
return parseData(rowsGroup, {
|
|
361
|
+
...ungroupedColumns,
|
|
362
|
+
...groupedColumns,
|
|
363
|
+
});
|
|
364
|
+
} else if (!exclude[column.name] && !rowsGroupedNames[column.name]) {
|
|
365
|
+
return {
|
|
366
|
+
name: column.name,
|
|
367
|
+
cssVar: column.cssVar,
|
|
368
|
+
fixed: column.fixed,
|
|
369
|
+
data: null,
|
|
370
|
+
cellPropsLayers: cellPropsLayers[column.name] || [],
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
})
|
|
374
|
+
.filter((column) => column !== undefined)
|
|
375
|
+
.map((column) => column!);
|
|
376
|
+
|
|
377
|
+
cells.flatRowData = row;
|
|
378
|
+
return cells;
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
return parseData(data, {});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
componentDidUpdate() {
|
|
385
|
+
this.setVarStyle(this.columns);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
render() {
|
|
389
|
+
const SDataTable = Root;
|
|
390
|
+
const { Children, styles } = this.asProps;
|
|
391
|
+
|
|
392
|
+
return sstyled(styles)(
|
|
393
|
+
<SDataTable render={Box} __excludeProps={['data']} ref={this.tableRef}>
|
|
394
|
+
<Children />
|
|
395
|
+
</SDataTable>,
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
interface IDataTableCtx {
|
|
401
|
+
getHeadProps: PropGetterFn;
|
|
402
|
+
getBodyProps: PropGetterFn;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function ComponentDefinition() {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const DefinitionTable = createComponent(
|
|
410
|
+
RootDefinitionTable,
|
|
411
|
+
{
|
|
412
|
+
Head,
|
|
413
|
+
Body,
|
|
414
|
+
Column: ComponentDefinition,
|
|
415
|
+
Cell: ComponentDefinition,
|
|
416
|
+
Row: ComponentDefinition,
|
|
417
|
+
},
|
|
418
|
+
{},
|
|
419
|
+
) as (<T>(props: CProps<IDataTableProps & T, IDataTableCtx>) => ReturnEl) & {
|
|
420
|
+
Head: <T>(props: IDataTableHeadProps & T) => ReturnEl;
|
|
421
|
+
Body: <T>(props: IDataTableBodyProps & T) => ReturnEl;
|
|
422
|
+
Column: <T>(props: IDataTableColumnProps & T) => ReturnEl;
|
|
423
|
+
Cell: <T>(props: ChildRenderFn<IDataTableCellProps & T>) => ReturnEl;
|
|
424
|
+
Row: <T>(props: ChildRenderFn<IDataTableRowProps & T>) => ReturnEl;
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
export { ROW_GROUP };
|
|
428
|
+
export default DefinitionTable;
|