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 1 commit
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
25 changes: 12 additions & 13 deletions packages/password-field/src/vaadin-password-field.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ 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 = '';
}

Expand Down Expand Up @@ -158,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 Down Expand Up @@ -222,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 @@ -257,20 +263,13 @@ export class PasswordField extends TextField {
}

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

// By preventing `focusout` event, we also suppress related `change` event.
// Detect if that was the case and if so, dispatch `change` event manually.
if (this.__lastChange !== this.inputElement.value) {
this.__lastChange = this.inputElement.value;

this.validate();

this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
}
// Set the flag to dispatch `change` event manually when field loses focus.
this.__pendingChange = true;
vursen marked this conversation as resolved.
Show resolved Hide resolved

// Focus the input to avoid problem with password still visible
// when user clicks the reveal button and then clicks outside.
Expand Down
35 changes: 10 additions & 25 deletions packages/password-field/test/password-field.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,45 +46,40 @@ 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 reveal button touchend', () => {
it('should dispatch change event on focusout after changing the value', () => {
const spy = sinon.spy();
passwordField.addEventListener('change', spy);

input.value = 'test';

makeSoloTouchEvent('touchend', null, revealButton);
focusout(input);

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

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

makeSoloTouchEvent('touchend', null, revealButton);
focusout(input);

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

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

Expand All @@ -93,21 +88,11 @@ describe('password-field', () => {

spy.resetHistory();

makeSoloTouchEvent('touchend', null, revealButton);
focusout(input);

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

it('should validate on prevented reveal button touchend', () => {
const spy = sinon.spy(passwordField, 'validate');

input.value = 'test';

makeSoloTouchEvent('touchend', null, revealButton);

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

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