Skip to content

Commit

Permalink
feat: enable 1-click switch between pickers when open (#6785)
Browse files Browse the repository at this point in the history
  • Loading branch information
vursen authored Aug 12, 2024
1 parent b775965 commit e08a6e5
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 4 deletions.
25 changes: 23 additions & 2 deletions packages/date-picker/src/vaadin-date-picker-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,12 @@ export const DatePickerMixin = (subclass) =>
}
});

content.addEventListener('focusout', (event) => {
if (this._shouldRemoveFocus(event)) {
this._setFocused(false);
}
});

// Two-way data binding for `focusedDate` property
content.addEventListener('focused-date-changed', (e) => {
this._focusedDate = e.detail.value;
Expand Down Expand Up @@ -650,12 +656,27 @@ export const DatePickerMixin = (subclass) =>
* - when moving focus to the overlay content,
* - when closing on date click / outside click.
*
* @param {!FocusEvent} _event
* @param {FocusEvent} event
* @return {boolean}
* @protected
* @override
*/
_shouldRemoveFocus(_event) {
_shouldRemoveFocus(event) {
// Remove the focused state when clicking outside on a focusable element that is deliberately
// made targetable with pointer-events: auto, such as the time-picker in the date-time-picker.
// In this scenario, focus will move straight to that element and the closing overlay won't
// attempt to restore focus to the input.
const { relatedTarget } = event;
if (
this.opened &&
relatedTarget !== null &&
relatedTarget !== document.body &&
!this.contains(relatedTarget) &&
!this._overlayContent.contains(relatedTarget)
) {
return true;
}

return !this.opened;
}

Expand Down
9 changes: 9 additions & 0 deletions packages/date-time-picker/src/vaadin-date-time-picker.js
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ class DateTimePicker extends FieldMixin(DisabledMixin(FocusMixin(ThemableMixin(E

this.__changeEventHandler = this.__changeEventHandler.bind(this);
this.__valueChangedEventHandler = this.__valueChangedEventHandler.bind(this);
this.__openedChangedEventHandler = this.__openedChangedEventHandler.bind(this);
}

/** @private */
Expand Down Expand Up @@ -521,16 +522,24 @@ class DateTimePicker extends FieldMixin(DisabledMixin(FocusMixin(ThemableMixin(E
this.__dispatchChangeForValue = undefined;
}

/** @private */
__openedChangedEventHandler() {
const opened = this.__datePicker.opened || this.__timePicker.opened;
this.style.pointerEvents = opened ? 'auto' : '';
}

/** @private */
__addInputListeners(node) {
node.addEventListener('change', this.__changeEventHandler);
node.addEventListener('value-changed', this.__valueChangedEventHandler);
node.addEventListener('opened-changed', this.__openedChangedEventHandler);
}

/** @private */
__removeInputListeners(node) {
node.removeEventListener('change', this.__changeEventHandler);
node.removeEventListener('value-changed', this.__valueChangedEventHandler);
node.removeEventListener('opened-changed', this.__openedChangedEventHandler);
}

/** @private */
Expand Down
106 changes: 104 additions & 2 deletions packages/date-time-picker/test/basic.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { expect } from '@vaadin/chai-plugins';
import { aTimeout, fixtureSync, focusin, focusout, nextFrame, nextRender } from '@vaadin/testing-helpers';
import { aTimeout, fixtureSync, focusin, focusout, nextFrame, nextRender, outsideClick } from '@vaadin/testing-helpers';
import { sendKeys } from '@web/test-runner-commands';
import sinon from 'sinon';
import '../vaadin-date-time-picker.js';
import '../src/vaadin-date-time-picker.js';
import { changeInputValue } from './helpers.js';

const fixtures = {
Expand Down Expand Up @@ -102,6 +103,52 @@ describe('Basic features', () => {
});
});

describe('pointer-events', () => {
it('should not have by default', () => {
expect(dateTimePicker.style.pointerEvents).to.be.empty;
});

it('should set to `auto` when opening date-picker', async () => {
datePicker.click();
await nextRender();
expect(dateTimePicker.style.pointerEvents).to.equal('auto');
});

it('should remove when closing date-picker', async () => {
datePicker.click();
await nextRender();
outsideClick();
expect(dateTimePicker.style.pointerEvents).to.be.empty;
});

it('should set to `auto` when opening time-picker', async () => {
timePicker.click();
await nextRender();
expect(dateTimePicker.style.pointerEvents).to.equal('auto');
});

it('should remove when closing time-picker', async () => {
timePicker.click();
await nextRender();
outsideClick();
expect(dateTimePicker.style.pointerEvents).to.be.empty;
});

it('should keep `auto` when switching between pickers', async () => {
datePicker.click();
await nextRender();
expect(dateTimePicker.style.pointerEvents).to.equal('auto');

timePicker.click();
await nextRender();
expect(dateTimePicker.style.pointerEvents).to.equal('auto');

datePicker.click();
await nextRender();
expect(dateTimePicker.style.pointerEvents).to.equal('auto');
});
});

describe('focused', () => {
it('should set focused attribute on date-picker focusin', () => {
focusin(datePicker);
Expand Down Expand Up @@ -140,6 +187,61 @@ describe('Basic features', () => {
});
});

describe('date-picker focused', () => {
it('should remove focused attribute on time-picker click', async () => {
datePicker.focus();
datePicker.click();
await nextRender();
expect(datePicker.hasAttribute('focused')).to.be.true;

timePicker.focus();
timePicker.click();
expect(datePicker.hasAttribute('focused')).to.be.false;
});

it('should remove focus-ring attribute on time-picker click', async () => {
// Focus the date-picker with the keyboard
await sendKeys({ press: 'Tab' });
// Open the overlay with the keyboard
await sendKeys({ press: 'ArrowDown' });
await nextRender();
expect(datePicker.hasAttribute('focus-ring')).to.be.true;

timePicker.focus();
timePicker.click();
expect(datePicker.hasAttribute('focus-ring')).to.be.false;
});
});

describe('time-picker focused', () => {
it('should remove focused attribute on date-picker click', async () => {
timePicker.focus();
timePicker.click();
await nextRender();
expect(timePicker.hasAttribute('focused')).to.be.true;

datePicker.focus();
datePicker.click();
await nextRender();
expect(timePicker.hasAttribute('focused')).to.be.false;
});

it('should remove focus-ring attribute on date-picker click', async () => {
// Focus the time-picker with the keyboard
await sendKeys({ press: 'Tab' });
await sendKeys({ press: 'Tab' });
// Open the overlay with the keyboard
await sendKeys({ press: 'ArrowDown' });
await nextRender();
expect(timePicker.hasAttribute('focus-ring')).to.be.true;

datePicker.focus();
datePicker.click();
await nextRender();
expect(timePicker.hasAttribute('focus-ring')).to.be.false;
});
});

describe('change event', () => {
let spy;

Expand Down

0 comments on commit e08a6e5

Please sign in to comment.