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 13 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
71 changes: 55 additions & 16 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 @@ -245,10 +252,9 @@ export class Select
);
}
Observable.notify(this, 'value');
if (this.collapsible) {
Observable.notify(this, 'displayValue');
}
}

Observable.notify(this, 'displayValue');
atmgrifter00 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down Expand Up @@ -370,10 +376,18 @@ export class Select
break;
}
case 'selected': {
if (isNimbleListOption(sourceElement)) {
if (
isNimbleListOption(sourceElement)
&& sourceElement.selected
) {
this.selectedIndex = this.options.indexOf(sourceElement);
} else {
const placeholderOption = this.getPlaceholderOption();
this.selectedIndex = placeholderOption
? this.options.indexOf(placeholderOption)
: -1;
atmgrifter00 marked this conversation as resolved.
Show resolved Hide resolved
}
this.setSelectedOptions();
this.updateValue();
this.updateDisplayValue();
atmgrifter00 marked this conversation as resolved.
Show resolved Hide resolved
break;
}
Expand Down Expand Up @@ -449,14 +463,27 @@ export class Select
);
}

/**
* @internal
*/
public clearClickHandler(e: MouseEvent): void {
this.open = false;
const placeholderOption = this.getPlaceholderOption();
this.selectedIndex = placeholderOption
? this.options.indexOf(placeholderOption)
: -1;
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.committedSelectedOption === placeholderOption
) {
this.displayPlaceholder = true;
} else {
Expand Down Expand Up @@ -671,6 +698,7 @@ export class Select
if (this.selectedIndex === -1) {
this.selectedIndex = 0;
}
this.updateValue();
atmgrifter00 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down Expand Up @@ -749,10 +777,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,21 +897,27 @@ 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)
&& placeholderIndex === -1
atmgrifter00 marked this conversation as resolved.
Show resolved Hide resolved
&& 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
Expand Down Expand Up @@ -932,6 +966,10 @@ export class Select
}
}

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

private committedSelectedOptionChanged(): void {
this.updateDisplayValue();
}
Expand Down Expand Up @@ -1009,6 +1047,7 @@ export class Select
private updateValue(shouldEmit?: boolean): void {
if (this.$fastController.isConnected) {
this.value = this.firstSelectedOption?.value ?? '';
this.committedSelectedOption = this.firstSelectedOption;
atmgrifter00 marked this conversation as resolved.
Show resolved Hide resolved
}

if (shouldEmit) {
Expand Down
7 changes: 7 additions & 0 deletions packages/nimble-components/src/select/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@ export const styles = css`
}

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

.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};
}

.error-icon {
Expand Down
11 changes: 11 additions & 0 deletions 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 Down Expand Up @@ -65,6 +67,15 @@ 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
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,11 @@ export class SelectPageObject {
this.clickOption(optionIndex);
}

public clickClearButton(): void {
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 +224,14 @@ export class SelectPageObject {
);
}

public isClearButtonVisible(): boolean {
return (
this.selectElement.shadowRoot?.querySelector<Button>(
'.clear-button'
) != null
);
atmgrifter00 marked this conversation as resolved.
Show resolved Hide resolved
}

public isFilterInputVisible(): boolean {
return (
this.selectElement.shadowRoot?.querySelector('.filter-field')
Expand Down Expand Up @@ -246,20 +260,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 +268,25 @@ export class SelectPageObject {
}
return this.selectElement.filterInput;
}

private getClearButton(): Button | null | undefined {
if (!this.selectElement.clearable) {
atmgrifter00 marked this conversation as resolved.
Show resolved Hide resolved
throw new Error(
'Select must set "clearable" in order to click clear button'
atmgrifter00 marked this conversation as resolved.
Show resolved Hide resolved
);
}

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

return this.selectElement.shadowRoot?.querySelector<Button>(
'.clear-button'
);
}
}
Loading
Loading