Skip to content

Conversation

JCQuintas
Copy link
Member

@JCQuintas JCQuintas commented Jul 17, 2025

Fixes #18826

Test using the provided code
https://stackblitz.com/edit/yua3glfx-bsncmi27?file=package.json

I couldn't replicate the issue from stackblitz in an actual test because the main issue is due to event retargeting in the boundaries around the shadow dom. Which I couldn't simulate 😅

The PR revolves around making the gestures work in the shadowdom in open mode. I've left comments in the most important parts.

These changes won't work if the shadow root is in "closed" mode though. We will need more changes for that to work properly.

@JCQuintas JCQuintas self-assigned this Jul 17, 2025
@JCQuintas JCQuintas added type: regression A bug, but worse, it used to behave as expected. scope: charts Changes related to the charts. labels Jul 17, 2025
@mui-bot
Copy link

mui-bot commented Jul 17, 2025

Deploy preview: https://deploy-preview-18837--material-ui-x.netlify.app/

Bundle size report

Total Size Change: 🔺+2.84KB(+0.02%) - Total Gzip Change: 🔺+800B(+0.02%)
Files: 122 total (0 added, 0 removed, 20 changed)

Show details for 100 more bundles (22 more not shown)

@mui/x-chartsparsed: 🔺+142B(+0.05%) gzip: 🔺+40B(+0.04%)
@mui/x-charts-proparsed: 🔺+142B(+0.04%) gzip: 🔺+40B(+0.03%)
@mui/x-charts-pro/BarChartProparsed: 🔺+142B(+0.05%) gzip: 🔺+37B(+0.04%)
@mui/x-charts-pro/ChartContainerProparsed: 🔺+142B(+0.08%) gzip: 🔺+45B(+0.08%)
@mui/x-charts-pro/ChartDataProviderProparsed: 🔺+142B(+0.08%) gzip: 🔺+38B(+0.07%)
@mui/x-charts-pro/FunnelChartparsed: 🔺+142B(+0.06%) gzip: 🔺+48B(+0.06%)
@mui/x-charts-pro/Heatmapparsed: 🔺+142B(+0.06%) gzip: 🔺+44B(+0.06%)
@mui/x-charts-pro/LineChartProparsed: 🔺+142B(+0.05%) gzip: 🔺+40B(+0.04%)
@mui/x-charts-pro/PieChartProparsed: 🔺+142B(+0.06%) gzip: 🔺+37B(+0.05%)
@mui/x-charts-pro/RadarChartProparsed: 🔺+142B(+0.07%) gzip: 🔺+40B(+0.06%)
@mui/x-charts-pro/ScatterChartProparsed: 🔺+142B(+0.05%) gzip: 🔺+40B(+0.05%)
@mui/x-charts/BarChartparsed: 🔺+142B(+0.07%) gzip: 🔺+37B(+0.06%)
@mui/x-charts/ChartContainerparsed: 🔺+142B(+0.09%) gzip: 🔺+37B(+0.08%)
@mui/x-charts/ChartDataProviderparsed: 🔺+142B(+0.10%) gzip: 🔺+36B(+0.08%)
@mui/x-charts/Gaugeparsed: 🔺+142B(+0.10%) gzip: 🔺+53B(+0.12%)
@mui/x-charts/LineChartparsed: 🔺+142B(+0.06%) gzip: 🔺+39B(+0.06%)
@mui/x-charts/PieChartparsed: 🔺+142B(+0.07%) gzip: 🔺+34B(+0.06%)
@mui/x-charts/RadarChartparsed: 🔺+142B(+0.07%) gzip: 🔺+40B(+0.07%)
@mui/x-charts/ScatterChartparsed: 🔺+142B(+0.07%) gzip: 🔺+39B(+0.06%)
@mui/x-charts/SparkLineChartparsed: 🔺+142B(+0.07%) gzip: 🔺+36B(+0.05%)
@mui/x-charts-pro/ChartsToolbarProparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts-pro/ChartZoomSliderparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsAxisparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsAxisHighlightparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsClipPathparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsGridparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsLabelparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsLegendparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsLocalizationProviderparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsOverlayparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsReferenceLineparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsSurfaceparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsTextparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsTooltipparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsXAxisparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/ChartsYAxisparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-charts/Toolbarparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-data-gridparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-data-grid-premiumparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-data-grid-premium/DataGridPremiumparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-data-grid-proparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-data-grid-pro/DataGridProparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-data-grid/DataGridparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickersparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-proparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/AdapterDateFnsparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/AdapterDateFnsJalaliparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/AdapterDayjsparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/AdapterLuxonparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/AdapterMomentparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/AdapterMomentHijriparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/AdapterMomentJalaaliparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/DateRangeCalendarparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/DateRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/DateRangePickerDayparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/DateRangePickerDay2parsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/DateTimeRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/DesktopDateRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/DesktopDateTimeRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/DesktopTimeRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/LocalizationProviderparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/MobileDateRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/MobileDateTimeRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/MobileTimeRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/MultiInputDateRangeFieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/MultiInputDateTimeRangeFieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/MultiInputTimeRangeFieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/PickersRangeCalendarHeaderparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/SingleInputDateRangeFieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/SingleInputDateTimeRangeFieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/SingleInputTimeRangeFieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/StaticDateRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers-pro/TimeRangePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/AdapterDateFnsparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/AdapterDateFnsBaseparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/AdapterDateFnsJalaliparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/AdapterDayjsparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/AdapterLuxonparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/AdapterMomentparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/AdapterMomentHijriparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/AdapterMomentJalaaliparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DateCalendarparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DateFieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DatePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DateTimeFieldparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DateTimePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DayCalendarSkeletonparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DesktopDatePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DesktopDateTimePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DesktopTimePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/DigitalClockparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/LocalizationProviderparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/MobileDatePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/MobileDateTimePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/MobileTimePickerparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/MonthCalendarparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/MultiSectionDigitalClockparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/PickerDay2parsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/PickersActionBarparsed: 0B(0.00%) gzip: 0B(0.00%)
@mui/x-date-pickers/PickersCalendarHeaderparsed: 0B(0.00%) gzip: 0B(0.00%)

Details of bundle changes

Generated by 🚫 dangerJS against f3d98d3

Copy link

codspeed-hq bot commented Jul 17, 2025

CodSpeed Performance Report

Merging #18837 will not alter performance

Comparing JCQuintas:shadow-gestures (f3d98d3) with master (cc7e066)

Summary

✅ 10 untouched benchmarks

Comment on lines +317 to +319
('getRootNode' in this.element &&
this.element.getRootNode() instanceof ShadowRoot &&
event.composedPath().includes(this.element))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the main changes.

Comment on lines +156 to +158
('getRootNode' in calculatedTarget &&
calculatedTarget.getRootNode() instanceof ShadowRoot &&
pointer.srcEvent.composedPath().includes(calculatedTarget)),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another check for shadowroot

@@ -124,10 +124,10 @@ export class PointerManager {
new Set();

public constructor(options: PointerManagerOptions) {
this.root = (options.root ?? document.documentElement) as HTMLElement;
this.root = (options.root ?? document.getRootNode({ composed: true })) as HTMLElement;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We get the root node instead of documentElement, though it shouldn't really change in most cases.

}
}
} else if (type === 'pointermove') {
if (type === 'pointerdown' || type === 'pointermove') {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed pointerCapture logic as it is automatically done by the browsers, and was preventing the click in the shadow dom to go through, even though we had a catch there 🤷

@JCQuintas JCQuintas marked this pull request as ready for review July 17, 2025 22:19
@JCQuintas JCQuintas changed the title [charts] Fix shadowroot gesture issues [charts] Fix issue where charts gestures weren't properly working when inside the shadow-dom Jul 17, 2025
@@ -321,6 +321,7 @@ export class TurnWheelGesture<GestureName extends string> extends Gesture<Gestur
const domEvent = new CustomEvent(eventName, {
bubbles: true,
cancelable: true,
composed: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On this topic, I'm wondering if instead of true/false the default could be defined by the native event triggering the custom one.

I don't have enough overview on this part of the code so this proposal might make no sense

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They currently follow the native event. All of them have composed:true, except some special events.

composed:true allows events triggered by an element inside shadow-dom to bubble up to elements outside the shadow dom

Copy link
Member

@bernardobelchior bernardobelchior left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested it and it seems to be working 👍

@@ -225,7 +226,7 @@ export class GestureManager<
* @param gestureName - Name of the gesture whose state should be updated
* @param element - The DOM element where the gesture is attached
* @param state - New state to apply to the gesture
* @returns True if the state was successfully updated, false if the gesture wasn't found
* @returns false if the state was successfully updated, false if the gesture wasn't found
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume it's wrong replace all

Suggested change
* @returns false if the state was successfully updated, false if the gesture wasn't found
* @returns true if the state was successfully updated, false if the gesture wasn't found

@@ -321,6 +321,7 @@ export class TurnWheelGesture<GestureName extends string> extends Gesture<Gestur
const domEvent = new CustomEvent(eventName, {
bubbles: true,
cancelable: true,
composed: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On this topic, I'm wondering if instead of true/false the default could be defined by the native event triggering the custom one.

I don't have enough overview on this part of the code so this proposal might make no sense

@JCQuintas JCQuintas merged commit 6f840c0 into mui:master Jul 18, 2025
23 checks passed
@JCQuintas JCQuintas deleted the shadow-gestures branch July 18, 2025 09:59
@JCQuintas JCQuintas changed the title [charts] Fix issue where charts gestures weren't properly working when inside the shadow-dom [charts-pro] Fix issue where charts gestures weren't properly working when inside the shadow-dom Jul 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
scope: charts Changes related to the charts. type: regression A bug, but worse, it used to behave as expected.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[charts] break click event inside shadow dom
4 participants