-
Notifications
You must be signed in to change notification settings - Fork 461
Users/vnbaaij/improve datagrid script #4500
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
…rt and remove the resizeController for the grid instance. 2. Dynamic AbortSignal: Updated enableColumnResizing to: • Detect if a grid already has a resizeController and abort it before creating a new one (e.g., when columns are re-evaluated). 1. Create a localController if no external signal is provided from Blazor. 2. Use an effectiveSignal for all resizing event listeners. 3. Registry Persistence: Modified the grid registration logic to store the resizeController so it can be managed during re-initialization or disposal. These changes ensure that whenever column resizing is re-enabled (which happens in Blazor when columns change), the old JS listeners are properly cleaned up via the AbortController.
- Port v5 script improvement
|
✅ All tests passed successfully Details on your Workflow / Core Tests page. |
Summary - Unit Tests Code CoverageSummary
CoverageMicrosoft.FluentUI.AspNetCore.Components - 60.9%
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR improves the DataGrid JavaScript interop by enhancing abort controller management for proper cleanup of event listeners, improving keyboard event handling for closing popups with the Escape key, and fixing registry persistence to handle re-initialization scenarios.
Changes:
- Enhanced AbortController management to clean up resize event listeners when columns change
- Added Escape key handling at the body level to close column options and resize popups
- Improved grid registry to update existing entries instead of creating duplicates
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (columnResizeElement) { | ||
| if (event.key === "ArrowRight" || event.key === "ArrowLeft" || event.key === "ArrowDown" || event.key === "ArrowUp") { | ||
| event.stopPropagation(); | ||
| } | ||
| if (event.key === "Escape") { | ||
| gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true })); | ||
| gridElement.focus(); | ||
| return; | ||
| } |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The removal of the contains(event.target) check changes the behavior significantly. Previously, arrow key propagation was only stopped when the event target was inside the column resize element. Now it stops propagation whenever the column resize element merely exists in the DOM, regardless of whether the event originated from within it. This will incorrectly prevent arrow key navigation when column resize is open but focus is elsewhere in the grid.
| if (columnOptionsElement) { | ||
| gridElement.dispatchEvent(new CustomEvent('closecolumnoptions', { bubbles: true })); | ||
| gridElement.focus(); | ||
| } | ||
| const columnResizeElement = gridElement?.querySelector('.col-resize'); | ||
| if (columnResizeElement) { | ||
| gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true })); | ||
| gridElement.focus(); |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When multiple grids exist on the page and Escape is pressed, all grids' bodyKeyDownHandler functions will execute. Each grid that has column options or resize elements open will dispatch close events and call gridElement.focus(). This can cause a focus race condition where the last grid's handler steals focus, even if the user intended to close a popup on a different grid. Consider checking if the event originated from within the specific grid's DOM or if the popup element is currently focused before calling focus.
| if (columnOptionsElement) { | |
| gridElement.dispatchEvent(new CustomEvent('closecolumnoptions', { bubbles: true })); | |
| gridElement.focus(); | |
| } | |
| const columnResizeElement = gridElement?.querySelector('.col-resize'); | |
| if (columnResizeElement) { | |
| gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true })); | |
| gridElement.focus(); | |
| const columnResizeElement = gridElement?.querySelector('.col-resize'); | |
| // Determine whether this Escape event is relevant to this grid or its popups | |
| const eventPath = typeof event.composedPath === "function" ? event.composedPath() : []; | |
| const eventOriginatesFromThisGrid = eventPath && eventPath.indexOf(gridElement) >= 0; | |
| const activeElement = document.activeElement; | |
| if (columnOptionsElement) { | |
| gridElement.dispatchEvent(new CustomEvent('closecolumnoptions', { bubbles: true })); | |
| const optionsHasFocus = columnOptionsElement.contains(activeElement); | |
| if (eventOriginatesFromThisGrid || optionsHasFocus) { | |
| gridElement.focus(); | |
| } | |
| } | |
| if (columnResizeElement) { | |
| gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true })); | |
| const resizeHasFocus = columnResizeElement.contains(activeElement); | |
| if (eventOriginatesFromThisGrid || resizeHasFocus) { | |
| gridElement.focus(); | |
| } |
| grid.resizeController.abort(); | ||
| } | ||
|
|
||
| const localController = new AbortController(); |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an external signal is provided (e.g., from the init function), a localController is created on line 190 but never used because effectiveSignal will be the external signal. Additionally, line 250 stores undefined for resizeController when an external signal is used. This means when enableColumnResizing is called again (e.g., when columns change), lines 186-188 attempt to abort a controller that is always undefined in this scenario. While the old resize handle divs are removed (line 223) which cleans up their listeners, the localController serves no purpose and creates unnecessary overhead. Consider only creating localController when no external signal is provided.
| const localController = new AbortController(); | |
| const localController = signal ? null : new AbortController(); |
| const columnOptionsElement = gridElement?.querySelector('.col-options'); | ||
| if (columnOptionsElement) { | ||
| gridElement.dispatchEvent(new CustomEvent('closecolumnoptions', { bubbles: true })); | ||
| gridElement.focus(); | ||
| } | ||
| const columnResizeElement = gridElement?.querySelector('.col-resize'); | ||
| if (columnResizeElement) { | ||
| gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true })); |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The handler calls gridElement.focus() on both lines 34 and 39 when both column options and column resize elements exist simultaneously. This results in redundant focus calls. Consider checking if either element exists first, dispatching the appropriate close events, and then calling focus once at the end.
| const columnOptionsElement = gridElement?.querySelector('.col-options'); | |
| if (columnOptionsElement) { | |
| gridElement.dispatchEvent(new CustomEvent('closecolumnoptions', { bubbles: true })); | |
| gridElement.focus(); | |
| } | |
| const columnResizeElement = gridElement?.querySelector('.col-resize'); | |
| if (columnResizeElement) { | |
| gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true })); | |
| let shouldFocus = false; | |
| const columnOptionsElement = gridElement?.querySelector('.col-options'); | |
| if (columnOptionsElement) { | |
| gridElement.dispatchEvent(new CustomEvent('closecolumnoptions', { bubbles: true })); | |
| shouldFocus = true; | |
| } | |
| const columnResizeElement = gridElement?.querySelector('.col-resize'); | |
| if (columnResizeElement) { | |
| gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true })); | |
| shouldFocus = true; | |
| } | |
| if (shouldFocus) { |
| if (columnOptionsElement) { | ||
| if (event.key === "ArrowRight" || event.key === "ArrowLeft" || event.key === "ArrowDown" || event.key === "ArrowUp") { | ||
| event.stopPropagation(); | ||
| } | ||
| if (event.key === "Escape") { | ||
| gridElement.dispatchEvent(new CustomEvent('closecolumnoptions', { bubbles: true })); | ||
| gridElement.focus(); | ||
| return; | ||
| } |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The removal of the contains(event.target) check changes the behavior significantly. Previously, arrow key propagation was only stopped when the event target was inside the column options element. Now it stops propagation whenever the column options element merely exists in the DOM, regardless of whether the event originated from within it. This will incorrectly prevent arrow key navigation when column options are open but focus is elsewhere in the grid.
• Detect if a grid already has a resizeController and abort it before creating a new one (e.g., when columns are re-evaluated).
These changes ensure that whenever column resizing is re-enabled (which happens in Blazor when columns change), the old JS listeners are properly cleaned up via the AbortController. It also now close any popups correctly by using ESC key