diff --git a/CONTRIBUTOR-DOCS/03_project-planning/03_components/README.md b/CONTRIBUTOR-DOCS/03_project-planning/03_components/README.md index 83873ef904d..6b464116c2e 100644 --- a/CONTRIBUTOR-DOCS/03_project-planning/03_components/README.md +++ b/CONTRIBUTOR-DOCS/03_project-planning/03_components/README.md @@ -104,6 +104,7 @@ - [Switch migration roadmap](switch/rendering-and-styling-migration-analysis.md) - Tabs - [Tabs accessibility migration analysis](tabs/accessibility-migration-analysis.md) + - [Tabs migration plan](tabs/migration-plan.md) - [Tabs migration roadmap](tabs/rendering-and-styling-migration-analysis.md) - Tag - [Tag migration roadmap](tag/rendering-and-styling-migration-analysis.md) diff --git a/CONTRIBUTOR-DOCS/03_project-planning/03_components/tabs/migration-plan.md b/CONTRIBUTOR-DOCS/03_project-planning/03_components/tabs/migration-plan.md new file mode 100644 index 00000000000..4e86e9fc8d8 --- /dev/null +++ b/CONTRIBUTOR-DOCS/03_project-planning/03_components/tabs/migration-plan.md @@ -0,0 +1,814 @@ + + +[CONTRIBUTOR-DOCS](../../../README.md) / [Project planning](../../README.md) / [Components](../README.md) / Tabs / Tabs migration plan + + + +# Tabs migration plan + + + +
+In this doc + +- [TL;DR](#tldr) + - [Most blocking open questions](#most-blocking-open-questions) + - [Recently resolved](#recently-resolved) +- [1st-gen API surface](#1st-gen-api-surface) + - [sp-tabs (`Tabs`)](#sp-tabs-tabs) + - [sp-tab (`Tab`)](#sp-tab-tab) + - [sp-tab-panel (`TabPanel`)](#sp-tab-panel-tabpanel) + - [sp-tabs-overflow (`TabsOverflow`)](#sp-tabs-overflow-tabsoverflow) + - [Module-level exports](#module-level-exports) + - [CSS custom properties](#css-custom-properties) + - [Shadow DOM output (rendered HTML)](#shadow-dom-output-rendered-html) +- [Dependencies](#dependencies) +- [Changes overview](#changes-overview) + - [Must ship — breaking or a11y-required](#must-ship--breaking-or-a11y-required) + - [Additive — ships when ready, zero breakage for consumers already on 2nd-gen](#additive--ships-when-ready-zero-breakage-for-consumers-already-on-2nd-gen) +- [2nd-gen API decisions](#2nd-gen-api-decisions) + - [Public API](#public-api) + - [Behavioral semantics](#behavioral-semantics) + - [ARIA and keyboard contract](#aria-and-keyboard-contract) + - [Shadow DOM and cross-root ARIA](#shadow-dom-and-cross-root-aria) +- [Architecture: core vs SWC split](#architecture-core-vs-swc-split) +- [Migration checklist](#migration-checklist) + - [Preparation (this ticket)](#preparation-this-ticket) + - [Setup](#setup) + - [API](#api) + - [Styling](#styling) + - [Accessibility](#accessibility) + - [Testing](#testing) + - [Documentation](#documentation) + - [Review](#review) +- [Blockers and open questions](#blockers-and-open-questions) + - [Open — architecture and behavior](#open--architecture-and-behavior) + - [Open — scope and prerequisites](#open--scope-and-prerequisites) + - [Resolved decisions](#resolved-decisions) +- [References](#references) + +
+ + + +> **SWC-1898** · Planning output. Must be reviewed before implementation begins. + +--- + +## TL;DR + +- Tabs is a three-element architecture (`swc-tabs`, `swc-tab`, `swc-tab-panel`). Overflow is deferred to phase 2. +- Keyboard navigation migrates from `RovingTabindexController` to `FocusgroupNavigationController` ([#6129](https://github.com/adobe/spectrum-web-components/pull/6129), merged), fixing 1st-gen bugs with direction, RTL, and orientation +- Disabled tabs use `aria-disabled="true"` (not native `disabled`) so they remain discoverable by assistive technology +- `aria-orientation` must be co-located with `role="tablist"` (fixing a 1st-gen bug where they were on different elements) +- `auto` boolean renamed to `keyboardActivation` (`'automatic' | 'manual'`) to align with React Spectrum (Q16) +- `compact` boolean renamed to `density` (`'compact' | 'regular'`) to align with Figma/React Spectrum (Q17) +- `emphasized`, `quiet`, and `size` are **not in the S2 API** and are removed unless design confirms otherwise (Q18) +- `direction="vertical-right"` is a 1st-gen SWC addition not in Spectrum CSS; removal is a breaking change for consumers using it (Q4) +- S2 overflow changes from scroll-based to Picker-based collapse — **deferred to phase 2** (Q5, Q19 resolved) +- Several public API surfaces are removed: `rovingTabindexController` field, `focusElement` getter (`Focusable` dropped — Q2 resolved), module-level exports, CSS deep imports +- `change` event rename to `swc-change` would silently break all consumers — strongly recommend keeping `change` + +### Most blocking open questions + +- `Q3` in [Open — architecture and behavior](#open--architecture-and-behavior): cross-root ARIA — how will `aria-controls` / `aria-labelledby` ID references resolve if DOM arrangement changes? +- `Q7` in [Open — architecture and behavior](#open--architecture-and-behavior): `change` event naming — keep `change` or rename to `swc-change`? Needs team alignment. + +### Recently resolved + +- `Q1`: `FocusgroupNavigationController` merged ([#6129](https://github.com/adobe/spectrum-web-components/pull/6129)). Tabs will use this controller. +- `Q2`: `Focusable` mixin not needed — `FocusgroupNavigationController` + `delegatesFocus` covers all use cases. +- `Q5`, `Q14`, `Q19`: Overflow deferred to phase 2. +- `Q6`: `enableTabsScroll` deferred with overflow. +- `Q8`: Moot — `size` removed per Q18. +- `Q11`: Internal DOM changes are not a consumer concern. +- `Q18`: Downgraded from blocking — S2 evidence is clear, design confirmation can happen during implementation. + +--- + +## 1st-gen API surface + +**Source:** [`1st-gen/packages/tabs/src/`](../../../../1st-gen/packages/tabs/src/) (`Tabs.ts`, `Tab.ts`, `TabPanel.ts`, `TabsOverflow.ts`) +**Tests:** [`1st-gen/packages/tabs/test/`](../../../../1st-gen/packages/tabs/test/) (`tabs.test.ts`, `tab.test.ts`, `tabs-overflow.test.ts`) +**Version:** `@spectrum-web-components/tabs` +**Custom element tags:** `sp-tabs`, `sp-tab`, `sp-tab-panel`, `sp-tabs-overflow` + +### sp-tabs (`Tabs`) + +#### Properties / attributes + +| Property | Type | Default | Attribute | Notes | +|---|---|---|---|---| +| `auto` | `boolean` | `false` | `auto` | Automatic activation — selection follows focus. | +| `compact` | `boolean` | `false` | `compact` | Reflected. Tabs displayed closer together. | +| `direction` | `'horizontal' \| 'vertical' \| 'vertical-right'` | `'horizontal'` | `direction` | Reflected. Tablist orientation. `vertical-right` is a 1st-gen SWC addition not present in Spectrum CSS. | +| `emphasized` | `boolean` | `false` | `emphasized` | Reflected. Visually emphasized style. | +| `label` | `string` | `''` | `label` | `aria-label` for the tablist. | +| `enableTabsScroll` | `boolean` | `false` | `enable-tabs-scroll` | Enable horizontal scroll on the tab list. camelCase attribute is unusual; candidate for rename. | +| `quiet` | `boolean` | `false` | `quiet` | Reflected. Display without divider. | +| `selected` | `string` | `''` | `selected` | Reflected. `value` of the selected tab. | +| `size` | `ElementSize` | none | `size` | From `SizedMixin` with `noDefaultSize: true`. CSS treats missing size as medium styling. | +| `disabled` | `boolean` | `false` | `disabled` | Inherited from `Focusable` base class. | +| `selectionIndicatorStyle` | `string` | (no selection) | `attribute: false` | Internal. Inline style for the selection indicator. | +| `shouldAnimate` | `boolean` | `false` | `attribute: false` | Internal. Animation toggle. | +| `autofocus` | `boolean` | `false` | `autofocus` | Inherited from `Focusable`. Private in JSDoc. | +| `tabIndex` | `number` | managed | `tabindex` | Inherited from `Focusable`. Overridden getter/setter; private in JSDoc. | + +#### Methods + +| Method | Signature | Notes | +|---|---|---| +| `scrollTabs` | `(delta: number, behavior?: ScrollBehavior): void` | Scrolls the tab list horizontally. | +| `scrollToSelection` | `(): Promise` | Scrolls the selected tab into view. | +| `scrollState` | getter → `{ canScrollLeft, canScrollRight }` | RTL-aware scroll state. | +| `focusElement` | getter → `Tab \| this` | Returns the focusable element. | + +#### Events + +| Event | Detail | Bubbles | Composed | Notes | +|---|---|---|---|---| +| `change` | none | No | No | Cancelable; `preventDefault()` reverts selection. | +| `sp-tabs-scroll` | none | Yes | Yes | Fired on scroll of the tab list. | + +#### Slots + +| Slot | Content | Notes | +|---|---|---| +| default | `sp-tab` elements | | +| `tab-panel` | `sp-tab-panel` elements | | + +#### CSS parts + +| Part | Description | +|---|---| +| `tablist` | The `#list` container div. | + +### sp-tab (`Tab`) + +#### Properties / attributes + +| Property | Type | Default | Attribute | Notes | +|---|---|---|---|---| +| `disabled` | `boolean` | `false` | `disabled` | Reflected. | +| `label` | `string` | `''` | `label` | Reflected. Fallback text label when default slot is empty. | +| `selected` | `boolean` | `false` | `selected` | Reflected. Set by parent `sp-tabs`. | +| `vertical` | `boolean` | `false` | `vertical` | Reflected. Vertical orientation styling. | +| `value` | `string` | `''` | `value` | Reflected. Unique ID, used for tab-panel matching. | + +#### Slots + +| Slot | Content | Notes | +|---|---|---| +| default | Text label of the tab | Nested under a `