@azrico/reactgrid 1.4.1 → 1.5.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/example.tsx CHANGED
@@ -1,178 +1,184 @@
1
- export * from './views/DataGrid';
2
-
3
- import AzGrid from './views/DataGrid';
4
- import React from 'react';
5
- import './themes/azgrid-theme.css';
6
- import './themes/example-palettes.css';
7
-
8
- const columns = [
9
- {
10
- name: 'name',
11
- title: 'Name',
12
- defaultFlex: 1,
13
- },
14
- {
15
- name: 'job',
16
- title: 'Job',
17
- type: 'combo',
18
- items: ['None', 'Engineer', 'Doctor'],
19
- defaultFlex: 1,
20
- },
21
- {
22
- name: 'age',
23
- title: 'Age',
24
- type: 'number',
25
- defaultFlex: 1,
26
- },
27
- ];
28
-
29
- const editableColumns = ['job', 'age'];
30
-
31
- function getData(delta: any, max = 200) {
32
- const fd: any[] = [];
33
- for (let index = 0; index < max; index++) {
34
- const currentObject = {
35
- __table_id: String(index),
36
- name: 'user-' + index,
37
- age: 5 + index,
38
- job: 'None',
39
- };
40
- if (delta[index] && typeof delta[index] === 'object') {
41
- for (const key in delta[index]) {
42
- currentObject[key] = delta[index][key];
43
- }
44
- }
45
-
46
- const childrows: any[] = [];
47
- if (index % 5 == 0) {
48
- const sub_selfid = 's' + index;
49
- childrows.push({
50
- __table_id: sub_selfid,
51
- __parent__table_id: String(index),
52
- name: 'sub-' + index,
53
- age: 20,
54
- });
55
- }
56
-
57
- currentObject['__children'] = childrows;
58
- fd.push(currentObject);
59
- }
60
- return fd;
61
- }
62
-
63
- export function ExampleGrid() {
64
- const [ds_reset, set_ds_reset] = React.useState(0);
65
- const [delta, set_delta] = React.useState({});
66
- const [delayed_reload, set_delayed_reload] = React.useState(false);
67
-
68
- const dataSource = React.useMemo(() => getData(delta), []);
69
- // const dataSource = React.useCallback(
70
- // async (dt) => {
71
- // //console.log('Start loading');
72
- // return await new Promise((resolve) => {
73
- // setTimeout(() => {
74
- // resolve(getData(delta));
75
- // }, 1000);
76
- // });
77
- // },
78
- // [ds_reset, delta]
79
- // );
80
- // const details_dataSource = React.useCallback(
81
- // async (dt) => {
82
- // //console.log('Start loading');
83
- // return await new Promise((resolve) => {
84
- // setTimeout(() => {
85
- // resolve(getData(delta, 5));
86
- // }, 1000);
87
- // });
88
- // },
89
- // [ds_reset, delta]
90
- // );
91
-
92
- function onEditComplete(params) {
93
- const { value, columnId, rowId, ...rest } = params;
94
- let new_rd = delta[rowId];
95
- if (!new_rd) new_rd = {};
96
- new_rd[columnId] = value;
97
- set_delta((d) => {
98
- return { ...d, [rowId]: new_rd };
99
- });
100
- }
101
-
102
- const onRowDoubleClick = React.useCallback((index: number, rowdata: any) => {
103
- console.log('dbl', rowdata?.data || rowdata);
104
- alert('dbl: ' + index);
105
- }, []);
106
- const onRowClick = React.useCallback((index: number, rowdata: any) => {
107
- console.log('click:', rowdata?.data || rowdata);
108
- }, []);
109
-
110
- React.useEffect(() => {
111
- setTimeout(() => {
112
- set_delayed_reload(true);
113
- }, 2000);
114
- }, []);
115
- const customColumns = React.useMemo(() => {
116
- if (!delayed_reload) return [];
117
- return [{ name: 'name', render: (r) => r.value + '^' }];
118
- }, [delayed_reload]);
119
-
120
- const contextMenuItems = React.useCallback((product: any) => {
121
- return [
122
- {
123
- type: 'text',
124
- content: product.name,
125
- },
126
- ];
127
- }, []);
128
-
129
- return (
130
- <div style={{ display: 'grid', gap: 20, height: '100%', overflow: 'auto' }}>
131
- {['light', 'dark', 'deco'].map((theme) => (
132
- <section key={theme} className={theme === 'deco' ? 'azgrid-example-deco' : undefined} style={{ height: 360, display: 'flex', flexDirection: 'column' }}>
133
- <h3>{theme === 'deco' ? 'Art Deco (host variables)' : `Default ${theme}`}</h3>
134
- <AzGrid
135
- theme={theme}
136
- delayedSize
137
- // details_dataSource={details_dataSource}
138
- dataSource={dataSource}
139
- details_columns={columns}
140
- onRowClick={onRowClick}
141
- onRowDoubleClick={onRowDoubleClick}
142
- allowChildRows
143
- // onSelectionChange={onSelectionChange}
144
- // selected={selected}
145
- contextMenuItems={contextMenuItems as any}
146
- customColumns={customColumns}
147
- columns={columns}
148
- editableColumns={editableColumns}
149
- editable={true}
150
- onEditComplete={onEditComplete}
151
- advancedFilterFields={['name']}
152
- // leftColumns={leftColumns}
153
- gridOptions={
154
- {
155
- defaultFilterValue: [{ name: 'name', operator: 'contains', type: 'string', value: '' }],
156
- pagination: 'local',
157
- defaultLimit: 10,
158
- pageSizes: [10, 25, 50],
159
- checkboxColumn: true,
160
- multiSelect: true,
161
- // defaultFilterValue: filterValue,
162
- // remoteFilter: false,
163
- // multiSelect: true,
164
- // pagination: 'local',
165
- // defaultLimit: 100,
166
- // limit: 100,
167
- // pageSizes: [100],
168
- // rowIndexColumn: true,
169
- // checkboxColumn: true,
170
- }
171
- }
172
- />
173
- </section>
174
- ))}
175
- </div>
176
- );
177
- }
178
- export default ExampleGrid;
1
+ export * from './views/DataGrid';
2
+
3
+ import AzGrid from './views/DataGrid';
4
+ import React from 'react';
5
+ import './themes/azgrid-theme.css';
6
+ import './themes/example-palettes.css';
7
+
8
+ const columns = [
9
+ {
10
+ name: 'name',
11
+ title: 'Name',
12
+ defaultFlex: 1,
13
+ },
14
+ {
15
+ name: 'job',
16
+ title: 'Job',
17
+ type: 'combo',
18
+ items: ['None', 'Engineer', 'Doctor'],
19
+ defaultFlex: 1,
20
+ },
21
+ {
22
+ name: 'age',
23
+ title: 'Age',
24
+ type: 'number',
25
+ defaultFlex: 1,
26
+ ariaLabel: (value: any) => `${value} years`,
27
+ },
28
+ ];
29
+
30
+ const editableColumns = ['job', 'age'];
31
+
32
+ function getData(delta: any, max = 200) {
33
+ const fd: any[] = [];
34
+ for (let index = 0; index < max; index++) {
35
+ const currentObject = {
36
+ __table_id: String(index),
37
+ name: 'user-' + index,
38
+ age: 5 + index,
39
+ job: 'None',
40
+ };
41
+ if (delta[index] && typeof delta[index] === 'object') {
42
+ for (const key in delta[index]) {
43
+ currentObject[key] = delta[index][key];
44
+ }
45
+ }
46
+
47
+ const childrows: any[] = [];
48
+ if (index % 5 == 0) {
49
+ const sub_selfid = 's' + index;
50
+ childrows.push({
51
+ __table_id: sub_selfid,
52
+ __parent__table_id: String(index),
53
+ name: 'sub-' + index,
54
+ age: 20,
55
+ });
56
+ }
57
+
58
+ currentObject['__children'] = childrows;
59
+ fd.push(currentObject);
60
+ }
61
+ return fd;
62
+ }
63
+
64
+ export function ExampleGrid() {
65
+ const [ds_reset, set_ds_reset] = React.useState(0);
66
+ const [delta, set_delta] = React.useState({});
67
+ const [delayed_reload, set_delayed_reload] = React.useState(false);
68
+
69
+ const dataSource = React.useMemo(() => getData(delta), []);
70
+ // const dataSource = React.useCallback(
71
+ // async (dt) => {
72
+ // //console.log('Start loading');
73
+ // return await new Promise((resolve) => {
74
+ // setTimeout(() => {
75
+ // resolve(getData(delta));
76
+ // }, 1000);
77
+ // });
78
+ // },
79
+ // [ds_reset, delta]
80
+ // );
81
+ // const details_dataSource = React.useCallback(
82
+ // async (dt) => {
83
+ // //console.log('Start loading');
84
+ // return await new Promise((resolve) => {
85
+ // setTimeout(() => {
86
+ // resolve(getData(delta, 5));
87
+ // }, 1000);
88
+ // });
89
+ // },
90
+ // [ds_reset, delta]
91
+ // );
92
+
93
+ function onEditComplete(params) {
94
+ const { value, columnId, rowId, ...rest } = params;
95
+ let new_rd = delta[rowId];
96
+ if (!new_rd) new_rd = {};
97
+ new_rd[columnId] = value;
98
+ set_delta((d) => {
99
+ return { ...d, [rowId]: new_rd };
100
+ });
101
+ }
102
+
103
+ const onRowDoubleClick = React.useCallback((index: number, rowdata: any) => {
104
+ console.log('dbl', rowdata?.data || rowdata);
105
+ alert('dbl: ' + index);
106
+ }, []);
107
+ const onRowClick = React.useCallback((index: number, rowdata: any) => {
108
+ console.log('click:', rowdata?.data || rowdata);
109
+ }, []);
110
+
111
+ React.useEffect(() => {
112
+ setTimeout(() => {
113
+ set_delayed_reload(true);
114
+ }, 2000);
115
+ }, []);
116
+ const customColumns = React.useMemo(() => {
117
+ if (!delayed_reload) return [];
118
+ return [{ name: 'name', render: (r) => r.value + '^' }];
119
+ }, [delayed_reload]);
120
+
121
+ const contextMenuItems = React.useCallback((product: any) => {
122
+ return [
123
+ {
124
+ type: 'text',
125
+ content: product.name,
126
+ },
127
+ ];
128
+ }, []);
129
+
130
+ return (
131
+ <div style={{ display: 'grid', gap: 20, height: '100%', overflow: 'auto' }}>
132
+ {['light', 'dark', 'deco'].map((theme) => (
133
+ <section key={theme} className={theme === 'deco' ? 'azgrid-example-deco' : undefined} style={{ height: 360, display: 'flex', flexDirection: 'column' }}>
134
+ <h3>{theme === 'deco' ? 'Art Deco (host variables)' : `Default ${theme}`}</h3>
135
+ <AzGrid
136
+ theme={theme}
137
+ ariaLabel={`Users (${theme})`}
138
+ delayedSize
139
+ // details_dataSource={details_dataSource}
140
+ dataSource={dataSource}
141
+ details_columns={columns}
142
+ onRowClick={onRowClick}
143
+ onRowDoubleClick={onRowDoubleClick}
144
+ allowChildRows
145
+ // onSelectionChange={onSelectionChange}
146
+ // selected={selected}
147
+ contextMenuItems={contextMenuItems as any}
148
+ customColumns={customColumns}
149
+ columns={columns}
150
+ editableColumns={editableColumns}
151
+ editable={true}
152
+ onEditComplete={onEditComplete}
153
+ advancedFilterFields={['name']}
154
+ // leftColumns={leftColumns}
155
+ gridOptions={
156
+ {
157
+ defaultFilterValue: [{ name: 'name', operator: 'contains', type: 'string', value: '' }],
158
+ pagination: 'local',
159
+ defaultLimit: 10,
160
+ pageSizes: [10, 25, 50],
161
+ checkboxColumn: true,
162
+ multiSelect: true,
163
+ // consumer onRenderRow runs after the internal one
164
+ onRenderRow: (rowProps: any) => {
165
+ rowProps['data-example-row'] = 'true';
166
+ },
167
+ // defaultFilterValue: filterValue,
168
+ // remoteFilter: false,
169
+ // multiSelect: true,
170
+ // pagination: 'local',
171
+ // defaultLimit: 100,
172
+ // limit: 100,
173
+ // pageSizes: [100],
174
+ // rowIndexColumn: true,
175
+ // checkboxColumn: true,
176
+ }
177
+ }
178
+ />
179
+ </section>
180
+ ))}
181
+ </div>
182
+ );
183
+ }
184
+ export default ExampleGrid;
@@ -0,0 +1,159 @@
1
+ import { GridType } from '../types/types';
2
+
3
+ /*
4
+ * Accessibility helpers.
5
+ * Inovua's grid renders anonymous divs, so we fill in ARIA grid semantics here.
6
+ * Cell/header DOM attributes go through the column's cellDOMProps/headerDOMProps
7
+ * (attributes set on cellProps inside onRender are not forwarded to the DOM).
8
+ */
9
+
10
+ export type ColumnA11yOptions = {
11
+ /** role of body cells, e.g. 'gridcell' or 'rowheader' */
12
+ role?: string;
13
+ /** grid level editable flag, columns are readonly when the grid isn't editable */
14
+ gridEditable?: boolean;
15
+ /** false skips aria-readonly (for interactive special columns like the checkbox column) */
16
+ markReadonly?: boolean;
17
+ };
18
+
19
+ function set_default(target: any, key: string, value: any) {
20
+ if (target[key] === undefined && value !== undefined && value !== null) target[key] = value;
21
+ }
22
+ function resolve_dom_props(src: any, cellProps: any) {
23
+ const res = typeof src === 'function' ? src(cellProps) : src;
24
+ return res ? { ...res } : {};
25
+ }
26
+ function get_sort_dir(sortInfo: any) {
27
+ if (!sortInfo || !sortInfo.dir) return 'none';
28
+ return sortInfo.dir > 0 ? 'ascending' : 'descending';
29
+ }
30
+ function is_cell_readonly(cellProps: any, gridEditable: boolean) {
31
+ if (!gridEditable) return true;
32
+ const editable = cellProps.computedEditable;
33
+ //function editables are decided per edit, we can't know it here
34
+ if (typeof editable === 'function') return false;
35
+ return !editable;
36
+ }
37
+
38
+ /**
39
+ * wraps column dom props to add ARIA attributes,
40
+ * consumer's cellDOMProps/headerDOMProps run first and win over ours
41
+ * @param col
42
+ * @param options
43
+ * @returns a new column object
44
+ */
45
+ export function apply_column_a11y<T extends Record<string, any>>(
46
+ col: T,
47
+ options: ColumnA11yOptions = {}
48
+ ): T {
49
+ const { role = 'gridcell', gridEditable = false, markReadonly = true } = options;
50
+ const consumerCellDOMProps = col.cellDOMProps;
51
+ const consumerHeaderDOMProps = col.headerDOMProps;
52
+ const ariaLabel = col.ariaLabel;
53
+
54
+ return {
55
+ ...col,
56
+ cellDOMProps: (cellProps: any) => {
57
+ const res = resolve_dom_props(consumerCellDOMProps, cellProps);
58
+ set_default(res, 'role', role);
59
+ set_default(res, 'aria-colindex', cellProps.computedVisibleIndex + 1);
60
+ set_default(res, 'data-column', cellProps.name || cellProps.id);
61
+ if (markReadonly && is_cell_readonly(cellProps, gridEditable))
62
+ set_default(res, 'aria-readonly', 'true');
63
+
64
+ const data = cellProps.data;
65
+ if (typeof ariaLabel === 'function' && data && !data.__group && !data.__summary)
66
+ set_default(res, 'aria-label', ariaLabel(cellProps.value, data));
67
+ return res;
68
+ },
69
+ headerDOMProps: (cellProps: any) => {
70
+ const res = resolve_dom_props(consumerHeaderDOMProps, cellProps);
71
+ set_default(res, 'role', 'columnheader');
72
+ set_default(res, 'aria-colindex', cellProps.computedVisibleIndex + 1);
73
+ if (cellProps.computedSortable)
74
+ set_default(res, 'aria-sort', get_sort_dir(cellProps.computedSortInfo));
75
+ return res;
76
+ },
77
+ };
78
+ }
79
+
80
+ /**
81
+ * adds ARIA attributes to a row, called from onRenderRow
82
+ * @param rowProps
83
+ * @param selectionEnabled
84
+ */
85
+ export function apply_row_a11y(rowProps: any, selectionEnabled: boolean) {
86
+ //filler rows (showEmptyRows) are not data rows
87
+ if (rowProps.empty) return;
88
+ const index = rowProps.remoteRowIndex ?? rowProps.realIndex ?? 0;
89
+ rowProps['role'] = 'row';
90
+ rowProps['aria-rowindex'] = index + 2; // 1-based, header row is 1
91
+ if (selectionEnabled) rowProps['aria-selected'] = rowProps.selected ? 'true' : 'false';
92
+ }
93
+
94
+ function set_attr(el: Element, key: string, value: any) {
95
+ if (value === undefined || value === null || value === '') {
96
+ if (el.hasAttribute(key)) el.removeAttribute(key);
97
+ return;
98
+ }
99
+ const str = String(value);
100
+ if (el.getAttribute(key) !== str) el.setAttribute(key, str);
101
+ }
102
+
103
+ /**
104
+ * total number of data rows, uses the datasource count when paginated
105
+ * so the real size is reported, not just the visible page
106
+ * @param grid
107
+ */
108
+ export function get_grid_row_count(grid: GridType): number {
109
+ const g = grid as any;
110
+ const rendered = Array.isArray(g.data) ? g.data.length : 0;
111
+ if (!g.computedPagination) return rendered;
112
+ const offset = g.computedLivePagination ? 0 : g.computedSkip || 0;
113
+ return Math.max(g.paginationCount || 0, offset + rendered);
114
+ }
115
+
116
+ export type RootA11yOptions = { ariaLabel?: string; editable?: boolean };
117
+
118
+ /**
119
+ * root element of the grid, null when it isn't mounted
120
+ * @param grid computed props of the grid
121
+ */
122
+ export function get_grid_root(grid: GridType | null | undefined): Element | null {
123
+ return (grid as any)?.getDOMNode?.() || null;
124
+ }
125
+
126
+ /**
127
+ * key of the values apply_root_a11y writes, used to skip applying when nothing changed
128
+ * @param grid computed props of the grid
129
+ * @param options
130
+ */
131
+ export function get_root_a11y_key(grid: GridType | null | undefined, options: RootA11yOptions) {
132
+ const g = grid as any;
133
+ if (!g) return '';
134
+ return [
135
+ get_grid_row_count(g),
136
+ (g.visibleColumns || []).length,
137
+ options.ariaLabel ?? '',
138
+ options.editable ? 1 : 0,
139
+ ].join('|');
140
+ }
141
+
142
+ /**
143
+ * sets ARIA attributes on the grid root element
144
+ * inovua doesn't forward props to its root, so this is applied on the DOM node
145
+ * @param grid computed props of the grid
146
+ * @param options
147
+ * @returns the element the attributes were applied to, null when the grid isn't mounted
148
+ */
149
+ export function apply_root_a11y(grid: GridType | null | undefined, options: RootA11yOptions) {
150
+ const g = grid as any;
151
+ const el = get_grid_root(g);
152
+ if (!el) return null;
153
+ set_attr(el, 'role', 'grid');
154
+ set_attr(el, 'aria-rowcount', get_grid_row_count(g) + 1);
155
+ set_attr(el, 'aria-colcount', (g.visibleColumns || []).length);
156
+ set_attr(el, 'aria-label', options.ariaLabel);
157
+ set_attr(el, 'aria-readonly', options.editable ? undefined : 'true');
158
+ return el;
159
+ }