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: improve focus selector perf with focus-visible and focus-within polyfill #24154

Merged
merged 15 commits into from
Aug 3, 2022

Conversation

ling1726
Copy link
Member

@ling1726 ling1726 commented Jul 30, 2022

Adds a polyfill for :focus-visible that is built on top of Keyborg
that serves as a replacement for useKeyboardNavAttribute

It should be possible to improve even further but I can't find a way to do chained class selectors in Griffel microsoft/griffel#178

Below are results of the SelectorStats trace from edge://tracing which measures the time spent per-selector during the matching phase. The comparison was done by comparing selector stats for .fui-focus-visible (polyfill) vs the current [data-keyboard-nav] selector.

(Lower numbers are better)

[data-keyboard-nav] on provider root

time elapsed fast reject count match attempts match count
2098 1120 23511 416

:focus-visible polyfill

Changes to :focus-visible will not trigger :focus-within

time elapsed fast reject count match attempts match count
377 0 1484 364

Steps to repro:

  • build @fluentui/ssr-tests-v9
  • serve the ssr test app
  • Click on the first focusable element (Accordion item)
  • Tab 3 times

:focus-within polyfill

Changes to :focus-within will trigger :focus-visible

time elapsed fast reject count match attempts match count
69 0 216 156

Steps to repro:

  • build @fluentui/ssr-tests-v9
  • serve the ssr test app
  • Click on the first Switch
  • Tab 3 times

:focus-visible Improvement in %

time elapsed fast reject count match attempts match count
82% Inf% 93.6% 12.5%

:focus-within Improvement in %

time elapsed fast reject count match attempts match count
96.7% Inf% 99% 62.5%

Addresses #24183

Adds a polyfill for `:focus-visible` that is built on top of Keyborg
that serves as a replacement for `useKeyboardNavAttribute`
@fabricteam
Copy link
Collaborator

fabricteam commented Jul 30, 2022

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-accordion
Accordion (including children components)
79.023 kB
24.029 kB
79.438 kB
24.068 kB
415 B
39 B
react-alert
Alert
82.173 kB
20.519 kB
82.756 kB
20.559 kB
583 B
40 B
react-avatar
AvatarGroup
138.314 kB
40.998 kB
139.268 kB
41.173 kB
954 B
175 B
react-button
Button
35.766 kB
9.51 kB
36.349 kB
9.557 kB
583 B
47 B
react-button
CompoundButton
42.839 kB
10.74 kB
43.422 kB
10.79 kB
583 B
50 B
react-button
MenuButton
38.384 kB
10.375 kB
38.967 kB
10.434 kB
583 B
59 B
react-button
SplitButton
45.717 kB
11.718 kB
46.459 kB
11.805 kB
742 B
87 B
react-button
ToggleButton
50.991 kB
10.931 kB
51.865 kB
10.984 kB
874 B
53 B
react-card
Card - All
67.014 kB
19.206 kB
67.42 kB
19.249 kB
406 B
43 B
react-card
Card
62.696 kB
18.126 kB
63.102 kB
18.167 kB
406 B
41 B
react-combobox
Combobox (including child components)
74.425 kB
23.999 kB
75.081 kB
24.145 kB
656 B
146 B
react-combobox
Dropdown (including child components)
73.938 kB
23.991 kB
74.594 kB
24.134 kB
656 B
143 B
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
190.59 kB
52.497 kB
192.652 kB
52.771 kB
2.062 kB
274 B
react-components
react-components: FluentProvider & webLightTheme
32.036 kB
10.577 kB
32.688 kB
10.736 kB
652 B
159 B
react-dialog
Dialog (including children components)
83.94 kB
25.082 kB
85.002 kB
25.276 kB
1.062 kB
194 B
react-link
Link
12.082 kB
4.88 kB
12.197 kB
4.912 kB
115 B
32 B
react-menu
Menu (including children components)
117.801 kB
35.808 kB
118.866 kB
36.006 kB
1.065 kB
198 B
react-menu
Menu (including selectable components)
121 kB
36.288 kB
122.065 kB
36.499 kB
1.065 kB
211 B
react-popover
Popover
105.483 kB
32.077 kB
106.133 kB
32.244 kB
650 B
167 B
react-portal
Portal
9.844 kB
3.677 kB
10.49 kB
3.845 kB
646 B
168 B
react-provider
FluentProvider
14.913 kB
5.658 kB
15.565 kB
5.818 kB
652 B
160 B
react-radio
Radio
30.481 kB
10.406 kB
36.329 kB
12.024 kB
5.848 kB
1.618 kB
react-slider
Slider
26.136 kB
8.387 kB
31.988 kB
10.019 kB
5.852 kB
1.632 kB
react-switch
Switch
26.951 kB
8.688 kB
32.781 kB
10.348 kB
5.83 kB
1.66 kB
react-tooltip
Tooltip
44.853 kB
15.406 kB
45.509 kB
15.537 kB
656 B
131 B
Unchanged fixtures
Package & Exports Size (minified/GZIP)
react-avatar
Avatar
48.533 kB
13.78 kB
react-avatar
AvatarGroupItem
68.248 kB
19.109 kB
react-card
CardFooter
8.461 kB
3.555 kB
react-card
CardHeader
9.504 kB
3.896 kB
react-card
CardPreview
8.562 kB
3.61 kB
react-radio
RadioGroup
14.319 kB
5.711 kB
🤖 This report was generated against f11b365e857e7f01879f0a597c2496821a844ada

@size-auditor
Copy link

size-auditor bot commented Jul 30, 2022

Asset size changes

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

Baseline commit: f11b365e857e7f01879f0a597c2496821a844ada (build)

@ling1726 ling1726 changed the title feat: focus-visible polyfill feat: focus-visible and focus-within polyfill Jul 31, 2022
@fabricteam
Copy link
Collaborator

fabricteam commented Jul 31, 2022

Perf Analysis (@fluentui/react-components)

No significant results to display.

All results

Scenario Render type Master Ticks PR Ticks Iterations Status
Avatar mount 1288 1274 5000
Button mount 923 920 5000
FluentProvider mount 1494 1485 5000
FluentProviderWithTheme mount 564 576 10
FluentProviderWithTheme virtual-rerender 537 539 10
FluentProviderWithTheme virtual-rerender-with-unmount 570 571 10
MakeStyles mount 1978 1983 50000
SpinButton mount 2385 2425 5000

@codesandbox-ci
Copy link

codesandbox-ci bot commented Aug 1, 2022

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 d5aae20:

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

@ling1726 ling1726 changed the title feat: focus-visible and focus-within polyfill feat: improve focus selector perf with focus-visible and focus-within polyfill Aug 1, 2022
@ling1726 ling1726 marked this pull request as ready for review August 1, 2022 09:00
@layershifter layershifter self-requested a review August 1, 2022 10:05
Copy link
Contributor

@jspurlin jspurlin left a comment

Choose a reason for hiding this comment

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

:shipit:

@jspurlin
Copy link
Contributor

jspurlin commented Aug 1, 2022

Is there any special focus-within styling needed for the splitButton where the whole control gets styling when focus is on either the primary or menuButton portion of the splitButton?

@@ -69,7 +70,10 @@ export const useCheckbox_unstable = (props: CheckboxProps, ref: React.Ref<HTMLIn
},
root: resolveShorthand(props.root, {
required: true,
defaultProps: nativeProps.root,
defaultProps: {
ref: useFocusWithin<HTMLSpanElement>(),
Copy link
Member

Choose a reason for hiding this comment

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

The story with createCustomFocusIndicatorStyle() & useFocusWithin() looks a bit dangerous for me as the dependency on useFocusWithin() is implicit. Do you have something on your mind to make it explicit?

Copy link
Member Author

Choose a reason for hiding this comment

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

tbh I am not happy at all that this focus-within requires an extra hook, but globally there is no way to know what parent of the focused element should have this class.

The 'safety' mechanism currently is that createCustomFocusIndicatorStyles needs explicit focus-within selector to be used. The styles make sure that there is no any focus styles are applied unless useFocusWithin is called.

If anyone uses :focus-within directly (I think inputs do it) then they should know that there is no keyboard/mouse heuristic there.

If you have any proposals, I'd be happy to hear them, I'm not 100% happy with the current situation either

Copy link
Member

Choose a reason for hiding this comment

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

I don't have a good proposal, the one that I have will be a regression to the previous behavior in terms of performance...

@layershifter
Copy link
Member

layershifter commented Aug 2, 2022

<Boring part> Currently it's more ponyfill rather than a real polyfill. </Boring part>

I think that it's a step forward as it improves performance due less complicated selectors, but it's not making us closer to native :focus-visible usage and the best possible performance 😥

I suggest to explore how we could use native :focus-visible and fallback to our code only when the platform does not support it. For example, we could try to use @supports queries or CSS.supports().

@ling1726
Copy link
Member Author

ling1726 commented Aug 2, 2022

I suggest to explore how we could use native :focus-visible and fallback to our code only when the platform does not support it. For example, we could try to use @supports queries or CSS.supports().

I plan to introduce native support in the next PR since we can't do that unless we first have the fallback ponyfill 🏇

My idea is to bake CSS.supports directly into the ponyfill, so that it won't be applied if the platform supports :focus-visible

@ling1726 ling1726 requested a review from spmonahan August 2, 2022 11:32
@ling1726 ling1726 requested a review from layershifter August 2, 2022 11:39
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.

9 participants