Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 15 additions & 35 deletions 2nd-gen/packages/core/components/alert-banner/AlertBanner.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,47 +51,17 @@ export abstract class AlertBannerBase extends SpectrumElement {
public dismissible = false;

/**
* The variant applies specific styling when set to `negative` or `info`;
* `variant` attribute is removed when it's passed an invalid variant.
* The variant applies specific styling for the `neutral`, `info`, and
* `negative` states. Warn-only: an invalid value is left in place and a
* dev-mode warning is emitted rather than being coerced.
*/
@property({ type: String })
public set variant(variant: AlertBannerVariant) {
if (variant === this.variant) {
return;
}
const oldValue = this.variant;

if (this.isValidVariant(variant)) {
this.setAttribute('variant', variant);
this._variant = variant;
} else {
this.removeAttribute('variant');
this._variant = '';

validateEnum(this, {
prop: 'variant',
value: variant,
valid: ALERT_BANNER_VALID_VARIANTS,
url: 'https://spectrum-web-components.adobe.com/?path=/docs/components-alert-banner--docs',
});
}
this.requestUpdate('variant', oldValue);
}

public get variant(): AlertBannerVariant {
return this._variant;
}

private _variant: AlertBannerVariant = '';
@property({ type: String, reflect: true })
public variant: AlertBannerVariant = 'neutral';

// ──────────────────────
// IMPLEMENTATION
// ──────────────────────

protected isValidVariant(variant: string): boolean {
return (ALERT_BANNER_VALID_VARIANTS as readonly string[]).includes(variant);
}

protected abstract renderIcon(variant: string): TemplateResult;

protected shouldClose(): void {
Expand All @@ -117,6 +87,16 @@ export abstract class AlertBannerBase extends SpectrumElement {
}
}

protected override update(changes: PropertyValues): void {
validateEnum(this, {
prop: 'variant',
value: this.variant,
valid: ALERT_BANNER_VALID_VARIANTS,
url: 'https://spectrum-web-components.adobe.com/?path=/docs/components-alert-banner--docs',
});
super.update(changes);
}

protected override updated(changes: PropertyValues): void {
super.updated(changes);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,4 @@ export const ALERT_BANNER_VALID_VARIANTS = [
'negative',
] as const satisfies readonly string[];

export type AlertBannerVariant =
| (typeof ALERT_BANNER_VALID_VARIANTS)[number]
| '';
export type AlertBannerVariant = (typeof ALERT_BANNER_VALID_VARIANTS)[number];
6 changes: 0 additions & 6 deletions 2nd-gen/packages/core/components/meter/Meter.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,6 @@ export abstract class MeterBase extends LinearProgressMixin(
valid: constructor.VARIANTS,
url: 'https://spectrum-web-components.adobe.com/?path=/docs/components-meter--docs',
});
// Unknown variant: fall back to the default so the reflected
// attribute and fill color always resolve to a valid variant.
// Normalizing in `willUpdate` (Lit's input-normalization hook) folds
// the change into the current cycle instead of scheduling a second
// reactive update.
this.variant = 'informative';
}
super.willUpdate(changes);
}
Expand Down
118 changes: 30 additions & 88 deletions 2nd-gen/packages/core/components/tabs/Tabs.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,103 +84,21 @@ export abstract class TabsBase extends SpectrumElement {
* @see https://w3c.github.io/aria-practices/#kbd_selection_follows_focus
*/
@property({ type: String, reflect: true, attribute: 'keyboard-activation' })
public get keyboardActivation(): KeyboardActivation {
return this._keyboardActivation;
}

public set keyboardActivation(value: string) {
const isValid = (KEYBOARD_ACTIVATIONS as readonly string[]).includes(value);

validateEnum(this, {
prop: 'keyboard-activation',
value,
valid: KEYBOARD_ACTIVATIONS,
url: 'https://spectrum-web-components.adobe.com/?path=/docs/components-tabs--docs',
});

const valid = isValid
? (value as KeyboardActivation)
: KEYBOARD_ACTIVATION_DEFAULT;

if (this._keyboardActivation === valid) {
return;
}

const old = this._keyboardActivation;
this._keyboardActivation = valid;
this.requestUpdate('keyboardActivation', old);
}

/** @internal */
private _keyboardActivation: KeyboardActivation = KEYBOARD_ACTIVATION_DEFAULT;
public keyboardActivation: KeyboardActivation = KEYBOARD_ACTIVATION_DEFAULT;

/**
* Layout density: `regular` (default) or `compact` (reduced tab spacing).
*/
@property({ type: String, reflect: true })
public get density(): TabDensity {
return this._density;
}

public set density(value: string) {
const isValid = (TAB_DENSITIES as readonly string[]).includes(value);

validateEnum(this, {
prop: 'density',
value,
valid: TAB_DENSITIES,
url: 'https://spectrum-web-components.adobe.com/?path=/docs/components-tabs--docs',
});

const valid = isValid ? (value as TabDensity) : TAB_DENSITY_DEFAULT;

if (this._density === valid) {
return;
}

const old = this._density;
this._density = valid;
this.requestUpdate('density', old);
}

/** @internal */
private _density: TabDensity = TAB_DENSITY_DEFAULT;
public density: TabDensity = TAB_DENSITY_DEFAULT;

/**
* The layout direction of the tab list: `horizontal` (default) or `vertical`.
*
* @default 'horizontal'
*/
@property({ type: String, reflect: true })
public get direction(): TabsDirection {
return this._direction;
}

public set direction(value: TabsDirection) {
const isValid = (TABS_DIRECTIONS as readonly string[]).includes(value);

validateEnum(this, {
prop: 'direction',
value,
valid: TABS_DIRECTIONS,
url: 'https://spectrum-web-components.adobe.com/?path=/docs/components-tabs--docs',
});

const validDirection = isValid
? (value as TabsDirection)
: TABS_DEFAULT_DIRECTION;

if (this._direction === validDirection) {
return;
}

const oldDirection = this._direction;
this._direction = validDirection;
this.requestUpdate('direction', oldDirection);
}

/** @internal */
private _direction: TabsDirection = TABS_DEFAULT_DIRECTION;
public direction: TabsDirection = TABS_DEFAULT_DIRECTION;

/**
* Whether the entire tab list is disabled. When `true`,
Expand Down Expand Up @@ -249,7 +167,7 @@ export abstract class TabsBase extends SpectrumElement {
* tabs lose their tab stop, matching the `aria-disabled` tablist behavior.
*/
private readonly _navigation = new FocusgroupNavigationController(this, {
direction: this._direction,
direction: this.direction,
wrap: true,
memory: true,
getItems: () => (this.disabled ? [] : (this._tabs as HTMLElement[])),
Expand All @@ -269,7 +187,7 @@ export abstract class TabsBase extends SpectrumElement {
* or `change` event.
*/
private readonly _handleNavigationActiveChange = (event: Event): void => {
if (this._keyboardActivation !== 'automatic') {
if (this.keyboardActivation !== 'automatic') {
return;
}
const { activeElement, source } = (
Expand Down Expand Up @@ -483,7 +401,7 @@ export abstract class TabsBase extends SpectrumElement {
const tabRect = selectedElement.getBoundingClientRect();
const listRect = tablist.getBoundingClientRect();

if (this._direction === 'horizontal') {
if (this.direction === 'horizontal') {
const isRtl = getComputedStyle(this).direction === 'rtl';
const offset = isRtl
? tabRect.right - listRect.right
Expand Down Expand Up @@ -523,6 +441,30 @@ export abstract class TabsBase extends SpectrumElement {
// ───────────────────────────────────

protected override willUpdate(changes: PropertyValues): void {
// Warn-only enum validation, before render. Invalid values are left in
// place (not coerced); the union types are the compile-time guardrail and
// these warnings are the runtime backstop for attribute / plain-JS writes.
const url =
'https://spectrum-web-components.adobe.com/?path=/docs/components-tabs--docs';
validateEnum(this, {
prop: 'direction',
value: this.direction,
valid: TABS_DIRECTIONS,
url,
});
validateEnum(this, {
prop: 'keyboard-activation',
value: this.keyboardActivation,
valid: KEYBOARD_ACTIVATIONS,
url,
});
validateEnum(this, {
prop: 'density',
value: this.density,
valid: TAB_DENSITIES,
url,
});

if (!this.hasUpdated) {
const selectedChild = this.querySelector(
':scope > [selected]'
Expand Down
7 changes: 3 additions & 4 deletions 2nd-gen/packages/swc/components/meter/test/meter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,15 @@ export const VariantValidationTest: Story = {
})
);

await step('invalid variant warns and falls back to informative', () =>
await step('invalid variant warns', () =>
withWarningSpy(async (warnCalls) => {
meter.variant = 'invalid' as Meter['variant'];
await meter.updateComplete;
expect(warnCalls.length).toBeGreaterThan(0);
expect(String(warnCalls[0]?.[1] ?? '')).toContain('variant');
// Unknown variant is sanitized to the default and reflected.
await meter.updateComplete;
expect(meter.variant).toBe('informative');
expect(meter.getAttribute('variant')).toBe('informative');
expect(meter.variant).toBe('invalid');
expect(meter.getAttribute('variant')).toBe('invalid');
})
);
},
Expand Down
10 changes: 6 additions & 4 deletions 2nd-gen/packages/swc/components/tabs/test/tabs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,14 @@ export const DirectionPropertyTest: Story = {
).toBe('horizontal');
});

await step('invalid direction falls back to horizontal', async () => {
await step('invalid direction is left in place (warn-only)', async () => {
tabs.direction = 'invalid' as Tabs['direction'];
await tabs.updateComplete;
expect(tabs.direction, 'direction resets to horizontal').toBe(
'horizontal'
);
expect(tabs.direction, 'invalid direction persists').toBe('invalid');
expect(
tabs.getAttribute('direction'),
'invalid direction reflects to the attribute'
).toBe('invalid');
});
},
};
Expand Down
Loading