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

fix: ensure change event is fired on blur after prevented mousedown #6150

Merged
merged 4 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions packages/password-field/src/vaadin-password-field.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ export class PasswordField extends TextField {
super();
this._setType('password');
this.__boundRevealButtonClick = this._onRevealButtonClick.bind(this);
this.__boundRevealButtonTouchend = this._onRevealButtonTouchend.bind(this);
this.__boundRevealButtonMouseDown = this._onRevealButtonMouseDown.bind(this);
this.__lastChange = '';
}

/** @protected */
Expand Down Expand Up @@ -157,7 +158,7 @@ export class PasswordField extends TextField {
btn.disabled = this.disabled;

btn.addEventListener('click', this.__boundRevealButtonClick);
btn.addEventListener('touchend', this.__boundRevealButtonTouchend);
btn.addEventListener('mousedown', this.__boundRevealButtonMouseDown);
},
});
this.addController(this._revealButtonController);
Expand All @@ -172,6 +173,19 @@ export class PasswordField extends TextField {
}
}

/**
* Override an event listener inherited from `InputControlMixin`
* to store the value at the moment of the native `change` event.
* @param {Event} event
* @protected
* @override
*/
_onChange(event) {
super._onChange(event);

this.__lastChange = this.inputElement.value;
}

/**
* Override method inherited from `FocusMixin` to mark field as focused
* when focus moves to the reveal button using Shift Tab.
Expand Down Expand Up @@ -208,6 +222,12 @@ export class PasswordField extends TextField {

if (!focused) {
this._setPasswordVisible(false);

// Detect if `focusout` was prevented and if so, dispatch `change` event manually.
if (this.__lastChange !== this.inputElement.value) {
this.__lastChange = this.inputElement.value;
this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
}
} else {
const isButtonFocused = this.getRootNode().activeElement === this._revealNode;
// Remove focus-ring from the field when the reveal button gets focused
Expand Down Expand Up @@ -243,10 +263,10 @@ export class PasswordField extends TextField {
}

/** @private */
_onRevealButtonTouchend(e) {
// Cancel the following click event
_onRevealButtonMouseDown(e) {
// Cancel the following focusout event
e.preventDefault();
this._togglePasswordVisibility();

// Focus the input to avoid problem with password still visible
// when user clicks the reveal button and then clicks outside.
this.inputElement.focus();
Expand Down
49 changes: 39 additions & 10 deletions packages/password-field/test/password-field.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from '@esm-bundle/chai';
import { fixtureSync, focusout, makeSoloTouchEvent, mousedown, nextRender } from '@vaadin/testing-helpers';
import { fire, fixtureSync, focusout, makeSoloTouchEvent, mousedown, nextRender } from '@vaadin/testing-helpers';
import { sendKeys } from '@web/test-runner-commands';
import sinon from 'sinon';
import '../src/vaadin-password-field.js';
Expand Down Expand Up @@ -46,24 +46,53 @@ describe('password-field', () => {
expect(input.type).to.equal('text');
});

it('should prevent touchend event on reveal button', () => {
const event1 = makeSoloTouchEvent('touchend', null, revealButton);
expect(event1.defaultPrevented).to.be.true;
expect(input.type).to.equal('text');

const event2 = makeSoloTouchEvent('touchend', null, revealButton);
expect(event2.defaultPrevented).to.be.true;
expect(input.type).to.equal('password');
it('should prevent mousedown event on reveal button', () => {
const event = fire(revealButton, 'mousedown');
expect(event.defaultPrevented).to.be.true;
});

it('should focus the input on reveal button touchend', () => {
web-padawan marked this conversation as resolved.
Show resolved Hide resolved
const spy = sinon.spy(input, 'focus');

makeSoloTouchEvent('touchend', null, revealButton);
fire(revealButton, 'mousedown');

expect(spy.calledOnce).to.be.true;
});

it('should dispatch change event on focusout after changing the value', () => {
const spy = sinon.spy();
passwordField.addEventListener('change', spy);

input.value = 'test';

focusout(input);

expect(spy.calledOnce).to.be.true;
});

it('should not dispatch change event on focusout if value is the same', () => {
const spy = sinon.spy();
passwordField.addEventListener('change', spy);

focusout(input);

expect(spy.called).to.be.false;
});

it('should not dispatch change event on focusout after native change', () => {
const spy = sinon.spy();
passwordField.addEventListener('change', spy);

input.value = 'test';
fire(input, 'change');

spy.resetHistory();

focusout(input);

expect(spy.called).to.be.false;
});

it('should toggle aria-pressed attribute on reveal button click', () => {
revealButton.click();
expect(revealButton.getAttribute('aria-pressed')).to.equal('true');
Expand Down