@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.
- package/CHANGELOG.md +33 -19
- package/README.md +165 -164
- package/__tests__/context-menu.config.cjs +14 -0
- package/__tests__/context-menu.test.js +150 -0
- package/config/generate-theme.cjs +1 -1
- package/config/theme-extras.css +11 -16
- package/dist/src/example.js +27 -2
- package/dist/src/func/func_a11y.d.ts +14 -0
- package/dist/src/func/func_a11y.js +63 -10
- package/dist/src/func/func_grid.d.ts +6 -0
- package/dist/src/func/func_grid.js +15 -4
- package/dist/src/hooks/useGridEvents.js +16 -4
- package/dist/src/themes/azgrid-theme.css +12 -17
- package/dist/src/types/types.d.ts +4 -0
- package/dist/src/views/DataGrid.js +10 -22
- package/package.json +1 -1
- package/src/example.tsx +202 -186
- package/src/func/func_a11y.ts +62 -4
- package/src/func/func_grid.ts +351 -340
- package/src/hooks/useGridEvents.ts +171 -158
- package/src/themes/azgrid-theme.css +12 -17
- package/src/types/types.ts +157 -153
- package/src/views/DataGrid.tsx +9 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
|
-
|
|
5
|
-
###
|
|
6
|
-
|
|
7
|
-
* Fix
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* Add `
|
|
12
|
-
*
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
|
+
|
|
5
|
+
### Unreleased
|
|
6
|
+
|
|
7
|
+
* Fix columns with a `type` (`number`, `select`, `bool`, ...) staying read-only unless they were listed in `editableColumns`. A typed column is editable unless you set `editable: false`.
|
|
8
|
+
* Fix a typed column's `editable` function being replaced by `true`. Functions and booleans are kept, so per-cell editability works again.
|
|
9
|
+
* Fix select `items` given as plain strings storing a lowercased value (`"Shelf A"` was saved as `"shelf a"`). The item id is now the original string.
|
|
10
|
+
* Fix the "Failed prop type: Invalid prop `headerDOMProps` of type `function`" warning on every grid. `headerDOMProps` is now an object; `aria-colindex` and `aria-sort` are set on the header cells from the grid state. Header cells also get `data-column`.
|
|
11
|
+
* Add `children` to `AzGridProps`, rendered after the grid.
|
|
12
|
+
* Fix the row context menu ignoring the grid theme with `@azrico/controls` 1.6.2, whose menu is no longer MUI-based and is portaled to `<body>`. The menu now gets the grid's theme class (`azgrid-theme-<name>`) and is styled through the controls' `--azrico-contextmenu-*` variables from the `--azgrid-menu-*` tokens. The MUI `ThemeProvider` around the menu is removed.
|
|
13
|
+
* Add `contextMenuClassName` to add your own classes to the context menu. Use it to bring host variables set on a wrapper element, which the portaled menu no longer inherits.
|
|
14
|
+
|
|
15
|
+
### 1.6.5 (2026-10-15)
|
|
16
|
+
|
|
17
|
+
* Leave the browser's context menu available when a row has no resolved `contextMenuItems`, and close any previously open grid menu. Respect right-clicks already handled by nested row details.
|
|
18
|
+
|
|
19
|
+
### 1.7.1 (2026-10-14)
|
|
20
|
+
|
|
21
|
+
* Fix two quick clicks on different rows firing `onRowDoubleClick`: the double-click check now keys clicks by the row's id instead of `undefined`.
|
|
22
|
+
|
|
23
|
+
### 1.7.0 (2026-10-14)
|
|
24
|
+
|
|
25
|
+
* Add `advancedFilterLabel` to rename the quick-search label (default "Search").
|
|
26
|
+
* Add `advancedFilterPlaceholder` for the quick-search input. A string is the native placeholder; any other node (custom HTML) is shown over the empty input.
|
|
27
|
+
|
|
28
|
+
### 1.6.0 (2026-10-14)
|
|
29
|
+
|
|
30
|
+
* Pass `filterValue`, `sortInfo` and `groupBy` to a `dataSource` function next to `skip`, `limit`, `cached_data` and `last_cache_info`, so header filters can drive a server-side search.
|
|
31
|
+
* With `gridOptions.remoteFilter` / `remoteSort`, a filter or sort change invalidates the data cache and calls `dataSource` again. Local filtering and sorting still reuse the cached rows.
|
|
32
|
+
* Export the typed `AzDataSourceParams` and `AzDataSourceFunc` for the `dataSource` and `details_dataSource` props.
|
|
33
|
+
* Re-export `SelectFilter`, `NumberFilter`, `DateFilter` and `BoolFilter` for `column.filterEditor`.
|
package/README.md
CHANGED
|
@@ -1,164 +1,165 @@
|
|
|
1
|
-
# @azrico/reactgrid
|
|
2
|
-
|
|
3
|
-
Private React data grid package for Azrideus' projects.
|
|
4
|
-
|
|
5
|
-
## Usage
|
|
6
|
-
|
|
7
|
-
```tsx
|
|
8
|
-
import { AzGrid } from '@azrico/reactgrid';
|
|
9
|
-
import '@azrico/reactgrid/dist/src/themes/azgrid-theme.css';
|
|
10
|
-
|
|
11
|
-
const columns = [{ name: 'name', header: 'Name', defaultFlex: 1 }];
|
|
12
|
-
const rows = [{ __table_id: '1', name: 'Alice' }];
|
|
13
|
-
|
|
14
|
-
<AzGrid theme="light" columns={columns} dataSource={rows} />;
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
The token stylesheet is optional. Without it, the grid uses Inovua's default-light appearance.
|
|
18
|
-
|
|
19
|
-
## Accessibility
|
|
20
|
-
|
|
21
|
-
The grid exposes ARIA grid semantics, so screen readers and automation tools can read it from the accessibility tree.
|
|
22
|
-
|
|
23
|
-
| Element | Attributes |
|
|
24
|
-
| --- | --- |
|
|
25
|
-
| Grid root | `role="grid"`, `aria-rowcount` (total rows incl. header; the full data count when paginated), `aria-colcount` (visible columns), `aria-label`, `aria-readonly="true"` when the grid isn't editable, `aria-busy="true"` while loading |
|
|
26
|
-
| Header row | `role="row"`, `aria-rowindex="1"` |
|
|
27
|
-
| Header cells | `role="columnheader"`, `aria-colindex`, `aria-sort` (`ascending` / `descending` / `none`, sortable columns only) |
|
|
28
|
-
| Rows | `role="row"`, `aria-rowindex` (1-based, header is 1, includes the page offset), `aria-selected` when selection is enabled, `data-row-id` |
|
|
29
|
-
| Body cells | `role="gridcell"`, `aria-colindex`, `data-column="<column name>"`, `aria-readonly="true"` when the cell can't be edited |
|
|
30
|
-
| Row-number cells | `role="rowheader"` |
|
|
31
|
-
|
|
32
|
-
New props:
|
|
33
|
-
|
|
34
|
-
- `ariaLabel?: string` on the grid sets the accessible name of the grid.
|
|
35
|
-
- `ariaLabel?: (value, data) => string` on a column sets `aria-label` on its cells. Use it for icons, badges or values whose visible text isn't descriptive.
|
|
36
|
-
|
|
37
|
-
```tsx
|
|
38
|
-
const columns = [{ name: 'status', header: 'Status', ariaLabel: (value) => `Status: ${value}` }];
|
|
39
|
-
|
|
40
|
-
<AzGrid ariaLabel="Orders" columns={columns} dataSource={rows} />;
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Override any cell attribute with the column's `cellDOMProps` / `headerDOMProps`. Values you set there are kept.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
`
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
--azgrid-
|
|
65
|
-
--azgrid-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
|
77
|
-
|
|
|
78
|
-
| `--azgrid-
|
|
79
|
-
| `--azgrid-
|
|
80
|
-
| `--azgrid-
|
|
81
|
-
| `--azgrid-
|
|
82
|
-
| `--azgrid-
|
|
83
|
-
| `--azgrid-
|
|
84
|
-
| `--azgrid-
|
|
85
|
-
| `--azgrid-
|
|
86
|
-
| `--azgrid-
|
|
87
|
-
| `--azgrid-header-
|
|
88
|
-
| `--azgrid-header-
|
|
89
|
-
| `--azgrid-header-
|
|
90
|
-
| `--azgrid-header-
|
|
91
|
-
| `--azgrid-header-
|
|
92
|
-
| `--azgrid-header-
|
|
93
|
-
| `--azgrid-
|
|
94
|
-
| `--azgrid-row-bg-
|
|
95
|
-
| `--azgrid-row-bg-
|
|
96
|
-
| `--azgrid-row-
|
|
97
|
-
| `--azgrid-row-
|
|
98
|
-
| `--azgrid-row-
|
|
99
|
-
| `--azgrid-row-
|
|
100
|
-
| `--azgrid-
|
|
101
|
-
| `--azgrid-cell-border
|
|
102
|
-
| `--azgrid-cell-
|
|
103
|
-
| `--azgrid-
|
|
104
|
-
| `--azgrid-filter-
|
|
105
|
-
| `--azgrid-filter-
|
|
106
|
-
| `--azgrid-filter-
|
|
107
|
-
| `--azgrid-filter-
|
|
108
|
-
| `--azgrid-
|
|
109
|
-
| `--azgrid-pagination-
|
|
110
|
-
| `--azgrid-pagination-
|
|
111
|
-
| `--azgrid-
|
|
112
|
-
| `--azgrid-menu-
|
|
113
|
-
| `--azgrid-menu-
|
|
114
|
-
| `--azgrid-menu-
|
|
115
|
-
| `--azgrid-
|
|
116
|
-
| `--azgrid-scrollbar-
|
|
117
|
-
| `--azgrid-
|
|
118
|
-
| `--azgrid-empty-
|
|
119
|
-
| `--azgrid-
|
|
120
|
-
| `--azgrid-loading-
|
|
121
|
-
| `--azgrid-
|
|
122
|
-
| `--azgrid-tree-
|
|
123
|
-
| `--azgrid-
|
|
124
|
-
| `--azgrid-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
--azgrid-
|
|
141
|
-
--azgrid-
|
|
142
|
-
--azgrid-
|
|
143
|
-
--azgrid-
|
|
144
|
-
--azgrid-
|
|
145
|
-
--azgrid-header-
|
|
146
|
-
--azgrid-header-
|
|
147
|
-
--azgrid-header-
|
|
148
|
-
--azgrid-header-
|
|
149
|
-
--azgrid-
|
|
150
|
-
--azgrid-cell-border
|
|
151
|
-
--azgrid-
|
|
152
|
-
--azgrid-row-bg-
|
|
153
|
-
--azgrid-row-bg-
|
|
154
|
-
--azgrid-row-bg-
|
|
155
|
-
--azgrid-
|
|
156
|
-
--azgrid-scrollbar-
|
|
157
|
-
--azgrid-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
1
|
+
# @azrico/reactgrid
|
|
2
|
+
|
|
3
|
+
Private React data grid package for Azrideus' projects.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { AzGrid } from '@azrico/reactgrid';
|
|
9
|
+
import '@azrico/reactgrid/dist/src/themes/azgrid-theme.css';
|
|
10
|
+
|
|
11
|
+
const columns = [{ name: 'name', header: 'Name', defaultFlex: 1 }];
|
|
12
|
+
const rows = [{ __table_id: '1', name: 'Alice' }];
|
|
13
|
+
|
|
14
|
+
<AzGrid theme="light" columns={columns} dataSource={rows} />;
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The token stylesheet is optional. Without it, the grid uses Inovua's default-light appearance.
|
|
18
|
+
|
|
19
|
+
## Accessibility
|
|
20
|
+
|
|
21
|
+
The grid exposes ARIA grid semantics, so screen readers and automation tools can read it from the accessibility tree.
|
|
22
|
+
|
|
23
|
+
| Element | Attributes |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| Grid root | `role="grid"`, `aria-rowcount` (total rows incl. header; the full data count when paginated), `aria-colcount` (visible columns), `aria-label`, `aria-readonly="true"` when the grid isn't editable, `aria-busy="true"` while loading |
|
|
26
|
+
| Header row | `role="row"`, `aria-rowindex="1"` |
|
|
27
|
+
| Header cells | `role="columnheader"`, `aria-colindex`, `aria-sort` (`ascending` / `descending` / `none`, sortable columns only), `data-column="<column id>"` |
|
|
28
|
+
| Rows | `role="row"`, `aria-rowindex` (1-based, header is 1, includes the page offset), `aria-selected` when selection is enabled, `data-row-id` |
|
|
29
|
+
| Body cells | `role="gridcell"`, `aria-colindex`, `data-column="<column name>"`, `aria-readonly="true"` when the cell can't be edited |
|
|
30
|
+
| Row-number cells | `role="rowheader"` |
|
|
31
|
+
|
|
32
|
+
New props:
|
|
33
|
+
|
|
34
|
+
- `ariaLabel?: string` on the grid sets the accessible name of the grid.
|
|
35
|
+
- `ariaLabel?: (value, data) => string` on a column sets `aria-label` on its cells. Use it for icons, badges or values whose visible text isn't descriptive.
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
const columns = [{ name: 'status', header: 'Status', ariaLabel: (value) => `Status: ${value}` }];
|
|
39
|
+
|
|
40
|
+
<AzGrid ariaLabel="Orders" columns={columns} dataSource={rows} />;
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Override any cell attribute with the column's `cellDOMProps` / `headerDOMProps`. Values you set there are kept.
|
|
44
|
+
Pass `headerDOMProps` as an object: Inovua's prop types reject a function there. A function still works but logs a prop-type warning.
|
|
45
|
+
A `gridOptions.onRenderRow` runs after the grid's own row attributes are set, so it can change them too.
|
|
46
|
+
|
|
47
|
+
## Themes
|
|
48
|
+
|
|
49
|
+
Use `theme="light"` (default), `theme="dark"`, or a custom name. Custom names apply
|
|
50
|
+
`azgrid-theme-<name>` to the grid and use light defaults for unspecified variables.
|
|
51
|
+
|
|
52
|
+
`dark` remains a deprecated alias. `theme` takes precedence; `gridOptions.theme` is ignored.
|
|
53
|
+
|
|
54
|
+
Set `--azgrid-*` variables on a wrapper, `:root`, or a theme class. Each grid can use a different palette.
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
<div className="my-grid">
|
|
58
|
+
<AzGrid theme="light" columns={columns} dataSource={rows} />
|
|
59
|
+
</div>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```css
|
|
63
|
+
.my-grid {
|
|
64
|
+
--azgrid-accent: #345b93;
|
|
65
|
+
--azgrid-header-bg: #edf2f8;
|
|
66
|
+
--azgrid-row-bg-hover: #e4ecf7;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## CSS variables
|
|
71
|
+
|
|
72
|
+
All variables have light/dark defaults. Use valid CSS values for the named property.
|
|
73
|
+
`--azgrid-locked-column-shadow` accepts a complete `box-shadow` value.
|
|
74
|
+
|
|
75
|
+
<!-- azgrid-tokens -->
|
|
76
|
+
| Variable | Controls |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| `--azgrid-bg` | Grid background |
|
|
79
|
+
| `--azgrid-fg` | Grid text |
|
|
80
|
+
| `--azgrid-border` | Grid border and separators |
|
|
81
|
+
| `--azgrid-font-family` | Grid and control fonts |
|
|
82
|
+
| `--azgrid-radius` | Grid, input and menu corner radius; default `0` |
|
|
83
|
+
| `--azgrid-accent` | Focus, sort arrows, checkboxes and selection handles |
|
|
84
|
+
| `--azgrid-transparent` | Transparent structural surfaces |
|
|
85
|
+
| `--azgrid-muted-fg` | Disabled text and secondary icons |
|
|
86
|
+
| `--azgrid-shadow-color` | Menu and input shadow colour |
|
|
87
|
+
| `--azgrid-header-bg` | Header background |
|
|
88
|
+
| `--azgrid-header-fg` | Header text |
|
|
89
|
+
| `--azgrid-header-border` | Header separators |
|
|
90
|
+
| `--azgrid-header-font-size` | Header font size |
|
|
91
|
+
| `--azgrid-header-text-transform` | Header text transform |
|
|
92
|
+
| `--azgrid-header-letter-spacing` | Header letter spacing |
|
|
93
|
+
| `--azgrid-header-font-weight` | Header font weight |
|
|
94
|
+
| `--azgrid-row-bg-even` | Even and non-zebra rows |
|
|
95
|
+
| `--azgrid-row-bg-odd` | Odd rows |
|
|
96
|
+
| `--azgrid-row-bg-hover` | Hovered rows and cells |
|
|
97
|
+
| `--azgrid-row-fg-hover` | Hovered row text |
|
|
98
|
+
| `--azgrid-row-bg-selected` | Selected rows and cells |
|
|
99
|
+
| `--azgrid-row-fg-selected` | Selected row text |
|
|
100
|
+
| `--azgrid-row-bg-active` | Active rows |
|
|
101
|
+
| `--azgrid-cell-border` | Cell border colour |
|
|
102
|
+
| `--azgrid-cell-border-width` | Cell border width |
|
|
103
|
+
| `--azgrid-cell-padding` | Data cell padding |
|
|
104
|
+
| `--azgrid-filter-bg` | Filter row, input, editor and search background |
|
|
105
|
+
| `--azgrid-filter-fg` | Filter input and editor text |
|
|
106
|
+
| `--azgrid-filter-border` | Filter input borders |
|
|
107
|
+
| `--azgrid-filter-placeholder` | Input placeholder text |
|
|
108
|
+
| `--azgrid-filter-focus-ring` | Focused input border and ring |
|
|
109
|
+
| `--azgrid-pagination-bg` | Pagination toolbar and control background |
|
|
110
|
+
| `--azgrid-pagination-fg` | Pagination text and icons |
|
|
111
|
+
| `--azgrid-pagination-border` | Pagination control borders |
|
|
112
|
+
| `--azgrid-menu-bg` | Menu, select dropdown and date picker background |
|
|
113
|
+
| `--azgrid-menu-fg` | Menu text and icons |
|
|
114
|
+
| `--azgrid-menu-border` | Menu borders and separators |
|
|
115
|
+
| `--azgrid-menu-bg-hover` | Hovered and focused menu entries |
|
|
116
|
+
| `--azgrid-scrollbar-track` | Scrollbar tracks |
|
|
117
|
+
| `--azgrid-scrollbar-thumb` | Scrollbar thumbs |
|
|
118
|
+
| `--azgrid-empty-bg` | Empty state background |
|
|
119
|
+
| `--azgrid-empty-fg` | Empty state text |
|
|
120
|
+
| `--azgrid-loading-bg` | Loading overlay background |
|
|
121
|
+
| `--azgrid-loading-fg` | Loading text and spinner |
|
|
122
|
+
| `--azgrid-tree-bg` | Tree and group row background |
|
|
123
|
+
| `--azgrid-tree-fg` | Tree and group text and expand icons |
|
|
124
|
+
| `--azgrid-details-bg` | Row details background |
|
|
125
|
+
| `--azgrid-locked-column-shadow` | Locked column shadow |
|
|
126
|
+
<!-- /azgrid-tokens -->
|
|
127
|
+
|
|
128
|
+
## Art Deco example
|
|
129
|
+
|
|
130
|
+
Use dark defaults with an obsidian background, gold accents, square corners and uppercase headers.
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
<div className="deco-grid">
|
|
134
|
+
<AzGrid theme="dark" columns={columns} dataSource={rows} />
|
|
135
|
+
</div>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```css
|
|
139
|
+
.deco-grid {
|
|
140
|
+
--azgrid-bg: #0A0A0A;
|
|
141
|
+
--azgrid-fg: #eee7d6;
|
|
142
|
+
--azgrid-border: #6b5824;
|
|
143
|
+
--azgrid-accent: #D4AF37;
|
|
144
|
+
--azgrid-radius: 0;
|
|
145
|
+
--azgrid-header-bg: var(--azgrid-bg);
|
|
146
|
+
--azgrid-header-fg: var(--azgrid-accent);
|
|
147
|
+
--azgrid-header-text-transform: uppercase;
|
|
148
|
+
--azgrid-header-letter-spacing: .1em;
|
|
149
|
+
--azgrid-header-font-weight: 600;
|
|
150
|
+
--azgrid-cell-border: var(--azgrid-border);
|
|
151
|
+
--azgrid-cell-border-width: 1px;
|
|
152
|
+
--azgrid-row-bg-odd: #12110e;
|
|
153
|
+
--azgrid-row-bg-hover: #282211;
|
|
154
|
+
--azgrid-row-bg-selected: #423619;
|
|
155
|
+
--azgrid-row-bg-active: #332a15;
|
|
156
|
+
--azgrid-scrollbar-track: var(--azgrid-bg);
|
|
157
|
+
--azgrid-scrollbar-thumb: var(--azgrid-border);
|
|
158
|
+
--azgrid-loading-bg: rgba(10, 10, 10, .85);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
See [src/example.tsx](src/example.tsx) for light, dark and a complete custom `deco` palette.
|
|
163
|
+
|
|
164
|
+
Custom renderers must style their own content. External menu portal containers need
|
|
165
|
+
`azgrid-scope`, the matching `azgrid-theme-<name>` class and the host variables.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
rootDir: '..',
|
|
3
|
+
cacheDirectory: '<rootDir>/node_modules/.cache/jest-context-menu',
|
|
4
|
+
testMatch: ['<rootDir>/__tests__/context-menu.test.js'],
|
|
5
|
+
moduleNameMapper: {
|
|
6
|
+
'\\.(css|less|scss|sass)$': '<rootDir>/config/file-mock.js',
|
|
7
|
+
'^@azrico/controls/context-menu$': '<rootDir>/node_modules/@azrico/controls/dist/src/ContextMenu/index.js',
|
|
8
|
+
},
|
|
9
|
+
transform: {
|
|
10
|
+
'^.+\\.[jt]sx?$': ['babel-jest', {
|
|
11
|
+
presets: ['@babel/preset-env', '@babel/preset-typescript', '@babel/preset-react'],
|
|
12
|
+
}],
|
|
13
|
+
},
|
|
14
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/** @jest-environment jsdom */
|
|
2
|
+
const React = require('react');
|
|
3
|
+
const { createRoot } = require('react-dom/client');
|
|
4
|
+
const act = React.act || require('react-dom/test-utils').act;
|
|
5
|
+
const { ContextMenu } = require('@azrico/controls/context-menu');
|
|
6
|
+
const { AzGrid } = require('../src/views/DataGrid');
|
|
7
|
+
|
|
8
|
+
let mockGridProps;
|
|
9
|
+
jest.mock('@inovua/reactdatagrid-enterprise', () => {
|
|
10
|
+
const React = require('react');
|
|
11
|
+
class Grid extends React.Component {
|
|
12
|
+
render() {
|
|
13
|
+
mockGridProps = this.props;
|
|
14
|
+
return React.createElement('div', { 'data-row-id': 'real-row' },
|
|
15
|
+
React.createElement('span', { id: 'cell' }, 'Cell'));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
Grid.defaultProps = {};
|
|
19
|
+
return { __esModule: true, default: Grid };
|
|
20
|
+
});
|
|
21
|
+
jest.mock('@azrico/controls/context-menu', () => {
|
|
22
|
+
const actual = jest.requireActual('@azrico/controls/context-menu');
|
|
23
|
+
return { ...actual, ContextMenu: jest.fn(actual.ContextMenu) };
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const row = { __table_id: 'real-row', name: 'Row' };
|
|
27
|
+
const staleRow = { __table_id: 'stale-row' };
|
|
28
|
+
const rowProps = { data: staleRow, dataSourceArray: [row] };
|
|
29
|
+
let container, root;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
global.IS_REACT_ACT_ENVIRONMENT = true;
|
|
33
|
+
container = document.createElement('div');
|
|
34
|
+
document.body.appendChild(container);
|
|
35
|
+
root = createRoot(container);
|
|
36
|
+
ContextMenu.mockClear();
|
|
37
|
+
});
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
act(() => root.unmount());
|
|
40
|
+
container.remove();
|
|
41
|
+
jest.restoreAllMocks();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function render(contextMenuItems) {
|
|
45
|
+
act(() => root.render(React.createElement(AzGrid, {
|
|
46
|
+
columns: [], dataSource: [row], contextMenuItems,
|
|
47
|
+
})));
|
|
48
|
+
}
|
|
49
|
+
function rightClick(data = rowProps, alreadyHandled = false) {
|
|
50
|
+
const event = new MouseEvent('contextmenu', { cancelable: true, clientX: 120, clientY: 80 });
|
|
51
|
+
document.getElementById('cell').dispatchEvent(event);
|
|
52
|
+
if (alreadyHandled) event.preventDefault();
|
|
53
|
+
act(() => mockGridProps.onRowContextMenu(data, event));
|
|
54
|
+
return event;
|
|
55
|
+
}
|
|
56
|
+
function popup() {
|
|
57
|
+
// Controls 1.6.1 uses the first class; newer controls use the second.
|
|
58
|
+
return document.querySelector('.azrico-controls-popup, .azrico-controls-contextmenuPopup');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
test('omits ContextMenu entirely when there is no prop and leaves the native menu available', () => {
|
|
62
|
+
render();
|
|
63
|
+
expect(rightClick().defaultPrevented).toBe(false);
|
|
64
|
+
expect(ContextMenu).not.toHaveBeenCalled();
|
|
65
|
+
expect(popup()).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test.each([
|
|
69
|
+
['empty array', []],
|
|
70
|
+
['empty row items', () => []],
|
|
71
|
+
['null row items', () => null],
|
|
72
|
+
['entries resolving to nothing', () => [null, () => null, () => undefined, false, 7]],
|
|
73
|
+
])('%s leaves the native menu available without a popup', (_, items) => {
|
|
74
|
+
render(items);
|
|
75
|
+
expect(rightClick().defaultPrevented).toBe(false);
|
|
76
|
+
expect(popup()).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('resolves the real row before deciding whether to open and closes the previous menu for an empty row', () => {
|
|
80
|
+
const items = jest.fn((data) => data === row ? ['Open'] : []);
|
|
81
|
+
render(items);
|
|
82
|
+
expect(rightClick().defaultPrevented).toBe(true);
|
|
83
|
+
expect(items).toHaveBeenCalledWith(row);
|
|
84
|
+
expect(popup()).not.toBeNull();
|
|
85
|
+
expect(rightClick({ data: staleRow }).defaultPrevented).toBe(false);
|
|
86
|
+
expect(popup()).toBeNull();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('reads updated props through the same memoised handler', () => {
|
|
90
|
+
render(['Open']);
|
|
91
|
+
const handler = mockGridProps.onRowContextMenu;
|
|
92
|
+
rightClick();
|
|
93
|
+
render(() => null);
|
|
94
|
+
expect(mockGridProps.onRowContextMenu).toBe(handler);
|
|
95
|
+
expect(rightClick().defaultPrevented).toBe(false);
|
|
96
|
+
expect(popup()).toBeNull();
|
|
97
|
+
render(['Updated']);
|
|
98
|
+
expect(rightClick().defaultPrevented).toBe(true);
|
|
99
|
+
expect(popup().textContent).toContain('Updated');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test.each([
|
|
103
|
+
['string', 'Open'],
|
|
104
|
+
['object', { title: 'Open' }],
|
|
105
|
+
['entry function', [null, (data) => data === row ? 'Open' : null]],
|
|
106
|
+
])('opens a %s item at the cursor', (_, items) => {
|
|
107
|
+
render(items);
|
|
108
|
+
expect(rightClick().defaultPrevented).toBe(true);
|
|
109
|
+
expect(popup().textContent).toContain('Open');
|
|
110
|
+
const props = ContextMenu.mock.calls.slice(-1)[0][0];
|
|
111
|
+
expect(props.menuRow).toBe(row);
|
|
112
|
+
expect(props.menuAnchorEl.getBoundingClientRect()).toEqual({
|
|
113
|
+
width: 0, height: 0, top: 80, bottom: 80, left: 120, right: 120,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('passes the real row to onClick and closes the menu', () => {
|
|
118
|
+
const onClick = jest.fn();
|
|
119
|
+
render([{ title: 'Open', onClick }]);
|
|
120
|
+
rightClick();
|
|
121
|
+
act(() => document.querySelector('[role="menuitem"]').click());
|
|
122
|
+
expect(onClick).toHaveBeenCalledWith(row, undefined);
|
|
123
|
+
expect(popup()).toBeNull();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('clicking outside closes the menu', async () => {
|
|
127
|
+
render(['Open']);
|
|
128
|
+
rightClick();
|
|
129
|
+
await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
|
|
130
|
+
act(() => document.body.click());
|
|
131
|
+
expect(popup()).toBeNull();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('does not open or evaluate grid items for a right-click already handled by row details', () => {
|
|
135
|
+
const items = jest.fn(() => ['Open']);
|
|
136
|
+
render(items);
|
|
137
|
+
rightClick();
|
|
138
|
+
items.mockClear();
|
|
139
|
+
rightClick(rowProps, true);
|
|
140
|
+
expect(items).not.toHaveBeenCalled();
|
|
141
|
+
expect(popup()).toBeNull();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('preserves the text-selection bailout and clears an open menu', () => {
|
|
145
|
+
render(['Open']);
|
|
146
|
+
rightClick();
|
|
147
|
+
jest.spyOn(window, 'getSelection').mockReturnValue({ type: 'Range' });
|
|
148
|
+
expect(rightClick().defaultPrevented).toBe(false);
|
|
149
|
+
expect(popup()).toBeNull();
|
|
150
|
+
});
|
|
@@ -93,7 +93,7 @@ function tokenFor(selector, prop, literal) {
|
|
|
93
93
|
return bg ? 'bg' : /fill|stroke/.test(prop) ? 'muted-fg' : 'fg';
|
|
94
94
|
}
|
|
95
95
|
let css = '/* Token overrides for Inovua 5.10.2. Generated by config/generate-theme.cjs.\n * index.css retains layout and icons. Public tokens are inherited; preset tokens\n * are fallback implementation details. Do not set public defaults on the root. */\n';
|
|
96
|
-
for (const [index, selector] of [[0, '.azgrid-scope, .azgrid, .azgrid-theme-light'], [1, '.azgrid-theme-dark']]) {
|
|
96
|
+
for (const [index, selector] of [[0, '.azgrid-scope, .azgrid, .azgrid-theme-light, .azgrid-context-menu'], [1, '.azgrid-theme-dark']]) {
|
|
97
97
|
css += `${selector} {\n`;
|
|
98
98
|
for (const [name, values] of Object.entries(tokens)) css += `\t--azgrid-preset-${name}: ${values[index]};\n`;
|
|
99
99
|
css += '}\n\n';
|
package/config/theme-extras.css
CHANGED
|
@@ -74,23 +74,18 @@
|
|
|
74
74
|
.azgrid-scope .inovua-react-toolkit-menu {
|
|
75
75
|
border-radius: var(--azgrid-radius, var(--azgrid-preset-radius));
|
|
76
76
|
}
|
|
77
|
-
/*
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
/* The row context menu is portaled to <body>, outside .azgrid-scope: these rules stay
|
|
78
|
+
* unscoped and read the tokens from the theme class DataGrid puts on the menu.
|
|
79
|
+
* Two classes exceed @azrico/controls' single-class menu rules. */
|
|
80
|
+
.azgrid-context-menu.azrico-controls-contextmenu {
|
|
81
|
+
--azrico-contextmenu-color: var(--azgrid-menu-fg, var(--azgrid-preset-menu-fg));
|
|
82
|
+
--azrico-contextmenu-background: var(--azgrid-menu-bg, var(--azgrid-preset-menu-bg));
|
|
83
|
+
--azrico-contextmenu-hover: var(--azgrid-menu-bg-hover, var(--azgrid-preset-menu-bg-hover));
|
|
84
|
+
--azrico-contextmenu-focus: var(--azgrid-accent, var(--azgrid-preset-accent));
|
|
85
|
+
--azrico-contextmenu-radius: var(--azgrid-radius, var(--azgrid-preset-radius));
|
|
86
|
+
--azrico-contextmenu-shadow: 0 3px 8px var(--azgrid-shadow-color, var(--azgrid-preset-shadow-color));
|
|
87
|
+
--azrico-contextmenu-font: var(--azgrid-font-family, var(--azgrid-preset-font-family));
|
|
81
88
|
border: 1px solid var(--azgrid-menu-border, var(--azgrid-preset-menu-border));
|
|
82
|
-
border-radius: var(--azgrid-radius, var(--azgrid-preset-radius));
|
|
83
|
-
box-shadow: 0 3px 8px var(--azgrid-shadow-color, var(--azgrid-preset-shadow-color));
|
|
84
|
-
font-family: var(--azgrid-font-family, var(--azgrid-preset-font-family));
|
|
85
|
-
}
|
|
86
|
-
.azgrid-scope .azgrid-context-menu .MuiTypography-root,
|
|
87
|
-
.azgrid-scope .azgrid-context-menu .MuiMenuItem-root {
|
|
88
|
-
color: inherit;
|
|
89
|
-
font-family: inherit;
|
|
90
|
-
}
|
|
91
|
-
.azgrid-scope .azgrid-context-menu .MuiMenuItem-root:hover,
|
|
92
|
-
.azgrid-scope .azgrid-context-menu .MuiMenuItem-root.Mui-focusVisible {
|
|
93
|
-
background: var(--azgrid-menu-bg-hover, var(--azgrid-preset-menu-bg-hover));
|
|
94
89
|
}
|
|
95
90
|
.azgrid-scope, .azgrid-scope * {
|
|
96
91
|
accent-color: var(--azgrid-accent, var(--azgrid-preset-accent));
|