diff --git a/change/@fluentui-web-components-e7635a3f-6231-44e9-9ab4-bc1fdb86052c.json b/change/@fluentui-web-components-e7635a3f-6231-44e9-9ab4-bc1fdb86052c.json new file mode 100644 index 0000000000000..14ed7c05f5c84 --- /dev/null +++ b/change/@fluentui-web-components-e7635a3f-6231-44e9-9ab4-bc1fdb86052c.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "update label to use element internals custom states for styling", + "packageName": "@fluentui/web-components", + "email": "13071055+chrisdholt@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/web-components/docs/api-report.md b/packages/web-components/docs/api-report.md index 9b09601dbb3d6..119880d2028db 100644 --- a/packages/web-components/docs/api-report.md +++ b/packages/web-components/docs/api-report.md @@ -2146,9 +2146,14 @@ export const ImageTemplate: ElementViewTemplate; // @public export class Label extends FASTElement { disabled: boolean; + disabledChanged(prev: boolean | undefined, next: boolean | undefined): void; + // @internal + elementInternals: ElementInternals; required: boolean; size?: LabelSize; + sizeChanged(prev: LabelSize | undefined, next: LabelSize | undefined): void; weight?: LabelWeight; + weightChanged(prev: LabelWeight | undefined, next: LabelWeight | undefined): void; } // @public @@ -2883,9 +2888,11 @@ export const spacingVerticalXXXL = "var(--spacingVerticalXXXL)"; export class Spinner extends FASTElement { constructor(); appearance?: SpinnerAppearance; + appearanceChanged(prev: SpinnerAppearance | undefined, next: SpinnerAppearance | undefined): void; // @internal - protected elementInternals: ElementInternals; + elementInternals: ElementInternals; size?: SpinnerSize; + sizeChanged(prev: SpinnerSize | undefined, next: SpinnerSize | undefined): void; } // @public diff --git a/packages/web-components/src/label/label.spec.ts b/packages/web-components/src/label/label.spec.ts index 7aa08fe5a8f18..1837248632d79 100644 --- a/packages/web-components/src/label/label.spec.ts +++ b/packages/web-components/src/label/label.spec.ts @@ -21,14 +21,6 @@ test.describe('Label', () => { await page.close(); }); - test('should set default attribute values', async () => { - await expect(element).toHaveAttribute('size', 'medium'); - await expect(element).toHaveJSProperty('size', 'medium'); - - await expect(element).toHaveAttribute('weight', 'regular'); - await expect(element).toHaveJSProperty('weight', 'regular'); - }); - test('should reflect size attribute', async () => { await element.evaluate((node: Label) => { node.size = 'small'; @@ -36,6 +28,7 @@ test.describe('Label', () => { await expect(element).toHaveAttribute('size', 'small'); await expect(element).toHaveJSProperty('size', 'small'); + expect(await element.evaluate((node: Label) => node.elementInternals.states.has('small'))).toBe(true); await element.evaluate((node: Label) => { node.size = 'medium'; @@ -43,12 +36,16 @@ test.describe('Label', () => { await expect(element).toHaveAttribute('size', 'medium'); await expect(element).toHaveJSProperty('size', 'medium'); + expect(await element.evaluate((node: Label) => node.elementInternals.states.has('small'))).toBe(false); + expect(await element.evaluate((node: Label) => node.elementInternals.states.has('medium'))).toBe(true); await element.evaluate((node: Label) => { node.size = 'large'; }); await expect(element).toHaveAttribute('size', 'large'); await expect(element).toHaveJSProperty('size', 'large'); + expect(await element.evaluate((node: Label) => node.elementInternals.states.has('medium'))).toBe(false); + expect(await element.evaluate((node: Label) => node.elementInternals.states.has('large'))).toBe(true); }); test('should reflect weight attribute', async () => { @@ -57,12 +54,15 @@ test.describe('Label', () => { }); await expect(element).toHaveAttribute('weight', 'regular'); await expect(element).toHaveJSProperty('weight', 'regular'); + expect(await element.evaluate((node: Label) => node.elementInternals.states.has('regular'))).toBe(true); await element.evaluate((node: Label) => { node.weight = 'semibold'; }); await expect(element).toHaveAttribute('weight', 'semibold'); await expect(element).toHaveJSProperty('weight', 'semibold'); + expect(await element.evaluate((node: Label) => node.elementInternals.states.has('regular'))).toBe(false); + expect(await element.evaluate((node: Label) => node.elementInternals.states.has('semibold'))).toBe(true); }); test('should reflect disabled attribute', async () => { @@ -74,6 +74,7 @@ test.describe('Label', () => { await expect(element).toHaveAttribute('disabled', ''); await expect(element).toHaveJSProperty('disabled', true); + expect(await element.evaluate((node: Label) => node.elementInternals.states.has('disabled'))).toBe(true); }); test('should reflect required attribute and show asterisk', async () => { @@ -102,42 +103,4 @@ test.describe('Label', () => { await expect(element).toHaveJSProperty('required', false); await expect(asterisk).toBeHidden(); }); - - test('should reflect changes in size, weight, disabled, and required attributes', async () => { - await root.evaluate(node => { - node.innerHTML = /* html */ ` - Label - `; - }); - await expect(element).toHaveAttribute('size', 'medium'); - await expect(element).toHaveJSProperty('size', 'medium'); - - await expect(element).toHaveAttribute('weight', 'regular'); - await expect(element).toHaveJSProperty('weight', 'regular'); - - await expect(element).not.toHaveAttribute('disabled', ''); - await expect(element).toHaveJSProperty('disabled', false); - - await expect(element).not.toHaveAttribute('required', ''); - await expect(element).toHaveJSProperty('required', false); - - await element.evaluate((node: Label) => { - node.size = 'large'; - node.weight = 'semibold'; - node.disabled = true; - node.required = true; - }); - - await expect(element).toHaveAttribute('size', 'large'); - await expect(element).toHaveJSProperty('size', 'large'); - - await expect(element).toHaveAttribute('weight', 'semibold'); - await expect(element).toHaveJSProperty('weight', 'semibold'); - - await expect(element).toHaveAttribute('disabled', ''); - await expect(element).toHaveJSProperty('disabled', true); - - await expect(element).toHaveAttribute('required', ''); - await expect(element).toHaveJSProperty('required', true); - }); }); diff --git a/packages/web-components/src/label/label.styles.ts b/packages/web-components/src/label/label.styles.ts index 9180f15a62c9f..efa284e405cda 100644 --- a/packages/web-components/src/label/label.styles.ts +++ b/packages/web-components/src/label/label.styles.ts @@ -15,6 +15,7 @@ import { lineHeightBase400, spacingHorizontalXS, } from '../theme/design-tokens.js'; +import { largeState, smallState } from '../styles/states/index.js'; /** Label styles * @public @@ -37,23 +38,23 @@ export const styles = css` margin-inline-start: ${spacingHorizontalXS}; } - :host([size='small']) { + :host(${smallState}) { font-size: ${fontSizeBase200}; line-height: ${lineHeightBase200}; } - :host([size='large']) { + :host(${largeState}) { font-size: ${fontSizeBase400}; line-height: ${lineHeightBase400}; } - :host([size='large']), - :host([weight='semibold']) { + :host(${largeState}), + :host(:is([state--semibold], :state(semibold))) { font-weight: ${fontWeightSemibold}; } - :host([disabled]), - :host([disabled]) .asterisk { + :host(:is([state--disabled], :state(disabled))), + :host(:is([state--disabled], :state(disabled))) .asterisk { color: ${colorNeutralForegroundDisabled}; } `; diff --git a/packages/web-components/src/label/label.ts b/packages/web-components/src/label/label.ts index 6d4c88c6e4430..0ae400a5b72e7 100644 --- a/packages/web-components/src/label/label.ts +++ b/packages/web-components/src/label/label.ts @@ -1,4 +1,5 @@ import { attr, FASTElement } from '@microsoft/fast-element'; +import { toggleState } from '../utils/element-internals.js'; import { LabelSize, LabelWeight } from './label.options.js'; /** @@ -6,6 +7,13 @@ import { LabelSize, LabelWeight } from './label.options.js'; * @public */ export class Label extends FASTElement { + /** + * The internal {@link https://developer.mozilla.org/docs/Web/API/ElementInternals | `ElementInternals`} instance for the component. + * + * @internal + */ + public elementInternals: ElementInternals = this.attachInternals(); + /** * Specifies font size of a label * @@ -16,6 +24,20 @@ export class Label extends FASTElement { @attr public size?: LabelSize; + /** + * Handles changes to size attribute custom states + * @param prev - the previous state + * @param next - the next state + */ + public sizeChanged(prev: LabelSize | undefined, next: LabelSize | undefined) { + if (prev) { + toggleState(this.elementInternals, `${prev}`, false); + } + if (next) { + toggleState(this.elementInternals, `${next}`, true); + } + } + /** * Specifies font weight of a label * @@ -26,6 +48,20 @@ export class Label extends FASTElement { @attr public weight?: LabelWeight; + /** + * Handles changes to weight attribute custom states + * @param prev - the previous state + * @param next - the next state + */ + public weightChanged(prev: LabelWeight | undefined, next: LabelWeight | undefined) { + if (prev) { + toggleState(this.elementInternals, `${prev}`, false); + } + if (next) { + toggleState(this.elementInternals, `${next}`, true); + } + } + /** * Specifies styles for label when associated input is disabled * @@ -36,6 +72,15 @@ export class Label extends FASTElement { @attr({ mode: 'boolean' }) public disabled: boolean = false; + /** + * Handles changes to disabled attribute custom states + * @param prev - the previous state + * @param next - the next state + */ + public disabledChanged(prev: boolean | undefined, next: boolean | undefined) { + toggleState(this.elementInternals, 'disabled', next); + } + /** * Specifies styles for label when associated input is a required field *