diff --git a/change/@microsoft-fast-foundation-69119fce-3da9-46bf-a21b-c645b441fcab.json b/change/@microsoft-fast-foundation-69119fce-3da9-46bf-a21b-c645b441fcab.json new file mode 100644 index 00000000000..251ffd182c4 --- /dev/null +++ b/change/@microsoft-fast-foundation-69119fce-3da9-46bf-a21b-c645b441fcab.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Foundation: Update Number field, Search, Switch, Text area, and Text field templates (https://github.com/microsoft/fast/pull/6798)", + "packageName": "@microsoft/fast-foundation", + "email": "47367562+bheston@users.noreply.github.com", + "dependentChangeType": "prerelease" +} diff --git a/packages/web-components/fast-foundation/docs/api-report.md b/packages/web-components/fast-foundation/docs/api-report.md index 77b70a95936..c246768e513 100644 --- a/packages/web-components/fast-foundation/docs/api-report.md +++ b/packages/web-components/fast-foundation/docs/api-report.md @@ -1481,10 +1481,10 @@ export class FASTNumberField extends FormAssociatedNumberField { // @internal connectedCallback(): void; // @internal - control: HTMLInputElement; - // @internal defaultSlottedNodes: Node[]; // @internal + field: HTMLInputElement; + // @internal handleBlur(): void; // @internal handleChange(): void; @@ -1738,11 +1738,11 @@ export class FASTSearch extends FormAssociatedSearch { protected autofocusChanged(): void; // @internal (undocumented) connectedCallback(): void; - // @internal - control: HTMLInputElement; // @internal (undocumented) defaultSlottedNodes: Node[]; // @internal + field: HTMLInputElement; + // @internal handleChange(): void; handleClearInput(): void; // @internal @@ -1765,8 +1765,6 @@ export class FASTSearch extends FormAssociatedSearch { readOnly: boolean; // (undocumented) protected readOnlyChanged(): void; - // @internal - root: HTMLDivElement; size: number; // (undocumented) protected sizeChanged(): void; @@ -2009,10 +2007,10 @@ export class FASTTextArea extends FormAssociatedTextArea { // (undocumented) protected autofocusChanged(): void; cols: number; - // @internal - control: HTMLTextAreaElement; // @internal (undocumented) defaultSlottedNodes: Node[]; + // @internal + field: HTMLTextAreaElement; formId: string; // @internal handleChange(): void; @@ -2042,7 +2040,7 @@ export class FASTTextArea extends FormAssociatedTextArea { } // @internal -export interface FASTTextArea extends DelegatesARIATextbox { +export interface FASTTextArea extends StartEnd, DelegatesARIATextbox { } // Warning: (ae-different-release-tags) This symbol has another declaration with a different release tag @@ -2056,11 +2054,11 @@ export class FASTTextField extends FormAssociatedTextField { protected autofocusChanged(): void; // @internal (undocumented) connectedCallback(): void; - // @internal - control: HTMLInputElement; // @internal (undocumented) defaultSlottedNodes: Node[]; // @internal + field: HTMLInputElement; + // @internal handleChange(): void; // @internal handleTextInput(): void; @@ -2508,8 +2506,8 @@ export function noninteractiveCalendarTemplate(options: // @public export type NumberFieldOptions = StartEndOptions & { - stepDownGlyph?: StaticallyComposableHTML; - stepUpGlyph?: StaticallyComposableHTML; + stepDownIcon?: StaticallyComposableHTML; + stepUpIcon?: StaticallyComposableHTML; }; // @public @@ -2632,7 +2630,9 @@ export const ScrollEasing: { export type ScrollEasing = ValuesOf; // @public -export type SearchOptions = StartEndOptions; +export type SearchOptions = StartEndOptions & { + clearIcon?: StaticallyComposableHTML; +}; // @public export function searchTemplate(options?: SearchOptions): ElementViewTemplate; @@ -2732,7 +2732,7 @@ export const supportsElementInternals: boolean; // @public export type SwitchOptions = { - switch?: StaticallyComposableHTML; + thumb?: StaticallyComposableHTML; }; // @public @@ -2768,6 +2768,9 @@ export function tagFor(dependency: TemplateElementDependency): string; // @beta export type TemplateElementDependency = string | FASTElementDefinition | Constructable; +// @public +export type TextAreaOptions = StartEndOptions; + // @public export const TextAreaResize: { readonly none: "none"; @@ -2780,7 +2783,7 @@ export const TextAreaResize: { export type TextAreaResize = ValuesOf; // @public -export function textAreaTemplate(): ElementViewTemplate; +export function textAreaTemplate(options?: TextAreaOptions): ElementViewTemplate; // @public export type TextFieldOptions = StartEndOptions; diff --git a/packages/web-components/fast-foundation/src/number-field/README.md b/packages/web-components/fast-foundation/src/number-field/README.md index 313010ef670..16fe9a42bf4 100644 --- a/packages/web-components/fast-foundation/src/number-field/README.md +++ b/packages/web-components/fast-foundation/src/number-field/README.md @@ -161,24 +161,24 @@ This component is built with the expectation that focus is delegated to the inpu #### CSS Parts -| Name | Description | -| ----------- | --------------------------------------------------------------- | -| `label` | The label | -| `root` | The element wrapping the control, including start and end slots | -| `control` | The element representing the input | -| `controls` | The step up and step down controls | -| `step-up` | The step up control | -| `step-down` | The step down control | +| Name | Description | +| -------------- | ---------------------------------------------------------------------------------------- | +| `label` | The label | +| `control` | The logical control, the element wrapping the input field, including start and end slots | +| `field` | The element representing the input field | +| `step-buttons` | The step up and step down controls | +| `step-up` | The step up control | +| `step-down` | The step down control | #### Slots -| Name | Description | -| ----------------- | ----------------------------------------------------------- | -| `start` | Content which can be provided before the number field input | -| `end` | Content which can be provided after the number field input | -| | The default slot for the label | -| `step-up-glyph` | The glyph for the step up control | -| `step-down-glyph` | The glyph for the step down control | +| Name | Description | +| ---------------- | ----------------------------------------------------------- | +| `start` | Content which can be provided before the number field input | +| `end` | Content which can be provided after the number field input | +| | The default slot for the label | +| `step-up-icon` | The icon for the step up control | +| `step-down-icon` | The icon for the step down control |
diff --git a/packages/web-components/fast-foundation/src/number-field/number-field.pw.spec.ts b/packages/web-components/fast-foundation/src/number-field/number-field.pw.spec.ts index 17fa573e56e..6c993f8b2d6 100644 --- a/packages/web-components/fast-foundation/src/number-field/number-field.pw.spec.ts +++ b/packages/web-components/fast-foundation/src/number-field/number-field.pw.spec.ts @@ -17,7 +17,7 @@ test.describe("NumberField", () => { root = page.locator("#root"); - control = element.locator(".control"); + control = element.locator(".field"); await page.goto(fixtureURL("number-field--number-field")); }); diff --git a/packages/web-components/fast-foundation/src/number-field/number-field.spec.md b/packages/web-components/fast-foundation/src/number-field/number-field.spec.md index c8c9a2cd918..d201a07df75 100644 --- a/packages/web-components/fast-foundation/src/number-field/number-field.spec.md +++ b/packages/web-components/fast-foundation/src/number-field/number-field.spec.md @@ -52,9 +52,10 @@ Extends FAST Element
- + - +
+
``` @@ -67,8 +68,8 @@ Extends FAST Element *Slot Names* - default - the label content -- before-content - often times a glyph, icon, or button precedes input -- after-content - often times a glyph, icon, or button follows the input +- start - often times a glyph, icon, or button precedes input +- end - often times a glyph, icon, or button follows the input *Host Classes* - disabled @@ -78,9 +79,10 @@ Extends FAST Element *CSS Parts* - label - root -- before-content -- after-content +- start +- end - control +- step-buttons --- diff --git a/packages/web-components/fast-foundation/src/number-field/number-field.template.ts b/packages/web-components/fast-foundation/src/number-field/number-field.template.ts index fa01d9b54f7..4bc9b5afe8d 100644 --- a/packages/web-components/fast-foundation/src/number-field/number-field.template.ts +++ b/packages/web-components/fast-foundation/src/number-field/number-field.template.ts @@ -13,7 +13,7 @@ export function numberFieldTemplate( return html` -
+
${startSlotTemplate(options)} ( aria-owns="${x => x.ariaOwns}" aria-relevant="${x => x.ariaRelevant}" aria-roledescription="${x => x.ariaRoledescription}" - ${ref("control")} + ${ref("field")} /> ${when( x => !x.hideStep && !x.readOnly && !x.disabled, html` -
+
- - ${staticallyCompose(options.stepUpGlyph)} + + ${staticallyCompose(options.stepUpIcon)}
( part="step-down" @click="${x => x.stepDown()}" > - - ${staticallyCompose(options.stepDownGlyph)} + + ${staticallyCompose(options.stepDownIcon)}
diff --git a/packages/web-components/fast-foundation/src/number-field/number-field.ts b/packages/web-components/fast-foundation/src/number-field/number-field.ts index c67b9691a21..dc6a0f0352e 100644 --- a/packages/web-components/fast-foundation/src/number-field/number-field.ts +++ b/packages/web-components/fast-foundation/src/number-field/number-field.ts @@ -17,8 +17,8 @@ import { FormAssociatedNumberField } from "./number-field.form-associated.js"; * @public */ export type NumberFieldOptions = StartEndOptions & { - stepDownGlyph?: StaticallyComposableHTML; - stepUpGlyph?: StaticallyComposableHTML; + stepDownIcon?: StaticallyComposableHTML; + stepUpIcon?: StaticallyComposableHTML; }; /** @@ -28,12 +28,12 @@ export type NumberFieldOptions = StartEndOptions & { * @slot start - Content which can be provided before the number field input * @slot end - Content which can be provided after the number field input * @slot - The default slot for the label - * @slot step-up-glyph - The glyph for the step up control - * @slot step-down-glyph - The glyph for the step down control + * @slot step-up-icon - The icon for the step up control + * @slot step-down-icon - The icon for the step down control * @csspart label - The label - * @csspart root - The element wrapping the control, including start and end slots - * @csspart control - The element representing the input - * @csspart controls - The step up and step down controls + * @csspart control - The logical control, the element wrapping the input field, including start and end slots + * @csspart field - The element representing the input field + * @csspart step-buttons - The step up and step down controls * @csspart step-up - The step up control * @csspart step-down - The step down control * @fires input - Fires a custom 'input' event when the value has changed @@ -184,10 +184,10 @@ export class FASTNumberField extends FormAssociatedNumberField { public defaultSlottedNodes: Node[]; /** - * A reference to the internal input element + * A reference to the internal field element * @internal */ - public control: HTMLInputElement; + public field: HTMLInputElement; /** * Flag to indicate that the value change is from the user input @@ -212,7 +212,6 @@ export class FASTNumberField extends FormAssociatedNumberField { * Validates that the value is a number between the min and max * @param previous - previous stored value * @param next - value being updated - * @param updateControl - should the text field be updated with value, defaults to true * @internal */ public valueChanged(previous: string, next: string): void { @@ -223,8 +222,8 @@ export class FASTNumberField extends FormAssociatedNumberField { return; } - if (this.$fastController.isConnected && this.control?.value !== value) { - this.control.value = this.value; + if (this.$fastController.isConnected && this.field?.value !== value) { + this.field.value = this.value; } super.valueChanged(previous, this.value); @@ -239,7 +238,7 @@ export class FASTNumberField extends FormAssociatedNumberField { /** {@inheritDoc (FormAssociated:interface).validate} */ public validate(): void { - super.validate(this.control); + super.validate(this.field); } /** @@ -309,7 +308,7 @@ export class FASTNumberField extends FormAssociatedNumberField { this.proxy.setAttribute("type", "number"); this.validate(); - this.control.value = this.value; + this.field.value = this.value; if (this.autofocus) { Updates.enqueue(() => { @@ -324,7 +323,7 @@ export class FASTNumberField extends FormAssociatedNumberField { * @public */ public select(): void { - this.control.select(); + this.field.select(); /** * The select event does not permeate the shadow DOM boundary. @@ -336,13 +335,13 @@ export class FASTNumberField extends FormAssociatedNumberField { } /** - * Handles the internal control's `input` event + * Handles the internal input field's `input` event * @internal */ public handleTextInput(): void { - this.control.value = this.control.value.replace(/[^0-9\-+e.]/g, ""); + this.field.value = this.field.value.replace(/[^0-9\-+e.]/g, ""); this.isUserInput = true; - this.value = this.control.value; + this.value = this.field.value; } /** @@ -383,11 +382,11 @@ export class FASTNumberField extends FormAssociatedNumberField { /** * Handles populating the input field with a validated value when - * leaving the input field. + * leaving the input field. * @internal */ public handleBlur(): void { - this.control.value = this.value; + this.field.value = this.value; } } diff --git a/packages/web-components/fast-foundation/src/number-field/stories/number-field.register.ts b/packages/web-components/fast-foundation/src/number-field/stories/number-field.register.ts index f4b460e8e74..8f2b9249f6f 100644 --- a/packages/web-components/fast-foundation/src/number-field/stories/number-field.register.ts +++ b/packages/web-components/fast-foundation/src/number-field/stories/number-field.register.ts @@ -1,5 +1,6 @@ -import { html } from "@microsoft/fast-element"; import { css } from "@microsoft/fast-element"; +import chevronDownIcon from "../../../statics/svg/chevron_down_12_regular.svg"; +import chevronUpIcon from "../../../statics/svg/chevron_up_12_regular.svg"; import { FASTNumberField } from "../number-field.js"; import { numberFieldTemplate } from "../number-field.template.js"; @@ -16,7 +17,7 @@ const styles = css` display: none; } - .root { + .control { align-items: center; background: var(--neutral-fill-input-rest); border-radius: calc(var(--control-corner-radius) * 1px); @@ -32,7 +33,7 @@ const styles = css` position: relative; } - .control { + .field { appearance: none; background: transparent; border: 0; @@ -40,19 +41,20 @@ const styles = css` color: inherit; font: inherit; height: calc(100% - 4px); + flex-grow: 1; margin-bottom: auto; margin-top: auto; padding: 0 calc(var(--design-unit) * 2px + 1px); } - .control:hover, - .control:focus-visible, - .control:disabled, - .control:active { + .field:hover, + .field:focus-visible, + .field:disabled, + .field:active { outline: none; } - .controls { + .step-buttons { opacity: 0; } @@ -69,67 +71,64 @@ const styles = css` visibility: hidden; } + ::slotted([slot="start"]), + ::slotted([slot="end"]), + .field, + .step-buttons { + align-self: center; + } + ::slotted([slot="start"]), ::slotted([slot="end"]) { display: flex; margin-inline: 11px; } - .step-up-glyph, - .step-down-glyph { - cursor: pointer; - display: block; - padding: 4px 10px; - } - - .step-up-glyph:before, - .step-down-glyph:before { - border: solid transparent 6px; - content: ""; - display: block; + .field.icon-only { + line-height: 0; + padding: 0; } - .step-up-glyph:before { - border-bottom-color: var(--neutral-foreground-rest); - } - - .step-down-glyph:before { - border-top-color: var(--neutral-foreground-rest); + .step-up, + .step-down { + cursor: pointer; + display: flex; + padding: 4px; } - :host(:hover:not([disabled])) .root { + :host(:hover:not([disabled])) .control { background: var(--neutral-fill-input-hover); border-color: var(--accent-fill-hover); } - :host(:active:not([disabled])) .root { + :host(:active:not([disabled])) .control { background: var(--neutral-fill-input-hover); border-color: var(--accent-fill-active); } - :host(:focus-within:not([disabled])) .root { + :host(:focus-within:not([disabled])) .control { border-color: var(--focus-stroke-outer); box-shadow: 0 0 0 calc(var(--focus-stroke-width) * 1px) var(--focus-stroke-outer) inset; } - :host(:hover:not([disabled])) .controls, - :host(:focus-within:not([disabled])) .controls { + :host(:hover:not([disabled])) .step-buttons, + :host(:focus-within:not([disabled])) .step-buttons { opacity: 1; } - :host([appearance="filled"]) .root { + :host([appearance="filled"]) .control { background: var(--neutral-fill-rest); } - :host([appearance="filled"]:hover:not([disabled])) .root { + :host([appearance="filled"]:hover:not([disabled])) .control { background: var(--neutral-fill-hover); } :host([disabled]) .label, :host([readonly]) .label, - :host([readonly]) .control, - :host([disabled]) .control { + :host([readonly]) .field, + :host([disabled]) .field { cursor: not-allowed; } @@ -137,7 +136,7 @@ const styles = css` opacity: var(--disabled-opacity); } - :host([disabled]) .control { + :host([disabled]) .field { border-color: var(--neutral-stroke-rest); } `; @@ -146,12 +145,8 @@ FASTNumberField.define({ name: "fast-number-field", styles, template: numberFieldTemplate({ - stepDownGlyph: /* html */ html` - - `, - stepUpGlyph: /* html */ html` - - `, + stepDownIcon: chevronDownIcon, + stepUpIcon: chevronUpIcon, }), shadowOptions: { delegatesFocus: true, diff --git a/packages/web-components/fast-foundation/src/search/README.md b/packages/web-components/fast-foundation/src/search/README.md index f2652eb0b05..0367b64f0d7 100644 --- a/packages/web-components/fast-foundation/src/search/README.md +++ b/packages/web-components/fast-foundation/src/search/README.md @@ -120,7 +120,7 @@ This component is built with the expectation that focus is delegated to the inpu | `sizeChanged` | protected | | | `void` | | | `spellcheckChanged` | protected | | | `void` | | | `validate` | public | {@inheritDoc (FormAssociated:interface).validate} | | `void` | | -| `handleClearInput` | public | Handles the control's clear value event | | `void` | | +| `handleClearInput` | public | Clears the value | | `void` | | #### Attributes @@ -138,12 +138,12 @@ This component is built with the expectation that focus is delegated to the inpu #### CSS Parts -| Name | Description | -| -------------- | --------------------------------------------------------------- | -| `label` | The label | -| `root` | The element wrapping the control, including start and end slots | -| `control` | The element representing the input | -| `clear-button` | The button to clear the input | +| Name | Description | +| -------------- | ---------------------------------------------------------------------------------------- | +| `label` | The label | +| `control` | The logical control, the element wrapping the input field, including start and end slots | +| `field` | The element representing the input field | +| `clear-button` | The button to clear the input | #### Slots @@ -153,7 +153,7 @@ This component is built with the expectation that focus is delegated to the inpu | `end` | Content which can be provided after the search clear button | | | The default slot for the label | | `clear-button` | The clear button | -| `clear-glyph` | The clear glyph | +| `clear-icon` | The clear icon |
diff --git a/packages/web-components/fast-foundation/src/search/search.pw.spec.ts b/packages/web-components/fast-foundation/src/search/search.pw.spec.ts index 5a3f8669440..bc393c15979 100644 --- a/packages/web-components/fast-foundation/src/search/search.pw.spec.ts +++ b/packages/web-components/fast-foundation/src/search/search.pw.spec.ts @@ -8,7 +8,7 @@ test.describe("Search", () => { let page: Page; let element: Locator; let root: Locator; - let control: Locator; + let field: Locator; test.beforeAll(async ({ browser }) => { page = await browser.newPage(); @@ -17,7 +17,7 @@ test.describe("Search", () => { root = page.locator("#root"); - control = element.locator(".control"); + field = element.locator(".field"); await page.goto(fixtureURL("search--search")); }); @@ -95,7 +95,7 @@ test.describe("Search", () => { { attrToken, value } ); - await expect(control).toHaveAttribute(spinalCase(attribute), `${value}`); + await expect(field).toHaveAttribute(spinalCase(attribute), `${value}`); }); } }); @@ -222,7 +222,7 @@ test.describe("Search", () => { `; }); - const control = element.locator(".control"); + const field = element.locator(".field"); const [wasChanged] = await Promise.all([ element.evaluate( @@ -231,7 +231,7 @@ test.describe("Search", () => { node.addEventListener("change", () => resolve(true)); }) ), - control.evaluate(node => { + field.evaluate(node => { node.dispatchEvent(new KeyboardEvent("change")); }), ]); diff --git a/packages/web-components/fast-foundation/src/search/search.template.ts b/packages/web-components/fast-foundation/src/search/search.template.ts index cfe6ae3f517..232e1e9dd7b 100644 --- a/packages/web-components/fast-foundation/src/search/search.template.ts +++ b/packages/web-components/fast-foundation/src/search/search.template.ts @@ -1,4 +1,5 @@ import { ElementViewTemplate, html, ref, slotted } from "@microsoft/fast-element"; +import { staticallyCompose } from "../utilities/template-helpers.js"; import { endSlotTemplate, startSlotTemplate } from "../patterns/index.js"; import { whitespaceFilter } from "../utilities/whitespace-filter.js"; import type { FASTSearch, SearchOptions } from "./search.js"; @@ -13,7 +14,7 @@ export function searchTemplate( return html` -
+
${startSlotTemplate(options)} -
- - - - -
+ + + + ${endSlotTemplate(options)}
`; diff --git a/packages/web-components/fast-foundation/src/search/search.ts b/packages/web-components/fast-foundation/src/search/search.ts index f509c5aa7f7..994b15d58ad 100644 --- a/packages/web-components/fast-foundation/src/search/search.ts +++ b/packages/web-components/fast-foundation/src/search/search.ts @@ -7,12 +7,16 @@ import { import { ARIAGlobalStatesAndProperties, StartEnd } from "../patterns/index.js"; import type { StartEndOptions } from "../patterns/start-end.js"; import { applyMixins } from "../utilities/apply-mixins.js"; +import type { StaticallyComposableHTML } from "../utilities/template-helpers.js"; import { FormAssociatedSearch } from "./search.form-associated.js"; + /** * Search configuration options * @public */ -export type SearchOptions = StartEndOptions; +export type SearchOptions = StartEndOptions & { + clearIcon?: StaticallyComposableHTML; +}; /** * A Search Custom HTML Element. @@ -22,10 +26,10 @@ export type SearchOptions = StartEndOptions; * @slot end - Content which can be provided after the search clear button * @slot - The default slot for the label * @slot clear-button - The clear button - * @slot clear-glyph - The clear glyph + * @slot clear-icon - The clear icon * @csspart label - The label - * @csspart root - The element wrapping the control, including start and end slots - * @csspart control - The element representing the input + * @csspart control - The logical control, the element wrapping the input field, including start and end slots + * @csspart field - The element representing the input field * @csspart clear-button - The button to clear the input * * @public @@ -171,16 +175,10 @@ export class FASTSearch extends FormAssociatedSearch { public defaultSlottedNodes: Node[]; /** - * A reference to the internal close button element - * @internal - */ - public root: HTMLDivElement; - - /** - * A reference to the internal input element + * A reference to the internal field element * @internal */ - public control: HTMLInputElement; + public field: HTMLInputElement; /** * @internal @@ -199,24 +197,24 @@ export class FASTSearch extends FormAssociatedSearch { /** {@inheritDoc (FormAssociated:interface).validate} */ public validate(): void { - super.validate(this.control); + super.validate(this.field); } /** - * Handles the internal control's `input` event + * Handles the internal input field's `input` event * @internal */ public handleTextInput(): void { - this.value = this.control.value; + this.value = this.field.value; } /** - * Handles the control's clear value event + * Clears the value * @public */ public handleClearInput(): void { this.value = ""; - this.control.focus(); + this.field.focus(); this.handleChange(); } diff --git a/packages/web-components/fast-foundation/src/search/stories/search.register.ts b/packages/web-components/fast-foundation/src/search/stories/search.register.ts index 69457b68bf1..591750085a2 100644 --- a/packages/web-components/fast-foundation/src/search/stories/search.register.ts +++ b/packages/web-components/fast-foundation/src/search/stories/search.register.ts @@ -1,4 +1,5 @@ import { css } from "@microsoft/fast-element"; +import dismissIcon from "../../../statics/svg/dismiss_12_regular.svg"; import { FASTSearch } from "../search.js"; import { searchTemplate } from "../search.template.js"; @@ -6,16 +7,15 @@ const styles = css` :host([hidden]) { display: none; } - :host { - display: inline-block; - } :host { + display: inline-block; font-family: var(--body-font); outline: none; user-select: none; } - .root { + + .control { box-sizing: border-box; position: relative; display: flex; @@ -27,14 +27,15 @@ const styles = css` height: calc(var(--height-number) * 1px); align-items: center; } - .control { + + .field { -webkit-appearance: none; font: inherit; background: transparent; border: 0; color: inherit; height: calc(100% - 4px); - width: 100%; + flex-grow: 1; margin-top: auto; margin-bottom: auto; border: none; @@ -42,15 +43,18 @@ const styles = css` font-size: var(--type-ramp-base-font-size); line-height: var(--type-ramp-base-line-height); } - .control::-webkit-search-cancel-button { + + .field::-webkit-search-cancel-button { -webkit-appearance: none; } - .control:hover, - .control:focus-visible, - .control:disabled, - .control:active { + + .field:hover, + .field:focus-visible, + .field:disabled, + .field:active { outline: none; } + .clear-button { height: calc(100% - 2px); opacity: 0; @@ -67,23 +71,22 @@ const styles = css` font-family: var(--body-font); padding: 0 calc((10 + (var(--design-unit) * 2 * var(--density))) * 1px); } + .clear-button:hover { background: var(--neutral-fill-stealth-hover); } + .clear-button:active { background: var(--neutral-fill-stealth-active); } + :host([appearance="filled"]) .clear-button:hover { background: var(--clear-button-hover); } + :host([appearance="filled"]) .clear-button:active { background: var(--clear-button-active); } - .input-wrapper { - display: flex; - position: relative; - height: 100%; - } .label { display: block; color: var(--neutral-foreground-rest); @@ -92,63 +95,84 @@ const styles = css` line-height: var(--type-ramp-base-line-height); margin-bottom: 4px; } + .label__hidden { display: none; visibility: hidden; } + + ::slotted([slot="start"]), + ::slotted([slot="end"]), + .field { + align-self: center; + } + ::slotted([slot="start"]), ::slotted([slot="end"]) { display: flex; margin-inline: 11px; } - :host(:hover:not([disabled])) .root { + + :host(:hover:not([disabled])) .control { background: var(--neutral-fill-input-hover); border-color: var(--accent-fill-hover); } - :host(:active:not([disabled])) .root { + + :host(:active:not([disabled])) .control { background: var(--neutral-fill-input-hover); border-color: var(--accent-fill-active); } - :host(:focus-within:not([disabled])) .root { + + :host(:focus-within:not([disabled])) .control { border-color: var(--focus-stroke-outer); box-shadow: 0 0 0 1px var(--focus-stroke-outer) inset; } + .clear-button__hidden { opacity: 0; } + :host(:hover:not([disabled], [readonly])) .clear-button, :host(:active:not([disabled], [readonly])) .clear-button, :host(:focus-within:not([disabled], [readonly])) .clear-button { opacity: 1; } + :host(:hover:not([disabled], [readonly])) .clear-button__hidden, :host(:active:not([disabled], [readonly])) .clear-button__hidden, :host(:focus-within:not([disabled], [readonly])) .clear-button__hidden { opacity: 0; } - :host([appearance="filled"]) .root { + + :host([appearance="filled"]) .control { background: var(--fill-color); } - :host([appearance="filled"]:hover:not([disabled])) .root { + + :host([appearance="filled"]:hover:not([disabled])) .control { background: var(--neutral-fill-hover); } + :host([disabled]) .label, :host([readonly]) .label, - :host([readonly]) .control, - :host([disabled]) .control { + :host([readonly]) .field, + :host([disabled]) .field { cursor: var(--disabled-cursor); } + :host([disabled]) { opacity: var(--disabled-opacity); } - :host([disabled]) .control { + + :host([disabled]) .field { border-color: var(--neutral-stroke-rest); } `; FASTSearch.define({ name: "fast-search", - template: searchTemplate(), + template: searchTemplate({ + clearIcon: dismissIcon, + }), shadowOptions: { delegatesFocus: true, }, diff --git a/packages/web-components/fast-foundation/src/switch/README.md b/packages/web-components/fast-foundation/src/switch/README.md index 209a373c9fa..d55d4d98951 100644 --- a/packages/web-components/fast-foundation/src/switch/README.md +++ b/packages/web-components/fast-foundation/src/switch/README.md @@ -131,21 +131,18 @@ export const mySwitch = Switch.compose({ #### CSS Parts -| Name | Description | -| ------------------- | -------------------------------------------------------------- | -| `label` | The label | -| `switch` | The element representing the switch, which wraps the indicator | -| `status-message` | The wrapper for the status messages | -| `checked-message` | The checked message | -| `unchecked-message` | The unchecked message | +| Name | Description | +| --------- | ---------------------------------------------------------- | +| `label` | The label | +| `control` | The element representing the switch, which wraps the thumb | +| `thumb` | The thumb element | #### Slots -| Name | Description | -| ------------------- | -------------------------------------- | -| | The deafult slot for the label | -| `checked-message` | The message when in a checked state | -| `unchecked-message` | The message when in an unchecked state | +| Name | Description | +| ------- | ------------------------------- | +| | The default slot for the label | +| `thumb` | For content inside of the thumb |
diff --git a/packages/web-components/fast-foundation/src/switch/stories/switch.register.ts b/packages/web-components/fast-foundation/src/switch/stories/switch.register.ts index fd52ebb7ba8..42e823a2a1c 100644 --- a/packages/web-components/fast-foundation/src/switch/stories/switch.register.ts +++ b/packages/web-components/fast-foundation/src/switch/stories/switch.register.ts @@ -1,4 +1,3 @@ -import { html } from "@microsoft/fast-element"; import { css } from "@microsoft/fast-element"; import { FASTSwitch } from "../switch.js"; import { switchTemplate } from "../switch.template.js"; @@ -7,6 +6,7 @@ const styles = css` :host([hidden]) { display: none; } + :host { display: inline-flex; align-items: center; @@ -15,16 +15,19 @@ const styles = css` margin: calc(var(--design-unit) * 1px) 0; user-select: none; } + :host([disabled]) { opacity: var(--disabled-opacity); } + :host([disabled]) .label, :host([readonly]) .label, - :host([readonly]) .switch, - :host([disabled]) .switch { + :host([readonly]) .control, + :host([disabled]) .control { cursor: var(--disabled-cursor); } - .switch { + + .control { position: relative; outline: none; box-sizing: border-box; @@ -34,25 +37,24 @@ const styles = css` border-radius: calc(var(--control-corner-radius) * 1px); border: calc(var(--stroke-width) * 1px) solid var(--neutral-stroke-rest); } - .switch:hover { - background: var(--neutral-fill-input-hover); - border-color: var(--neutralStrokeHover); - cursor: pointer; - } - host([disabled]) .switch:hover, - host([readonly]) .switch:hover { + + :host(:not([disabled]):hover) .control, + :host(:not([readonly]):hover) .control { background: var(--neutral-fill-input-hover); border-color: var(--neutral-stroke-hover); - cursor: var(--disabled-cursor); + cursor: pointer; } - :host(:not([disabled])) .switch:active { + + :host(:not([disabled]):active) .control { background: var(--neutral-fill-input-active); border-color: var(--neutral-stroke-active); } - :host(:focus-visible) .switch { + + :host(:focus-visible) .control { box-shadow: 0 0 0 2px var(--fill-color), 0 0 0 4px var(--focusStrokeOuter); } - .checked-indicator { + + .thumb { position: absolute; top: 5px; bottom: 5px; @@ -60,6 +62,14 @@ const styles = css` border-radius: calc(var(--control-corner-radius) * 1px); transition: all 0.2s ease-in-out; } + + :host([disabled]) .control, + :host([readonly]) .control, + :host([disabled]) .status-message, + :host([readonly]) .status-message { + cursor: var(--disabled-cursor); + } + .label { color: var(--neutral-foreground-rest); margin-inline-end: calc(var(--design-unit) * 2px + 2px); @@ -67,39 +77,49 @@ const styles = css` line-height: var(--type-ramp-base-line-height); cursor: pointer; } + .label__hidden { display: none; visibility: hidden; } - :host([aria-checked="true"]) .checked-indicator { + + :host([aria-checked="true"]) .thumb { background: var(--foreground-on-accent-rest); } - :host([aria-checked="true"]) .switch { + + :host([aria-checked="true"]) .control { background: var(--accent-fill-rest); border-color: var(--accent-fill-rest); } - :host([aria-checked="true"]:not([disabled])) .switch:hover { + + :host([aria-checked="true"]:not([disabled]):hover) .control { background: var(--accent-fill-hover); border-color: var(--accent-fill-hover); } - :host([aria-checked="true"]:not([disabled])) .switch:hover .checked-indicator { + + :host([aria-checked="true"]:not([disabled]):hover) .control .thumb { background: var(--foreground-on-accent-hover); } - :host([aria-checked="true"]:not([disabled])) .switch:active { + + :host([aria-checked="true"]:not([disabled]):active) .control { background: var(--accent-fill-active); border-color: var(--accentt-fill-active); } - :host([aria-checked="true"]:not([disabled])) .switch:active .checked-indicator { + + :host([aria-checked="true"]:not([disabled]):active) .control .thumb { background: var(--foreground-on-accent-active); } - :host([aria-checked="true"]:focus-visible:not([disabled])) .switch { + + :host([aria-checked="true"]:focus-visible:not([disabled])) .control { box-shadow: 0 0 0 2px var(--fill-color), 0 0 0 4px var(--focus-stroke-outer); } - .checked-indicator { + + .thumb { left: 5px; right: calc(((var(--height-number) / 2) + 1) * 1px); } - :host([aria-checked="true"]) .checked-indicator { + + :host([aria-checked="true"]) .thumb { left: calc(((var(--height-number) / 2) + 1) * 1px); right: 5px; } @@ -107,10 +127,6 @@ const styles = css` FASTSwitch.define({ name: "fast-switch", - template: switchTemplate({ - switch: /* html */ html` - - `, - }), + template: switchTemplate(), styles, }); diff --git a/packages/web-components/fast-foundation/src/switch/switch.template.ts b/packages/web-components/fast-foundation/src/switch/switch.template.ts index 1ee39d71f09..1057c302cee 100644 --- a/packages/web-components/fast-foundation/src/switch/switch.template.ts +++ b/packages/web-components/fast-foundation/src/switch/switch.template.ts @@ -28,8 +28,10 @@ export function switchTemplate( > -
- ${staticallyCompose(options.switch)} +
+
+ ${staticallyCompose(options.thumb)} +
`; diff --git a/packages/web-components/fast-foundation/src/switch/switch.ts b/packages/web-components/fast-foundation/src/switch/switch.ts index 565a857cb08..19a054702f3 100644 --- a/packages/web-components/fast-foundation/src/switch/switch.ts +++ b/packages/web-components/fast-foundation/src/switch/switch.ts @@ -8,21 +8,18 @@ import { FormAssociatedSwitch } from "./switch.form-associated.js"; * @public */ export type SwitchOptions = { - switch?: StaticallyComposableHTML; + thumb?: StaticallyComposableHTML; }; /** * A Switch Custom HTML Element. * Implements the {@link https://www.w3.org/TR/wai-aria-1.1/#switch | ARIA switch }. * - * @slot - The deafult slot for the label - * @slot checked-message - The message when in a checked state - * @slot unchecked-message - The message when in an unchecked state + * @slot - The default slot for the label + * @slot thumb - For content inside of the thumb * @csspart label - The label - * @csspart switch - The element representing the switch, which wraps the indicator - * @csspart status-message - The wrapper for the status messages - * @csspart checked-message - The checked message - * @csspart unchecked-message - The unchecked message + * @csspart control - The element representing the switch, which wraps the thumb + * @csspart thumb - The thumb element * @fires change - Emits a custom change event when the checked state changes * * @public diff --git a/packages/web-components/fast-foundation/src/text-area/README.md b/packages/web-components/fast-foundation/src/text-area/README.md index 8315ecfa47d..5f4bde21f10 100644 --- a/packages/web-components/fast-foundation/src/text-area/README.md +++ b/packages/web-components/fast-foundation/src/text-area/README.md @@ -153,17 +153,19 @@ This component is built with the expectation that focus is delegated to the inpu #### CSS Parts -| Name | Description | -| --------- | -------------------------------- | -| `label` | The label | -| `root` | The element wrapping the control | -| `control` | The textarea element | +| Name | Description | +| --------- | ---------------------------------------------------------------------------------------- | +| `label` | The label | +| `control` | The logical control, the element wrapping the input field, including start and end slots | +| `field` | The textarea element | #### Slots -| Name | Description | -| ---- | ------------------------------ | -| | The default slot for the label | +| Name | Description | +| ------- | -------------------------------------------------------- | +| `start` | Content which can be provided before the text area input | +| `end` | Content which can be provided after the text area input | +| | The default slot for the label |
diff --git a/packages/web-components/fast-foundation/src/text-area/index.ts b/packages/web-components/fast-foundation/src/text-area/index.ts index 49290387222..82404d897d2 100644 --- a/packages/web-components/fast-foundation/src/text-area/index.ts +++ b/packages/web-components/fast-foundation/src/text-area/index.ts @@ -1,2 +1,2 @@ export { textAreaTemplate } from "./text-area.template.js"; -export { FASTTextArea, TextAreaResize } from "./text-area.js"; +export { FASTTextArea, TextAreaOptions, TextAreaResize } from "./text-area.js"; diff --git a/packages/web-components/fast-foundation/src/text-area/stories/text-area.register.ts b/packages/web-components/fast-foundation/src/text-area/stories/text-area.register.ts index 0c8a836a93d..d1cc20445e7 100644 --- a/packages/web-components/fast-foundation/src/text-area/stories/text-area.register.ts +++ b/packages/web-components/fast-foundation/src/text-area/stories/text-area.register.ts @@ -3,6 +3,10 @@ import { FASTTextArea } from "../text-area.js"; import { textAreaTemplate } from "../text-area.template.js"; const styles = css` + :host([hidden]) { + display: none; + } + :host { display: inline-block; font-family: var(--body-font); @@ -13,11 +17,11 @@ const styles = css` .control { box-sizing: border-box; position: relative; + display: flex; color: var(--neutral-foreground-rest); background: var(--neutral-fill-input-rest); border-radius: calc(var(--control-corner-radius) * 1px); border: calc(var(--stroke-width) * 1px) solid var(--accent-fill-rest); - height: calc(var(--height-number) * 2px); font: inherit; font-size: var(--type-ramp-base-font-size); line-height: var(--type-ramp-base-line-height); @@ -36,10 +40,26 @@ const styles = css` border-color: var(--accent-fill-active); } - .control:hover, - .control:focus-visible, - .control:disabled, - .control:active { + .field { + -webkit-appearance: none; + font: inherit; + background: transparent; + border: 0; + color: inherit; + height: calc(100% - 4px); + flex-grow: 1; + margin-top: auto; + margin-bottom: auto; + border: none; + padding: 0 calc(var(--design-unit) * 2px + 1px); + font-size: var(--type-ramp-base-font-size); + line-height: var(--type-ramp-base-line-height); + } + + .field:hover, + .field:focus-visible, + .field:disabled, + .field:active { outline: none; } @@ -56,15 +76,15 @@ const styles = css` background: var(--neutral-fill-hover); } - :host([resize="both"]) .control { + :host([resize="both"]) .field { resize: both; } - :host([resize="horizontal"]) .control { + :host([resize="horizontal"]) .field { resize: horizontal; } - :host([resize="vertical"]) .control { + :host([resize="vertical"]) .field { resize: vertical; } @@ -99,7 +119,7 @@ const styles = css` width: initial; } - :host([rows]) .control { + :host([rows]) .field { height: initial; } `; diff --git a/packages/web-components/fast-foundation/src/text-area/stories/text-area.stories.ts b/packages/web-components/fast-foundation/src/text-area/stories/text-area.stories.ts index 04a5366ef52..58b9c46b161 100644 --- a/packages/web-components/fast-foundation/src/text-area/stories/text-area.stories.ts +++ b/packages/web-components/fast-foundation/src/text-area/stories/text-area.stories.ts @@ -98,6 +98,15 @@ export default { export const TextArea: Story = renderComponent(storyTemplate).bind({}); +export const TextAreaWithSlottedStartEnd: Story = TextArea.bind({}); +TextAreaWithSlottedStartEnd.args = { + storyContent: html` + + Text Area + + `, +}; + export const TextAreaInForm: Story = renderComponent(html`
${storyTemplate} diff --git a/packages/web-components/fast-foundation/src/text-area/text-area.pw.spec.ts b/packages/web-components/fast-foundation/src/text-area/text-area.pw.spec.ts index b7850f432fb..21dfe343dd1 100644 --- a/packages/web-components/fast-foundation/src/text-area/text-area.pw.spec.ts +++ b/packages/web-components/fast-foundation/src/text-area/text-area.pw.spec.ts @@ -17,7 +17,7 @@ test.describe("TextArea", () => { root = page.locator("#root"); - control = element.locator(".control"); + control = element.locator(".field"); await page.goto(fixtureURL("text-area--text-area")); }); diff --git a/packages/web-components/fast-foundation/src/text-area/text-area.template.ts b/packages/web-components/fast-foundation/src/text-area/text-area.template.ts index 7daed26a765..d4dcaee46eb 100644 --- a/packages/web-components/fast-foundation/src/text-area/text-area.template.ts +++ b/packages/web-components/fast-foundation/src/text-area/text-area.template.ts @@ -1,15 +1,18 @@ import { ElementViewTemplate, html, ref, slotted } from "@microsoft/fast-element"; -import type { FASTTextArea } from "./text-area.js"; +import { endSlotTemplate, startSlotTemplate } from "../patterns/index.js"; +import type { FASTTextArea, TextAreaOptions } from "./text-area.js"; /** * The template for the {@link @microsoft/fast-foundation#(FASTTextArea:class)} component. * @public */ -export function textAreaTemplate(): ElementViewTemplate { +export function textAreaTemplate( + options: TextAreaOptions = {} +): ElementViewTemplate { return html` - +
+ ${startSlotTemplate(options)} + + ${endSlotTemplate(options)} +
`; } diff --git a/packages/web-components/fast-foundation/src/text-area/text-area.ts b/packages/web-components/fast-foundation/src/text-area/text-area.ts index e48f5163966..db2fb23e3f7 100644 --- a/packages/web-components/fast-foundation/src/text-area/text-area.ts +++ b/packages/web-components/fast-foundation/src/text-area/text-area.ts @@ -1,19 +1,29 @@ import { attr, nullableNumberConverter, observable } from "@microsoft/fast-element"; import { DelegatesARIATextbox } from "../text-field/text-field.js"; +import { StartEnd } from "../patterns/start-end.js"; +import type { StartEndOptions } from "../patterns/start-end.js"; import { applyMixins } from "../utilities/apply-mixins.js"; import { FormAssociatedTextArea } from "./text-area.form-associated.js"; import { TextAreaResize } from "./text-area.options.js"; export { TextAreaResize }; +/** + * Text area configuration options + * @public + */ +export type TextAreaOptions = StartEndOptions; + /** * A Text Area Custom HTML Element. * Based largely on the {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea |