Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
02db50d
feat(core): init poc selection controller
nikkimk Jun 11, 2026
dc92ec0
feat(core): fixed storybook and updated docs
nikkimk Jun 11, 2026
72e363f
feat(tabs): implemented focus group navigation and selection controllers
nikkimk Jun 12, 2026
11b1dbf
chore: added changeset
nikkimk Jun 12, 2026
19f38f5
test(core): added tests for selection controller
nikkimk Jun 12, 2026
f9e89ef
chore(rules): updates to stories documentation rules to expand on con…
nikkimk Jun 12, 2026
eef6543
fix(core): fixed failing tests for tabs using focus group navigation …
nikkimk Jun 12, 2026
1765662
docs(core): test fixes for selection controller
nikkimk Jun 12, 2026
3c43f1b
Merge branch 'main' into nikkimk/poc-selection-controller
nikkimk Jun 12, 2026
19de942
fix(core): fixing another failing a11y test
nikkimk Jun 12, 2026
2cc8a6a
docs(core): fix selection controller stories
nikkimk Jun 15, 2026
82b54c6
Merge branch 'main' into nikkimk/poc-selection-controller
nikkimk Jun 15, 2026
ae96a75
Merge branch 'nikkimk/poc-selection-controller' of github.com:adobe/s…
nikkimk Jun 15, 2026
032b4ee
fix(core): enforcing a selection with disabled host with radio type
nikkimk Jun 15, 2026
5719c93
revert(global-button): remove accidental CSS reformatting
nikkimk Jun 15, 2026
ff795b8
revert(claude): remove accidentally committed settings file
nikkimk Jun 15, 2026
95624e8
docs(css, ai): document process and features of shared style director…
5t3ph Jun 12, 2026
602ebc0
docs(color-handle): accessbility migration-analysis (#6397)
nikkimk Jun 15, 2026
b97c3f8
Merge branch 'main' into nikkimk/poc-selection-controller
nikkimk Jun 12, 2026
37f3fd8
fix(core): fixing another failing a11y test
nikkimk Jun 12, 2026
6be1608
Merge branch 'main' into nikkimk/poc-selection-controller
nikkimk Jun 15, 2026
db0bffb
docs(core): fix selection controller stories
nikkimk Jun 15, 2026
a3dd2ed
Merge branch 'nikkimk/poc-selection-controller' of github.com:adobe/s…
nikkimk Jun 15, 2026
9f358ff
fix(core): enforcing a selection with disabled host with radio type
nikkimk Jun 15, 2026
aafc290
revert(claude): remove accidentally committed settings file
nikkimk Jun 15, 2026
216734b
Merge branch 'nikkimk/poc-selection-controller' of github.com:adobe/s…
nikkimk Jun 15, 2026
7ec56a7
chore(global-button): sync with main
nikkimk Jun 15, 2026
6c41476
Merge branch 'main' of github.com:adobe/spectrum-web-components into …
nikkimk Jun 16, 2026
8ace672
Merge branch 'main' into nikkimk/poc-selection-controller
nikkimk Jul 6, 2026
d20463d
Merge branch 'main' into nikkimk/poc-selection-controller
nikkimk Jul 7, 2026
2870704
fix(core): updated selection controller based on PR feedback
nikkimk Jul 7, 2026
b652aa4
fix(tabs): updated tabs based on PR feedback
nikkimk Jul 7, 2026
ba5c257
feat(accordion): refactored accordion to use selection controller
nikkimk Jul 7, 2026
c692710
feat(core): refactored selection controller with silent, enable inter…
nikkimk Jul 7, 2026
1d7e10b
fix(core): updated based on change handler feedback
nikkimk Jul 7, 2026
2dd846b
fix(core): updated to fix navigation change handler
nikkimk Jul 7, 2026
1b4a071
fix(core): updated selection controller to clear selection when nothi…
nikkimk Jul 7, 2026
40d738b
test(core): added selection and focus group controller tests
nikkimk Jul 7, 2026
d7b3312
fix(core): fixed host connected refresh, added force flag, and update…
nikkimk Jul 7, 2026
5e89d6c
Merge branch 'main' into nikkimk/poc-selection-controller
nikkimk Jul 7, 2026
b6336c2
Merge branch 'nikkimk/poc-selection-controller' of github.com:adobe/s…
nikkimk Jul 7, 2026
c37809f
Merge branch 'main' into nikkimk/poc-selection-controller
nikkimk Jul 8, 2026
2e1836d
feat(accordion): allowed accordion to switch to allow multiple via se…
nikkimk Jul 8, 2026
eb5a302
fix(core): made selection controller more useful for cases like accor…
nikkimk Jul 8, 2026
6210479
fix(core): performance fixes and updated docs
nikkimk Jul 8, 2026
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
223 changes: 215 additions & 8 deletions .ai/rules/stories-documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ Long-form documentation lives in the per-unit MDX file at the unit's root, not i

### File template

#### Component / pattern MDX template

```mdx
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
import { DocsFooter, DocsHeader } from '../../.storybook/blocks'; // components
// or '../../../.storybook/blocks' for patterns
// or '../../../swc/.storybook/blocks' for controllers

import * as Stories from './stories/<unit>.stories';

Expand Down Expand Up @@ -80,8 +81,101 @@ import * as Stories from './stories/<unit>.stories';
<DocsFooter />
```

#### Controller MDX template

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.

Controllers require different documentation, like how to use them with a host, so they should have different rules.


Controllers do not expose a visual DOM surface, so their MDX uses different top-level sections. The section headings `## What it does`, `## Basic usage`, `## Behaviors`, `## Accessibility`, `## API`, and `## Appendix` are the controller equivalents of the component section order. The `## API` section is **hand-authored** (controllers opt out of `<ApiTable />` via `'controller'` in `meta.tags`).

````mdx
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
import { DocsFooter, DocsHeader } from '../../../swc/.storybook/blocks';

import * as Stories from './stories/<controller>.stories';

<Meta of={Stories} />

<DocsHeader />

## What it does

### [Feature group 1]

- Bullet describing behavior A (e.g. "Collapses the tab sequence to one tab stop")
- Bullet describing behavior B

### [Feature group 2]

...

## Basic usage

1. Construct the controller in your element's `constructor`, passing required options.
2. Ensure `getItems` returns live `HTMLElement` references from `this.renderRoot`.
3. Call `refresh()` from `firstUpdated` once shadow DOM exists.
4. Pair with other controllers when needed (e.g. `FocusgroupNavigationController`).

```typescript
// Minimal constructor + firstUpdated setup example
```
````

## Behaviors

### [Story display name]

...prose...

<Canvas of={Stories.StoryName} />

## Accessibility

### Features

...keyboard navigation, ARIA management, etc....

### Best practices

...

<Canvas of={Stories.Accessibility} />

## API

### Methods

| Member | Description |
| ----------- | ---------------------------------- |
| `refresh()` | Re-query items and re-apply state. |

### Events

The controller dispatches **`event-name`** on the host. The event bubbles and is composed.

```typescript
import { eventConstant } from '@spectrum-web-components/core/controllers';

host.addEventListener(eventConstant, (event) => { ... });
```

### Options

| Option | Type | Default | Description |
| ---------- | --------------------- | ---------- | ----------- |
| `getItems` | `() => HTMLElement[]` | (required) | ... |

## Appendix

### See also

- [Related controller](../?path=/docs/controllers-related--docs)
- [APG pattern link](https://www.w3.org/WAI/ARIA/apg/...)

<DocsFooter />
```

### Canonical section order

#### Components and patterns

`DocumentTemplate.mdx` (the fallback template for units without a per-unit MDX) renders sections in this same order, so converted units and unconverted units sit side-by-side without surprises:

1. **Anatomy** — `hideTitle` semantics: no per-story `###` heading
Expand All @@ -96,15 +190,31 @@ import * as Stories from './stories/<unit>.stories';
10. **Appendix** — `hideTitle` semantics; prose-only.
11. **Feedback** — rendered by `<DocsFooter />`.

#### Controllers

Controller MDX uses a different section order and different top-level headings because controllers have no visual DOM surface:

1. **`## What it does`** — bullet-list description of what the controller provides (navigation, selection, positioning, etc.)
2. **`## Basic usage`** — numbered steps: constructor setup, `getItems` contract, `refresh()` lifecycle, pairing guidance, code snippet
3. **`## Behaviors`** — `### Story display name` + prose + `<Canvas />` for each `'behaviors'`-tagged story
4. **`## Accessibility`** — `### Features` and `### Best practices` subheadings; `<Canvas of={Stories.Accessibility} />`
5. **`## API`** — hand-authored; three subsections: `### Methods` (table), `### Events` (import snippet + description), `### Options` (table)
6. **`## Appendix`** — prose-only; relationship to other controllers, "See also" links
7. **Feedback** — rendered by `<DocsFooter />`

Sections `## Anatomy`, `## Options`, `## States` do **not** appear in pure utility controllers because there is no user-facing DOM surface to describe. Exceptions: a controller with configurable options that have visual effects (e.g., `HoverController`) may include `## Options` and `## States` sections.

### Genre-specific notes

| Topic | Component | Internal | Pattern | Controller |
| ------------------------------------ | --------------------------- | --------------------------------- | ------------------------------ | ---------------------------------------------------- |
| MDX path | `components/<n>/<n>.mdx` | `components/<n>/<n>.internal.mdx` | `patterns/<g>/<n>/<n>.mdx` | `controllers/<n>/<n>.mdx` |
| Blocks import path | `'../../.storybook/blocks'` | `'../../.storybook/blocks'` | `'../../../.storybook/blocks'` | `'../../../swc/.storybook/blocks'` |
| Meta tag for `DocsFooter` API branch | `'migrated'` | n/a | n/a | `'controller'` (omits `<ApiTable />`) |
| Anatomy section | required (DOM-bearing) | required if applicable | required (DOM-bearing) | omitted (controllers have no DOM) |
| Production build inclusion | yes | no (`.internal.mdx` excluded) | yes | yes (dev) — core docs excluded from production build |
| Topic | Component | Internal | Pattern | Controller |
| ------------------------------------ | --------------------------- | --------------------------------- | ------------------------------ | ---------------------------------------------- |
| MDX path | `components/<n>/<n>.mdx` | `components/<n>/<n>.internal.mdx` | `patterns/<g>/<n>/<n>.mdx` | `controllers/<n>/<n>.mdx` |
| Blocks import path | `'../../.storybook/blocks'` | `'../../.storybook/blocks'` | `'../../../.storybook/blocks'` | `'../../../swc/.storybook/blocks'` |
| Meta tag for `DocsFooter` API branch | `'migrated'` | n/a | n/a | `'controller'` (omits `<ApiTable />`) |
| Top-level section headings | `Anatomy`, `Options`, etc. | `Anatomy`, `Options`, etc. | `Anatomy`, `Options`, etc. | See controller canonical section order above |
| Anatomy section | required (DOM-bearing) | required if applicable | required (DOM-bearing) | omitted (controllers have no DOM surface) |
| API section | auto by `DocsFooter` | auto by `DocsFooter` | auto by `DocsFooter` | hand-authored `## API` before `<DocsFooter />` |
| Production build inclusion | yes | no (`.internal.mdx` excluded) | yes | yes (dev) — core docs excluded from prod build |

### Per-story title rules

Expand Down Expand Up @@ -160,6 +270,103 @@ Organize MDX into these sections (skip sections that don't apply):

The remaining sections of this rule describe the **prose content** for each section. Author that prose inside the per-unit MDX, not as JSDoc above stories.

## Controller-specific MDX sections

The following sections apply **only to controllers** (`core/controllers/<name>/<name>.mdx`). Skip these when authoring component or pattern MDX.

### `## What it does`

A concise feature inventory for the controller: what it provides and what it explicitly does **not** provide. Use bullet groups with `###` subheadings for logical clusters:

```md
## What it does

### Navigation

- Collapses a composite widget to a single Tab stop via roving `tabindex`.
- Arrow keys move focus according to `direction`.
- `Home` / `End` jump to the first or last item.

### Configuration

- `wrap`: end wraps to start.
- `skipDisabled`: disabled items excluded from arrow movement.

### Programmatic API

- `setActiveItem(element)`: sets the roving tab stop without calling `focus()`.
```

### `## Basic usage`

A numbered integration checklist followed by a minimal code example. Always include:

1. Where to construct (in `constructor`)
2. What `getItems` must return (live references from `this.renderRoot`)
3. When to call `refresh()` (from `firstUpdated`, and after DOM changes)
4. What the host must provide (ARIA roles, labels — the controller does not set these)
5. Which controller to pair with when relevant

```md
## Basic usage

1. Construct the controller in your element's `constructor`.
2. Ensure `getItems` returns live `HTMLElement` references from `this.renderRoot`.
3. Call `refresh()` from `firstUpdated` once shadow DOM exists.
4. Provide ARIA roles and labels on the host; the controller does not set them.
5. Pair with `FocusgroupNavigationController` when the composite also needs arrow keys.

\`\`\`typescript
private readonly selection = new SelectionController(this, {
getItems: () => Array.from(this.renderRoot.querySelectorAll('[role="option"]')),
selectItem: (el) => el.setAttribute('aria-selected', 'true'),
deselectItem: (el) => el.setAttribute('aria-selected', 'false'),
mode: 'multiple',
keydownActivation: true,
});

protected override firstUpdated(): void {
this.selection.refresh();
}
\`\`\`
```

### Hand-authored `## API` section

Controllers opt out of the auto-generated `<ApiTable />` via `'controller'` in `meta.tags`. The `## API` section is therefore written by hand before `<DocsFooter />`. Use three fixed subsections:

```md
## API

### Methods

| Member | Description |
| --------------------- | ---------------------------------------------------------- |
| `refresh()` | Re-query items and re-apply state. Call after DOM changes. |
| `setOptions(partial)` | Merge option updates. |

### Events

The controller dispatches **`swc-example-change`** (`exampleChange`) on the host with
`detail: { ... }`. The event bubbles and is composed.

\`\`\`typescript
import { exampleChange } from '@spectrum-web-components/core/controllers';

host.addEventListener(exampleChange, (event) => {
console.log(event.detail);
});
\`\`\`

### Options

| Option | Type | Default | Description |
| ---------- | --------------------- | ---------- | ------------------------ |
| `getItems` | `() => HTMLElement[]` | (required) | Current participant set. |
```

If the controller dispatches no events, omit `### Events` and state that explicitly. If the controller has no public methods beyond `refresh()`, list only `refresh()` in the table.

## Helpers section

**Purpose**: Organize shared code, label mappings, and utilities used across multiple stories.
Expand Down
94 changes: 86 additions & 8 deletions .ai/rules/stories-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Component-specific `.usage.mdx` files are no longer used. Units without a per-un

### Stories file (`.stories.ts`)

#### Component / pattern section order

Required structure with visual separators between sections:

1. **Copyright header** (lines 1-11)
Expand All @@ -56,6 +58,22 @@ Required structure with visual separators between sections:
10. **BEHAVIORS STORIES** - Built-in functionality (if applicable)
11. **ACCESSIBILITY STORIES** - A11y demonstration

#### Controller section order

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.

Controllers require different documentation, like how to use them with a host, so they should have different rules.


Controllers do not have `ANATOMY STORIES`, `OPTIONS STORIES`, or `STATES STORIES` sections (no visual DOM surface). The typical controller story file structure is:

1. **Copyright header** (lines 1-11)
2. **Imports**
3. **METADATA** - Meta object with `tags: ['migrated', 'controller']`
4. **HELPERS** - Shared utilities (if needed)
5. **PLAYGROUND STORY** - tags: `['dev']`
6. **OVERVIEW STORY** - tags: `['overview']`
7. **USAGE STORIES** _(optional)_ - tags: `['usage']` / `['usage', 'description-only']` with `'section-order'` parameter; JSDoc code examples above each export (see [Usage stories in controllers](#usage-stories-in-controllers))
8. **BEHAVIORS STORIES** - tags: `['behaviors']`
9. **ACCESSIBILITY STORIES** - tags: `['a11y']`

A controller may also include `OPTIONS STORIES` and `STATES STORIES` if it has configurable behaviors with visible differences (e.g., `HoverController` with `disabled` and `manual` states).

#### Visual separators

```typescript
Expand Down Expand Up @@ -318,6 +336,64 @@ Section ordering is hand-authored in each component's per-component MDX file (`<

Do not use a `section-order` parameter on stories. The previous `section-order` workaround is retired now that MDX is the source of truth for documentation layout.

**Controller exception:** Some existing `core/controllers` stories (notably `selection-controller.stories.ts`) use the `'usage'` tag, `'description-only'` tag, and `'section-order'` parameter on usage-example stories. These are a preserved documentation pattern for controllers that ship inline code examples alongside the story canvas. Do **not** remove these stories or tags to satisfy a lint error. If a lint error occurs on a different story in the same file (e.g. an `'a11y'` story without a `<Canvas>` reference in the MDX), fix only that story. See the [Usage stories in controllers](#usage-stories-in-controllers) section below.

## Usage stories in controllers

Some `core/controllers` stories use a legacy inline-documentation pattern where `'usage'`-tagged exports carry JSDoc code examples. This is distinct from the component pattern (no JSDoc above story exports). Preserve these stories when they exist.

### When to use

Use this pattern **only in `core/controllers`** when the controller's primary audience is engineers integrating it into a component, and inline code snippets alongside a live demo are more useful than prose alone in MDX.

### Pattern

````typescript
// ──────────────────────────
// USAGE STORIES
// ──────────────────────────

/**
* ## Anatomy of a `MyController`
*
* Constructor signature, options, and lifecycle description.
*
* ```typescript
* // Example setup code
* ```
*/
export const Usage: Story = {
tags: ['usage', 'description-only'],
parameters: { 'section-order': 0 },
};

/**
* ### Single mode example
*
* Explanation of when to use this mode.
*
* ```typescript
* // Mode-specific code
* ```
*/
export const UsageExampleSingle: Story = {
name: 'Example: single mode',
tags: ['usage'],
parameters: { 'section-order': 1 },
render: () => html`
<demo-host></demo-host>
`,
};
````

### Rules specific to controller usage stories

- The JSDoc above each usage story export **is** authored (exception to the no-story-JSDoc rule).
- `'description-only'` on the header story means it has no canvas — prose only.
- `'section-order'` controls display order within the Usage section in `DocumentTemplate.mdx`.
- The demo custom element rendered by usage stories lives in `stories/demo-hosts.ts` alongside the stories file.
- Do not add `<Canvas of={Stories.UsageExample...} />` to the controller MDX — usage stories render through `DocumentTemplate.mdx` via the `'usage'` tag, not through the hand-authored MDX page.

## Tags

### Required tags
Expand All @@ -338,7 +414,9 @@ Do not use a `section-order` parameter on stories. The previous `section-order`

- `'upcoming'` - Story demonstrates a feature or variant that is not yet available

> The previous `'description-only'` tag is retired. Prose-only sections live in the per-component MDX (`<component>.mdx`) as Markdown without a `<Canvas>` reference, not as story exports.
> The `'description-only'` tag is retired **for components and patterns**. Prose-only sections live in the per-unit MDX as Markdown without a `<Canvas>` reference, not as story exports.
>
> **Controller exception:** `'usage'` and `'description-only'` remain valid in `core/controllers` stories for the usage-example pattern. See [Usage stories in controllers](#usage-stories-in-controllers).

### Exclusion tags

Expand Down Expand Up @@ -680,10 +758,10 @@ See `asset.stories.ts` for complete examples.

### ❌ Don't

- Add JSDoc above any individual story export (Playground, Overview, or any other)
- Use the `'usage'` tag for new units (deprecated)
- Use the `'description-only'` tag (retired — prose-only sections live in MDX)
- Use the `'section-order'` parameter (retired — section order is hand-authored in MDX)
- Add JSDoc above any individual story export (Playground, Overview, or any other) — **except** usage-example stories in `core/controllers` (see [Usage stories in controllers](#usage-stories-in-controllers))
- Use the `'usage'` tag for new **component or pattern** units (deprecated for those genres)
- Use the `'description-only'` tag for **components or patterns** (retired — prose-only sections live in MDX)
- Use the `'section-order'` parameter for **components or patterns** (retired — section order is hand-authored in MDX)
- Use `tags: ['autodocs', 'dev']` on a unit that has a per-unit MDX file (creates a duplicate Docs entry — use `['dev']`)
- Omit `subtitle` in meta parameters
- Use placeholder text
Expand Down Expand Up @@ -715,9 +793,9 @@ See `asset.stories.ts` for complete examples.
- [ ] Behaviors: `['behaviors']` tag (if applicable)
- [ ] Accessibility: `['a11y']` tag (prose lives in MDX)
- [ ] Static colors: three-story or combined-story pattern with `staticColorsDemo` (if applicable)
- [ ] No story-level JSDoc comments above any `export const`
- [ ] No `section-order` parameter on any story
- [ ] No `description-only` tag on any story
- [ ] No story-level JSDoc comments above any `export const` (exception: usage-example stories in `core/controllers`)
- [ ] No `section-order` parameter on any story (exception: usage-example stories in `core/controllers`)
- [ ] No `description-only` tag on any story (exception: usage-example stories in `core/controllers`)
- [ ] All stories accessible with meaningful content
- [ ] Image assets: use `picsum.photos` with static IDs (if applicable)
- [ ] Per-unit MDX file exists at the unit root and references each section-tagged story via `<Canvas of={Stories.StoryName} />` (see `.ai/rules/stories-documentation.md`)
Loading
Loading