diff --git a/2nd-gen/packages/core/components/tooltip/Tooltip.base.ts b/2nd-gen/packages/core/components/tooltip/Tooltip.base.ts index 3cfd943481..44b6efcd39 100644 --- a/2nd-gen/packages/core/components/tooltip/Tooltip.base.ts +++ b/2nd-gen/packages/core/components/tooltip/Tooltip.base.ts @@ -63,7 +63,7 @@ export abstract class TooltipBase extends SpectrumElement { /** * The preferred placement of the tooltip relative to its trigger. - * Applies a CSS class for tip direction; pixel positioning requires `PlacementController` (additive phase). + * Controls the tip direction via the reflected `placement` attribute; pixel positioning requires `PlacementController` (additive phase). * * @default 'top' */ @@ -81,7 +81,7 @@ export abstract class TooltipBase extends SpectrumElement { /** * The `id` of the trigger element in the same document tree root. * Resolved via `getRootNode().getElementById(this.for)`. - * Active from the initial release; drives ARIA relationship wiring on `open` change. + * Drives ARIA relationship wiring on `open` change. */ @property({ attribute: 'for', type: String }) public for: string | undefined; @@ -89,7 +89,7 @@ export abstract class TooltipBase extends SpectrumElement { /** * Explicit trigger element reference. Overrides `for` when set. * Use for cross-shadow-root triggers or programmatic insertion where `getElementById` is scoped to the wrong root. - * Setter only — no HTML attribute. + * Setter only; no HTML attribute. * * @default null */ @@ -99,8 +99,8 @@ export abstract class TooltipBase extends SpectrumElement { /** * Duration in milliseconds of the warm-up delay before the tooltip shows on pointer hover. * Set to `0` to show immediately on hover. Keyboard focus (`focusin` when `:focus-visible`) - * always shows the tooltip immediately regardless of this value. The cooldown duration (before the next hover must wait again) - * matches this value. Warm-up/cooldown state is shared across all tooltips in the same document, + * always shows the tooltip immediately regardless of this value. The cooldown duration after the + * pointer leaves the trigger matches this value. Warm-up/cooldown state is shared across all tooltips in the same document, * so moving quickly between adjacent triggers (e.g. a toolbar) shows each subsequent tooltip * immediately after the first warm-up elapses. * diff --git a/2nd-gen/packages/swc/.storybook/preview-head.html b/2nd-gen/packages/swc/.storybook/preview-head.html index bfd12babc7..c61e8ffd0e 100644 --- a/2nd-gen/packages/swc/.storybook/preview-head.html +++ b/2nd-gen/packages/swc/.storybook/preview-head.html @@ -116,7 +116,25 @@ } h4 { - font-size: var(--swc-heading-size-s) !important; + font-size: var(--swc-heading-size-xs) !important; + } + + kbd { + line-height: 1; + margin: 0px 2px; + padding: 3px 5px; + white-space: nowrap; + border-radius: 3px; + font-size: 12px; + border: 1px solid rgb(147, 158, 172); + border-inline-end-width: 2px; + border-block-end-width: 2px; + color: rgba(46, 51, 56, 0.9); + background-color: rgb(246, 249, 252); + font-family: + ui-monospace, Menlo, Monaco, 'Roboto Mono', 'Oxygen Mono', + 'Ubuntu Monospace', 'Source Code Pro', 'Droid Sans Mono', 'Courier New', + monospace !important; } :is(html, p, li, ul, ol) { diff --git a/2nd-gen/packages/swc/components/tooltip/Tooltip.ts b/2nd-gen/packages/swc/components/tooltip/Tooltip.ts index 9136027c9d..8e5e399d06 100644 --- a/2nd-gen/packages/swc/components/tooltip/Tooltip.ts +++ b/2nd-gen/packages/swc/components/tooltip/Tooltip.ts @@ -27,7 +27,7 @@ import styles from './tooltip.css'; * * @slot - Text label displayed in the tooltip. * - * @cssprop --swc-tooltip-background-color - Background color of the tooltip bubble. Defaults to the neutral background color token. + * @cssprop --swc-tooltip-background-color - Background color of the tooltip bubble. Overrides the variant-specific background color. * * @fires swc-open - Dispatched when the tooltip begins to open, before the transition plays. * @fires swc-close - Dispatched when the tooltip begins to close, before the transition plays. diff --git a/2nd-gen/packages/swc/components/tooltip/migration-guide.mdx b/2nd-gen/packages/swc/components/tooltip/migration-guide.mdx new file mode 100644 index 0000000000..6f797fb551 --- /dev/null +++ b/2nd-gen/packages/swc/components/tooltip/migration-guide.mdx @@ -0,0 +1,283 @@ +import { Meta } from '@storybook/addon-docs/blocks'; + + + +# Tooltip migration guide + +Replace `` with ``, update the import, rename the tag, update event listeners, and change the authoring pattern so the tooltip is a sibling of the trigger rather than nested inside it. + +
+ ℹ️ Initial release scope. This release delivers the tag + rename, variant and event API changes, ARIA relationship wiring via{' '} + for, and native {'popover="auto"'} open/close + behavior.{' '} + + Automatic hover/focus trigger wiring and viewport-aware pixel positioning + are not yet available. + {' '} + They require HoverController and PlacementController + , which ship in a future release. Until then, control visibility via the{' '} + open property and position the tooltip with a helper library like + Floating UI. +
+ +## Installation + +```bash +# Remove +yarn remove @spectrum-web-components/tooltip + +# Add +yarn add @adobe/spectrum-wc +``` + +Update your import: + +```js +// Before +import '@spectrum-web-components/tooltip/sp-tooltip.js'; + +// After +import '@adobe/spectrum-wc/components/tooltip/swc-tooltip.js'; +``` + +> `@adobe/spectrum-wc` is a monolithic package. Importing via subpath (e.g. `@adobe/spectrum-wc/components/tooltip/swc-tooltip.js`) registers and loads only that component's bundle. + +## What changed + +### Renamed + +| Area | Spectrum 1 (`sp-tooltip`) | Spectrum 2 (`swc-tooltip`) | +| ------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | +| Tag | `sp-tooltip` | `swc-tooltip` | +| Import path | `@spectrum-web-components/tooltip/sp-tooltip.js` | `@adobe/spectrum-wc/components/tooltip/swc-tooltip.js` | +| `variant` | `'info'` | `'informative'` | +| `swc-open` / `swc-close` events | `sp-opened` / `sp-closed` | `swc-open`, `swc-after-open`, `swc-close`, `swc-after-close` | +| Trigger wiring attribute | `self-managed` | `for="[id]"` declares the trigger relationship and wires ARIA now. Automatic hover/focus open/close ships in a future release. | + +### Added in Spectrum 2 + +| Addition | Notes | +| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| `for` attribute | Declares the trigger relationship by ID. Required for ARIA wiring and (in the additive phase) automatic hover/focus open/close. | +| `manual` boolean attribute | Opt out of automatic controller wiring; consumer manages open/close via the `open` property. ARIA wiring still fires on `open` change. | +| Logical placements: `start`, `end` | RTL-aware inline placement values; physical values (`top`, `bottom`, `left`, `right`) are unchanged. | +| `delay` attribute | Warm-up/cooldown duration in ms (default 1500). `delay="0"` shows immediately. **Additive phase:** active when `HoverController` ships. | +| `labeling` boolean attribute | Switches ARIA wiring to `ariaLabelledByElements` for icon-only triggers. **Additive phase.** | +| `swc-after-open` / `swc-after-close` events | Fire after the open/close transition completes. | + +### Removed in Spectrum 2 + +| Removed | Replacement | +| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `slot="icon"` | No replacement. Remove all icon content from tooltips; Spectrum 2 tooltip is text-only. | +| `variant="positive"` | Use `variant="informative"`, `variant="neutral"`, or `variant="negative"` as content warrants. | +| `variant="info"` | Renamed to `variant="informative"`. | +| `self-managed` attribute | Removed. Automatic trigger wiring is the default. Add `for="[id]"` to the tooltip pointing to the trigger's `id`. | +| `sp-opened` / `sp-closed` events | Replace with `swc-open` and `swc-close`. The timing also changes; see [step 4](#4-update-event-listeners). | +| `--mod-tooltip-*` CSS custom properties | No `--mod-*` pass-through in Spectrum 2. See [Styling](#styling). | + +## Update your code + +### 1. Update the import + +```js +// Before +import '@spectrum-web-components/tooltip/sp-tooltip.js'; + +// After +import '@adobe/spectrum-wc/components/tooltip/swc-tooltip.js'; +``` + +### 2. Rename the tag and fix variant values + +```html + +Save your changes +Upload complete +No changes yet + + +Save your changes + +Upload complete +No changes yet +``` + +### 3. Remove icon slot content + +Spectrum 2 tooltip does not render icons. Remove all `slot="icon"` content. + +```html + + + + Learn more + + + +Learn more +``` + +### 4. Update event listeners + +Replace `sp-opened`/`sp-closed` with `swc-open` and `swc-close`. Use `swc-after-open` and `swc-after-close` when you need to act after the open or close transition completes. + +**Timing change:** In Spectrum 1, events fired from the internal overlay bridge. In Spectrum 2, they fire from native `popover` lifecycle hooks (`beforetoggle` / `transitionend`). Code that depended on the exact timing relative to DOM paint or transition frames may need adjustment. + +```js +// Before +tooltip.addEventListener('sp-opened', handleOpen); +tooltip.addEventListener('sp-closed', handleClose); + +// After +tooltip.addEventListener('swc-open', handleOpen); // fires before transition +tooltip.addEventListener('swc-after-open', handleOpen); // fires after transition completes +tooltip.addEventListener('swc-close', handleClose); +tooltip.addEventListener('swc-after-close', handleClose); +``` + +### 5. Change the authoring pattern + +In Spectrum 1, `` was typically nested inside the trigger and required the `self-managed` attribute. In Spectrum 2, the tooltip is authored as a **sibling** of the trigger. Add an `id` to the trigger element and a `for` attribute on the tooltip referencing that `id`. + +```html + + + Save + Save your changes + + + +Save +Save your changes +``` + +The tooltip may be placed anywhere in the same document tree root; it does not need to be immediately adjacent to the trigger. + +**For cross-shadow-root triggers** where `getElementById` cannot reach the trigger, set the `triggerElement` property directly instead of using `for`: + +```js +const tooltip = document.querySelector('#my-tip'); +tooltip.triggerElement = shadowRoot.querySelector('#the-trigger'); +``` + +
+ + ℹ️ What for does in this release. + {' '} + Setting {'for="[id]"'} establishes the ARIA relationship between + the trigger and tooltip. It does not yet open or close the + tooltip automatically on hover or focus. Until automatic trigger wiring ships, + you must control visibility manually (see step 6 below). +
+ +### 6. Manage open/close until automatic wiring ships + +In this release, automatic hover/focus open/close is not active. Implement `mouseenter`/`mouseleave` and `focusin`/`focusout` listeners yourself. Keep `for` set so the ARIA relationship is always established when the tooltip opens. + +When automatic wiring ships in a future release, remove these manual listeners; `for` will drive open/close automatically. Add `manual` to the tooltip only when you need to permanently opt out of automatic wiring at that point. + +```html + + + + Save your changes + + +``` + +## Accessibility + +- **Trigger relationship:** Set `for="[trigger-id]"` on every ``. Without it, the ARIA relationship between the tooltip and its trigger is not established, and screen readers will not associate the tooltip text with the trigger. See [step 5](#5-change-the-authoring-pattern). +- **Icon-only triggers:** Add `accessible-label` to the trigger (2nd-gen SWC components) or `aria-label` (native elements) so the trigger has an accessible name independent of the tooltip. The tooltip describes; it does not name. See [step 5](#5-change-the-authoring-pattern). +- **No interactive content in tooltips:** `role="tooltip"` prohibits interactive elements (links, buttons, inputs) inside the tooltip. Refactor those patterns to `` or a dialog component. +- **Non-interactive triggers:** Tooltips must be attached to focusable elements. Static text, decorative icons, and non-interactive elements are not valid tooltip triggers; use contextual help instead. +- **Touch and mobile:** `` is hover/focus only. For touch-accessible disclosure, use `` or contextual help. +- **`popover="auto"` auto-stack change:** Opening a `` closes other open `auto` popovers (menus, pickers). This differs from the Spectrum 1 `type="hint"` isolation behavior, which left menus and pickers open. This is expected behavior, not a bug. + +## Styling + +Spectrum 1 `--mod-tooltip-*` CSS custom properties are **not** supported in Spectrum 2. Spectrum 2 exposes one public custom property. + +{/* @todo Replace the inline-styled callouts in this section with `` once it is migrated to Spectrum 2. */} + +
+ ⚠️ Breaking change. Spectrum 1{' '} + {'--mod-tooltip-*'} properties do not apply to{' '} + {''}. Remove every {'--mod-tooltip-*'}{' '} + override. The only public custom property in Spectrum 2 is listed below. +
+ +{/* @todo Replace the Description column with the `@cssproperty` JSDoc descriptions from ``'s CEM entry once they are added in a follow-up PR. */} + +| Custom property | Description | +| -------------------------------- | ---------------------------------------------------------------------------------------- | +| `--swc-tooltip-background-color` | Background color of the tooltip bubble. Overrides the variant-specific background color. | + +
+ 🚫 Do not target internals. Internal classes,{' '} + {'--_swc-tooltip-*'} private properties, and shadow DOM are{' '} + not public API. Styling applied to them will break without + warning on minor releases. +
+ +## Checklist + +- [ ] Update imports from `@spectrum-web-components/tooltip` to `@adobe/spectrum-wc` +- [ ] Rename all `` to `` +- [ ] Replace `variant="info"` with `variant="informative"` +- [ ] Replace `variant="positive"` with `variant="informative"`, `variant="neutral"`, or `variant="negative"` as appropriate +- [ ] Remove all `slot="icon"` content from tooltips +- [ ] Replace `sp-opened`/`sp-closed` event listeners with `swc-open`/`swc-close` (and `swc-after-open`/`swc-after-close` where needed) +- [ ] Remove `self-managed` from all tooltips; add `id` to the trigger and `for="[id]"` to the tooltip +- [ ] Confirm no tooltip is nested inside its trigger element +- [ ] Add `mouseenter`/`mouseleave` and `focusin`/`focusout` listeners to control `open` until automatic wiring ships +- [ ] Remove all `--mod-tooltip-*` CSS overrides diff --git a/2nd-gen/packages/swc/components/tooltip/stories/tooltip.stories.ts b/2nd-gen/packages/swc/components/tooltip/stories/tooltip.stories.ts index ef4da40b86..32d14dcc88 100644 --- a/2nd-gen/packages/swc/components/tooltip/stories/tooltip.stories.ts +++ b/2nd-gen/packages/swc/components/tooltip/stories/tooltip.stories.ts @@ -180,8 +180,16 @@ const triggered = ( }; /** - * Each story renders one or more buttons that trigger associated tooltips when clicked. - * These stories use a temporary click-to-toggle implementation until `HoverController` is available in the additive phase. + * A `` displays a brief, contextual message near a trigger element. + * + * Author `` as a sibling of the trigger element. Reference the trigger by its `id` + * using the `for` attribute; the tooltip can be placed anywhere in the same document tree root. + * + * For cross-shadow-root triggers where `getElementById` cannot reach the trigger, set the + * `triggerElement` property directly with an element reference. + * + * Each story in this document uses a temporary click-to-toggle interaction. + * Automatic hover and focus wiring is available in a future release. */ const meta: Meta = { title: 'Tooltip', @@ -242,8 +250,6 @@ export const Overview: Story = { * 1. **Tooltip bubble**: Container with rounded corners and variant-specific background color * 2. **Tip indicator**: Triangular arrow pointing toward the trigger element (placement-aware) * 3. **Default slot**: Text content displayed inside the bubble - * - * Click each button below to toggle short and long text variants. */ export const Anatomy: Story = { render: (args) => html` @@ -269,6 +275,11 @@ export const Anatomy: Story = { // ────────────────────────── // OPTIONS STORIES // ────────────────────────── + +/** + * Three semantic variants are available: `neutral` (default), `informative`, and `negative`. + * Each applies a distinct background color token. + */ export const Variants: Story = { render: (args) => html` ${TOOLTIP_VARIANTS.map((variant) => @@ -285,7 +296,7 @@ export const Variants: Story = { /** * The `placement` attribute sets the preferred position of the tooltip relative to its trigger. - * Pixel-accurate anchoring requires `PlacementController` (additive phase); these stories + * Pixel-accurate anchoring requires `PlacementController` (future release); these stories * temporarily use Floating UI directly to verify visual appearance across placements. */ export const Placements: Story = { @@ -348,11 +359,71 @@ export const Placements: Story = { // STATES STORIES // ────────────────────────── -// TODO: will complete in separate documentation pass of phase 7 +/** + * The tooltip has two states: closed (default) and open. + * + * When open, the bubble and directional tip are visible. The `open` property reflects whether the + * tooltip is currently shown. Click the button below to toggle the open state. + */ +export const Open: Story = { + render: (args) => html` + ${triggered({ ...args }, 'tooltip-state-open', 'Action')} + `, + args: { + variant: 'neutral', + placement: 'top', + 'default-slot': 'Save your changes', + }, + play: async ({ canvasElement }: { canvasElement: HTMLElement }) => { + const button = canvasElement.querySelector('swc-button') as HTMLElement; + button?.click(); + }, + tags: ['states'], +}; -// ────────────────────────────── +// ──────────────────────────────── // BEHAVIORS STORIES -// ────────────────────────────── +// ──────────────────────────────── + +/** + * `` dispatches four lifecycle events as the tooltip opens and closes: + * + * - **`swc-open`**: Dispatched when the tooltip begins to open, before the enter transition plays. + * - **`swc-after-open`**: Dispatched after the enter transition completes. + * - **`swc-close`**: Dispatched when the tooltip begins to close, before the exit transition plays. + * - **`swc-after-close`**: Dispatched after the exit transition completes. + * + * When no CSS transition is active (for example, when `prefers-reduced-motion` removes it), + * `swc-after-open` and `swc-after-close` are dispatched synchronously on the same tick as + * `swc-open` and `swc-close`. + * + * Events fire regardless of what caused the state change: setting `open` directly, calling + * `showPopover()`/`hidePopover()`, pressing `Escape`, or a light-dismiss click. + */ +export const Events: Story = { + render: (args) => html` + ${triggered({ ...args }, 'tooltip-behavior-events', 'Open')} + `, + args: { + variant: 'neutral', + placement: 'top', + 'default-slot': 'Save your changes', + }, + tags: ['behaviors'], +}; + +/** + * `` uses `popover="auto"`, which participates in the browser's auto popover stack. + * Opening a tooltip dismisses any other open `auto` popover in the document: other tooltips, + * menus, pickers, and selects. + * + * Set `manual` on the tooltip and manage the `open` property directly to opt out of automatic + * open and close behavior. ARIA relationship wiring still fires on `open` change when `for` or + * `triggerElement` is set. + */ +export const AutoStack: Story = { + tags: ['behaviors', 'description-only'], +}; /** * When a trigger has no visible text label and the tooltip text is its sole accessible name, @@ -385,4 +456,95 @@ export const Labeling: Story = { // ACCESSIBILITY STORIES // ──────────────────────────────── -// TODO: will complete in separate documentation pass of phase 7 +/** + * ### Features + * + * The `` element implements the following accessibility features: + * + * #### ARIA role and relationship + * + * 1. **`role="tooltip"`**: Set on the host element via `connectedCallback`. + * 2. **`ariaDescribedByElements`**: When the tooltip opens, the SWC layer resolves the trigger + * via `for` or `triggerElement` and sets `Element.ariaDescribedByElements = [tooltipHost]` + * on the trigger's interactive surface. For 2nd-gen SWC components with an open shadow root, + * the inner `