Skip to content
Merged
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
12 changes: 6 additions & 6 deletions .ai/skills/migration-api/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ The plan's architectural sections also govern **where each property and type lan

**Deferred items are out of scope.** The plan may explicitly defer features to follow-up tickets (e.g. form-associated behavior, cross-root ARIA). Do not implement deferred items in Phase 3 even if they appear in the API section of the plan — mark them as skipped and note the tracking ticket.

**1st-gen changes are limited to deprecation notices only.** When adding deprecation notices to 1st-gen, restrict changes to: (1) `@deprecated` JSDoc on exported types, consts, and properties; and (2) `window.__swc.warn()` calls added inside already-existing setters or other existing code paths. Do not refactor 1st-gen code structure — no new backing types, no converting simple properties to getter/setter patterns, no new private fields. TypeScript TS6385 hints that arise from deprecated types referencing each other internally are an accepted side effect and do not require structural fixes.
**For each breaking change in the migration plan, you must add both `@deprecated` JSDoc and a `window.__swc.warn()` call to the 1st-gen file.** JSDoc alone is not enough — types and IDE tooling pick it up, but consumers building against compiled output won't see it. Restrict 1st-gen changes to these two things only: do not add new backing types, do not convert plain properties to getter/setter patterns, do not add new private fields.
Comment thread
miwha-adobe marked this conversation as resolved.

For where the warn goes — inside an existing setter, or in the `updated()` lifecycle method with a `changes.has()` guard and a non-default value check for a setter-less `@property` — follow the [Where to place the warning](../../../CONTRIBUTOR-DOCS/02_style-guide/02_typescript/17_debug-validation.md#where-to-place-the-warning) section of the debug and validation guide. The setter-less pattern matters here because Phase 3 does not allow converting a plain `@property` into a getter/setter just to host the warn, so `updated()` is its only home.

After writing the deprecation notices, scan every `@deprecated` tag in the 1st-gen file and confirm a `__swc.warn()` call exists for each one before closing Phase 3. TypeScript TS6385 hints that arise from deprecated types referencing each other internally are an accepted side effect and do not require structural fixes.

**`@deprecated` JSDoc message format** — the washing machine workflow Phase 3 section on "Deprecating 1st-gen type and const exports" says to document migration to class-level inference (e.g. `typeof Badge.prototype.variant`, `typeof Badge.FIXED_VALUES`) when a class static already exists in 1st-gen. Apply this as follows:

Expand All @@ -26,11 +30,7 @@ The plan's architectural sections also govern **where each property and type lan
- Do not add new class statics to 1st-gen just to provide this alternative — that is a refactor and out of scope.
- For **renames**, note the new name in the JSDoc: "...will be replaced by `newName` in a future release."

For the `window.__swc.warn()` call format and the `{ level: 'deprecation' }` option, follow the **Deprecation warnings** section of [`CONTRIBUTOR-DOCS/02_style-guide/02_typescript/17_debug-validation.md`](../../../CONTRIBUTOR-DOCS/02_style-guide/02_typescript/17_debug-validation.md). Key rules from that guide:

- Use `{ level: 'deprecation' }` instead of `{ issues: [...] }`.
- If a 1st-gen replacement exists, include `Use "newProp" instead.` at the end of the message. If there is no 1st-gen alternative, omit the "Use" clause.
- Do not reference 2nd-gen APIs as replacements in runtime warn messages.
For the `window.__swc.warn()` call format, the `{ level: 'deprecation' }` option, and the rules on referencing a replacement in the message, follow the **[Deprecation warnings](../../../CONTRIBUTOR-DOCS/02_style-guide/02_typescript/17_debug-validation.md#deprecation-warnings)** section of the debug and validation guide. Applying its replacement-reference rule to migration: reference the 1st-gen replacement if one exists, otherwise omit the "Use" clause — and never point a 1st-gen runtime warn at a 2nd-gen API, since consumers on the 1st-gen build cannot use it.

## When to use this skill

Expand Down
27 changes: 27 additions & 0 deletions 1st-gen/packages/progress-bar/src/ProgressBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,33 @@ export class ProgressBar extends SizedMixin(
}
}

if (window.__swc?.DEBUG) {
if (changes.has('label') && this.label.length > 0) {
window.__swc.warn(
this,
`The "label" attribute on <${this.localName}> has been deprecated and will be removed in a future release. Use a "label" named slot or the "accessible-label" attribute instead.`,
'https://opensource.adobe.com/spectrum-web-components/components/progress-bar/#accessibility',
{ level: 'deprecation' }
);
}
if (changes.has('sideLabel') && this.sideLabel) {
window.__swc.warn(
this,
`The "side-label" attribute on <${this.localName}> has been deprecated and will be removed in a future release. Use label-position="side" instead.`,
'https://opensource.adobe.com/spectrum-web-components/components/progress-bar/',
{ level: 'deprecation' }
);
}
if (changes.has('progress') && this.progress !== 0) {
window.__swc.warn(
this,
`The "progress" property on <${this.localName}> has been deprecated and will be removed in a future release. Use the "value" attribute instead.`,
'https://opensource.adobe.com/spectrum-web-components/components/progress-bar/',
{ level: 'deprecation' }
);
}
}

if (window.__swc?.DEBUG) {
if (
!this.label &&
Expand Down
48 changes: 19 additions & 29 deletions 1st-gen/packages/progress-bar/test/progress-bar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,29 +168,6 @@ describe('ProgressBar', () => {
consoleWarnStub.restore();
});

it('warns in Dev Mode when accessible attributes are not leveraged', async () => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a duplicte test in the file.

const el = await fixture<ProgressBar>(html`
<sp-progress-bar progress="50"></sp-progress-bar>
`);

await elementUpdated(el);

const spyCall = consoleWarnStub.getCall(0);
expect(
(spyCall.args[0] as string).includes('accessible'),
'confirm accessibility-centric message'
).to.be.true;
expect(
spyCall.args[spyCall.args.length - 1],
'confirm `data` shape'
).to.deep.equal({
data: {
localName: 'sp-progress-bar',
type: 'accessibility',
level: 'default',
},
});
});
it('resolves a language (en-US)', async () => {
const [languageContext] = createLanguageContext('en-US');
const test = await fixture(html`
Expand Down Expand Up @@ -270,13 +247,18 @@ describe('ProgressBar', () => {
'confirm deprecated over-background warning'
).to.be.true;

const spyCall = consoleWarnStub.getCall(0);
const spyCall = consoleWarnStub
.getCalls()
.find((call: { args: unknown[] }) =>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the test to be more specific in finding the call. Previously it was just getting the first response, but with the addition of deprecated dev warnings, this broke.

(call.args[0] as string)?.includes('over-background')
);
expect(spyCall, 'over-background warning emitted').to.not.be.undefined;
expect(
(spyCall.args[0] as string).includes('deprecated'),
(spyCall!.args[0] as string).includes('deprecated'),
'confirm deprecated over-background warning'
).to.be.true;
expect(
spyCall.args[spyCall.args.length - 1],
spyCall!.args[spyCall!.args.length - 1],
'confirm `data` shape'
).to.deep.equal({
data: {
Expand All @@ -294,13 +276,21 @@ describe('ProgressBar', () => {
await elementUpdated(el);

expect(consoleWarnStub.called).to.be.true;
const spyCall = consoleWarnStub.getCall(0);
const spyCall = consoleWarnStub.getCalls().find(
(call: { args: unknown[] }) =>
(
call.args[call.args.length - 1] as {
data?: { type?: string };
}
)?.data?.type === 'accessibility'
);
expect(spyCall, 'accessibility warning emitted').to.not.be.undefined;
expect(
(spyCall.args[0] as string).includes('accessible'),
(spyCall!.args[0] as string).includes('accessible'),
'confirm accessibility-centric message'
).to.be.true;
expect(
spyCall.args[spyCall.args.length - 1],
spyCall!.args[spyCall!.args.length - 1],
'confirm `data` shape'
).to.deep.equal({
data: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ argTypes.value = {
* user actions rather than system progress. The accessible `meter` role and its
* `aria-value*` attributes live on the internal bar wrapper; the host carries no
* ARIA role.
*
* To show the progression of a system operation such as downloading or processing,
* see [Progress Bar](../?path=/docs/components-progress-bar--docs).
*/
const meta: Meta = {
title: 'Meter',
Expand Down
117 changes: 117 additions & 0 deletions 2nd-gen/packages/swc/components/progress-bar/progress-bar.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
import { DocsFooter, DocsHeader } from '../../.storybook/blocks';

import * as Stories from './stories/progress-bar.stories';

<Meta of={Stories} />

<DocsHeader />

## Anatomy

A progress bar consists of:

1. **Label**: text describing the operation in progress, provided through the `label` named slot
2. **Value**: the formatted current value, rendered from `value` within the `min-value` and `max-value` range (omitted when indeterminate)
3. **Track**: the bar background showing the full range
4. **Fill**: the portion of the track representing the current value (animates continuously when indeterminate)
5. **Description**: optional supporting text below the bar, provided through the `description` named slot

The `progressbar` role and all `aria-value*`, `aria-labelledby`, and `aria-describedby` attributes live on the internal bar wrapper, not on the host element.

When there is no visible `label` slot content, set the `accessible-label` attribute to supply an accessible name without showing visible text (for example, a full-page loading animation).

<Canvas of={Stories.Anatomy} />

## Options

### Sizes

Progress bars come in four sizes: small (`s`), medium (`m`), large (`l`), and extra-large (`xl`). Medium is the default.

<Canvas of={Stories.Sizes} />

### Label Position

The label can be displayed above the bar or beside it. Set `label-position="top"` (default) or `label-position="side"`.

<Canvas of={Stories.LabelPosition} />

### Static Colors

Use the `static-color` attribute when the progress bar is displayed over images or colored backgrounds:

- **white**: use on dark or colored backgrounds for contrast
- **black**: use on light backgrounds for contrast

<Canvas of={Stories.StaticColors} />

## States

### Values

The bar fill and the formatted value reflect `value` relative to the sanitized `min-value` and `max-value` range. Values outside the range are clamped to the nearest bound.

<Canvas of={Stories.Values} />

### Indeterminate

Set `indeterminate` when the operation is in progress but the completion time cannot be determined, such as reconnecting to a server. The fill runs a looping animation and all four `aria-value*` attributes are omitted.

<Canvas of={Stories.Indeterminate} />

## Behaviors

### Custom range

Set `min-value` and `max-value` to measure against an arbitrary numeric range rather than the default 0 to 100. The fill and `aria-valuetext` use the sanitized range; if the two bounds are reversed they are swapped, and non-finite bounds fall back to 0 and 100.

<Canvas of={Stories.CustomRange} />

### Value label

Set the `value-label` attribute to replace the auto-formatted value in both the rendered value text and `aria-valuetext` with a custom string, such as `3 of 10 steps`. This attribute is ignored when `indeterminate` is set.

<Canvas of={Stories.ValueLabel} />

### Format options

When `value-label` is unset, the value is formatted with `Intl.NumberFormat`. The default style is percent. Set the `formatOptions` property (JavaScript only, no attribute) to any `Intl.NumberFormatOptions` to change the formatting. Percent style formats the fraction of the range; every other style formats the raw clamped value.

<Canvas of={Stories.FormatOptions} />

## Accessibility

### Features

The `<swc-progress-bar>` element implements these accessibility features:

#### ARIA implementation

1. **ARIA role**: sets `role="progressbar"` on the internal bar wrapper. The host element carries no ARIA role.
2. **Labeling**: when the `label` slot has content, the role element references it via `aria-labelledby`. When there is no `label` slot content, the `accessible-label` value is exposed as `aria-label`.
3. **Description**: when the `description` slot has content, the role element references it via `aria-describedby`.
4. **Determinate value**: sets `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, and `aria-valuetext` from the sanitized range and clamped value. `aria-valuetext` matches the formatted value, including `value-label` and `formatOptions` overrides, and is localized from the document language.
5. **Indeterminate state**: all four `aria-value*` attributes are omitted when `indeterminate` is set, as required by ARIA.

#### Visual accessibility

- Progress is communicated through the fill amount and the formatted value text, not through color alone.
- Forced-colors mode is supported with appropriate color overrides.
- Static color variants maintain sufficient contrast on images and colored backgrounds.

#### Non-interactive element

- Progress bars are not focusable and are not in the tab order.
- No keyboard interaction is required or expected.

### Best practices

- Provide an accessible name through either visible `label` slot content or the `accessible-label` attribute.
- Use specific, meaningful label text that describes the ongoing operation; for example, `"Uploading file"` rather than `"Loading"`.
- When no visible label is appropriate, such as when the surrounding context already names the operation, set `accessible-label` to provide a screen-reader-only name.
- Ensure sufficient color contrast between the bar and its background; use `static-color="white"` on dark backgrounds and `static-color="black"` on light backgrounds.

<Canvas of={Stories.Accessibility} />

<DocsFooter />
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,22 @@ argTypes.value = {
},
};

/**
* A `<swc-progress-bar>` shows the progression of a system operation such as
* downloading, uploading, or processing. Use a determinate bar when progress
* can be calculated against a known total; set `indeterminate` when the
* completion time cannot be determined.
*
* For circular progress indicators, see
* [Progress Circle](../?path=/docs/components-progress-circle--docs) and for read-only
* values use [Meter](../?path=/docs/components-meter--docs).
*/
const meta: Meta = {
title: 'Progress Bar',
component: 'swc-progress-bar',
parameters: {
docs: {
subtitle: `Non-focusable, read-only bar that shows task progress or an indeterminate loading state.`,
subtitle: `Shows the progress of a task or an indeterminate loading state.`,
},
styles: { 'min-inline-size': '250px' },
},
Expand Down Expand Up @@ -110,7 +120,7 @@ const staticColorLabels = {
// ────────────────────

export const Playground: Story = {
tags: ['autodocs', 'dev'],
tags: ['dev'],
args: {
size: 'm',
value: 60,
Expand Down Expand Up @@ -216,7 +226,6 @@ export const LabelPosition: Story = {
tags: ['options'],
args: { value: 50 },
};
LabelPosition.storyName = 'Label position';

export const StaticColors: Story = {
render: (args) => html`
Expand All @@ -232,7 +241,6 @@ export const StaticColors: Story = {
tags: ['options', '!test'],
args: { value: 50 },
};
StaticColors.storyName = 'Static colors';

// ──────────────────────────
// STATES STORIES
Expand Down
Loading
Loading