Skip to content

Commit bc3e88f

Browse files
driskullbenelan
authored andcommitted
fix(autocomplete): handle focusing when item is clicked and reset inputValue on form reset (#11099)
**Related Issue:** #10044 ## Summary - opens autocomplete when input is clicked - This is useful for when the input is already focused but the autocomplete is closed. - Sets focus and sets `open=false` when `autocomplete-item` is clicked - ensures that the input receives focus after an `autocomplete-item` is clicked - use `enterKeyHint` property JSX - update `onFormReset` to be called after form component logic - This allows us to reset the `inputValue` to the `defaultInputValue` without overriding existing logic - adds autocomplete to validation demo cc @benelan BEGIN_COMMIT_OVERRIDE END_COMMIT_OVERRIDE
1 parent 7c6ec61 commit bc3e88f

File tree

5 files changed

+102
-10
lines changed

5 files changed

+102
-10
lines changed

packages/calcite-components/src/components/autocomplete-item/autocomplete-item.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ export class AutocompleteItem
118118

119119
// #region Private Methods
120120

121-
private handleClick(): void {
121+
private handleClick(event: MouseEvent): void {
122+
event.preventDefault();
122123
this.calciteInternalAutocompleteItemSelect.emit();
123124
}
124125

packages/calcite-components/src/components/autocomplete/autocomplete.e2e.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
import { html } from "../../../support/formatting";
2020
import { defaultMenuPlacement } from "../../utils/floating-ui";
2121
import { Input } from "../input/input";
22-
import { skipAnimations } from "../../tests/utils";
22+
import { isElementFocused, skipAnimations } from "../../tests/utils";
2323
import { CSS, SLOTS } from "./resources";
2424
import { Autocomplete } from "./autocomplete";
2525

@@ -552,6 +552,19 @@ describe("calcite-autocomplete", () => {
552552
expect(await autocomplete.getProperty("open")).toBe(false);
553553
});
554554

555+
it("should open when input is clicked", async () => {
556+
const page = await newE2EPage();
557+
await page.setContent(`${simpleHTML}<div id="test">test</div>`);
558+
559+
const input = await page.find("calcite-autocomplete >>> calcite-input");
560+
await input.click();
561+
await page.waitForChanges();
562+
563+
const autocomplete = await page.find("calcite-autocomplete");
564+
565+
expect(await autocomplete.getProperty("open")).toBe(true);
566+
});
567+
555568
it("should set value, close, and emit calciteAutocompleteChange when item is selected via mouse", async () => {
556569
const page = await newE2EPage();
557570
await page.setContent(simpleHTML);
@@ -569,6 +582,7 @@ describe("calcite-autocomplete", () => {
569582
expect(await autocomplete.getProperty("value")).toBe("two");
570583
expect(await autocomplete.getProperty("open")).toBe(false);
571584
expect(changeEvent).toHaveReceivedEventTimes(1);
585+
expect(await isElementFocused(page, "#myAutocomplete")).toBe(true);
572586
});
573587

574588
it("should set value, close, and emit calciteAutocompleteChange when item is selected via keyboard", async () => {

packages/calcite-components/src/components/autocomplete/autocomplete.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ export class Autocomplete
102102

103103
defaultValue: Autocomplete["value"];
104104

105+
defaultInputValue: Autocomplete["inputValue"];
106+
105107
floatingEl: HTMLDivElement;
106108

107109
floatingLayout?: FloatingLayout;
@@ -396,6 +398,7 @@ export class Autocomplete
396398
this.mutationObserver?.observe(this.el, { childList: true, subtree: true });
397399
connectLabel(this);
398400
connectForm(this);
401+
this.defaultInputValue = this.inputValue || "";
399402

400403
this.getAllItemsDebounced();
401404

@@ -456,6 +459,7 @@ export class Autocomplete
456459

457460
loaded(): void {
458461
afterConnectDefaultValueSet(this, this.value || "");
462+
this.defaultInputValue = this.inputValue || "";
459463
setComponentLoaded(this);
460464
connectFloatingUI(this);
461465
}
@@ -516,10 +520,12 @@ export class Autocomplete
516520
this.open = false;
517521
}
518522

519-
private handleInternalAutocompleteItemSelect(event: Event): void {
523+
private async handleInternalAutocompleteItemSelect(event: Event): Promise<void> {
520524
this.value = (event.target as AutocompleteItem["el"]).value;
521525
event.stopPropagation();
522526
this.emitChange();
527+
await this.setFocus();
528+
this.open = false;
523529
}
524530

525531
private mutationObserver = createObserver("mutation", () => this.getAllItemsDebounced());
@@ -532,6 +538,10 @@ export class Autocomplete
532538
this.setFocus();
533539
}
534540

541+
onFormReset(): void {
542+
this.inputValue = this.defaultInputValue;
543+
}
544+
535545
onBeforeOpen(): void {
536546
this.calciteAutocompleteBeforeOpen.emit();
537547
}
@@ -549,7 +559,6 @@ export class Autocomplete
549559
}
550560

551561
private emitChange(): void {
552-
this.open = false;
553562
this.calciteAutocompleteChange.emit();
554563
}
555564

@@ -624,11 +633,6 @@ export class Autocomplete
624633
this.resizeObserver?.observe(el);
625634

626635
connectFloatingUI(this);
627-
628-
// TODO: fixme when supported in jsx
629-
// https://devtopia.esri.com/WebGIS/arcgis-web-components/issues/2694
630-
const enterKeyHint = this.el.getAttribute("enterkeyhint");
631-
el.enterKeyHint = enterKeyHint;
632636
}
633637

634638
private keyDownHandler(event: KeyboardEvent): void {
@@ -654,6 +658,7 @@ export class Autocomplete
654658
if (open && activeIndex > -1) {
655659
this.value = enabledItems[activeIndex].value;
656660
this.emitChange();
661+
this.open = false;
657662
event.preventDefault();
658663
} else if (!event.defaultPrevented) {
659664
if (submitForm(this)) {
@@ -692,6 +697,14 @@ export class Autocomplete
692697
this.calciteAutocompleteTextChange.emit();
693698
}
694699

700+
private inputClickHandler(event: MouseEvent): void {
701+
if (event.defaultPrevented) {
702+
return;
703+
}
704+
705+
this.open = true;
706+
}
707+
695708
private inputHandler(event: CustomEvent): void {
696709
event.stopPropagation();
697710
this.inputValue = (event.target as Input["el"]).value;
@@ -715,6 +728,7 @@ export class Autocomplete
715728
const { disabled, listId, inputId, isOpen } = this;
716729

717730
const autofocus = this.el.autofocus || this.el.hasAttribute("autofocus") ? true : null;
731+
const enterKeyHint = this.el.getAttribute("enterkeyhint");
718732
const inputMode = this.el.getAttribute("inputmode") as
719733
| "none"
720734
| "text"
@@ -741,6 +755,7 @@ export class Autocomplete
741755
class={CSS.input}
742756
clearable={true}
743757
disabled={disabled}
758+
enterKeyHint={enterKeyHint}
744759
form={this.form}
745760
icon={this.getIcon()}
746761
iconFlipRtl={this.iconFlipRtl}
@@ -752,6 +767,7 @@ export class Autocomplete
752767
messageOverrides={this.messages}
753768
minLength={this.minLength}
754769
name={this.name}
770+
onClick={this.inputClickHandler}
755771
onKeyDown={this.keyDownHandler}
756772
oncalciteInputChange={this.changeHandler}
757773
oncalciteInputInput={this.inputHandler}

packages/calcite-components/src/demos/validation.html

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,59 @@ <h1 style="margin: 0 auto; text-align: center">Form Validation</h1>
281281
<calcite-rating required name="rating"></calcite-rating>
282282
</calcite-label>
283283
</li>
284+
285+
<!-- Autocomplete -->
286+
<li>
287+
<calcite-label>
288+
Which framework do you use?
289+
<calcite-autocomplete name="framework" required id="framework">
290+
<calcite-autocomplete-item-group heading="Frameworks">
291+
<calcite-autocomplete-item
292+
label="VanillaJS"
293+
value="vanilla"
294+
heading="VanillaJS"
295+
description="VanillaJS with @esri/calcite-components"
296+
icon-start="ribbon"
297+
></calcite-autocomplete-item>
298+
<calcite-autocomplete-item
299+
label="React"
300+
value="react"
301+
heading="React"
302+
description="React with @esri/calcite-components-react"
303+
icon-start="code"
304+
></calcite-autocomplete-item>
305+
<calcite-autocomplete-item
306+
label="Vue"
307+
value="vue"
308+
heading="Vue"
309+
description="Vue with @esri/calcite-components"
310+
icon-start="package"
311+
></calcite-autocomplete-item>
312+
<calcite-autocomplete-item
313+
label="Svelte"
314+
value="svelte"
315+
heading="Svelte"
316+
description="Svelte with @esri/calcite-components"
317+
icon-start="banana"
318+
></calcite-autocomplete-item>
319+
<calcite-autocomplete-item
320+
label="Angular"
321+
value="angular"
322+
heading="Angular"
323+
description="Angular with @esri/calcite-components"
324+
icon-start="data"
325+
></calcite-autocomplete-item>
326+
<calcite-autocomplete-item
327+
label="Ember"
328+
value="ember"
329+
heading="Ember"
330+
description="Ember with @esri/calcite-components"
331+
icon-start="trash"
332+
></calcite-autocomplete-item>
333+
</calcite-autocomplete-item-group>
334+
</calcite-autocomplete>
335+
</calcite-label>
336+
</li>
284337
</ol>
285338

286339
<!-- Button -->
@@ -457,6 +510,12 @@ <h1 style="margin: 0 auto; text-align: center">Form Validation</h1>
457510
datePicker.maxAsDate = new Date();
458511
datePicker.min = "2021-01-01";
459512

513+
// Autocomplete
514+
const autocomplete = document.getElementById("framework");
515+
autocomplete.addEventListener("calciteAutocompleteChange", (event) => {
516+
autocomplete.inputValue = event.target.value;
517+
});
518+
460519
// Form submission
461520
const form = document.getElementById("form");
462521
const submit = document.getElementById("submit");

packages/calcite-components/src/utils/form.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ export function connectForm<T>(component: FormComponent<T>): void {
365365
component.defaultChecked = component.checked;
366366
}
367367

368-
const boundOnFormReset = (component.onFormReset || onFormReset).bind(component);
368+
const boundOnFormReset = onFormReset.bind(component);
369369
associatedForm.addEventListener("reset", boundOnFormReset);
370370
onFormResetMap.set(component.el, boundOnFormReset);
371371
formComponentSet.add(el);
@@ -403,6 +403,8 @@ function onFormReset<T>(this: FormComponent<T>): void {
403403
}
404404

405405
this.value = this.defaultValue;
406+
407+
this.onFormReset?.();
406408
}
407409

408410
/**

0 commit comments

Comments
 (0)