@react-fabric/data 1.0.0-beta.8 → 1.0.1
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/{src/histogram/Histogram.module.css → css/_histogram.css} +16 -13
- package/css/styles.css +24 -0
- package/dist/cjs/checklist/CheckList.js +59 -33
- package/dist/cjs/histogram/Histogram.js +41 -26
- package/dist/cjs/hooks/useFilteredList.js +100 -17
- package/dist/cjs/hooks/usePagination.js +29 -8
- package/dist/cjs/hooks/useSelectableList.js +30 -5
- package/dist/cjs/i18n/{ar.json.js → ar/data.json.js} +8 -1
- package/dist/cjs/i18n/{en.json.js → en/data.json.js} +8 -1
- package/dist/cjs/i18n/index.js +4 -4
- package/dist/cjs/index.js +30 -0
- package/dist/cjs/json/JsonViewer.js +80 -43
- package/dist/cjs/pagination/Pagination.js +24 -15
- package/dist/cjs/table/AddColumn.js +5 -5
- package/dist/cjs/table/BodyCell.js +20 -13
- package/dist/cjs/table/BodyRow.js +41 -28
- package/dist/cjs/table/CheckboxCell.js +10 -9
- package/dist/cjs/table/ColumnFilters.js +100 -0
- package/dist/cjs/table/EmptyCell.js +4 -4
- package/dist/cjs/table/HeaderCell.js +79 -53
- package/dist/cjs/table/HtmlTable.js +84 -0
- package/dist/cjs/table/{Context.js → Provider.js} +8 -10
- package/dist/cjs/table/Table.js +99 -33
- package/dist/cjs/{tree/types.js → table/context.js} +9 -5
- package/dist/cjs/table/types.js +29 -0
- package/dist/cjs/table/useResizer.js +10 -10
- package/dist/cjs/table/useTableColumns.js +4 -4
- package/dist/cjs/table/useVirtualTable.js +129 -22
- package/dist/cjs/tree/TreeNode.js +66 -46
- package/dist/cjs/tree/TreePanel.js +60 -24
- package/dist/cjs/tree/useTree.js +212 -47
- package/dist/cjs/tree/utils.js +63 -34
- package/dist/cjs/utils/getColor.js +58 -0
- package/dist/cjs/virtual/Gallery.js +119 -61
- package/dist/cjs/virtual/List.js +116 -55
- package/dist/cjs/virtual/Scroller.js +16 -8
- package/dist/esm/checklist/CheckList.js +61 -35
- package/dist/esm/histogram/Histogram.js +43 -28
- package/dist/esm/hooks/useFilteredList.js +101 -18
- package/dist/esm/hooks/usePagination.js +29 -8
- package/dist/esm/hooks/useSelectableList.js +30 -5
- package/dist/esm/i18n/{ar.json.js → ar/data.json.js} +8 -2
- package/dist/esm/i18n/{en.json.js → en/data.json.js} +8 -2
- package/dist/esm/i18n/index.js +2 -2
- package/dist/esm/index.js +31 -0
- package/dist/esm/json/JsonViewer.js +82 -45
- package/dist/esm/pagination/Pagination.js +25 -16
- package/dist/esm/table/AddColumn.js +6 -6
- package/dist/esm/table/BodyCell.js +21 -14
- package/dist/esm/table/BodyRow.js +41 -28
- package/dist/esm/table/CheckboxCell.js +11 -10
- package/dist/esm/table/ColumnFilters.js +100 -0
- package/dist/esm/table/EmptyCell.js +4 -4
- package/dist/esm/table/HeaderCell.js +80 -54
- package/dist/esm/table/HtmlTable.js +84 -0
- package/dist/esm/table/{Context.js → Provider.js} +11 -12
- package/dist/esm/table/Table.js +100 -34
- package/dist/esm/{tree/types.js → table/context.js} +8 -4
- package/dist/esm/table/types.js +29 -0
- package/dist/esm/table/useResizer.js +10 -10
- package/dist/esm/table/useTableColumns.js +4 -4
- package/dist/esm/table/useVirtualTable.js +131 -24
- package/dist/esm/tree/TreeNode.js +69 -49
- package/dist/esm/tree/TreePanel.js +62 -26
- package/dist/esm/tree/useTree.js +215 -50
- package/dist/esm/tree/utils.js +64 -35
- package/dist/esm/utils/getColor.js +58 -0
- package/dist/esm/virtual/Gallery.js +121 -63
- package/dist/esm/virtual/List.js +118 -57
- package/dist/esm/virtual/Scroller.js +17 -9
- package/dist/types/checklist/CheckList.d.ts +25 -6
- package/dist/types/histogram/Histogram.d.ts +28 -6
- package/dist/types/hooks/useFilteredList.d.ts +62 -5
- package/dist/types/hooks/usePagination.d.ts +18 -2
- package/dist/types/hooks/useSelectableList.d.ts +24 -1
- package/dist/types/index.d.ts +27 -0
- package/dist/types/json/JsonViewer.d.ts +23 -3
- package/dist/types/pagination/Pagination.d.ts +8 -2
- package/dist/types/table/AddColumn.d.ts +2 -2
- package/dist/types/table/BodyCell.d.ts +2 -2
- package/dist/types/table/BodyRow.d.ts +9 -9
- package/dist/types/table/CheckboxCell.d.ts +3 -3
- package/dist/types/table/ColumnFilters.d.ts +12 -0
- package/dist/types/table/EmptyCell.d.ts +1 -1
- package/dist/types/table/HeaderCell.d.ts +1 -1
- package/dist/types/table/HtmlTable.d.ts +21 -0
- package/dist/types/table/Provider.d.ts +2 -0
- package/dist/types/table/Table.d.ts +10 -1
- package/dist/types/table/context.d.ts +7 -0
- package/dist/types/table/types.d.ts +52 -10
- package/dist/types/table/useResizer.d.ts +1 -1
- package/dist/types/table/useTableColumns.d.ts +1 -1
- package/dist/types/table/useVirtualTable.d.ts +31 -12
- package/dist/types/tree/TreeNode.d.ts +1 -1
- package/dist/types/tree/TreePanel.d.ts +36 -1
- package/dist/types/tree/types.d.ts +75 -28
- package/dist/types/tree/useTree.d.ts +8 -12
- package/dist/types/tree/utils.d.ts +15 -8
- package/dist/types/utils/getColor.d.ts +43 -0
- package/dist/types/virtual/Gallery.d.ts +37 -6
- package/dist/types/virtual/List.d.ts +38 -6
- package/dist/types/virtual/Scroller.d.ts +1 -1
- package/package.json +17 -19
- package/src/checklist/CheckList.tsx +68 -71
- package/src/histogram/Histogram.tsx +47 -48
- package/src/hooks/useFilteredList.tsx +111 -28
- package/src/hooks/usePagination.tsx +39 -39
- package/src/hooks/useSelectableList.ts +45 -33
- package/src/i18n/{ar.json → ar/data.json} +5 -0
- package/src/i18n/{en.json → en/data.json} +5 -0
- package/src/i18n/index.ts +2 -2
- package/src/index.ts +23 -17
- package/src/json/JsonViewer.tsx +89 -120
- package/src/pagination/Pagination.tsx +104 -105
- package/src/table/AddColumn.tsx +6 -15
- package/src/table/BodyCell.tsx +27 -36
- package/src/table/BodyRow.tsx +36 -59
- package/src/table/CheckboxCell.tsx +12 -15
- package/src/table/ColumnFilters.tsx +93 -0
- package/src/table/EmptyCell.tsx +5 -18
- package/src/table/HeaderCell.tsx +72 -53
- package/src/table/HtmlTable.tsx +106 -0
- package/src/table/{Context.tsx → Provider.tsx} +11 -28
- package/src/table/Table.tsx +98 -83
- package/src/table/context.ts +33 -0
- package/src/table/types.ts +53 -27
- package/src/table/useResizer.ts +5 -9
- package/src/table/useTableColumns.ts +4 -6
- package/src/table/useVirtualTable.ts +136 -37
- package/src/tree/TreeNode.tsx +91 -86
- package/src/tree/TreePanel.tsx +87 -51
- package/src/tree/types.ts +85 -46
- package/src/tree/useTree.ts +210 -65
- package/src/tree/utils.ts +81 -62
- package/src/utils/getColor.ts +55 -0
- package/src/virtual/Gallery.tsx +129 -120
- package/src/virtual/List.tsx +124 -91
- package/src/virtual/Scroller.tsx +25 -9
- package/dist/cjs/histogram/Histogram.module.css +0 -1
- package/dist/esm/histogram/Histogram.module.css +0 -1
- package/dist/types/table/Context.d.ts +0 -8
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* React Fabric
|
|
3
|
+
* @version: 1.0.0
|
|
4
|
+
*
|
|
5
|
+
*
|
|
6
|
+
* The MIT License (MIT)
|
|
7
|
+
* Copyright (c) 2026 Adarsh Pastakia
|
|
8
|
+
*
|
|
9
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
|
10
|
+
* and associated documentation files (the "Software"), to deal in the Software without restriction,
|
|
11
|
+
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
12
|
+
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
|
13
|
+
* subject to the following conditions:
|
|
14
|
+
*
|
|
15
|
+
* The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
16
|
+
* portions of the Software.
|
|
17
|
+
*
|
|
18
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
19
|
+
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
20
|
+
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
+
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { createContext, use } from "react";
|
|
25
|
+
|
|
26
|
+
interface Context {
|
|
27
|
+
widths: Map<string, number>;
|
|
28
|
+
startResize: (col: HTMLElement) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const TableContext = createContext<Context>({} as Context);
|
|
32
|
+
|
|
33
|
+
export const useTableContext = () => use(TableContext);
|
package/src/table/types.ts
CHANGED
|
@@ -21,49 +21,66 @@
|
|
|
21
21
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
type Elements,
|
|
27
|
-
type IconProps,
|
|
28
|
-
type RefProp,
|
|
29
|
-
type TestProps,
|
|
30
|
-
} from "@react-fabric/core/dist/types/types";
|
|
24
|
+
import { type IconProps } from "@react-fabric/core/dist/types/components/icon/Icon";
|
|
25
|
+
import { type CssProp, type NestedKeys, type RefProp, type TestProps } from "@react-fabric/core/dist/types/types";
|
|
31
26
|
import { type ReactElement } from "react";
|
|
32
27
|
|
|
33
28
|
export const COL_MIN_WIDTH = "3rem";
|
|
34
29
|
export const COL_MAX_WIDTH = "64rem";
|
|
35
30
|
export const COL_DEFAULT_WIDTH = "12rem";
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
/**
|
|
33
|
+
* TableColumn interface defines the structure of a column in a table.
|
|
34
|
+
* It includes properties for column identification, display options, alignment,
|
|
35
|
+
* visibility, resizing, sorting, filtering, and rendering.
|
|
36
|
+
*
|
|
37
|
+
* @template T - The type of data the column will represent, defaulting to KeyValue.
|
|
38
|
+
* @property {keyof T | string} id - Unique identifier for the column.
|
|
39
|
+
* @property {"string" | "number" | "boolean" | "datetime" | "date" | "time" | "unknown"} [dataType] - Data type of the column values.
|
|
40
|
+
* @property {string} [label] - Display label for the column header.
|
|
41
|
+
* @property {string} [tooltip] - Tooltip text for the column header.
|
|
42
|
+
* @property {string} [format] - Format string for displaying the column values.
|
|
43
|
+
* @property {"start" | "center" | "end"} [align] - Text alignment within the column.
|
|
44
|
+
* @property {"start" | "end"} [locked] - Indicates if the column is locked at the start or end.
|
|
45
|
+
* @property {boolean} [hidden] - Whether the column is hidden.
|
|
46
|
+
* @property {boolean} [truncate] - Whether to truncate the text in the column.
|
|
47
|
+
* @property {boolean} [hideable] - Whether the column can be hidden by the user.
|
|
48
|
+
* @property {boolean} [resizable] - Whether the column can be resized by the user.
|
|
49
|
+
* @property {boolean} [sortable] - Whether the column can be sorted.
|
|
50
|
+
* @property {boolean} [filterable] - Whether the column can be filtered.
|
|
51
|
+
* @property {AnyObject} [filter] - Filter criteria for the column.
|
|
52
|
+
* @property {ReactElement[]} [actions] - Array of action elements to be displayed in the column.
|
|
53
|
+
* @property {AnyObject[]} [filterOptions] - Options for filtering the column.
|
|
54
|
+
* @property {string | number} [width] - Width of the column.
|
|
55
|
+
* @property {string | number} [minWidth] - Minimum width of the column.
|
|
56
|
+
* @property {string | number} [maxWidth] - Maximum width of the column.
|
|
57
|
+
* @property {KeyValue} [valueMap] - Mapping of values for the column.
|
|
58
|
+
* @property {(value: AnyObject, data: T, index: number) => Elements<AnyObject>} [renderer] - Custom renderer function for the column values.
|
|
59
|
+
*/
|
|
60
|
+
export interface TableColumn<T = KeyValue> extends CssProp, TestProps {
|
|
61
|
+
id: NestedKeys<T>;
|
|
62
|
+
field?: string;
|
|
42
63
|
label?: string;
|
|
43
64
|
tooltip?: string;
|
|
44
|
-
|
|
65
|
+
icon?: IconProps;
|
|
66
|
+
actions?: ReactElement[];
|
|
45
67
|
align?: "start" | "center" | "end";
|
|
46
68
|
locked?: "start" | "end";
|
|
47
69
|
hidden?: boolean;
|
|
70
|
+
truncate?: boolean;
|
|
48
71
|
hideable?: boolean;
|
|
49
72
|
resizable?: boolean;
|
|
50
73
|
sortable?: boolean;
|
|
51
74
|
filterable?: boolean;
|
|
52
|
-
|
|
75
|
+
filter?: string | string[];
|
|
76
|
+
filterOptions?: Array<string | { id: string; label: string }>;
|
|
53
77
|
width?: string | number;
|
|
54
78
|
minWidth?: string | number;
|
|
55
79
|
maxWidth?: string | number;
|
|
56
|
-
dataType?:
|
|
57
|
-
| "string"
|
|
58
|
-
| "number"
|
|
59
|
-
| "boolean"
|
|
60
|
-
| "datetime"
|
|
61
|
-
| "date"
|
|
62
|
-
| "time"
|
|
63
|
-
| "unknown";
|
|
80
|
+
dataType?: "string" | "number" | "boolean" | "datetime" | "date" | "time" | "unknown";
|
|
64
81
|
format?: string;
|
|
65
|
-
valueMap?: KeyValue
|
|
66
|
-
renderer?: (value: AnyObject, data: T, index: number) =>
|
|
82
|
+
valueMap?: KeyValue<string>;
|
|
83
|
+
renderer?: (value: AnyObject, data: T, index: number) => React.ReactElement<unknown>;
|
|
67
84
|
}
|
|
68
85
|
|
|
69
86
|
export interface TableRef {
|
|
@@ -75,13 +92,17 @@ export interface TableRef {
|
|
|
75
92
|
export interface TableProps<T = KeyValue> extends RefProp<TableRef> {
|
|
76
93
|
columns: Array<TableColumn<T>>;
|
|
77
94
|
data: T[];
|
|
78
|
-
keyProperty?:
|
|
95
|
+
keyProperty?: NestedKeys<T>;
|
|
96
|
+
groupProperty?: NestedKeys<T>;
|
|
97
|
+
groupRenderer?: (key: AnyObject) => ReactElement;
|
|
79
98
|
emptyDisplay?: ReactElement;
|
|
99
|
+
|
|
80
100
|
/**
|
|
81
101
|
* checkable rows
|
|
82
102
|
*/
|
|
103
|
+
checked?: AnyObject[];
|
|
83
104
|
checkableRows?: boolean;
|
|
84
|
-
onCheckedChanged?: (rows:
|
|
105
|
+
onCheckedChanged?: (rows: AnyObject[]) => void;
|
|
85
106
|
/**
|
|
86
107
|
* sorting
|
|
87
108
|
*/
|
|
@@ -97,5 +118,10 @@ export interface TableProps<T = KeyValue> extends RefProp<TableRef> {
|
|
|
97
118
|
onScroll?: (firstItem: number) => void;
|
|
98
119
|
|
|
99
120
|
canExpand?: (record: T) => boolean;
|
|
100
|
-
children?: (record: T) =>
|
|
121
|
+
children?: (record: T) => React.ReactElement<unknown>;
|
|
122
|
+
|
|
123
|
+
onFilter?: (id: NestedKeys<T> | string, value?: AnyObject) => void;
|
|
124
|
+
|
|
125
|
+
total?: number;
|
|
126
|
+
onLoadMore?: (lastIndex?: number) => void;
|
|
101
127
|
}
|
package/src/table/useResizer.ts
CHANGED
|
@@ -21,11 +21,7 @@
|
|
|
21
21
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
export
|
|
25
|
-
colEl: HTMLElement,
|
|
26
|
-
ghostEl: HTMLElement,
|
|
27
|
-
callback: (width: number) => void,
|
|
28
|
-
) => {
|
|
24
|
+
export function startResizer(colEl: HTMLElement, ghostEl: HTMLElement, callback: (width: number) => void) {
|
|
29
25
|
const placeholder = ghostEl.firstElementChild as HTMLElement;
|
|
30
26
|
const isRtl = getComputedStyle(colEl).direction === "rtl";
|
|
31
27
|
|
|
@@ -47,13 +43,13 @@ export const useResizer = (
|
|
|
47
43
|
/** ***************** attach handlers *******************/
|
|
48
44
|
const box = colEl.getBoundingClientRect();
|
|
49
45
|
if (colEl.parentElement != null) {
|
|
50
|
-
const parentBox = colEl.
|
|
46
|
+
const parentBox = colEl.closest("[data-ref='tableScroller']")?.getBoundingClientRect() as unknown as DOMRect;
|
|
51
47
|
if (isRtl) {
|
|
52
48
|
placeholder.style.left = "unset";
|
|
53
|
-
placeholder.style.right = `${
|
|
49
|
+
placeholder.style.right = `${parentBox.width - box.right}px`;
|
|
54
50
|
} else {
|
|
55
51
|
placeholder.style.right = "unset";
|
|
56
|
-
placeholder.style.left = `${box.left
|
|
52
|
+
placeholder.style.left = `${box.left}px`;
|
|
57
53
|
}
|
|
58
54
|
}
|
|
59
55
|
placeholder.style.width = `${colEl.offsetWidth}px`;
|
|
@@ -63,4 +59,4 @@ export const useResizer = (
|
|
|
63
59
|
|
|
64
60
|
document.addEventListener("mousemove", onResize);
|
|
65
61
|
document.addEventListener("mouseup", onResizeEnd);
|
|
66
|
-
}
|
|
62
|
+
}
|
|
@@ -32,11 +32,9 @@ interface ColumnState {
|
|
|
32
32
|
cols: TableColumn[];
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
type ColumnActions =
|
|
36
|
-
| { type: "columns"; columns: TableColumn[] }
|
|
37
|
-
| { type: "hide"; id: string };
|
|
35
|
+
type ColumnActions = { type: "columns"; columns: TableColumn[] } | { type: "hide"; id: string };
|
|
38
36
|
|
|
39
|
-
export
|
|
37
|
+
export function useTableColumns(columns: TableColumn[]) {
|
|
40
38
|
const [state, dispatch] = useReducer(
|
|
41
39
|
(state: ColumnState, action: ColumnActions) => {
|
|
42
40
|
if (action.type === "columns") {
|
|
@@ -44,7 +42,7 @@ export const useTableColumns = (columns: TableColumn[]) => {
|
|
|
44
42
|
}
|
|
45
43
|
if (action.type === "hide") {
|
|
46
44
|
const col = state.columns.find((col) => col.id === action.id);
|
|
47
|
-
col
|
|
45
|
+
if (col)
|
|
48
46
|
state.columns.splice(state.columns.indexOf(col), 1, {
|
|
49
47
|
...col,
|
|
50
48
|
hidden: col.hidden !== true,
|
|
@@ -82,4 +80,4 @@ export const useTableColumns = (columns: TableColumn[]) => {
|
|
|
82
80
|
state,
|
|
83
81
|
onHide: (id: string) => dispatch({ type: "hide", id }),
|
|
84
82
|
};
|
|
85
|
-
}
|
|
83
|
+
}
|
|
@@ -21,93 +21,192 @@
|
|
|
21
21
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
24
|
+
import { compareValues, debounce, groupBy } from "@react-fabric/utilities";
|
|
25
|
+
import { defaultRangeExtractor, type Range, useVirtualizer } from "@tanstack/react-virtual";
|
|
26
|
+
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
26
27
|
|
|
27
|
-
interface
|
|
28
|
+
interface DataGroup {
|
|
29
|
+
__GROUP__: true;
|
|
30
|
+
key: Key;
|
|
31
|
+
open: boolean;
|
|
32
|
+
itemCount: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface DataState<T = KeyValue> {
|
|
36
|
+
data: T[];
|
|
37
|
+
items: Array<T | DataGroup>;
|
|
38
|
+
grouped: KeyValue<T[]>;
|
|
39
|
+
groups: DataGroup[];
|
|
28
40
|
expanded: Key[];
|
|
29
41
|
checked: Key[];
|
|
30
|
-
allChecked:
|
|
42
|
+
allChecked: number;
|
|
31
43
|
}
|
|
32
44
|
|
|
33
|
-
type DataAction =
|
|
45
|
+
type DataAction<T = KeyValue> =
|
|
46
|
+
| { type: "updateState"; checked: Key[] }
|
|
47
|
+
| { type: "groupExpand"; key: keyof T }
|
|
48
|
+
| { type: "reset"; key?: keyof T; data: T[] }
|
|
34
49
|
| { type: "expand"; key: Key }
|
|
35
50
|
| { type: "check"; key: Key }
|
|
36
51
|
| { type: "checkall" };
|
|
37
52
|
|
|
38
|
-
export
|
|
39
|
-
data:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
export function useVirtualTable<T extends KeyValue = KeyValue>(
|
|
54
|
+
data: T[],
|
|
55
|
+
options: {
|
|
56
|
+
checked?: Key[];
|
|
57
|
+
keyProperty: keyof T;
|
|
58
|
+
groupProperty?: keyof T;
|
|
59
|
+
loading?: boolean;
|
|
60
|
+
total: number;
|
|
61
|
+
onLoadMore?: (startIndex: number) => void;
|
|
62
|
+
onCheckChanged?: (checked: AnyObject[]) => void;
|
|
63
|
+
},
|
|
64
|
+
) {
|
|
43
65
|
// TODO: refactor data into item list, apply grouping if groupColumn provided
|
|
44
66
|
// TODO: when grouping maintain map of group with children, {___GROUP___:true, label, items, open}
|
|
45
|
-
|
|
46
|
-
const scrollerRef = useRef<HTMLDivElement>(null);
|
|
47
|
-
const virtualizer = useVirtualizer({
|
|
48
|
-
count: data.length,
|
|
49
|
-
getScrollElement: () => scrollerRef.current,
|
|
50
|
-
estimateSize: () => 24,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
67
|
const [state, dispatch] = useReducer(
|
|
54
|
-
(state: DataState
|
|
68
|
+
(state: DataState<T>, action: DataAction<T>) => {
|
|
69
|
+
if (action.type === "updateState") {
|
|
70
|
+
state.checked = action.checked;
|
|
71
|
+
}
|
|
55
72
|
if (action.type === "expand") {
|
|
56
|
-
if (state.expanded.includes(action.key))
|
|
57
|
-
state.expanded.splice(state.expanded.indexOf(action.key), 1);
|
|
73
|
+
if (state.expanded.includes(action.key)) state.expanded.splice(state.expanded.indexOf(action.key), 1);
|
|
58
74
|
else state.expanded.push(action.key);
|
|
59
75
|
}
|
|
60
76
|
if (action.type === "check") {
|
|
61
|
-
if (state.checked.includes(action.key))
|
|
62
|
-
state.checked.splice(state.checked.indexOf(action.key), 1);
|
|
77
|
+
if (state.checked.includes(action.key)) state.checked.splice(state.checked.indexOf(action.key), 1);
|
|
63
78
|
else state.checked.push(action.key);
|
|
64
|
-
onCheckChanged?.(state.checked);
|
|
79
|
+
options.onCheckChanged?.(state.checked);
|
|
65
80
|
}
|
|
66
81
|
if (action.type === "checkall") {
|
|
67
82
|
if (state.allChecked !== 0) {
|
|
68
83
|
state.checked = [];
|
|
69
|
-
} else if (keyProperty) {
|
|
70
|
-
state.checked = data.map((item) => item[keyProperty]);
|
|
84
|
+
} else if (options.keyProperty) {
|
|
85
|
+
state.checked = state.data.map((item) => item[options.keyProperty]);
|
|
86
|
+
}
|
|
87
|
+
options.onCheckChanged?.(state.checked);
|
|
88
|
+
}
|
|
89
|
+
const checkList = state.data.filter((hit) => state.checked.includes(hit[options.keyProperty]));
|
|
90
|
+
if (action.type === "groupExpand") {
|
|
91
|
+
const group = state.groups.find((group) => group.key === action.key);
|
|
92
|
+
if (group) {
|
|
93
|
+
group.open = !group.open;
|
|
94
|
+
state.groups = state.groups.slice();
|
|
95
|
+
state.items = state.groups
|
|
96
|
+
.map((group) => [group, group.open ? state.grouped[group.key] : undefined])
|
|
97
|
+
.flat(2)
|
|
98
|
+
.filter(Boolean) as never;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (action.type === "reset") {
|
|
102
|
+
state.data = action.data;
|
|
103
|
+
if (action.key) {
|
|
104
|
+
state.grouped = groupBy<T>(state.data, action.key);
|
|
105
|
+
state.groups = Object.keys(state.grouped)
|
|
106
|
+
.sort(compareValues("asc"))
|
|
107
|
+
.map((group) => ({
|
|
108
|
+
__GROUP__: true,
|
|
109
|
+
open: true,
|
|
110
|
+
key: group,
|
|
111
|
+
itemCount: state.grouped[group].length,
|
|
112
|
+
}));
|
|
113
|
+
state.items = state.groups.map((group) => [group, ...state.grouped[group.key]]).flat();
|
|
114
|
+
} else {
|
|
115
|
+
state.items = state.data;
|
|
71
116
|
}
|
|
72
|
-
onCheckChanged?.(state.checked);
|
|
73
117
|
}
|
|
74
118
|
return {
|
|
75
119
|
...state,
|
|
76
|
-
allChecked:
|
|
77
|
-
|
|
78
|
-
? 1
|
|
79
|
-
: state.checked.length
|
|
80
|
-
? 2
|
|
81
|
-
: 0,
|
|
82
|
-
} as DataState;
|
|
120
|
+
allChecked: checkList.length === state.data.length ? 1 : state.checked.length ? 2 : 0,
|
|
121
|
+
};
|
|
83
122
|
},
|
|
84
123
|
{
|
|
124
|
+
data,
|
|
125
|
+
items: [],
|
|
126
|
+
grouped: {},
|
|
127
|
+
groups: [],
|
|
85
128
|
checked: [],
|
|
86
129
|
expanded: [],
|
|
87
130
|
allChecked: 0,
|
|
88
131
|
},
|
|
89
132
|
);
|
|
90
133
|
|
|
134
|
+
const activeStickyIndexRef = useRef(0);
|
|
135
|
+
const stickyIndexes = useMemo<number[]>(
|
|
136
|
+
() => state.items.map((hit, idx) => ("__GROUP__" in hit ? idx : undefined)).filter(Boolean) as number[],
|
|
137
|
+
[state.items],
|
|
138
|
+
);
|
|
139
|
+
const isActiveSticky = (index: number) => activeStickyIndexRef.current === index;
|
|
140
|
+
|
|
141
|
+
const [hasActiveSticky, setHasActiveSticky] = useState(false);
|
|
142
|
+
const scrollerRef = useRef<HTMLDivElement>(null);
|
|
143
|
+
const virtualizer = useVirtualizer({
|
|
144
|
+
count: state.items.length,
|
|
145
|
+
getScrollElement: () => scrollerRef.current,
|
|
146
|
+
estimateSize: () => 32,
|
|
147
|
+
rangeExtractor: useCallback(
|
|
148
|
+
(range: Range) => {
|
|
149
|
+
activeStickyIndexRef.current = [...stickyIndexes].reverse().find((index) => range.startIndex > index) ?? 0;
|
|
150
|
+
|
|
151
|
+
setHasActiveSticky(range.startIndex !== activeStickyIndexRef.current);
|
|
152
|
+
|
|
153
|
+
const next = new Set([activeStickyIndexRef.current, ...defaultRangeExtractor(range)]);
|
|
154
|
+
|
|
155
|
+
return [...next];
|
|
156
|
+
},
|
|
157
|
+
[stickyIndexes],
|
|
158
|
+
),
|
|
159
|
+
});
|
|
160
|
+
|
|
91
161
|
const virtualItems = virtualizer.getVirtualItems();
|
|
92
162
|
|
|
93
163
|
const getData = useCallback(
|
|
94
164
|
(index: number) => {
|
|
95
|
-
return
|
|
165
|
+
return state.items[index];
|
|
96
166
|
},
|
|
97
|
-
[
|
|
167
|
+
[state.items],
|
|
98
168
|
);
|
|
99
169
|
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
dispatch({ type: "reset", key: options.groupProperty, data });
|
|
172
|
+
}, [data, options.groupProperty]);
|
|
173
|
+
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
dispatch({ type: "updateState", checked: options.checked ?? [] });
|
|
176
|
+
}, [options.checked]);
|
|
177
|
+
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
if (options.onLoadMore) {
|
|
180
|
+
const firstItem = virtualItems[0]?.index;
|
|
181
|
+
const lastItem = virtualItems?.slice(-1).pop()?.index;
|
|
182
|
+
const canLoadPrevPage = firstItem !== undefined && state.items[firstItem] === null;
|
|
183
|
+
const canLoadNextPage =
|
|
184
|
+
lastItem !== undefined && lastItem === state.items.length - 1 && state.items.length < options.total;
|
|
185
|
+
const db = debounce(options.onLoadMore);
|
|
186
|
+
if (canLoadPrevPage) {
|
|
187
|
+
db(firstItem);
|
|
188
|
+
} else if (!options.loading && canLoadNextPage) {
|
|
189
|
+
db(lastItem + 1);
|
|
190
|
+
}
|
|
191
|
+
return () => db.cancel();
|
|
192
|
+
}
|
|
193
|
+
// eslint-disable-next-line @eslint-react/exhaustive-deps
|
|
194
|
+
}, [options.onLoadMore, virtualItems]);
|
|
195
|
+
|
|
100
196
|
return {
|
|
101
197
|
scrollerRef,
|
|
102
198
|
items: virtualItems,
|
|
103
199
|
checkState: state,
|
|
104
200
|
getData,
|
|
105
201
|
virtualizer,
|
|
202
|
+
hasActiveSticky,
|
|
203
|
+
isActiveSticky,
|
|
106
204
|
top: virtualizer.range?.startIndex,
|
|
107
205
|
totalSize: virtualizer.getTotalSize,
|
|
108
206
|
measureElement: virtualizer.measureElement,
|
|
207
|
+
toggleGroupExpand: (key: Key) => dispatch({ type: "groupExpand", key }),
|
|
109
208
|
toggleExpand: (key: Key) => dispatch({ type: "expand", key }),
|
|
110
209
|
toggleChecked: (key: Key) => dispatch({ type: "check", key }),
|
|
111
210
|
toggleAllChecked: () => dispatch({ type: "checkall" }),
|
|
112
211
|
};
|
|
113
|
-
}
|
|
212
|
+
}
|