@azrico/reactgrid 1.6.2 → 1.6.5

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,158 +1,171 @@
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 rowId = get_row_id(rowProps.data);
57
- const lastclick: number = click_data.current[rowId];
58
- if (lastclick) {
59
- const diffTime = Math.abs(nowDate - lastclick);
60
- if (diffTime < 400) {
61
- click_data.current[rowId] = null;
62
- return onRowDoubleClick && onRowDoubleClick(rowProps.rowIndex, rowProps.data);
63
- }
64
- }
65
-
66
- click_data.current[rowId] = nowDate;
67
- return onRowClick && onRowClick(rowProps.rowIndex, rowProps.data);
68
- }, []);
69
-
70
- React.useEffect(() => {
71
- click_data.current = {};
72
- }, [props.dataSource]);
73
-
74
- /* ------------------------------- ContextMenu ------------------------------ */
75
- const onRowContextMenu = React.useCallback((rowProps, event) => {
76
- if (!rowProps || !rowProps.data || func_selection.has_selection()) {
77
- update_menuRow(null, null);
78
- return true;
79
- }
80
- event.preventDefault();
81
-
82
- const realRow = find_realData(rowProps.dataSourceArray, event.target, rowProps.data);
83
- const anEl = func_selection.getVirtualElement(event.clientX, event.clientY);
84
- update_menuRow(realRow, anEl);
85
- return false;
86
- }, []);
87
-
88
- /* -------------------------------------------------------------------------- */
89
- /* ------------------------------ Accessibility ----------------------------- */
90
- const rootSyncFrame = React.useRef(0);
91
- // last applied root values, so row renders (e.g. while scrolling) don't re-apply them
92
- const lastRoot = React.useRef<{ el: Element | null; key: string; busy: boolean }>({
93
- el: null,
94
- key: '',
95
- busy: false,
96
- });
97
- const getRootOptions = () => ({
98
- ariaLabel: latestProps.current.ariaLabel,
99
- editable: !!latestProps.current.editable,
100
- });
101
- // root attributes are set on the DOM node, batched to once per frame
102
- const scheduleRootSync = React.useCallback((force = false) => {
103
- if (rootSyncFrame.current || typeof requestAnimationFrame === 'undefined') return;
104
- const grid = latestProps.current.liveGridRef?.current?.current;
105
- const key = get_root_a11y_key(grid, getRootOptions());
106
- if (
107
- !force &&
108
- key &&
109
- key === lastRoot.current.key &&
110
- get_grid_root(grid) === lastRoot.current.el
111
- )
112
- return;
113
- rootSyncFrame.current = requestAnimationFrame(() => {
114
- rootSyncFrame.current = 0;
115
- const liveGrid = latestProps.current.liveGridRef?.current?.current;
116
- const options = getRootOptions();
117
- const el = apply_root_a11y(liveGrid, options);
118
- lastRoot.current = {
119
- el,
120
- key: el ? get_root_a11y_key(liveGrid, options) : '',
121
- busy: el ? is_grid_busy(liveGrid) : false,
122
- };
123
- });
124
- }, []);
125
- React.useEffect(() => {
126
- scheduleRootSync();
127
- }, [scheduleRootSync, props.ariaLabel, props.editable, props.columns, props.dataSource]);
128
- React.useEffect(
129
- () => () => {
130
- if (rootSyncFrame.current) cancelAnimationFrame(rootSyncFrame.current);
131
- rootSyncFrame.current = 0;
132
- },
133
- []
134
- );
135
-
136
- const onRenderRow = React.useCallback((rowProps) => {
137
- // save the original handlers to be called later
138
- rowProps['data-row-id'] = get_row_id(rowProps.data);
139
- const liveGrid: any = latestProps.current.liveGridRef?.current?.current;
140
- apply_row_a11y(rowProps, !!liveGrid?.computedRowSelectionEnabled);
141
- scheduleRootSync();
142
- }, []);
143
-
144
- // called by the grid on every render with the load mask state, keeps aria-busy in sync
145
- const onRenderLoadMask = React.useCallback((maskProps: { visible?: boolean }) => {
146
- if (!!maskProps?.visible !== lastRoot.current.busy) scheduleRootSync(true);
147
- }, []);
148
-
149
- return {
150
- onRowClick,
151
- onRenderLoadMask,
152
- onRowContextMenu,
153
- onRenderRow,
154
- update_menuRow,
155
- menuAnchorEl,
156
- menuRow,
157
- };
158
- }
1
+ import React from 'react';
2
+ import { fn_eval } from '@azrico/object';
3
+ import { DataGridWrapperProps, GridType } from '../types/types';
4
+ import { find_realData, get_row_id } from '../func/func_grid';
5
+ import { func_selection } from '../func/func_child';
6
+ import {
7
+ apply_root_a11y,
8
+ apply_row_a11y,
9
+ get_grid_root,
10
+ get_root_a11y_key,
11
+ has_unsynced_header,
12
+ is_grid_busy,
13
+ } from '../func/func_a11y';
14
+
15
+ function useGridMenuRow() {
16
+ const [menuAnchorEl, set_menuAnchorEl] = React.useState<any>(null);
17
+ const [menuRow, set_menuRow] = React.useState<any>(null);
18
+ const update_menuRow = React.useCallback((r: any = null, el: any = null) => {
19
+ set_menuRow(r || null);
20
+ set_menuAnchorEl(el || null);
21
+ }, []);
22
+ return { update_menuRow, menuAnchorEl, menuRow };
23
+ }
24
+ export function useGridEvents(grid: GridType, props: DataGridWrapperProps) {
25
+ const click_data = React.useRef<any>(null);
26
+ // latest props, so the callbacks below stay stable and don't re-render the grid
27
+ const latestProps = React.useRef(props);
28
+ latestProps.current = props;
29
+ const { update_menuRow, menuAnchorEl, menuRow } = useGridMenuRow();
30
+
31
+ const refreshColumn = React.useCallback(
32
+ (col: string) => {
33
+ const dt = grid.ungroupedData;
34
+ for (let index = 0; index < dt.length; index++) {
35
+ (grid as any).setItemPropertyAt(index, col, dt[index][col]);
36
+ }
37
+ },
38
+ [grid]
39
+ );
40
+ const refresh = React.useCallback(() => {
41
+ const dt = grid.ungroupedData;
42
+ for (let index = 0; index < dt.length; index++) {
43
+ (grid as any).setItemAt(index, {}, { replace: false });
44
+ }
45
+ }, [grid]);
46
+
47
+ React.useEffect(() => {
48
+ if (!grid) return;
49
+ grid['refresh'] = refresh;
50
+ grid['refreshColumn'] = refreshColumn;
51
+ }, [grid, props.dataSource, refresh, refreshColumn]);
52
+ /* -------------------------------------------------------------------------- */
53
+ /* events */
54
+ /* -------------------------------------------------------------------------- */
55
+ const onRowClick = React.useCallback((rowProps, event) => {
56
+ const { onRowClick, onRowDoubleClick } = latestProps.current;
57
+ const nowDate = Date.now();
58
+ const rowId = get_row_id(rowProps.data);
59
+ const lastclick: number = click_data.current[rowId];
60
+ if (lastclick) {
61
+ const diffTime = Math.abs(nowDate - lastclick);
62
+ if (diffTime < 400) {
63
+ click_data.current[rowId] = null;
64
+ return onRowDoubleClick && onRowDoubleClick(rowProps.rowIndex, rowProps.data);
65
+ }
66
+ }
67
+
68
+ click_data.current[rowId] = nowDate;
69
+ return onRowClick && onRowClick(rowProps.rowIndex, rowProps.data);
70
+ }, []);
71
+
72
+ React.useEffect(() => {
73
+ click_data.current = {};
74
+ }, [props.dataSource]);
75
+
76
+ /* ------------------------------- ContextMenu ------------------------------ */
77
+ const onRowContextMenu = React.useCallback((rowProps, event) => {
78
+ if (event.defaultPrevented || !rowProps || !rowProps.data || func_selection.has_selection()) {
79
+ update_menuRow(null, null);
80
+ return true;
81
+ }
82
+ const realRow = find_realData(rowProps.dataSourceArray, event.target, rowProps.data);
83
+ const items = fn_eval(latestProps.current.contextMenuItems, realRow) || [];
84
+ const resolvedItems = (Array.isArray(items) ? items : [items])
85
+ .map((item) => {
86
+ const value = fn_eval(item, realRow);
87
+ return typeof value === 'string' ? { title: value } : value;
88
+ })
89
+ .filter((item) => item != null && typeof item === 'object');
90
+ if (!resolvedItems.length) {
91
+ update_menuRow(null, null);
92
+ return true;
93
+ }
94
+ event.preventDefault();
95
+ const anEl = func_selection.getVirtualElement(event.clientX, event.clientY);
96
+ update_menuRow(realRow, anEl);
97
+ return false;
98
+ }, []);
99
+
100
+ /* -------------------------------------------------------------------------- */
101
+ /* ------------------------------ Accessibility ----------------------------- */
102
+ const rootSyncFrame = React.useRef(0);
103
+ // last applied root values, so row renders (e.g. while scrolling) don't re-apply them
104
+ const lastRoot = React.useRef<{ el: Element | null; key: string; busy: boolean }>({
105
+ el: null,
106
+ key: '',
107
+ busy: false,
108
+ });
109
+ const getRootOptions = () => ({
110
+ ariaLabel: latestProps.current.ariaLabel,
111
+ editable: !!latestProps.current.editable,
112
+ });
113
+ // root and header cell attributes are set on the DOM nodes, batched to once per frame
114
+ const scheduleRootSync = React.useCallback((force = false) => {
115
+ if (rootSyncFrame.current || typeof requestAnimationFrame === 'undefined') return;
116
+ const grid = latestProps.current.liveGridRef?.current?.current;
117
+ const key = get_root_a11y_key(grid, getRootOptions());
118
+ if (
119
+ !force &&
120
+ key &&
121
+ key === lastRoot.current.key &&
122
+ get_grid_root(grid) === lastRoot.current.el &&
123
+ !has_unsynced_header(lastRoot.current.el)
124
+ )
125
+ return;
126
+ rootSyncFrame.current = requestAnimationFrame(() => {
127
+ rootSyncFrame.current = 0;
128
+ const liveGrid = latestProps.current.liveGridRef?.current?.current;
129
+ const options = getRootOptions();
130
+ const el = apply_root_a11y(liveGrid, options);
131
+ lastRoot.current = {
132
+ el,
133
+ key: el ? get_root_a11y_key(liveGrid, options) : '',
134
+ busy: el ? is_grid_busy(liveGrid) : false,
135
+ };
136
+ });
137
+ }, []);
138
+ React.useEffect(() => {
139
+ scheduleRootSync();
140
+ }, [scheduleRootSync, props.ariaLabel, props.editable, props.columns, props.dataSource]);
141
+ React.useEffect(
142
+ () => () => {
143
+ if (rootSyncFrame.current) cancelAnimationFrame(rootSyncFrame.current);
144
+ rootSyncFrame.current = 0;
145
+ },
146
+ []
147
+ );
148
+
149
+ const onRenderRow = React.useCallback((rowProps) => {
150
+ // save the original handlers to be called later
151
+ rowProps['data-row-id'] = get_row_id(rowProps.data);
152
+ const liveGrid: any = latestProps.current.liveGridRef?.current?.current;
153
+ apply_row_a11y(rowProps, !!liveGrid?.computedRowSelectionEnabled);
154
+ scheduleRootSync();
155
+ }, []);
156
+
157
+ // called by the grid on every render with the load mask state, keeps aria-busy in sync
158
+ const onRenderLoadMask = React.useCallback((maskProps: { visible?: boolean }) => {
159
+ if (!!maskProps?.visible !== lastRoot.current.busy) scheduleRootSync(true);
160
+ }, []);
161
+
162
+ return {
163
+ onRowClick,
164
+ onRenderLoadMask,
165
+ onRowContextMenu,
166
+ onRenderRow,
167
+ update_menuRow,
168
+ menuAnchorEl,
169
+ menuRow,
170
+ };
171
+ }
@@ -1,7 +1,7 @@
1
1
  /* Token overrides for Inovua 5.10.2. Generated by config/generate-theme.cjs.
2
2
  * index.css retains layout and icons. Public tokens are inherited; preset tokens
3
3
  * are fallback implementation details. Do not set public defaults on the root. */
4
- .azgrid-scope, .azgrid, .azgrid-theme-light {
4
+ .azgrid-scope, .azgrid, .azgrid-theme-light, .azgrid-context-menu {
5
5
  --azgrid-preset-bg: #fff;
6
6
  --azgrid-preset-fg: #555e68;
7
7
  --azgrid-preset-border: #e4e3e2;
@@ -2010,23 +2010,18 @@
2010
2010
  .azgrid-scope .inovua-react-toolkit-menu {
2011
2011
  border-radius: var(--azgrid-radius, var(--azgrid-preset-radius));
2012
2012
  }
2013
- /* Three classes exceed MUI's generated Paper/MenuItem selector specificity. */
2014
- .azgrid-scope .azgrid-context-menu.MuiPaper-root {
2015
- background: var(--azgrid-menu-bg, var(--azgrid-preset-menu-bg));
2016
- color: var(--azgrid-menu-fg, var(--azgrid-preset-menu-fg));
2013
+ /* The row context menu is portaled to <body>, outside .azgrid-scope: these rules stay
2014
+ * unscoped and read the tokens from the theme class DataGrid puts on the menu.
2015
+ * Two classes exceed @azrico/controls' single-class menu rules. */
2016
+ .azgrid-context-menu.azrico-controls-contextmenu {
2017
+ --azrico-contextmenu-color: var(--azgrid-menu-fg, var(--azgrid-preset-menu-fg));
2018
+ --azrico-contextmenu-background: var(--azgrid-menu-bg, var(--azgrid-preset-menu-bg));
2019
+ --azrico-contextmenu-hover: var(--azgrid-menu-bg-hover, var(--azgrid-preset-menu-bg-hover));
2020
+ --azrico-contextmenu-focus: var(--azgrid-accent, var(--azgrid-preset-accent));
2021
+ --azrico-contextmenu-radius: var(--azgrid-radius, var(--azgrid-preset-radius));
2022
+ --azrico-contextmenu-shadow: 0 3px 8px var(--azgrid-shadow-color, var(--azgrid-preset-shadow-color));
2023
+ --azrico-contextmenu-font: var(--azgrid-font-family, var(--azgrid-preset-font-family));
2017
2024
  border: 1px solid var(--azgrid-menu-border, var(--azgrid-preset-menu-border));
2018
- border-radius: var(--azgrid-radius, var(--azgrid-preset-radius));
2019
- box-shadow: 0 3px 8px var(--azgrid-shadow-color, var(--azgrid-preset-shadow-color));
2020
- font-family: var(--azgrid-font-family, var(--azgrid-preset-font-family));
2021
- }
2022
- .azgrid-scope .azgrid-context-menu .MuiTypography-root,
2023
- .azgrid-scope .azgrid-context-menu .MuiMenuItem-root {
2024
- color: inherit;
2025
- font-family: inherit;
2026
- }
2027
- .azgrid-scope .azgrid-context-menu .MuiMenuItem-root:hover,
2028
- .azgrid-scope .azgrid-context-menu .MuiMenuItem-root.Mui-focusVisible {
2029
- background: var(--azgrid-menu-bg-hover, var(--azgrid-preset-menu-bg-hover));
2030
2025
  }
2031
2026
  .azgrid-scope, .azgrid-scope * {
2032
2027
  accent-color: var(--azgrid-accent, var(--azgrid-preset-accent));