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

[datetime] fix(DateInput): make popover focusable via keyboard #4925

Merged
merged 7 commits into from
Sep 27, 2021

Conversation

michael-yx-wu
Copy link
Contributor

Related to #4812

Changes proposed in this pull request:

Pressing Tab when focused on a DateInput's text field will focus the popover content. Previously, pressing tab would focus the next keyboard-focusable element on the page. Since the popover is rendered with a portal by default, this would usually result in the popover closing.

Does not autofocus the popover to continue to allow users to manually type in a date string if they prefer not to use the popover.

Screenshot

Before:
2021-09-22 17 18 08

After:

2021-09-22 17 16 34

Worth noting that the element tab order in the DatePicker seems weird

  1. Left/right arrows
  2. Month dropdown
  3. Year dropdown

I would expect this order instead:

  1. Left arrow
  2. Month dropdown
  3. Year dropdown
  4. Right arrow

Copy link
Contributor

@adidahiya adidahiya left a comment

Choose a reason for hiding this comment

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

how do you get back to the input after tabbing into the popover? shift+tab doesn't seem to work:

2021-09-23 12 30 42

@michael-yx-wu
Copy link
Contributor Author

Shift+tab interactions should be fixed now:
2021-09-24 10 55 49

@blueprint-bot
Copy link

Close popover if shift+tab on input

Previews: documentation | landing | table

Copy link
Contributor

@adidahiya adidahiya left a comment

Choose a reason for hiding this comment

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

almost there

packages/datetime/src/dateInput.tsx Show resolved Hide resolved
packages/datetime/src/dateInput.tsx Show resolved Hide resolved
packages/datetime/src/dateInput.tsx Outdated Show resolved Hide resolved
Since the popover is usually open in a portal which makes it one of the
last elements in the DOM, when closing the popover with tab or
shift+tab, focus is likely to go to the browser address bar or an
element at the bottom of the page, respectively.

This change adds invisible divs to the beginning and end of the popover
that, when focused, return focus to the input and close the popover. We
cannot use the existing Overlay#shouldReturnFocusOnClose prop because it
would reopen the popover.
private getKeyboardFocusableElements = (): HTMLElement[] => {
const elements: HTMLElement[] = Array.from(
this.popoverContentElement?.querySelectorAll(
"button:not([disabled]),input,[tabindex]:not([tabindex='-1'])",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a subset of the selector used in Overlay. I wonder if it's worth sharing that in the future

@michael-yx-wu
Copy link
Contributor Author

Makes sense. I'm now keeping the popover open when switching focus to the input. Also fixed the issue of tabbing in the popover focusing the browser address bar.

2021-09-24 12 55 42

@blueprint-bot
Copy link

Focus input when closing DateInput popover

Previews: documentation | landing | table

@michael-yx-wu
Copy link
Contributor Author

Will fix tests

Comment on lines +95 to +99
// Skipping because simulate just invokes the function passed to React's "on<EventName>" prop
// and doesn't actually simulate anything. Properly testing would require running with an actual
// browser and focusing specific elements via the DOM API. This would require changing the Karma
// config to run with Chrome instead of ChromeHeadless.
it.skip("Popover closes when tabbing on first day of the month", () => {
Copy link
Contributor Author

@michael-yx-wu michael-yx-wu Sep 24, 2021

Choose a reason for hiding this comment

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

I do have a working local version but could only get it to work by switching the Karma browser from ChromeHeadless to Chrome. The problem with simulate is that there's no way to get the focusin event handler functions to run because React doesn't provide an onFocusIn prop, only onFocus. If I use the DOM API instead and call Element#focus(), the browser will fire a focusin event on my behalf. I'm not sure if that's an anti-pattern for the unit tests.

Copy link
Contributor

Choose a reason for hiding this comment

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

ok, this is fine to skip for now. testing focus behavior is tricky in these test suites

@blueprint-bot
Copy link

Skip failing test

Previews: documentation | landing | table

Comment on lines +95 to +99
// Skipping because simulate just invokes the function passed to React's "on<EventName>" prop
// and doesn't actually simulate anything. Properly testing would require running with an actual
// browser and focusing specific elements via the DOM API. This would require changing the Karma
// config to run with Chrome instead of ChromeHeadless.
it.skip("Popover closes when tabbing on first day of the month", () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

ok, this is fine to skip for now. testing focus behavior is tricky in these test suites

@@ -301,6 +305,8 @@ export class DateInput extends AbstractPureComponent2<DateInputProps, IDateInput
const { popoverProps = {} } = this.props;
popoverProps.onClose?.(e);
this.setState({ isOpen: false });
this.startFocusBoundaryElement?.removeEventListener("focusin", this.handleStartFocusBoundaryFocusIn);
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we need to use this ref approach and manually bind focusin event handlers? why can’t it be declarative like normal React code <div onFocusIn={this.handleEndFocusBoundaryFocusIn} /> ?

Copy link
Contributor Author

@michael-yx-wu michael-yx-wu Sep 26, 2021

Choose a reason for hiding this comment

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

React doesn't support onFocusIn, but it looks like their onFocus prop listens to the focusin event under the hood (facebook/react#6410 (comment)). I switched to this approach and everything still works as expected.

@blueprint-bot
Copy link

Use focus prop and stop manually adding event listener

Previews: documentation | landing | table

@adidahiya adidahiya changed the title Make DateInput's popover keyboard-focusable [datetime] fix(DateInput): make popover focusable via keyboard Sep 27, 2021
@adidahiya adidahiya merged commit e148da2 into develop Sep 27, 2021
@adidahiya adidahiya deleted the mw/dateinput-cal-focus branch September 27, 2021 15:45
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.

3 participants