Skip to content

Commit

Permalink
(#1642) Update combo box text clear/unselected event
Browse files Browse the repository at this point in the history
- Updates some lacking documentation as well

Closes #1642
  • Loading branch information
olitharp-nci authored and bryanpizzillo committed Apr 9, 2024
1 parent cb4e386 commit 039e4d6
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -413,10 +413,11 @@ describe('Combo box - Events', () => {
document.body.append(container);

const textChangeEvent = jest.fn();
const textClearedEvent = jest.fn();
const listboxExpandedEvent = jest.fn();
const listboxCollapsedEvent = jest.fn();
const listboxNoResultsEvent = jest.fn();
const clearedEvent = jest.fn();
const unselectedEvent = jest.fn();
const selectedEvent = jest.fn();

const element = document.querySelector('.usa-combo-box') as HTMLElement;
Expand All @@ -426,6 +427,10 @@ describe('Combo box - Events', () => {
'usa-combo-box:input:text-change',
textChangeEvent
);
element.addEventListener(
'usa-combo-box:input:text-cleared',
textClearedEvent
);
element.addEventListener(
'usa-combo-box:listbox:expanded',
listboxExpandedEvent
Expand All @@ -438,7 +443,7 @@ describe('Combo box - Events', () => {
'usa-combo-box:listbox:no-results',
listboxNoResultsEvent
);
element.addEventListener('usa-combo-box:cleared', clearedEvent);
element.addEventListener('usa-combo-box:unselected', unselectedEvent);
element.addEventListener('usa-combo-box:selected', selectedEvent);

const combobox = await screen.findByRole('combobox');
Expand All @@ -462,20 +467,27 @@ describe('Combo box - Events', () => {
inputValue: 'a',
});

const previouslySelected1 = Object.assign({}, selected);
await user.keyboard('[Enter]');
expect(selectedEvent).toHaveBeenCalledTimes(1);
expect(selectedEvent.mock.calls[0][0].detail).toEqual({
comboBox: comboBoxEl,
inputValue: 'Apple',
selected: selected,
previouslySelected: previouslySelected1,
});
expect(listboxCollapsedEvent).toHaveBeenCalledTimes(1);
expect(listboxCollapsedEvent.mock.calls[0][0].detail).toEqual({
comboBox: comboBoxEl,
inputValue: 'Apple',
});

const previouslySelected = Object.assign({}, selected);
const previouslySelected2 = Object.assign({}, selected);
await user.keyboard('[Escape]');
expect(clearedEvent).toHaveBeenCalledTimes(1);
expect(clearedEvent.mock.calls[0][0].detail).toEqual({
expect(unselectedEvent).toHaveBeenCalledTimes(1);
expect(unselectedEvent.mock.calls[0][0].detail).toEqual({
comboBox: comboBoxEl,
previouslySelected: previouslySelected,
previouslySelected: previouslySelected2,
});

await user.keyboard('aa');
Expand All @@ -484,5 +496,13 @@ describe('Combo box - Events', () => {
comboBox: comboBoxEl,
inputValue: 'aa',
});

await user.keyboard('[Escape]');
expect(textClearedEvent).toHaveBeenCalledTimes(1);
expect(textClearedEvent.mock.calls[0][0].detail).toEqual({
comboBox: comboBoxEl,
previousInputValue: 'aa',
selected: selected,
});
});
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ComboBoxEventDetails } from './combo-box.event-details';

/**
* Custom event details for the `combo-box:cleared` event.
* Custom event details for the `combo-box:unselected` event.
*/
export type ComboBoxClearedEventDetails = ComboBoxEventDetails & {
export type ComboBoxUnselectedEventDetails = ComboBoxEventDetails & {
/** An [HTML collection of selected options](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement/selectedOptions) before a change was made. */
previouslySelected: HTMLCollectionOf<HTMLOptionElement>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ import { ComboBoxEventDetails } from './combo-box.event-details';
* Custom event details for the `combo-box:input:text-change` event.
*/
export type ComboBoxTextChangeEventDetails = ComboBoxEventDetails & {
/** The current value of the combo box input. */
inputValue: string;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ComboBoxEventDetails } from './combo-box.event-details';

/**
* Custom event details for the `combo-box:input:text-cleared` event.
*/
export type ComboBoxTextClearedEventDetails = ComboBoxEventDetails & {
/**
* An
* [HTML collection of selected options](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement/selectedOptions)
* via the combo box select element. May be empty if no value was selected
* before the text was cleared.
*/
selected: HTMLCollectionOf<HTMLOptionElement>;
/** The previous value of the combo box input before the text was cleared. */
previousInputValue: string;
};
6 changes: 4 additions & 2 deletions packages/ncids-js/src/components/usa-combo-box/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@
export { USAComboBox } from './usa-combo-box.component';

import type { ComboBoxEventDetails } from './event-details/combo-box.event-details';
import type { ComboBoxClearedEventDetails } from './event-details/combo-box.cleared.event-details';
import type { ComboBoxUnselectedEventDetails } from './event-details/combo-box.unselected.event-details';
import type { ComboBoxSelectedEventDetails } from './event-details/combo-box.selected.event-details';
import type { ComboBoxTextChangeEventDetails } from './event-details/input.text-change.event-details';
import type { ComboBoxTextClearedEventDetails } from './event-details/input.text-cleared.event-details';
import type { ComboBoxCollapsedEventDetails } from './event-details/listbox.collapsed.event-details';
import type { ComboBoxExpandedEventDetails } from './event-details/listbox.expanded.event-details';
import type { ComboBoxNoResultsEventDetails } from './event-details/listbox.no-results.event-details';

export type {
ComboBoxEventDetails,
ComboBoxClearedEventDetails,
ComboBoxUnselectedEventDetails,
ComboBoxSelectedEventDetails,
ComboBoxTextChangeEventDetails,
ComboBoxTextClearedEventDetails,
ComboBoxCollapsedEventDetails,
ComboBoxExpandedEventDetails,
ComboBoxNoResultsEventDetails,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ComboBoxClearedEventDetails } from './event-details/combo-box.cleared.event-details';
import { ComboBoxUnselectedEventDetails } from './event-details/combo-box.unselected.event-details';
import { ComboBoxSelectedEventDetails } from './event-details/combo-box.selected.event-details';
import { ComboBoxTextChangeEventDetails } from './event-details/input.text-change.event-details';
import { ComboBoxTextClearedEventDetails } from './event-details/input.text-cleared.event-details';
import { ComboBoxCollapsedEventDetails } from './event-details/listbox.collapsed.event-details';
import { ComboBoxExpandedEventDetails } from './event-details/listbox.expanded.event-details';
import { ComboBoxNoResultsEventDetails } from './event-details/listbox.no-results.event-details';
Expand All @@ -9,11 +10,17 @@ import { ComboBoxNoResultsEventDetails } from './event-details/listbox.no-result
* The USAComboBox component is used to initialize the `.usa-combo-box`
* component.
*
* The combo box is a combination of two different user interface elements, an
* [HTMLSelectElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement)
* for capturing options, and an
* [HTMLInputElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement)
* that allows users to filter the options.
*
* ## Default options
* There are no options for USAComboBox.
*
* ## Initialization examples
* The easiest way to user the comobo box is to let the NCIDS automatically
* The easiest way to user the combo box is to let the NCIDS automatically
* initialize your combo box HTML.
*
* Add the following to the top of your main Javascript file, and it will add
Expand All @@ -40,17 +47,18 @@ import { ComboBoxNoResultsEventDetails } from './event-details/listbox.no-result
*
* ## HTML Events
*
* The USAComboBox component will dispatch the following
* The combo box component will dispatch the following
* {@link https://developer.mozilla.org/docs/Web/API/CustomEvent | CustomEvent} types
* that can be used to track analytics or other needs when a visitor interacts with
* the combo box.
* the combo box:
*
* * `usa-combo-box:input:text-change` - Dispatched with {@link usa-combo-box~ComboBoxTextChangeEventDetails | ComboBoxTextChangeEventDetails} when the input value changes.
* * `usa-combo-box:input:text-cleared` - Dispatched with {@link usa-combo-box~ComboBoxTextClearedEventDetails | ComboBoxTextChangeEventDetails} when the input value is cleared without a new selection being made.
* * `usa-combo-box:listbox:expanded` - Dispatched with {@link usa-combo-box~ComboBoxExpandedEventDetails | ComboBoxExpandedEventDetails} when the listbox is opened.
* * `usa-combo-box:listbox:collapsed` - Dispatched with {@link usa-combo-box~ComboBoxCollapsedEventDetails | ComboBoxCollapsedEventDetails} when the listbox is closed.
* * `usa-combo-box:listbox:no-results` - Dispatched with {@link usa-combo-box~ComboBoxNoResultsEventDetails | ComboBoxNoResultsEventDetails} when the listbox shows the 'No Results Found' text.
* * `usa-combo-box:cleared` - Dispatched with {@link usa-combo-box~ComboBoxClearedEventDetails | ComboBoxClearedEventDetails} when the input is cleared.
* * `usa-combo-box:selected` - Dispatched with {@link usa-combo-box~ComboBoxSelectedEventDetails | ComboBoxSelectedEventDetails} when the input value is selected.
* * `usa-combo-box:selected` - Dispatched with {@link usa-combo-box~ComboBoxSelectedEventDetails | ComboBoxSelectedEventDetails} when the user has selected an option from the listbox.
* * `usa-combo-box:unselected` - Dispatched with {@link usa-combo-box~ComboBoxUnselectedEventDetails | ComboBoxClearedEventDetails} when the selected option is unselected.
*
* @example
* ```js
Expand Down Expand Up @@ -605,7 +613,7 @@ export class USAComboBox {
* Handles mouse click event of clear button. Clears the combo box.
*/
private handleClear() {
this.clearComboBox();
this.unsetComboBox();
}

/**
Expand Down Expand Up @@ -766,7 +774,7 @@ export class USAComboBox {
hidden before Escape is pressed, clears the combobox.
*/
if (this.listbox) {
this.listbox.hidden ? this.clearComboBox() : this.hideListbox(true);
this.listbox.hidden ? this.unsetComboBox() : this.hideListbox(true);
}

break;
Expand All @@ -793,7 +801,8 @@ export class USAComboBox {
}

/**
* Handles users editing the combo box input to filter results.
* Handles users editing the combo box input to filter results. Dispatches the
* `usa-combo-box:input:text-change` event.
*/
private handleInputChange(event: Event): void {
const inputEvent = event as InputEvent;
Expand All @@ -817,7 +826,7 @@ export class USAComboBox {
}

/**
* Handles clicks outside an active listbox and closes it
* Handles clicks outside an active listbox and closes it.
* @param event click event
*/
private handleOutsideClick(event: Event): void {
Expand Down Expand Up @@ -847,7 +856,8 @@ export class USAComboBox {
/**
* Shows listbox.
*
* Checks if listbox is already shown before triggering.
* Checks if listbox is already shown before triggering. Dispatches the
* `usa-combo-box:listbox:expanded` event.
*/
private showListbox(): void {
if (!this.listbox || !this.input || !this.toggleButton) {
Expand Down Expand Up @@ -878,7 +888,8 @@ export class USAComboBox {
/**
* Hides listbox.
*
* Checks if listbox is already hidden before triggering.
* Checks if listbox is already hidden before triggering. Dispatches the
* `usa-combo-box:listbox:collapsed` event.
*
* @param validateOption If true, after closing the listbox, checks if the
* value entered is valid. Default: false
Expand Down Expand Up @@ -923,8 +934,10 @@ export class USAComboBox {

/**
* Populates the listbox with filtered list of items via editable combo box.
* Dispatches the `usa-combo-box:listbox:no-results` event if the filter
* finds no results.
*
* @see Outstanding issue uswds#1564
* @param {string} value Text input used to filter the listbox.
*/
private filterListbox(value: string) {
if (!this.listOptions || !this.input) {
Expand Down Expand Up @@ -1065,7 +1078,48 @@ export class USAComboBox {
}

/**
* Sets combo box input and select values.
* Resets combo box to its previous selection if the current entry is invalid.
* Clears the input if there is no value selected.
*/
private checkComboBoxOption(): void {
if (
this.input &&
this.input.getAttribute('data-value') !== this.select.value
) {
this.select.value
? this.setSelectedByValue(this.select.value)
: this.clearInput(this.input);
}
}

/**
* Clears the input if there is an invalid option left in the input field.
* Dispatches the `usa-combo-box:input:text-cleared` event.
*
* @param {HTMLInputElement} input The input element.
*/
private clearInput(input: HTMLInputElement) {
// The previous value of the combo box input before the text was cleared.
const previousInputValue = input.value;

this.resetComboBox();

this.comboBox.dispatchEvent(
new CustomEvent('usa-combo-box:input:text-cleared', {
bubbles: true,
detail: <ComboBoxTextClearedEventDetails>{
comboBox: this.comboBox,
selected: this.select.selectedOptions,
previousInputValue: previousInputValue,
},
})
);
}

/**
* Sets combo box input and select values. Dispatches `usa-combo-box:selected`
* event.
*
* @param listItem Option being selected.
*/
private setComboBox(listItem: HTMLLIElement): void {
Expand Down Expand Up @@ -1101,27 +1155,30 @@ export class USAComboBox {
}

/**
* Resets combo box to its previous selection if the current entry is invalid.
* Clears the input if there is no value selected.
* Clears combo box input and select values and dispatches
* `usa-combo-box:unselected` event.
*/
private checkComboBoxOption(): void {
if (
this.input &&
this.input.getAttribute('data-value') !== this.select.value
) {
this.select.value
? this.setSelectedByValue(this.select.value)
: this.clearComboBox();
}
private unsetComboBox(): void {
// Clone of the previously selected options for the dispatched event.
const previouslySelected = Object.assign({}, this.select.selectedOptions);

this.resetComboBox();

this.comboBox.dispatchEvent(
new CustomEvent('usa-combo-box:unselected', {
bubbles: true,
detail: <ComboBoxUnselectedEventDetails>{
comboBox: this.comboBox,
previouslySelected: previouslySelected,
},
})
);
}

/**
* Clears combo box input and select values. Resets to clean state.
* Resets combo box UI to clean state.
*/
private clearComboBox(): void {
// Clone of the previously selected options for the dispatched event.
const previouslySelected = Object.assign({}, this.select.selectedOptions);

private resetComboBox() {
this.selectedOption = undefined;
this.removeHighlightedOption();
this.comboBox.classList.remove('usa-combo-box--pristine');
Expand All @@ -1140,15 +1197,5 @@ export class USAComboBox {
this.input.value = '';
this.input.focus();
}

this.comboBox.dispatchEvent(
new CustomEvent('usa-combo-box:cleared', {
bubbles: true,
detail: <ComboBoxClearedEventDetails>{
comboBox: this.comboBox,
previouslySelected: previouslySelected,
},
})
);
}
}

0 comments on commit 039e4d6

Please sign in to comment.