@spectrum-web-components/reactive-controllers 1.9.1 → 1.11.0-preview-b6b1becb.20251121200357

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/README.md CHANGED
@@ -1,14 +1,270 @@
1
- ## Description
1
+ ## Overview
2
2
 
3
- [Reactive controllers](https://lit.dev/docs/composition/controllers/) are a tool for code reuse and composition within [Lit](https://lit.dev), a core dependency of Spectrum Web Components. Reactive controllers can be reused across components to reduce both code complexity and size, and to deliver a consistent user experience. These reactive controllers are used by the Spectrum Web Components library and are published to NPM for you to leverage in your projects as well.
3
+ [Reactive controllers](https://lit.dev/docs/composition/controllers/) are a powerful tool for code reuse and composition within [Lit](https://lit.dev), a core dependency of Spectrum Web Components. They enable you to extract common behaviors into reusable packages that can be shared across multiple components, reducing code complexity and size while delivering a consistent user experience.
4
4
 
5
- ### Reactive controllers
5
+ ### Usage
6
6
 
7
- - [ColorController](../color-controller)
8
- - [ElementResolutionController](../element-resolution)
9
- - FocusGroupController
10
- - LanguageResolutionController
11
- - [MatchMediaController](../match-media)
12
- - [RovingTabindexController](../roving-tab-index)
13
- - [PendingStateController](../pending-state)
14
- - SystemContextResolutionController
7
+ ```bash
8
+ yarn add @spectrum-web-components/reactive-controllers
9
+ ```
10
+
11
+ Reactive controllers are instantiated in your component and automatically hook into the component's lifecycle:
12
+
13
+ ```typescript
14
+ import { LitElement, html } from 'lit';
15
+ import { MatchMediaController } from '@spectrum-web-components/reactive-controllers/src/MatchMedia.js';
16
+
17
+ class MyComponent extends LitElement {
18
+ // Create controller instance
19
+ darkMode = new MatchMediaController(this, '(prefers-color-scheme: dark)');
20
+
21
+ render() {
22
+ // Use controller state in render
23
+ return html`
24
+ <div class=${this.darkMode.matches ? 'dark' : 'light'}>Content</div>
25
+ `;
26
+ }
27
+ }
28
+ ```
29
+
30
+ ### Controller lifecycle
31
+
32
+ Reactive controllers implement the `ReactiveController` interface with the following optional lifecycle methods:
33
+
34
+ - **`hostConnected()`**: Called when the host element is connected to the DOM
35
+ - **`hostDisconnected()`**: Called when the host element is disconnected from the DOM
36
+ - **`hostUpdate()`**: Called before the host's `update()` method
37
+ - **`hostUpdated()`**: Called after the host's `update()` method
38
+
39
+ Controllers can also call `host.requestUpdate()` to trigger an update cycle on the host element.
40
+
41
+ ### Creating your own controllers
42
+
43
+ You can create custom reactive controllers by implementing the `ReactiveController` interface:
44
+
45
+ ```typescript
46
+ import { ReactiveController, ReactiveElement } from 'lit';
47
+
48
+ export class MyController implements ReactiveController {
49
+ private host: ReactiveElement;
50
+
51
+ constructor(host: ReactiveElement) {
52
+ this.host = host;
53
+ // Register this controller with the host
54
+ this.host.addController(this);
55
+ }
56
+
57
+ hostConnected() {
58
+ // Called when host is connected to DOM
59
+ }
60
+
61
+ hostDisconnected() {
62
+ // Called when host is disconnected from DOM
63
+ }
64
+ }
65
+ ```
66
+
67
+ ### Available controllers
68
+
69
+ #### ColorController
70
+
71
+ Manages and validates color values in various color spaces (RGB, HSL, HSV, Hex). Provides conversion between formats and state management for color-related interactions.
72
+
73
+ **Use cases:**
74
+
75
+ - Color pickers and selectors
76
+ - Color input validation
77
+ - Color format conversion
78
+ - Theme customization UIs
79
+
80
+ **Key features:**
81
+
82
+ - Multiple color format support (hex, RGB, HSL, HSV)
83
+ - Color validation
84
+ - Format preservation
85
+ - Undo/redo support
86
+
87
+ [Learn more →](../color-controller)
88
+
89
+ ---
90
+
91
+ #### DependencyManagerController
92
+
93
+ Manages the availability of custom element dependencies, enabling lazy loading patterns and progressive enhancement strategies.
94
+
95
+ **Use cases:**
96
+
97
+ - Code splitting and lazy loading
98
+ - Progressive enhancement
99
+ - Route-based component loading
100
+ - Conditional feature loading
101
+
102
+ **Key features:**
103
+
104
+ - Tracks custom element registration
105
+ - Reactive loading state
106
+ - Multiple dependency management
107
+ - Works with dynamic imports
108
+
109
+ [Learn more →](../dependency-manager)
110
+
111
+ ---
112
+
113
+ #### ElementResolutionController
114
+
115
+ Maintains an active reference to another element in the DOM tree, automatically tracking changes and updating when the DOM mutates.
116
+
117
+ **Use cases:**
118
+
119
+ - Accessible label associations
120
+ - Focus trap management
121
+ - Form validation connections
122
+ - Dynamic element relationships
123
+
124
+ **Key features:**
125
+
126
+ - Automatic DOM observation
127
+ - ID selector optimization
128
+ - Shadow DOM support
129
+ - Reactive updates
130
+
131
+ [Learn more →](../element-resolution)
132
+
133
+ ---
134
+
135
+ #### FocusGroupController
136
+
137
+ Base controller for managing keyboard focus within groups of elements. Extended by `RovingTabindexController` with tabindex management capabilities.
138
+
139
+ **Use cases:**
140
+
141
+ - Custom composite widgets
142
+ - Keyboard navigation patterns
143
+ - Focus management
144
+
145
+ **Key features:**
146
+
147
+ - Arrow key navigation
148
+ - Configurable direction modes
149
+ - Focus entry points
150
+ - Element enter actions
151
+
152
+ **Note:** This controller is typically not used directly. Use [RovingTabindexController](../roving-tab-index) instead for most use cases.
153
+
154
+ ---
155
+
156
+ #### LanguageResolutionController
157
+
158
+ Resolves and tracks the language/locale context of the host element, responding to changes in the `lang` attribute up the DOM tree.
159
+
160
+ **Use cases:**
161
+
162
+ - Internationalization (i18n)
163
+ - Localized content
164
+ - RTL/LTR text direction
165
+ - Locale-specific formatting
166
+
167
+ **Key features:**
168
+
169
+ - Automatic language detection
170
+ - Locale change tracking
171
+ - Supports Shadow DOM
172
+ - Bubbles up DOM tree
173
+
174
+ [Learn more →](../language-resolution)
175
+
176
+ ---
177
+
178
+ #### MatchMediaController
179
+
180
+ Binds CSS media queries to reactive elements, automatically updating when queries match or unmatch.
181
+
182
+ **Use cases:**
183
+
184
+ - Responsive design
185
+ - Dark mode detection
186
+ - Mobile/desktop layouts
187
+ - Print styles
188
+ - Accessibility preferences (prefers-reduced-motion, etc.)
189
+
190
+ **Key features:**
191
+
192
+ - Multiple media query support
193
+ - Reactive updates
194
+ - Predefined queries (DARK_MODE, IS_MOBILE)
195
+ - Event-driven
196
+
197
+ [Learn more →](../match-media)
198
+
199
+ ---
200
+
201
+ #### PendingStateController
202
+
203
+ Manages pending/loading states for interactive elements, providing visual feedback and accessible state communication.
204
+
205
+ **Use cases:**
206
+
207
+ - Async button actions
208
+ - Form submission states
209
+ - Loading indicators
210
+ - Progress feedback
211
+
212
+ **Key features:**
213
+
214
+ - Automatic ARIA label management
215
+ - Progress circle rendering
216
+ - Label caching and restoration
217
+ - Disabled state awareness
218
+
219
+ **Note:** Currently used primarily by Button component. May be deprecated in future versions.
220
+
221
+ [Learn more →](../pending-state)
222
+
223
+ ---
224
+
225
+ #### RovingTabindexController
226
+
227
+ Implements the W3C ARIA roving tabindex pattern for keyboard navigation in composite widgets, managing `tabindex` attributes and arrow key navigation.
228
+
229
+ **Use cases:**
230
+
231
+ - Toolbars
232
+ - Tab lists
233
+ - Menus
234
+ - Radio groups
235
+ - Listboxes
236
+ - Grids
237
+
238
+ **Key features:**
239
+
240
+ - Arrow key navigation (with Home/End support)
241
+ - Automatic tabindex management
242
+ - Flexible direction modes (horizontal, vertical, both, grid)
243
+ - Skips disabled elements
244
+ - WCAG compliant
245
+
246
+ [Learn more →](../roving-tab-index)
247
+
248
+ ---
249
+
250
+ #### SystemContextResolutionController
251
+
252
+ Resolves and tracks system-level context like color scheme and scale preferences from Spectrum theme providers.
253
+
254
+ **Use cases:**
255
+
256
+ - Theme integration
257
+ - Design system variant detection (Spectrum Classic, Express, Spectrum 2)
258
+ - System-specific asset loading
259
+ - Adaptive UI rendering
260
+
261
+ **Key features:**
262
+
263
+ - Automatic theme context resolution
264
+ - Reactive system variant updates
265
+ - Event-based communication with `<sp-theme>`
266
+ - Automatic cleanup on disconnect
267
+
268
+ **Note:** Private Beta API - subject to changes.
269
+
270
+ [Learn more →](../system-context-resolution)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spectrum-web-components/reactive-controllers",
3
- "version": "1.9.1",
3
+ "version": "1.11.0-preview-b6b1becb.20251121200357",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -9,7 +9,7 @@
9
9
  "repository": {
10
10
  "type": "git",
11
11
  "url": "https://github.com/adobe/spectrum-web-components.git",
12
- "directory": "tools/reactive-controllers"
12
+ "directory": "1st-gen/tools/reactive-controllers"
13
13
  },
14
14
  "author": "Adobe",
15
15
  "homepage": "https://opensource.adobe.com/spectrum-web-components/tools/reactive-controllers",
@@ -89,7 +89,7 @@
89
89
  "css"
90
90
  ],
91
91
  "dependencies": {
92
- "@spectrum-web-components/progress-circle": "1.9.1",
92
+ "@spectrum-web-components/progress-circle": "1.11.0-preview-b6b1becb.20251121200357",
93
93
  "colorjs.io": "0.5.2",
94
94
  "lit": "^2.5.0 || ^3.1.3"
95
95
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["PendingState.ts"],
4
- "sourcesContent": ["/**\n * Copyright 2025 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 '@spectrum-web-components/progress-circle/sp-progress-circle.js';\nimport { html, LitElement, ReactiveController, TemplateResult } from 'lit';\n\n/**\n * Renders a pending state visual element and manages the aria-label of the host element.\n * \n * Currently this is used by Button only since the host element is the interactive element that needs pending state. This pattern does not work for components where the interactive element that needs pending state is in the shadow DOM. i.e. Combobox and Picker.\n * \n * @TODO consider deprecating this controller since it is not used by any other component.\n */\nexport interface HostWithPendingState extends LitElement {\n pendingLabel?: string;\n pending: boolean;\n disabled: boolean;\n pendingStateController: PendingStateController<HostWithPendingState>;\n}\n\n/**\n * Represents a controller for managing the pending state of a reactive element.\n *\n * @template T - The type of the reactive element.\n */\nexport class PendingStateController<T extends HostWithPendingState>\n implements ReactiveController\n{\n /**\n * The host element that this controller is attached to.\n */\n public host: T;\n\n /**\n * Creates an instance of PendingStateController.\n * @param host - The host element that this controller is attached to.\n */\n constructor(host: T) {\n this.host = host;\n this.host.addController(this);\n }\n\n public cachedAriaLabel: string | null = null;\n /**\n * Renders the pending state UI.\n * @returns A TemplateResult representing the pending state UI.\n *\n * @TODO [SWC-1119, SWC-1255, SWC-459] Confirm the accessibility warning and a11y dom tree are accurate for the pending state in button, combobox, and picker components.\n */\n public renderPendingState(): TemplateResult {\n return this.host.pending\n ? html`\n <sp-progress-circle\n id=\"loader\"\n size=\"s\"\n indeterminate\n class=\"progress-circle\"\n role=\"presentation\"\n ></sp-progress-circle>\n `\n : html``;\n }\n\n /**\n * Updates the ARIA label of the host element based on the pending state.\n * Manages Cached Aria Label\n */\n private updateAriaLabel(): void {\n const { pending, disabled, pendingLabel } = this.host;\n const currentAriaLabel = this.host.getAttribute('aria-label');\n\n function shouldCacheAriaLabel(\n cached: string | null,\n current: string | null,\n pending: string | undefined\n ): boolean {\n return (\n (!cached && current !== pending) ||\n (cached !== current && current !== pending)\n );\n }\n\n // If the current `aria-label` is different from the pending label, cache it\n // or if the cached `aria-label` is different from the current `aria-label`, cache it\n if (\n shouldCacheAriaLabel(\n this.cachedAriaLabel,\n currentAriaLabel,\n pendingLabel\n )\n ) {\n this.cachedAriaLabel = currentAriaLabel;\n }\n\n if (pending && !disabled) {\n // Since it is pending, we set the aria-label to `pendingLabel` or \"Pending\"\n this.host.setAttribute('aria-label', pendingLabel || 'Pending');\n } else {\n // Restore the cached `aria-label` if it exists\n if (this.cachedAriaLabel) {\n this.host.setAttribute('aria-label', this.cachedAriaLabel);\n } else {\n this.host.removeAttribute('aria-label');\n }\n }\n }\n\n hostConnected(): void {\n if (!this.cachedAriaLabel)\n this.cachedAriaLabel = this.host.getAttribute('aria-label');\n this.updateAriaLabel();\n }\n\n hostUpdated(): void {\n this.updateAriaLabel();\n }\n}\n"],
4
+ "sourcesContent": ["/**\n * Copyright 2025 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 '@spectrum-web-components/progress-circle/sp-progress-circle.js';\nimport { html, LitElement, ReactiveController, TemplateResult } from 'lit';\n\n/**\n * Renders a pending state visual element and manages the aria-label of the host element.\n *\n * Currently this is used by Button only since the host element is the interactive element that needs pending state. This pattern does not work for components where the interactive element that needs pending state is in the shadow DOM. i.e. Combobox and Picker.\n *\n * @TODO consider deprecating this controller since it is not used by any other component.\n */\nexport interface HostWithPendingState extends LitElement {\n pendingLabel?: string;\n pending: boolean;\n disabled: boolean;\n pendingStateController: PendingStateController<HostWithPendingState>;\n}\n\n/**\n * Represents a controller for managing the pending state of a reactive element.\n *\n * @template T - The type of the reactive element.\n */\nexport class PendingStateController<T extends HostWithPendingState>\n implements ReactiveController\n{\n /**\n * The host element that this controller is attached to.\n */\n public host: T;\n\n /**\n * Creates an instance of PendingStateController.\n * @param host - The host element that this controller is attached to.\n */\n constructor(host: T) {\n this.host = host;\n this.host.addController(this);\n }\n\n public cachedAriaLabel: string | null = null;\n /**\n * Renders the pending state UI.\n * @returns A TemplateResult representing the pending state UI.\n *\n * @TODO [SWC-1119, SWC-1255, SWC-459] Confirm the accessibility warning and a11y dom tree are accurate for the pending state in button, combobox, and picker components.\n */\n public renderPendingState(): TemplateResult {\n return this.host.pending\n ? html`\n <sp-progress-circle\n id=\"loader\"\n size=\"s\"\n indeterminate\n class=\"progress-circle\"\n role=\"presentation\"\n ></sp-progress-circle>\n `\n : html``;\n }\n\n /**\n * Updates the ARIA label of the host element based on the pending state.\n * Manages Cached Aria Label\n */\n private updateAriaLabel(): void {\n const { pending, disabled, pendingLabel } = this.host;\n const currentAriaLabel = this.host.getAttribute('aria-label');\n\n function shouldCacheAriaLabel(\n cached: string | null,\n current: string | null,\n pending: string | undefined\n ): boolean {\n return (\n (!cached && current !== pending) ||\n (cached !== current && current !== pending)\n );\n }\n\n // If the current `aria-label` is different from the pending label, cache it\n // or if the cached `aria-label` is different from the current `aria-label`, cache it\n if (\n shouldCacheAriaLabel(\n this.cachedAriaLabel,\n currentAriaLabel,\n pendingLabel\n )\n ) {\n this.cachedAriaLabel = currentAriaLabel;\n }\n\n if (pending && !disabled) {\n // Since it is pending, we set the aria-label to `pendingLabel` or \"Pending\"\n this.host.setAttribute('aria-label', pendingLabel || 'Pending');\n } else {\n // Restore the cached `aria-label` if it exists\n if (this.cachedAriaLabel) {\n this.host.setAttribute('aria-label', this.cachedAriaLabel);\n } else {\n this.host.removeAttribute('aria-label');\n }\n }\n }\n\n hostConnected(): void {\n if (!this.cachedAriaLabel)\n this.cachedAriaLabel = this.host.getAttribute('aria-label');\n this.updateAriaLabel();\n }\n\n hostUpdated(): void {\n this.updateAriaLabel();\n }\n}\n"],
5
5
  "mappings": ";AAYA,OAAO;AACP,SAAS,YAA4D;AAqB9D,aAAM,uBAEb;AAAA;AAAA;AAAA;AAAA;AAAA,EAUI,YAAY,MAAS;AAKrB,SAAO,kBAAiC;AAJpC,SAAK,OAAO;AACZ,SAAK,KAAK,cAAc,IAAI;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASO,qBAAqC;AACxC,WAAO,KAAK,KAAK,UACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBASA;AAAA,EACV;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,kBAAwB;AAC5B,UAAM,EAAE,SAAS,UAAU,aAAa,IAAI,KAAK;AACjD,UAAM,mBAAmB,KAAK,KAAK,aAAa,YAAY;AAE5D,aAAS,qBACL,QACA,SACAA,UACO;AACP,aACK,CAAC,UAAU,YAAYA,YACvB,WAAW,WAAW,YAAYA;AAAA,IAE3C;AAIA,QACI;AAAA,MACI,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IACJ,GACF;AACE,WAAK,kBAAkB;AAAA,IAC3B;AAEA,QAAI,WAAW,CAAC,UAAU;AAEtB,WAAK,KAAK,aAAa,cAAc,gBAAgB,SAAS;AAAA,IAClE,OAAO;AAEH,UAAI,KAAK,iBAAiB;AACtB,aAAK,KAAK,aAAa,cAAc,KAAK,eAAe;AAAA,MAC7D,OAAO;AACH,aAAK,KAAK,gBAAgB,YAAY;AAAA,MAC1C;AAAA,IACJ;AAAA,EACJ;AAAA,EAEA,gBAAsB;AAClB,QAAI,CAAC,KAAK;AACN,WAAK,kBAAkB,KAAK,KAAK,aAAa,YAAY;AAC9D,SAAK,gBAAgB;AAAA,EACzB;AAAA,EAEA,cAAoB;AAChB,SAAK,gBAAgB;AAAA,EACzB;AACJ;",
6
6
  "names": ["pending"]
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["PendingState.ts"],
4
- "sourcesContent": ["/**\n * Copyright 2025 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 '@spectrum-web-components/progress-circle/sp-progress-circle.js';\nimport { html, LitElement, ReactiveController, TemplateResult } from 'lit';\n\n/**\n * Renders a pending state visual element and manages the aria-label of the host element.\n * \n * Currently this is used by Button only since the host element is the interactive element that needs pending state. This pattern does not work for components where the interactive element that needs pending state is in the shadow DOM. i.e. Combobox and Picker.\n * \n * @TODO consider deprecating this controller since it is not used by any other component.\n */\nexport interface HostWithPendingState extends LitElement {\n pendingLabel?: string;\n pending: boolean;\n disabled: boolean;\n pendingStateController: PendingStateController<HostWithPendingState>;\n}\n\n/**\n * Represents a controller for managing the pending state of a reactive element.\n *\n * @template T - The type of the reactive element.\n */\nexport class PendingStateController<T extends HostWithPendingState>\n implements ReactiveController\n{\n /**\n * The host element that this controller is attached to.\n */\n public host: T;\n\n /**\n * Creates an instance of PendingStateController.\n * @param host - The host element that this controller is attached to.\n */\n constructor(host: T) {\n this.host = host;\n this.host.addController(this);\n }\n\n public cachedAriaLabel: string | null = null;\n /**\n * Renders the pending state UI.\n * @returns A TemplateResult representing the pending state UI.\n *\n * @TODO [SWC-1119, SWC-1255, SWC-459] Confirm the accessibility warning and a11y dom tree are accurate for the pending state in button, combobox, and picker components.\n */\n public renderPendingState(): TemplateResult {\n return this.host.pending\n ? html`\n <sp-progress-circle\n id=\"loader\"\n size=\"s\"\n indeterminate\n class=\"progress-circle\"\n role=\"presentation\"\n ></sp-progress-circle>\n `\n : html``;\n }\n\n /**\n * Updates the ARIA label of the host element based on the pending state.\n * Manages Cached Aria Label\n */\n private updateAriaLabel(): void {\n const { pending, disabled, pendingLabel } = this.host;\n const currentAriaLabel = this.host.getAttribute('aria-label');\n\n function shouldCacheAriaLabel(\n cached: string | null,\n current: string | null,\n pending: string | undefined\n ): boolean {\n return (\n (!cached && current !== pending) ||\n (cached !== current && current !== pending)\n );\n }\n\n // If the current `aria-label` is different from the pending label, cache it\n // or if the cached `aria-label` is different from the current `aria-label`, cache it\n if (\n shouldCacheAriaLabel(\n this.cachedAriaLabel,\n currentAriaLabel,\n pendingLabel\n )\n ) {\n this.cachedAriaLabel = currentAriaLabel;\n }\n\n if (pending && !disabled) {\n // Since it is pending, we set the aria-label to `pendingLabel` or \"Pending\"\n this.host.setAttribute('aria-label', pendingLabel || 'Pending');\n } else {\n // Restore the cached `aria-label` if it exists\n if (this.cachedAriaLabel) {\n this.host.setAttribute('aria-label', this.cachedAriaLabel);\n } else {\n this.host.removeAttribute('aria-label');\n }\n }\n }\n\n hostConnected(): void {\n if (!this.cachedAriaLabel)\n this.cachedAriaLabel = this.host.getAttribute('aria-label');\n this.updateAriaLabel();\n }\n\n hostUpdated(): void {\n this.updateAriaLabel();\n }\n}\n"],
4
+ "sourcesContent": ["/**\n * Copyright 2025 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 '@spectrum-web-components/progress-circle/sp-progress-circle.js';\nimport { html, LitElement, ReactiveController, TemplateResult } from 'lit';\n\n/**\n * Renders a pending state visual element and manages the aria-label of the host element.\n *\n * Currently this is used by Button only since the host element is the interactive element that needs pending state. This pattern does not work for components where the interactive element that needs pending state is in the shadow DOM. i.e. Combobox and Picker.\n *\n * @TODO consider deprecating this controller since it is not used by any other component.\n */\nexport interface HostWithPendingState extends LitElement {\n pendingLabel?: string;\n pending: boolean;\n disabled: boolean;\n pendingStateController: PendingStateController<HostWithPendingState>;\n}\n\n/**\n * Represents a controller for managing the pending state of a reactive element.\n *\n * @template T - The type of the reactive element.\n */\nexport class PendingStateController<T extends HostWithPendingState>\n implements ReactiveController\n{\n /**\n * The host element that this controller is attached to.\n */\n public host: T;\n\n /**\n * Creates an instance of PendingStateController.\n * @param host - The host element that this controller is attached to.\n */\n constructor(host: T) {\n this.host = host;\n this.host.addController(this);\n }\n\n public cachedAriaLabel: string | null = null;\n /**\n * Renders the pending state UI.\n * @returns A TemplateResult representing the pending state UI.\n *\n * @TODO [SWC-1119, SWC-1255, SWC-459] Confirm the accessibility warning and a11y dom tree are accurate for the pending state in button, combobox, and picker components.\n */\n public renderPendingState(): TemplateResult {\n return this.host.pending\n ? html`\n <sp-progress-circle\n id=\"loader\"\n size=\"s\"\n indeterminate\n class=\"progress-circle\"\n role=\"presentation\"\n ></sp-progress-circle>\n `\n : html``;\n }\n\n /**\n * Updates the ARIA label of the host element based on the pending state.\n * Manages Cached Aria Label\n */\n private updateAriaLabel(): void {\n const { pending, disabled, pendingLabel } = this.host;\n const currentAriaLabel = this.host.getAttribute('aria-label');\n\n function shouldCacheAriaLabel(\n cached: string | null,\n current: string | null,\n pending: string | undefined\n ): boolean {\n return (\n (!cached && current !== pending) ||\n (cached !== current && current !== pending)\n );\n }\n\n // If the current `aria-label` is different from the pending label, cache it\n // or if the cached `aria-label` is different from the current `aria-label`, cache it\n if (\n shouldCacheAriaLabel(\n this.cachedAriaLabel,\n currentAriaLabel,\n pendingLabel\n )\n ) {\n this.cachedAriaLabel = currentAriaLabel;\n }\n\n if (pending && !disabled) {\n // Since it is pending, we set the aria-label to `pendingLabel` or \"Pending\"\n this.host.setAttribute('aria-label', pendingLabel || 'Pending');\n } else {\n // Restore the cached `aria-label` if it exists\n if (this.cachedAriaLabel) {\n this.host.setAttribute('aria-label', this.cachedAriaLabel);\n } else {\n this.host.removeAttribute('aria-label');\n }\n }\n }\n\n hostConnected(): void {\n if (!this.cachedAriaLabel)\n this.cachedAriaLabel = this.host.getAttribute('aria-label');\n this.updateAriaLabel();\n }\n\n hostUpdated(): void {\n this.updateAriaLabel();\n }\n}\n"],
5
5
  "mappings": "aAYA,MAAO,iEACP,OAAS,QAAAA,MAA4D,MAqB9D,aAAM,sBAEb,CAUI,YAAYC,EAAS,CAKrB,KAAO,gBAAiC,KAJpC,KAAK,KAAOA,EACZ,KAAK,KAAK,cAAc,IAAI,CAChC,CASO,oBAAqC,CACxC,OAAO,KAAK,KAAK,QACXD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBASAA,GACV,CAMQ,iBAAwB,CAC5B,KAAM,CAAE,QAAAE,EAAS,SAAAC,EAAU,aAAAC,CAAa,EAAI,KAAK,KAC3CC,EAAmB,KAAK,KAAK,aAAa,YAAY,EAE5D,SAASC,EACLC,EACAC,EACAN,EACO,CACP,MACK,CAACK,GAAUC,IAAYN,GACvBK,IAAWC,GAAWA,IAAYN,CAE3C,CAKII,EACI,KAAK,gBACLD,EACAD,CACJ,IAEA,KAAK,gBAAkBC,GAGvBH,GAAW,CAACC,EAEZ,KAAK,KAAK,aAAa,aAAcC,GAAgB,SAAS,EAG1D,KAAK,gBACL,KAAK,KAAK,aAAa,aAAc,KAAK,eAAe,EAEzD,KAAK,KAAK,gBAAgB,YAAY,CAGlD,CAEA,eAAsB,CACb,KAAK,kBACN,KAAK,gBAAkB,KAAK,KAAK,aAAa,YAAY,GAC9D,KAAK,gBAAgB,CACzB,CAEA,aAAoB,CAChB,KAAK,gBAAgB,CACzB,CACJ",
6
6
  "names": ["html", "host", "pending", "disabled", "pendingLabel", "currentAriaLabel", "shouldCacheAriaLabel", "cached", "current"]
7
7
  }