-
Notifications
You must be signed in to change notification settings - Fork 80
feat(combobox): make combobox clearable #6972
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
Changes from 4 commits
6b09704
a220970
57bc748
203f076
6718916
2c4b335
d453a83
10c7c50
4209421
c9d37de
a5a0512
ea4045c
e6dc7f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -127,11 +127,14 @@ export class ComboboxItem implements ConditionalSlotComponent, InteractiveCompon | |
// | ||
// -------------------------------------------------------------------------- | ||
|
||
toggleSelected(coerce?: boolean): Promise<void> { | ||
if (this.disabled) { | ||
toggleSelected(): Promise<void> { | ||
const isSingleSelect = getElementProp(this.el, "selection-mode", "multiple") === "single"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a note that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I remember. I'll submit a PR to rename it today, so it's not misleading when reading code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. related to #6038 |
||
|
||
if (this.disabled || (isSingleSelect && this.selected)) { | ||
return; | ||
} | ||
this.selected = typeof coerce === "boolean" ? coerce : !this.selected; | ||
|
||
this.selected = !this.selected; | ||
} | ||
|
||
itemClickHandler = (event: MouseEvent): void => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
{ | ||
"clear": "Clear value", | ||
"removeTag": "Remove tag" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
{ | ||
"clear": "Clear value", | ||
"removeTag": "Remove tag" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ | |
} | ||
|
||
@include disabled(); | ||
@include xButton(); | ||
|
||
:host([scale="s"]) { | ||
@apply text-n2; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,6 +64,7 @@ import { ComboboxMessages } from "./assets/combobox/t9n"; | |
import { ComboboxChildElement } from "./interfaces"; | ||
import { ComboboxChildSelector, ComboboxItem, ComboboxItemGroup } from "./resources"; | ||
import { getItemAncestors, getItemChildren, hasActiveChildren } from "./utils"; | ||
import { XButton, CSS as XButtonCSS } from "../functional/XButton"; | ||
|
||
interface ItemData { | ||
label: string; | ||
|
@@ -111,6 +112,11 @@ export class Combobox | |
// | ||
//-------------------------------------------------------------------------- | ||
|
||
/** | ||
* When `true`, a clear button is displayed when the component has a value. | ||
*/ | ||
@Prop({ reflect: true }) clearable = false; | ||
|
||
/**When `true`, displays and positions the component. */ | ||
@Prop({ reflect: true, mutable: true }) open = false; | ||
|
||
|
@@ -506,6 +512,19 @@ export class Combobox | |
// | ||
// -------------------------------------------------------------------------- | ||
|
||
private clearValue(): void { | ||
this.ignoreSelectedEventsFlag = true; | ||
jcfranco marked this conversation as resolved.
Show resolved
Hide resolved
|
||
this.items.forEach((el) => (el.selected = false)); | ||
this.ignoreSelectedEventsFlag = false; | ||
this.selectedItems = []; | ||
this.emitComboboxChange(); | ||
this.open = false; | ||
this.updateActiveItemIndex(-1); | ||
this.resetText(); | ||
this.filterItems(""); | ||
this.setFocus(); | ||
} | ||
|
||
setFilteredPlacements = (): void => { | ||
const { el, flipPlacements } = this; | ||
|
||
|
@@ -688,11 +707,20 @@ export class Combobox | |
}; | ||
|
||
clickHandler = (event: MouseEvent): void => { | ||
if (event.composedPath().some((node: HTMLElement) => node.tagName === "CALCITE-CHIP")) { | ||
const composedPath = event.composedPath(); | ||
|
||
if (composedPath.some((node: HTMLElement) => node.tagName === "CALCITE-CHIP")) { | ||
this.open = false; | ||
event.stopPropagation(); | ||
event.preventDefault(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @macandcheese Tests seem to pass, but do you recall why the original event needed to stop propagating? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It allowed focus to be set on the clicked Chip's close button, when user clicked the Chip, which was a change to support how Chips behaved post some Chip Group related changes. Although looking at it in action (and compared to past behavior), that probably wasn't needed, so this should be fine to remove, and have focus instead shift to Combobox. |
||
return; | ||
} | ||
|
||
if (composedPath.some((node: HTMLElement) => node.classList?.contains(XButtonCSS.button))) { | ||
this.clearValue(); | ||
event.preventDefault(); | ||
return; | ||
} | ||
|
||
this.open = !this.open; | ||
this.updateActiveItemIndex(0); | ||
this.setFocus(); | ||
|
@@ -1242,6 +1270,7 @@ export class Combobox | |
render(): VNode { | ||
const { guid, label, open } = this; | ||
const single = this.selectionMode === "single"; | ||
const isClearable = this.clearable && this.value.length > 0; | ||
|
||
return ( | ||
<Host onClick={this.comboboxFocusHandler}> | ||
|
@@ -1276,6 +1305,14 @@ export class Combobox | |
</label> | ||
{this.renderInput()} | ||
</div> | ||
{isClearable ? ( | ||
<XButton | ||
disabled={this.disabled} | ||
key="close-button" | ||
label={this.messages.clear} | ||
scale={this.scale} | ||
/> | ||
) : null} | ||
{this.renderIconEnd()} | ||
</div> | ||
<ul | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { FunctionalComponent, h } from "@stencil/core"; | ||
import { JSXAttributes, JSXBase } from "@stencil/core/internal"; | ||
import { Scale } from "../interfaces"; | ||
|
||
export interface XButtonOptions extends JSXAttributes { | ||
jcfranco marked this conversation as resolved.
Show resolved
Hide resolved
|
||
disabled: boolean; | ||
label: string; | ||
scale: Scale; | ||
onClick?: JSXBase.DOMAttributes<HTMLElement>["onClick"]; | ||
} | ||
|
||
export const CSS = { | ||
button: "x-button" | ||
}; | ||
|
||
export function XButton({ | ||
disabled, | ||
key, | ||
label, | ||
onClick, | ||
ref, | ||
scale | ||
}: XButtonOptions): FunctionalComponent { | ||
return ( | ||
<button | ||
aria-label={label} | ||
class={CSS.button} | ||
disabled={disabled} | ||
key={key} | ||
onClick={onClick} | ||
tabIndex={-1} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just confirming that clearing here only happens via mouse. A keyboard user would need to select all text and delete. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great point. I think I had it this way because of how it works in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Synced up w/ @ashetland. Following |
||
type="button" | ||
// eslint-disable-next-line react/jsx-sort-props | ||
ref={ref} | ||
> | ||
<calcite-icon icon="x" scale={scale === "l" ? "m" : "s"} /> | ||
</button> | ||
); | ||
} |
Uh oh!
There was an error while loading. Please reload this page.