diff --git a/src/components/input/bl-input.css b/src/components/input/bl-input.css index 18710769..1de23758 100644 --- a/src/components/input/bl-input.css +++ b/src/components/input/bl-input.css @@ -159,6 +159,10 @@ input::-webkit-calendar-picker-indicator { display: none; } +input::-webkit-search-cancel-button { + display: none; +} + input::-moz-calendar-picker-indicator { display: none; } @@ -193,7 +197,11 @@ input:-webkit-autofill { margin-inline-end: var(--label-padding); } -bl-icon:not(.reveal-icon), +.icon:has(.clear-icon) { + gap: var(--bl-size-3xs); +} + +bl-icon:not(.reveal-icon, .clear-icon), ::slotted(bl-icon) { font-size: var(--icon-size); color: var(--icon-color); @@ -315,3 +323,10 @@ bl-icon[name="eye_on"] { .dirty.invalid .error-icon { display: inline-block; } + +.split-divider { + display: block; + height: 1rem; + width: 1px; + background-color: var(--bl-color-neutral-lighter); +} diff --git a/src/components/input/bl-input.test.ts b/src/components/input/bl-input.test.ts index 1b6c7d5c..5d45c7f0 100644 --- a/src/components/input/bl-input.test.ts +++ b/src/components/input/bl-input.test.ts @@ -154,6 +154,18 @@ describe("bl-input", () => { expect(revealButton).to.have.style("display", "none"); }); + + it("should hide clear button for empty or non-search inputs", async () => { + const emptySearchEl = await fixture(html``); + const emptySearchCloseIcon = emptySearchEl?.shadowRoot?.querySelector('bl-icon[name="close"]'); + + expect(emptySearchCloseIcon).to.not.exist; + + const textInputEl = await fixture(html``); + const textInputCloseIcon = textInputEl?.shadowRoot?.querySelector('bl-icon[name="close"]'); + + expect(textInputCloseIcon).to.not.exist; + }); }); describe("validation", () => { @@ -319,6 +331,22 @@ describe("bl-input", () => { expect(input).to.attr("type", "text"); }); + it("should show clear button and clear value on clear button click", async () => { + const el = await fixture(html``); + const closeIcon = el?.shadowRoot?.querySelector('bl-icon[name="close"]') as HTMLElement | null; + const input = el?.shadowRoot?.querySelector("input"); + + expect(input).to.attr("type", "search"); + expect(closeIcon).to.exist; + expect(el.value).to.equal("test"); + + closeIcon?.click(); + await elementUpdated(el); + + expect(el.value).to.equal(""); + }); + + it("should fire bl-input event when input value changes", async () => { const el = await fixture(html``); const input = el.shadowRoot?.querySelector("input"); diff --git a/src/components/input/bl-input.ts b/src/components/input/bl-input.ts index 1c704909..ba59bf97 100644 --- a/src/components/input/bl-input.ts +++ b/src/components/input/bl-input.ts @@ -259,6 +259,13 @@ export default class BlInput extends FormControlMixin(LitElement) { this.passwordVisible = !this.passwordVisible; } + private async handleSearchClear() { + this.value = ""; + + await this.clearCustomError(); + this.validationTarget.focus(); + } + showPicker() { if ("showPicker" in HTMLInputElement.prototype) { this.validationTarget.showPicker(); @@ -391,6 +398,22 @@ export default class BlInput extends FormControlMixin(LitElement) { ` : ""; + const clearSearchButton = + this.type === "search" && this.value !== "" && this.value !== null + ? html` + + + +
+ ` + : ""; + const hasCustomIcon = this.icon || this._hasIconSlot; const classes = { "wrapper": true, @@ -430,7 +453,7 @@ export default class BlInput extends FormControlMixin(LitElement) { aria-describedby=${ifDefined(this.helpText ? "helpText" : undefined)} aria-errormessage=${ifDefined(this.checkValidity() ? undefined : "errorMessage")} /> -
${revealButton} ${icon}
+
${revealButton} ${clearSearchButton} ${icon}
${invalidMessage} ${helpMessage}
`;