Skip to content

Prevent Tooltip from overriding any aria-label, etc. props on its child#21532

Merged
behowell merged 2 commits into
microsoft:masterfrom
behowell:tooltip-dont-override-aria-label
Feb 4, 2022
Merged

Prevent Tooltip from overriding any aria-label, etc. props on its child#21532
behowell merged 2 commits into
microsoft:masterfrom
behowell:tooltip-dont-override-aria-label

Conversation

@behowell

@behowell behowell commented Feb 2, 2022

Copy link
Copy Markdown
Contributor

Current Behavior

Tooltip currently overrides aria props set on their child component. For example, the Button ends up with aria-label="Foo" in this scenario:

<Tooltip content="Foo" relationship="label">
  <Button aria-label="Bar">...</Button>
</Tooltip>

New Behavior

Have tooltip check for the existence of aria-label/aria-labelledby before setting either prop. Likewise, check for aria-description/aria-describedby before setting a description.

Related Issue(s)

@codesandbox-ci

codesandbox-ci Bot commented Feb 2, 2022

Copy link
Copy Markdown

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit a84ca7b:

Sandbox Source
@fluentui/react 8 starter Configuration
@fluentui/react-components 9 starter Configuration

@fabricteam

Copy link
Copy Markdown
Collaborator

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-tooltip
Tooltip
42.008 kB
14.478 kB
42.189 kB
14.512 kB
181 B
34 B
Unchanged fixtures
Package & Exports Size (minified/GZIP)
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
165.834 kB
46.987 kB
react-components
react-components: FluentProvider & webLightTheme
32.489 kB
10.636 kB
🤖 This report was generated against a21bfaceecf201d0c984fa84704148a24f956901

@size-auditor

size-auditor Bot commented Feb 2, 2022

Copy link
Copy Markdown

Asset size changes

Size Auditor did not detect a change in bundle size for any component!

Baseline commit: a21bfaceecf201d0c984fa84704148a24f956901 (build)

@fabricteam

Copy link
Copy Markdown
Collaborator

Perf Analysis (@fluentui/react)

Scenario Render type Master Ticks PR Ticks Iterations Status
ContextualMenu mount 14598 7280 1000 Possible regression
All results

Scenario Render type Master Ticks PR Ticks Iterations Status
Avatar mount 808 838 5000
BaseButton mount 804 798 5000
Breadcrumb mount 2257 2297 1000
ButtonNext mount 449 439 5000
Checkbox mount 1307 1331 5000
CheckboxBase mount 1056 1075 5000
ChoiceGroup mount 4116 4043 5000
ComboBox mount 844 857 1000
CommandBar mount 8779 8813 1000
ContextualMenu mount 14598 7280 1000 Possible regression
DefaultButton mount 968 993 5000
DetailsRow mount 3153 3078 5000
DetailsRowFast mount 3224 3246 5000
DetailsRowNoStyles mount 2938 3046 5000
Dialog mount 2271 2249 1000
DocumentCardTitle mount 156 162 1000
Dropdown mount 2787 2668 5000
FluentProviderNext mount 1641 1670 5000
FluentProviderWithTheme mount 161 153 10
FluentProviderWithTheme virtual-rerender 87 98 10
FluentProviderWithTheme virtual-rerender-with-unmount 164 153 10
FocusTrapZone mount 1591 1606 5000
FocusZone mount 1566 1543 5000
IconButton mount 1521 1513 5000
Label mount 331 332 5000
Layer mount 2476 2576 5000
Link mount 413 430 5000
MakeStyles mount 1428 1335 50000
MenuButton mount 1221 1204 5000
MessageBar mount 1730 1733 5000
Nav mount 2796 2808 1000
OverflowSet mount 984 870 5000
Panel mount 2190 2023 1000
Persona mount 770 754 1000
Pivot mount 1247 1263 1000
PrimaryButton mount 1147 1028 5000
Rating mount 6433 6363 5000
SearchBox mount 1110 1074 5000
Shimmer mount 2167 2009 5000
Slider mount 1571 1628 5000
SpinButton mount 4257 4001 5000
Spinner mount 393 398 5000
SplitButton mount 2697 2557 5000
Stack mount 468 429 5000
StackWithIntrinsicChildren mount 1980 2007 5000
StackWithTextChildren mount 4398 4393 5000
SwatchColorPicker mount 9891 9532 5000
TagPicker mount 2241 2193 5000
TeachingBubble mount 11108 11223 5000
Text mount 395 398 5000
TextField mount 1228 1113 5000
ThemeProvider mount 1040 1046 5000
ThemeProvider virtual-rerender 498 547 5000
ThemeProvider virtual-rerender-with-unmount 1631 1549 5000
Toggle mount 727 729 5000
buttonNative mount 130 145 5000

Perf Analysis (@fluentui/react-northstar)

⚠️ No perf measurements available

triggerProps['aria-labelledby'] = state.content.id;
// Always render the tooltip even if hidden, so that aria-labelledby refers to a valid element
state.shouldRenderTooltip = true;
const hasLabel = child?.props && ('aria-label' in child.props || 'aria-labelledby' in child.props);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IMO this solution is error prone and requires a lot of responsibility from library developers as we will need to check existence of every prop that we are trying to apply. We will also need to test these cases to ensure that it works. For example, this PR already adds 3 additional tests.

Taking into account that it affects all components that use trigger pattern (Popup, Menu) I still think that it's better to update applyTriggerPropsToElement() to handle it inside .cloneElement():

React.cloneElement(children, {
  ...nonMergeableProps, // aria, id, etc.
  ...children.props,
  ...mergeableProps // callbacks & ref
});

We also cannot rely on reading props in examples like this as we don't really traverse React tree (which is much more complex). In that example MenuTrigger will not be able to read props of Button.

And even if it works properly in the snippet below it's a side-effect.

const CallButton = React.forwardRef((props, ref) => {
  return (
    <Tooltip content="bar" relationship="label">
      <Button
        {...props}
        ref={ref}
        // works as Tooltip reads props
        aria-label="foo"
        // works as we override `props` passed by "Menu"
        aria-haspopup="false"
      >
        Toggle menu
      </Button>
    </Tooltip>
  );
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Discussed in a meeting. In summary, the issues @layershifter described are nuances of prop merging in general. These trigger props are just one instance of prop merging in our library, but we do it in many places, in every component (for native props on component's root, as well as any slot).

It's probably worthwhile creating a prop merging helper that can make sure it's done safely in all cases. However, it seems out of scope to me to include that in applyTriggerPropsToChildren, which is just a wrapper around React.cloneElement.

Comment thread packages/react-tooltip/src/components/Tooltip/useTooltip.tsx
@behowell behowell requested a review from spmonahan February 3, 2022 01:10
@behowell behowell merged commit 2aacc83 into microsoft:master Feb 4, 2022
@behowell behowell deleted the tooltip-dont-override-aria-label branch February 4, 2022 02:10
@behowell behowell mentioned this pull request Feb 7, 2022
14 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants