Skip to content
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

feat: Focus Management within ShadowDOM #6046

Merged
merged 146 commits into from
Feb 6, 2025

Conversation

MahmoudElsayad
Copy link
Contributor

@MahmoudElsayad MahmoudElsayad commented Mar 11, 2024

Closes #1472

This PR enhances focus management capabilities in React Spectrum applications when used within Shadow DOM environments.

Changes

  • Introduced a new utility function getRootNode, designed to return a given element's contextually appropriate root (Document or ShadowRoot). This improves the library's ability to query and manipulate focus within shadow DOMs.
  • Added Shadow DOM support to the following:
    • FocusScope
    • useFocus
    • useFocusVisible
    • useFocusWithin
    • useInteractionOutside
    • usePress
  • Implemented a new utility function, getRootBody that determines the effective "body" element for an event's propagation path, supporting both Shadow DOM and traditional document structures.
  • implemented a new utility, ' getDeepActiveElement,` which retrieves the currently focused element across Shadow DOM boundaries, ensuring accurate focus management in complex DOM structures.
  • Added tests for all new utility functions and affected hooks/components.

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

  • Open any storybook example that uses any of the affected hooks/components
  • Test the functionality and make sure it works when the story is encapsulated inside a shadow root.
// custom wrapper to run any story inside a shadow root
function ShadowDomWrapper({ children }) {
  const container = useRef(null);

  useEffect(() => {
    const _container = container.current;
    if (!_container) return;

    const div = document.createElement('div');
    _container.appendChild(div);

    const shadowRoot = div.attachShadow({ mode: 'open' });
    const main = document.createElement('main');
    shadowRoot.appendChild(main);

    // Wrap children with the ThemeProvider when rendering
    ReactDOM.render(
      children,
      main
    );

    return () => {
      ReactDOM.unmountComponentAtNode(main);
      if (_container) {
        _container.innerHTML = '';
      }
    };
  }, [children]);

  return <div ref={container} />;
}
// .storybook/preview.js
export const decorators = [
+    (Story) => (
+     <ShadowDomWrapper>
+        <Story />
+      </ShadowDomWrapper>
+    ),
  withScrollingSwitcher,
  ...(process.env.NODE_ENV !== 'production' ? [withStrictModeSwitcher] : []),
  withProviderSwitcher
];

🧢 Your Project:

PSPDFKit -

@MahmoudElsayad MahmoudElsayad marked this pull request as ready for review March 18, 2024 02:53
@ritz078
Copy link
Contributor

ritz078 commented Mar 18, 2024

@snowystinger We are working on fixing the linting and type errors but if you want you can give an early eye to this PR.

@snowystinger
Copy link
Member

perf and size comments on PR #7548 (comment)

@snowystinger snowystinger force-pushed the shadow-dom-enhancement-1-getRootNode branch from 80daa95 to 21b80bd Compare January 20, 2025 05:07
# Conflicts:
#	packages/@react-aria/focus/src/FocusScope.tsx
#	packages/@react-aria/interactions/src/usePress.ts
#	packages/@react-aria/interactions/test/usePress.test.js
#	packages/@react-spectrum/table/test/Table.test.js
@ritz078
Copy link
Contributor

ritz078 commented Jan 22, 2025

would you mind testing the new changes in your application and let us know if they still work for you

@snowystinger These changes look good so far. Our internal tests are passing. We will be opening some small followup PRs too once this gets merged. Any idea about the timeline for this PR ?

snowystinger
snowystinger previously approved these changes Jan 22, 2025
@snowystinger
Copy link
Member

Thanks for checking in, we are still in discussions internally. Should have more information for you early next week.

snowystinger
snowystinger previously approved these changes Jan 28, 2025
devongovett
devongovett previously approved these changes Feb 6, 2025
Copy link
Member

@devongovett devongovett left a comment

Choose a reason for hiding this comment

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

Thanks for working on this. I appreciate the persistence, and I know it's been a long process.

In the future, I think we may need to revisit how some of our components are implemented more holistically to account for shadow dom. While deeply traversing into shadow roots works in some cases, it won't work with closed shadow roots. For example, even with the FocusScope changes here, things like native <video> elements, <input type="date">, etc., along with any third party components using closed shadow roots will still not work correctly since we cannot traverse into them. Fixing that would require a completely different implementation strategy.

Overall, shadow dom is meant to encapsulate behavior, and deep traversal sort of goes against that goal. For now it's better than nothing, but it would be good to try to find ways to avoid it in the future if possible.

NOTICE.txt Outdated Show resolved Hide resolved
NOTICE.txt Outdated Show resolved Hide resolved
@snowystinger snowystinger dismissed stale reviews from devongovett and themself via dc4ca82 February 6, 2025 01:38
@snowystinger snowystinger added this pull request to the merge queue Feb 6, 2025
Merged via the queue into adobe:main with commit c78b248 Feb 6, 2025
29 checks passed
@snowystinger
Copy link
Member

Thanks again, this will be available in our nightlies starting tonight. You can enable the behavior using the feature flag.

import {enableShadowDOM} from '@react-stately/flags';

As @devongovett noted, we're starting to think about how we want to move forward with this kind of work. We've made a start here: #7687
Feel free to add your voice as well. We're still building up the requirements and it helps to have real life cases to draw from.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

FocusScope not working when used inside shadowRoot
10 participants