Closed gavinxgu closed 2 years ago
Thanks for the report, looking into it...
I was able to fix following issue with following function:
import { getAllAffectedNodes } from './utils/all-affected';
import { toArray } from './utils/array';
import { getActiveElement } from './utils/getActiveElement';
const focusInFrame = (frame: HTMLIFrameElement) => frame === document.activeElement;
const focusInsideIframe = (topNode: Element) =>
Boolean(toArray(topNode.querySelectorAll<HTMLIFrameElement>('iframe')).some((node) => focusInFrame(node)));
const focusInsideShadowDom = (activeElement: HTMLElement, node: Element): boolean => {
let currentElement = activeElement;
while (currentElement && currentElement.parentNode) {
if (currentElement.parentNode === node) {
return true;
} else if (currentElement.parentNode instanceof ShadowRoot) {
currentElement = currentElement.parentNode.host as HTMLElement;
} else {
currentElement = currentElement.parentNode as HTMLElement;
}
}
return false;
};
export const focusInside = (topNode: HTMLElement | HTMLElement[]): boolean => {
const activeElement = document && getActiveElement();
if (!activeElement || (activeElement.dataset && activeElement.dataset.focusGuard)) {
return false;
}
return getAllAffectedNodes(topNode).reduce(
(result, node) =>
result || node.contains(activeElement) || focusInsideIframe(node) || focusInsideShadowDom(activeElement, node),
false as boolean
);
};
react-focus-lock@2.9.0 has been released with the all required patches.
Demo
https://codesandbox.io/s/vigorous-bird-bpy9s3?file=/src/App.tsx
It occurs when focus-lock 0.10.2 adds shadowRoot support for
getActiveElement
, but it usesnode.contains
to determine containment relationship.https://github.com/theKashey/focus-lock/blob/1fe68ec4fb489f5b78be5790aa07f5e0d5bf00b5/src/focusInside.ts#L18
For example, we have a cuz element
my-input
like this.Suppose we have a React component like this.
When we click input to focus,
focusInside
function returns false, then we lost the focus state.node
value is the div element andactiveElement
is the input element inside shadowRoot,butdiv.contains(innerInput)
is false.P.S. react-remove-scroll also has this problem.