Skip to content

Commit 0089e08

Browse files
authored
fix: select combo box input text on next click after dropdown closed (#11324)
1 parent 9a251b7 commit 0089e08

File tree

2 files changed

+164
-1
lines changed

2 files changed

+164
-1
lines changed

packages/combo-box/src/vaadin-combo-box-base-mixin.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,13 @@ export const ComboBoxBaseMixin = (superClass) =>
314314
}
315315
}
316316
} else {
317+
if (this.autoselect) {
318+
// When the dropdown closes, the input remains focused. Mark that the next
319+
// host click should re-trigger autoselect, since the normal focus event
320+
// won't fire again.
321+
this.__autoselectPending = true;
322+
}
323+
317324
this._onClosed();
318325
}
319326

@@ -387,6 +394,21 @@ export const ComboBoxBaseMixin = (superClass) =>
387394

388395
/** @private */
389396
_onClick(event) {
397+
// Select the input text on click when autoselect is enabled and a pending
398+
// autoselect was set by a dropdown close. This handles the case where the
399+
// overlay mousedown listener prevents blur on outside click, so the input
400+
// never loses focus and subsequent clicks don't trigger the focus event
401+
// where autoselect is normally handled.
402+
if (this.autoselect && this.inputElement && this.__autoselectPending) {
403+
// Skip if the user has already made a partial text selection
404+
// (e.g. by click-dragging).
405+
const isTextManuallySelected = this.inputElement.selectionStart !== this.inputElement.selectionEnd;
406+
if (!isTextManuallySelected) {
407+
this.inputElement.select();
408+
}
409+
}
410+
this.__autoselectPending = false;
411+
390412
if (this._isClearButton(event)) {
391413
this._onClearButtonClick(event);
392414
} else if (event.composedPath().includes(this._toggleElement)) {
@@ -716,6 +738,10 @@ export const ComboBoxBaseMixin = (superClass) =>
716738
_setFocused(focused) {
717739
super._setFocused(focused);
718740

741+
if (!focused) {
742+
this.__autoselectPending = false;
743+
}
744+
719745
if (!focused && !this.readonly && !this._closeOnBlurIsPrevented) {
720746
this._handleFocusOut();
721747
}

packages/combo-box/test/basic.test.js

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect } from '@vaadin/chai-plugins';
2-
import { fixtureSync, nextRender, outsideClick } from '@vaadin/testing-helpers';
2+
import { escKeyDown, fixtureSync, nextRender, outsideClick } from '@vaadin/testing-helpers';
33
import sinon from 'sinon';
44
import '../src/vaadin-combo-box.js';
55
import { getViewportItems, setInputValue } from './helpers.js';
@@ -185,6 +185,143 @@ describe('basic features', () => {
185185
input.focus();
186186
expect(spy.calledOnce).to.be.true;
187187
});
188+
189+
it('should select content on host click after dropdown close', () => {
190+
comboBox.items = ['foo', 'bar'];
191+
comboBox.value = 'foo';
192+
comboBox.autoselect = true;
193+
input.focus();
194+
195+
comboBox.open();
196+
comboBox.close();
197+
198+
const spy = sinon.spy(input, 'select');
199+
comboBox.click();
200+
expect(spy.calledOnce).to.be.true;
201+
});
202+
203+
it('should not select content on second host click after dropdown close', () => {
204+
comboBox.items = ['foo', 'bar'];
205+
comboBox.value = 'foo';
206+
comboBox.autoselect = true;
207+
input.focus();
208+
209+
comboBox.open();
210+
comboBox.close();
211+
212+
comboBox.click();
213+
214+
const spy = sinon.spy(input, 'select');
215+
comboBox.click();
216+
expect(spy.called).to.be.false;
217+
});
218+
219+
it('should select content on host click after Escape close', () => {
220+
comboBox.items = ['foo', 'bar'];
221+
comboBox.value = 'foo';
222+
comboBox.autoselect = true;
223+
input.focus();
224+
225+
comboBox.open();
226+
escKeyDown(input);
227+
228+
const spy = sinon.spy(input, 'select');
229+
comboBox.click();
230+
expect(spy.calledOnce).to.be.true;
231+
});
232+
233+
it('should select content on host click after outside click close', () => {
234+
comboBox.items = ['foo', 'bar'];
235+
comboBox.value = 'foo';
236+
comboBox.autoselect = true;
237+
input.focus();
238+
239+
comboBox.open();
240+
outsideClick();
241+
// Focus again to match browser behavior
242+
input.focus();
243+
input.setSelectionRange(0, 0);
244+
245+
const spy = sinon.spy(input, 'select');
246+
comboBox.click();
247+
expect(spy.calledOnce).to.be.true;
248+
});
249+
250+
it('should select content on host click after dropdown close when autoOpenDisabled', () => {
251+
comboBox.items = ['foo', 'bar'];
252+
comboBox.value = 'foo';
253+
comboBox.autoselect = true;
254+
comboBox.autoOpenDisabled = true;
255+
input.focus();
256+
257+
comboBox.open();
258+
comboBox.close();
259+
260+
const spy = sinon.spy(input, 'select');
261+
comboBox.click();
262+
expect(spy.calledOnce).to.be.true;
263+
});
264+
265+
it('should not select content on second host click when autoOpenDisabled', () => {
266+
comboBox.items = ['foo', 'bar'];
267+
comboBox.value = 'foo';
268+
comboBox.autoselect = true;
269+
comboBox.autoOpenDisabled = true;
270+
input.focus();
271+
272+
comboBox.open();
273+
comboBox.close();
274+
275+
comboBox.click();
276+
277+
const spy = sinon.spy(input, 'select');
278+
comboBox.click();
279+
expect(spy.called).to.be.false;
280+
});
281+
282+
it('should select content on toggle button click after dropdown close', () => {
283+
comboBox.items = ['foo', 'bar'];
284+
comboBox.value = 'foo';
285+
comboBox.autoselect = true;
286+
input.focus();
287+
288+
comboBox.open();
289+
comboBox.close();
290+
291+
const spy = sinon.spy(input, 'select');
292+
comboBox.$.toggleButton.click();
293+
expect(spy.calledOnce).to.be.true;
294+
});
295+
296+
it('should not select all when user has partially selected text', () => {
297+
comboBox.items = ['foo', 'bar'];
298+
comboBox.value = 'foo';
299+
comboBox.autoselect = true;
300+
input.focus();
301+
302+
comboBox.open();
303+
comboBox.close();
304+
305+
// Simulate user drag-selecting part of the text
306+
input.setSelectionRange(0, 2);
307+
308+
const spy = sinon.spy(input, 'select');
309+
comboBox.click();
310+
expect(spy.called).to.be.false;
311+
});
312+
313+
it('should not select content on host click when autoselect is false', () => {
314+
comboBox.items = ['foo', 'bar'];
315+
comboBox.value = 'foo';
316+
input.focus();
317+
318+
comboBox.open();
319+
comboBox.close();
320+
321+
const spy = sinon.spy(input, 'select');
322+
comboBox.click();
323+
expect(spy.called).to.be.false;
324+
});
188325
});
189326
});
190327

0 commit comments

Comments
 (0)