@azrico/reactgrid 1.4.1 → 1.5.2

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.
@@ -1,83 +1,92 @@
1
- type mrow = { __parent__table_id: any; __table_id: any; __children?: any[] };
2
- type converted_row = { parent: any; self: any; data: mrow };
3
-
4
- function getParentId(obj) {
5
- return obj.__parent__table_id ?? obj.parentid ?? obj.parent;
6
- }
7
- function getSelfId(obj) {
8
- return obj.__table_id ?? obj._id;
9
- }
10
- //given an array of rows, fixes parent,child relations
11
- export function fix_parent_child_relations<T extends mrow>(rows: T[]): T[] {
12
- if (!Array.isArray(rows)) rows = [];
13
- const anyparentid = rows.find((r) => getParentId(r));
14
- const anyselfid = rows.find((r) => getSelfId(r));
15
- if (!anyparentid || !anyselfid) return rows;
16
-
17
- const converted_rows: converted_row[] = rows
18
- .filter((r) => r != null)
19
- .map((r) => {
20
- return { parent: getParentId(r), self: getSelfId(r), data: r };
21
- });
22
-
23
- const top_level_rows: converted_row[] = converted_rows.filter((r) => !r.parent);
24
- const any_child = converted_rows.find((r) => Boolean(r.parent));
25
- if (any_child) {
26
- //find children of top-level parents:
27
- for (let index = 0; index < top_level_rows.length; index++) {
28
- const mrow = top_level_rows[index];
29
- assign_children(mrow, converted_rows);
30
- }
31
- }
32
- return top_level_rows.map((r) => r.data) as T[];
33
- }
34
-
35
- function assign_children(row: converted_row, all_rows: converted_row[]) {
36
- const my_children = all_rows.filter((s) => row.self == s.parent);
37
- if (my_children.length > 0) {
38
- //find children of my children:
39
- for (let index = 0; index < my_children.length; index++) {
40
- const child = my_children[index];
41
- assign_children(child, all_rows);
42
- }
43
- row.data.__children = my_children.map((r) => r.data);
44
- } else if (!Array.isArray(row.data.__children) || row.data.__children.length == 0)
45
- row.data.__children = undefined;
46
- }
47
- export class func_selection {
48
- static getVirtualElement(x = 0, y = 0) {
49
- const boundsRect = func_selection.generateGetBoundingClientRect(x, y);
50
- return {
51
- getBoundingClientRect: boundsRect,
52
- };
53
- }
54
- static generateGetBoundingClientRect(x = 0, y = 0) {
55
- return () => ({
56
- width: 0,
57
- height: 0,
58
- top: y,
59
- right: x,
60
- bottom: y,
61
- left: x,
62
- });
63
- }
64
- static get_selectedText(): string {
65
- var text: any = '';
66
- if (window.getSelection) {
67
- text = window.getSelection()?.toString();
68
- } else if (
69
- (document as any).selection &&
70
- (document as any).selection.type != 'Control'
71
- ) {
72
- text = (document as any).selection.createRange()?.text;
73
- }
74
- return text || '';
75
- }
76
- static has_selection(): boolean {
77
- const slct = window.getSelection();
78
- if (slct == null) return false;
79
- if (slct.type == null) return func_selection.get_selectedText().length !== 0;
80
- if (slct.type === 'Range') return true;
81
- return false;
82
- }
83
- }
1
+ type mrow = { __parent__table_id: any; __table_id: any; __children?: any[] };
2
+ type converted_row = { parent: any; self: any; data: mrow };
3
+
4
+ function getParentId(obj) {
5
+ return obj.__parent__table_id ?? obj.parentid ?? obj.parent;
6
+ }
7
+ function getSelfId(obj) {
8
+ return obj.__table_id ?? obj._id;
9
+ }
10
+ //given an array of rows, fixes parent,child relations
11
+ export function fix_parent_child_relations<T extends mrow>(rows: T[]): T[] {
12
+ if (!Array.isArray(rows)) rows = [];
13
+ const anyparentid = rows.find((r) => getParentId(r));
14
+ const anyselfid = rows.find((r) => getSelfId(r));
15
+ if (!anyparentid || !anyselfid) return rows;
16
+
17
+ const converted_rows: converted_row[] = rows
18
+ .filter((r) => r != null)
19
+ .map((r) => {
20
+ return { parent: getParentId(r), self: getSelfId(r), data: r };
21
+ });
22
+
23
+ const top_level_rows: converted_row[] = converted_rows.filter((r) => !r.parent);
24
+ //group rows by parent id once, instead of scanning all rows for every parent
25
+ const children_by_parent = new Map<string, converted_row[]>();
26
+ for (let index = 0; index < converted_rows.length; index++) {
27
+ const r = converted_rows[index];
28
+ if (!r.parent) continue;
29
+ const key = String(r.parent);
30
+ const siblings = children_by_parent.get(key);
31
+ if (siblings) siblings.push(r);
32
+ else children_by_parent.set(key, [r]);
33
+ }
34
+ if (children_by_parent.size > 0) {
35
+ //find children of top-level parents:
36
+ for (let index = 0; index < top_level_rows.length; index++) {
37
+ const mrow = top_level_rows[index];
38
+ assign_children(mrow, children_by_parent);
39
+ }
40
+ }
41
+ return top_level_rows.map((r) => r.data) as T[];
42
+ }
43
+
44
+ function assign_children(row: converted_row, children_by_parent: Map<string, converted_row[]>) {
45
+ const my_children = row.self == null ? undefined : children_by_parent.get(String(row.self));
46
+ if (my_children && my_children.length > 0) {
47
+ //find children of my children:
48
+ for (let index = 0; index < my_children.length; index++) {
49
+ const child = my_children[index];
50
+ assign_children(child, children_by_parent);
51
+ }
52
+ row.data.__children = my_children.map((r) => r.data);
53
+ } else if (!Array.isArray(row.data.__children) || row.data.__children.length == 0)
54
+ row.data.__children = undefined;
55
+ }
56
+ export class func_selection {
57
+ static getVirtualElement(x = 0, y = 0) {
58
+ const boundsRect = func_selection.generateGetBoundingClientRect(x, y);
59
+ return {
60
+ getBoundingClientRect: boundsRect,
61
+ };
62
+ }
63
+ static generateGetBoundingClientRect(x = 0, y = 0) {
64
+ return () => ({
65
+ width: 0,
66
+ height: 0,
67
+ top: y,
68
+ right: x,
69
+ bottom: y,
70
+ left: x,
71
+ });
72
+ }
73
+ static get_selectedText(): string {
74
+ var text: any = '';
75
+ if (window.getSelection) {
76
+ text = window.getSelection()?.toString();
77
+ } else if (
78
+ (document as any).selection &&
79
+ (document as any).selection.type != 'Control'
80
+ ) {
81
+ text = (document as any).selection.createRange()?.text;
82
+ }
83
+ return text || '';
84
+ }
85
+ static has_selection(): boolean {
86
+ const slct = window.getSelection();
87
+ if (slct == null) return false;
88
+ if (slct.type == null) return func_selection.get_selectedText().length !== 0;
89
+ if (slct.type === 'Range') return true;
90
+ return false;
91
+ }
92
+ }
@@ -1,34 +1,28 @@
1
- import React from 'react';
2
- import {
3
- AzGridProps,
4
- DataGridWrapperProps,
5
- GridType,
6
- LooseColumnDefinition,
7
- } from '../types/types';
8
- import { applyColumnData, find_realData, get_row_id } from '../func/func_grid';
9
- import { func_selection } from '../func/func_child';
10
-
11
- export function useColumnData(
12
- inputOrProps: undefined | any[] | LooseColumnDefinition[] | AzGridProps
13
- ) {
14
- if (inputOrProps == null) return [];
15
- if (Array.isArray(inputOrProps)) return useThisColumnData(inputOrProps);
16
- return usePropsColumnData(inputOrProps);
17
- }
18
- function useThisColumnData(cols: any[]) {
19
- return React.useMemo(() => {
20
- return applyColumnData(cols);
21
- }, [cols]);
22
- }
23
- function usePropsColumnData(props: AzGridProps) {
24
- return React.useMemo(() => {
25
- return applyColumnData(props);
26
- }, [
27
- props.columns,
28
- props.leftColumns,
29
- props.rightColumns,
30
- props.editableColumns,
31
- props.columnExtensions,
32
- props.customColumns,
33
- ]);
34
- }
1
+ import React from 'react';
2
+ import { AzGridProps, LooseColumnDefinition } from '../types/types';
3
+ import { applyColumnData } from '../func/func_grid';
4
+
5
+ const EMPTY_COLUMNS: any[] = [];
6
+
7
+ export function useColumnData(
8
+ inputOrProps: undefined | any[] | LooseColumnDefinition[] | AzGridProps
9
+ ) {
10
+ // one useMemo for every input kind, so switching between them keeps the hook order
11
+ const isProps = inputOrProps != null && !Array.isArray(inputOrProps);
12
+ const props = (isProps ? inputOrProps : {}) as AzGridProps;
13
+ const deps = isProps
14
+ ? [
15
+ props.columns,
16
+ props.leftColumns,
17
+ props.rightColumns,
18
+ props.editableColumns,
19
+ props.columnExtensions,
20
+ props.customColumns,
21
+ ]
22
+ : [inputOrProps, null, null, null, null, null];
23
+
24
+ return React.useMemo(() => {
25
+ if (inputOrProps == null) return EMPTY_COLUMNS;
26
+ return applyColumnData(inputOrProps);
27
+ }, [isProps, ...deps]);
28
+ }
@@ -1,96 +1,157 @@
1
- import React from 'react';
2
- import { DataGridWrapperProps, GridType } from '../types/types';
3
- import { find_realData, get_row_id } from '../func/func_grid';
4
- import { func_selection } from '../func/func_child';
5
-
6
- function useGridMenuRow() {
7
- const [menuAnchorEl, set_menuAnchorEl] = React.useState<any>(null);
8
- const [menuRow, set_menuRow] = React.useState<any>(null);
9
- const update_menuRow = React.useCallback((r: any = null, el: any = null) => {
10
- set_menuRow(r || null);
11
- set_menuAnchorEl(el || null);
12
- }, []);
13
- return { update_menuRow, menuAnchorEl, menuRow };
14
- }
15
- export function useGridEvents(grid: GridType, props: DataGridWrapperProps) {
16
- const click_data = React.useRef<any>(null);
17
- const { update_menuRow, menuAnchorEl, menuRow } = useGridMenuRow();
18
-
19
- const refreshColumn = React.useCallback(
20
- (col: string) => {
21
- const dt = grid.ungroupedData;
22
- for (let index = 0; index < dt.length; index++) {
23
- (grid as any).setItemPropertyAt(index, col, dt[index][col]);
24
- }
25
- },
26
- [grid]
27
- );
28
- const refresh = React.useCallback(() => {
29
- const dt = grid.ungroupedData;
30
- for (let index = 0; index < dt.length; index++) {
31
- (grid as any).setItemAt(index, {}, { replace: false });
32
- }
33
- }, [grid]);
34
-
35
- React.useEffect(() => {
36
- if (!grid) return;
37
- grid['refresh'] = refresh;
38
- grid['refreshColumn'] = refreshColumn;
39
- }, [grid, props.dataSource, refresh, refreshColumn]);
40
- /* -------------------------------------------------------------------------- */
41
- /* events */
42
- /* -------------------------------------------------------------------------- */
43
- const onRowClick = React.useCallback(
44
- (rowProps, event) => {
45
- const nowDate = Date.now();
46
- const lastclick: number = click_data.current[get_row_id(rowProps)];
47
- if (lastclick) {
48
- const diffTime = Math.abs(nowDate - lastclick);
49
- if (diffTime < 400) {
50
- click_data.current[get_row_id(rowProps)] = null;
51
- return (
52
- props.onRowDoubleClick &&
53
- props.onRowDoubleClick(rowProps.rowIndex, rowProps.data)
54
- );
55
- }
56
- }
57
-
58
- click_data.current[get_row_id(rowProps)] = nowDate;
59
- return props.onRowClick && props.onRowClick(rowProps.rowIndex, rowProps.data);
60
- },
61
- [props.onRowClick, props.onRowDoubleClick]
62
- );
63
-
64
- React.useEffect(() => {
65
- click_data.current = {};
66
- }, [props.dataSource]);
67
-
68
- /* ------------------------------- ContextMenu ------------------------------ */
69
- const onRowContextMenu = React.useCallback((rowProps, event) => {
70
- if (!rowProps || !rowProps.data || func_selection.has_selection()) {
71
- update_menuRow(null, null);
72
- return true;
73
- }
74
- event.preventDefault();
75
-
76
- const realRow = find_realData(rowProps.dataSourceArray, event.target, rowProps.data);
77
- const anEl = func_selection.getVirtualElement(event.clientX, event.clientY);
78
- update_menuRow(realRow, anEl);
79
- return false;
80
- }, []);
81
-
82
- /* -------------------------------------------------------------------------- */
83
- const onRenderRow = React.useCallback((rowProps) => {
84
- // save the original handlers to be called later
85
- rowProps['data-row-id'] = get_row_id(rowProps.data);
86
- }, []);
87
-
88
- return {
89
- onRowClick,
90
- onRowContextMenu,
91
- onRenderRow,
92
- update_menuRow,
93
- menuAnchorEl,
94
- menuRow,
95
- };
96
- }
1
+ import React from 'react';
2
+ import { DataGridWrapperProps, GridType } from '../types/types';
3
+ import { find_realData, get_row_id } from '../func/func_grid';
4
+ import { func_selection } from '../func/func_child';
5
+ import {
6
+ apply_root_a11y,
7
+ apply_row_a11y,
8
+ get_grid_root,
9
+ get_root_a11y_key,
10
+ is_grid_busy,
11
+ } from '../func/func_a11y';
12
+
13
+ function useGridMenuRow() {
14
+ const [menuAnchorEl, set_menuAnchorEl] = React.useState<any>(null);
15
+ const [menuRow, set_menuRow] = React.useState<any>(null);
16
+ const update_menuRow = React.useCallback((r: any = null, el: any = null) => {
17
+ set_menuRow(r || null);
18
+ set_menuAnchorEl(el || null);
19
+ }, []);
20
+ return { update_menuRow, menuAnchorEl, menuRow };
21
+ }
22
+ export function useGridEvents(grid: GridType, props: DataGridWrapperProps) {
23
+ const click_data = React.useRef<any>(null);
24
+ // latest props, so the callbacks below stay stable and don't re-render the grid
25
+ const latestProps = React.useRef(props);
26
+ latestProps.current = props;
27
+ const { update_menuRow, menuAnchorEl, menuRow } = useGridMenuRow();
28
+
29
+ const refreshColumn = React.useCallback(
30
+ (col: string) => {
31
+ const dt = grid.ungroupedData;
32
+ for (let index = 0; index < dt.length; index++) {
33
+ (grid as any).setItemPropertyAt(index, col, dt[index][col]);
34
+ }
35
+ },
36
+ [grid]
37
+ );
38
+ const refresh = React.useCallback(() => {
39
+ const dt = grid.ungroupedData;
40
+ for (let index = 0; index < dt.length; index++) {
41
+ (grid as any).setItemAt(index, {}, { replace: false });
42
+ }
43
+ }, [grid]);
44
+
45
+ React.useEffect(() => {
46
+ if (!grid) return;
47
+ grid['refresh'] = refresh;
48
+ grid['refreshColumn'] = refreshColumn;
49
+ }, [grid, props.dataSource, refresh, refreshColumn]);
50
+ /* -------------------------------------------------------------------------- */
51
+ /* events */
52
+ /* -------------------------------------------------------------------------- */
53
+ const onRowClick = React.useCallback((rowProps, event) => {
54
+ const { onRowClick, onRowDoubleClick } = latestProps.current;
55
+ const nowDate = Date.now();
56
+ const lastclick: number = click_data.current[get_row_id(rowProps)];
57
+ if (lastclick) {
58
+ const diffTime = Math.abs(nowDate - lastclick);
59
+ if (diffTime < 400) {
60
+ click_data.current[get_row_id(rowProps)] = null;
61
+ return onRowDoubleClick && onRowDoubleClick(rowProps.rowIndex, rowProps.data);
62
+ }
63
+ }
64
+
65
+ click_data.current[get_row_id(rowProps)] = nowDate;
66
+ return onRowClick && onRowClick(rowProps.rowIndex, rowProps.data);
67
+ }, []);
68
+
69
+ React.useEffect(() => {
70
+ click_data.current = {};
71
+ }, [props.dataSource]);
72
+
73
+ /* ------------------------------- ContextMenu ------------------------------ */
74
+ const onRowContextMenu = React.useCallback((rowProps, event) => {
75
+ if (!rowProps || !rowProps.data || func_selection.has_selection()) {
76
+ update_menuRow(null, null);
77
+ return true;
78
+ }
79
+ event.preventDefault();
80
+
81
+ const realRow = find_realData(rowProps.dataSourceArray, event.target, rowProps.data);
82
+ const anEl = func_selection.getVirtualElement(event.clientX, event.clientY);
83
+ update_menuRow(realRow, anEl);
84
+ return false;
85
+ }, []);
86
+
87
+ /* -------------------------------------------------------------------------- */
88
+ /* ------------------------------ Accessibility ----------------------------- */
89
+ const rootSyncFrame = React.useRef(0);
90
+ // last applied root values, so row renders (e.g. while scrolling) don't re-apply them
91
+ const lastRoot = React.useRef<{ el: Element | null; key: string; busy: boolean }>({
92
+ el: null,
93
+ key: '',
94
+ busy: false,
95
+ });
96
+ const getRootOptions = () => ({
97
+ ariaLabel: latestProps.current.ariaLabel,
98
+ editable: !!latestProps.current.editable,
99
+ });
100
+ // root attributes are set on the DOM node, batched to once per frame
101
+ const scheduleRootSync = React.useCallback((force = false) => {
102
+ if (rootSyncFrame.current || typeof requestAnimationFrame === 'undefined') return;
103
+ const grid = latestProps.current.liveGridRef?.current?.current;
104
+ const key = get_root_a11y_key(grid, getRootOptions());
105
+ if (
106
+ !force &&
107
+ key &&
108
+ key === lastRoot.current.key &&
109
+ get_grid_root(grid) === lastRoot.current.el
110
+ )
111
+ return;
112
+ rootSyncFrame.current = requestAnimationFrame(() => {
113
+ rootSyncFrame.current = 0;
114
+ const liveGrid = latestProps.current.liveGridRef?.current?.current;
115
+ const options = getRootOptions();
116
+ const el = apply_root_a11y(liveGrid, options);
117
+ lastRoot.current = {
118
+ el,
119
+ key: el ? get_root_a11y_key(liveGrid, options) : '',
120
+ busy: el ? is_grid_busy(liveGrid) : false,
121
+ };
122
+ });
123
+ }, []);
124
+ React.useEffect(() => {
125
+ scheduleRootSync();
126
+ }, [scheduleRootSync, props.ariaLabel, props.editable, props.columns, props.dataSource]);
127
+ React.useEffect(
128
+ () => () => {
129
+ if (rootSyncFrame.current) cancelAnimationFrame(rootSyncFrame.current);
130
+ rootSyncFrame.current = 0;
131
+ },
132
+ []
133
+ );
134
+
135
+ const onRenderRow = React.useCallback((rowProps) => {
136
+ // save the original handlers to be called later
137
+ rowProps['data-row-id'] = get_row_id(rowProps.data);
138
+ const liveGrid: any = latestProps.current.liveGridRef?.current?.current;
139
+ apply_row_a11y(rowProps, !!liveGrid?.computedRowSelectionEnabled);
140
+ scheduleRootSync();
141
+ }, []);
142
+
143
+ // called by the grid on every render with the load mask state, keeps aria-busy in sync
144
+ const onRenderLoadMask = React.useCallback((maskProps: { visible?: boolean }) => {
145
+ if (!!maskProps?.visible !== lastRoot.current.busy) scheduleRootSync(true);
146
+ }, []);
147
+
148
+ return {
149
+ onRowClick,
150
+ onRenderLoadMask,
151
+ onRowContextMenu,
152
+ onRenderRow,
153
+ update_menuRow,
154
+ menuAnchorEl,
155
+ menuRow,
156
+ };
157
+ }