adobe / react-spectrum

A collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.
https://react-spectrum.adobe.com
Apache License 2.0
13.04k stars 1.13k forks source link

<object/>'s used to display images with a fallback break keyboard navigation inside of RadioGroup #6496

Open lithdew opened 5 months ago

lithdew commented 5 months ago

Provide a general summary of the issue here

I have the following JSX inside of a <Radio/>:

<object
      className={cn("aspect-square size-10 rounded object-cover", className)}
      width={Number(pass.avatar.width)}
      height={Number(pass.avatar.height)}
      data={pass.avatar.url}
      type={type}
      aria-label={pass.name}
      ref={forwardedRef}
      tabIndex={-1}
    >
      <img
        className={cn("aspect-square size-10 rounded object-cover", className)}
        width={Number(pass.avatar.width)}
        height={Number(pass.avatar.height)}
        src={pass.avatar.thumbnailUrl}
        alt={pass.name}
     />
</object>

This markup is used to implement images with a fallback as described here: https://blog.sentry.io/fallbacks-for-http-404-images-in-html-and-javascript/

When navigating with the keyboard across different <Radio/>'s inside of a <RadioGroup/>, the <object/>'s inside of each <Radio/> gets focused on rather than the the currently selected <Radio/>'s preceding/proceeding <Radio/>.

This is because <object/>'s are marked as focusable. I don't believe there is any way to mark <object/>'s as not focusable based on the code in getFocusableTreeWalker(). If there is a way to explicitly mark JSX elements as not focusable however, that would solve the issue.

Based on what I read from getFocusableTreeWalker() as well, setting a tabIndex of -1 to an element only marks the element as not tabbable which thus still causes this bug.

πŸ€” Expected Behavior?

That keyboard navigation across <Radio/>'s with <object/>'s representing images with fallbacks works.

😯 Current Behavior

Proceeding/preceding <Radio/>'s upon keyboard navigation do not properly get focused.

πŸ’ Possible Solution

No response

πŸ”¦ Context

No response

πŸ–₯️ Steps to Reproduce

https://codesandbox.io/p/devbox/dvzc8v?migrateFrom=fprxtz&layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clx17srml00063b6stwmynu23%2522%252C%2522sizes%2522%253A%255B71.9675925925926%252C28.032407407407405%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clx17srml00023b6sbvdb6yu9%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clx17srml00043b6savd7k50x%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clx17srml00053b6s7ajihjnl%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B62.44345812627669%252C37.55654187372331%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clx17srml00023b6sbvdb6yu9%2522%253A%257B%2522id%2522%253A%2522clx17srml00023b6sbvdb6yu9%2522%252C%2522activeTabId%2522%253A%2522clx17tju200aa3b6s3fjyiwyz%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clx17srml00013b6sq97i4c1l%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Fpackage.json%2522%257D%252C%257B%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Fpostcss.config.js%2522%252C%2522initialSelections%2522%253A%255B%257B%2522startLineNumber%2522%253A1%252C%2522endLineNumber%2522%253A1%252C%2522startColumn%2522%253Anull%252C%2522endColumn%2522%253Anull%257D%255D%252C%2522id%2522%253A%2522clx17tju200aa3b6s3fjyiwyz%2522%252C%2522mode%2522%253A%2522temporary%2522%257D%255D%257D%252C%2522clx17srml00053b6s7ajihjnl%2522%253A%257B%2522id%2522%253A%2522clx17srml00053b6s7ajihjnl%2522%252C%2522tabs%2522%253A%255B%257B%2522type%2522%253A%2522TASK_PORT%2522%252C%2522port%2522%253A5173%252C%2522taskId%2522%253A%2522Development%2522%252C%2522id%2522%253A%2522clx17uih600he3b6ss9gkyblg%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522path%2522%253A%2522%252F%2522%257D%255D%252C%2522activeTabId%2522%253A%2522clx17uih600he3b6ss9gkyblg%2522%257D%252C%2522clx17srml00043b6savd7k50x%2522%253A%257B%2522id%2522%253A%2522clx17srml00043b6savd7k50x%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clx17srml00033b6sfvxoysu7%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TERMINAL%2522%252C%2522shellId%2522%253A%2522clx17ss3a000adaf85mhlgjcm%2522%257D%252C%257B%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522Development%2522%252C%2522id%2522%253A%2522clx17tdx2007e3b6sl4ffimih%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%255D%252C%2522activeTabId%2522%253A%2522clx17tdx2007e3b6sl4ffimih%2522%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Atrue%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D

Version

react-aria 3.33.1, react-aria-components 1.2.1

What browsers are you seeing the problem on?

Chrome

If other, please specify.

No response

What operating system are you using?

Mac

🧒 Your Company/Team

Only1

πŸ•· Tracking Issue

No response

snowystinger commented 5 months ago

Thanks for reporting that. I'm not sure why we explicitly allow for object to be focusable according to our tree walker. They don't appear to be focusable on their own in plain HTML

https://jsfiddle.net/v0c2kn5L/8/ It looks like they aren't focusable by keyboards even if a tab index is specified. They can be programmatically focused, but would need a tabIndex >= 0

I'll see if anyone on the team remembers more about this line https://github.com/adobe/react-spectrum/blob/0c17289de18041e6f8b99df26a5a3ca922cc5145/packages/%40react-aria/focus/src/FocusScope.tsx#L279

lithdew commented 5 months ago

It looks like what needs to be done to check if an element is focusable/tabbable is a lot more complex than it seems vs. the checks in getFocusableTreeWalker(): https://allyjs.io/data-tables/focusable.html#object-element

In the specific case of RadioGroup though, I think the code could be amended to solely walk the tree for preceeding/proceeding <Radio/> items based on my cursory read of the ARIA RadioGroup spec.

https://www.w3.org/WAI/ARIA/apg/patterns/radio/

For Radio Groups Not Contained in a Toolbar

  • Right Arrow and Down Arrow: move focus to the next radio button in the group, uncheck the previously focused button, and check the newly focused button. If focus is on the last button, focus moves to the first button.
  • Left Arrow and Up Arrow: move focus to the previous radio button in the group, uncheck the previously focused button, and check the newly focused button. If focus is on the first button, focus moves to the last button.

For Radio Group Contained in a Toolbar

  • Right Arrow: When focus is on a radio button and that radio button is not the last radio button in the radio group, moves focus to the next radio button. When focus is on the last radio button in the radio group and that radio button is not the last element in the toolbar, moves focus to the next element in the toolbar. When focus is on the last radio button in the radio group and that radio button is also the last element in the toolbar, moves focus to the first element in the toolbar.
  • Left Arrow: When focus is on a radio button and that radio button is not the first radio button in the radio group, moves focus to the previous radio button. When focus is on the first radio button in the radio group and that radio button is not the first element in the toolbar, moves focus to the previous element in the toolbar. When focus is on the first radio button in the radio group and that radio button is also the first element in the toolbar, moves focus to the last element in the toolbar.
  • Down Arrow (optional): Moves focus to the next radio button in the radio group. If focus is on the last radio button in the radio group, moves focus to the first radio button in the group.
  • Up Arrow (optional): Moves focus to the previous radio button in the radio group. If focus is on the first radio button in the radio group, moves focus to the last radio button in the group.
majornista commented 5 months ago

Thanks for reporting that. I'm not sure why we explicitly allow for object to be focusable according to our tree walker. They don't appear to be focusable on their own in plain HTML

https://jsfiddle.net/v0c2kn5L/8/ It looks like they aren't focusable by keyboards even if a tab index is specified. They can be programmatically focused, but would need a tabIndex >= 0

I'll see if anyone on the team remembers more about this line

https://github.com/adobe/react-spectrum/blob/0c17289de18041e6f8b99df26a5a3ca922cc5145/packages/%40react-aria/focus/src/FocusScope.tsx#L279

It was more relevant in the age of plugins, but object tags are focusable depending on the element being rendered.