diff --git a/skills/tedi-angular/references/components.md b/skills/tedi-angular/references/components.md index 157c5476f..4e499dbb6 100644 --- a/skills/tedi-angular/references/components.md +++ b/skills/tedi-angular/references/components.md @@ -201,6 +201,23 @@ Both entry points declare `[tedi-floating-button]`. The Community component is content. It is decorative and `aria-hidden`, which means the projected text is the whole accessible name — if the icon carries meaning the text doesn't, compose a heading and an icon yourself. +### Clear buttons + +- **`showClearOnInteraction` belongs on the component that renders the clear button.** For a text + field or textarea wrapped in ``, set it on the form field, not the + control. `` owns its form field and exposes the input itself; `` exposes + it for its own clear button. In each case, `clearable` must be true and the field must have a value. +- With the option enabled, the clear button appears while the field is hovered, active, or contains + focus (including keyboard focus). The default is false, which keeps the clear button visible + whenever the clearable field has a value. + +```html + + + + +``` + ### Search suggestions - **`tedi-search` does not filter.** Bind `suggestions` to a list you have already filtered and react @@ -233,9 +250,9 @@ Both entry points declare `[tedi-floating-button]`. The Community component is - **`tedi-table`, not `tedi-table-styles`.** The Community `tedi-table-styles` only paints a hand-rolled ``; the TEDI-Ready `tedi-table` brings TanStack sorting, filtering and pagination. -- **`tedi-form-field` is only needed for a label, feedback text, or a `characterLimit` counter.** - Controls paint their own field surface, so wrapping is otherwise redundant. `tedi-search` renders - its own and must **not** be wrapped. +- **`tedi-form-field` adds a label, feedback text, a `characterLimit` counter, an icon, or a clear + button to controls that support it.** A standalone text field paints its own surface, so wrapping + it is otherwise redundant. `tedi-search` renders its own form field and must **not** be wrapped. - **`tedi-heading-with-icon` over a hand-composed `` plus `tedi-icon`** when the heading can wrap: it centres the icon on the *first line* rather than the middle of the block. A heading value in `modifiers` overrides `element`'s typography and the icon follows it, so diff --git a/src/tedi/components/form/form-field/form-field.component.scss b/src/tedi/components/form/form-field/form-field.component.scss index 5016bc255..faa979831 100644 --- a/src/tedi/components/form/form-field/form-field.component.scss +++ b/src/tedi/components/form/form-field/form-field.component.scss @@ -74,6 +74,16 @@ } } + &--clear-on-interaction &__buttons { + visibility: hidden; + } + + &--clear-on-interaction &__box:hover &__buttons, + &--clear-on-interaction &__box:active &__buttons, + &--clear-on-interaction &__box:focus-within &__buttons { + visibility: visible; + } + &__clear:disabled { cursor: not-allowed; } diff --git a/src/tedi/components/form/form-field/form-field.component.spec.ts b/src/tedi/components/form/form-field/form-field.component.spec.ts index 69d89cb73..cb07de527 100644 --- a/src/tedi/components/form/form-field/form-field.component.spec.ts +++ b/src/tedi/components/form/form-field/form-field.component.spec.ts @@ -45,6 +45,7 @@ class MockControlComponent implements FormFieldControl { [size]="size" [icon]="icon" [clearable]="clearable" + [showClearOnInteraction]="showClearOnInteraction" [inputClass]="inputClass" [characterLimit]="characterLimit" > @@ -66,6 +67,7 @@ class TestHostComponent { size: InputSize = "default"; icon?: string | FormFieldIcon; clearable = false; + showClearOnInteraction = false; inputClass?: string; characterLimit?: number; feedbackType: "valid" | "error" | "hint" = "hint"; @@ -174,6 +176,36 @@ describe("FormFieldComponent", () => { expect(button.hasAttribute("disabled")).toBe(false); }); + it("applies the interaction modifier only to a filled clearable field when opted in", () => { + const field = fixture.nativeElement.querySelector("tedi-form-field"); + host.showClearOnInteraction = true; + fixture.detectChanges(); + expect( + field.classList.contains("tedi-form-field--clear-on-interaction"), + ).toBe(false); + + host.clearable = true; + host.mockControl.value.set("Test"); + fixture.detectChanges(); + expect( + field.classList.contains("tedi-form-field--clear-on-interaction"), + ).toBe(true); + expect(field.querySelector(".tedi-form-field__clear")).toBeTruthy(); + + host.mockControl.value.set(""); + fixture.detectChanges(); + expect( + field.classList.contains("tedi-form-field--clear-on-interaction"), + ).toBe(false); + + host.mockControl.value.set("Test"); + host.showClearOnInteraction = false; + fixture.detectChanges(); + expect( + field.classList.contains("tedi-form-field--clear-on-interaction"), + ).toBe(false); + }); + it("should not render buttons slot when clearable is false", () => { host.clearable = false; host.mockControl.value.set("Test"); diff --git a/src/tedi/components/form/form-field/form-field.component.ts b/src/tedi/components/form/form-field/form-field.component.ts index 1d69e5f83..ed4422635 100644 --- a/src/tedi/components/form/form-field/form-field.component.ts +++ b/src/tedi/components/form/form-field/form-field.component.ts @@ -87,6 +87,12 @@ export class FormFieldComponent implements FieldContext { * @default false */ clearable = input(false); + /** + * Show the clear button only while the filled field is hovered or focused. + * Requires `clearable`. + * @default false + */ + showClearOnInteraction = input(false); /** * Custom CSS classes for the field box. * @@ -232,6 +238,8 @@ export class FormFieldComponent implements FieldContext { "tedi-form-field--valid": this.validationState() === "valid", "tedi-form-field--invalid": this.validationState() === "invalid", "tedi-form-field--disabled": this.isDisabled(), + "tedi-form-field--clear-on-interaction": + this.showClearOnInteraction() && this.showClearButton(), "tedi-form-field--small": this.size() === "small", "tedi-form-field--large": this.size() === "large", }; diff --git a/src/tedi/components/form/search/search.component.html b/src/tedi/components/form/search/search.component.html index c322548cf..906f520fb 100644 --- a/src/tedi/components/form/search/search.component.html +++ b/src/tedi/components/form/search/search.component.html @@ -5,6 +5,7 @@ [size]="size()" [icon]="fieldIcon()" [clearable]="clearable() && !isDisabled()" + [showClearOnInteraction]="showClearOnInteraction()" > @if (label()) {