From f976796b35d3ab09958e6a6dc04dc359469d70fd Mon Sep 17 00:00:00 2001 From: Vaadin Bot Date: Fri, 5 Jul 2024 11:33:16 +0200 Subject: [PATCH] fix: blur active element on step buttons touchend (#7512) (#7514) Co-authored-by: Serhii Kulykov --- .../src/vaadin-number-field-mixin.js | 14 ++++ .../test/value-control-buttons.common.js | 74 +++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/packages/number-field/src/vaadin-number-field-mixin.js b/packages/number-field/src/vaadin-number-field-mixin.js index 7eeb622376..55296a0f74 100644 --- a/packages/number-field/src/vaadin-number-field-mixin.js +++ b/packages/number-field/src/vaadin-number-field-mixin.js @@ -3,6 +3,7 @@ * Copyright (c) 2021 - 2023 Vaadin Ltd. * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ */ +import { getDeepActiveElement } from '@vaadin/a11y-base/src/focus-utils.js'; import { InputController } from '@vaadin/field-base/src/input-controller.js'; import { InputFieldMixin } from '@vaadin/field-base/src/input-field-mixin.js'; import { LabelledInputController } from '@vaadin/field-base/src/labelled-input-controller.js'; @@ -194,6 +195,7 @@ export const NumberFieldMixin = (superClass) => // it means scrolling is in progress, therefore we shouldn't update field value. if (e.cancelable) { e.preventDefault(); + this.__blurActiveElement(); this._decreaseValue(); } } @@ -204,10 +206,22 @@ export const NumberFieldMixin = (superClass) => // it means scrolling is in progress, therefore we shouldn't update field value. if (e.cancelable) { e.preventDefault(); + this.__blurActiveElement(); this._increaseValue(); } } + /** @private */ + __blurActiveElement() { + // If another element is focused, blur it on step button touch to hide + // the mobile keyboard that might still be open for the other element. + // See https://github.com/vaadin/web-components/issues/7494 + const activeElement = getDeepActiveElement(); + if (activeElement && activeElement !== this.inputElement) { + activeElement.blur(); + } + } + /** @protected */ _onDecreaseButtonClick() { this._decreaseValue(); diff --git a/packages/number-field/test/value-control-buttons.common.js b/packages/number-field/test/value-control-buttons.common.js index ffb1d83081..7efbe8a7d0 100644 --- a/packages/number-field/test/value-control-buttons.common.js +++ b/packages/number-field/test/value-control-buttons.common.js @@ -656,3 +656,77 @@ describe('value control buttons', () => { }); }); }); + +describe('multiple fields', () => { + let container, fields; + + beforeEach(async () => { + container = fixtureSync(` +
+ + +
+ `); + await nextRender(); + fields = [...container.children]; + }); + + ['increase', 'decrease'].forEach((type) => { + describe(`${type} button`, () => { + let button; + + beforeEach(() => { + button = fields[1].shadowRoot.querySelector(`[part=${type}-button]`); + }); + + it(`should blur the other field on ${type} button touchend`, () => { + const input = fields[0].inputElement; + input.focus(); + + const spy = sinon.spy(input, 'blur'); + const e = new CustomEvent('touchend', { cancelable: true }); + button.dispatchEvent(e); + + expect(spy).to.be.calledOnce; + }); + + it(`should not blur the other field on ${type} button touchend if not cancelable`, () => { + const input = fields[0].inputElement; + input.focus(); + + const spy = sinon.spy(input, 'blur'); + const e = new CustomEvent('touchend', { cancelable: false }); + button.dispatchEvent(e); + + expect(spy).to.be.not.called; + }); + + it(`should not blur the field on its own ${type} button touchend`, () => { + const input = fields[1].inputElement; + input.focus(); + + const spy = sinon.spy(input, 'blur'); + const e = new CustomEvent('touchend', { cancelable: true }); + button.dispatchEvent(e); + + expect(spy).to.be.not.called; + }); + + it(`should not blur the field on its own ${type} button touchend when in shadow root`, () => { + // Move the field into shadow root to verify the deep active element logic + const inner = document.createElement('div'); + inner.attachShadow({ mode: 'open' }); + container.appendChild(inner); + inner.shadowRoot.appendChild(fields[1]); + + const input = fields[1].inputElement; + input.focus(); + + const e = new CustomEvent('touchend', { cancelable: true }); + button.dispatchEvent(e); + + expect(inner.shadowRoot.activeElement).to.be.equal(input); + }); + }); + }); +});