@adobe/spectrum-wc-core 0.4.0-snapshot-test.20260717104105 → 2.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +125 -0
- package/dist/components/button/Button.base.d.ts +7 -0
- package/dist/components/button/Button.base.js.map +1 -1
- package/dist/components/button-group/ButtonGroup.base.js +1 -1
- package/dist/components/button-group/ButtonGroup.base.js.map +1 -1
- package/dist/components/illustrated-message/IllustratedMessage.base.js +1 -1
- package/dist/components/illustrated-message/IllustratedMessage.base.js.map +1 -1
- package/dist/components/status-light/StatusLight.base.d.ts +5 -1
- package/dist/components/status-light/StatusLight.base.js.map +1 -1
- package/dist/components/tabs/Tabs.base.d.ts +1 -4
- package/dist/components/tabs/Tabs.base.js.map +1 -1
- package/dist/components/tooltip/Tooltip.base.d.ts +5 -0
- package/dist/components/tooltip/Tooltip.base.js.map +1 -1
- package/dist/controllers/color-controller/index.js +2 -1
- package/dist/controllers/focusgroup-navigation-controller/index.js +2 -0
- package/dist/controllers/hover-controller/index.js +2 -0
- package/dist/controllers/index.d.ts +1 -0
- package/dist/controllers/index.js +12 -11
- package/dist/controllers/live-selection-controller/index.js +2 -0
- package/dist/controllers/pending-controller/index.js +2 -1
- package/dist/controllers/placement-controller/index.js +4 -3
- package/dist/controllers/slot-attribute-propagation-controller/index.js +2 -0
- package/dist/controllers/{slot-attribute-propagation → slot-attribute-propagation-controller}/src/slot-attribute-propagation-controller.d.ts +14 -4
- package/dist/controllers/{slot-attribute-propagation → slot-attribute-propagation-controller}/src/slot-attribute-propagation-controller.js +4 -3
- package/dist/controllers/slot-attribute-propagation-controller/src/slot-attribute-propagation-controller.js.map +1 -0
- package/dist/element/version.d.ts +2 -2
- package/dist/element/version.js +1 -1
- package/dist/element/version.js.map +1 -1
- package/package.json +9 -2
- package/dist/controllers/slot-attribute-propagation/src/slot-attribute-propagation-controller.js.map +0 -1
- /package/dist/controllers/{slot-attribute-propagation → slot-attribute-propagation-controller}/index.d.ts +0 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# @spectrum-web-components/core
|
|
2
|
+
|
|
3
|
+
## 2.0.0-beta.2
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#6413](https://github.com/adobe/spectrum-web-components/pull/6413) [`b4f454b`](https://github.com/adobe/spectrum-web-components/commit/b4f454b979fe60212d1c7ea4f4b86a29def4666b) Thanks [@Rajdeepc](https://github.com/Rajdeepc)! - Add the 2nd-gen `<swc-close-button>`, migrated from the Spectrum 1 `<sp-close-button>`.
|
|
8
|
+
- **API**: `size`, `disabled`, `accessible-label`, and `static-color`; omits the legacy `variant` surface in favor of `static-color`.
|
|
9
|
+
- **Accessibility**: renders a real inner `<button type="button">` with delegated focus; requires `accessible-label` for its icon-only name; the cross icon remains decorative.
|
|
10
|
+
- **Styling**: ships Spectrum 2 sizing and static-color treatments plus the `--swc-close-button-*` custom-property surface for token-aligned overrides.
|
|
11
|
+
- **Docs and tests**: includes Storybook docs, consumer migration guidance, unit coverage, and Playwright accessibility and keyboard tests.
|
|
12
|
+
|
|
13
|
+
- [#6413](https://github.com/adobe/spectrum-web-components/pull/6413) [`b4f454b`](https://github.com/adobe/spectrum-web-components/commit/b4f454b979fe60212d1c7ea4f4b86a29def4666b) Thanks [@Rajdeepc](https://github.com/Rajdeepc)! - **feat(color-handle):** Added `<swc-color-handle>`, the Spectrum 2 migration of `sp-color-handle`.
|
|
14
|
+
|
|
15
|
+
A non-interactive color-picker primitive exposing `color`, `disabled`, `focused`, `open`, and the new `fill` property, with a built-in `<swc-color-loupe>` and an adaptive white-first dual border that meets WCAG 1.4.11 non-text contrast across the color spectrum. The `--mod-colorhandle-*` custom properties are removed; see the migration guide.
|
|
16
|
+
|
|
17
|
+
- [#6439](https://github.com/adobe/spectrum-web-components/pull/6439) [`7f0c3e9`](https://github.com/adobe/spectrum-web-components/commit/7f0c3e937e697e3e1d5d3973ce4f709542f1bbef) Thanks [@caseyisonit](https://github.com/caseyisonit)! - Extract the pending (busy) state into reusable, decoupled 2nd-gen core primitives so any pending-capable component can adopt it.
|
|
18
|
+
- **`@adobe/spectrum-wc-core`**: adds `PendingController` (`/controllers/pending-controller`) for the pending state (delayed activation, inline-size freeze, derived busy accessible name), the render-only `renderPendingSpinner` directive (`/directives/pending-spinner`), and `PendingMixin` (`/mixins`) which wires the controller, the `pending` / `pending-label` properties, and click suppression. `ButtonBase` no longer owns pending state.
|
|
19
|
+
- **`@adobe/spectrum-wc`**: `swc-button` and `swc-action-button` now consume these primitives via `PendingMixin`. No public API change — `pending` / `pending-label` and the busy behavior are unchanged.
|
|
20
|
+
|
|
21
|
+
- [#6413](https://github.com/adobe/spectrum-web-components/pull/6413) [`b4f454b`](https://github.com/adobe/spectrum-web-components/commit/b4f454b979fe60212d1c7ea4f4b86a29def4666b) Thanks [@Rajdeepc](https://github.com/Rajdeepc)! - **refactor(tabs):** Migrate `<swc-tabs>` keyboard navigation to `FocusgroupNavigationController`.
|
|
22
|
+
|
|
23
|
+
Arrow-key, Home, and End navigation is now handled by the controller rather than by manually computed deltas in `handleKeyDown`. The `handleKeyDown` method retains only Enter/Space activation; all roving-tabindex management is delegated to the controller. In automatic activation mode, `focusgroupNavigationActiveChange` events with `source: 'keyboard'` drive selection-follows-focus; events with `source: 'refresh'` or `source: 'programmatic'` are intentionally ignored so that mounting or toggling `disabled` cannot trigger spurious `change` events.
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- [#6518](https://github.com/adobe/spectrum-web-components/pull/6518) [`36052f3`](https://github.com/adobe/spectrum-web-components/commit/36052f305622ad8f46b44bf5d71e2b02ebd8229c) Thanks [@rubencarvalho](https://github.com/rubencarvalho)! - Fix inaccurate API reference tables for several 2nd-gen components by documenting CSS custom properties and JSDoc property descriptions that were missing from the generated Custom Elements Manifest.
|
|
28
|
+
- Added missing `@cssprop` entries: `swc-tabs`/`swc-tab` (5 props), `swc-action-button` and `swc-button` (down-state transform, button also gets `max-inline-size`), `swc-illustrated-message` (10 props covering illustration sizing/color and heading/description typography).
|
|
29
|
+
- Added missing `size`/`variant` property descriptions for `swc-button`, `swc-action-button`, `swc-close-button`, `swc-status-light`, and `swc-badge`.
|
|
30
|
+
- Fixed the Tabs docs page rendering only `swc-tabs`'s API table; `swc-tab` and `swc-tab-panel` (the other two elements of the three-element component) now get their own API tables too, matching the pattern already used by Accordion.
|
|
31
|
+
|
|
32
|
+
No component runtime changes; documentation and Storybook docs-page rendering only.
|
|
33
|
+
|
|
34
|
+
- [#6513](https://github.com/adobe/spectrum-web-components/pull/6513) [`41e0483`](https://github.com/adobe/spectrum-web-components/commit/41e0483f59d3956ce973bbe50b2d54a1d8c73e9f) Thanks [@blunteshwar](https://github.com/blunteshwar)! - **fix(color-loupe):** Added an adaptive white-first inner border to `<swc-color-loupe>` so its chrome meets WCAG 1.4.11 non-text contrast (≥3:1) across the color spectrum.
|
|
35
|
+
|
|
36
|
+
The inner border's opacity now escalates above its default floor only when the white outer halo can't itself carry 3:1 contrast against the loupe's color. The outer border, shape, and sizing are unchanged, and there is no public API change. This supersedes the prior practical-limits exception, matching the adaptive dual-border approach already shipped for `<swc-color-handle>`.
|
|
37
|
+
|
|
38
|
+
- [#6413](https://github.com/adobe/spectrum-web-components/pull/6413) [`b4f454b`](https://github.com/adobe/spectrum-web-components/commit/b4f454b979fe60212d1c7ea4f4b86a29def4666b) Thanks [@Rajdeepc](https://github.com/Rajdeepc)! - **fix(popover):** Fixed `swc-popover` staying dismissed on the next unrelated outside click after a trigger press was dragged off and released elsewhere.
|
|
39
|
+
|
|
40
|
+
A `pointerdown` on the trigger followed by a drag off the trigger and a release elsewhere never dispatches a `click`, so the internal reopen-guard flag was left stuck `true`, misattributing the next unrelated outside light-dismiss to that stale press and swallowing the following legitimate trigger click.
|
|
41
|
+
|
|
42
|
+
- [#6490](https://github.com/adobe/spectrum-web-components/pull/6490) [`1e053fe`](https://github.com/adobe/spectrum-web-components/commit/1e053fe761c20b1df70c842cb3f9dcee94e02d8d) Thanks [@cdransf](https://github.com/cdransf)! - **fix(slot-attribute-propagation-controller):** Fixed `SlotAttributePropagationController` permanently skipping re-propagation when a repeated value was recorded before its target slot had resolved, and added support for propagating attributes that are only sometimes present on the host.
|
|
43
|
+
|
|
44
|
+
`getValue` may now return `null` to remove the propagated attribute from assigned elements instead of setting it to an empty string. The `hostUpdated()` no-op guard now only records a value as applied once the slot actually resolves, so a slot that resolves after an earlier unsuccessful attempt with the same value is no longer skipped forever.
|
|
45
|
+
|
|
46
|
+
`SlotAttributePropagationController` is also now a public export of `@adobe/spectrum-wc-core` (`@spectrum-web-components/core/controllers/slot-attribute-propagation-controller.js`), alongside dedicated tests and a Storybook controller docs page. Existing consumers (`<swc-button-group>`, `<swc-illustrated-message>`) are unaffected aside from benefiting from the propagation-guard fix.
|
|
47
|
+
|
|
48
|
+
- [#6533](https://github.com/adobe/spectrum-web-components/pull/6533) [`5da5474`](https://github.com/adobe/spectrum-web-components/commit/5da54741eadcf61d60fd37d700ff457419a4d2ca) Thanks [@cdransf](https://github.com/cdransf)! - **fix(progress-circle):** Replaced the `animation: none` reduced-motion override on `<swc-progress-circle>`'s indeterminate state with a slowed, single-rotation animation driven by custom properties (`--swc-progress-circle-rotate-start`, `--swc-progress-circle-rotate-end`, `--swc-progress-circle-dashoffset-30`), so `prefers-reduced-motion: reduce` still conveys progress without the distracting spin.
|
|
49
|
+
|
|
50
|
+
## 2.0.0-beta.1
|
|
51
|
+
|
|
52
|
+
### Minor Changes
|
|
53
|
+
|
|
54
|
+
- [#6340](https://github.com/adobe/spectrum-web-components/pull/6340) [`57a77bc`](https://github.com/adobe/spectrum-web-components/commit/57a77bcee7eeb8f5a7b1084f3ad91543c2f92034) Thanks [@cdransf](https://github.com/cdransf)! - Added `<swc-action-button>` with full Spectrum 2 visual fidelity, migrated from the Spectrum 1 `<sp-action-button>`. See the [component docs](https://spectrum-web-components.adobe.com/?path=/docs/components-action-button--docs) and [migration guide](https://spectrum-web-components.adobe.com/?path=/docs/components-action-button-migration-guide--docs).
|
|
55
|
+
- **API**: `accessible-label` replaces `label`; `size` includes `xs` (not on `swc-button`); `quiet` and `static-color` retained as primary visual differentiators; `pending` / `pending-label` added (matching `swc-button`); `aria-haspopup` / `aria-expanded` forwarded to the inner `<button>` for menu-trigger patterns.
|
|
56
|
+
- **Breaking changes**: `toggles`, `selected`, `emphasized`, and `aria-pressed` removed (toggle UX moves to `swc-toggle-button` / `swc-toggle-button-group`); `href` and the link API removed (use native `<a>`); `hold-affordance` / `longpress` deferred; `label` renamed to `accessible-label`.
|
|
57
|
+
- **Accessibility**: semantics and focus land on the internal native `<button>` (`delegatesFocus: true`); host carries no `role="button"`; `aria-disabled="true"` on the inner `<button>` during pending state; dev-mode warning when icon-only usage is missing `accessible-label`.
|
|
58
|
+
- **Styling**: exposes `--swc-action-button-*` custom properties (replaces `--mod-actionbutton-*` / `--spectrum-actionbutton-*`); full Spectrum 2 token coverage across all size × quiet × static-color combinations; Windows High Contrast support.
|
|
59
|
+
- **Docs and tests**: per-component Storybook docs page, consumer migration guide, and full unit + accessibility test coverage.
|
|
60
|
+
|
|
61
|
+
- [#6395](https://github.com/adobe/spectrum-web-components/pull/6395) [`b4740eb`](https://github.com/adobe/spectrum-web-components/commit/b4740eb58f8a9410dbfae2969ca7a21c07bc63bd) Thanks [@Rajdeepc](https://github.com/Rajdeepc)! - - [#6395](https://github.com/adobe/spectrum-web-components/pull/6395) [`acd555a`](https://github.com/adobe/spectrum-web-components/commit/acd555a975508f9249a1394ac808a62b2d7cbfe3) - `ButtonGroup` — Added `<swc-button-group>` with full Spectrum 2 visual fidelity. See the [component docs](https://spectrum-web-components.adobe.com/?path=/docs/button-group--docs) and [migration guide](https://spectrum-web-components.adobe.com/?path=/docs/button-group-migration-guide--docs).
|
|
62
|
+
|
|
63
|
+
- [#6454](https://github.com/adobe/spectrum-web-components/pull/6454) [`591fa65`](https://github.com/adobe/spectrum-web-components/commit/591fa65574f7f8175373683e7495059b8ca27fa4) Thanks [@cdransf](https://github.com/cdransf)! - Added an `actions` slot to `<swc-illustrated-message>` for placing a `<swc-button>` or `<swc-button-group>` below the description. The component automatically propagates its `size` to every slotted element. Also adds `SlotAttributePropagationController`, an internal Lit `ReactiveController` that propagates any host attribute to slotted children; used by `IllustratedMessage` and `ButtonGroup`.
|
|
64
|
+
|
|
65
|
+
## 2.0.0-beta.0
|
|
66
|
+
|
|
67
|
+
### Minor Changes
|
|
68
|
+
|
|
69
|
+
- [#6395](https://github.com/adobe/spectrum-web-components/pull/6395) [`b4740eb`](https://github.com/adobe/spectrum-web-components/commit/b4740eb58f8a9410dbfae2969ca7a21c07bc63bd) Thanks [@Rajdeepc](https://github.com/Rajdeepc)! - `Button Group` — Added `<swc-button-group>` with full Spectrum 2 visual fidelity. See the [component docs](https://spectrum-web-components.adobe.com/?path=/docs/button-group--docs) and [migration guide](https://spectrum-web-components.adobe.com/?path=/docs/button-group-migration-guide--docs).
|
|
70
|
+
|
|
71
|
+
- [#6358](https://github.com/adobe/spectrum-web-components/pull/6358) [`fe85234`](https://github.com/adobe/spectrum-web-components/commit/fe8523478348419aaeb7dc3f925ef00c4b8b40aa) Thanks [@5t3ph](https://github.com/5t3ph)! - Add `HoverController`, a Lit `ReactiveController` that manages hover and keyboard-focus event wiring for components that use the native Popover API, such as Tooltip.
|
|
72
|
+
|
|
73
|
+
- [#6370](https://github.com/adobe/spectrum-web-components/pull/6370) [`7c1d0c5`](https://github.com/adobe/spectrum-web-components/commit/7c1d0c574c918474196fa0f6007b9f905455f772) Thanks [@blunteshwar](https://github.com/blunteshwar)! - Add the 2nd-gen `<swc-meter>`, migrated from the Spectrum 1 `<sp-meter>`.
|
|
74
|
+
- **API**: `value` (replaces `progress`) with `min-value` / `max-value` for arbitrary ranges; `variant` (`informative` default, `positive`, `notice`, `negative`) with unknown values normalized to `informative`; `label-position` (`top` / `side`, replaces the `side-label` boolean); `value-label` and `formatOptions` for locale-aware value text; `label` and `description` named slots; `accessible-label` for the no-visible-label case.
|
|
75
|
+
- **Accessibility**: the WAI-ARIA `meter` role and all `aria-value*`, `aria-labelledby`, and `aria-describedby` attributes live on the internal bar element, not the host; non-focusable, read-only; honors `prefers-reduced-motion: reduce` (WCAG 2.3.3) on the shared linear-progress base by dropping the fill transition.
|
|
76
|
+
- **Styling**: exposes the `--swc-linear-progress-*` custom-property surface (replaces `--mod-progressbar-*` / `--mod-meter-*`); adds `static-color="black"` alongside `white`.
|
|
77
|
+
- **Docs and tests**: per-component Storybook docs page, consumer migration guide, and full unit + accessibility test coverage.
|
|
78
|
+
|
|
79
|
+
- [#6337](https://github.com/adobe/spectrum-web-components/pull/6337) [`e334d4f`](https://github.com/adobe/spectrum-web-components/commit/e334d4f4d622325984912b9689aae05e5ac179ce) Thanks [@rubencarvalho](https://github.com/rubencarvalho)! - Add `PlacementController`, a Floating UI-backed reactive controller for positioning anchored floating elements relative to a trigger. This adds `@floating-ui/dom` as a runtime dependency of `@spectrum-web-components/core`.
|
|
80
|
+
|
|
81
|
+
- [#6387](https://github.com/adobe/spectrum-web-components/pull/6387) [`64a3d50`](https://github.com/adobe/spectrum-web-components/commit/64a3d50a6f4284cc32b8e13f2c8e05489012cef6) Thanks [@5t3ph](https://github.com/5t3ph)! - Add the 2nd-gen `<swc-tooltip>`, migrated from the Spectrum 1 `<sp-tooltip>`.
|
|
82
|
+
- **API**: `for` attribute wires the tooltip to a trigger by ID; `trigger-element` property for programmatic or cross-shadow-root wiring; `manual` opts out of automatic wiring; `delay` (default 1500ms) for hover warm-up; `offset`, `cross-offset`, `container-padding`, and `should-flip` for viewport-aware positioning; `labeling` switches ARIA wiring to `ariaLabelledByElements` for icon-only triggers; `variant` accepts `neutral` (default), `informative`, and `negative`.
|
|
83
|
+
- **Breaking changes from `<sp-tooltip>`**: `slot="icon"` removed; `variant="positive"` removed; `variant="info"` renamed to `variant="informative"`; `self-managed` attribute removed (automatic wiring is now the default; use `manual` to opt out); events renamed from `sp-opened`/`sp-closed` to `swc-open`/`swc-after-open`/`swc-close`/`swc-after-close`; authoring pattern changed — `<swc-tooltip>` is authored as a sibling of the trigger, not nested inside it.
|
|
84
|
+
- **Accessibility**: `role="tooltip"` set on the host; `Element.ariaDescribedByElements` wired on the trigger's inner interactive element on open; `Escape` closes without moving focus via native `popover="auto"`; WCAG 1.4.13 pointer bridge keeps the tooltip open when the pointer moves from the trigger into the bubble; high-contrast border in forced-colors mode.
|
|
85
|
+
- **Controllers**: `HoverController` manages hover and keyboard-focus wiring with warm-up/cooldown timing; `PlacementController` handles viewport-aware pixel positioning via Floating UI with automatic flip on viewport collision.
|
|
86
|
+
- **Docs and tests**: per-component Storybook docs page, consumer migration guide, and full unit + accessibility test coverage.
|
|
87
|
+
|
|
88
|
+
## 0.1.0
|
|
89
|
+
|
|
90
|
+
### Minor Changes
|
|
91
|
+
|
|
92
|
+
- [#6067](https://github.com/adobe/spectrum-web-components/pull/6067) [`ba14a2b`](https://github.com/adobe/spectrum-web-components/commit/ba14a2b6361a0089a9a8c72232f245cde0716d89) - `Core` — Refactored size mixin exports and badge type naming for consistency. Replaced `ElementSizes` record with `ELEMENT_SIZES` const array and `DEFAULT_ELEMENT_SIZES`; `VALID_SIZES` typed as `readonly ElementSize[]`. Badge exports renamed: `BADGE_VARIANTS_S2` → `BADGE_VARIANTS`, `BadgeVariantS2` → `BadgeVariant`, `BadgeColorVariantS2` → `BadgeColorVariant`.
|
|
93
|
+
|
|
94
|
+
- [#6122](https://github.com/adobe/spectrum-web-components/pull/6122) [`7b153b5`](https://github.com/adobe/spectrum-web-components/commit/7b153b5433a8f6d47850f8939b058f7bc431c48c) - `Badge` — Migrated to 2nd-gen with `subtle`/`outline` styles, additional color variants, and updated defaults. See the [component docs](https://spectrum-web-components.adobe.com/?path=/docs/components-badge--docs) and [migration guide](https://spectrum-web-components.adobe.com/?path=/docs/components-badge-migration-guide--docs).
|
|
95
|
+
`Divider` — Migrated to 2nd-gen. See the [component docs](https://spectrum-web-components.adobe.com/?path=/docs/components-divider--docs) and [migration guide](https://spectrum-web-components.adobe.com/?path=/docs/components-divider-migration-guide--docs).
|
|
96
|
+
`Progress Circle` — Migrated to 2nd-gen; removed `indeterminate` attribute. See the [component docs](https://spectrum-web-components.adobe.com/?path=/docs/components-progress-circle--docs) and [migration guide](https://spectrum-web-components.adobe.com/?path=/docs/components-progress-circle-migration-guide--docs).
|
|
97
|
+
`Status Light` — Migrated to 2nd-gen; removed `disabled` attribute and `accent` variant. See the [component docs](https://spectrum-web-components.adobe.com/?path=/docs/components-status-light--docs) and [migration guide](https://spectrum-web-components.adobe.com/?path=/docs/components-status-light-migration-guide--docs).
|
|
98
|
+
|
|
99
|
+
- [#6183](https://github.com/adobe/spectrum-web-components/pull/6183) [`8aa488e`](https://github.com/adobe/spectrum-web-components/commit/8aa488e3867725b1ed93b2b5c31cba686f116688) - `Tabs` — Added 2nd-gen tabs (`swc-tabs`, `swc-tab`, `swc-tab-panel`) with Spectrum 2 styling, selection indicator, and keyboard navigation. See the [component docs](https://spectrum-web-components.adobe.com/?path=/docs/components-tabs--docs) and [migration guide](https://spectrum-web-components.adobe.com/?path=/docs/components-tabs-migration-guide--docs).
|
|
100
|
+
|
|
101
|
+
### Patch Changes
|
|
102
|
+
|
|
103
|
+
- [#5936](https://github.com/adobe/spectrum-web-components/pull/5936) [`f37dec6`](https://github.com/adobe/spectrum-web-components/commit/f37dec6ae39fd89a4c12e084b4a0f4d9092d79b0) - `Core` — Overhauled text direction management. Replaced active `dir` attribute management with passive CSS `:dir()` pseudo-class and `getComputedStyle(this).direction`; removed `MutationObserver`-based direction tracking and converted physical CSS properties to logical equivalents.
|
|
104
|
+
|
|
105
|
+
## 0.0.4
|
|
106
|
+
|
|
107
|
+
### Patch Changes
|
|
108
|
+
|
|
109
|
+
- [#5998](https://github.com/adobe/spectrum-web-components/pull/5998) [`6f5419a`](https://github.com/adobe/spectrum-web-components/commit/6f5419a4de29a1ee440a36c1a57d8c2e1476e2f6) - `Core` — Fixed missing export for `alert-banner` which could cause build failures in certain environments.
|
|
110
|
+
|
|
111
|
+
## 0.0.3
|
|
112
|
+
|
|
113
|
+
### Patch Changes
|
|
114
|
+
|
|
115
|
+
- [#5993](https://github.com/adobe/spectrum-web-components/pull/5993) [`95e1c25`](https://github.com/adobe/spectrum-web-components/commit/95e1c25672f62f3723dfa66129ae5ecdeabe578a) - `Core` — Replaced wildcard exports with explicit named exports for better bundler compatibility. Changed build target from ES2022 to ES2018. Added `@spectrum-web-components/core` as direct dependency to `@spectrum-web-components/shared`. Added `@lit-labs/observers` as dependency.
|
|
116
|
+
|
|
117
|
+
## 0.0.2
|
|
118
|
+
|
|
119
|
+
### Patch Changes
|
|
120
|
+
|
|
121
|
+
- [#5900](https://github.com/adobe/spectrum-web-components/pull/5900) [`283f0fe`](https://github.com/adobe/spectrum-web-components/commit/283f0fe07533c464e9fe1a3e7edebecb9128e11f) - `Core` — Added missing dependencies to package.json files of several components.
|
|
122
|
+
|
|
123
|
+
- [#5893](https://github.com/adobe/spectrum-web-components/pull/5893) [`1d76b70`](https://github.com/adobe/spectrum-web-components/commit/1d76b7093d8ff5f9b26f07a69086488341a02650) - `Overlay` — Fixed hover overlays to close with the Esc key when trigger is not focused.
|
|
124
|
+
|
|
125
|
+
- [#5866](https://github.com/adobe/spectrum-web-components/pull/5866) [`9cb816b`](https://github.com/adobe/spectrum-web-components/commit/9cb816b5ac80387fdc9bb87381f5149fecb1b595) - `Core` — Added `typesVersions` to improve TypeScript module resolution for users with `moduleResolution: "node"`.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PropertyValues } from 'lit';
|
|
2
2
|
import { SpectrumElement } from '../../element/index.js';
|
|
3
|
+
import { ButtonSize } from './Button.types.js';
|
|
3
4
|
declare const ButtonBase_base: typeof SpectrumElement & {
|
|
4
5
|
new (...args: any[]): import('../../mixins/index.js').SlotPresenceObservingInterface;
|
|
5
6
|
prototype: import('../../mixins/index.js').SlotPresenceObservingInterface;
|
|
@@ -27,6 +28,12 @@ declare const ButtonBase_base: typeof SpectrumElement & {
|
|
|
27
28
|
*/
|
|
28
29
|
export declare abstract class ButtonBase extends ButtonBase_base {
|
|
29
30
|
static shadowRootOptions: ShadowRootInit;
|
|
31
|
+
/**
|
|
32
|
+
* The size of the button.
|
|
33
|
+
*
|
|
34
|
+
* @default m
|
|
35
|
+
*/
|
|
36
|
+
size: ButtonSize;
|
|
30
37
|
/**
|
|
31
38
|
* Whether the button is disabled. Removes focusability and prevents
|
|
32
39
|
* interaction.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.base.js","names":[],"sources":["../../../components/button/Button.base.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\n\nimport { SpectrumElement } from '@adobe/spectrum-wc-core/element/index.js';\nimport {\n ObserveSlotPresence,\n ObserveSlotText,\n SizedMixin,\n} from '@adobe/spectrum-wc-core/mixins/index.js';\n\nimport { BUTTON_VALID_SIZES } from './Button.types.js';\n\n/**\n * Abstract base class for all button-like components. Owns shared semantic\n * concerns: interaction state, sizing, slot-derived icon/label state,\n * accessible-name resolution, and host-to-internal-button attribute forwarding.\n *\n * Visual API specific to `sp-button` (`variant`, `fill-style`, `static-color`)\n * is intentionally absent so that ActionButton, ClearButton, CloseButton,\n * PickerButton, and InfieldButton can extend this base without inheriting\n * the `swc-button` visual surface.\n *\n * @attribute {ElementSize} size - The size of the button.\n *\n * @todo We currently have 3 levels of mixins on this class, but the mixin\n * composition guide recommends a maximum of 2. Explore reducing after milestone 2.\n */\nexport abstract class ButtonBase extends SizedMixin(\n ObserveSlotText(ObserveSlotPresence(SpectrumElement, '[slot=\"icon\"]'), ''),\n { validSizes: BUTTON_VALID_SIZES }\n) {\n static override shadowRootOptions: ShadowRootInit = {\n ...SpectrumElement.shadowRootOptions,\n delegatesFocus: true,\n };\n\n // ──────────────────\n // SHARED API\n // ──────────────────\n\n /**\n * Whether the button is disabled. Removes focusability and prevents\n * interaction.\n */\n @property({ type: Boolean, reflect: true })\n public disabled: boolean = false;\n\n /**\n * Accessible label forwarded to the internal `<button>` element as\n * `aria-label`. Required for icon-only buttons, which have no visible text.\n */\n @property({ type: String, attribute: 'accessible-label' })\n public accessibleLabel?: string;\n\n // ──────────────────────\n // IMPLEMENTATION\n // ──────────────────────\n\n protected get hasIcon(): boolean {\n return this.slotContentIsPresent;\n }\n\n protected get hasLabel(): boolean {\n return this.slotHasContent;\n }\n\n /**\n * Returns the set of attributes that should be forwarded to the internal\n * semantic `<button>` element, if not otherwise directly managed.\n *\n * @internal\n */\n protected getForwardedButtonAttributes(): Record<\n string,\n string | boolean | undefined\n > {\n return {\n disabled: this.disabled,\n };\n }\n\n public override connectedCallback(): void {\n super.connectedCallback();\n // Capture phase so slotted light-DOM clicks are suppressed before host\n // listeners (e.g. Storybook actions) run.\n this.addEventListener('click', this.handleClick, true);\n }\n\n public override disconnectedCallback(): void {\n this.removeEventListener('click', this.handleClick, true);\n super.disconnectedCallback();\n }\n\n /**\n * Suppresses click activation while the button is `disabled`.\n *\n * Slotted icon content lives in the light DOM, so pointer clicks on icons\n * bypass the disabled inner `<button>` and bubble on the host. The host\n * listener (capture) and inner `@click` binding both call this handler.\n */\n protected readonly handleClick = (event: Event): void => {\n if (this.disabled) {\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n };\n\n protected override update(changedProperties: PropertyValues): void {\n super.update(changedProperties);\n if (window.__swc?.DEBUG) {\n if (this.hasIcon && !this.hasLabel && !this.accessibleLabel) {\n window.__swc.warn(\n this,\n `<${this.localName}> with an icon and no label must have an \"accessible-label\" attribute to be accessible.`,\n 'https://opensource.adobe.com/spectrum-web-components/components/button/#icon-only',\n { issues: ['accessible-label'] }\n );\n }\n }\n }\n}\n"],"mappings":";;;;;;OAuCsB,IAAtB,cAAyC,EACvC,EAAgB,EAAoB,GAAiB,kBAAgB,EAAE,GAAG,EAC1E,EAAE,YAAY,GAAoB,CACnC,CAAC;;+
|
|
1
|
+
{"version":3,"file":"Button.base.js","names":[],"sources":["../../../components/button/Button.base.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\n\nimport { SpectrumElement } from '@adobe/spectrum-wc-core/element/index.js';\nimport {\n ObserveSlotPresence,\n ObserveSlotText,\n SizedMixin,\n} from '@adobe/spectrum-wc-core/mixins/index.js';\n\nimport { BUTTON_VALID_SIZES, type ButtonSize } from './Button.types.js';\n\n/**\n * Abstract base class for all button-like components. Owns shared semantic\n * concerns: interaction state, sizing, slot-derived icon/label state,\n * accessible-name resolution, and host-to-internal-button attribute forwarding.\n *\n * Visual API specific to `sp-button` (`variant`, `fill-style`, `static-color`)\n * is intentionally absent so that ActionButton, ClearButton, CloseButton,\n * PickerButton, and InfieldButton can extend this base without inheriting\n * the `swc-button` visual surface.\n *\n * @attribute {ElementSize} size - The size of the button.\n *\n * @todo We currently have 3 levels of mixins on this class, but the mixin\n * composition guide recommends a maximum of 2. Explore reducing after milestone 2.\n */\nexport abstract class ButtonBase extends SizedMixin(\n ObserveSlotText(ObserveSlotPresence(SpectrumElement, '[slot=\"icon\"]'), ''),\n { validSizes: BUTTON_VALID_SIZES }\n) {\n static override shadowRootOptions: ShadowRootInit = {\n ...SpectrumElement.shadowRootOptions,\n delegatesFocus: true,\n };\n\n // ──────────────────\n // SHARED API\n // ──────────────────\n\n /**\n * The size of the button.\n *\n * @default m\n */\n declare public size: ButtonSize;\n\n /**\n * Whether the button is disabled. Removes focusability and prevents\n * interaction.\n */\n @property({ type: Boolean, reflect: true })\n public disabled: boolean = false;\n\n /**\n * Accessible label forwarded to the internal `<button>` element as\n * `aria-label`. Required for icon-only buttons, which have no visible text.\n */\n @property({ type: String, attribute: 'accessible-label' })\n public accessibleLabel?: string;\n\n // ──────────────────────\n // IMPLEMENTATION\n // ──────────────────────\n\n protected get hasIcon(): boolean {\n return this.slotContentIsPresent;\n }\n\n protected get hasLabel(): boolean {\n return this.slotHasContent;\n }\n\n /**\n * Returns the set of attributes that should be forwarded to the internal\n * semantic `<button>` element, if not otherwise directly managed.\n *\n * @internal\n */\n protected getForwardedButtonAttributes(): Record<\n string,\n string | boolean | undefined\n > {\n return {\n disabled: this.disabled,\n };\n }\n\n public override connectedCallback(): void {\n super.connectedCallback();\n // Capture phase so slotted light-DOM clicks are suppressed before host\n // listeners (e.g. Storybook actions) run.\n this.addEventListener('click', this.handleClick, true);\n }\n\n public override disconnectedCallback(): void {\n this.removeEventListener('click', this.handleClick, true);\n super.disconnectedCallback();\n }\n\n /**\n * Suppresses click activation while the button is `disabled`.\n *\n * Slotted icon content lives in the light DOM, so pointer clicks on icons\n * bypass the disabled inner `<button>` and bubble on the host. The host\n * listener (capture) and inner `@click` binding both call this handler.\n */\n protected readonly handleClick = (event: Event): void => {\n if (this.disabled) {\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n };\n\n protected override update(changedProperties: PropertyValues): void {\n super.update(changedProperties);\n if (window.__swc?.DEBUG) {\n if (this.hasIcon && !this.hasLabel && !this.accessibleLabel) {\n window.__swc.warn(\n this,\n `<${this.localName}> with an icon and no label must have an \"accessible-label\" attribute to be accessible.`,\n 'https://opensource.adobe.com/spectrum-web-components/components/button/#icon-only',\n { issues: ['accessible-label'] }\n );\n }\n }\n }\n}\n"],"mappings":";;;;;;OAuCsB,IAAtB,cAAyC,EACvC,EAAgB,EAAoB,GAAiB,kBAAgB,EAAE,GAAG,EAC1E,EAAE,YAAY,GAAoB,CACnC,CAAC;;+BAsB2B,wBAuDO,MAAuB;AACvD,GAAI,KAAK,aACP,EAAM,gBAAgB,EACtB,EAAM,0BAA0B;;;CA7CpC,IAAc,UAAmB;AAC/B,SAAO,KAAK;;CAGd,IAAc,WAAoB;AAChC,SAAO,KAAK;;CASd,+BAGE;AACA,SAAO,EACL,UAAU,KAAK,UAChB;;CAGH,oBAA0C;AAIxC,EAHA,MAAM,mBAAmB,EAGzB,KAAK,iBAAiB,SAAS,KAAK,aAAa,GAAK;;CAGxD,uBAA6C;AAE3C,EADA,KAAK,oBAAoB,SAAS,KAAK,aAAa,GAAK,EACzD,MAAM,sBAAsB;;CAiB9B,OAA0B,GAAyC;;AAEjE,EADA,MAAM,OAAO,EAAkB,GAC/B,IAAI,OAAO,UAAA,QAAA,EAAO,SACZ,KAAK,WAAW,CAAC,KAAK,YAAY,CAAC,KAAK,mBAC1C,OAAO,MAAM,KACX,MACA,IAAI,KAAK,UAAU,0FACnB,qFACA,EAAE,QAAQ,CAAC,mBAAmB,EAAE,CACjC;;;6BA5F6C;CAClD,GAAG,EAAgB;CACnB,gBAAgB;CACjB,KAiBA,EAAS;CAAE,MAAM;CAAS,SAAS;CAAM,CAAC,CAAA,EAAA,EAAA,WAAA,YAAA,KAAA,EAAA,KAO1C,EAAS;CAAE,MAAM;CAAQ,WAAW;CAAoB,CAAC,CAAA,EAAA,EAAA,WAAA,mBAAA,KAAA,EAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { __decorate as e } from "../../_virtual/_@oxc-project_runtime@0.124.0/helpers/decorate.js";
|
|
2
|
-
import { SlotAttributePropagationController as t } from "../../controllers/slot-attribute-propagation/src/slot-attribute-propagation-controller.js";
|
|
2
|
+
import { SlotAttributePropagationController as t } from "../../controllers/slot-attribute-propagation-controller/src/slot-attribute-propagation-controller.js";
|
|
3
3
|
import { BUTTON_GROUP_ALIGNMENTS as n, BUTTON_GROUP_ORIENTATIONS as r, BUTTON_GROUP_SIZES as i } from "./ButtonGroup.types.js";
|
|
4
4
|
import { property as a, queryAssignedElements as o } from "lit/decorators.js";
|
|
5
5
|
import { SpectrumElement as s } from "@adobe/spectrum-wc-core/element/index.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ButtonGroup.base.js","names":[],"sources":["../../../components/button-group/ButtonGroup.base.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { PropertyValues } from 'lit';\nimport { property, queryAssignedElements } from 'lit/decorators.js';\n\nimport type { ButtonBase } from '@adobe/spectrum-wc-core/components/button';\nimport { SpectrumElement } from '@adobe/spectrum-wc-core/element/index.js';\nimport { SizedMixin } from '@adobe/spectrum-wc-core/mixins/index.js';\n\nimport { SlotAttributePropagationController } from '../../controllers/slot-attribute-propagation/index.js';\nimport {\n BUTTON_GROUP_ALIGNMENTS,\n BUTTON_GROUP_ORIENTATIONS,\n BUTTON_GROUP_SIZES,\n type ButtonGroupAlignment,\n type ButtonGroupOrientation,\n type ButtonGroupSize,\n} from './ButtonGroup.types.js';\n\n/**\n * A button group clusters related actions together, providing consistent\n * spacing, sizing, and orientation. The host exposes `role=\"group\"` and\n * propagates `size` and `disabled` to slotted button children.\n *\n * This base class owns shared logic and accessibility semantics. Rendering\n * and styling live in the concrete SWC subclass.\n *\n * @slot - One or more `swc-button` elements.\n *\n * @attribute {ElementSize} size - The size of the button group and its children.\n */\nexport abstract class ButtonGroupBase extends SizedMixin(SpectrumElement, {\n validSizes: BUTTON_GROUP_SIZES,\n}) {\n /**\n * The size of the button group. Propagated to all slotted button children.\n *\n * @default m\n */\n declare public size: ButtonGroupSize;\n\n // ──────────────────\n // SHARED API\n // ──────────────────\n\n /**\n * @internal\n *\n * Valid orientation values for validation.\n */\n static readonly ORIENTATIONS: readonly string[] = BUTTON_GROUP_ORIENTATIONS;\n\n /**\n * @internal\n *\n * Valid alignment values for validation.\n */\n static readonly ALIGNMENTS: readonly string[] = BUTTON_GROUP_ALIGNMENTS;\n\n /**\n * The layout direction of the button group.\n *\n * Note: this property does NOT set `aria-orientation` on the host because\n * `aria-orientation` is only meaningful for roles that manage arrow-key\n * navigation (e.g. `toolbar`, `listbox`). Since button-group uses\n * sequential Tab navigation (not roving tabindex), the attribute would\n * be invalid.\n */\n @property({ type: String, reflect: true })\n public orientation: ButtonGroupOrientation = 'horizontal';\n\n /**\n * Whether all buttons in the group are disabled. When `true`, forces\n * every slotted button child to `disabled`. When `false`, children\n * retain their individual disabled state — a button slotted with\n * `disabled` remains disabled even when the group is enabled.\n */\n @property({ type: Boolean, reflect: true })\n public disabled = false;\n\n /**\n * The alignment of buttons within the group along the main axis.\n */\n @property({ type: String, reflect: true })\n public align: ButtonGroupAlignment = 'start';\n\n // ──────────────────────\n // IMPLEMENTATION\n // ──────────────────────\n\n @queryAssignedElements({ flatten: true })\n private buttons!: ButtonBase[];\n\n /**\n * Tracks buttons that were individually disabled before the group\n * forced all children disabled. Used to restore their state when\n * the group becomes enabled again.\n */\n private individuallyDisabled = new WeakSet<ButtonBase>();\n\n private readonly _sizePropagation = new SlotAttributePropagationController(\n this,\n {\n attribute: 'size',\n getValue: () => this.size,\n }\n );\n\n protected override firstUpdated(changed: PropertyValues<this>): void {\n super.firstUpdated(changed);\n this.setAttribute('role', 'group');\n }\n\n protected override update(changedProperties: PropertyValues): void {\n if (window.__swc?.DEBUG) {\n const constructor = this.constructor as typeof ButtonGroupBase;\n if (!constructor.ORIENTATIONS.includes(this.orientation)) {\n window.__swc.warn(\n this,\n `<${this.localName}> element expects the \"orientation\" attribute to be one of the following:`,\n 'https://opensource.adobe.com/spectrum-web-components/components/button-group/',\n { issues: [...constructor.ORIENTATIONS] }\n );\n }\n if (!constructor.ALIGNMENTS.includes(this.align)) {\n window.__swc.warn(\n this,\n `<${this.localName}> element expects the \"align\" attribute to be one of the following:`,\n 'https://opensource.adobe.com/spectrum-web-components/components/button-group/',\n { issues: [...constructor.ALIGNMENTS] }\n );\n }\n }\n super.update(changedProperties);\n }\n\n protected override updated(changed: PropertyValues<this>): void {\n super.updated(changed);\n\n if (changed.has('disabled')) {\n const wasDisabled = changed.get('disabled');\n if (this.disabled || wasDisabled) {\n this.propagateDisabledToChildren();\n }\n }\n }\n\n /**\n * Handles slotchange events from the default slot. Ensures newly slotted\n * children receive the current size and disabled state.\n */\n protected handleSlotchange(): void {\n this._sizePropagation.propagate();\n\n if (this.disabled) {\n this.propagateDisabledToChildren();\n }\n }\n\n /**\n * Propagates disabled state to slotted button children.\n *\n * Uses a one-directional model: when the group is disabled, all children\n * are forced disabled (tracking which were already individually disabled).\n * When the group is enabled, children are restored to their individual\n * disabled state — a button slotted with `disabled` remains disabled.\n *\n * On slotchange when the group is enabled, buttons are not touched so\n * their declarative `disabled` attribute is preserved.\n */\n private propagateDisabledToChildren(): void {\n for (const button of this.buttons) {\n if (this.disabled) {\n if (button.disabled) {\n this.individuallyDisabled.add(button);\n }\n button.disabled = true;\n } else {\n button.disabled = this.individuallyDisabled.has(button);\n }\n }\n\n if (!this.disabled) {\n this.individuallyDisabled = new WeakSet();\n }\n }\n}\n"],"mappings":";;;;;;;OAyCsB,IAAtB,cAA8C,EAAW,GAAiB,EACxE,YAAY,GACb,CAAC,CAAC;;kCAoC4C,8BAS3B,iBAMmB,qDAcN,IAAI,SAAqB,0BAEpB,IAAI,EACtC,MACA;GACE,WAAW;GACX,gBAAgB,KAAK;GACtB,CACF;;CAED,aAAgC,GAAqC;AAEnE,EADA,MAAM,aAAa,EAAQ,EAC3B,KAAK,aAAa,QAAQ,QAAQ;;CAGpC,OAA0B,GAAyC;;AACjE,OAAA,IAAI,OAAO,UAAA,QAAA,EAAO,OAAO;GACvB,IAAM,IAAc,KAAK;AASzB,GARK,EAAY,aAAa,SAAS,KAAK,YAAY,IACtD,OAAO,MAAM,KACX,MACA,IAAI,KAAK,UAAU,4EACnB,iFACA,EAAE,QAAQ,CAAC,GAAG,EAAY,aAAa,EAAE,CAC1C,EAEE,EAAY,WAAW,SAAS,KAAK,MAAM,IAC9C,OAAO,MAAM,KACX,MACA,IAAI,KAAK,UAAU,sEACnB,iFACA,EAAE,QAAQ,CAAC,GAAG,EAAY,WAAW,EAAE,CACxC;;AAGL,QAAM,OAAO,EAAkB;;CAGjC,QAA2B,GAAqC;AAG9D,MAFA,MAAM,QAAQ,EAAQ,EAElB,EAAQ,IAAI,WAAW,EAAE;GAC3B,IAAM,IAAc,EAAQ,IAAI,WAAW;AAC3C,IAAI,KAAK,YAAY,MACnB,KAAK,6BAA6B;;;CASxC,mBAAmC;AAGjC,EAFA,KAAK,iBAAiB,WAAW,EAE7B,KAAK,YACP,KAAK,6BAA6B;;CAetC,8BAA4C;AAC1C,OAAK,IAAM,KAAU,KAAK,QACxB,CAAI,KAAK,YACH,EAAO,YACT,KAAK,qBAAqB,IAAI,EAAO,EAEvC,EAAO,WAAW,MAElB,EAAO,WAAW,KAAK,qBAAqB,IAAI,EAAO;AAI3D,EAAK,KAAK,aACR,KAAK,uCAAuB,IAAI,SAAS;;;wBArIK,kBAOF,MAW/C,EAAS;CAAE,MAAM;CAAQ,SAAS;CAAM,CAAC,CAAA,EAAA,EAAA,WAAA,eAAA,KAAA,EAAA,KASzC,EAAS;CAAE,MAAM;CAAS,SAAS;CAAM,CAAC,CAAA,EAAA,EAAA,WAAA,YAAA,KAAA,EAAA,KAM1C,EAAS;CAAE,MAAM;CAAQ,SAAS;CAAM,CAAC,CAAA,EAAA,EAAA,WAAA,SAAA,KAAA,EAAA,KAOzC,EAAsB,EAAE,SAAS,IAAM,CAAC,CAAA,EAAA,EAAA,WAAA,WAAA,KAAA,EAAA"}
|
|
1
|
+
{"version":3,"file":"ButtonGroup.base.js","names":[],"sources":["../../../components/button-group/ButtonGroup.base.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { PropertyValues } from 'lit';\nimport { property, queryAssignedElements } from 'lit/decorators.js';\n\nimport type { ButtonBase } from '@adobe/spectrum-wc-core/components/button';\nimport { SpectrumElement } from '@adobe/spectrum-wc-core/element/index.js';\nimport { SizedMixin } from '@adobe/spectrum-wc-core/mixins/index.js';\n\nimport { SlotAttributePropagationController } from '../../controllers/slot-attribute-propagation-controller/index.js';\nimport {\n BUTTON_GROUP_ALIGNMENTS,\n BUTTON_GROUP_ORIENTATIONS,\n BUTTON_GROUP_SIZES,\n type ButtonGroupAlignment,\n type ButtonGroupOrientation,\n type ButtonGroupSize,\n} from './ButtonGroup.types.js';\n\n/**\n * A button group clusters related actions together, providing consistent\n * spacing, sizing, and orientation. The host exposes `role=\"group\"` and\n * propagates `size` and `disabled` to slotted button children.\n *\n * This base class owns shared logic and accessibility semantics. Rendering\n * and styling live in the concrete SWC subclass.\n *\n * @slot - One or more `swc-button` elements.\n *\n * @attribute {ElementSize} size - The size of the button group and its children.\n */\nexport abstract class ButtonGroupBase extends SizedMixin(SpectrumElement, {\n validSizes: BUTTON_GROUP_SIZES,\n}) {\n /**\n * The size of the button group. Propagated to all slotted button children.\n *\n * @default m\n */\n declare public size: ButtonGroupSize;\n\n // ──────────────────\n // SHARED API\n // ──────────────────\n\n /**\n * @internal\n *\n * Valid orientation values for validation.\n */\n static readonly ORIENTATIONS: readonly string[] = BUTTON_GROUP_ORIENTATIONS;\n\n /**\n * @internal\n *\n * Valid alignment values for validation.\n */\n static readonly ALIGNMENTS: readonly string[] = BUTTON_GROUP_ALIGNMENTS;\n\n /**\n * The layout direction of the button group.\n *\n * Note: this property does NOT set `aria-orientation` on the host because\n * `aria-orientation` is only meaningful for roles that manage arrow-key\n * navigation (e.g. `toolbar`, `listbox`). Since button-group uses\n * sequential Tab navigation (not roving tabindex), the attribute would\n * be invalid.\n */\n @property({ type: String, reflect: true })\n public orientation: ButtonGroupOrientation = 'horizontal';\n\n /**\n * Whether all buttons in the group are disabled. When `true`, forces\n * every slotted button child to `disabled`. When `false`, children\n * retain their individual disabled state — a button slotted with\n * `disabled` remains disabled even when the group is enabled.\n */\n @property({ type: Boolean, reflect: true })\n public disabled = false;\n\n /**\n * The alignment of buttons within the group along the main axis.\n */\n @property({ type: String, reflect: true })\n public align: ButtonGroupAlignment = 'start';\n\n // ──────────────────────\n // IMPLEMENTATION\n // ──────────────────────\n\n @queryAssignedElements({ flatten: true })\n private buttons!: ButtonBase[];\n\n /**\n * Tracks buttons that were individually disabled before the group\n * forced all children disabled. Used to restore their state when\n * the group becomes enabled again.\n */\n private individuallyDisabled = new WeakSet<ButtonBase>();\n\n private readonly _sizePropagation = new SlotAttributePropagationController(\n this,\n {\n attribute: 'size',\n getValue: () => this.size,\n }\n );\n\n protected override firstUpdated(changed: PropertyValues<this>): void {\n super.firstUpdated(changed);\n this.setAttribute('role', 'group');\n }\n\n protected override update(changedProperties: PropertyValues): void {\n if (window.__swc?.DEBUG) {\n const constructor = this.constructor as typeof ButtonGroupBase;\n if (!constructor.ORIENTATIONS.includes(this.orientation)) {\n window.__swc.warn(\n this,\n `<${this.localName}> element expects the \"orientation\" attribute to be one of the following:`,\n 'https://opensource.adobe.com/spectrum-web-components/components/button-group/',\n { issues: [...constructor.ORIENTATIONS] }\n );\n }\n if (!constructor.ALIGNMENTS.includes(this.align)) {\n window.__swc.warn(\n this,\n `<${this.localName}> element expects the \"align\" attribute to be one of the following:`,\n 'https://opensource.adobe.com/spectrum-web-components/components/button-group/',\n { issues: [...constructor.ALIGNMENTS] }\n );\n }\n }\n super.update(changedProperties);\n }\n\n protected override updated(changed: PropertyValues<this>): void {\n super.updated(changed);\n\n if (changed.has('disabled')) {\n const wasDisabled = changed.get('disabled');\n if (this.disabled || wasDisabled) {\n this.propagateDisabledToChildren();\n }\n }\n }\n\n /**\n * Handles slotchange events from the default slot. Ensures newly slotted\n * children receive the current size and disabled state.\n */\n protected handleSlotchange(): void {\n this._sizePropagation.propagate();\n\n if (this.disabled) {\n this.propagateDisabledToChildren();\n }\n }\n\n /**\n * Propagates disabled state to slotted button children.\n *\n * Uses a one-directional model: when the group is disabled, all children\n * are forced disabled (tracking which were already individually disabled).\n * When the group is enabled, children are restored to their individual\n * disabled state — a button slotted with `disabled` remains disabled.\n *\n * On slotchange when the group is enabled, buttons are not touched so\n * their declarative `disabled` attribute is preserved.\n */\n private propagateDisabledToChildren(): void {\n for (const button of this.buttons) {\n if (this.disabled) {\n if (button.disabled) {\n this.individuallyDisabled.add(button);\n }\n button.disabled = true;\n } else {\n button.disabled = this.individuallyDisabled.has(button);\n }\n }\n\n if (!this.disabled) {\n this.individuallyDisabled = new WeakSet();\n }\n }\n}\n"],"mappings":";;;;;;;OAyCsB,IAAtB,cAA8C,EAAW,GAAiB,EACxE,YAAY,GACb,CAAC,CAAC;;kCAoC4C,8BAS3B,iBAMmB,qDAcN,IAAI,SAAqB,0BAEpB,IAAI,EACtC,MACA;GACE,WAAW;GACX,gBAAgB,KAAK;GACtB,CACF;;CAED,aAAgC,GAAqC;AAEnE,EADA,MAAM,aAAa,EAAQ,EAC3B,KAAK,aAAa,QAAQ,QAAQ;;CAGpC,OAA0B,GAAyC;;AACjE,OAAA,IAAI,OAAO,UAAA,QAAA,EAAO,OAAO;GACvB,IAAM,IAAc,KAAK;AASzB,GARK,EAAY,aAAa,SAAS,KAAK,YAAY,IACtD,OAAO,MAAM,KACX,MACA,IAAI,KAAK,UAAU,4EACnB,iFACA,EAAE,QAAQ,CAAC,GAAG,EAAY,aAAa,EAAE,CAC1C,EAEE,EAAY,WAAW,SAAS,KAAK,MAAM,IAC9C,OAAO,MAAM,KACX,MACA,IAAI,KAAK,UAAU,sEACnB,iFACA,EAAE,QAAQ,CAAC,GAAG,EAAY,WAAW,EAAE,CACxC;;AAGL,QAAM,OAAO,EAAkB;;CAGjC,QAA2B,GAAqC;AAG9D,MAFA,MAAM,QAAQ,EAAQ,EAElB,EAAQ,IAAI,WAAW,EAAE;GAC3B,IAAM,IAAc,EAAQ,IAAI,WAAW;AAC3C,IAAI,KAAK,YAAY,MACnB,KAAK,6BAA6B;;;CASxC,mBAAmC;AAGjC,EAFA,KAAK,iBAAiB,WAAW,EAE7B,KAAK,YACP,KAAK,6BAA6B;;CAetC,8BAA4C;AAC1C,OAAK,IAAM,KAAU,KAAK,QACxB,CAAI,KAAK,YACH,EAAO,YACT,KAAK,qBAAqB,IAAI,EAAO,EAEvC,EAAO,WAAW,MAElB,EAAO,WAAW,KAAK,qBAAqB,IAAI,EAAO;AAI3D,EAAK,KAAK,aACR,KAAK,uCAAuB,IAAI,SAAS;;;wBArIK,kBAOF,MAW/C,EAAS;CAAE,MAAM;CAAQ,SAAS;CAAM,CAAC,CAAA,EAAA,EAAA,WAAA,eAAA,KAAA,EAAA,KASzC,EAAS;CAAE,MAAM;CAAS,SAAS;CAAM,CAAC,CAAA,EAAA,EAAA,WAAA,YAAA,KAAA,EAAA,KAM1C,EAAS;CAAE,MAAM;CAAQ,SAAS;CAAM,CAAC,CAAA,EAAA,EAAA,WAAA,SAAA,KAAA,EAAA,KAOzC,EAAsB,EAAE,SAAS,IAAM,CAAC,CAAA,EAAA,EAAA,WAAA,WAAA,KAAA,EAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { __decorate as e } from "../../_virtual/_@oxc-project_runtime@0.124.0/helpers/decorate.js";
|
|
2
|
-
import { SlotAttributePropagationController as t } from "../../controllers/slot-attribute-propagation/src/slot-attribute-propagation-controller.js";
|
|
2
|
+
import { SlotAttributePropagationController as t } from "../../controllers/slot-attribute-propagation-controller/src/slot-attribute-propagation-controller.js";
|
|
3
3
|
import { ILLUSTRATED_MESSAGE_VALID_ORIENTATIONS as n, ILLUSTRATED_MESSAGE_VALID_SIZES as r } from "./IllustratedMessage.types.js";
|
|
4
4
|
import { property as i } from "lit/decorators.js";
|
|
5
5
|
import { SpectrumElement as a } from "@adobe/spectrum-wc-core/element/index.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IllustratedMessage.base.js","names":[],"sources":["../../../components/illustrated-message/IllustratedMessage.base.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\n\nimport { SpectrumElement } from '@adobe/spectrum-wc-core/element/index.js';\n\nimport { SlotAttributePropagationController } from '../../controllers/slot-attribute-propagation/index.js';\nimport {\n ILLUSTRATED_MESSAGE_VALID_ORIENTATIONS,\n ILLUSTRATED_MESSAGE_VALID_SIZES,\n type IllustratedMessageOrientation,\n type IllustratedMessageSize,\n} from './IllustratedMessage.types.js';\n\n/**\n * An illustrated message displays an illustration and a message, typically\n * used in empty states or error pages.\n *\n * @slot - Decorative or informative SVG illustration\n * @slot heading - The heading element, h2–h6\n * @todo SWC-1943 Add slot constraints once the CEM slot constraints work is complete:\n * `{required} {allowedChildren: h2, h3, h4, h5, h6} {maxChildren: 1}`\n * @slot description - Supporting description text\n * @slot actions - Optional action controls displayed below the description, typically a button or button group. Receives `size` automatically from the illustrated message.\n */\nexport abstract class IllustratedMessageBase extends SpectrumElement {\n // ─────────────────────────\n // API TO OVERRIDE\n // ─────────────────────────\n\n /**\n * @internal\n */\n static readonly VALID_SIZES: readonly IllustratedMessageSize[] =\n ILLUSTRATED_MESSAGE_VALID_SIZES;\n\n /**\n * @internal\n */\n static readonly VALID_ORIENTATIONS: readonly IllustratedMessageOrientation[] =\n ILLUSTRATED_MESSAGE_VALID_ORIENTATIONS;\n\n // ──────────────────\n // SHARED API\n // ──────────────────\n\n /**\n * The size of the message\n *\n * @default m\n */\n @property({ type: String, reflect: true })\n public size: IllustratedMessageSize = 'm';\n\n /**\n * The layout orientation\n */\n @property({ type: String, reflect: true })\n public orientation: IllustratedMessageOrientation = 'vertical';\n\n // ──────────────────────\n // IMPLEMENTATION\n // ──────────────────────\n\n private readonly _sizePropagation = new SlotAttributePropagationController(\n this,\n {\n attribute: 'size',\n getValue: () => this.size,\n slotName: 'actions',\n }\n );\n\n protected override updated(changedProperties: PropertyValues): void {\n super.updated(changedProperties);\n\n if (window.__swc?.DEBUG) {\n if (\n changedProperties.has('size') &&\n !ILLUSTRATED_MESSAGE_VALID_SIZES.includes(this.size)\n ) {\n window.__swc.warn(\n this,\n `<${this.localName}> received an invalid \"size\" value of \"${this.size}\". Valid values are ${ILLUSTRATED_MESSAGE_VALID_SIZES.join(', ')}.`,\n 'https://opensource.adobe.com/spectrum-web-components/components/illustrated-message/',\n { issues: [`size=\"${this.size}\"`] }\n );\n }\n\n if (\n changedProperties.has('orientation') &&\n !ILLUSTRATED_MESSAGE_VALID_ORIENTATIONS.includes(this.orientation)\n ) {\n window.__swc.warn(\n this,\n `<${this.localName}> received an invalid \"orientation\" value of \"${this.orientation}\". Valid values are ${ILLUSTRATED_MESSAGE_VALID_ORIENTATIONS.join(', ')}.`,\n 'https://opensource.adobe.com/spectrum-web-components/components/illustrated-message/',\n { issues: [`orientation=\"${this.orientation}\"`] }\n );\n }\n }\n }\n\n protected handleActionsSlotChange(): void {\n this._sizePropagation.propagate();\n }\n\n /**\n * @internal\n *\n * Validates that the heading slot only contains `<h2>`–`<h6>` elements.\n * Rendering subclasses must wire this to the heading slot's `slotchange`\n * event (e.g. `<slot name=\"heading\" @slotchange=${this.handleHeadingSlotChange}>`)\n * for the validation warning to fire.\n */\n protected handleHeadingSlotChange(event: Event): void {\n if (window.__swc?.DEBUG) {\n const headingSlot = event.target as HTMLSlotElement;\n for (const el of headingSlot.assignedElements()) {\n if (!['H2', 'H3', 'H4', 'H5', 'H6'].includes(el.tagName)) {\n window.__swc.warn(\n this,\n `<${this.localName}> heading slot received a <${el.tagName.toLowerCase()}> element. Only <h2>–<h6> elements are allowed in the heading slot.`,\n 'https://opensource.adobe.com/spectrum-web-components/components/illustrated-message/',\n { issues: [`heading slot: <${el.tagName.toLowerCase()}>`] }\n );\n }\n }\n }\n }\n}\n"],"mappings":";;;;;;OAoCsB,IAAtB,cAAqD,EAAgB;;2BA2B7B,wBAMc,oCAMhB,IAAI,EACtC,MACA;GACE,WAAW;GACX,gBAAgB,KAAK;GACrB,UAAU;GACX,CACF;;CAED,QAA2B,GAAyC;;AAGlE,EAFA,MAAM,QAAQ,EAAkB,GAEhC,IAAI,OAAO,UAAA,QAAA,EAAO,UAEd,EAAkB,IAAI,OAAO,IAC7B,CAAC,EAAgC,SAAS,KAAK,KAAK,IAEpD,OAAO,MAAM,KACX,MACA,IAAI,KAAK,UAAU,yCAAyC,KAAK,KAAK,sBAAsB,EAAgC,KAAK,KAAK,CAAC,IACvI,wFACA,EAAE,QAAQ,CAAC,SAAS,KAAK,KAAK,GAAG,EAAE,CACpC,EAID,EAAkB,IAAI,cAAc,IACpC,CAAC,EAAuC,SAAS,KAAK,YAAY,IAElE,OAAO,MAAM,KACX,MACA,IAAI,KAAK,UAAU,gDAAgD,KAAK,YAAY,sBAAsB,EAAuC,KAAK,KAAK,CAAC,IAC5J,wFACA,EAAE,QAAQ,CAAC,gBAAgB,KAAK,YAAY,GAAG,EAAE,CAClD;;CAKP,0BAA0C;AACxC,OAAK,iBAAiB,WAAW;;CAWnC,wBAAkC,GAAoB;;AACpD,OAAA,IAAI,OAAO,UAAA,QAAA,EAAO,OAAO;GACvB,IAAM,IAAc,EAAM;AAC1B,QAAK,IAAM,KAAM,EAAY,kBAAkB,CAC7C,CAAK;IAAC;IAAM;IAAM;IAAM;IAAM;IAAK,CAAC,SAAS,EAAG,QAAQ,IACtD,OAAO,MAAM,KACX,MACA,IAAI,KAAK,UAAU,6BAA6B,EAAG,QAAQ,aAAa,CAAC,sEACzE,wFACA,EAAE,QAAQ,CAAC,kBAAkB,EAAG,QAAQ,aAAa,CAAC,GAAG,EAAE,CAC5D;;;;uBA3FP,0BAMA,MAWD,EAAS;CAAE,MAAM;CAAQ,SAAS;CAAM,CAAC,CAAA,EAAA,EAAA,WAAA,QAAA,KAAA,EAAA,KAMzC,EAAS;CAAE,MAAM;CAAQ,SAAS;CAAM,CAAC,CAAA,EAAA,EAAA,WAAA,eAAA,KAAA,EAAA"}
|
|
1
|
+
{"version":3,"file":"IllustratedMessage.base.js","names":[],"sources":["../../../components/illustrated-message/IllustratedMessage.base.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\n\nimport { SpectrumElement } from '@adobe/spectrum-wc-core/element/index.js';\n\nimport { SlotAttributePropagationController } from '../../controllers/slot-attribute-propagation-controller/index.js';\nimport {\n ILLUSTRATED_MESSAGE_VALID_ORIENTATIONS,\n ILLUSTRATED_MESSAGE_VALID_SIZES,\n type IllustratedMessageOrientation,\n type IllustratedMessageSize,\n} from './IllustratedMessage.types.js';\n\n/**\n * An illustrated message displays an illustration and a message, typically\n * used in empty states or error pages.\n *\n * @slot - Decorative or informative SVG illustration\n * @slot heading - The heading element, h2–h6\n * @todo SWC-1943 Add slot constraints once the CEM slot constraints work is complete:\n * `{required} {allowedChildren: h2, h3, h4, h5, h6} {maxChildren: 1}`\n * @slot description - Supporting description text\n * @slot actions - Optional action controls displayed below the description, typically a button or button group. Receives `size` automatically from the illustrated message.\n */\nexport abstract class IllustratedMessageBase extends SpectrumElement {\n // ─────────────────────────\n // API TO OVERRIDE\n // ─────────────────────────\n\n /**\n * @internal\n */\n static readonly VALID_SIZES: readonly IllustratedMessageSize[] =\n ILLUSTRATED_MESSAGE_VALID_SIZES;\n\n /**\n * @internal\n */\n static readonly VALID_ORIENTATIONS: readonly IllustratedMessageOrientation[] =\n ILLUSTRATED_MESSAGE_VALID_ORIENTATIONS;\n\n // ──────────────────\n // SHARED API\n // ──────────────────\n\n /**\n * The size of the message\n *\n * @default m\n */\n @property({ type: String, reflect: true })\n public size: IllustratedMessageSize = 'm';\n\n /**\n * The layout orientation\n */\n @property({ type: String, reflect: true })\n public orientation: IllustratedMessageOrientation = 'vertical';\n\n // ──────────────────────\n // IMPLEMENTATION\n // ──────────────────────\n\n private readonly _sizePropagation = new SlotAttributePropagationController(\n this,\n {\n attribute: 'size',\n getValue: () => this.size,\n slotName: 'actions',\n }\n );\n\n protected override updated(changedProperties: PropertyValues): void {\n super.updated(changedProperties);\n\n if (window.__swc?.DEBUG) {\n if (\n changedProperties.has('size') &&\n !ILLUSTRATED_MESSAGE_VALID_SIZES.includes(this.size)\n ) {\n window.__swc.warn(\n this,\n `<${this.localName}> received an invalid \"size\" value of \"${this.size}\". Valid values are ${ILLUSTRATED_MESSAGE_VALID_SIZES.join(', ')}.`,\n 'https://opensource.adobe.com/spectrum-web-components/components/illustrated-message/',\n { issues: [`size=\"${this.size}\"`] }\n );\n }\n\n if (\n changedProperties.has('orientation') &&\n !ILLUSTRATED_MESSAGE_VALID_ORIENTATIONS.includes(this.orientation)\n ) {\n window.__swc.warn(\n this,\n `<${this.localName}> received an invalid \"orientation\" value of \"${this.orientation}\". Valid values are ${ILLUSTRATED_MESSAGE_VALID_ORIENTATIONS.join(', ')}.`,\n 'https://opensource.adobe.com/spectrum-web-components/components/illustrated-message/',\n { issues: [`orientation=\"${this.orientation}\"`] }\n );\n }\n }\n }\n\n protected handleActionsSlotChange(): void {\n this._sizePropagation.propagate();\n }\n\n /**\n * @internal\n *\n * Validates that the heading slot only contains `<h2>`–`<h6>` elements.\n * Rendering subclasses must wire this to the heading slot's `slotchange`\n * event (e.g. `<slot name=\"heading\" @slotchange=${this.handleHeadingSlotChange}>`)\n * for the validation warning to fire.\n */\n protected handleHeadingSlotChange(event: Event): void {\n if (window.__swc?.DEBUG) {\n const headingSlot = event.target as HTMLSlotElement;\n for (const el of headingSlot.assignedElements()) {\n if (!['H2', 'H3', 'H4', 'H5', 'H6'].includes(el.tagName)) {\n window.__swc.warn(\n this,\n `<${this.localName}> heading slot received a <${el.tagName.toLowerCase()}> element. Only <h2>–<h6> elements are allowed in the heading slot.`,\n 'https://opensource.adobe.com/spectrum-web-components/components/illustrated-message/',\n { issues: [`heading slot: <${el.tagName.toLowerCase()}>`] }\n );\n }\n }\n }\n }\n}\n"],"mappings":";;;;;;OAoCsB,IAAtB,cAAqD,EAAgB;;2BA2B7B,wBAMc,oCAMhB,IAAI,EACtC,MACA;GACE,WAAW;GACX,gBAAgB,KAAK;GACrB,UAAU;GACX,CACF;;CAED,QAA2B,GAAyC;;AAGlE,EAFA,MAAM,QAAQ,EAAkB,GAEhC,IAAI,OAAO,UAAA,QAAA,EAAO,UAEd,EAAkB,IAAI,OAAO,IAC7B,CAAC,EAAgC,SAAS,KAAK,KAAK,IAEpD,OAAO,MAAM,KACX,MACA,IAAI,KAAK,UAAU,yCAAyC,KAAK,KAAK,sBAAsB,EAAgC,KAAK,KAAK,CAAC,IACvI,wFACA,EAAE,QAAQ,CAAC,SAAS,KAAK,KAAK,GAAG,EAAE,CACpC,EAID,EAAkB,IAAI,cAAc,IACpC,CAAC,EAAuC,SAAS,KAAK,YAAY,IAElE,OAAO,MAAM,KACX,MACA,IAAI,KAAK,UAAU,gDAAgD,KAAK,YAAY,sBAAsB,EAAuC,KAAK,KAAK,CAAC,IAC5J,wFACA,EAAE,QAAQ,CAAC,gBAAgB,KAAK,YAAY,GAAG,EAAE,CAClD;;CAKP,0BAA0C;AACxC,OAAK,iBAAiB,WAAW;;CAWnC,wBAAkC,GAAoB;;AACpD,OAAA,IAAI,OAAO,UAAA,QAAA,EAAO,OAAO;GACvB,IAAM,IAAc,EAAM;AAC1B,QAAK,IAAM,KAAM,EAAY,kBAAkB,CAC7C,CAAK;IAAC;IAAM;IAAM;IAAM;IAAM;IAAK,CAAC,SAAS,EAAG,QAAQ,IACtD,OAAO,MAAM,KACX,MACA,IAAI,KAAK,UAAU,6BAA6B,EAAG,QAAQ,aAAa,CAAC,sEACzE,wFACA,EAAE,QAAQ,CAAC,kBAAkB,EAAG,QAAQ,aAAa,CAAC,GAAG,EAAE,CAC5D;;;;uBA3FP,0BAMA,MAWD,EAAS;CAAE,MAAM;CAAQ,SAAS;CAAM,CAAC,CAAA,EAAA,EAAA,WAAA,QAAA,KAAA,EAAA,KAMzC,EAAS;CAAE,MAAM;CAAQ,SAAS;CAAM,CAAC,CAAA,EAAA,EAAA,WAAA,eAAA,KAAA,EAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PropertyValues } from 'lit';
|
|
2
2
|
import { SpectrumElement } from '../../element/index.js';
|
|
3
|
-
import { StatusLightVariant } from './StatusLight.types.js';
|
|
3
|
+
import { StatusLightSize, StatusLightVariant } from './StatusLight.types.js';
|
|
4
4
|
declare const StatusLightBase_base: typeof SpectrumElement & {
|
|
5
5
|
new (...args: any[]): import('../../mixins/index.js').SizedElementInterface;
|
|
6
6
|
prototype: import('../../mixins/index.js').SizedElementInterface;
|
|
@@ -12,6 +12,10 @@ declare const StatusLightBase_base: typeof SpectrumElement & {
|
|
|
12
12
|
* @attribute {ElementSize} size - The size of the status light.
|
|
13
13
|
*/
|
|
14
14
|
export declare abstract class StatusLightBase extends StatusLightBase_base {
|
|
15
|
+
/**
|
|
16
|
+
* The size of the status light.
|
|
17
|
+
*/
|
|
18
|
+
size: StatusLightSize;
|
|
15
19
|
/**
|
|
16
20
|
* @internal
|
|
17
21
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusLight.base.js","names":[],"sources":["../../../components/status-light/StatusLight.base.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\nimport { PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\n\nimport { SpectrumElement } from '@adobe/spectrum-wc-core/element/index.js';\nimport { SizedMixin } from '@adobe/spectrum-wc-core/mixins/index.js';\n\nimport {\n STATUS_LIGHT_VALID_SIZES,\n type StatusLightVariant,\n} from './StatusLight.types.js';\n\n/**\n * A status light is a great way to convey semantic meaning and the condition of an entity, such as statuses and categories. It provides visual indicators through colored dots accompanied by descriptive text.\n *\n * @slot - The text label of the status light.\n * @attribute {ElementSize} size - The size of the status light.\n */\nexport abstract class StatusLightBase extends SizedMixin(SpectrumElement, {\n validSizes: STATUS_LIGHT_VALID_SIZES,\n noDefaultSize: true,\n}) {\n // ─────────────────────────\n // API TO OVERRIDE\n // ─────────────────────────\n\n /**\n * @internal\n *\n * A readonly array of the valid color variants for the status light.\n *\n * This is an actual internal property, intended not for customer use\n * but for use in internal validation logic, stories, tests, etc.\n *\n * Because S1 and S2 support different color variants, the value of this\n * property must be set in each subclass.\n */\n static readonly VARIANTS_COLOR: readonly string[];\n\n /**\n * @internal\n *\n * A readonly array of the valid semantic variants for the status light.\n *\n * This is an actual internal property, intended not for customer use\n * but for use in internal validation logic, stories, tests, etc.\n *\n * Because S1 and S2 support different semantic variants, the value of this\n * property must be set in each subclass.\n */\n static readonly VARIANTS_SEMANTIC: readonly string[];\n\n /**\n * @internal\n *\n * A readonly array of all valid variants for the status light.\n *\n * This is an actual internal property, intended not for customer use\n * but for use in internal validation logic, stories, tests, etc.\n *\n * Because S1 and S2 support different variants, the value of this\n * property must be set in each subclass.\n */\n static readonly VARIANTS: readonly string[];\n\n /**\n * @internal\n *\n * The variant of the status light.\n *\n * This is a public property, but its valid values vary between S1 and S2,\n * so the property (and its docs) need to be redefined in each subclass.\n *\n * The type declared here is a union of the valid values for S1 and S2,\n * and should be narrowed in each subclass.\n */\n @property({ type: String, reflect: true })\n public variant: StatusLightVariant = 'neutral';\n\n // ──────────────────────\n // IMPLEMENTATION\n // ──────────────────────\n\n protected override updated(changes: PropertyValues): void {\n super.updated(changes);\n if (window.__swc?.DEBUG) {\n const constructor = this.constructor as typeof StatusLightBase;\n // @ts-expect-error -- intentional runtime guard: 1st-gen consumers may pass 'accent'\n if (this.variant === 'accent') {\n window.__swc.warn(\n this,\n `<${this.localName}> does not support the \"accent\" variant in Spectrum 2. Use \"neutral\" or \"info\" depending on intent.`,\n 'https://spectrum-web-components.adobe.com/?path=/docs/status-light-migration-guide--docs',\n { level: 'deprecation' }\n );\n } else if (!constructor.VARIANTS.includes(this.variant)) {\n window.__swc.warn(\n this,\n `<${this.localName}> element expects the \"variant\" attribute to be one of the following:`,\n 'https://opensource.adobe.com/spectrum-web-components/components/status-light/#variants',\n {\n issues: [...constructor.VARIANTS],\n }\n );\n }\n if (this.hasAttribute('disabled')) {\n window.__swc.warn(\n this,\n `<${this.localName}> does not support the \"disabled\" attribute. It was deprecated in Spectrum 1 and has been removed in Spectrum 2.`,\n 'https://spectrum-web-components.adobe.com/?path=/docs/status-light-migration-guide--docs',\n { level: 'deprecation' }\n );\n }\n }\n }\n}\n"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"StatusLight.base.js","names":[],"sources":["../../../components/status-light/StatusLight.base.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\nimport { PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\n\nimport { SpectrumElement } from '@adobe/spectrum-wc-core/element/index.js';\nimport { SizedMixin } from '@adobe/spectrum-wc-core/mixins/index.js';\n\nimport {\n STATUS_LIGHT_VALID_SIZES,\n type StatusLightSize,\n type StatusLightVariant,\n} from './StatusLight.types.js';\n\n/**\n * A status light is a great way to convey semantic meaning and the condition of an entity, such as statuses and categories. It provides visual indicators through colored dots accompanied by descriptive text.\n *\n * @slot - The text label of the status light.\n * @attribute {ElementSize} size - The size of the status light.\n */\nexport abstract class StatusLightBase extends SizedMixin(SpectrumElement, {\n validSizes: STATUS_LIGHT_VALID_SIZES,\n noDefaultSize: true,\n}) {\n /**\n * The size of the status light.\n */\n declare public size: StatusLightSize;\n\n // ─────────────────────────\n // API TO OVERRIDE\n // ─────────────────────────\n\n /**\n * @internal\n *\n * A readonly array of the valid color variants for the status light.\n *\n * This is an actual internal property, intended not for customer use\n * but for use in internal validation logic, stories, tests, etc.\n *\n * Because S1 and S2 support different color variants, the value of this\n * property must be set in each subclass.\n */\n static readonly VARIANTS_COLOR: readonly string[];\n\n /**\n * @internal\n *\n * A readonly array of the valid semantic variants for the status light.\n *\n * This is an actual internal property, intended not for customer use\n * but for use in internal validation logic, stories, tests, etc.\n *\n * Because S1 and S2 support different semantic variants, the value of this\n * property must be set in each subclass.\n */\n static readonly VARIANTS_SEMANTIC: readonly string[];\n\n /**\n * @internal\n *\n * A readonly array of all valid variants for the status light.\n *\n * This is an actual internal property, intended not for customer use\n * but for use in internal validation logic, stories, tests, etc.\n *\n * Because S1 and S2 support different variants, the value of this\n * property must be set in each subclass.\n */\n static readonly VARIANTS: readonly string[];\n\n /**\n * @internal\n *\n * The variant of the status light.\n *\n * This is a public property, but its valid values vary between S1 and S2,\n * so the property (and its docs) need to be redefined in each subclass.\n *\n * The type declared here is a union of the valid values for S1 and S2,\n * and should be narrowed in each subclass.\n */\n @property({ type: String, reflect: true })\n public variant: StatusLightVariant = 'neutral';\n\n // ──────────────────────\n // IMPLEMENTATION\n // ──────────────────────\n\n protected override updated(changes: PropertyValues): void {\n super.updated(changes);\n if (window.__swc?.DEBUG) {\n const constructor = this.constructor as typeof StatusLightBase;\n // @ts-expect-error -- intentional runtime guard: 1st-gen consumers may pass 'accent'\n if (this.variant === 'accent') {\n window.__swc.warn(\n this,\n `<${this.localName}> does not support the \"accent\" variant in Spectrum 2. Use \"neutral\" or \"info\" depending on intent.`,\n 'https://spectrum-web-components.adobe.com/?path=/docs/status-light-migration-guide--docs',\n { level: 'deprecation' }\n );\n } else if (!constructor.VARIANTS.includes(this.variant)) {\n window.__swc.warn(\n this,\n `<${this.localName}> element expects the \"variant\" attribute to be one of the following:`,\n 'https://opensource.adobe.com/spectrum-web-components/components/status-light/#variants',\n {\n issues: [...constructor.VARIANTS],\n }\n );\n }\n if (this.hasAttribute('disabled')) {\n window.__swc.warn(\n this,\n `<${this.localName}> does not support the \"disabled\" attribute. It was deprecated in Spectrum 1 and has been removed in Spectrum 2.`,\n 'https://spectrum-web-components.adobe.com/?path=/docs/status-light-migration-guide--docs',\n { level: 'deprecation' }\n );\n }\n }\n }\n}\n"],"mappings":";;;;;;AA6BA,IAAsB,IAAtB,cAA8C,EAAW,GAAiB;CACxE,YAAY;CACZ,eAAe;CAChB,CAAC,CAAC;;8BA6DoC;;CAMrC,QAA2B,GAA+B;;AAExD,MADA,MAAM,QAAQ,EAAQ,GACtB,IAAI,OAAO,UAAA,QAAA,EAAO,OAAO;GACvB,IAAM,IAAc,KAAK;AAmBzB,GAjBI,KAAK,YAAY,WACnB,OAAO,MAAM,KACX,MACA,IAAI,KAAK,UAAU,sGACnB,4FACA,EAAE,OAAO,eAAe,CACzB,GACS,EAAY,SAAS,SAAS,KAAK,QAAQ,IACrD,OAAO,MAAM,KACX,MACA,IAAI,KAAK,UAAU,wEACnB,0FACA,EACE,QAAQ,CAAC,GAAG,EAAY,SAAS,EAClC,CACF,EAEC,KAAK,aAAa,WAAW,IAC/B,OAAO,MAAM,KACX,MACA,IAAI,KAAK,UAAU,mHACnB,4FACA,EAAE,OAAO,eAAe,CACzB;;;;GAnCN,EAAS;CAAE,MAAM;CAAQ,SAAS;CAAM,CAAC,CAAA,EAAA,EAAA,WAAA,WAAA,KAAA,EAAA"}
|
|
@@ -54,10 +54,7 @@ export declare abstract class TabsBase extends SpectrumElement {
|
|
|
54
54
|
/** @internal */
|
|
55
55
|
private _density;
|
|
56
56
|
/**
|
|
57
|
-
* The layout direction of the tab list
|
|
58
|
-
*
|
|
59
|
-
* **Breaking change:** `'vertical-right'` is no longer supported.
|
|
60
|
-
* Use `'vertical'` instead.
|
|
57
|
+
* The layout direction of the tab list: `horizontal` (default) or `vertical`.
|
|
61
58
|
*
|
|
62
59
|
* @default 'horizontal'
|
|
63
60
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tabs.base.js","names":[],"sources":["../../../components/tabs/Tabs.base.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\nimport { PropertyValues } from 'lit';\nimport { property, state } from 'lit/decorators.js';\n\nimport {\n focusgroupNavigationActiveChange,\n type FocusgroupNavigationActiveChangeDetail,\n FocusgroupNavigationController,\n} from '@adobe/spectrum-wc-core/controllers/index.js';\nimport { SpectrumElement } from '@adobe/spectrum-wc-core/element/index.js';\n\nimport {\n KEYBOARD_ACTIVATION_DEFAULT,\n KEYBOARD_ACTIVATIONS,\n type KeyboardActivation,\n TAB_DENSITIES,\n TAB_DENSITY_DEFAULT,\n type TabDensity,\n type TabLike,\n type TabPanelLike,\n TABS_DEFAULT_DIRECTION,\n TABS_DIRECTIONS,\n type TabsDirection,\n} from './Tabs.types.js';\n\n/**\n * Base class for a tabbed interface container.\n *\n * Manages selection state, tab/panel association, ARIA wiring,\n * keyboard navigation, and event dispatching. Concrete classes\n * supply the stylesheet, render template, and visual behaviors\n * such as the selection indicator.\n *\n * Public API follows the Spectrum 2 / Spectrum Design–aligned surface\n * from the tabs migration plan: `keyboard-activation` and `density`\n * instead of legacy `auto`, `compact`, `quiet`, `emphasized`, and\n * t-shirt `size` attributes.\n *\n * @slot - Tab items (elements with `role=\"tab\"`)\n * @slot tab-panel - Tab panel content (elements with `role=\"tabpanel\"`)\n *\n * @fires change - The selected tab has changed. Cancelable —\n * calling `preventDefault()` reverts the selection.\n */\nexport abstract class TabsBase extends SpectrumElement {\n // ─────────────────────────\n // SHARED API\n // ─────────────────────────\n\n /**\n * @internal\n *\n * Valid direction values for validation and CEM/stories.\n */\n static readonly VALID_DIRECTIONS: readonly TabsDirection[] = TABS_DIRECTIONS;\n\n /**\n * @internal\n */\n static readonly VALID_KEYBOARD_ACTIVATIONS: readonly KeyboardActivation[] =\n KEYBOARD_ACTIVATIONS;\n\n /**\n * @internal\n */\n static readonly VALID_DENSITIES: readonly TabDensity[] = TAB_DENSITIES;\n\n /**\n * Whether selection follows keyboard focus (`automatic`, default) or the\n * user must press Enter or Space to activate (`manual`). Prefer `manual`\n * when tab panels are expensive to render or not fully present in the DOM.\n *\n * @see https://w3c.github.io/aria-practices/#kbd_selection_follows_focus\n */\n @property({ type: String, reflect: true, attribute: 'keyboard-activation' })\n public get keyboardActivation(): KeyboardActivation {\n return this._keyboardActivation;\n }\n\n public set keyboardActivation(value: string) {\n const isValid = (KEYBOARD_ACTIVATIONS as readonly string[]).includes(value);\n\n if (!isValid && window.__swc?.DEBUG) {\n window.__swc.warn(\n this,\n `<${this.localName}> expects \"keyboard-activation\" to be one of:`,\n 'https://opensource.adobe.com/spectrum-web-components/components/tabs/',\n { issues: [...KEYBOARD_ACTIVATIONS] }\n );\n }\n\n const valid = isValid\n ? (value as KeyboardActivation)\n : KEYBOARD_ACTIVATION_DEFAULT;\n\n if (this._keyboardActivation === valid) {\n return;\n }\n\n const old = this._keyboardActivation;\n this._keyboardActivation = valid;\n this.requestUpdate('keyboardActivation', old);\n }\n\n /** @internal */\n private _keyboardActivation: KeyboardActivation = KEYBOARD_ACTIVATION_DEFAULT;\n\n /**\n * Layout density: `regular` (default) or `compact` (reduced tab spacing).\n */\n @property({ type: String, reflect: true })\n public get density(): TabDensity {\n return this._density;\n }\n\n public set density(value: string) {\n const isValid = (TAB_DENSITIES as readonly string[]).includes(value);\n\n if (!isValid && window.__swc?.DEBUG) {\n window.__swc.warn(\n this,\n `<${this.localName}> expects \"density\" to be one of:`,\n 'https://opensource.adobe.com/spectrum-web-components/components/tabs/',\n { issues: [...TAB_DENSITIES] }\n );\n }\n\n const valid = isValid ? (value as TabDensity) : TAB_DENSITY_DEFAULT;\n\n if (this._density === valid) {\n return;\n }\n\n const old = this._density;\n this._density = valid;\n this.requestUpdate('density', old);\n }\n\n /** @internal */\n private _density: TabDensity = TAB_DENSITY_DEFAULT;\n\n /**\n * The layout direction of the tab list.\n *\n * **Breaking change:** `'vertical-right'` is no longer supported.\n * Use `'vertical'` instead.\n *\n * @default 'horizontal'\n */\n @property({ type: String, reflect: true })\n public get direction(): TabsDirection {\n return this._direction;\n }\n\n public set direction(value: TabsDirection) {\n const isValid = (TABS_DIRECTIONS as readonly string[]).includes(value);\n\n if (!isValid && window.__swc?.DEBUG) {\n window.__swc.warn(\n this,\n `<${this.localName}> expects the \"direction\" attribute to be one of the following:`,\n 'https://opensource.adobe.com/spectrum-web-components/components/tabs/',\n {\n issues: [...TABS_DIRECTIONS],\n }\n );\n }\n\n const validDirection = isValid\n ? (value as TabsDirection)\n : TABS_DEFAULT_DIRECTION;\n\n if (this._direction === validDirection) {\n return;\n }\n\n const oldDirection = this._direction;\n this._direction = validDirection;\n this.requestUpdate('direction', oldDirection);\n }\n\n /** @internal */\n private _direction: TabsDirection = TABS_DEFAULT_DIRECTION;\n\n /**\n * Whether the entire tab list is disabled. When `true`,\n * `aria-disabled=\"true\"` is applied to the tablist element and\n * all interaction is suppressed.\n */\n @property({ type: Boolean, reflect: true })\n public disabled = false;\n\n /**\n * Accessible label for the tablist. Rendered as `aria-label` on the\n * element with `role=\"tablist\"` in the concrete template.\n */\n @property({ type: String, attribute: 'accessible-label' })\n public accessibleLabel = '';\n\n /**\n * The `tab-id` of the currently selected tab. Setting this property\n * updates which tab appears selected and which panel is visible.\n */\n @property({ type: String, reflect: true })\n public selected = '';\n\n // ──────────────────────\n // IMPLEMENTATION\n // ──────────────────────\n\n /** @internal */\n private static readonly INDICATOR_BASE_SIZE = 100;\n\n /**\n * @internal\n *\n * Inline style applied to the selection indicator element.\n * Computed from the selected tab's position and dimensions.\n */\n @state()\n protected selectionIndicatorStyle = '';\n\n /**\n * @internal\n *\n * Suppresses the transition on the very first indicator placement\n * so it doesn't animate from the origin.\n */\n @state()\n protected shouldAnimate = false;\n\n /**\n * @internal\n *\n * Cached list of tab elements managed by this container. Updated\n * via `handleTabSlotChange`.\n */\n private _tabs: TabLike[] = [];\n\n /**\n * @internal\n *\n * Manages roving tabindex and arrow-key / Home / End focus movement within\n * the tab list. Direction is kept in sync with `this.direction` via\n * `setOptions` in `willUpdate`. Disabled tabs remain in the navigation\n * sequence (per APG) but are not activatable.\n *\n * `getItems` returns an empty array when the container is disabled so all\n * tabs lose their tab stop, matching the `aria-disabled` tablist behavior.\n */\n private readonly _navigation = new FocusgroupNavigationController(this, {\n direction: this._direction,\n wrap: true,\n memory: true,\n getItems: () => (this.disabled ? [] : (this._tabs as HTMLElement[])),\n });\n\n /**\n * Handles `focusgroupNavigationActiveChange` events dispatched by\n * `_navigation`. In automatic activation mode, selects the newly focused\n * tab (unless it is disabled).\n *\n * Only `source: 'keyboard'` (arrow keys, Home/End) and `source: 'focus'`\n * (pointer click, Tab-key entry) represent a real focus move that\n * \"selection follows focus\" should react to. `'refresh'` and\n * `'programmatic'` fire when `_navigation` re-parks the roving tab stop\n * without anyone actually moving focus there — for example on mount or when\n * re-enabling after `disabled` — and must not trigger a spurious selection\n * or `change` event.\n */\n private readonly _handleNavigationActiveChange = (event: Event): void => {\n if (this._keyboardActivation !== 'automatic') {\n return;\n }\n const { activeElement, source } = (\n event as CustomEvent<FocusgroupNavigationActiveChangeDetail>\n ).detail;\n if (source !== 'keyboard' && source !== 'focus') {\n return;\n }\n if (!activeElement) {\n return;\n }\n const tab = activeElement as TabLike;\n if (tab.disabled) {\n return;\n }\n this.selectTarget(tab);\n };\n\n /**\n * Whether an assigned node is treated as a tab. `role=\"tab\"` is set in\n * each tab's `firstUpdated`, so `slotchange` may run before that — accept\n * known tab host tag names so the tab list and selection indicator sync.\n */\n private static isTabSlotNode(el: Element): el is TabLike {\n if (!(el instanceof HTMLElement)) {\n return false;\n }\n if (el.getAttribute('role') === 'tab') {\n return true;\n }\n return el.localName === 'swc-tab';\n }\n\n /**\n * Called by the concrete class when the default slot's content\n * changes. Rebuilds the internal tab list and syncs selection\n * state.\n */\n protected handleTabSlotChange(event: Event): void {\n const slot = event.target as HTMLSlotElement;\n this._tabs = slot\n .assignedElements()\n .filter(TabsBase.isTabSlotNode) as TabLike[];\n\n this._navigation.refresh();\n this.updateCheckedState();\n this.updateSelectionIndicator();\n }\n\n /**\n * Called by the concrete class when the `tab-panel` slot's content\n * changes. Wires up `aria-controls` / `aria-labelledby` between\n * each tab and its panel.\n */\n protected handlePanelSlotChange(event: Event): void {\n const slot = event.target as HTMLSlotElement;\n const panels = slot.assignedElements() as TabPanelLike[];\n this.managePanels(panels);\n }\n\n /**\n * Click handler bound to the tablist wrapper in the concrete\n * template. Activates the clicked tab.\n */\n protected handleClick(event: Event): void {\n if (this.disabled) {\n return;\n }\n\n const target = event\n .composedPath()\n .find((el) => (el as TabLike).parentElement === this) as\n | TabLike\n | undefined;\n\n if (!target || target.disabled) {\n return;\n }\n\n this.selectTarget(target);\n }\n\n /**\n * Keyboard handler for Enter and Space activation. Arrow-key, Home, and\n * End navigation is handled by `FocusgroupNavigationController` in capture\n * phase on the host element.\n */\n protected handleKeyDown(event: KeyboardEvent): void {\n if (this.disabled) {\n return;\n }\n\n const { code } = event;\n if (code === 'Enter' || code === 'Space') {\n event.preventDefault();\n const target = event.target as TabLike | null;\n if (target && !target.disabled) {\n this.selectTarget(target);\n }\n }\n }\n\n /**\n * Attempts to select the given tab element. Dispatches a cancelable\n * `change` event — if the consumer calls `preventDefault()`, the\n * selection reverts to the previous value.\n */\n private selectTarget(target: TabLike): void {\n const id = target.tabId;\n if (!id || id === this.selected) {\n return;\n }\n\n const previous = this.selected;\n this.selected = id;\n\n // Sync child tab.selected eagerly so that change-event handlers read the\n // correct state on individual tab elements without waiting for the next\n // Lit render cycle (tabs.selected is already updated synchronously above).\n this.updateCheckedState();\n\n const applyDefault = this.dispatchEvent(\n new Event('change', {\n cancelable: true,\n })\n );\n\n if (!applyDefault) {\n this.selected = previous;\n }\n }\n\n /**\n * Synchronizes the `selected` property on each child tab to match\n * `this.selected`, then parks the roving tab stop on the selected tab via\n * `_navigation.setActiveItem`. Tabindex management is otherwise owned by\n * `FocusgroupNavigationController` — for example, `_navigation.refresh()`\n * resets all tabindex values when the tab list or `disabled` state changes.\n */\n private updateCheckedState(): void {\n for (const tab of this._tabs) {\n tab.selected = false;\n }\n\n if (this.selected) {\n const currentTab = this._tabs.find((el) => el.tabId === this.selected);\n\n if (currentTab) {\n currentTab.selected = true;\n this._navigation.setActiveItem(currentTab as HTMLElement);\n } else {\n this.selected = '';\n }\n }\n }\n\n /**\n * Wires up cross-element ARIA relationships between tabs and\n * panels. Each tab gets `aria-controls` pointing at its panel's\n * `id`, and each panel gets `aria-labelledby` pointing at its\n * tab's `id`.\n */\n private managePanels(panels: TabPanelLike[]): void {\n for (const panel of panels) {\n const { tabId, id } = panel;\n const tab = this.querySelector(`[role=\"tab\"][tab-id=\"${tabId}\"]`);\n\n if (tab) {\n tab.setAttribute('aria-controls', id);\n panel.setAttribute('aria-labelledby', tab.id);\n }\n\n panel.selected = tabId === this.selected;\n }\n }\n\n // ───────────────────────────────────\n // SELECTION INDICATOR\n // ───────────────────────────────────\n\n /**\n * @internal\n *\n * Recalculates the selection indicator's position and size based\n * on the currently selected tab element. Uses CSS transforms for\n * smooth animation between tab positions.\n *\n * The indicator is a fixed-size element (100px base) that gets\n * `scaleX`/`scaleY` to match the selected tab's width/height,\n * and `translateX`/`translateY` to match its offset position.\n */\n protected updateSelectionIndicator = async (): Promise<void> => {\n const selectedElement = this._tabs.find((el) => el.selected);\n if (!selectedElement) {\n this.selectionIndicatorStyle =\n 'transform: translateX(0px) scaleX(0) scaleY(0)';\n return;\n }\n\n const tablist = this.renderRoot?.querySelector('.tablist');\n if (!tablist) {\n return;\n }\n\n await Promise.all([\n (selectedElement as HTMLElement & { updateComplete?: Promise<boolean> })\n .updateComplete ?? Promise.resolve(),\n document.fonts ? document.fonts.ready : Promise.resolve(),\n ]);\n\n const tabRect = selectedElement.getBoundingClientRect();\n const listRect = tablist.getBoundingClientRect();\n\n if (this._direction === 'horizontal') {\n const isRtl = getComputedStyle(this).direction === 'rtl';\n const offset = isRtl\n ? tabRect.right - listRect.right\n : tabRect.left - listRect.left;\n const scale = tabRect.width / TabsBase.INDICATOR_BASE_SIZE;\n this.selectionIndicatorStyle = `transform: translateX(${offset}px) scaleX(${scale})`;\n } else {\n const top = tabRect.top - listRect.top;\n const scale = tabRect.height / TabsBase.INDICATOR_BASE_SIZE;\n this.selectionIndicatorStyle = `transform: translateY(${top}px) scaleY(${scale})`;\n }\n\n if (!this.shouldAnimate) {\n await this.updateComplete;\n this.shouldAnimate = true;\n }\n };\n\n /** @internal */\n private _resizeObserver?: ResizeObserver;\n\n /**\n * @internal\n *\n * Watches for `dir` attribute changes so the indicator recalculates\n * when writing direction flips at runtime. `getComputedStyle` in\n * `updateSelectionIndicator` resolves inherited direction correctly,\n * but nothing else observes it: a `dir` flip on the document root or\n * an ancestor doesn't resize this element, so the `ResizeObserver`\n * below won't catch it. Mirrors the `dir`-watching pattern in\n * `placement-controller.ts`.\n */\n private _directionObserver?: MutationObserver;\n\n // ───────────────────────────────────\n // LIFECYCLE\n // ───────────────────────────────────\n\n protected override willUpdate(changes: PropertyValues): void {\n if (!this.hasUpdated) {\n const selectedChild = this.querySelector(\n ':scope > [selected]'\n ) as TabLike | null;\n\n if (selectedChild) {\n this.selectTarget(selectedChild);\n }\n }\n\n super.willUpdate(changes);\n\n if (changes.has('selected')) {\n if (this._tabs.length) {\n this.updateCheckedState();\n }\n\n const previousValue = changes.get('selected') as string | undefined;\n if (previousValue) {\n const previous = this.querySelector(\n `[role=\"tabpanel\"][tab-id=\"${previousValue}\"]`\n ) as TabPanelLike | null;\n\n if (previous) {\n previous.selected = false;\n }\n }\n\n const next = this.querySelector(\n `[role=\"tabpanel\"][tab-id=\"${this.selected}\"]`\n ) as TabPanelLike | null;\n\n if (next) {\n next.selected = true;\n }\n\n this.updateSelectionIndicator();\n }\n\n if (changes.has('disabled') && this._tabs.length) {\n this._navigation.refresh();\n if (this.disabled) {\n // When disabled, getItems() returns [] so the controller's cleanup\n // loop in refresh() iterates nothing. Explicitly remove all tabs from\n // the tab order so the tablist is fully inert.\n for (const tab of this._tabs) {\n (tab as HTMLElement).tabIndex = -1;\n }\n } else {\n // After re-enabling, restore the roving tab stop to the selected tab.\n this.updateCheckedState();\n }\n }\n\n if (changes.has('direction')) {\n this._navigation.setOptions({ direction: this.direction });\n this.updateSelectionIndicator();\n }\n }\n\n public override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener(\n focusgroupNavigationActiveChange,\n this._handleNavigationActiveChange\n );\n window.addEventListener('resize', this.updateSelectionIndicator);\n if ('fonts' in document) {\n document.fonts.addEventListener(\n 'loadingdone',\n this.updateSelectionIndicator\n );\n }\n this._resizeObserver = new ResizeObserver(() => {\n this.updateSelectionIndicator();\n });\n this._resizeObserver.observe(this);\n\n this._directionObserver = new MutationObserver(() => {\n this.updateSelectionIndicator();\n });\n const observeDir = (node: Element | null | undefined) => {\n if (node) {\n this._directionObserver?.observe(node, {\n attributes: true,\n attributeFilter: ['dir'],\n });\n }\n };\n observeDir(this.ownerDocument.documentElement);\n observeDir(this.closest('[dir]'));\n }\n\n public override disconnectedCallback(): void {\n this.removeEventListener(\n focusgroupNavigationActiveChange,\n this._handleNavigationActiveChange\n );\n window.removeEventListener('resize', this.updateSelectionIndicator);\n if ('fonts' in document) {\n document.fonts.removeEventListener(\n 'loadingdone',\n this.updateSelectionIndicator\n );\n }\n this._resizeObserver?.disconnect();\n this._resizeObserver = undefined;\n this._directionObserver?.disconnect();\n this._directionObserver = undefined;\n super.disconnectedCallback();\n }\n\n /**\n * Focuses the selected tab, or the first tab when none is selected yet.\n * Slotted tabs live in the light DOM; this is more reliable than relying\n * only on shadow `delegatesFocus` across browsers and test harnesses.\n */\n public override focus(options?: FocusOptions): void {\n if (this.disabled) {\n return;\n }\n\n const selectedTab = this._tabs.find((tab) => tab.selected);\n if (selectedTab) {\n (selectedTab as HTMLElement).focus(options);\n return;\n }\n\n if (this._tabs.length) {\n (this._tabs[0] as HTMLElement).focus(options);\n return;\n }\n\n super.focus(options);\n }\n\n protected override firstUpdated(changes: PropertyValues): void {\n super.firstUpdated(changes);\n\n if (!this.hasAttribute('direction')) {\n this.setAttribute('direction', this.direction);\n }\n\n if (window.__swc?.DEBUG && !this.accessibleLabel) {\n window.__swc.warn(\n this,\n `<${this.localName}> requires an \"accessible-label\" attribute to provide an accessible name for the tablist.`,\n 'https://opensource.adobe.com/spectrum-web-components/components/tabs/',\n { type: 'accessibility', level: 'high' }\n );\n }\n }\n\n /**\n * Waits for all child tab elements to finish their update cycle\n * before resolving so layout-dependent callers see stable geometry.\n */\n public override async getUpdateComplete(): Promise<boolean> {\n const complete = await super.getUpdateComplete();\n\n const children = [...this.children] as HTMLElement[];\n const childUpdates = children.map((child) => {\n if ('updateComplete' in child) {\n return (child as unknown as { updateComplete: Promise<boolean> })\n .updateComplete;\n }\n return Promise.resolve(true);\n });\n\n await Promise.all(childUpdates);\n return complete;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Tabs.base.js","names":[],"sources":["../../../components/tabs/Tabs.base.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\nimport { PropertyValues } from 'lit';\nimport { property, state } from 'lit/decorators.js';\n\nimport {\n focusgroupNavigationActiveChange,\n type FocusgroupNavigationActiveChangeDetail,\n FocusgroupNavigationController,\n} from '@adobe/spectrum-wc-core/controllers/index.js';\nimport { SpectrumElement } from '@adobe/spectrum-wc-core/element/index.js';\n\nimport {\n KEYBOARD_ACTIVATION_DEFAULT,\n KEYBOARD_ACTIVATIONS,\n type KeyboardActivation,\n TAB_DENSITIES,\n TAB_DENSITY_DEFAULT,\n type TabDensity,\n type TabLike,\n type TabPanelLike,\n TABS_DEFAULT_DIRECTION,\n TABS_DIRECTIONS,\n type TabsDirection,\n} from './Tabs.types.js';\n\n/**\n * Base class for a tabbed interface container.\n *\n * Manages selection state, tab/panel association, ARIA wiring,\n * keyboard navigation, and event dispatching. Concrete classes\n * supply the stylesheet, render template, and visual behaviors\n * such as the selection indicator.\n *\n * Public API follows the Spectrum 2 / Spectrum Design–aligned surface\n * from the tabs migration plan: `keyboard-activation` and `density`\n * instead of legacy `auto`, `compact`, `quiet`, `emphasized`, and\n * t-shirt `size` attributes.\n *\n * @slot - Tab items (elements with `role=\"tab\"`)\n * @slot tab-panel - Tab panel content (elements with `role=\"tabpanel\"`)\n *\n * @fires change - The selected tab has changed. Cancelable —\n * calling `preventDefault()` reverts the selection.\n */\nexport abstract class TabsBase extends SpectrumElement {\n // ─────────────────────────\n // SHARED API\n // ─────────────────────────\n\n /**\n * @internal\n *\n * Valid direction values for validation and CEM/stories.\n */\n static readonly VALID_DIRECTIONS: readonly TabsDirection[] = TABS_DIRECTIONS;\n\n /**\n * @internal\n */\n static readonly VALID_KEYBOARD_ACTIVATIONS: readonly KeyboardActivation[] =\n KEYBOARD_ACTIVATIONS;\n\n /**\n * @internal\n */\n static readonly VALID_DENSITIES: readonly TabDensity[] = TAB_DENSITIES;\n\n /**\n * Whether selection follows keyboard focus (`automatic`, default) or the\n * user must press Enter or Space to activate (`manual`). Prefer `manual`\n * when tab panels are expensive to render or not fully present in the DOM.\n *\n * @see https://w3c.github.io/aria-practices/#kbd_selection_follows_focus\n */\n @property({ type: String, reflect: true, attribute: 'keyboard-activation' })\n public get keyboardActivation(): KeyboardActivation {\n return this._keyboardActivation;\n }\n\n public set keyboardActivation(value: string) {\n const isValid = (KEYBOARD_ACTIVATIONS as readonly string[]).includes(value);\n\n if (!isValid && window.__swc?.DEBUG) {\n window.__swc.warn(\n this,\n `<${this.localName}> expects \"keyboard-activation\" to be one of:`,\n 'https://opensource.adobe.com/spectrum-web-components/components/tabs/',\n { issues: [...KEYBOARD_ACTIVATIONS] }\n );\n }\n\n const valid = isValid\n ? (value as KeyboardActivation)\n : KEYBOARD_ACTIVATION_DEFAULT;\n\n if (this._keyboardActivation === valid) {\n return;\n }\n\n const old = this._keyboardActivation;\n this._keyboardActivation = valid;\n this.requestUpdate('keyboardActivation', old);\n }\n\n /** @internal */\n private _keyboardActivation: KeyboardActivation = KEYBOARD_ACTIVATION_DEFAULT;\n\n /**\n * Layout density: `regular` (default) or `compact` (reduced tab spacing).\n */\n @property({ type: String, reflect: true })\n public get density(): TabDensity {\n return this._density;\n }\n\n public set density(value: string) {\n const isValid = (TAB_DENSITIES as readonly string[]).includes(value);\n\n if (!isValid && window.__swc?.DEBUG) {\n window.__swc.warn(\n this,\n `<${this.localName}> expects \"density\" to be one of:`,\n 'https://opensource.adobe.com/spectrum-web-components/components/tabs/',\n { issues: [...TAB_DENSITIES] }\n );\n }\n\n const valid = isValid ? (value as TabDensity) : TAB_DENSITY_DEFAULT;\n\n if (this._density === valid) {\n return;\n }\n\n const old = this._density;\n this._density = valid;\n this.requestUpdate('density', old);\n }\n\n /** @internal */\n private _density: TabDensity = TAB_DENSITY_DEFAULT;\n\n /**\n * The layout direction of the tab list: `horizontal` (default) or `vertical`.\n *\n * @default 'horizontal'\n */\n @property({ type: String, reflect: true })\n public get direction(): TabsDirection {\n return this._direction;\n }\n\n public set direction(value: TabsDirection) {\n const isValid = (TABS_DIRECTIONS as readonly string[]).includes(value);\n\n if (!isValid && window.__swc?.DEBUG) {\n window.__swc.warn(\n this,\n `<${this.localName}> expects the \"direction\" attribute to be one of the following:`,\n 'https://opensource.adobe.com/spectrum-web-components/components/tabs/',\n {\n issues: [...TABS_DIRECTIONS],\n }\n );\n }\n\n const validDirection = isValid\n ? (value as TabsDirection)\n : TABS_DEFAULT_DIRECTION;\n\n if (this._direction === validDirection) {\n return;\n }\n\n const oldDirection = this._direction;\n this._direction = validDirection;\n this.requestUpdate('direction', oldDirection);\n }\n\n /** @internal */\n private _direction: TabsDirection = TABS_DEFAULT_DIRECTION;\n\n /**\n * Whether the entire tab list is disabled. When `true`,\n * `aria-disabled=\"true\"` is applied to the tablist element and\n * all interaction is suppressed.\n */\n @property({ type: Boolean, reflect: true })\n public disabled = false;\n\n /**\n * Accessible label for the tablist. Rendered as `aria-label` on the\n * element with `role=\"tablist\"` in the concrete template.\n */\n @property({ type: String, attribute: 'accessible-label' })\n public accessibleLabel = '';\n\n /**\n * The `tab-id` of the currently selected tab. Setting this property\n * updates which tab appears selected and which panel is visible.\n */\n @property({ type: String, reflect: true })\n public selected = '';\n\n // ──────────────────────\n // IMPLEMENTATION\n // ──────────────────────\n\n /** @internal */\n private static readonly INDICATOR_BASE_SIZE = 100;\n\n /**\n * @internal\n *\n * Inline style applied to the selection indicator element.\n * Computed from the selected tab's position and dimensions.\n */\n @state()\n protected selectionIndicatorStyle = '';\n\n /**\n * @internal\n *\n * Suppresses the transition on the very first indicator placement\n * so it doesn't animate from the origin.\n */\n @state()\n protected shouldAnimate = false;\n\n /**\n * @internal\n *\n * Cached list of tab elements managed by this container. Updated\n * via `handleTabSlotChange`.\n */\n private _tabs: TabLike[] = [];\n\n /**\n * @internal\n *\n * Manages roving tabindex and arrow-key / Home / End focus movement within\n * the tab list. Direction is kept in sync with `this.direction` via\n * `setOptions` in `willUpdate`. Disabled tabs remain in the navigation\n * sequence (per APG) but are not activatable.\n *\n * `getItems` returns an empty array when the container is disabled so all\n * tabs lose their tab stop, matching the `aria-disabled` tablist behavior.\n */\n private readonly _navigation = new FocusgroupNavigationController(this, {\n direction: this._direction,\n wrap: true,\n memory: true,\n getItems: () => (this.disabled ? [] : (this._tabs as HTMLElement[])),\n });\n\n /**\n * Handles `focusgroupNavigationActiveChange` events dispatched by\n * `_navigation`. In automatic activation mode, selects the newly focused\n * tab (unless it is disabled).\n *\n * Only `source: 'keyboard'` (arrow keys, Home/End) and `source: 'focus'`\n * (pointer click, Tab-key entry) represent a real focus move that\n * \"selection follows focus\" should react to. `'refresh'` and\n * `'programmatic'` fire when `_navigation` re-parks the roving tab stop\n * without anyone actually moving focus there — for example on mount or when\n * re-enabling after `disabled` — and must not trigger a spurious selection\n * or `change` event.\n */\n private readonly _handleNavigationActiveChange = (event: Event): void => {\n if (this._keyboardActivation !== 'automatic') {\n return;\n }\n const { activeElement, source } = (\n event as CustomEvent<FocusgroupNavigationActiveChangeDetail>\n ).detail;\n if (source !== 'keyboard' && source !== 'focus') {\n return;\n }\n if (!activeElement) {\n return;\n }\n const tab = activeElement as TabLike;\n if (tab.disabled) {\n return;\n }\n this.selectTarget(tab);\n };\n\n /**\n * Whether an assigned node is treated as a tab. `role=\"tab\"` is set in\n * each tab's `firstUpdated`, so `slotchange` may run before that — accept\n * known tab host tag names so the tab list and selection indicator sync.\n */\n private static isTabSlotNode(el: Element): el is TabLike {\n if (!(el instanceof HTMLElement)) {\n return false;\n }\n if (el.getAttribute('role') === 'tab') {\n return true;\n }\n return el.localName === 'swc-tab';\n }\n\n /**\n * Called by the concrete class when the default slot's content\n * changes. Rebuilds the internal tab list and syncs selection\n * state.\n */\n protected handleTabSlotChange(event: Event): void {\n const slot = event.target as HTMLSlotElement;\n this._tabs = slot\n .assignedElements()\n .filter(TabsBase.isTabSlotNode) as TabLike[];\n\n this._navigation.refresh();\n this.updateCheckedState();\n this.updateSelectionIndicator();\n }\n\n /**\n * Called by the concrete class when the `tab-panel` slot's content\n * changes. Wires up `aria-controls` / `aria-labelledby` between\n * each tab and its panel.\n */\n protected handlePanelSlotChange(event: Event): void {\n const slot = event.target as HTMLSlotElement;\n const panels = slot.assignedElements() as TabPanelLike[];\n this.managePanels(panels);\n }\n\n /**\n * Click handler bound to the tablist wrapper in the concrete\n * template. Activates the clicked tab.\n */\n protected handleClick(event: Event): void {\n if (this.disabled) {\n return;\n }\n\n const target = event\n .composedPath()\n .find((el) => (el as TabLike).parentElement === this) as\n | TabLike\n | undefined;\n\n if (!target || target.disabled) {\n return;\n }\n\n this.selectTarget(target);\n }\n\n /**\n * Keyboard handler for Enter and Space activation. Arrow-key, Home, and\n * End navigation is handled by `FocusgroupNavigationController` in capture\n * phase on the host element.\n */\n protected handleKeyDown(event: KeyboardEvent): void {\n if (this.disabled) {\n return;\n }\n\n const { code } = event;\n if (code === 'Enter' || code === 'Space') {\n event.preventDefault();\n const target = event.target as TabLike | null;\n if (target && !target.disabled) {\n this.selectTarget(target);\n }\n }\n }\n\n /**\n * Attempts to select the given tab element. Dispatches a cancelable\n * `change` event — if the consumer calls `preventDefault()`, the\n * selection reverts to the previous value.\n */\n private selectTarget(target: TabLike): void {\n const id = target.tabId;\n if (!id || id === this.selected) {\n return;\n }\n\n const previous = this.selected;\n this.selected = id;\n\n // Sync child tab.selected eagerly so that change-event handlers read the\n // correct state on individual tab elements without waiting for the next\n // Lit render cycle (tabs.selected is already updated synchronously above).\n this.updateCheckedState();\n\n const applyDefault = this.dispatchEvent(\n new Event('change', {\n cancelable: true,\n })\n );\n\n if (!applyDefault) {\n this.selected = previous;\n }\n }\n\n /**\n * Synchronizes the `selected` property on each child tab to match\n * `this.selected`, then parks the roving tab stop on the selected tab via\n * `_navigation.setActiveItem`. Tabindex management is otherwise owned by\n * `FocusgroupNavigationController` — for example, `_navigation.refresh()`\n * resets all tabindex values when the tab list or `disabled` state changes.\n */\n private updateCheckedState(): void {\n for (const tab of this._tabs) {\n tab.selected = false;\n }\n\n if (this.selected) {\n const currentTab = this._tabs.find((el) => el.tabId === this.selected);\n\n if (currentTab) {\n currentTab.selected = true;\n this._navigation.setActiveItem(currentTab as HTMLElement);\n } else {\n this.selected = '';\n }\n }\n }\n\n /**\n * Wires up cross-element ARIA relationships between tabs and\n * panels. Each tab gets `aria-controls` pointing at its panel's\n * `id`, and each panel gets `aria-labelledby` pointing at its\n * tab's `id`.\n */\n private managePanels(panels: TabPanelLike[]): void {\n for (const panel of panels) {\n const { tabId, id } = panel;\n const tab = this.querySelector(`[role=\"tab\"][tab-id=\"${tabId}\"]`);\n\n if (tab) {\n tab.setAttribute('aria-controls', id);\n panel.setAttribute('aria-labelledby', tab.id);\n }\n\n panel.selected = tabId === this.selected;\n }\n }\n\n // ───────────────────────────────────\n // SELECTION INDICATOR\n // ───────────────────────────────────\n\n /**\n * @internal\n *\n * Recalculates the selection indicator's position and size based\n * on the currently selected tab element. Uses CSS transforms for\n * smooth animation between tab positions.\n *\n * The indicator is a fixed-size element (100px base) that gets\n * `scaleX`/`scaleY` to match the selected tab's width/height,\n * and `translateX`/`translateY` to match its offset position.\n */\n protected updateSelectionIndicator = async (): Promise<void> => {\n const selectedElement = this._tabs.find((el) => el.selected);\n if (!selectedElement) {\n this.selectionIndicatorStyle =\n 'transform: translateX(0px) scaleX(0) scaleY(0)';\n return;\n }\n\n const tablist = this.renderRoot?.querySelector('.tablist');\n if (!tablist) {\n return;\n }\n\n await Promise.all([\n (selectedElement as HTMLElement & { updateComplete?: Promise<boolean> })\n .updateComplete ?? Promise.resolve(),\n document.fonts ? document.fonts.ready : Promise.resolve(),\n ]);\n\n const tabRect = selectedElement.getBoundingClientRect();\n const listRect = tablist.getBoundingClientRect();\n\n if (this._direction === 'horizontal') {\n const isRtl = getComputedStyle(this).direction === 'rtl';\n const offset = isRtl\n ? tabRect.right - listRect.right\n : tabRect.left - listRect.left;\n const scale = tabRect.width / TabsBase.INDICATOR_BASE_SIZE;\n this.selectionIndicatorStyle = `transform: translateX(${offset}px) scaleX(${scale})`;\n } else {\n const top = tabRect.top - listRect.top;\n const scale = tabRect.height / TabsBase.INDICATOR_BASE_SIZE;\n this.selectionIndicatorStyle = `transform: translateY(${top}px) scaleY(${scale})`;\n }\n\n if (!this.shouldAnimate) {\n await this.updateComplete;\n this.shouldAnimate = true;\n }\n };\n\n /** @internal */\n private _resizeObserver?: ResizeObserver;\n\n /**\n * @internal\n *\n * Watches for `dir` attribute changes so the indicator recalculates\n * when writing direction flips at runtime. `getComputedStyle` in\n * `updateSelectionIndicator` resolves inherited direction correctly,\n * but nothing else observes it: a `dir` flip on the document root or\n * an ancestor doesn't resize this element, so the `ResizeObserver`\n * below won't catch it. Mirrors the `dir`-watching pattern in\n * `placement-controller.ts`.\n */\n private _directionObserver?: MutationObserver;\n\n // ───────────────────────────────────\n // LIFECYCLE\n // ───────────────────────────────────\n\n protected override willUpdate(changes: PropertyValues): void {\n if (!this.hasUpdated) {\n const selectedChild = this.querySelector(\n ':scope > [selected]'\n ) as TabLike | null;\n\n if (selectedChild) {\n this.selectTarget(selectedChild);\n }\n }\n\n super.willUpdate(changes);\n\n if (changes.has('selected')) {\n if (this._tabs.length) {\n this.updateCheckedState();\n }\n\n const previousValue = changes.get('selected') as string | undefined;\n if (previousValue) {\n const previous = this.querySelector(\n `[role=\"tabpanel\"][tab-id=\"${previousValue}\"]`\n ) as TabPanelLike | null;\n\n if (previous) {\n previous.selected = false;\n }\n }\n\n const next = this.querySelector(\n `[role=\"tabpanel\"][tab-id=\"${this.selected}\"]`\n ) as TabPanelLike | null;\n\n if (next) {\n next.selected = true;\n }\n\n this.updateSelectionIndicator();\n }\n\n if (changes.has('disabled') && this._tabs.length) {\n this._navigation.refresh();\n if (this.disabled) {\n // When disabled, getItems() returns [] so the controller's cleanup\n // loop in refresh() iterates nothing. Explicitly remove all tabs from\n // the tab order so the tablist is fully inert.\n for (const tab of this._tabs) {\n (tab as HTMLElement).tabIndex = -1;\n }\n } else {\n // After re-enabling, restore the roving tab stop to the selected tab.\n this.updateCheckedState();\n }\n }\n\n if (changes.has('direction')) {\n this._navigation.setOptions({ direction: this.direction });\n this.updateSelectionIndicator();\n }\n }\n\n public override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener(\n focusgroupNavigationActiveChange,\n this._handleNavigationActiveChange\n );\n window.addEventListener('resize', this.updateSelectionIndicator);\n if ('fonts' in document) {\n document.fonts.addEventListener(\n 'loadingdone',\n this.updateSelectionIndicator\n );\n }\n this._resizeObserver = new ResizeObserver(() => {\n this.updateSelectionIndicator();\n });\n this._resizeObserver.observe(this);\n\n this._directionObserver = new MutationObserver(() => {\n this.updateSelectionIndicator();\n });\n const observeDir = (node: Element | null | undefined) => {\n if (node) {\n this._directionObserver?.observe(node, {\n attributes: true,\n attributeFilter: ['dir'],\n });\n }\n };\n observeDir(this.ownerDocument.documentElement);\n observeDir(this.closest('[dir]'));\n }\n\n public override disconnectedCallback(): void {\n this.removeEventListener(\n focusgroupNavigationActiveChange,\n this._handleNavigationActiveChange\n );\n window.removeEventListener('resize', this.updateSelectionIndicator);\n if ('fonts' in document) {\n document.fonts.removeEventListener(\n 'loadingdone',\n this.updateSelectionIndicator\n );\n }\n this._resizeObserver?.disconnect();\n this._resizeObserver = undefined;\n this._directionObserver?.disconnect();\n this._directionObserver = undefined;\n super.disconnectedCallback();\n }\n\n /**\n * Focuses the selected tab, or the first tab when none is selected yet.\n * Slotted tabs live in the light DOM; this is more reliable than relying\n * only on shadow `delegatesFocus` across browsers and test harnesses.\n */\n public override focus(options?: FocusOptions): void {\n if (this.disabled) {\n return;\n }\n\n const selectedTab = this._tabs.find((tab) => tab.selected);\n if (selectedTab) {\n (selectedTab as HTMLElement).focus(options);\n return;\n }\n\n if (this._tabs.length) {\n (this._tabs[0] as HTMLElement).focus(options);\n return;\n }\n\n super.focus(options);\n }\n\n protected override firstUpdated(changes: PropertyValues): void {\n super.firstUpdated(changes);\n\n if (!this.hasAttribute('direction')) {\n this.setAttribute('direction', this.direction);\n }\n\n if (window.__swc?.DEBUG && !this.accessibleLabel) {\n window.__swc.warn(\n this,\n `<${this.localName}> requires an \"accessible-label\" attribute to provide an accessible name for the tablist.`,\n 'https://opensource.adobe.com/spectrum-web-components/components/tabs/',\n { type: 'accessibility', level: 'high' }\n );\n }\n }\n\n /**\n * Waits for all child tab elements to finish their update cycle\n * before resolving so layout-dependent callers see stable geometry.\n */\n public override async getUpdateComplete(): Promise<boolean> {\n const complete = await super.getUpdateComplete();\n\n const children = [...this.children] as HTMLElement[];\n const childUpdates = children.map((child) => {\n if ('updateComplete' in child) {\n return (child as unknown as { updateComplete: Promise<boolean> })\n .updateComplete;\n }\n return Promise.resolve(true);\n });\n\n await Promise.all(childUpdates);\n return complete;\n }\n}\n"],"mappings":"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"}
|
|
@@ -9,6 +9,11 @@ import { TooltipPlacement, TooltipVariant } from './Tooltip.types.js';
|
|
|
9
9
|
* (hover/focus open/close), and `PlacementController` integration (pixel positioning).
|
|
10
10
|
*
|
|
11
11
|
* @slot - Text label displayed in the tooltip.
|
|
12
|
+
*
|
|
13
|
+
* @fires swc-open - Dispatched when the tooltip begins to open, before the transition plays.
|
|
14
|
+
* @fires swc-close - Dispatched when the tooltip begins to close, before the transition plays.
|
|
15
|
+
* @fires swc-after-open - Dispatched after the tooltip finishes opening, once the transition completes.
|
|
16
|
+
* @fires swc-after-close - Dispatched after the tooltip finishes closing, once the transition completes.
|
|
12
17
|
*/
|
|
13
18
|
export declare abstract class TooltipBase extends SpectrumElement implements HoverControllerHost {
|
|
14
19
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tooltip.base.js","names":[],"sources":["../../../components/tooltip/Tooltip.base.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\nimport { PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\n\nimport { SpectrumElement } from '@adobe/spectrum-wc-core/element/index.js';\n\nimport {\n HoverController,\n type HoverControllerHost,\n} from '../../controllers/hover-controller/index.js';\nimport {\n fromFloatingPlacement,\n type Placement as ControllerPlacement,\n PlacementController,\n toFloatingPlacement,\n} from '../../controllers/placement-controller/index.js';\nimport {\n TOOLTIP_PLACEMENTS,\n TOOLTIP_VARIANTS,\n type TooltipPlacement,\n type TooltipVariant,\n} from './Tooltip.types.js';\n\n/**\n * Abstract base class for the Tooltip component.\n * Handles all non-rendering logic: property declarations, popover lifecycle,\n * event dispatch, trigger resolution, ARIA wiring, `HoverController` integration\n * (hover/focus open/close), and `PlacementController` integration (pixel positioning).\n *\n * @slot - Text label displayed in the tooltip.\n */\nexport abstract class TooltipBase\n extends SpectrumElement\n implements HoverControllerHost\n{\n // ──────────────────\n // SHARED API\n // ──────────────────\n\n /**\n * @internal\n *\n * All valid variants for the tooltip.\n */\n static readonly VARIANTS: readonly string[] = TOOLTIP_VARIANTS;\n\n /**\n * @internal\n *\n * All valid placement values: physical cardinals (`top`, `bottom`, `left`, `right`)\n * and logical inline values (`start`, `end`).\n */\n static readonly PLACEMENTS: readonly string[] = TOOLTIP_PLACEMENTS;\n\n /**\n * The semantic variant of the tooltip.\n *\n * @default 'neutral'\n */\n @property({ type: String, reflect: true })\n public variant: TooltipVariant = 'neutral';\n\n /**\n * Preferred placement of the tooltip relative to its trigger. This is always\n * the consumer's requested side; the resolved physical side (after any\n * viewport-driven flip) is reflected as `actual-placement`.\n *\n * @default 'top'\n */\n @property({ type: String, reflect: true })\n public placement: TooltipPlacement = 'top';\n\n /**\n * Whether the tooltip is visible.\n *\n * @default false\n */\n @property({ type: Boolean, reflect: true })\n public open: boolean = false;\n\n /**\n * The `id` of the trigger element in the same document tree root.\n */\n @property({ attribute: 'for', type: String })\n public for: string | undefined;\n\n /**\n * Explicit trigger element reference; overrides `for` when set.\n * Use when `getElementById` cannot reach the trigger, such as across a shadow boundary.\n *\n * @default null\n */\n @property({ attribute: false })\n public triggerElement: HTMLElement | null = null;\n\n /**\n * Warm-up delay in milliseconds before the tooltip opens on pointer hover.\n * Set to `0` to open immediately. Keyboard focus always opens immediately.\n *\n * @default 1500\n */\n @property({ type: Number, reflect: true })\n public delay: number = 1500;\n\n /**\n * When set, the tooltip does not respond to hover or focus events and cannot\n * be opened. `disabled` takes priority over `open`: setting `open` to `true`\n * while disabled is a no-op, and disabling an open tooltip closes it. Applies\n * in `manual` mode as well.\n *\n * @default false\n */\n @property({ type: Boolean, reflect: true })\n public disabled: boolean = false;\n\n /**\n * Suppresses automatic hover and focus wiring.\n * The consumer manages visibility via the `open` property or the popover API.\n *\n * @default false\n */\n @property({ type: Boolean, reflect: true })\n public manual: boolean = false;\n\n /**\n * Pixel gap along the placement axis between the trigger and the tooltip bubble.\n *\n * @default 4\n */\n @property({ type: Number })\n public offset: number = 4;\n\n /**\n * Slide along the trigger edge perpendicular to the placement direction, in pixels.\n *\n * @default 0\n */\n @property({ type: Number, attribute: 'cross-offset' })\n public crossOffset: number = 0;\n\n /**\n * Minimum inset from the viewport edge, in pixels, for collision detection.\n *\n * @default 12\n */\n @property({ type: Number, attribute: 'container-padding' })\n public containerPadding: number = 12;\n\n /**\n * Whether the tooltip may reposition to the opposite side when the requested\n * placement does not fit within the viewport.\n *\n * @default true\n */\n @property({ type: Boolean, attribute: 'should-flip' })\n public shouldFlip: boolean = true;\n\n /**\n * When set, the tooltip acts as the trigger's accessible name rather than its description.\n * Use for icon-only triggers where the tooltip text is the sole accessible name.\n *\n * @default false\n */\n @property({ type: Boolean, reflect: true })\n public labeling: boolean = false;\n\n // ──────────────────────\n // IMPLEMENTATION\n // ──────────────────────\n\n private readonly hoverController = new HoverController(this, {\n warmStateKey: 'swc-tooltip',\n });\n\n private readonly placementController = new PlacementController(this);\n\n // Guards dispatchAfterEvent so only the first transitionend per open/close cycle fires,\n // preventing one after event from firing for each CSS property that transitions.\n private afterEventPending = false;\n\n // Fallback timer for browsers that do not fire transitionend for\n // transition-behavior:allow-discrete discrete properties (e.g. Firefox in CI).\n // Cleared when transitionend fires or a new toggle starts.\n private afterEventFallbackTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Tracks the most recently ARIA-wired target so syncAriaRelationship can\n // remove stale references when for or triggerElement changes while open.\n private _lastWiredTrigger:\n | (Element & {\n ariaDescribedByElements: Element[] | null;\n ariaLabelledByElements: Element[] | null;\n })\n | null = null;\n\n /**\n * Returns the tip arrow element to pass to `PlacementController` so the\n * `arrow` middleware keeps the tip aligned with the trigger when the bubble\n * is shifted (e.g. via `crossOffset` or viewport `shift`).\n *\n * Returns `null` in the base class; the SWC rendering layer overrides this\n * to return the actual `.swc-Tooltip-tip` element from the shadow DOM.\n */\n protected get tipElement(): HTMLElement | null {\n return null;\n }\n\n // Reflects the browser's actual popover state. Used in updated() to reconcile\n // `open` against native light-dismiss: when the browser closes the popover\n // (e.g. Escape or outside click), `open` is synced by the toggle listener\n // without re-invoking the Popover API, and the guard prevents a redundant\n // hidePopover() (which would throw on an already-closed popover).\n private get isPopoverOpen(): boolean {\n return this.matches(':popover-open');\n }\n\n /**\n * {@link HoverControllerHost} contract. `open` is the single source of truth\n * for visibility; the controller asks to open via this method rather than\n * driving the Popover API directly. `updated()` then reconciles the popover.\n */\n public requestOpen(): void {\n this.open = true;\n }\n\n /**\n * {@link HoverControllerHost} contract. Asks the tooltip to close by setting\n * `open`; `updated()` reconciles the popover.\n */\n public requestClose(): void {\n this.open = false;\n }\n\n // Writes actual-placement to the physical side of the declared `placement`,\n // resolving logical start/end against the trigger's writing direction. Used\n // synchronously before showPopover() so @starting-style has a direction; the\n // PlacementController overwrites it with the flip-resolved side afterwards.\n private setDeclaredActualPlacement(): void {\n const trigger = this.resolveTrigger();\n const direction =\n trigger && getComputedStyle(trigger).direction === 'rtl' ? 'rtl' : 'ltr';\n const side = fromFloatingPlacement(\n toFloatingPlacement(this.placement as ControllerPlacement, direction)\n ).split('-')[0];\n this.setAttribute('actual-placement', side);\n }\n\n private startPlacement(): void {\n const trigger = this.resolveTrigger();\n if (!trigger) {\n return;\n }\n this.placementController.start(trigger, this, {\n placement: this.placement as ControllerPlacement,\n offset: this.offset,\n crossOffset: this.crossOffset,\n containerPadding: this.containerPadding,\n shouldFlip: this.shouldFlip,\n tipElement: this.tipElement ?? undefined,\n onPlacementChange: (resolvedPlacement: ControllerPlacement) => {\n this.setAttribute('actual-placement', resolvedPlacement.split('-')[0]);\n },\n });\n }\n\n private resolveTrigger(): HTMLElement | null {\n if (this.triggerElement) {\n return this.triggerElement;\n }\n if (this.for) {\n const root = this.getRootNode() as Document | ShadowRoot;\n const trigger = root.getElementById(this.for);\n if (!trigger && window.__swc?.DEBUG) {\n window.__swc.warn(\n this,\n `<${this.localName}> for=\"${this.for}\" did not resolve to an element in the current tree root. Check that the referenced id exists in the same document tree root.`,\n 'https://opensource.adobe.com/spectrum-web-components/components/tooltip/',\n { level: 'high' }\n );\n }\n return trigger;\n }\n return null;\n }\n\n // Removes this tooltip from the previously wired target's ARIA element arrays\n // and forgets the reference. Called before re-wiring and on disconnect so a\n // trigger never retains a reference to a detached tooltip node.\n private clearAriaRelationship(): void {\n if (!this._lastWiredTrigger) {\n return;\n }\n const stale = this._lastWiredTrigger;\n stale.ariaDescribedByElements = (\n stale.ariaDescribedByElements ?? []\n ).filter((el) => el !== this);\n stale.ariaLabelledByElements = (stale.ariaLabelledByElements ?? []).filter(\n (el) => el !== this\n );\n this._lastWiredTrigger = null;\n }\n\n private syncAriaRelationship(): void {\n // Remove stale references from the previously wired target before resolving\n // the new one. Handles for/triggerElement changes while the tooltip is open.\n this.clearAriaRelationship();\n\n const trigger = this.resolveTrigger();\n if (!trigger) {\n return;\n }\n const target = (trigger.shadowRoot?.querySelector('button') ??\n trigger) as Element & {\n ariaDescribedByElements: Element[] | null;\n ariaLabelledByElements: Element[] | null;\n };\n\n if (this.labeling) {\n // Remove any stale describedby reference (e.g. if labeling changed while open).\n const described = target.ariaDescribedByElements ?? [];\n target.ariaDescribedByElements = described.filter((el) => el !== this);\n\n const labelled = target.ariaLabelledByElements ?? [];\n target.ariaLabelledByElements = this.open\n ? [...labelled.filter((el) => el !== this), this]\n : labelled.filter((el) => el !== this);\n } else {\n // Remove any stale labelledby reference (e.g. if labeling changed while open).\n const labelled = target.ariaLabelledByElements ?? [];\n target.ariaLabelledByElements = labelled.filter((el) => el !== this);\n\n const described = target.ariaDescribedByElements ?? [];\n target.ariaDescribedByElements = this.open\n ? [...described.filter((el) => el !== this), this]\n : described.filter((el) => el !== this);\n }\n\n if (this.open) {\n this._lastWiredTrigger = target;\n }\n }\n\n private clearPositioningState(): void {\n // Called after the exit transition. Clearing earlier would displace the\n // tooltip to 0,0 while still visible and fading out.\n this.removeAttribute('actual-placement');\n this.style.removeProperty('translate');\n this.style.removeProperty('top');\n this.style.removeProperty('left');\n this.style.removeProperty('--swc-placement-available-width');\n this.style.removeProperty('--swc-placement-available-height');\n if (this.tipElement) {\n this.tipElement.style.removeProperty('translate');\n this.tipElement.style.removeProperty('top');\n this.tipElement.style.removeProperty('left');\n }\n }\n\n private dispatchAfterEvent(isOpen: boolean): void {\n this.dispatchEvent(\n new CustomEvent(isOpen ? 'swc-after-open' : 'swc-after-close', {\n bubbles: true,\n composed: true,\n })\n );\n if (!isOpen) {\n this.clearPositioningState();\n }\n }\n\n private readonly handleBeforeToggle = (event: Event): void => {\n const { newState } = event as ToggleEvent;\n const eventName = newState === 'open' ? 'swc-open' : 'swc-close';\n this.dispatchEvent(\n new CustomEvent(eventName, { bubbles: true, composed: true })\n );\n // Set here so the flag is set regardless of whether this.open already matches\n // newState. handleToggle exits early when open was set externally, which would\n // otherwise leave the flag unset and suppress swc-after-open / swc-after-close.\n this.afterEventPending = true;\n };\n\n private readonly handleToggle = (event: Event): void => {\n // Cancel any in-flight close fallback; a new toggle resets the cycle.\n if (this.afterEventFallbackTimer !== null) {\n clearTimeout(this.afterEventFallbackTimer);\n this.afterEventFallbackTimer = null;\n }\n const { newState } = event as ToggleEvent;\n const isOpen = newState === 'open';\n if (isOpen !== this.open) {\n this.open = isOpen;\n }\n // When no CSS transition is active, dispatch after-* immediately since transitionend will not fire.\n // transitionDuration is comma-separated when multiple properties transition (\"0s, 0s, …\"),\n // so check that every value in the list is zero rather than comparing the full string.\n const durations = getComputedStyle(this).transitionDuration.split(',');\n if (durations.every((d) => d.trim() === '0s')) {\n this.afterEventPending = false;\n this.dispatchAfterEvent(isOpen);\n } else if (!isOpen) {\n // Some browsers (e.g. Firefox in CI) do not fire transitionend for\n // transition-behavior:allow-discrete discrete properties. Start a fallback\n // so positioning is always cleared after the exit transition window.\n const maxMs = Math.max(\n 0,\n ...durations.map((d) => {\n const trimmed = d.trim();\n const value = parseFloat(trimmed);\n return trimmed.endsWith('ms') ? value : value * 1000;\n })\n );\n this.afterEventFallbackTimer = setTimeout(() => {\n this.afterEventFallbackTimer = null;\n if (this.afterEventPending) {\n this.afterEventPending = false;\n this.dispatchAfterEvent(false);\n }\n }, maxMs + 100);\n }\n };\n\n private readonly handleTransitionEnd = (event: TransitionEvent): void => {\n if (event.target !== this || !this.afterEventPending) {\n return;\n }\n if (this.afterEventFallbackTimer !== null) {\n clearTimeout(this.afterEventFallbackTimer);\n this.afterEventFallbackTimer = null;\n }\n this.afterEventPending = false;\n this.dispatchAfterEvent(this.open);\n };\n\n // Escape-to-close. Registered on `document` only while open (see updated()),\n // not for the whole connected lifetime: the tooltip is non-interactive and\n // never receives focus, so a host-level keydown would not fire, and scoping to\n // the open state keeps at most one listener active at a time (popover=\"auto\"\n // permits one open tooltip). Under `popover=\"auto\"` this duplicates the\n // browser's native Escape light-dismiss; it is kept as a mode-independent\n // handler so a `manual` tooltip (which gets no native light-dismiss, e.g. one\n // that coexists with an open popover) still closes on Escape.\n private readonly handleKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Escape' && this.open) {\n this.open = false;\n }\n };\n\n protected override willUpdate(changedProperties: PropertyValues): void {\n super.willUpdate(changedProperties);\n // `disabled` takes priority over `open`: a disabled tooltip cannot be shown,\n // regardless of how `open` became true (set programmatically, or `disabled`\n // toggled on while already open). Normalizing here — before render and\n // before updated() acts on `open` — keeps `open` and visibility consistent\n // and prevents the `open` attribute from briefly reflecting `true`. Hover\n // and focus opening are separately blocked by HoverController's guards.\n if (this.disabled && this.open) {\n this.open = false;\n }\n }\n\n protected override updated(changedProperties: PropertyValues): void {\n super.updated(changedProperties);\n if (changedProperties.has('offset')) {\n this.style.setProperty(\n '--_swc-tooltip-animation-distance',\n `${this.offset}px`\n );\n }\n if (\n changedProperties.has('open') ||\n changedProperties.has('labeling') ||\n changedProperties.has('for') ||\n changedProperties.has('triggerElement')\n ) {\n this.syncAriaRelationship();\n }\n if (\n changedProperties.has('for') ||\n changedProperties.has('triggerElement')\n ) {\n this.hoverController.setTarget(this.resolveTrigger());\n }\n\n // Placement controller: start when opening, stop when closing, restart\n // when positioning options or the trigger change while already open.\n const openChanged = changedProperties.has('open');\n if (openChanged) {\n if (this.open) {\n // Register Escape handling only while open; removed on close below.\n document.addEventListener('keydown', this.handleKeyDown);\n // Set actual-placement to the declared side synchronously, before\n // showPopover(). @starting-style is evaluated by the browser the moment\n // the popover enters the top layer, so the direction-bearing attribute\n // must already be present or the entrance animation has no direction to\n // animate from. PlacementController writes actual-placement again from\n // onPlacementChange, but that is async (it awaits document.fonts.ready),\n // so it lands after showPopover() — too late for @starting-style. The\n // popover is display:none until shown and therefore unmeasurable, so a\n // viewport flip cannot be known here; on a flip the resolved side\n // arrives from onPlacementChange and the slide direction corrects then.\n this.setDeclaredActualPlacement();\n this.startPlacement();\n if (this.open !== this.isPopoverOpen) {\n this.showPopover();\n }\n } else {\n document.removeEventListener('keydown', this.handleKeyDown);\n if (this.open !== this.isPopoverOpen) {\n this.hidePopover();\n }\n // Stop autoUpdate immediately so positioning stops tracking the trigger\n // during the exit transition. clearPositioningState() runs after the\n // transition completes via dispatchAfterEvent.\n this.placementController.stop();\n }\n } else if (this.open) {\n const placementOptsChanged =\n changedProperties.has('placement') ||\n changedProperties.has('offset') ||\n changedProperties.has('crossOffset') ||\n changedProperties.has('containerPadding') ||\n changedProperties.has('shouldFlip') ||\n changedProperties.has('for') ||\n changedProperties.has('triggerElement');\n if (placementOptsChanged) {\n this.startPlacement();\n }\n }\n }\n\n public override connectedCallback(): void {\n super.connectedCallback();\n this.setAttribute('role', 'tooltip');\n this.setAttribute('popover', 'auto');\n this.addEventListener('beforetoggle', this.handleBeforeToggle);\n this.addEventListener('toggle', this.handleToggle);\n this.addEventListener('transitionend', this.handleTransitionEnd);\n }\n\n public override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('beforetoggle', this.handleBeforeToggle);\n this.removeEventListener('toggle', this.handleToggle);\n this.removeEventListener('transitionend', this.handleTransitionEnd);\n // Defensive: the keydown listener is normally removed on close, but a\n // tooltip disconnected while open would still have it registered.\n document.removeEventListener('keydown', this.handleKeyDown);\n if (this.afterEventFallbackTimer !== null) {\n clearTimeout(this.afterEventFallbackTimer);\n this.afterEventFallbackTimer = null;\n }\n // If the tooltip is removed while open, the trigger would otherwise retain a\n // reference to this now-detached node in its ARIA element arrays.\n this.clearAriaRelationship();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Tooltip.base.js","names":[],"sources":["../../../components/tooltip/Tooltip.base.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\nimport { PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\n\nimport { SpectrumElement } from '@adobe/spectrum-wc-core/element/index.js';\n\nimport {\n HoverController,\n type HoverControllerHost,\n} from '../../controllers/hover-controller/index.js';\nimport {\n fromFloatingPlacement,\n type Placement as ControllerPlacement,\n PlacementController,\n toFloatingPlacement,\n} from '../../controllers/placement-controller/index.js';\nimport {\n TOOLTIP_PLACEMENTS,\n TOOLTIP_VARIANTS,\n type TooltipPlacement,\n type TooltipVariant,\n} from './Tooltip.types.js';\n\n/**\n * Abstract base class for the Tooltip component.\n * Handles all non-rendering logic: property declarations, popover lifecycle,\n * event dispatch, trigger resolution, ARIA wiring, `HoverController` integration\n * (hover/focus open/close), and `PlacementController` integration (pixel positioning).\n *\n * @slot - Text label displayed in the tooltip.\n *\n * @fires swc-open - Dispatched when the tooltip begins to open, before the transition plays.\n * @fires swc-close - Dispatched when the tooltip begins to close, before the transition plays.\n * @fires swc-after-open - Dispatched after the tooltip finishes opening, once the transition completes.\n * @fires swc-after-close - Dispatched after the tooltip finishes closing, once the transition completes.\n */\nexport abstract class TooltipBase\n extends SpectrumElement\n implements HoverControllerHost\n{\n // ──────────────────\n // SHARED API\n // ──────────────────\n\n /**\n * @internal\n *\n * All valid variants for the tooltip.\n */\n static readonly VARIANTS: readonly string[] = TOOLTIP_VARIANTS;\n\n /**\n * @internal\n *\n * All valid placement values: physical cardinals (`top`, `bottom`, `left`, `right`)\n * and logical inline values (`start`, `end`).\n */\n static readonly PLACEMENTS: readonly string[] = TOOLTIP_PLACEMENTS;\n\n /**\n * The semantic variant of the tooltip.\n *\n * @default 'neutral'\n */\n @property({ type: String, reflect: true })\n public variant: TooltipVariant = 'neutral';\n\n /**\n * Preferred placement of the tooltip relative to its trigger. This is always\n * the consumer's requested side; the resolved physical side (after any\n * viewport-driven flip) is reflected as `actual-placement`.\n *\n * @default 'top'\n */\n @property({ type: String, reflect: true })\n public placement: TooltipPlacement = 'top';\n\n /**\n * Whether the tooltip is visible.\n *\n * @default false\n */\n @property({ type: Boolean, reflect: true })\n public open: boolean = false;\n\n /**\n * The `id` of the trigger element in the same document tree root.\n */\n @property({ attribute: 'for', type: String })\n public for: string | undefined;\n\n /**\n * Explicit trigger element reference; overrides `for` when set.\n * Use when `getElementById` cannot reach the trigger, such as across a shadow boundary.\n *\n * @default null\n */\n @property({ attribute: false })\n public triggerElement: HTMLElement | null = null;\n\n /**\n * Warm-up delay in milliseconds before the tooltip opens on pointer hover.\n * Set to `0` to open immediately. Keyboard focus always opens immediately.\n *\n * @default 1500\n */\n @property({ type: Number, reflect: true })\n public delay: number = 1500;\n\n /**\n * When set, the tooltip does not respond to hover or focus events and cannot\n * be opened. `disabled` takes priority over `open`: setting `open` to `true`\n * while disabled is a no-op, and disabling an open tooltip closes it. Applies\n * in `manual` mode as well.\n *\n * @default false\n */\n @property({ type: Boolean, reflect: true })\n public disabled: boolean = false;\n\n /**\n * Suppresses automatic hover and focus wiring.\n * The consumer manages visibility via the `open` property or the popover API.\n *\n * @default false\n */\n @property({ type: Boolean, reflect: true })\n public manual: boolean = false;\n\n /**\n * Pixel gap along the placement axis between the trigger and the tooltip bubble.\n *\n * @default 4\n */\n @property({ type: Number })\n public offset: number = 4;\n\n /**\n * Slide along the trigger edge perpendicular to the placement direction, in pixels.\n *\n * @default 0\n */\n @property({ type: Number, attribute: 'cross-offset' })\n public crossOffset: number = 0;\n\n /**\n * Minimum inset from the viewport edge, in pixels, for collision detection.\n *\n * @default 12\n */\n @property({ type: Number, attribute: 'container-padding' })\n public containerPadding: number = 12;\n\n /**\n * Whether the tooltip may reposition to the opposite side when the requested\n * placement does not fit within the viewport.\n *\n * @default true\n */\n @property({ type: Boolean, attribute: 'should-flip' })\n public shouldFlip: boolean = true;\n\n /**\n * When set, the tooltip acts as the trigger's accessible name rather than its description.\n * Use for icon-only triggers where the tooltip text is the sole accessible name.\n *\n * @default false\n */\n @property({ type: Boolean, reflect: true })\n public labeling: boolean = false;\n\n // ──────────────────────\n // IMPLEMENTATION\n // ──────────────────────\n\n private readonly hoverController = new HoverController(this, {\n warmStateKey: 'swc-tooltip',\n });\n\n private readonly placementController = new PlacementController(this);\n\n // Guards dispatchAfterEvent so only the first transitionend per open/close cycle fires,\n // preventing one after event from firing for each CSS property that transitions.\n private afterEventPending = false;\n\n // Fallback timer for browsers that do not fire transitionend for\n // transition-behavior:allow-discrete discrete properties (e.g. Firefox in CI).\n // Cleared when transitionend fires or a new toggle starts.\n private afterEventFallbackTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Tracks the most recently ARIA-wired target so syncAriaRelationship can\n // remove stale references when for or triggerElement changes while open.\n private _lastWiredTrigger:\n | (Element & {\n ariaDescribedByElements: Element[] | null;\n ariaLabelledByElements: Element[] | null;\n })\n | null = null;\n\n /**\n * Returns the tip arrow element to pass to `PlacementController` so the\n * `arrow` middleware keeps the tip aligned with the trigger when the bubble\n * is shifted (e.g. via `crossOffset` or viewport `shift`).\n *\n * Returns `null` in the base class; the SWC rendering layer overrides this\n * to return the actual `.swc-Tooltip-tip` element from the shadow DOM.\n */\n protected get tipElement(): HTMLElement | null {\n return null;\n }\n\n // Reflects the browser's actual popover state. Used in updated() to reconcile\n // `open` against native light-dismiss: when the browser closes the popover\n // (e.g. Escape or outside click), `open` is synced by the toggle listener\n // without re-invoking the Popover API, and the guard prevents a redundant\n // hidePopover() (which would throw on an already-closed popover).\n private get isPopoverOpen(): boolean {\n return this.matches(':popover-open');\n }\n\n /**\n * {@link HoverControllerHost} contract. `open` is the single source of truth\n * for visibility; the controller asks to open via this method rather than\n * driving the Popover API directly. `updated()` then reconciles the popover.\n */\n public requestOpen(): void {\n this.open = true;\n }\n\n /**\n * {@link HoverControllerHost} contract. Asks the tooltip to close by setting\n * `open`; `updated()` reconciles the popover.\n */\n public requestClose(): void {\n this.open = false;\n }\n\n // Writes actual-placement to the physical side of the declared `placement`,\n // resolving logical start/end against the trigger's writing direction. Used\n // synchronously before showPopover() so @starting-style has a direction; the\n // PlacementController overwrites it with the flip-resolved side afterwards.\n private setDeclaredActualPlacement(): void {\n const trigger = this.resolveTrigger();\n const direction =\n trigger && getComputedStyle(trigger).direction === 'rtl' ? 'rtl' : 'ltr';\n const side = fromFloatingPlacement(\n toFloatingPlacement(this.placement as ControllerPlacement, direction)\n ).split('-')[0];\n this.setAttribute('actual-placement', side);\n }\n\n private startPlacement(): void {\n const trigger = this.resolveTrigger();\n if (!trigger) {\n return;\n }\n this.placementController.start(trigger, this, {\n placement: this.placement as ControllerPlacement,\n offset: this.offset,\n crossOffset: this.crossOffset,\n containerPadding: this.containerPadding,\n shouldFlip: this.shouldFlip,\n tipElement: this.tipElement ?? undefined,\n onPlacementChange: (resolvedPlacement: ControllerPlacement) => {\n this.setAttribute('actual-placement', resolvedPlacement.split('-')[0]);\n },\n });\n }\n\n private resolveTrigger(): HTMLElement | null {\n if (this.triggerElement) {\n return this.triggerElement;\n }\n if (this.for) {\n const root = this.getRootNode() as Document | ShadowRoot;\n const trigger = root.getElementById(this.for);\n if (!trigger && window.__swc?.DEBUG) {\n window.__swc.warn(\n this,\n `<${this.localName}> for=\"${this.for}\" did not resolve to an element in the current tree root. Check that the referenced id exists in the same document tree root.`,\n 'https://opensource.adobe.com/spectrum-web-components/components/tooltip/',\n { level: 'high' }\n );\n }\n return trigger;\n }\n return null;\n }\n\n // Removes this tooltip from the previously wired target's ARIA element arrays\n // and forgets the reference. Called before re-wiring and on disconnect so a\n // trigger never retains a reference to a detached tooltip node.\n private clearAriaRelationship(): void {\n if (!this._lastWiredTrigger) {\n return;\n }\n const stale = this._lastWiredTrigger;\n stale.ariaDescribedByElements = (\n stale.ariaDescribedByElements ?? []\n ).filter((el) => el !== this);\n stale.ariaLabelledByElements = (stale.ariaLabelledByElements ?? []).filter(\n (el) => el !== this\n );\n this._lastWiredTrigger = null;\n }\n\n private syncAriaRelationship(): void {\n // Remove stale references from the previously wired target before resolving\n // the new one. Handles for/triggerElement changes while the tooltip is open.\n this.clearAriaRelationship();\n\n const trigger = this.resolveTrigger();\n if (!trigger) {\n return;\n }\n const target = (trigger.shadowRoot?.querySelector('button') ??\n trigger) as Element & {\n ariaDescribedByElements: Element[] | null;\n ariaLabelledByElements: Element[] | null;\n };\n\n if (this.labeling) {\n // Remove any stale describedby reference (e.g. if labeling changed while open).\n const described = target.ariaDescribedByElements ?? [];\n target.ariaDescribedByElements = described.filter((el) => el !== this);\n\n const labelled = target.ariaLabelledByElements ?? [];\n target.ariaLabelledByElements = this.open\n ? [...labelled.filter((el) => el !== this), this]\n : labelled.filter((el) => el !== this);\n } else {\n // Remove any stale labelledby reference (e.g. if labeling changed while open).\n const labelled = target.ariaLabelledByElements ?? [];\n target.ariaLabelledByElements = labelled.filter((el) => el !== this);\n\n const described = target.ariaDescribedByElements ?? [];\n target.ariaDescribedByElements = this.open\n ? [...described.filter((el) => el !== this), this]\n : described.filter((el) => el !== this);\n }\n\n if (this.open) {\n this._lastWiredTrigger = target;\n }\n }\n\n private clearPositioningState(): void {\n // Called after the exit transition. Clearing earlier would displace the\n // tooltip to 0,0 while still visible and fading out.\n this.removeAttribute('actual-placement');\n this.style.removeProperty('translate');\n this.style.removeProperty('top');\n this.style.removeProperty('left');\n this.style.removeProperty('--swc-placement-available-width');\n this.style.removeProperty('--swc-placement-available-height');\n if (this.tipElement) {\n this.tipElement.style.removeProperty('translate');\n this.tipElement.style.removeProperty('top');\n this.tipElement.style.removeProperty('left');\n }\n }\n\n private dispatchAfterEvent(isOpen: boolean): void {\n this.dispatchEvent(\n new CustomEvent(isOpen ? 'swc-after-open' : 'swc-after-close', {\n bubbles: true,\n composed: true,\n })\n );\n if (!isOpen) {\n this.clearPositioningState();\n }\n }\n\n private readonly handleBeforeToggle = (event: Event): void => {\n const { newState } = event as ToggleEvent;\n const eventName = newState === 'open' ? 'swc-open' : 'swc-close';\n this.dispatchEvent(\n new CustomEvent(eventName, { bubbles: true, composed: true })\n );\n // Set here so the flag is set regardless of whether this.open already matches\n // newState. handleToggle exits early when open was set externally, which would\n // otherwise leave the flag unset and suppress swc-after-open / swc-after-close.\n this.afterEventPending = true;\n };\n\n private readonly handleToggle = (event: Event): void => {\n // Cancel any in-flight close fallback; a new toggle resets the cycle.\n if (this.afterEventFallbackTimer !== null) {\n clearTimeout(this.afterEventFallbackTimer);\n this.afterEventFallbackTimer = null;\n }\n const { newState } = event as ToggleEvent;\n const isOpen = newState === 'open';\n if (isOpen !== this.open) {\n this.open = isOpen;\n }\n // When no CSS transition is active, dispatch after-* immediately since transitionend will not fire.\n // transitionDuration is comma-separated when multiple properties transition (\"0s, 0s, …\"),\n // so check that every value in the list is zero rather than comparing the full string.\n const durations = getComputedStyle(this).transitionDuration.split(',');\n if (durations.every((d) => d.trim() === '0s')) {\n this.afterEventPending = false;\n this.dispatchAfterEvent(isOpen);\n } else if (!isOpen) {\n // Some browsers (e.g. Firefox in CI) do not fire transitionend for\n // transition-behavior:allow-discrete discrete properties. Start a fallback\n // so positioning is always cleared after the exit transition window.\n const maxMs = Math.max(\n 0,\n ...durations.map((d) => {\n const trimmed = d.trim();\n const value = parseFloat(trimmed);\n return trimmed.endsWith('ms') ? value : value * 1000;\n })\n );\n this.afterEventFallbackTimer = setTimeout(() => {\n this.afterEventFallbackTimer = null;\n if (this.afterEventPending) {\n this.afterEventPending = false;\n this.dispatchAfterEvent(false);\n }\n }, maxMs + 100);\n }\n };\n\n private readonly handleTransitionEnd = (event: TransitionEvent): void => {\n if (event.target !== this || !this.afterEventPending) {\n return;\n }\n if (this.afterEventFallbackTimer !== null) {\n clearTimeout(this.afterEventFallbackTimer);\n this.afterEventFallbackTimer = null;\n }\n this.afterEventPending = false;\n this.dispatchAfterEvent(this.open);\n };\n\n // Escape-to-close. Registered on `document` only while open (see updated()),\n // not for the whole connected lifetime: the tooltip is non-interactive and\n // never receives focus, so a host-level keydown would not fire, and scoping to\n // the open state keeps at most one listener active at a time (popover=\"auto\"\n // permits one open tooltip). Under `popover=\"auto\"` this duplicates the\n // browser's native Escape light-dismiss; it is kept as a mode-independent\n // handler so a `manual` tooltip (which gets no native light-dismiss, e.g. one\n // that coexists with an open popover) still closes on Escape.\n private readonly handleKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Escape' && this.open) {\n this.open = false;\n }\n };\n\n protected override willUpdate(changedProperties: PropertyValues): void {\n super.willUpdate(changedProperties);\n // `disabled` takes priority over `open`: a disabled tooltip cannot be shown,\n // regardless of how `open` became true (set programmatically, or `disabled`\n // toggled on while already open). Normalizing here — before render and\n // before updated() acts on `open` — keeps `open` and visibility consistent\n // and prevents the `open` attribute from briefly reflecting `true`. Hover\n // and focus opening are separately blocked by HoverController's guards.\n if (this.disabled && this.open) {\n this.open = false;\n }\n }\n\n protected override updated(changedProperties: PropertyValues): void {\n super.updated(changedProperties);\n if (changedProperties.has('offset')) {\n this.style.setProperty(\n '--_swc-tooltip-animation-distance',\n `${this.offset}px`\n );\n }\n if (\n changedProperties.has('open') ||\n changedProperties.has('labeling') ||\n changedProperties.has('for') ||\n changedProperties.has('triggerElement')\n ) {\n this.syncAriaRelationship();\n }\n if (\n changedProperties.has('for') ||\n changedProperties.has('triggerElement')\n ) {\n this.hoverController.setTarget(this.resolveTrigger());\n }\n\n // Placement controller: start when opening, stop when closing, restart\n // when positioning options or the trigger change while already open.\n const openChanged = changedProperties.has('open');\n if (openChanged) {\n if (this.open) {\n // Register Escape handling only while open; removed on close below.\n document.addEventListener('keydown', this.handleKeyDown);\n // Set actual-placement to the declared side synchronously, before\n // showPopover(). @starting-style is evaluated by the browser the moment\n // the popover enters the top layer, so the direction-bearing attribute\n // must already be present or the entrance animation has no direction to\n // animate from. PlacementController writes actual-placement again from\n // onPlacementChange, but that is async (it awaits document.fonts.ready),\n // so it lands after showPopover() — too late for @starting-style. The\n // popover is display:none until shown and therefore unmeasurable, so a\n // viewport flip cannot be known here; on a flip the resolved side\n // arrives from onPlacementChange and the slide direction corrects then.\n this.setDeclaredActualPlacement();\n this.startPlacement();\n if (this.open !== this.isPopoverOpen) {\n this.showPopover();\n }\n } else {\n document.removeEventListener('keydown', this.handleKeyDown);\n if (this.open !== this.isPopoverOpen) {\n this.hidePopover();\n }\n // Stop autoUpdate immediately so positioning stops tracking the trigger\n // during the exit transition. clearPositioningState() runs after the\n // transition completes via dispatchAfterEvent.\n this.placementController.stop();\n }\n } else if (this.open) {\n const placementOptsChanged =\n changedProperties.has('placement') ||\n changedProperties.has('offset') ||\n changedProperties.has('crossOffset') ||\n changedProperties.has('containerPadding') ||\n changedProperties.has('shouldFlip') ||\n changedProperties.has('for') ||\n changedProperties.has('triggerElement');\n if (placementOptsChanged) {\n this.startPlacement();\n }\n }\n }\n\n public override connectedCallback(): void {\n super.connectedCallback();\n this.setAttribute('role', 'tooltip');\n this.setAttribute('popover', 'auto');\n this.addEventListener('beforetoggle', this.handleBeforeToggle);\n this.addEventListener('toggle', this.handleToggle);\n this.addEventListener('transitionend', this.handleTransitionEnd);\n }\n\n public override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('beforetoggle', this.handleBeforeToggle);\n this.removeEventListener('toggle', this.handleToggle);\n this.removeEventListener('transitionend', this.handleTransitionEnd);\n // Defensive: the keydown listener is normally removed on close, but a\n // tooltip disconnected while open would still have it registered.\n document.removeEventListener('keydown', this.handleKeyDown);\n if (this.afterEventFallbackTimer !== null) {\n clearTimeout(this.afterEventFallbackTimer);\n this.afterEventFallbackTimer = null;\n }\n // If the tooltip is removed while open, the trigger would otherwise retain a\n // reference to this now-detached node in its ARIA element arrays.\n this.clearAriaRelationship();\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import "./src/color-controller.js";
|
|
1
|
+
import { ColorController as e } from "./src/color-controller.js";
|
|
2
|
+
export { e as ColorController };
|
|
@@ -20,3 +20,4 @@ export { LanguageResolutionController, languageResolverUpdatedSymbol, } from './
|
|
|
20
20
|
export { PageScrollLockController } from './page-scroll-lock.js';
|
|
21
21
|
export { PendingController, type PendingControllerHost, type PendingControllerOptions, } from './pending-controller/index.js';
|
|
22
22
|
export { ALL_PLACEMENTS, fromFloatingPlacement, PlacementController, toFloatingPlacement, type Placement, type PlacementOptions, type VirtualTrigger, } from './placement-controller/index.js';
|
|
23
|
+
export { SlotAttributePropagationController, type SlotAttributePropagationControllerOptions, } from './slot-attribute-propagation-controller/index.js';
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { SlotAttributePropagationController as e } from "./slot-attribute-propagation-controller/src/slot-attribute-propagation-controller.js";
|
|
2
|
+
import { HoverController as t } from "./hover-controller/src/hover-controller.js";
|
|
3
|
+
import { fromFloatingPlacement as n, toFloatingPlacement as r } from "./placement-controller/src/placement-conversion.js";
|
|
4
|
+
import { ALL_PLACEMENTS as i } from "./placement-controller/src/types.js";
|
|
5
|
+
import { PlacementController as a } from "./placement-controller/src/placement-controller.js";
|
|
5
6
|
import "./placement-controller/index.js";
|
|
6
|
-
import { LanguageResolutionController as
|
|
7
|
-
import { PendingController as
|
|
7
|
+
import { LanguageResolutionController as o, languageResolverUpdatedSymbol as s } from "./language-resolution.js";
|
|
8
|
+
import { PendingController as c } from "./pending-controller/src/pending-controller.js";
|
|
8
9
|
import "./pending-controller/index.js";
|
|
9
|
-
import { ColorController as
|
|
10
|
+
import { ColorController as l } from "./color-controller/src/color-controller.js";
|
|
10
11
|
import "./color-controller/index.js";
|
|
11
|
-
import { FocusgroupNavigationController as
|
|
12
|
-
import { LiveSelectionController as
|
|
13
|
-
import { PageScrollLockController as
|
|
14
|
-
export {
|
|
12
|
+
import { FocusgroupNavigationController as u, focusgroupNavigationActiveChange as d } from "./focusgroup-navigation-controller/src/focusgroup-navigation-controller.js";
|
|
13
|
+
import { LiveSelectionController as f } from "./live-selection-controller/src/live-selection-controller.js";
|
|
14
|
+
import { PageScrollLockController as p } from "./page-scroll-lock.js";
|
|
15
|
+
export { i as ALL_PLACEMENTS, l as ColorController, u as FocusgroupNavigationController, t as HoverController, o as LanguageResolutionController, f as LiveSelectionController, p as PageScrollLockController, c as PendingController, a as PlacementController, e as SlotAttributePropagationController, d as focusgroupNavigationActiveChange, n as fromFloatingPlacement, s as languageResolverUpdatedSymbol, r as toFloatingPlacement };
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import "./src/pending-controller.js";
|
|
1
|
+
import { PendingController as e } from "./src/pending-controller.js";
|
|
2
|
+
export { e as PendingController };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import "./src/placement-conversion.js";
|
|
2
|
-
import "./src/types.js";
|
|
3
|
-
import "./src/placement-controller.js";
|
|
1
|
+
import { fromFloatingPlacement as e, toFloatingPlacement as t } from "./src/placement-conversion.js";
|
|
2
|
+
import { ALL_PLACEMENTS as n } from "./src/types.js";
|
|
3
|
+
import { PlacementController as r } from "./src/placement-controller.js";
|
|
4
|
+
export { n as ALL_PLACEMENTS, r as PlacementController, e as fromFloatingPlacement, t as toFloatingPlacement };
|
|
@@ -2,8 +2,12 @@ import { ReactiveController, ReactiveElement } from 'lit';
|
|
|
2
2
|
export interface SlotAttributePropagationControllerOptions {
|
|
3
3
|
/** The attribute name to propagate to assigned elements. */
|
|
4
4
|
attribute: string;
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Returns the current value to propagate. Return `null` to remove the
|
|
7
|
+
* attribute from assigned elements, for attributes that are only
|
|
8
|
+
* sometimes present on the host.
|
|
9
|
+
*/
|
|
10
|
+
getValue: () => string | null;
|
|
7
11
|
/** Named slot to target. Omit for the default (unnamed) slot. */
|
|
8
12
|
slotName?: string;
|
|
9
13
|
/**
|
|
@@ -20,6 +24,10 @@ export interface SlotAttributePropagationControllerOptions {
|
|
|
20
24
|
* value returned by `getValue` changes. Call `propagate()` from the host's
|
|
21
25
|
* `slotchange` handler to cover elements inserted after the first render.
|
|
22
26
|
*
|
|
27
|
+
* `getValue` may return `null` for attributes that are only sometimes present
|
|
28
|
+
* on the host; the controller removes the attribute from assigned elements
|
|
29
|
+
* rather than setting it to an empty string.
|
|
30
|
+
*
|
|
23
31
|
* @example
|
|
24
32
|
* ```ts
|
|
25
33
|
* class MyBase extends SpectrumElement {
|
|
@@ -46,8 +54,10 @@ export declare class SlotAttributePropagationController implements ReactiveContr
|
|
|
46
54
|
hostDisconnected(): void;
|
|
47
55
|
hostUpdated(): void;
|
|
48
56
|
/**
|
|
49
|
-
* Propagates the current value to all matching assigned elements
|
|
50
|
-
*
|
|
57
|
+
* Propagates the current value to all matching assigned elements
|
|
58
|
+
* immediately, updating the value tracked for the `hostUpdated()` no-op
|
|
59
|
+
* guard so it isn't repeated once the pending update runs. Call this from
|
|
60
|
+
* the host's `slotchange` event handler.
|
|
51
61
|
*/
|
|
52
62
|
propagate(): void;
|
|
53
63
|
private _propagateToSlot;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region controllers/slot-attribute-propagation/src/slot-attribute-propagation-controller.ts
|
|
1
|
+
//#region controllers/slot-attribute-propagation-controller/src/slot-attribute-propagation-controller.ts
|
|
2
2
|
var e = class {
|
|
3
3
|
constructor(e, t) {
|
|
4
4
|
this._host = e, this._options = t, e.addController(this);
|
|
@@ -8,7 +8,7 @@ var e = class {
|
|
|
8
8
|
}
|
|
9
9
|
hostUpdated() {
|
|
10
10
|
let e = this._options.getValue();
|
|
11
|
-
e !== this._previousValue &&
|
|
11
|
+
e !== this._previousValue && this._propagateToSlot(e);
|
|
12
12
|
}
|
|
13
13
|
propagate() {
|
|
14
14
|
this._propagateToSlot(this._options.getValue());
|
|
@@ -16,8 +16,9 @@ var e = class {
|
|
|
16
16
|
_propagateToSlot(e) {
|
|
17
17
|
let t = this._resolveSlot();
|
|
18
18
|
if (!t) return;
|
|
19
|
+
this._previousValue = e;
|
|
19
20
|
let n = t.assignedElements({ flatten: !0 }), r = this._options.selector ? n.filter((e) => e.matches(this._options.selector)) : n;
|
|
20
|
-
for (let t of r) t.setAttribute(this._options.attribute, e);
|
|
21
|
+
for (let t of r) e === null ? t.removeAttribute(this._options.attribute) : t.setAttribute(this._options.attribute, e);
|
|
21
22
|
}
|
|
22
23
|
_resolveSlot() {
|
|
23
24
|
let e = this._host.shadowRoot;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slot-attribute-propagation-controller.js","names":[],"sources":["../../../../controllers/slot-attribute-propagation-controller/src/slot-attribute-propagation-controller.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport type { ReactiveController, ReactiveElement } from 'lit';\n\n// ─────────────────────────\n// TYPES\n// ─────────────────────────\n\nexport interface SlotAttributePropagationControllerOptions {\n /** The attribute name to propagate to assigned elements. */\n attribute: string;\n\n /**\n * Returns the current value to propagate. Return `null` to remove the\n * attribute from assigned elements, for attributes that are only\n * sometimes present on the host.\n */\n getValue: () => string | null;\n /** Named slot to target. Omit for the default (unnamed) slot. */\n slotName?: string;\n\n /**\n * CSS selector used to filter assigned elements before propagating.\n * Omit to propagate to all assigned elements.\n */\n selector?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// CONTROLLER\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * A Lit {@link ReactiveController} that propagates an attribute from the host\n * element to elements assigned to one of its slots.\n *\n * The controller automatically re-propagates in `hostUpdated()` whenever the\n * value returned by `getValue` changes. Call `propagate()` from the host's\n * `slotchange` handler to cover elements inserted after the first render.\n *\n * `getValue` may return `null` for attributes that are only sometimes present\n * on the host; the controller removes the attribute from assigned elements\n * rather than setting it to an empty string.\n *\n * @example\n * ```ts\n * class MyBase extends SpectrumElement {\n * @property({ type: String, reflect: true })\n * public size: MySize = 'm';\n *\n * private readonly _sizePropagation = new SlotAttributePropagationController(this, {\n * attribute: 'size',\n * getValue: () => this.size,\n * slotName: 'actions',\n * });\n *\n * protected handleActionsSlotChange(): void {\n * this._sizePropagation.propagate();\n * }\n * }\n * ```\n */\nexport class SlotAttributePropagationController implements ReactiveController {\n private readonly _host: ReactiveElement;\n private readonly _options: SlotAttributePropagationControllerOptions;\n private _previousValue?: string | null;\n\n constructor(\n host: ReactiveElement,\n options: SlotAttributePropagationControllerOptions\n ) {\n this._host = host;\n this._options = options;\n host.addController(this);\n }\n\n public hostDisconnected(): void {\n this._previousValue = undefined;\n }\n\n public hostUpdated(): void {\n const value = this._options.getValue();\n if (value !== this._previousValue) {\n this._propagateToSlot(value);\n }\n }\n\n /**\n * Propagates the current value to all matching assigned elements\n * immediately, updating the value tracked for the `hostUpdated()` no-op\n * guard so it isn't repeated once the pending update runs. Call this from\n * the host's `slotchange` event handler.\n */\n public propagate(): void {\n this._propagateToSlot(this._options.getValue());\n }\n\n private _propagateToSlot(value: string | null): void {\n const slot = this._resolveSlot();\n if (!slot) {\n return;\n }\n // Tracked here (not just in hostUpdated()) so a propagate() call from a\n // slotchange handler also updates the no-op guard; otherwise a\n // subsequently-scheduled hostUpdated() with the same value would repeat\n // the same setAttribute()/removeAttribute() sweep over every target. Set\n // only once the slot resolves: if a call (from either hostUpdated() or\n // propagate()) can't resolve the slot yet, the value must not be marked\n // as applied, or a slot that resolves later with an unchanged value\n // would be silently skipped forever.\n this._previousValue = value;\n const assigned = slot.assignedElements({ flatten: true });\n const targets = this._options.selector\n ? assigned.filter((el) => el.matches(this._options.selector!))\n : assigned;\n for (const el of targets) {\n if (value === null) {\n el.removeAttribute(this._options.attribute);\n } else {\n el.setAttribute(this._options.attribute, value);\n }\n }\n }\n\n private _resolveSlot(): HTMLSlotElement | null {\n const root = this._host.shadowRoot;\n if (!root) {\n return null;\n }\n const { slotName } = this._options;\n return slotName\n ? root.querySelector<HTMLSlotElement>(`slot[name=\"${slotName}\"]`)\n : root.querySelector<HTMLSlotElement>('slot:not([name])');\n }\n}\n"],"mappings":";AAwEA,IAAa,IAAb,MAA8E;CAK5E,YACE,GACA,GACA;AAGA,EAFA,KAAK,QAAQ,GACb,KAAK,WAAW,GAChB,EAAK,cAAc,KAAK;;CAG1B,mBAAgC;AAC9B,OAAK,iBAAiB,KAAA;;CAGxB,cAA2B;EACzB,IAAM,IAAQ,KAAK,SAAS,UAAU;AACtC,EAAI,MAAU,KAAK,kBACjB,KAAK,iBAAiB,EAAM;;CAUhC,YAAyB;AACvB,OAAK,iBAAiB,KAAK,SAAS,UAAU,CAAC;;CAGjD,iBAAyB,GAA4B;EACnD,IAAM,IAAO,KAAK,cAAc;AAChC,MAAI,CAAC,EACH;AAUF,OAAK,iBAAiB;EACtB,IAAM,IAAW,EAAK,iBAAiB,EAAE,SAAS,IAAM,CAAC,EACnD,IAAU,KAAK,SAAS,WAC1B,EAAS,QAAQ,MAAO,EAAG,QAAQ,KAAK,SAAS,SAAU,CAAC,GAC5D;AACJ,OAAK,IAAM,KAAM,EACf,CAAI,MAAU,OACZ,EAAG,gBAAgB,KAAK,SAAS,UAAU,GAE3C,EAAG,aAAa,KAAK,SAAS,WAAW,EAAM;;CAKrD,eAA+C;EAC7C,IAAM,IAAO,KAAK,MAAM;AACxB,MAAI,CAAC,EACH,QAAO;EAET,IAAM,EAAE,gBAAa,KAAK;AAC1B,SAAO,IACH,EAAK,cAA+B,cAAc,EAAS,IAAI,GAC/D,EAAK,cAA+B,mBAAmB"}
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
/**
|
|
13
13
|
* The version of the 2nd-gen Spectrum Web Components library.
|
|
14
14
|
*/
|
|
15
|
-
export declare const version = "0.
|
|
15
|
+
export declare const version = "2.0.0-beta.1";
|
|
16
16
|
/**
|
|
17
17
|
* The version of the core base package.
|
|
18
18
|
*/
|
|
19
|
-
export declare const coreVersion = "0.
|
|
19
|
+
export declare const coreVersion = "2.0.0-beta.1";
|
package/dist/element/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","names":[],"sources":["../../element/version.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n// Auto-generated from 2nd-gen/packages/core/package.json\n// Generation: 2nd-gen\n// DO NOT EDIT - This file is generated by scripts/generate-versions.js\n\n/**\n * The version of the 2nd-gen Spectrum Web Components library.\n */\nexport const version = '0.
|
|
1
|
+
{"version":3,"file":"version.js","names":[],"sources":["../../element/version.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n// Auto-generated from 2nd-gen/packages/core/package.json\n// Generation: 2nd-gen\n// DO NOT EDIT - This file is generated by scripts/generate-versions.js\n\n/**\n * The version of the 2nd-gen Spectrum Web Components library.\n */\nexport const version = '2.0.0-beta.1';\n\n/**\n * The version of the core base package.\n */\nexport const coreVersion = '2.0.0-beta.1';\n"],"mappings":";AAmBA,IAAa,IAAU,gBAKV,IAAc"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/spectrum-wc-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0-beta.2",
|
|
4
4
|
"description": "Abstract base classes for Spectrum Web Components",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Adobe",
|
|
@@ -167,6 +167,10 @@
|
|
|
167
167
|
"types": "./dist/controllers/placement-controller/index.d.ts",
|
|
168
168
|
"import": "./dist/controllers/placement-controller/index.js"
|
|
169
169
|
},
|
|
170
|
+
"./controllers/slot-attribute-propagation-controller.js": {
|
|
171
|
+
"types": "./dist/controllers/slot-attribute-propagation-controller/index.d.ts",
|
|
172
|
+
"import": "./dist/controllers/slot-attribute-propagation-controller/index.js"
|
|
173
|
+
},
|
|
170
174
|
"./directives": {
|
|
171
175
|
"types": "./dist/directives/index.d.ts",
|
|
172
176
|
"import": "./dist/directives/index.js"
|
|
@@ -369,6 +373,9 @@
|
|
|
369
373
|
"controllers/placement-controller/index.js": [
|
|
370
374
|
"dist/controllers/placement-controller/index.d.ts"
|
|
371
375
|
],
|
|
376
|
+
"controllers/slot-attribute-propagation-controller.js": [
|
|
377
|
+
"dist/controllers/slot-attribute-propagation-controller/index.d.ts"
|
|
378
|
+
],
|
|
372
379
|
"directives": [
|
|
373
380
|
"dist/directives/index.d.ts"
|
|
374
381
|
],
|
|
@@ -453,4 +460,4 @@
|
|
|
453
460
|
"publishConfig": {
|
|
454
461
|
"access": "public"
|
|
455
462
|
}
|
|
456
|
-
}
|
|
463
|
+
}
|
package/dist/controllers/slot-attribute-propagation/src/slot-attribute-propagation-controller.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"slot-attribute-propagation-controller.js","names":[],"sources":["../../../../controllers/slot-attribute-propagation/src/slot-attribute-propagation-controller.ts"],"sourcesContent":["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport type { ReactiveController, ReactiveElement } from 'lit';\n\n// ─────────────────────────\n// TYPES\n// ─────────────────────────\n\nexport interface SlotAttributePropagationControllerOptions {\n /** The attribute name to propagate to assigned elements. */\n attribute: string;\n /** Returns the current value to propagate. */\n getValue: () => string;\n /** Named slot to target. Omit for the default (unnamed) slot. */\n slotName?: string;\n\n /**\n * CSS selector used to filter assigned elements before propagating.\n * Omit to propagate to all assigned elements.\n */\n selector?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// CONTROLLER\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * A Lit {@link ReactiveController} that propagates an attribute from the host\n * element to elements assigned to one of its slots.\n *\n * The controller automatically re-propagates in `hostUpdated()` whenever the\n * value returned by `getValue` changes. Call `propagate()` from the host's\n * `slotchange` handler to cover elements inserted after the first render.\n *\n * @example\n * ```ts\n * class MyBase extends SpectrumElement {\n * @property({ type: String, reflect: true })\n * public size: MySize = 'm';\n *\n * private readonly _sizePropagation = new SlotAttributePropagationController(this, {\n * attribute: 'size',\n * getValue: () => this.size,\n * slotName: 'actions',\n * });\n *\n * protected handleActionsSlotChange(): void {\n * this._sizePropagation.propagate();\n * }\n * }\n * ```\n */\nexport class SlotAttributePropagationController implements ReactiveController {\n private readonly _host: ReactiveElement;\n private readonly _options: SlotAttributePropagationControllerOptions;\n private _previousValue?: string;\n\n constructor(\n host: ReactiveElement,\n options: SlotAttributePropagationControllerOptions\n ) {\n this._host = host;\n this._options = options;\n host.addController(this);\n }\n\n public hostDisconnected(): void {\n this._previousValue = undefined;\n }\n\n public hostUpdated(): void {\n const value = this._options.getValue();\n if (value !== this._previousValue) {\n this._previousValue = value;\n this._propagateToSlot(value);\n }\n }\n\n /**\n * Propagates the current value to all matching assigned elements.\n * Call this from the host's `slotchange` event handler.\n */\n public propagate(): void {\n this._propagateToSlot(this._options.getValue());\n }\n\n private _propagateToSlot(value: string): void {\n const slot = this._resolveSlot();\n if (!slot) {\n return;\n }\n const assigned = slot.assignedElements({ flatten: true });\n const targets = this._options.selector\n ? assigned.filter((el) => el.matches(this._options.selector!))\n : assigned;\n for (const el of targets) {\n el.setAttribute(this._options.attribute, value);\n }\n }\n\n private _resolveSlot(): HTMLSlotElement | null {\n const root = this._host.shadowRoot;\n if (!root) {\n return null;\n }\n const { slotName } = this._options;\n return slotName\n ? root.querySelector<HTMLSlotElement>(`slot[name=\"${slotName}\"]`)\n : root.querySelector<HTMLSlotElement>('slot:not([name])');\n }\n}\n"],"mappings":";AA+DA,IAAa,IAAb,MAA8E;CAK5E,YACE,GACA,GACA;AAGA,EAFA,KAAK,QAAQ,GACb,KAAK,WAAW,GAChB,EAAK,cAAc,KAAK;;CAG1B,mBAAgC;AAC9B,OAAK,iBAAiB,KAAA;;CAGxB,cAA2B;EACzB,IAAM,IAAQ,KAAK,SAAS,UAAU;AACtC,EAAI,MAAU,KAAK,mBACjB,KAAK,iBAAiB,GACtB,KAAK,iBAAiB,EAAM;;CAQhC,YAAyB;AACvB,OAAK,iBAAiB,KAAK,SAAS,UAAU,CAAC;;CAGjD,iBAAyB,GAAqB;EAC5C,IAAM,IAAO,KAAK,cAAc;AAChC,MAAI,CAAC,EACH;EAEF,IAAM,IAAW,EAAK,iBAAiB,EAAE,SAAS,IAAM,CAAC,EACnD,IAAU,KAAK,SAAS,WAC1B,EAAS,QAAQ,MAAO,EAAG,QAAQ,KAAK,SAAS,SAAU,CAAC,GAC5D;AACJ,OAAK,IAAM,KAAM,EACf,GAAG,aAAa,KAAK,SAAS,WAAW,EAAM;;CAInD,eAA+C;EAC7C,IAAM,IAAO,KAAK,MAAM;AACxB,MAAI,CAAC,EACH,QAAO;EAET,IAAM,EAAE,gBAAa,KAAK;AAC1B,SAAO,IACH,EAAK,cAA+B,cAAc,EAAS,IAAI,GAC/D,EAAK,cAA+B,mBAAmB"}
|
|
File without changes
|