Created
October 9, 2024 13:05
-
-
Save vasnakos/91aaccc88765d2346765b928e7b82559 to your computer and use it in GitHub Desktop.
This function is designed to address issues with event handling in React 16 when using a Shadow DOM. React 16's synthetic event system does not work well with Shadow DOM due to limitations in how events are retargeted. This function "retargets" events that occur within a shadow DOM, making them compatible with React's synthetic event system by e…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Retargets DOM events within a Shadow DOM to ensure they are correctly recognized | |
* by React 16's synthetic event system. This is necessary because React 16 does not | |
* natively handle events from within a Shadow DOM, leading to issues with event propagation | |
* and target recognition. | |
* | |
* @param {ShadowRoot} shadowRoot - The root of the Shadow DOM where event retargeting should be applied. | |
*/ | |
function retargetEvents(shadowRoot) { | |
// List of all event types to listen for within the Shadow DOM. | |
// These are standard DOM events that React may need to handle. | |
const eventTypes = [ | |
"abort", | |
"animationcancel", | |
"animationend", | |
"animationiteration", | |
"animationstart", | |
"auxclick", | |
"blur", | |
"cancel", | |
"canplay", | |
"canplaythrough", | |
"change", | |
"click", | |
"close", | |
"contextmenu", | |
"cuechange", | |
"dblclick", | |
"drag", | |
"dragend", | |
"dragenter", | |
"dragexit", | |
"dragleave", | |
"dragover", | |
"dragstart", | |
"drop", | |
"durationchange", | |
"emptied", | |
"ended", | |
"error", | |
"focus", | |
"focusin", | |
"focusout", | |
"gotpointercapture", | |
"input", | |
"invalid", | |
"keydown", | |
"keypress", | |
"keyup", | |
"load", | |
"loadeddata", | |
"loadedmetadata", | |
"loadstart", | |
"lostpointercapture", | |
"mousedown", | |
"mouseenter", | |
"mouseleave", | |
"mousemove", | |
"mouseout", | |
"mouseover", | |
"mouseup", | |
"pause", | |
"play", | |
"playing", | |
"pointercancel", | |
"pointerdown", | |
"pointerenter", | |
"pointerleave", | |
"pointermove", | |
"pointerout", | |
"pointerover", | |
"pointerup", | |
"progress", | |
"ratechange", | |
"reset", | |
"resize", | |
"scroll", | |
"securitypolicyviolation", | |
"seeked", | |
"seeking", | |
"select", | |
"selectionchange", | |
"selectstart", | |
"stalled", | |
"submit", | |
"suspend", | |
"timeupdate", | |
"toggle", | |
"touchcancel", | |
"touchend", | |
"touchmove", | |
"touchstart", | |
"transitioncancel", | |
"transitionend", | |
"transitionrun", | |
"transitionstart", | |
"volumechange", | |
"waiting", | |
"wheel", | |
]; | |
/** | |
* Patches the event object to ensure proper event propagation and compatibility | |
* across Shadow DOM boundaries. | |
* | |
* @param {Event} event - The event object to patch. | |
*/ | |
function patchEvent(event) { | |
event.stopPropagation = function () { | |
const e = this; | |
if (!e.cancelBubble) { | |
e.cancelBubble = true; | |
} | |
}; | |
// `composed` property of the Event interface returns a boolean value which indicates whether or not the event | |
// will propagate across the shadow DOM boundary into the standard DOM so we set composed=true to allow the event | |
// to cross Shadow DOM boundaries | |
if (event.composed === undefined) { | |
event.composed = true; | |
} | |
} | |
/** | |
* Retargets the event's target to the original element that triggered the event. | |
* This is crucial for React to correctly identify which element initiated the event, | |
* especially when events originate from within a Shadow DOM. | |
* | |
* @param {Event} event - The event object to retarget. | |
* @returns {Event} - The modified event with the retargeted `target`. | |
*/ | |
function retarget(event) { | |
// Get the path of elements the event traveled through | |
const path = event.composedPath(); | |
// Override the target property to point to the original element | |
// that triggered the event (path[0] is the deepest element) | |
Object.defineProperty(event, "target", { | |
enumerable: true, //make the property visible when iterating over the event object | |
configurable: true, // Allows further modification or deletion of this property if needed | |
get: () => path[0], // ensure that event.target always points to the original triggering element. | |
}); | |
// Apply additional patches to the event to ensure proper behavior. | |
patchEvent(event); | |
// Return the modified event with the updated `target`. | |
return event; | |
} | |
// Iterate over each event type and add an event listener to the Shadow Root. | |
// The listener retargets the event during the capturing phase to ensure it happens before React's synthetic event system. | |
eventTypes.forEach((eventType) => { | |
shadowRoot.addEventListener( | |
eventType, | |
(event) => { | |
retarget(event); | |
}, | |
true // Use capture phase to intercept events before they reach React. | |
); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment