@azrico/reactgrid 1.6.6 → 1.6.7

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 CHANGED
@@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. See [standa
4
4
 
5
5
  ### Unreleased
6
6
 
7
+ * Make closing the row context menu with Escape opt-in through `escapeClosesContextMenu` (default `false`).
8
+ * Keep the right-clicked row highlighted with the theme's hover colours while its context menu is open, preserving caller row classes and clearing the highlight on close. Correct row hover rules to use hover tokens instead of empty-state tokens.
7
9
  * Add `dontAutoCloseContextMenu` to keep the row context menu open on outside clicks, and `hideContextMenu` to hide it while leaving the browser's context menu available.
8
10
  * 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`.
9
11
  * Fix a typed column's `editable` function being replaced by `true`. Functions and booleans are kept, so per-cell editability works again.
@@ -11,7 +11,8 @@ jest.mock('@inovua/reactdatagrid-enterprise', () => {
11
11
  class Grid extends React.Component {
12
12
  render() {
13
13
  mockGridProps = this.props;
14
- return React.createElement('div', { 'data-row-id': 'real-row' },
14
+ const rowClassName = this.props.rowClassName?.({ data: { __table_id: 'real-row' } });
15
+ return React.createElement('div', { 'data-row-id': 'real-row', className: rowClassName },
15
16
  React.createElement('span', { id: 'cell' }, 'Cell'));
16
17
  }
17
18
  }
@@ -63,6 +64,7 @@ test('omits ContextMenu entirely when there is no prop and leaves the native men
63
64
  expect(rightClick().defaultPrevented).toBe(false);
64
65
  expect(ContextMenu).not.toHaveBeenCalled();
65
66
  expect(popup()).toBeNull();
67
+ expect(container.querySelector('.azgrid-row--menu-open')).toBeNull();
66
68
  });
67
69
 
68
70
  test.each([
@@ -74,6 +76,7 @@ test.each([
74
76
  render(items);
75
77
  expect(rightClick().defaultPrevented).toBe(false);
76
78
  expect(popup()).toBeNull();
79
+ expect(container.querySelector('.azgrid-row--menu-open')).toBeNull();
77
80
  });
78
81
 
79
82
  test('resolves the real row before deciding whether to open and closes the previous menu for an empty row', () => {
@@ -118,17 +121,80 @@ test('passes the real row to onClick and closes the menu', () => {
118
121
  const onClick = jest.fn();
119
122
  render([{ title: 'Open', onClick }]);
120
123
  rightClick();
124
+ expect(container.querySelector('.azgrid-row--menu-open')).not.toBeNull();
121
125
  act(() => document.querySelector('[role="menuitem"]').click());
122
126
  expect(onClick).toHaveBeenCalledWith(row, undefined);
123
127
  expect(popup()).toBeNull();
128
+ expect(container.querySelector('.azgrid-row--menu-open')).toBeNull();
124
129
  });
125
130
 
126
131
  test('clicking outside closes the menu', async () => {
127
132
  render(['Open']);
128
133
  rightClick();
134
+ expect(container.querySelector('.azgrid-row--menu-open')).not.toBeNull();
129
135
  await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
130
136
  act(() => document.body.click());
131
137
  expect(popup()).toBeNull();
138
+ expect(container.querySelector('.azgrid-row--menu-open')).toBeNull();
139
+ });
140
+
141
+ test.each(['light', 'dark', 'deco'])('keeps the menu row highlighted in %s when moving into the portal', (theme) => {
142
+ render(['Open'], { theme, escapeClosesContextMenu: true, gridOptions: { rowClassName: 'consumer-row' } });
143
+ rightClick();
144
+ const highlighted = container.querySelector('.azgrid-row--menu-open');
145
+ expect(highlighted.classList.contains('consumer-row')).toBe(true);
146
+ const menuItem = document.querySelector('[role="menuitem"]');
147
+ expect(container.contains(menuItem)).toBe(false);
148
+ act(() => menuItem.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })));
149
+ expect(highlighted.classList.contains('azgrid-row--menu-open')).toBe(true);
150
+ act(() => document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })));
151
+ expect(popup()).toBeNull();
152
+ expect(container.querySelector('.azgrid-row--menu-open')).toBeNull();
153
+ expect(highlighted.classList.contains('consumer-row')).toBe(true);
154
+ });
155
+
156
+ test('merges caller rowClassName functions and matches rows by ID rather than object identity', () => {
157
+ const rowClassName = jest.fn(() => 'consumer-row');
158
+ render(['Open'], { gridOptions: { rowClassName } });
159
+ rightClick();
160
+ const args = { data: { ...row }, props: {}, className: 'inovua-row' };
161
+ expect(mockGridProps.rowClassName(args)).toBe('consumer-row azgrid-row--menu-open');
162
+ expect(rowClassName).toHaveBeenLastCalledWith(args);
163
+ expect(mockGridProps.rowClassName({ data: staleRow })).toBe('consumer-row');
164
+ expect(mockGridProps.rowClassName({ data: {} })).toBe('consumer-row');
165
+ });
166
+
167
+ test.each([undefined, false])('Escape leaves the menu and highlight open when escapeClosesContextMenu is %s', (escapeClosesContextMenu) => {
168
+ render(['Open'], { escapeClosesContextMenu });
169
+ rightClick();
170
+ act(() => document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })));
171
+ expect(popup()).not.toBeNull();
172
+ expect(container.querySelector('.azgrid-row--menu-open')).not.toBeNull();
173
+ });
174
+
175
+ test('escapeClosesContextMenu can be disabled and enabled while the menu is open', () => {
176
+ const items = ['Open'];
177
+ render(items, { escapeClosesContextMenu: true });
178
+ rightClick();
179
+ render(items, { escapeClosesContextMenu: false });
180
+ act(() => document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })));
181
+ expect(popup()).not.toBeNull();
182
+ render(items, { escapeClosesContextMenu: true });
183
+ act(() => document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })));
184
+ expect(popup()).toBeNull();
185
+ expect(container.querySelector('.azgrid-row--menu-open')).toBeNull();
186
+ });
187
+
188
+ test('removing menu items clears the highlight and does not reopen the menu when items return', () => {
189
+ const items = ['Open'];
190
+ render(items);
191
+ rightClick();
192
+ expect(container.querySelector('.azgrid-row--menu-open')).not.toBeNull();
193
+ render();
194
+ expect(container.querySelector('.azgrid-row--menu-open')).toBeNull();
195
+ render(items);
196
+ expect(popup()).toBeNull();
197
+ expect(container.querySelector('.azgrid-row--menu-open')).toBeNull();
132
198
  });
133
199
 
134
200
  test('dontAutoCloseContextMenu keeps the menu open on outside clicks and can be updated', async () => {
@@ -5,6 +5,29 @@ const cssTree = require('css-tree');
5
5
  const css = fs.readFileSync('src/themes/azgrid-theme.css', 'utf8');
6
6
  cssTree.parse(css);
7
7
  const theme = postcss.parse(css);
8
+ const selectorParser = require('postcss-selector-parser');
9
+ const { compare, selectorSpecificity } = require('@csstools/selector-specificity');
10
+ const specificity = selector => selectorSpecificity(selectorParser().astSync(selector).first);
11
+ const menuRowRule = theme.nodes.find(n => n.type === 'rule' && n.selector.includes('.azgrid-row--menu-open'));
12
+ assert(menuRowRule, 'missing open-menu row styles');
13
+ const menuPaint = Object.fromEntries(menuRowRule.nodes.filter(n => n.type === 'decl').map(d => [d.prop, d.value]));
14
+ assert.equal(menuPaint.background, 'var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover))');
15
+ assert.equal(menuPaint.color, 'var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover))');
16
+ for (const suffix of ['__row-hover-target', '__cell', '__row-index-column']) {
17
+ assert(menuRowRule.selectors.some(s => s.endsWith('.InovuaReactDataGrid' + suffix)), 'missing menu row surface ' + suffix);
18
+ }
19
+ theme.walkRules(rule => {
20
+ for (const selector of rule.selectors) {
21
+ if (!selector.includes('__row-hover-target:hover')) continue;
22
+ const surface = selector.includes('__row-index-column') ? '__row-index-column'
23
+ : selector.includes('__cell--over') ? '__cell' : '__row-hover-target';
24
+ const menuSelector = menuRowRule.selectors.find(s => s.endsWith('.InovuaReactDataGrid' + surface));
25
+ assert(compare(specificity(menuSelector), specificity(selector)) >= 0, 'menu highlight weaker than ' + selector);
26
+ for (const declaration of rule.nodes.filter(n => n.type === 'decl')) {
27
+ if (declaration.prop in menuPaint) assert.equal(declaration.value, menuPaint[declaration.prop], 'hover paint differs from menu highlight');
28
+ }
29
+ }
30
+ });
8
31
  const presets = new Set();
9
32
  const used = new Set();
10
33
  let important = [];
@@ -67,6 +67,8 @@ function tokenFor(selector, prop, literal) {
67
67
  if (/loader-spinner/.test(selector)) return literal === '#7986cb' ? 'accent' : 'muted-fg';
68
68
  if (/loader-loadbar/.test(selector)) return 'loading-fg';
69
69
  if (/load-mask|loading/.test(selector)) return bg ? 'loading-bg' : 'loading-fg';
70
+ // :not(.row--empty) in row hover selectors does not describe an empty state.
71
+ if (/__row-hover-target:hover/.test(selector)) return bg ? 'row-bg-hover' : 'row-fg-hover';
70
72
  if (/empty/.test(selector)) return bg ? 'empty-bg' : 'empty-fg';
71
73
  if (/placeholder/.test(selector)) return 'filter-placeholder';
72
74
  if (/sort-icon|selection-dragger|active-borders|checkbox/.test(selector)) {
@@ -1,3 +1,12 @@
1
+ /* Keep the menu row in its hover state while the pointer is in the portaled menu.
2
+ * Match the strongest selected-row :hover selectors, including their cells. */
3
+ .azgrid.InovuaReactDataGrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row.azgrid-row--menu-open:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target,
4
+ .azgrid.InovuaReactDataGrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row.azgrid-row--menu-open:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target .InovuaReactDataGrid__cell,
5
+ .azgrid.InovuaReactDataGrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row.azgrid-row--menu-open:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target .InovuaReactDataGrid__row-index-column {
6
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
7
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
8
+ }
9
+
1
10
  /* Additional surfaces and typography not assigned colours by Inovua. */
2
11
  .azgrid.InovuaReactDataGrid.InovuaReactDataGrid--theme-default-light .InovuaReactDataGrid__row--active:not(.InovuaReactDataGrid__row--selected) .InovuaReactDataGrid__cell:not(.InovuaReactDataGrid__cell--no-background) {
3
12
  background: var(--azgrid-row-bg-active, var(--azgrid-preset-row-bg-active));
@@ -78,8 +78,16 @@ function useGridEvents(grid, props) {
78
78
  }, [props.dataSource]);
79
79
  /* ------------------------------- ContextMenu ------------------------------ */
80
80
  _react.default.useEffect(() => {
81
- if (props.hideContextMenu) update_menuRow(null, null);
82
- }, [props.hideContextMenu, update_menuRow]);
81
+ if (props.hideContextMenu || !props.contextMenuItems) update_menuRow(null, null);
82
+ }, [props.hideContextMenu, props.contextMenuItems, update_menuRow]);
83
+ _react.default.useEffect(() => {
84
+ if (!menuAnchorEl || !props.escapeClosesContextMenu) return;
85
+ const onKeyDown = event => {
86
+ if (event.key === 'Escape') update_menuRow(null, null);
87
+ };
88
+ document.addEventListener('keydown', onKeyDown);
89
+ return () => document.removeEventListener('keydown', onKeyDown);
90
+ }, [menuAnchorEl, props.escapeClosesContextMenu, update_menuRow]);
83
91
  const onRowContextMenu = _react.default.useCallback((rowProps, event) => {
84
92
  if (latestProps.current.hideContextMenu || event.defaultPrevented || !rowProps || !rowProps.data || _func_child.func_selection.has_selection()) {
85
93
  update_menuRow(null, null);
@@ -1452,65 +1452,65 @@
1452
1452
  }
1453
1453
 
1454
1454
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--even:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover {
1455
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1456
- color: var(--azgrid-empty-fg, var(--azgrid-preset-empty-fg));
1455
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1456
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1457
1457
  }
1458
1458
 
1459
1459
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--even:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__row-index-column {
1460
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1460
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1461
1461
  }
1462
1462
 
1463
1463
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--even:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__cell--over {
1464
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1465
- color: var(--azgrid-empty-fg, var(--azgrid-preset-empty-fg));
1464
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1465
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1466
1466
  }
1467
1467
 
1468
1468
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--even:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover {
1469
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1470
- color: var(--azgrid-empty-fg, var(--azgrid-preset-empty-fg));
1469
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1470
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1471
1471
  }
1472
1472
 
1473
1473
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--even:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__row-index-column {
1474
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1474
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1475
1475
  }
1476
1476
 
1477
1477
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--even:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__cell--over {
1478
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1479
- color: var(--azgrid-empty-fg, var(--azgrid-preset-empty-fg));
1478
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1479
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1480
1480
  }
1481
1481
 
1482
1482
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--no-zebra:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover,
1483
1483
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--odd:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover {
1484
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1485
- color: var(--azgrid-empty-fg, var(--azgrid-preset-empty-fg));
1484
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1485
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1486
1486
  }
1487
1487
 
1488
1488
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--no-zebra:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__row-index-column,
1489
1489
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--odd:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__row-index-column {
1490
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1490
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1491
1491
  }
1492
1492
 
1493
1493
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--no-zebra:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__cell--over,
1494
1494
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--odd:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__cell--over {
1495
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1496
- color: var(--azgrid-empty-fg, var(--azgrid-preset-empty-fg));
1495
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1496
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1497
1497
  }
1498
1498
 
1499
1499
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--no-zebra:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover,
1500
1500
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--odd:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover {
1501
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1502
- color: var(--azgrid-empty-fg, var(--azgrid-preset-empty-fg));
1501
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1502
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1503
1503
  }
1504
1504
 
1505
1505
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--no-zebra:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__row-index-column,
1506
1506
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--odd:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__row-index-column {
1507
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1507
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1508
1508
  }
1509
1509
 
1510
1510
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--no-zebra:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__cell--over,
1511
1511
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--odd:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__cell--over {
1512
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1513
- color: var(--azgrid-empty-fg, var(--azgrid-preset-empty-fg));
1512
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1513
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1514
1514
  }
1515
1515
 
1516
1516
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--focused .InovuaReactDataGrid__row--active>.InovuaReactDataGrid__row-active-borders-wrapper>.InovuaReactDataGrid__row-active-borders:before,
@@ -1934,6 +1934,15 @@
1934
1934
  fill: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1935
1935
  }
1936
1936
 
1937
+ /* Keep the menu row in its hover state while the pointer is in the portaled menu.
1938
+ * Match the strongest selected-row :hover selectors, including their cells. */
1939
+ .azgrid.InovuaReactDataGrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row.azgrid-row--menu-open:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target,
1940
+ .azgrid.InovuaReactDataGrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row.azgrid-row--menu-open:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target .InovuaReactDataGrid__cell,
1941
+ .azgrid.InovuaReactDataGrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row.azgrid-row--menu-open:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target .InovuaReactDataGrid__row-index-column {
1942
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1943
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1944
+ }
1945
+
1937
1946
  /* Additional surfaces and typography not assigned colours by Inovua. */
1938
1947
  .azgrid.InovuaReactDataGrid.InovuaReactDataGrid--theme-default-light .InovuaReactDataGrid__row--active:not(.InovuaReactDataGrid__row--selected) .InovuaReactDataGrid__cell:not(.InovuaReactDataGrid__cell--no-background) {
1939
1948
  background: var(--azgrid-row-bg-active, var(--azgrid-preset-row-bg-active));
@@ -75,6 +75,8 @@ export type AzGridProps = {
75
75
  contextMenuItems?: ConvertableToMenuItem;
76
76
  /** Keep the context menu open when clicking outside (default false). */
77
77
  dontAutoCloseContextMenu?: boolean;
78
+ /** Close the context menu on Escape (default false). */
79
+ escapeClosesContextMenu?: boolean;
78
80
  /** Hide the grid context menu and leave the browser's context menu available. */
79
81
  hideContextMenu?: boolean;
80
82
  /** Extra classes on the row context menu, which is portaled to <body> */
@@ -121,7 +121,7 @@ function AzGrid(props) {
121
121
  * @returns
122
122
  */
123
123
  function DataGridEventsWrapper(props) {
124
- var _props$theme, _props$dontAutoCloseC;
124
+ var _props$theme, _props$static_gridOpt, _props$dontAutoCloseC;
125
125
  const themeClass = `azgrid-theme-${(_props$theme = props.theme) !== null && _props$theme !== void 0 ? _props$theme : props.dark ? 'dark' : 'light'}`;
126
126
  /* -------------------------------------------------------------------------- */
127
127
  const {
@@ -133,6 +133,9 @@ function DataGridEventsWrapper(props) {
133
133
  menuAnchorEl,
134
134
  menuRow
135
135
  } = (0, _useGridEvents.useGridEvents)(props.gridRef.current, props);
136
+ const menuRowId = menuAnchorEl && !props.hideContextMenu && props.contextMenuItems ? (0, _func_grid.get_row_id)(menuRow) : undefined;
137
+ const consumerRowClassName = (_props$static_gridOpt = props.static_gridOptions) === null || _props$static_gridOpt === void 0 ? void 0 : _props$static_gridOpt.rowClassName;
138
+ const rowClassName = _react.default.useCallback(rowProps => (0, _clsx.default)(typeof consumerRowClassName === 'function' ? consumerRowClassName(rowProps) : consumerRowClassName, menuRowId != null && (0, _func_grid.get_row_id)(rowProps.data) === menuRowId && 'azgrid-row--menu-open'), [consumerRowClassName, menuRowId]);
136
139
  /* -------------------------------------------------------------------------- */
137
140
  /**
138
141
  * Merge click events into grid options
@@ -146,6 +149,7 @@ function DataGridEventsWrapper(props) {
146
149
  // internal row attributes first, consumer's onRenderRow last so it can override them
147
150
  const consumerOnRenderRow = opts.onRenderRow;
148
151
  const renderProps = {
152
+ rowClassName,
149
153
  onRenderRow: consumerOnRenderRow ? rowProps => {
150
154
  onRenderRow(rowProps);
151
155
  consumerOnRenderRow(rowProps);
@@ -180,7 +184,7 @@ function DataGridEventsWrapper(props) {
180
184
  ...renderProps,
181
185
  ...a11yProps
182
186
  };
183
- }, [props.static_gridOptions, props.editable, onRowClick, onRowContextMenu, onRenderRow, onRenderLoadMask]);
187
+ }, [props.static_gridOptions, props.editable, onRowClick, onRowContextMenu, onRenderRow, onRenderLoadMask, rowClassName]);
184
188
  /* -------------------------------------------------------------------------- */
185
189
  // The menu is portaled to <body>, so it carries the theme class itself to get the theme tokens.
186
190
  return _react.default.createElement("div", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azrico/reactgrid",
3
- "version": "1.6.6",
3
+ "version": "1.6.7",
4
4
  "main": "dist/index.js",
5
5
  "description": "React Datagrid",
6
6
  "author": "Azrideus <MehrdadTavangar@gmail.com>",
@@ -75,8 +75,17 @@ export function useGridEvents(grid: GridType, props: DataGridWrapperProps) {
75
75
 
76
76
  /* ------------------------------- ContextMenu ------------------------------ */
77
77
  React.useEffect(() => {
78
- if (props.hideContextMenu) update_menuRow(null, null);
79
- }, [props.hideContextMenu, update_menuRow]);
78
+ if (props.hideContextMenu || !props.contextMenuItems) update_menuRow(null, null);
79
+ }, [props.hideContextMenu, props.contextMenuItems, update_menuRow]);
80
+
81
+ React.useEffect(() => {
82
+ if (!menuAnchorEl || !props.escapeClosesContextMenu) return;
83
+ const onKeyDown = (event: KeyboardEvent) => {
84
+ if (event.key === 'Escape') update_menuRow(null, null);
85
+ };
86
+ document.addEventListener('keydown', onKeyDown);
87
+ return () => document.removeEventListener('keydown', onKeyDown);
88
+ }, [menuAnchorEl, props.escapeClosesContextMenu, update_menuRow]);
80
89
 
81
90
  const onRowContextMenu = React.useCallback((rowProps, event) => {
82
91
  if (
@@ -1452,65 +1452,65 @@
1452
1452
  }
1453
1453
 
1454
1454
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--even:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover {
1455
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1456
- color: var(--azgrid-empty-fg, var(--azgrid-preset-empty-fg));
1455
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1456
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1457
1457
  }
1458
1458
 
1459
1459
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--even:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__row-index-column {
1460
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1460
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1461
1461
  }
1462
1462
 
1463
1463
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--even:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__cell--over {
1464
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1465
- color: var(--azgrid-empty-fg, var(--azgrid-preset-empty-fg));
1464
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1465
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1466
1466
  }
1467
1467
 
1468
1468
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--even:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover {
1469
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1470
- color: var(--azgrid-empty-fg, var(--azgrid-preset-empty-fg));
1469
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1470
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1471
1471
  }
1472
1472
 
1473
1473
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--even:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__row-index-column {
1474
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1474
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1475
1475
  }
1476
1476
 
1477
1477
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--even:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__cell--over {
1478
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1479
- color: var(--azgrid-empty-fg, var(--azgrid-preset-empty-fg));
1478
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1479
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1480
1480
  }
1481
1481
 
1482
1482
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--no-zebra:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover,
1483
1483
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--odd:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover {
1484
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1485
- color: var(--azgrid-empty-fg, var(--azgrid-preset-empty-fg));
1484
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1485
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1486
1486
  }
1487
1487
 
1488
1488
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--no-zebra:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__row-index-column,
1489
1489
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--odd:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__row-index-column {
1490
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1490
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1491
1491
  }
1492
1492
 
1493
1493
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--no-zebra:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__cell--over,
1494
1494
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--odd:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__cell--over {
1495
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1496
- color: var(--azgrid-empty-fg, var(--azgrid-preset-empty-fg));
1495
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1496
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1497
1497
  }
1498
1498
 
1499
1499
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--no-zebra:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover,
1500
1500
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--odd:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover {
1501
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1502
- color: var(--azgrid-empty-fg, var(--azgrid-preset-empty-fg));
1501
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1502
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1503
1503
  }
1504
1504
 
1505
1505
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--no-zebra:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__row-index-column,
1506
1506
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--odd:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__row-index-column {
1507
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1507
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1508
1508
  }
1509
1509
 
1510
1510
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--no-zebra:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__cell--over,
1511
1511
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row--odd:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty).InovuaReactDataGrid__row--selected .InovuaReactDataGrid__row-hover-target:hover .InovuaReactDataGrid__cell--over {
1512
- background: var(--azgrid-empty-bg, var(--azgrid-preset-empty-bg));
1513
- color: var(--azgrid-empty-fg, var(--azgrid-preset-empty-fg));
1512
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1513
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1514
1514
  }
1515
1515
 
1516
1516
  .azgrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--focused .InovuaReactDataGrid__row--active>.InovuaReactDataGrid__row-active-borders-wrapper>.InovuaReactDataGrid__row-active-borders:before,
@@ -1934,6 +1934,15 @@
1934
1934
  fill: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1935
1935
  }
1936
1936
 
1937
+ /* Keep the menu row in its hover state while the pointer is in the portaled menu.
1938
+ * Match the strongest selected-row :hover selectors, including their cells. */
1939
+ .azgrid.InovuaReactDataGrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row.azgrid-row--menu-open:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target,
1940
+ .azgrid.InovuaReactDataGrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row.azgrid-row--menu-open:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target .InovuaReactDataGrid__cell,
1941
+ .azgrid.InovuaReactDataGrid.InovuaReactDataGrid--theme-default-light.InovuaReactDataGrid--show-hover-rows .InovuaReactDataGrid__row.azgrid-row--menu-open:not(.InovuaReactDataGrid__row--scrolling):not(.InovuaReactDataGrid__row--empty) .InovuaReactDataGrid__row-hover-target .InovuaReactDataGrid__row-index-column {
1942
+ background: var(--azgrid-row-bg-hover, var(--azgrid-preset-row-bg-hover));
1943
+ color: var(--azgrid-row-fg-hover, var(--azgrid-preset-row-fg-hover));
1944
+ }
1945
+
1937
1946
  /* Additional surfaces and typography not assigned colours by Inovua. */
1938
1947
  .azgrid.InovuaReactDataGrid.InovuaReactDataGrid--theme-default-light .InovuaReactDataGrid__row--active:not(.InovuaReactDataGrid__row--selected) .InovuaReactDataGrid__cell:not(.InovuaReactDataGrid__cell--no-background) {
1939
1948
  background: var(--azgrid-row-bg-active, var(--azgrid-preset-row-bg-active));
@@ -97,6 +97,8 @@ export type AzGridProps = {
97
97
  contextMenuItems?: ConvertableToMenuItem;
98
98
  /** Keep the context menu open when clicking outside (default false). */
99
99
  dontAutoCloseContextMenu?: boolean;
100
+ /** Close the context menu on Escape (default false). */
101
+ escapeClosesContextMenu?: boolean;
100
102
  /** Hide the grid context menu and leave the browser's context menu available. */
101
103
  hideContextMenu?: boolean;
102
104
  /** Extra classes on the row context menu, which is portaled to <body> */
@@ -1,7 +1,7 @@
1
1
  import '@inovua/reactdatagrid-enterprise/index.css';
2
2
  import '../themes/base_styles.css';
3
3
  import '../themes/grid.css';
4
- import { get_default_key } from '../func/func_grid';
4
+ import { get_default_key, get_row_id } from '../func/func_grid';
5
5
  import { ContextMenu } from '@azrico/controls/context-menu';
6
6
  import React from 'react';
7
7
 
@@ -164,6 +164,20 @@ function DataGridEventsWrapper(props: DataGridWrapperProps) {
164
164
  menuAnchorEl,
165
165
  menuRow,
166
166
  } = useGridEvents(props.gridRef.current, props);
167
+ const menuRowId =
168
+ menuAnchorEl && !props.hideContextMenu && props.contextMenuItems
169
+ ? get_row_id(menuRow)
170
+ : undefined;
171
+ const consumerRowClassName = props.static_gridOptions?.rowClassName;
172
+ const rowClassName = React.useCallback(
173
+ (rowProps) => clsx(
174
+ typeof consumerRowClassName === 'function'
175
+ ? consumerRowClassName(rowProps)
176
+ : consumerRowClassName,
177
+ menuRowId != null && get_row_id(rowProps.data) === menuRowId && 'azgrid-row--menu-open'
178
+ ),
179
+ [consumerRowClassName, menuRowId]
180
+ );
167
181
  /* -------------------------------------------------------------------------- */
168
182
  /**
169
183
  * Merge click events into grid options
@@ -178,6 +192,7 @@ function DataGridEventsWrapper(props: DataGridWrapperProps) {
178
192
  // internal row attributes first, consumer's onRenderRow last so it can override them
179
193
  const consumerOnRenderRow = opts.onRenderRow;
180
194
  const renderProps: any = {
195
+ rowClassName,
181
196
  onRenderRow: consumerOnRenderRow
182
197
  ? (rowProps) => {
183
198
  onRenderRow(rowProps);
@@ -218,6 +233,7 @@ function DataGridEventsWrapper(props: DataGridWrapperProps) {
218
233
  onRowContextMenu,
219
234
  onRenderRow,
220
235
  onRenderLoadMask,
236
+ rowClassName,
221
237
  ]);
222
238
 
223
239
  /* -------------------------------------------------------------------------- */