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

Add clear button for Select #2015

Merged
merged 30 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
fbc3739
Working implementation.
atmgrifter00 Apr 8, 2024
87173a3
Update to spec.
atmgrifter00 Apr 12, 2024
f5d2430
Cleanup, unit tests, and matrix tests.
atmgrifter00 Apr 16, 2024
ea80908
Merge branch 'main' into clear-select
atmgrifter00 Apr 16, 2024
74a2e58
Change files
atmgrifter00 Apr 16, 2024
5e030f8
Merge branch 'main' into clear-select
atmgrifter00 May 7, 2024
e0c306d
Merge branch 'main' into clear-select
atmgrifter00 May 7, 2024
4f4ea9a
Merge fix.
atmgrifter00 May 7, 2024
c9359b8
Don't cache placeholder. Update tests.
atmgrifter00 May 8, 2024
f18a2ce
Merge branch 'main' into clear-select
atmgrifter00 May 8, 2024
2296afc
Merge branch 'main' into clear-select
atmgrifter00 May 8, 2024
5a0caf5
Remove unused members.
atmgrifter00 May 8, 2024
a8e422c
Merge branch 'clear-select' of https://github.com/ni/nimble into clea…
atmgrifter00 May 8, 2024
884c85e
Update packages/nimble-components/src/select/testing/select.pageobjec…
atmgrifter00 May 9, 2024
d4f9202
Handle PR feedback.
atmgrifter00 May 9, 2024
07caa36
Merge branch 'clear-select' of https://github.com/ni/nimble into clea…
atmgrifter00 May 9, 2024
44719b0
Handle PR feedback.
atmgrifter00 May 9, 2024
84cbdd2
Slight test description change.
atmgrifter00 May 9, 2024
1086748
Handle PR feedback.
atmgrifter00 May 9, 2024
5beb1ce
Handle PR feedback.
atmgrifter00 May 10, 2024
2d5b25d
Handle pressing Esc to clear value when dropdown is closed.
atmgrifter00 May 10, 2024
84d6eb5
Merge branch 'main' into clear-select
atmgrifter00 May 10, 2024
9a8143c
Handle PR feedback.
atmgrifter00 May 10, 2024
9e8307e
Merge branch 'clear-select' of https://github.com/ni/nimble into clea…
atmgrifter00 May 10, 2024
ae4fa8b
Update docs.
atmgrifter00 May 10, 2024
5228fbd
Merge branch 'main' into clear-select
atmgrifter00 May 10, 2024
f329aa5
Merge branch 'main' into clear-select
atmgrifter00 May 13, 2024
3e682d3
Update packages/nimble-components/src/select/tests/select.spec.ts
atmgrifter00 May 15, 2024
f618689
Handle PR feedback
atmgrifter00 May 15, 2024
1aa2644
Merge branch 'main' into clear-select
atmgrifter00 May 15, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
atmgrifter00 marked this conversation as resolved.
Show resolved Hide resolved
"type": "minor",
"comment": "Add clear button for Select",
"packageName": "@ni/nimble-components",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export const styles = css`

.indicator {
flex: none;
margin-inline-start: 1em;
margin-left: ${smallPadding};
padding-right: 8px;
display: flex;
justify-content: center;
Expand Down
87 changes: 46 additions & 41 deletions packages/nimble-components/src/select/index.ts
atmgrifter00 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ const isOptionSelectable = (el: ListOption): boolean => {
return !el.visuallyHidden && !el.disabled && !el.hidden;
};

const isOptionPlaceholder = (el: ListboxOption): boolean => {
return el.disabled && el.hidden;
};

/**
* A nimble-styled HTML select.
*/
Expand Down Expand Up @@ -94,6 +98,9 @@ export class Select
@attr({ attribute: 'filter-mode' })
public filterMode: FilterMode = FilterMode.none;

@attr({ attribute: 'clearable', mode: 'boolean' })
atmgrifter00 marked this conversation as resolved.
Show resolved Hide resolved
public clearable = false;

/**
* @internal
*/
Expand Down Expand Up @@ -170,12 +177,6 @@ export class Select
@observable
public filter = '';

/**
* @internal
*/
@observable
public committedSelectedOption?: ListboxOption;

/**
* The max height for the listbox when opened.
*
Expand Down Expand Up @@ -239,15 +240,8 @@ export class Select
if (prev !== newValue && !(this.open && this.selectedIndex < 0)) {
this._value = newValue;
super.valueChanged(prev, newValue);
if (!this.open) {
this.committedSelectedOption = this.options.find(
o => o.value === newValue
);
}
Observable.notify(this, 'value');
if (this.collapsible) {
Observable.notify(this, 'displayValue');
}
this.updateDisplayValue();
}
}

Expand All @@ -257,7 +251,7 @@ export class Select
@volatile
public get displayValue(): string {
Observable.track(this, 'displayValue');
return this.committedSelectedOption?.text ?? '';
return this.firstSelectedOption?.text ?? '';
}

/**
Expand Down Expand Up @@ -315,7 +309,6 @@ export class Select
if (value) {
this.value = value;
}
this.committedSelectedOption = this.options[this.selectedIndex];
}

/**
Expand Down Expand Up @@ -370,11 +363,14 @@ export class Select
break;
}
case 'selected': {
if (isNimbleListOption(sourceElement)) {
if (
isNimbleListOption(sourceElement)
&& sourceElement.selected
) {
this.selectedIndex = this.options.indexOf(sourceElement);
} else {
this.clearSelect();
}
this.setSelectedOptions();
this.updateDisplayValue();
break;
}
case 'hidden': {
Expand Down Expand Up @@ -443,29 +439,28 @@ export class Select
/**
* @internal
*/
public changeValueHandler(): void {
this.committedSelectedOption = this.options.find(
option => option.selected
);
public clearClickHandler(e: MouseEvent): void {
this.open = false;
this.clearSelect();
this.updateValue(true);
e.stopPropagation();
}

/**
* @internal
*/
public updateDisplayValue(): void {
const placeholderOption = this.getPlaceholderOption();
if (
this.committedSelectedOption?.disabled
&& this.committedSelectedOption?.hidden
&& this.committedSelectedOption?.selected
placeholderOption
&& this.firstSelectedOption === placeholderOption
) {
this.displayPlaceholder = true;
} else {
this.displayPlaceholder = false;
}

if (this.collapsible) {
Observable.notify(this, 'displayValue');
}
Observable.notify(this, 'displayValue');
}

/**
Expand Down Expand Up @@ -749,10 +744,10 @@ export class Select
this.options.push(option);
}

// Prevents parent classes from resetting selectedIndex to a positive
// value while filtering, which can result in a disabled option being
// selected.
protected override setSelectedOptions(): void {
// Prevents parent classes from resetting selectedIndex to a positive
// value while filtering, which can result in a disabled option being
// selected.
if (this.open && this.selectedIndex === -1) {
return;
}
Expand Down Expand Up @@ -869,27 +864,31 @@ export class Select
};
let selectedIndex = -1;
let firstValidOptionIndex = -1;
let placeholderIndex = -1;
for (let i = 0; i < options?.length; i++) {
const option = options[i];
if (optionIsSelected(option!) || option?.value === this.value) {
const option = options[i]!;
if (optionIsSelected(option) || option.value === this.value) {
selectedIndex = i;
}
if (
break;
} else if (placeholderIndex === -1 && isOptionPlaceholder(option)) {
placeholderIndex = i;
} else if (
firstValidOptionIndex === -1
&& isOptionSelectable(option! as ListOption)
&& isOptionSelectable(option as ListOption)
) {
firstValidOptionIndex = i;
}
}

if (selectedIndex !== -1) {
this.selectedIndex = selectedIndex;
} else if (placeholderIndex !== -1) {
this.selectedIndex = placeholderIndex;
} else if (firstValidOptionIndex !== -1) {
this.selectedIndex = firstValidOptionIndex;
} else {
atmgrifter00 marked this conversation as resolved.
Show resolved Hide resolved
this.selectedIndex = 0;
}
this.committedSelectedOption = options[this.selectedIndex];
}

private setActiveOption(newActiveIndex: number): void {
Expand Down Expand Up @@ -932,8 +931,8 @@ export class Select
}
}

private committedSelectedOptionChanged(): void {
this.updateDisplayValue();
private getPlaceholderOption(): ListOption | undefined {
return this.options.find(o => o.hidden && o.disabled) as ListOption;
}

private setPositioning(): void {
Expand Down Expand Up @@ -1020,6 +1019,13 @@ export class Select
}
}

private clearSelect(): void {
const placeholder = this.getPlaceholderOption();
this.selectedIndex = placeholder
? this.options.indexOf(placeholder)
: -1;
}

/**
* Resets and fills the proxy to match the component's options.
*
Expand Down Expand Up @@ -1050,7 +1056,6 @@ export class Select
}

private initializeOpenState(): void {
this.committedSelectedOption = this.options[this.selectedIndex];
this.setActiveOption(this.selectedIndex);
this.ariaControls = this.listboxId;
this.ariaExpanded = 'true';
Expand Down
9 changes: 8 additions & 1 deletion packages/nimble-components/src/select/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,15 @@ export const styles = css`
order: 1;
}

[part='indicator'] {
.clear-button {
atmgrifter00 marked this conversation as resolved.
Show resolved Hide resolved
atmgrifter00 marked this conversation as resolved.
Show resolved Hide resolved
order: 3;
width: auto;
height: auto;
margin-left: ${smallPadding};
}

[part='indicator'] {
order: 4;
}

.error-icon {
Expand Down
13 changes: 12 additions & 1 deletion packages/nimble-components/src/select/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
filterSearchLabel
} from '../label-provider/core/label-tokens';
import { FilterMode } from './types';
import { buttonTag } from '../button';
import { iconXmarkTag } from '../icons/xmark';

/* eslint-disable @typescript-eslint/indent */
// prettier-ignore
Expand All @@ -46,7 +48,6 @@ SelectOptions
role="combobox"
tabindex="${x => (!x.disabled ? '0' : null)}"
@click="${(x, c) => x.clickHandler(c.event as MouseEvent)}"
@change="${x => x.changeValueHandler()}"
@contentchange="${x => x.updateDisplayValue()}"
@focusin="${(x, c) => x.focusinHandler(c.event as FocusEvent)}"
@focusout="${(x, c) => x.focusoutHandler(c.event as FocusEvent)}"
Expand All @@ -65,6 +66,16 @@ SelectOptions
<div class="selected-value ${x => (x.displayPlaceholder ? 'placeholder' : '')}" part="selected-value" ${overflow('hasOverflow')} title=${x => (x.hasOverflow && x.displayValue ? x.displayValue : null)}>
<slot name="selected-value">${x => x.displayValue}</slot>
</div>
${when(x => x.clearable && !x.displayPlaceholder && x.selectedIndex >= 0, html<Select>`
<${buttonTag}
class="clear-button"
atmgrifter00 marked this conversation as resolved.
Show resolved Hide resolved
part="clear-button"
content-hidden
appearance="ghost"
@click="${(x, c) => x.clearClickHandler(c.event as MouseEvent)}">
<${iconXmarkTag} slot="start"></${iconXmarkTag}>
</${buttonTag}>
`)}
<div aria-hidden="true" class="indicator" part="indicator">
<slot name="indicator">
${definition.indicator || ''}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { Select } from '..';
import type { ListOption } from '../../list-option';
import { waitForUpdatesAsync } from '../../testing/async-helpers';
import { FilterMode } from '../types';
import type { Button } from '../../button';

/**
* Page object for the `nimble-select` component to provide consistent ways
Expand Down Expand Up @@ -128,6 +129,26 @@ export class SelectPageObject {
this.clickOption(optionIndex);
}

public clickClearButton(): void {
if (!this.selectElement.clearable) {
throw new Error(
'Select must set "clearable" in order to click clear button'
);
}

if (
this.selectElement.selectedIndex === -1
|| this.selectElement.displayPlaceholder
) {
throw new Error(
'Select must have a selected element in order to click clear button'
);
}

const clearButton = this.getClearButton();
clearButton?.click();
}

public async clickAway(): Promise<void> {
this.selectElement.dispatchEvent(new Event('focusout'));
await waitForUpdatesAsync();
Expand Down Expand Up @@ -218,6 +239,10 @@ export class SelectPageObject {
);
}

public isClearButtonVisible(): boolean {
return this.getClearButton() != null;
}

public isFilterInputVisible(): boolean {
return (
this.selectElement.shadowRoot?.querySelector('.filter-field')
Expand Down Expand Up @@ -246,20 +271,6 @@ export class SelectPageObject {
return this.selectElement.filterInput?.value ?? '';
}

public async waitForChange(): Promise<string> {
return new Promise(resolve => {
this.selectElement.addEventListener(
'change',
() => {
resolve(this.selectElement.value);
},
{
once: true
}
);
});
}

private getFilterInput(): HTMLInputElement | null | undefined {
if (this.selectElement.filterMode === FilterMode.none) {
throw new Error(
Expand All @@ -268,4 +279,10 @@ export class SelectPageObject {
}
return this.selectElement.filterInput;
}

private getClearButton(): Button | null | undefined {
return this.selectElement.shadowRoot?.querySelector<Button>(
'.clear-button'
);
}
}
Loading
Loading