@spectrum-web-components/reactive-controllers 1.7.0 → 1.9.0-nightly.20251013134115

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["FocusGroup.ts"],
4
- "sourcesContent": ["/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\nimport type { ReactiveController, ReactiveElement } from 'lit';\n\ntype DirectionTypes = 'horizontal' | 'vertical' | 'both' | 'grid';\nexport type FocusGroupConfig<T> = {\n hostDelegatesFocus?: boolean;\n focusInIndex?: (_elements: T[]) => number;\n direction?: DirectionTypes | (() => DirectionTypes);\n elementEnterAction?: (el: T) => void;\n elements: () => T[];\n isFocusableElement?: (el: T) => boolean;\n listenerScope?: HTMLElement | (() => HTMLElement);\n};\n\nfunction ensureMethod<T, RT>(\n value: T | RT | undefined,\n type: string,\n fallback: T\n): T {\n if (typeof value === type) {\n return (() => value) as T;\n } else if (typeof value === 'function') {\n return value as T;\n }\n return fallback;\n}\n\nexport class FocusGroupController<T extends HTMLElement>\n implements ReactiveController\n{\n protected cachedElements?: T[];\n private mutationObserver: MutationObserver;\n\n get currentIndex(): number {\n if (this._currentIndex === -1) {\n this._currentIndex = this.focusInIndex;\n }\n return this._currentIndex - this.offset;\n }\n\n set currentIndex(currentIndex) {\n this._currentIndex = currentIndex + this.offset;\n }\n\n private _currentIndex = -1;\n\n private prevIndex = -1;\n\n get direction(): DirectionTypes {\n return this._direction();\n }\n\n _direction = (): DirectionTypes => 'both';\n\n public directionLength = 5;\n\n public hostDelegatesFocus = false;\n\n elementEnterAction = (_el: T): void => {\n return;\n };\n\n get elements(): T[] {\n if (!this.cachedElements) {\n this.cachedElements = this._elements();\n }\n return this.cachedElements;\n }\n\n private _elements!: () => T[];\n\n protected set focused(focused: boolean) {\n /* c8 ignore next 1 */\n if (focused === this.focused) return;\n this._focused = focused;\n }\n\n protected get focused(): boolean {\n return this._focused;\n }\n\n private _focused = false;\n\n get focusInElement(): T {\n return this.elements[this.focusInIndex];\n }\n\n get focusInIndex(): number {\n return this._focusInIndex(this.elements);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n _focusInIndex = (_elements: T[]): number => 0;\n\n host: ReactiveElement;\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n isFocusableElement = (_el: T): boolean => true;\n\n isEventWithinListenerScope(event: Event): boolean {\n if (this._listenerScope() === this.host) return true;\n return event.composedPath().includes(this._listenerScope());\n }\n\n _listenerScope = (): HTMLElement => this.host;\n\n // When elements are virtualized, the delta between the first element\n // and the first rendered element.\n offset = 0;\n\n recentlyConnected = false;\n\n constructor(\n host: ReactiveElement,\n {\n hostDelegatesFocus,\n direction,\n elementEnterAction,\n elements,\n focusInIndex,\n isFocusableElement,\n listenerScope,\n }: FocusGroupConfig<T> = { elements: () => [] }\n ) {\n this.mutationObserver = new MutationObserver(() => {\n this.handleItemMutation();\n });\n this.hostDelegatesFocus = hostDelegatesFocus || false;\n this.host = host;\n this.host.addController(this);\n this._elements = elements;\n this.isFocusableElement = isFocusableElement || this.isFocusableElement;\n this._direction = ensureMethod<() => DirectionTypes, DirectionTypes>(\n direction,\n 'string',\n this._direction\n );\n this.elementEnterAction = elementEnterAction || this.elementEnterAction;\n this._focusInIndex = ensureMethod<(_elements: T[]) => number, number>(\n focusInIndex,\n 'number',\n this._focusInIndex\n );\n this._listenerScope = ensureMethod<() => HTMLElement, HTMLElement>(\n listenerScope,\n 'object',\n this._listenerScope\n );\n }\n /* In handleItemMutation() method the first if condition is checking if the element is not focused or if the element's children's length is not decreasing then it means no element has been deleted and we must return.\n Then we are checking if the deleted element was the focused one before the deletion if so then we need to proceed else we casn return;\n */\n handleItemMutation(): void {\n if (\n this._currentIndex == -1 ||\n this.elements.length <= this._elements().length\n )\n return;\n const focusedElement = this.elements[this.currentIndex];\n this.clearElementCache();\n if (this.elements.includes(focusedElement)) return;\n const moveToNextElement = this.currentIndex !== this.elements.length;\n const diff = moveToNextElement ? 1 : -1;\n if (moveToNextElement) {\n this.setCurrentIndexCircularly(-1);\n }\n this.setCurrentIndexCircularly(diff);\n this.focus();\n }\n\n update({ elements }: FocusGroupConfig<T> = { elements: () => [] }): void {\n this.unmanage();\n this._elements = elements;\n this.clearElementCache();\n this.manage();\n }\n\n /**\n * resets the focusedItem to initial item\n */\n reset(): void {\n const elements = this.elements;\n if (!elements.length) return;\n this.setCurrentIndexCircularly(this.focusInIndex - this.currentIndex);\n let focusElement = elements[this.currentIndex];\n if (this.currentIndex < 0) {\n return;\n }\n if (!focusElement || !this.isFocusableElement(focusElement)) {\n this.setCurrentIndexCircularly(1);\n focusElement = elements[this.currentIndex];\n }\n if (focusElement && this.isFocusableElement(focusElement)) {\n elements[this.prevIndex]?.setAttribute('tabindex', '-1');\n focusElement.setAttribute('tabindex', '0');\n }\n }\n\n focusOnItem(item?: T, options?: FocusOptions): void {\n const elements = this.elements || [];\n const newIndex: number =\n !item || !this.isFocusableElement(item)\n ? -1\n : elements.indexOf(item);\n if (newIndex > -1) {\n this.currentIndex = newIndex;\n elements[this.prevIndex]?.setAttribute('tabindex', '-1');\n }\n this.focus(options);\n }\n\n focus(options?: FocusOptions): void {\n const elements = this.elements;\n if (!elements.length) return;\n let focusElement = elements[this.currentIndex];\n if (!focusElement || !this.isFocusableElement(focusElement)) {\n this.setCurrentIndexCircularly(1);\n focusElement = elements[this.currentIndex];\n }\n if (focusElement && this.isFocusableElement(focusElement)) {\n if (\n !this.hostDelegatesFocus ||\n elements[this.prevIndex] !== focusElement\n ) {\n elements[this.prevIndex]?.setAttribute('tabindex', '-1');\n }\n focusElement.tabIndex = 0;\n focusElement.focus(options);\n if (this.hostDelegatesFocus && !this.focused) {\n this.hostContainsFocus();\n }\n }\n }\n\n clearElementCache(offset = 0): void {\n this.mutationObserver.disconnect();\n delete this.cachedElements;\n this.offset = offset;\n requestAnimationFrame(() => {\n this.elements.forEach((element) => {\n this.mutationObserver.observe(element, {\n attributes: true,\n });\n });\n });\n }\n\n setCurrentIndexCircularly(diff: number): void {\n const { length } = this.elements;\n let steps = length;\n this.prevIndex = this.currentIndex;\n // start at a possibly not 0 index\n let nextIndex = (length + this.currentIndex + diff) % length;\n while (\n // don't cycle the elements more than once\n steps &&\n this.elements[nextIndex] &&\n !this.isFocusableElement(this.elements[nextIndex])\n ) {\n nextIndex = (length + nextIndex + diff) % length;\n steps -= 1;\n }\n this.currentIndex = nextIndex;\n }\n\n hostContainsFocus(): void {\n this.host.addEventListener('focusout', this.handleFocusout);\n this.host.addEventListener('keydown', this.handleKeydown);\n this.focused = true;\n }\n\n hostNoLongerContainsFocus(): void {\n this.host.addEventListener('focusin', this.handleFocusin);\n this.host.removeEventListener('focusout', this.handleFocusout);\n this.host.removeEventListener('keydown', this.handleKeydown);\n this.focused = false;\n }\n\n isRelatedTargetOrContainAnElement(event: FocusEvent): boolean {\n const relatedTarget = event.relatedTarget as null | Element;\n\n const isRelatedTargetAnElement = this.elements.includes(\n relatedTarget as T\n );\n const isRelatedTargetContainedWithinElements = this.elements.some(\n (el) => el.contains(relatedTarget)\n );\n return !(\n isRelatedTargetAnElement || isRelatedTargetContainedWithinElements\n );\n }\n\n handleFocusin = (event: FocusEvent): void => {\n if (!this.isEventWithinListenerScope(event)) return;\n\n const path = event.composedPath() as T[];\n let targetIndex = -1;\n path.find((el) => {\n targetIndex = this.elements.indexOf(el);\n return targetIndex !== -1;\n });\n this.prevIndex = this.currentIndex;\n this.currentIndex = targetIndex > -1 ? targetIndex : this.currentIndex;\n\n if (this.isRelatedTargetOrContainAnElement(event)) {\n this.hostContainsFocus();\n }\n };\n\n /**\n * handleClick - Finds the element that was clicked and sets the tabindex to 0\n * @returns void\n */\n handleClick = (): void => {\n // Manually set the tabindex to 0 for the current element on receiving focus (from keyboard or mouse)\n const elements = this.elements;\n if (!elements.length) return;\n let focusElement = elements[this.currentIndex];\n if (this.currentIndex < 0) {\n return;\n }\n if (!focusElement || !this.isFocusableElement(focusElement)) {\n this.setCurrentIndexCircularly(1);\n focusElement = elements[this.currentIndex];\n }\n if (focusElement && this.isFocusableElement(focusElement)) {\n elements[this.prevIndex]?.setAttribute('tabindex', '-1');\n focusElement.setAttribute('tabindex', '0');\n }\n };\n\n handleFocusout = (event: FocusEvent): void => {\n if (this.isRelatedTargetOrContainAnElement(event)) {\n this.hostNoLongerContainsFocus();\n }\n };\n\n acceptsEventKey(key: string): boolean {\n if (key === 'End' || key === 'Home') {\n return true;\n }\n switch (this.direction) {\n case 'horizontal':\n return key === 'ArrowLeft' || key === 'ArrowRight';\n case 'vertical':\n return key === 'ArrowUp' || key === 'ArrowDown';\n case 'both':\n case 'grid':\n return key.startsWith('Arrow');\n }\n }\n\n handleKeydown = (event: KeyboardEvent): void => {\n if (!this.acceptsEventKey(event.key) || event.defaultPrevented) {\n return;\n }\n let diff = 0;\n this.prevIndex = this.currentIndex;\n switch (event.key) {\n case 'ArrowRight':\n diff += 1;\n break;\n case 'ArrowDown':\n diff += this.direction === 'grid' ? this.directionLength : 1;\n break;\n case 'ArrowLeft':\n diff -= 1;\n break;\n case 'ArrowUp':\n diff -= this.direction === 'grid' ? this.directionLength : 1;\n break;\n case 'End':\n this.currentIndex = 0;\n diff -= 1;\n break;\n case 'Home':\n this.currentIndex = this.elements.length - 1;\n diff += 1;\n break;\n }\n event.preventDefault();\n if (this.direction === 'grid' && this.currentIndex + diff < 0) {\n this.currentIndex = 0;\n } else if (\n this.direction === 'grid' &&\n this.currentIndex + diff > this.elements.length - 1\n ) {\n this.currentIndex = this.elements.length - 1;\n } else {\n this.setCurrentIndexCircularly(diff);\n }\n // To allow the `focusInIndex` to be calculated with the \"after\" state of the keyboard interaction\n // do `elementEnterAction` _before_ focusing the next element.\n this.elementEnterAction(this.elements[this.currentIndex]);\n this.focus();\n };\n\n manage(): void {\n this.addEventListeners();\n }\n\n unmanage(): void {\n this.removeEventListeners();\n }\n\n addEventListeners(): void {\n this.host.addEventListener('focusin', this.handleFocusin);\n this.host.addEventListener('click', this.handleClick);\n }\n\n removeEventListeners(): void {\n this.host.removeEventListener('focusin', this.handleFocusin);\n this.host.removeEventListener('focusout', this.handleFocusout);\n this.host.removeEventListener('keydown', this.handleKeydown);\n this.host.removeEventListener('click', this.handleClick);\n }\n\n hostConnected(): void {\n this.recentlyConnected = true;\n this.addEventListeners();\n }\n\n hostDisconnected(): void {\n this.mutationObserver.disconnect();\n this.removeEventListeners();\n }\n\n hostUpdated(): void {\n if (this.recentlyConnected) {\n this.recentlyConnected = false;\n this.elements.forEach((element) => {\n this.mutationObserver.observe(element, {\n attributes: true,\n });\n });\n }\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 */\nimport type { ReactiveController, ReactiveElement } from 'lit';\n\ntype DirectionTypes = 'horizontal' | 'vertical' | 'both' | 'grid';\nexport type FocusGroupConfig<T> = {\n hostDelegatesFocus?: boolean;\n focusInIndex?: (_elements: T[]) => number;\n direction?: DirectionTypes | (() => DirectionTypes);\n elementEnterAction?: (el: T) => void;\n elements: () => T[];\n isFocusableElement?: (el: T) => boolean;\n listenerScope?: HTMLElement | (() => HTMLElement);\n};\n\nfunction ensureMethod<T, RT>(\n value: T | RT | undefined,\n type: string,\n fallback: T\n): T {\n if (typeof value === type) {\n return (() => value) as T;\n } else if (typeof value === 'function') {\n return value as T;\n }\n return fallback;\n}\n\nexport class FocusGroupController<T extends HTMLElement>\n implements ReactiveController\n{\n protected cachedElements?: T[];\n private mutationObserver: MutationObserver;\n\n get currentIndex(): number {\n if (this._currentIndex === -1) {\n this._currentIndex = this.focusInIndex;\n }\n return this._currentIndex - this.offset;\n }\n\n set currentIndex(currentIndex) {\n this._currentIndex = currentIndex + this.offset;\n }\n\n private _currentIndex = -1;\n\n private prevIndex = -1;\n\n get direction(): DirectionTypes {\n return this._direction();\n }\n\n _direction = (): DirectionTypes => 'both';\n\n public directionLength = 5;\n\n public hostDelegatesFocus = false;\n\n elementEnterAction = (_el: T): void => {\n return;\n };\n\n get elements(): T[] {\n if (!this.cachedElements) {\n this.cachedElements = this._elements();\n }\n return this.cachedElements;\n }\n\n private _elements!: () => T[];\n\n protected set focused(focused: boolean) {\n /* c8 ignore next 1 */\n if (focused === this.focused) return;\n this._focused = focused;\n }\n\n protected get focused(): boolean {\n return this._focused;\n }\n\n private _focused = false;\n\n get focusInElement(): T {\n return this.elements[this.focusInIndex];\n }\n\n get focusInIndex(): number {\n return this._focusInIndex(this.elements);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n _focusInIndex = (_elements: T[]): number => 0;\n\n host: ReactiveElement;\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n isFocusableElement = (_el: T): boolean => true;\n\n isEventWithinListenerScope(event: Event): boolean {\n if (this._listenerScope() === this.host) return true;\n return event.composedPath().includes(this._listenerScope());\n }\n\n _listenerScope = (): HTMLElement => this.host;\n\n // When elements are virtualized, the delta between the first element\n // and the first rendered element.\n offset = 0;\n\n recentlyConnected = false;\n\n constructor(\n host: ReactiveElement,\n {\n hostDelegatesFocus,\n direction,\n elementEnterAction,\n elements,\n focusInIndex,\n isFocusableElement,\n listenerScope,\n }: FocusGroupConfig<T> = { elements: () => [] }\n ) {\n this.mutationObserver = new MutationObserver(() => {\n this.handleItemMutation();\n });\n this.hostDelegatesFocus = hostDelegatesFocus || false;\n this.host = host;\n this.host.addController(this);\n this._elements = elements;\n this.isFocusableElement = isFocusableElement || this.isFocusableElement;\n this._direction = ensureMethod<() => DirectionTypes, DirectionTypes>(\n direction,\n 'string',\n this._direction\n );\n this.elementEnterAction = elementEnterAction || this.elementEnterAction;\n this._focusInIndex = ensureMethod<(_elements: T[]) => number, number>(\n focusInIndex,\n 'number',\n this._focusInIndex\n );\n this._listenerScope = ensureMethod<() => HTMLElement, HTMLElement>(\n listenerScope,\n 'object',\n this._listenerScope\n );\n }\n /* In handleItemMutation() method the first if condition is checking if the element is not focused or if the element's children's length is not decreasing then it means no element has been deleted and we must return.\n Then we are checking if the deleted element was the focused one before the deletion if so then we need to proceed else we casn return;\n */\n handleItemMutation(): void {\n if (\n this._currentIndex == -1 ||\n this.elements.length <= this._elements().length\n )\n return;\n const focusedElement = this.elements[this.currentIndex];\n this.clearElementCache();\n if (this.elements.includes(focusedElement)) return;\n const moveToNextElement = this.currentIndex !== this.elements.length;\n const diff = moveToNextElement ? 1 : -1;\n if (moveToNextElement) {\n this.setCurrentIndexCircularly(-1);\n }\n this.setCurrentIndexCircularly(diff);\n this.focus();\n }\n\n update({ elements }: FocusGroupConfig<T> = { elements: () => [] }): void {\n this.unmanage();\n this._elements = elements;\n this.clearElementCache();\n this.manage();\n }\n\n /**\n * resets the focusedItem to initial item\n */\n reset(): void {\n const elements = this.elements;\n if (!elements.length) return;\n this.setCurrentIndexCircularly(this.focusInIndex - this.currentIndex);\n let focusElement = elements[this.currentIndex];\n if (this.currentIndex < 0) {\n return;\n }\n if (!focusElement || !this.isFocusableElement(focusElement)) {\n this.setCurrentIndexCircularly(1);\n focusElement = elements[this.currentIndex];\n }\n if (focusElement && this.isFocusableElement(focusElement)) {\n elements[this.prevIndex]?.setAttribute('tabindex', '-1');\n focusElement.setAttribute('tabindex', '0');\n }\n }\n\n focusOnItem(item?: T, options?: FocusOptions): void {\n const elements = this.elements || [];\n const newIndex: number =\n !item || !this.isFocusableElement(item)\n ? -1\n : elements.indexOf(item);\n if (newIndex > -1) {\n this.currentIndex = newIndex;\n elements[this.prevIndex]?.setAttribute('tabindex', '-1');\n }\n this.focus(options);\n }\n\n focus(options?: FocusOptions): void {\n const elements = this.elements;\n if (!elements.length) return;\n let focusElement = elements[this.currentIndex];\n if (!focusElement || !this.isFocusableElement(focusElement)) {\n this.setCurrentIndexCircularly(1);\n focusElement = elements[this.currentIndex];\n }\n if (focusElement && this.isFocusableElement(focusElement)) {\n if (\n !this.hostDelegatesFocus ||\n elements[this.prevIndex] !== focusElement\n ) {\n elements[this.prevIndex]?.setAttribute('tabindex', '-1');\n }\n focusElement.tabIndex = 0;\n focusElement.focus(options);\n if (this.hostDelegatesFocus && !this.focused) {\n this.hostContainsFocus();\n }\n }\n }\n\n clearElementCache(offset = 0): void {\n this.mutationObserver.disconnect();\n delete this.cachedElements;\n this.offset = offset;\n requestAnimationFrame(() => {\n this.elements.forEach((element) => {\n this.mutationObserver.observe(element, {\n attributes: true,\n });\n });\n });\n }\n\n setCurrentIndexCircularly(diff: number): void {\n const { length } = this.elements;\n let steps = length;\n this.prevIndex = this.currentIndex;\n // start at a possibly not 0 index\n let nextIndex = (length + this.currentIndex + diff) % length;\n while (\n // don't cycle the elements more than once\n steps &&\n this.elements[nextIndex] &&\n !this.isFocusableElement(this.elements[nextIndex])\n ) {\n nextIndex = (length + nextIndex + diff) % length;\n steps -= 1;\n }\n this.currentIndex = nextIndex;\n }\n\n hostContainsFocus(): void {\n this.host.addEventListener('focusout', this.handleFocusout);\n this.host.addEventListener('keydown', this.handleKeydown);\n this.focused = true;\n }\n\n hostNoLongerContainsFocus(): void {\n this.host.addEventListener('focusin', this.handleFocusin);\n this.host.removeEventListener('focusout', this.handleFocusout);\n this.host.removeEventListener('keydown', this.handleKeydown);\n this.focused = false;\n }\n\n isRelatedTargetOrContainAnElement(event: FocusEvent): boolean {\n const relatedTarget = event.relatedTarget as null | Element;\n\n const isRelatedTargetAnElement = this.elements.includes(\n relatedTarget as T\n );\n const isRelatedTargetContainedWithinElements = this.elements.some(\n (el) => el.contains(relatedTarget)\n );\n return !(\n isRelatedTargetAnElement || isRelatedTargetContainedWithinElements\n );\n }\n\n handleFocusin = (event: FocusEvent): void => {\n if (!this.isEventWithinListenerScope(event)) return;\n\n const path = event.composedPath() as T[];\n let targetIndex = -1;\n path.find((el) => {\n targetIndex = this.elements.indexOf(el);\n return targetIndex !== -1;\n });\n this.prevIndex = this.currentIndex;\n this.currentIndex = targetIndex > -1 ? targetIndex : this.currentIndex;\n\n if (this.isRelatedTargetOrContainAnElement(event)) {\n this.hostContainsFocus();\n }\n };\n\n /**\n * handleClick - Finds the element that was clicked and sets the tabindex to 0\n * @returns void\n */\n handleClick = (): void => {\n // Manually set the tabindex to 0 for the current element on receiving focus (from keyboard or mouse)\n const elements = this.elements;\n if (!elements.length) return;\n let focusElement = elements[this.currentIndex];\n if (this.currentIndex < 0) {\n return;\n }\n if (!focusElement || !this.isFocusableElement(focusElement)) {\n this.setCurrentIndexCircularly(1);\n focusElement = elements[this.currentIndex];\n }\n if (focusElement && this.isFocusableElement(focusElement)) {\n elements[this.prevIndex]?.setAttribute('tabindex', '-1');\n focusElement.setAttribute('tabindex', '0');\n }\n };\n\n handleFocusout = (event: FocusEvent): void => {\n if (this.isRelatedTargetOrContainAnElement(event)) {\n this.hostNoLongerContainsFocus();\n }\n };\n\n acceptsEventKey(key: string): boolean {\n if (key === 'End' || key === 'Home') {\n return true;\n }\n switch (this.direction) {\n case 'horizontal':\n return key === 'ArrowLeft' || key === 'ArrowRight';\n case 'vertical':\n return key === 'ArrowUp' || key === 'ArrowDown';\n case 'both':\n case 'grid':\n return key.startsWith('Arrow');\n }\n }\n\n handleKeydown = (event: KeyboardEvent): void => {\n if (!this.acceptsEventKey(event.key) || event.defaultPrevented) {\n return;\n }\n let diff = 0;\n this.prevIndex = this.currentIndex;\n switch (event.key) {\n case 'ArrowRight':\n diff += 1;\n break;\n case 'ArrowDown':\n diff += this.direction === 'grid' ? this.directionLength : 1;\n break;\n case 'ArrowLeft':\n diff -= 1;\n break;\n case 'ArrowUp':\n diff -= this.direction === 'grid' ? this.directionLength : 1;\n break;\n case 'End':\n this.currentIndex = 0;\n diff -= 1;\n break;\n case 'Home':\n this.currentIndex = this.elements.length - 1;\n diff += 1;\n break;\n }\n event.preventDefault();\n if (this.direction === 'grid' && this.currentIndex + diff < 0) {\n this.currentIndex = 0;\n } else if (\n this.direction === 'grid' &&\n this.currentIndex + diff > this.elements.length - 1\n ) {\n this.currentIndex = this.elements.length - 1;\n } else {\n this.setCurrentIndexCircularly(diff);\n }\n // To allow the `focusInIndex` to be calculated with the \"after\" state of the keyboard interaction\n // do `elementEnterAction` _before_ focusing the next element.\n this.elementEnterAction(this.elements[this.currentIndex]);\n this.focus();\n };\n\n manage(): void {\n this.addEventListeners();\n }\n\n unmanage(): void {\n this.removeEventListeners();\n }\n\n addEventListeners(): void {\n this.host.addEventListener('focusin', this.handleFocusin);\n this.host.addEventListener('click', this.handleClick);\n }\n\n removeEventListeners(): void {\n this.host.removeEventListener('focusin', this.handleFocusin);\n this.host.removeEventListener('focusout', this.handleFocusout);\n this.host.removeEventListener('keydown', this.handleKeydown);\n this.host.removeEventListener('click', this.handleClick);\n }\n\n hostConnected(): void {\n this.recentlyConnected = true;\n this.addEventListeners();\n }\n\n hostDisconnected(): void {\n this.mutationObserver.disconnect();\n this.removeEventListeners();\n }\n\n hostUpdated(): void {\n if (this.recentlyConnected) {\n this.recentlyConnected = false;\n this.elements.forEach((element) => {\n this.mutationObserver.observe(element, {\n attributes: true,\n });\n });\n }\n }\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["FocusGroup.ts"],
4
- "sourcesContent": ["/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\nimport type { ReactiveController, ReactiveElement } from 'lit';\n\ntype DirectionTypes = 'horizontal' | 'vertical' | 'both' | 'grid';\nexport type FocusGroupConfig<T> = {\n hostDelegatesFocus?: boolean;\n focusInIndex?: (_elements: T[]) => number;\n direction?: DirectionTypes | (() => DirectionTypes);\n elementEnterAction?: (el: T) => void;\n elements: () => T[];\n isFocusableElement?: (el: T) => boolean;\n listenerScope?: HTMLElement | (() => HTMLElement);\n};\n\nfunction ensureMethod<T, RT>(\n value: T | RT | undefined,\n type: string,\n fallback: T\n): T {\n if (typeof value === type) {\n return (() => value) as T;\n } else if (typeof value === 'function') {\n return value as T;\n }\n return fallback;\n}\n\nexport class FocusGroupController<T extends HTMLElement>\n implements ReactiveController\n{\n protected cachedElements?: T[];\n private mutationObserver: MutationObserver;\n\n get currentIndex(): number {\n if (this._currentIndex === -1) {\n this._currentIndex = this.focusInIndex;\n }\n return this._currentIndex - this.offset;\n }\n\n set currentIndex(currentIndex) {\n this._currentIndex = currentIndex + this.offset;\n }\n\n private _currentIndex = -1;\n\n private prevIndex = -1;\n\n get direction(): DirectionTypes {\n return this._direction();\n }\n\n _direction = (): DirectionTypes => 'both';\n\n public directionLength = 5;\n\n public hostDelegatesFocus = false;\n\n elementEnterAction = (_el: T): void => {\n return;\n };\n\n get elements(): T[] {\n if (!this.cachedElements) {\n this.cachedElements = this._elements();\n }\n return this.cachedElements;\n }\n\n private _elements!: () => T[];\n\n protected set focused(focused: boolean) {\n /* c8 ignore next 1 */\n if (focused === this.focused) return;\n this._focused = focused;\n }\n\n protected get focused(): boolean {\n return this._focused;\n }\n\n private _focused = false;\n\n get focusInElement(): T {\n return this.elements[this.focusInIndex];\n }\n\n get focusInIndex(): number {\n return this._focusInIndex(this.elements);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n _focusInIndex = (_elements: T[]): number => 0;\n\n host: ReactiveElement;\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n isFocusableElement = (_el: T): boolean => true;\n\n isEventWithinListenerScope(event: Event): boolean {\n if (this._listenerScope() === this.host) return true;\n return event.composedPath().includes(this._listenerScope());\n }\n\n _listenerScope = (): HTMLElement => this.host;\n\n // When elements are virtualized, the delta between the first element\n // and the first rendered element.\n offset = 0;\n\n recentlyConnected = false;\n\n constructor(\n host: ReactiveElement,\n {\n hostDelegatesFocus,\n direction,\n elementEnterAction,\n elements,\n focusInIndex,\n isFocusableElement,\n listenerScope,\n }: FocusGroupConfig<T> = { elements: () => [] }\n ) {\n this.mutationObserver = new MutationObserver(() => {\n this.handleItemMutation();\n });\n this.hostDelegatesFocus = hostDelegatesFocus || false;\n this.host = host;\n this.host.addController(this);\n this._elements = elements;\n this.isFocusableElement = isFocusableElement || this.isFocusableElement;\n this._direction = ensureMethod<() => DirectionTypes, DirectionTypes>(\n direction,\n 'string',\n this._direction\n );\n this.elementEnterAction = elementEnterAction || this.elementEnterAction;\n this._focusInIndex = ensureMethod<(_elements: T[]) => number, number>(\n focusInIndex,\n 'number',\n this._focusInIndex\n );\n this._listenerScope = ensureMethod<() => HTMLElement, HTMLElement>(\n listenerScope,\n 'object',\n this._listenerScope\n );\n }\n /* In handleItemMutation() method the first if condition is checking if the element is not focused or if the element's children's length is not decreasing then it means no element has been deleted and we must return.\n Then we are checking if the deleted element was the focused one before the deletion if so then we need to proceed else we casn return;\n */\n handleItemMutation(): void {\n if (\n this._currentIndex == -1 ||\n this.elements.length <= this._elements().length\n )\n return;\n const focusedElement = this.elements[this.currentIndex];\n this.clearElementCache();\n if (this.elements.includes(focusedElement)) return;\n const moveToNextElement = this.currentIndex !== this.elements.length;\n const diff = moveToNextElement ? 1 : -1;\n if (moveToNextElement) {\n this.setCurrentIndexCircularly(-1);\n }\n this.setCurrentIndexCircularly(diff);\n this.focus();\n }\n\n update({ elements }: FocusGroupConfig<T> = { elements: () => [] }): void {\n this.unmanage();\n this._elements = elements;\n this.clearElementCache();\n this.manage();\n }\n\n /**\n * resets the focusedItem to initial item\n */\n reset(): void {\n const elements = this.elements;\n if (!elements.length) return;\n this.setCurrentIndexCircularly(this.focusInIndex - this.currentIndex);\n let focusElement = elements[this.currentIndex];\n if (this.currentIndex < 0) {\n return;\n }\n if (!focusElement || !this.isFocusableElement(focusElement)) {\n this.setCurrentIndexCircularly(1);\n focusElement = elements[this.currentIndex];\n }\n if (focusElement && this.isFocusableElement(focusElement)) {\n elements[this.prevIndex]?.setAttribute('tabindex', '-1');\n focusElement.setAttribute('tabindex', '0');\n }\n }\n\n focusOnItem(item?: T, options?: FocusOptions): void {\n const elements = this.elements || [];\n const newIndex: number =\n !item || !this.isFocusableElement(item)\n ? -1\n : elements.indexOf(item);\n if (newIndex > -1) {\n this.currentIndex = newIndex;\n elements[this.prevIndex]?.setAttribute('tabindex', '-1');\n }\n this.focus(options);\n }\n\n focus(options?: FocusOptions): void {\n const elements = this.elements;\n if (!elements.length) return;\n let focusElement = elements[this.currentIndex];\n if (!focusElement || !this.isFocusableElement(focusElement)) {\n this.setCurrentIndexCircularly(1);\n focusElement = elements[this.currentIndex];\n }\n if (focusElement && this.isFocusableElement(focusElement)) {\n if (\n !this.hostDelegatesFocus ||\n elements[this.prevIndex] !== focusElement\n ) {\n elements[this.prevIndex]?.setAttribute('tabindex', '-1');\n }\n focusElement.tabIndex = 0;\n focusElement.focus(options);\n if (this.hostDelegatesFocus && !this.focused) {\n this.hostContainsFocus();\n }\n }\n }\n\n clearElementCache(offset = 0): void {\n this.mutationObserver.disconnect();\n delete this.cachedElements;\n this.offset = offset;\n requestAnimationFrame(() => {\n this.elements.forEach((element) => {\n this.mutationObserver.observe(element, {\n attributes: true,\n });\n });\n });\n }\n\n setCurrentIndexCircularly(diff: number): void {\n const { length } = this.elements;\n let steps = length;\n this.prevIndex = this.currentIndex;\n // start at a possibly not 0 index\n let nextIndex = (length + this.currentIndex + diff) % length;\n while (\n // don't cycle the elements more than once\n steps &&\n this.elements[nextIndex] &&\n !this.isFocusableElement(this.elements[nextIndex])\n ) {\n nextIndex = (length + nextIndex + diff) % length;\n steps -= 1;\n }\n this.currentIndex = nextIndex;\n }\n\n hostContainsFocus(): void {\n this.host.addEventListener('focusout', this.handleFocusout);\n this.host.addEventListener('keydown', this.handleKeydown);\n this.focused = true;\n }\n\n hostNoLongerContainsFocus(): void {\n this.host.addEventListener('focusin', this.handleFocusin);\n this.host.removeEventListener('focusout', this.handleFocusout);\n this.host.removeEventListener('keydown', this.handleKeydown);\n this.focused = false;\n }\n\n isRelatedTargetOrContainAnElement(event: FocusEvent): boolean {\n const relatedTarget = event.relatedTarget as null | Element;\n\n const isRelatedTargetAnElement = this.elements.includes(\n relatedTarget as T\n );\n const isRelatedTargetContainedWithinElements = this.elements.some(\n (el) => el.contains(relatedTarget)\n );\n return !(\n isRelatedTargetAnElement || isRelatedTargetContainedWithinElements\n );\n }\n\n handleFocusin = (event: FocusEvent): void => {\n if (!this.isEventWithinListenerScope(event)) return;\n\n const path = event.composedPath() as T[];\n let targetIndex = -1;\n path.find((el) => {\n targetIndex = this.elements.indexOf(el);\n return targetIndex !== -1;\n });\n this.prevIndex = this.currentIndex;\n this.currentIndex = targetIndex > -1 ? targetIndex : this.currentIndex;\n\n if (this.isRelatedTargetOrContainAnElement(event)) {\n this.hostContainsFocus();\n }\n };\n\n /**\n * handleClick - Finds the element that was clicked and sets the tabindex to 0\n * @returns void\n */\n handleClick = (): void => {\n // Manually set the tabindex to 0 for the current element on receiving focus (from keyboard or mouse)\n const elements = this.elements;\n if (!elements.length) return;\n let focusElement = elements[this.currentIndex];\n if (this.currentIndex < 0) {\n return;\n }\n if (!focusElement || !this.isFocusableElement(focusElement)) {\n this.setCurrentIndexCircularly(1);\n focusElement = elements[this.currentIndex];\n }\n if (focusElement && this.isFocusableElement(focusElement)) {\n elements[this.prevIndex]?.setAttribute('tabindex', '-1');\n focusElement.setAttribute('tabindex', '0');\n }\n };\n\n handleFocusout = (event: FocusEvent): void => {\n if (this.isRelatedTargetOrContainAnElement(event)) {\n this.hostNoLongerContainsFocus();\n }\n };\n\n acceptsEventKey(key: string): boolean {\n if (key === 'End' || key === 'Home') {\n return true;\n }\n switch (this.direction) {\n case 'horizontal':\n return key === 'ArrowLeft' || key === 'ArrowRight';\n case 'vertical':\n return key === 'ArrowUp' || key === 'ArrowDown';\n case 'both':\n case 'grid':\n return key.startsWith('Arrow');\n }\n }\n\n handleKeydown = (event: KeyboardEvent): void => {\n if (!this.acceptsEventKey(event.key) || event.defaultPrevented) {\n return;\n }\n let diff = 0;\n this.prevIndex = this.currentIndex;\n switch (event.key) {\n case 'ArrowRight':\n diff += 1;\n break;\n case 'ArrowDown':\n diff += this.direction === 'grid' ? this.directionLength : 1;\n break;\n case 'ArrowLeft':\n diff -= 1;\n break;\n case 'ArrowUp':\n diff -= this.direction === 'grid' ? this.directionLength : 1;\n break;\n case 'End':\n this.currentIndex = 0;\n diff -= 1;\n break;\n case 'Home':\n this.currentIndex = this.elements.length - 1;\n diff += 1;\n break;\n }\n event.preventDefault();\n if (this.direction === 'grid' && this.currentIndex + diff < 0) {\n this.currentIndex = 0;\n } else if (\n this.direction === 'grid' &&\n this.currentIndex + diff > this.elements.length - 1\n ) {\n this.currentIndex = this.elements.length - 1;\n } else {\n this.setCurrentIndexCircularly(diff);\n }\n // To allow the `focusInIndex` to be calculated with the \"after\" state of the keyboard interaction\n // do `elementEnterAction` _before_ focusing the next element.\n this.elementEnterAction(this.elements[this.currentIndex]);\n this.focus();\n };\n\n manage(): void {\n this.addEventListeners();\n }\n\n unmanage(): void {\n this.removeEventListeners();\n }\n\n addEventListeners(): void {\n this.host.addEventListener('focusin', this.handleFocusin);\n this.host.addEventListener('click', this.handleClick);\n }\n\n removeEventListeners(): void {\n this.host.removeEventListener('focusin', this.handleFocusin);\n this.host.removeEventListener('focusout', this.handleFocusout);\n this.host.removeEventListener('keydown', this.handleKeydown);\n this.host.removeEventListener('click', this.handleClick);\n }\n\n hostConnected(): void {\n this.recentlyConnected = true;\n this.addEventListeners();\n }\n\n hostDisconnected(): void {\n this.mutationObserver.disconnect();\n this.removeEventListeners();\n }\n\n hostUpdated(): void {\n if (this.recentlyConnected) {\n this.recentlyConnected = false;\n this.elements.forEach((element) => {\n this.mutationObserver.observe(element, {\n attributes: true,\n });\n });\n }\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 */\nimport type { ReactiveController, ReactiveElement } from 'lit';\n\ntype DirectionTypes = 'horizontal' | 'vertical' | 'both' | 'grid';\nexport type FocusGroupConfig<T> = {\n hostDelegatesFocus?: boolean;\n focusInIndex?: (_elements: T[]) => number;\n direction?: DirectionTypes | (() => DirectionTypes);\n elementEnterAction?: (el: T) => void;\n elements: () => T[];\n isFocusableElement?: (el: T) => boolean;\n listenerScope?: HTMLElement | (() => HTMLElement);\n};\n\nfunction ensureMethod<T, RT>(\n value: T | RT | undefined,\n type: string,\n fallback: T\n): T {\n if (typeof value === type) {\n return (() => value) as T;\n } else if (typeof value === 'function') {\n return value as T;\n }\n return fallback;\n}\n\nexport class FocusGroupController<T extends HTMLElement>\n implements ReactiveController\n{\n protected cachedElements?: T[];\n private mutationObserver: MutationObserver;\n\n get currentIndex(): number {\n if (this._currentIndex === -1) {\n this._currentIndex = this.focusInIndex;\n }\n return this._currentIndex - this.offset;\n }\n\n set currentIndex(currentIndex) {\n this._currentIndex = currentIndex + this.offset;\n }\n\n private _currentIndex = -1;\n\n private prevIndex = -1;\n\n get direction(): DirectionTypes {\n return this._direction();\n }\n\n _direction = (): DirectionTypes => 'both';\n\n public directionLength = 5;\n\n public hostDelegatesFocus = false;\n\n elementEnterAction = (_el: T): void => {\n return;\n };\n\n get elements(): T[] {\n if (!this.cachedElements) {\n this.cachedElements = this._elements();\n }\n return this.cachedElements;\n }\n\n private _elements!: () => T[];\n\n protected set focused(focused: boolean) {\n /* c8 ignore next 1 */\n if (focused === this.focused) return;\n this._focused = focused;\n }\n\n protected get focused(): boolean {\n return this._focused;\n }\n\n private _focused = false;\n\n get focusInElement(): T {\n return this.elements[this.focusInIndex];\n }\n\n get focusInIndex(): number {\n return this._focusInIndex(this.elements);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n _focusInIndex = (_elements: T[]): number => 0;\n\n host: ReactiveElement;\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n isFocusableElement = (_el: T): boolean => true;\n\n isEventWithinListenerScope(event: Event): boolean {\n if (this._listenerScope() === this.host) return true;\n return event.composedPath().includes(this._listenerScope());\n }\n\n _listenerScope = (): HTMLElement => this.host;\n\n // When elements are virtualized, the delta between the first element\n // and the first rendered element.\n offset = 0;\n\n recentlyConnected = false;\n\n constructor(\n host: ReactiveElement,\n {\n hostDelegatesFocus,\n direction,\n elementEnterAction,\n elements,\n focusInIndex,\n isFocusableElement,\n listenerScope,\n }: FocusGroupConfig<T> = { elements: () => [] }\n ) {\n this.mutationObserver = new MutationObserver(() => {\n this.handleItemMutation();\n });\n this.hostDelegatesFocus = hostDelegatesFocus || false;\n this.host = host;\n this.host.addController(this);\n this._elements = elements;\n this.isFocusableElement = isFocusableElement || this.isFocusableElement;\n this._direction = ensureMethod<() => DirectionTypes, DirectionTypes>(\n direction,\n 'string',\n this._direction\n );\n this.elementEnterAction = elementEnterAction || this.elementEnterAction;\n this._focusInIndex = ensureMethod<(_elements: T[]) => number, number>(\n focusInIndex,\n 'number',\n this._focusInIndex\n );\n this._listenerScope = ensureMethod<() => HTMLElement, HTMLElement>(\n listenerScope,\n 'object',\n this._listenerScope\n );\n }\n /* In handleItemMutation() method the first if condition is checking if the element is not focused or if the element's children's length is not decreasing then it means no element has been deleted and we must return.\n Then we are checking if the deleted element was the focused one before the deletion if so then we need to proceed else we casn return;\n */\n handleItemMutation(): void {\n if (\n this._currentIndex == -1 ||\n this.elements.length <= this._elements().length\n )\n return;\n const focusedElement = this.elements[this.currentIndex];\n this.clearElementCache();\n if (this.elements.includes(focusedElement)) return;\n const moveToNextElement = this.currentIndex !== this.elements.length;\n const diff = moveToNextElement ? 1 : -1;\n if (moveToNextElement) {\n this.setCurrentIndexCircularly(-1);\n }\n this.setCurrentIndexCircularly(diff);\n this.focus();\n }\n\n update({ elements }: FocusGroupConfig<T> = { elements: () => [] }): void {\n this.unmanage();\n this._elements = elements;\n this.clearElementCache();\n this.manage();\n }\n\n /**\n * resets the focusedItem to initial item\n */\n reset(): void {\n const elements = this.elements;\n if (!elements.length) return;\n this.setCurrentIndexCircularly(this.focusInIndex - this.currentIndex);\n let focusElement = elements[this.currentIndex];\n if (this.currentIndex < 0) {\n return;\n }\n if (!focusElement || !this.isFocusableElement(focusElement)) {\n this.setCurrentIndexCircularly(1);\n focusElement = elements[this.currentIndex];\n }\n if (focusElement && this.isFocusableElement(focusElement)) {\n elements[this.prevIndex]?.setAttribute('tabindex', '-1');\n focusElement.setAttribute('tabindex', '0');\n }\n }\n\n focusOnItem(item?: T, options?: FocusOptions): void {\n const elements = this.elements || [];\n const newIndex: number =\n !item || !this.isFocusableElement(item)\n ? -1\n : elements.indexOf(item);\n if (newIndex > -1) {\n this.currentIndex = newIndex;\n elements[this.prevIndex]?.setAttribute('tabindex', '-1');\n }\n this.focus(options);\n }\n\n focus(options?: FocusOptions): void {\n const elements = this.elements;\n if (!elements.length) return;\n let focusElement = elements[this.currentIndex];\n if (!focusElement || !this.isFocusableElement(focusElement)) {\n this.setCurrentIndexCircularly(1);\n focusElement = elements[this.currentIndex];\n }\n if (focusElement && this.isFocusableElement(focusElement)) {\n if (\n !this.hostDelegatesFocus ||\n elements[this.prevIndex] !== focusElement\n ) {\n elements[this.prevIndex]?.setAttribute('tabindex', '-1');\n }\n focusElement.tabIndex = 0;\n focusElement.focus(options);\n if (this.hostDelegatesFocus && !this.focused) {\n this.hostContainsFocus();\n }\n }\n }\n\n clearElementCache(offset = 0): void {\n this.mutationObserver.disconnect();\n delete this.cachedElements;\n this.offset = offset;\n requestAnimationFrame(() => {\n this.elements.forEach((element) => {\n this.mutationObserver.observe(element, {\n attributes: true,\n });\n });\n });\n }\n\n setCurrentIndexCircularly(diff: number): void {\n const { length } = this.elements;\n let steps = length;\n this.prevIndex = this.currentIndex;\n // start at a possibly not 0 index\n let nextIndex = (length + this.currentIndex + diff) % length;\n while (\n // don't cycle the elements more than once\n steps &&\n this.elements[nextIndex] &&\n !this.isFocusableElement(this.elements[nextIndex])\n ) {\n nextIndex = (length + nextIndex + diff) % length;\n steps -= 1;\n }\n this.currentIndex = nextIndex;\n }\n\n hostContainsFocus(): void {\n this.host.addEventListener('focusout', this.handleFocusout);\n this.host.addEventListener('keydown', this.handleKeydown);\n this.focused = true;\n }\n\n hostNoLongerContainsFocus(): void {\n this.host.addEventListener('focusin', this.handleFocusin);\n this.host.removeEventListener('focusout', this.handleFocusout);\n this.host.removeEventListener('keydown', this.handleKeydown);\n this.focused = false;\n }\n\n isRelatedTargetOrContainAnElement(event: FocusEvent): boolean {\n const relatedTarget = event.relatedTarget as null | Element;\n\n const isRelatedTargetAnElement = this.elements.includes(\n relatedTarget as T\n );\n const isRelatedTargetContainedWithinElements = this.elements.some(\n (el) => el.contains(relatedTarget)\n );\n return !(\n isRelatedTargetAnElement || isRelatedTargetContainedWithinElements\n );\n }\n\n handleFocusin = (event: FocusEvent): void => {\n if (!this.isEventWithinListenerScope(event)) return;\n\n const path = event.composedPath() as T[];\n let targetIndex = -1;\n path.find((el) => {\n targetIndex = this.elements.indexOf(el);\n return targetIndex !== -1;\n });\n this.prevIndex = this.currentIndex;\n this.currentIndex = targetIndex > -1 ? targetIndex : this.currentIndex;\n\n if (this.isRelatedTargetOrContainAnElement(event)) {\n this.hostContainsFocus();\n }\n };\n\n /**\n * handleClick - Finds the element that was clicked and sets the tabindex to 0\n * @returns void\n */\n handleClick = (): void => {\n // Manually set the tabindex to 0 for the current element on receiving focus (from keyboard or mouse)\n const elements = this.elements;\n if (!elements.length) return;\n let focusElement = elements[this.currentIndex];\n if (this.currentIndex < 0) {\n return;\n }\n if (!focusElement || !this.isFocusableElement(focusElement)) {\n this.setCurrentIndexCircularly(1);\n focusElement = elements[this.currentIndex];\n }\n if (focusElement && this.isFocusableElement(focusElement)) {\n elements[this.prevIndex]?.setAttribute('tabindex', '-1');\n focusElement.setAttribute('tabindex', '0');\n }\n };\n\n handleFocusout = (event: FocusEvent): void => {\n if (this.isRelatedTargetOrContainAnElement(event)) {\n this.hostNoLongerContainsFocus();\n }\n };\n\n acceptsEventKey(key: string): boolean {\n if (key === 'End' || key === 'Home') {\n return true;\n }\n switch (this.direction) {\n case 'horizontal':\n return key === 'ArrowLeft' || key === 'ArrowRight';\n case 'vertical':\n return key === 'ArrowUp' || key === 'ArrowDown';\n case 'both':\n case 'grid':\n return key.startsWith('Arrow');\n }\n }\n\n handleKeydown = (event: KeyboardEvent): void => {\n if (!this.acceptsEventKey(event.key) || event.defaultPrevented) {\n return;\n }\n let diff = 0;\n this.prevIndex = this.currentIndex;\n switch (event.key) {\n case 'ArrowRight':\n diff += 1;\n break;\n case 'ArrowDown':\n diff += this.direction === 'grid' ? this.directionLength : 1;\n break;\n case 'ArrowLeft':\n diff -= 1;\n break;\n case 'ArrowUp':\n diff -= this.direction === 'grid' ? this.directionLength : 1;\n break;\n case 'End':\n this.currentIndex = 0;\n diff -= 1;\n break;\n case 'Home':\n this.currentIndex = this.elements.length - 1;\n diff += 1;\n break;\n }\n event.preventDefault();\n if (this.direction === 'grid' && this.currentIndex + diff < 0) {\n this.currentIndex = 0;\n } else if (\n this.direction === 'grid' &&\n this.currentIndex + diff > this.elements.length - 1\n ) {\n this.currentIndex = this.elements.length - 1;\n } else {\n this.setCurrentIndexCircularly(diff);\n }\n // To allow the `focusInIndex` to be calculated with the \"after\" state of the keyboard interaction\n // do `elementEnterAction` _before_ focusing the next element.\n this.elementEnterAction(this.elements[this.currentIndex]);\n this.focus();\n };\n\n manage(): void {\n this.addEventListeners();\n }\n\n unmanage(): void {\n this.removeEventListeners();\n }\n\n addEventListeners(): void {\n this.host.addEventListener('focusin', this.handleFocusin);\n this.host.addEventListener('click', this.handleClick);\n }\n\n removeEventListeners(): void {\n this.host.removeEventListener('focusin', this.handleFocusin);\n this.host.removeEventListener('focusout', this.handleFocusout);\n this.host.removeEventListener('keydown', this.handleKeydown);\n this.host.removeEventListener('click', this.handleClick);\n }\n\n hostConnected(): void {\n this.recentlyConnected = true;\n this.addEventListeners();\n }\n\n hostDisconnected(): void {\n this.mutationObserver.disconnect();\n this.removeEventListeners();\n }\n\n hostUpdated(): void {\n if (this.recentlyConnected) {\n this.recentlyConnected = false;\n this.elements.forEach((element) => {\n this.mutationObserver.observe(element, {\n attributes: true,\n });\n });\n }\n }\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["ensureMethod", "value", "type", "fallback", "host", "hostDelegatesFocus", "direction", "elementEnterAction", "elements", "focusInIndex", "isFocusableElement", "listenerScope", "_el", "_elements", "event", "path", "targetIndex", "el", "_a", "focusElement", "diff", "currentIndex", "focused", "focusedElement", "moveToNextElement", "item", "options", "newIndex", "offset", "element", "length", "steps", "nextIndex", "relatedTarget", "isRelatedTargetAnElement", "isRelatedTargetContainedWithinElements", "key"]
7
7
  }
@@ -1,3 +1,14 @@
1
+ /**
2
+ * Copyright 2025 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
1
12
  import type { ReactiveController, ReactiveElement } from 'lit';
2
13
  export declare const languageResolverUpdatedSymbol: unique symbol;
3
14
  export declare class LanguageResolutionController implements ReactiveController {
@@ -4,7 +4,7 @@ export const languageResolverUpdatedSymbol = Symbol(
4
4
  );
5
5
  export class LanguageResolutionController {
6
6
  constructor(host) {
7
- this.language = document.documentElement.lang || navigator.language;
7
+ this.language = document.documentElement.lang || navigator.language || "en-US";
8
8
  this.host = host;
9
9
  this.host.addController(this);
10
10
  }
@@ -16,6 +16,11 @@ export class LanguageResolutionController {
16
16
  (_a = this.unsubscribe) == null ? void 0 : _a.call(this);
17
17
  }
18
18
  resolveLanguage() {
19
+ try {
20
+ Intl.DateTimeFormat.supportedLocalesOf([this.language]);
21
+ } catch (e) {
22
+ this.language = "en-US";
23
+ }
19
24
  const queryThemeEvent = new CustomEvent(
20
25
  "sp-language-context",
21
26
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["LanguageResolution.ts"],
4
- "sourcesContent": ["/*\nCopyright 2022 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\n\nimport type { ReactiveController, ReactiveElement } from 'lit';\nimport { ProvideLang } from '@spectrum-web-components/theme';\n\nexport const languageResolverUpdatedSymbol = Symbol(\n 'language resolver updated'\n);\n\nexport class LanguageResolutionController implements ReactiveController {\n private host: ReactiveElement;\n language = document.documentElement.lang || navigator.language;\n private unsubscribe?: () => void;\n\n constructor(host: ReactiveElement) {\n this.host = host;\n this.host.addController(this);\n }\n\n public hostConnected(): void {\n this.resolveLanguage();\n }\n\n public hostDisconnected(): void {\n this.unsubscribe?.();\n }\n\n private resolveLanguage(): void {\n const queryThemeEvent = new CustomEvent<ProvideLang>(\n 'sp-language-context',\n {\n bubbles: true,\n composed: true,\n detail: {\n callback: (lang: string, unsubscribe: () => void) => {\n const previous = this.language;\n this.language = lang;\n this.unsubscribe = unsubscribe;\n this.host.requestUpdate(\n languageResolverUpdatedSymbol,\n previous\n );\n },\n },\n cancelable: true,\n }\n );\n this.host.dispatchEvent(queryThemeEvent);\n }\n}\n"],
5
- "mappings": ";AAeO,aAAM,gCAAgC;AAAA,EACzC;AACJ;AAEO,aAAM,6BAA2D;AAAA,EAKpE,YAAY,MAAuB;AAHnC,oBAAW,SAAS,gBAAgB,QAAQ,UAAU;AAIlD,SAAK,OAAO;AACZ,SAAK,KAAK,cAAc,IAAI;AAAA,EAChC;AAAA,EAEO,gBAAsB;AACzB,SAAK,gBAAgB;AAAA,EACzB;AAAA,EAEO,mBAAyB;AAjCpC;AAkCQ,eAAK,gBAAL;AAAA,EACJ;AAAA,EAEQ,kBAAwB;AAC5B,UAAM,kBAAkB,IAAI;AAAA,MACxB;AAAA,MACA;AAAA,QACI,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ;AAAA,UACJ,UAAU,CAAC,MAAc,gBAA4B;AACjD,kBAAM,WAAW,KAAK;AACtB,iBAAK,WAAW;AAChB,iBAAK,cAAc;AACnB,iBAAK,KAAK;AAAA,cACN;AAAA,cACA;AAAA,YACJ;AAAA,UACJ;AAAA,QACJ;AAAA,QACA,YAAY;AAAA,MAChB;AAAA,IACJ;AACA,SAAK,KAAK,cAAc,eAAe;AAAA,EAC3C;AACJ;",
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 type { ReactiveController, ReactiveElement } from 'lit';\nimport { ProvideLang } from '@spectrum-web-components/theme';\n\nexport const languageResolverUpdatedSymbol = Symbol(\n 'language resolver updated'\n);\n\nexport class LanguageResolutionController implements ReactiveController {\n private host: ReactiveElement;\n language = document.documentElement.lang || navigator.language || 'en-US';\n private unsubscribe?: () => void;\n\n constructor(host: ReactiveElement) {\n this.host = host;\n this.host.addController(this);\n }\n\n public hostConnected(): void {\n this.resolveLanguage();\n }\n\n public hostDisconnected(): void {\n this.unsubscribe?.();\n }\n\n private resolveLanguage(): void {\n try {\n Intl.DateTimeFormat.supportedLocalesOf([this.language]);\n } catch {\n this.language = 'en-US';\n }\n const queryThemeEvent = new CustomEvent<ProvideLang>(\n 'sp-language-context',\n {\n bubbles: true,\n composed: true,\n detail: {\n callback: (lang: string, unsubscribe: () => void) => {\n const previous = this.language;\n this.language = lang;\n this.unsubscribe = unsubscribe;\n this.host.requestUpdate(\n languageResolverUpdatedSymbol,\n previous\n );\n },\n },\n cancelable: true,\n }\n );\n this.host.dispatchEvent(queryThemeEvent);\n }\n}\n"],
5
+ "mappings": ";AAeO,aAAM,gCAAgC;AAAA,EACzC;AACJ;AAEO,aAAM,6BAA2D;AAAA,EAKpE,YAAY,MAAuB;AAHnC,oBAAW,SAAS,gBAAgB,QAAQ,UAAU,YAAY;AAI9D,SAAK,OAAO;AACZ,SAAK,KAAK,cAAc,IAAI;AAAA,EAChC;AAAA,EAEO,gBAAsB;AACzB,SAAK,gBAAgB;AAAA,EACzB;AAAA,EAEO,mBAAyB;AAjCpC;AAkCQ,eAAK,gBAAL;AAAA,EACJ;AAAA,EAEQ,kBAAwB;AAC5B,QAAI;AACA,WAAK,eAAe,mBAAmB,CAAC,KAAK,QAAQ,CAAC;AAAA,IAC1D,SAAQ;AACJ,WAAK,WAAW;AAAA,IACpB;AACA,UAAM,kBAAkB,IAAI;AAAA,MACxB;AAAA,MACA;AAAA,QACI,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ;AAAA,UACJ,UAAU,CAAC,MAAc,gBAA4B;AACjD,kBAAM,WAAW,KAAK;AACtB,iBAAK,WAAW;AAChB,iBAAK,cAAc;AACnB,iBAAK,KAAK;AAAA,cACN;AAAA,cACA;AAAA,YACJ;AAAA,UACJ;AAAA,QACJ;AAAA,QACA,YAAY;AAAA,MAChB;AAAA,IACJ;AACA,SAAK,KAAK,cAAc,eAAe;AAAA,EAC3C;AACJ;",
6
6
  "names": []
7
7
  }
@@ -1,2 +1,2 @@
1
- "use strict";export const languageResolverUpdatedSymbol=Symbol("language resolver updated");export class LanguageResolutionController{constructor(e){this.language=document.documentElement.lang||navigator.language;this.host=e,this.host.addController(this)}hostConnected(){this.resolveLanguage()}hostDisconnected(){var e;(e=this.unsubscribe)==null||e.call(this)}resolveLanguage(){const e=new CustomEvent("sp-language-context",{bubbles:!0,composed:!0,detail:{callback:(t,o)=>{const a=this.language;this.language=t,this.unsubscribe=o,this.host.requestUpdate(languageResolverUpdatedSymbol,a)}},cancelable:!0});this.host.dispatchEvent(e)}}
1
+ "use strict";export const languageResolverUpdatedSymbol=Symbol("language resolver updated");export class LanguageResolutionController{constructor(e){this.language=document.documentElement.lang||navigator.language||"en-US";this.host=e,this.host.addController(this)}hostConnected(){this.resolveLanguage()}hostDisconnected(){var e;(e=this.unsubscribe)==null||e.call(this)}resolveLanguage(){try{Intl.DateTimeFormat.supportedLocalesOf([this.language])}catch(t){this.language="en-US"}const e=new CustomEvent("sp-language-context",{bubbles:!0,composed:!0,detail:{callback:(t,o)=>{const a=this.language;this.language=t,this.unsubscribe=o,this.host.requestUpdate(languageResolverUpdatedSymbol,a)}},cancelable:!0});this.host.dispatchEvent(e)}}
2
2
  //# sourceMappingURL=LanguageResolution.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["LanguageResolution.ts"],
4
- "sourcesContent": ["/*\nCopyright 2022 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\n\nimport type { ReactiveController, ReactiveElement } from 'lit';\nimport { ProvideLang } from '@spectrum-web-components/theme';\n\nexport const languageResolverUpdatedSymbol = Symbol(\n 'language resolver updated'\n);\n\nexport class LanguageResolutionController implements ReactiveController {\n private host: ReactiveElement;\n language = document.documentElement.lang || navigator.language;\n private unsubscribe?: () => void;\n\n constructor(host: ReactiveElement) {\n this.host = host;\n this.host.addController(this);\n }\n\n public hostConnected(): void {\n this.resolveLanguage();\n }\n\n public hostDisconnected(): void {\n this.unsubscribe?.();\n }\n\n private resolveLanguage(): void {\n const queryThemeEvent = new CustomEvent<ProvideLang>(\n 'sp-language-context',\n {\n bubbles: true,\n composed: true,\n detail: {\n callback: (lang: string, unsubscribe: () => void) => {\n const previous = this.language;\n this.language = lang;\n this.unsubscribe = unsubscribe;\n this.host.requestUpdate(\n languageResolverUpdatedSymbol,\n previous\n );\n },\n },\n cancelable: true,\n }\n );\n this.host.dispatchEvent(queryThemeEvent);\n }\n}\n"],
5
- "mappings": "aAeO,aAAM,8BAAgC,OACzC,2BACJ,EAEO,aAAM,4BAA2D,CAKpE,YAAYA,EAAuB,CAHnC,cAAW,SAAS,gBAAgB,MAAQ,UAAU,SAIlD,KAAK,KAAOA,EACZ,KAAK,KAAK,cAAc,IAAI,CAChC,CAEO,eAAsB,CACzB,KAAK,gBAAgB,CACzB,CAEO,kBAAyB,CAjCpC,IAAAC,GAkCQA,EAAA,KAAK,cAAL,MAAAA,EAAA,UACJ,CAEQ,iBAAwB,CAC5B,MAAMC,EAAkB,IAAI,YACxB,sBACA,CACI,QAAS,GACT,SAAU,GACV,OAAQ,CACJ,SAAU,CAACC,EAAcC,IAA4B,CACjD,MAAMC,EAAW,KAAK,SACtB,KAAK,SAAWF,EAChB,KAAK,YAAcC,EACnB,KAAK,KAAK,cACN,8BACAC,CACJ,CACJ,CACJ,EACA,WAAY,EAChB,CACJ,EACA,KAAK,KAAK,cAAcH,CAAe,CAC3C,CACJ",
6
- "names": ["host", "_a", "queryThemeEvent", "lang", "unsubscribe", "previous"]
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 type { ReactiveController, ReactiveElement } from 'lit';\nimport { ProvideLang } from '@spectrum-web-components/theme';\n\nexport const languageResolverUpdatedSymbol = Symbol(\n 'language resolver updated'\n);\n\nexport class LanguageResolutionController implements ReactiveController {\n private host: ReactiveElement;\n language = document.documentElement.lang || navigator.language || 'en-US';\n private unsubscribe?: () => void;\n\n constructor(host: ReactiveElement) {\n this.host = host;\n this.host.addController(this);\n }\n\n public hostConnected(): void {\n this.resolveLanguage();\n }\n\n public hostDisconnected(): void {\n this.unsubscribe?.();\n }\n\n private resolveLanguage(): void {\n try {\n Intl.DateTimeFormat.supportedLocalesOf([this.language]);\n } catch {\n this.language = 'en-US';\n }\n const queryThemeEvent = new CustomEvent<ProvideLang>(\n 'sp-language-context',\n {\n bubbles: true,\n composed: true,\n detail: {\n callback: (lang: string, unsubscribe: () => void) => {\n const previous = this.language;\n this.language = lang;\n this.unsubscribe = unsubscribe;\n this.host.requestUpdate(\n languageResolverUpdatedSymbol,\n previous\n );\n },\n },\n cancelable: true,\n }\n );\n this.host.dispatchEvent(queryThemeEvent);\n }\n}\n"],
5
+ "mappings": "aAeO,aAAM,8BAAgC,OACzC,2BACJ,EAEO,aAAM,4BAA2D,CAKpE,YAAYA,EAAuB,CAHnC,cAAW,SAAS,gBAAgB,MAAQ,UAAU,UAAY,QAI9D,KAAK,KAAOA,EACZ,KAAK,KAAK,cAAc,IAAI,CAChC,CAEO,eAAsB,CACzB,KAAK,gBAAgB,CACzB,CAEO,kBAAyB,CAjCpC,IAAAC,GAkCQA,EAAA,KAAK,cAAL,MAAAA,EAAA,UACJ,CAEQ,iBAAwB,CAC5B,GAAI,CACA,KAAK,eAAe,mBAAmB,CAAC,KAAK,QAAQ,CAAC,CAC1D,OAAQC,EAAA,CACJ,KAAK,SAAW,OACpB,CACA,MAAMC,EAAkB,IAAI,YACxB,sBACA,CACI,QAAS,GACT,SAAU,GACV,OAAQ,CACJ,SAAU,CAACC,EAAcC,IAA4B,CACjD,MAAMC,EAAW,KAAK,SACtB,KAAK,SAAWF,EAChB,KAAK,YAAcC,EACnB,KAAK,KAAK,cACN,8BACAC,CACJ,CACJ,CACJ,EACA,WAAY,EAChB,CACJ,EACA,KAAK,KAAK,cAAcH,CAAe,CAC3C,CACJ",
6
+ "names": ["host", "_a", "e", "queryThemeEvent", "lang", "unsubscribe", "previous"]
7
7
  }
@@ -1,3 +1,14 @@
1
+ /**
2
+ * Copyright 2025 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
1
12
  import type { ReactiveController, ReactiveElement } from 'lit';
2
13
  export declare const DARK_MODE = "(prefers-color-scheme: dark)";
3
14
  export declare const IS_MOBILE = "(max-width: 743px) and (hover: none) and (pointer: coarse)";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["MatchMedia.ts"],
4
- "sourcesContent": ["/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\nimport type { ReactiveController, ReactiveElement } from 'lit';\n\nexport const DARK_MODE = '(prefers-color-scheme: dark)';\nexport const IS_MOBILE =\n '(max-width: 743px) and (hover: none) and (pointer: coarse)';\n\nexport class MatchMediaController implements ReactiveController {\n key = Symbol('match-media-key');\n\n matches = false;\n\n protected host: ReactiveElement;\n\n protected media: MediaQueryList;\n\n constructor(host: ReactiveElement, query: string) {\n this.host = host;\n this.host.addController(this);\n this.media = window.matchMedia(query);\n this.matches = this.media.matches;\n this.onChange = this.onChange.bind(this);\n host.addController(this);\n }\n\n public hostConnected(): void {\n this.media?.addEventListener('change', this.onChange);\n }\n\n public hostDisconnected(): void {\n this.media?.removeEventListener('change', this.onChange);\n }\n\n protected onChange(event: MediaQueryListEvent): void {\n if (this.matches === event.matches) return;\n this.matches = event.matches;\n this.host.requestUpdate(this.key, !this.matches);\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 */\nimport type { ReactiveController, ReactiveElement } from 'lit';\n\nexport const DARK_MODE = '(prefers-color-scheme: dark)';\nexport const IS_MOBILE =\n '(max-width: 743px) and (hover: none) and (pointer: coarse)';\n\nexport class MatchMediaController implements ReactiveController {\n key = Symbol('match-media-key');\n\n matches = false;\n\n protected host: ReactiveElement;\n\n protected media: MediaQueryList;\n\n constructor(host: ReactiveElement, query: string) {\n this.host = host;\n this.host.addController(this);\n this.media = window.matchMedia(query);\n this.matches = this.media.matches;\n this.onChange = this.onChange.bind(this);\n host.addController(this);\n }\n\n public hostConnected(): void {\n this.media?.addEventListener('change', this.onChange);\n }\n\n public hostDisconnected(): void {\n this.media?.removeEventListener('change', this.onChange);\n }\n\n protected onChange(event: MediaQueryListEvent): void {\n if (this.matches === event.matches) return;\n this.matches = event.matches;\n this.host.requestUpdate(this.key, !this.matches);\n }\n}\n"],
5
5
  "mappings": ";AAaO,aAAM,YAAY;AAClB,aAAM,YACT;AAEG,aAAM,qBAAmD;AAAA,EAS5D,YAAY,MAAuB,OAAe;AARlD,eAAM,OAAO,iBAAiB;AAE9B,mBAAU;AAON,SAAK,OAAO;AACZ,SAAK,KAAK,cAAc,IAAI;AAC5B,SAAK,QAAQ,OAAO,WAAW,KAAK;AACpC,SAAK,UAAU,KAAK,MAAM;AAC1B,SAAK,WAAW,KAAK,SAAS,KAAK,IAAI;AACvC,SAAK,cAAc,IAAI;AAAA,EAC3B;AAAA,EAEO,gBAAsB;AAnCjC;AAoCQ,eAAK,UAAL,mBAAY,iBAAiB,UAAU,KAAK;AAAA,EAChD;AAAA,EAEO,mBAAyB;AAvCpC;AAwCQ,eAAK,UAAL,mBAAY,oBAAoB,UAAU,KAAK;AAAA,EACnD;AAAA,EAEU,SAAS,OAAkC;AACjD,QAAI,KAAK,YAAY,MAAM,QAAS;AACpC,SAAK,UAAU,MAAM;AACrB,SAAK,KAAK,cAAc,KAAK,KAAK,CAAC,KAAK,OAAO;AAAA,EACnD;AACJ;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["MatchMedia.ts"],
4
- "sourcesContent": ["/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\nimport type { ReactiveController, ReactiveElement } from 'lit';\n\nexport const DARK_MODE = '(prefers-color-scheme: dark)';\nexport const IS_MOBILE =\n '(max-width: 743px) and (hover: none) and (pointer: coarse)';\n\nexport class MatchMediaController implements ReactiveController {\n key = Symbol('match-media-key');\n\n matches = false;\n\n protected host: ReactiveElement;\n\n protected media: MediaQueryList;\n\n constructor(host: ReactiveElement, query: string) {\n this.host = host;\n this.host.addController(this);\n this.media = window.matchMedia(query);\n this.matches = this.media.matches;\n this.onChange = this.onChange.bind(this);\n host.addController(this);\n }\n\n public hostConnected(): void {\n this.media?.addEventListener('change', this.onChange);\n }\n\n public hostDisconnected(): void {\n this.media?.removeEventListener('change', this.onChange);\n }\n\n protected onChange(event: MediaQueryListEvent): void {\n if (this.matches === event.matches) return;\n this.matches = event.matches;\n this.host.requestUpdate(this.key, !this.matches);\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 */\nimport type { ReactiveController, ReactiveElement } from 'lit';\n\nexport const DARK_MODE = '(prefers-color-scheme: dark)';\nexport const IS_MOBILE =\n '(max-width: 743px) and (hover: none) and (pointer: coarse)';\n\nexport class MatchMediaController implements ReactiveController {\n key = Symbol('match-media-key');\n\n matches = false;\n\n protected host: ReactiveElement;\n\n protected media: MediaQueryList;\n\n constructor(host: ReactiveElement, query: string) {\n this.host = host;\n this.host.addController(this);\n this.media = window.matchMedia(query);\n this.matches = this.media.matches;\n this.onChange = this.onChange.bind(this);\n host.addController(this);\n }\n\n public hostConnected(): void {\n this.media?.addEventListener('change', this.onChange);\n }\n\n public hostDisconnected(): void {\n this.media?.removeEventListener('change', this.onChange);\n }\n\n protected onChange(event: MediaQueryListEvent): void {\n if (this.matches === event.matches) return;\n this.matches = event.matches;\n this.host.requestUpdate(this.key, !this.matches);\n }\n}\n"],
5
5
  "mappings": "aAaO,aAAM,UAAY,+BACZ,UACT,6DAEG,aAAM,oBAAmD,CAS5D,YAAYA,EAAuBC,EAAe,CARlD,SAAM,OAAO,iBAAiB,EAE9B,aAAU,GAON,KAAK,KAAOD,EACZ,KAAK,KAAK,cAAc,IAAI,EAC5B,KAAK,MAAQ,OAAO,WAAWC,CAAK,EACpC,KAAK,QAAU,KAAK,MAAM,QAC1B,KAAK,SAAW,KAAK,SAAS,KAAK,IAAI,EACvCD,EAAK,cAAc,IAAI,CAC3B,CAEO,eAAsB,CAnCjC,IAAAE,GAoCQA,EAAA,KAAK,QAAL,MAAAA,EAAY,iBAAiB,SAAU,KAAK,SAChD,CAEO,kBAAyB,CAvCpC,IAAAA,GAwCQA,EAAA,KAAK,QAAL,MAAAA,EAAY,oBAAoB,SAAU,KAAK,SACnD,CAEU,SAASC,EAAkC,CAC7C,KAAK,UAAYA,EAAM,UAC3B,KAAK,QAAUA,EAAM,QACrB,KAAK,KAAK,cAAc,KAAK,IAAK,CAAC,KAAK,OAAO,EACnD,CACJ",
6
6
  "names": ["host", "query", "_a", "event"]
7
7
  }
@@ -1,7 +1,22 @@
1
- import { LitElement, ReactiveController, TemplateResult } from 'lit';
1
+ /**
2
+ * Copyright 2025 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
2
12
  import '@spectrum-web-components/progress-circle/sp-progress-circle.js';
13
+ import { LitElement, ReactiveController, TemplateResult } from 'lit';
3
14
  /**
4
- * Represents a host element with pending state.
15
+ * Renders a pending state visual element and manages the aria-label of the host element.
16
+ *
17
+ * 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.
18
+ *
19
+ * @TODO consider deprecating this controller since it is not used by any other component.
5
20
  */
6
21
  export interface HostWithPendingState extends LitElement {
7
22
  pendingLabel?: string;
@@ -28,6 +43,8 @@ export declare class PendingStateController<T extends HostWithPendingState> impl
28
43
  /**
29
44
  * Renders the pending state UI.
30
45
  * @returns A TemplateResult representing the pending state UI.
46
+ *
47
+ * @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.
31
48
  */
32
49
  renderPendingState(): TemplateResult;
33
50
  /**
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
- import { html } from "lit";
3
2
  import "@spectrum-web-components/progress-circle/sp-progress-circle.js";
3
+ import { html } from "lit";
4
4
  export class PendingStateController {
5
5
  /**
6
6
  * Creates an instance of PendingStateController.
@@ -14,16 +14,17 @@ export class PendingStateController {
14
14
  /**
15
15
  * Renders the pending state UI.
16
16
  * @returns A TemplateResult representing the pending state UI.
17
+ *
18
+ * @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.
17
19
  */
18
20
  renderPendingState() {
19
- const pendingLabel = this.host.pendingLabel || "Pending";
20
21
  return this.host.pending ? html`
21
22
  <sp-progress-circle
22
23
  id="loader"
23
24
  size="s"
24
25
  indeterminate
25
- aria-valuetext=${pendingLabel}
26
26
  class="progress-circle"
27
+ role="presentation"
27
28
  ></sp-progress-circle>
28
29
  ` : html``;
29
30
  }
@@ -34,13 +35,22 @@ export class PendingStateController {
34
35
  updateAriaLabel() {
35
36
  const { pending, disabled, pendingLabel } = this.host;
36
37
  const currentAriaLabel = this.host.getAttribute("aria-label");
37
- if (pending && !disabled && currentAriaLabel !== pendingLabel) {
38
+ function shouldCacheAriaLabel(cached, current, pending2) {
39
+ return !cached && current !== pending2 || cached !== current && current !== pending2;
40
+ }
41
+ if (shouldCacheAriaLabel(
42
+ this.cachedAriaLabel,
43
+ currentAriaLabel,
44
+ pendingLabel
45
+ )) {
38
46
  this.cachedAriaLabel = currentAriaLabel;
47
+ }
48
+ if (pending && !disabled) {
39
49
  this.host.setAttribute("aria-label", pendingLabel || "Pending");
40
- } else if (!pending || disabled) {
50
+ } else {
41
51
  if (this.cachedAriaLabel) {
42
52
  this.host.setAttribute("aria-label", this.cachedAriaLabel);
43
- } else if (!pending) {
53
+ } else {
44
54
  this.host.removeAttribute("aria-label");
45
55
  }
46
56
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["PendingState.ts"],
4
- "sourcesContent": ["/*\nCopyright 2024 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\n\nimport { html, LitElement, ReactiveController, TemplateResult } from 'lit';\nimport '@spectrum-web-components/progress-circle/sp-progress-circle.js';\n\n/**\n * Represents a host element with pending state.\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 public renderPendingState(): TemplateResult {\n const pendingLabel = this.host.pendingLabel || 'Pending';\n return this.host.pending\n ? html`\n <sp-progress-circle\n id=\"loader\"\n size=\"s\"\n indeterminate\n aria-valuetext=${pendingLabel}\n class=\"progress-circle\"\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 if (pending && !disabled && currentAriaLabel !== pendingLabel) {\n // Cache the current `aria-label` to be restored when no longer `pending`\n this.cachedAriaLabel = currentAriaLabel;\n // Since it is pending, we set the aria-label to `pendingLabel` or \"Pending\"\n this.host.setAttribute('aria-label', pendingLabel || 'Pending');\n } else if (!pending || disabled) {\n // Restore the cached `aria-label` if it exists\n if (this.cachedAriaLabel) {\n this.host.setAttribute('aria-label', this.cachedAriaLabel);\n } else if (!pending) {\n // If no cached `aria-label` and not `pending`, remove the `aria-label`\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
- "mappings": ";AAYA,SAAS,YAA4D;AACrE,OAAO;AAiBA,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,EAOO,qBAAqC;AACxC,UAAM,eAAe,KAAK,KAAK,gBAAgB;AAC/C,WAAO,KAAK,KAAK,UACX;AAAA;AAAA;AAAA;AAAA;AAAA,uCAKyB,YAAY;AAAA;AAAA;AAAA,kBAIrC;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,QAAI,WAAW,CAAC,YAAY,qBAAqB,cAAc;AAE3D,WAAK,kBAAkB;AAEvB,WAAK,KAAK,aAAa,cAAc,gBAAgB,SAAS;AAAA,IAClE,WAAW,CAAC,WAAW,UAAU;AAE7B,UAAI,KAAK,iBAAiB;AACtB,aAAK,KAAK,aAAa,cAAc,KAAK,eAAe;AAAA,MAC7D,WAAW,CAAC,SAAS;AAEjB,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
- "names": []
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
+ "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
+ "names": ["pending"]
7
7
  }
@@ -1,10 +1,10 @@
1
- "use strict";import{html as s}from"lit";import"@spectrum-web-components/progress-circle/sp-progress-circle.js";export class PendingStateController{constructor(e){this.cachedAriaLabel=null;this.host=e,this.host.addController(this)}renderPendingState(){const e=this.host.pendingLabel||"Pending";return this.host.pending?s`
1
+ "use strict";import"@spectrum-web-components/progress-circle/sp-progress-circle.js";import{html as r}from"lit";export class PendingStateController{constructor(e){this.cachedAriaLabel=null;this.host=e,this.host.addController(this)}renderPendingState(){return this.host.pending?r`
2
2
  <sp-progress-circle
3
3
  id="loader"
4
4
  size="s"
5
5
  indeterminate
6
- aria-valuetext=${e}
7
6
  class="progress-circle"
7
+ role="presentation"
8
8
  ></sp-progress-circle>
9
- `:s``}updateAriaLabel(){const{pending:e,disabled:t,pendingLabel:i}=this.host,a=this.host.getAttribute("aria-label");e&&!t&&a!==i?(this.cachedAriaLabel=a,this.host.setAttribute("aria-label",i||"Pending")):(!e||t)&&(this.cachedAriaLabel?this.host.setAttribute("aria-label",this.cachedAriaLabel):e||this.host.removeAttribute("aria-label"))}hostConnected(){this.cachedAriaLabel||(this.cachedAriaLabel=this.host.getAttribute("aria-label")),this.updateAriaLabel()}hostUpdated(){this.updateAriaLabel()}}
9
+ `:r``}updateAriaLabel(){const{pending:e,disabled:n,pendingLabel:i}=this.host,a=this.host.getAttribute("aria-label");function o(l,t,s){return!l&&t!==s||l!==t&&t!==s}o(this.cachedAriaLabel,a,i)&&(this.cachedAriaLabel=a),e&&!n?this.host.setAttribute("aria-label",i||"Pending"):this.cachedAriaLabel?this.host.setAttribute("aria-label",this.cachedAriaLabel):this.host.removeAttribute("aria-label")}hostConnected(){this.cachedAriaLabel||(this.cachedAriaLabel=this.host.getAttribute("aria-label")),this.updateAriaLabel()}hostUpdated(){this.updateAriaLabel()}}
10
10
  //# sourceMappingURL=PendingState.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["PendingState.ts"],
4
- "sourcesContent": ["/*\nCopyright 2024 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\n\nimport { html, LitElement, ReactiveController, TemplateResult } from 'lit';\nimport '@spectrum-web-components/progress-circle/sp-progress-circle.js';\n\n/**\n * Represents a host element with pending state.\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 public renderPendingState(): TemplateResult {\n const pendingLabel = this.host.pendingLabel || 'Pending';\n return this.host.pending\n ? html`\n <sp-progress-circle\n id=\"loader\"\n size=\"s\"\n indeterminate\n aria-valuetext=${pendingLabel}\n class=\"progress-circle\"\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 if (pending && !disabled && currentAriaLabel !== pendingLabel) {\n // Cache the current `aria-label` to be restored when no longer `pending`\n this.cachedAriaLabel = currentAriaLabel;\n // Since it is pending, we set the aria-label to `pendingLabel` or \"Pending\"\n this.host.setAttribute('aria-label', pendingLabel || 'Pending');\n } else if (!pending || disabled) {\n // Restore the cached `aria-label` if it exists\n if (this.cachedAriaLabel) {\n this.host.setAttribute('aria-label', this.cachedAriaLabel);\n } else if (!pending) {\n // If no cached `aria-label` and not `pending`, remove the `aria-label`\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
- "mappings": "aAYA,OAAS,QAAAA,MAA4D,MACrE,MAAO,iEAiBA,aAAM,sBAEb,CAUI,YAAYC,EAAS,CAKrB,KAAO,gBAAiC,KAJpC,KAAK,KAAOA,EACZ,KAAK,KAAK,cAAc,IAAI,CAChC,CAOO,oBAAqC,CACxC,MAAMC,EAAe,KAAK,KAAK,cAAgB,UAC/C,OAAO,KAAK,KAAK,QACXF;AAAA;AAAA;AAAA;AAAA;AAAA,uCAKyBE,CAAY;AAAA;AAAA;AAAA,gBAIrCF,GACV,CAMQ,iBAAwB,CAC5B,KAAM,CAAE,QAAAG,EAAS,SAAAC,EAAU,aAAAF,CAAa,EAAI,KAAK,KAC3CG,EAAmB,KAAK,KAAK,aAAa,YAAY,EAExDF,GAAW,CAACC,GAAYC,IAAqBH,GAE7C,KAAK,gBAAkBG,EAEvB,KAAK,KAAK,aAAa,aAAcH,GAAgB,SAAS,IACvD,CAACC,GAAWC,KAEf,KAAK,gBACL,KAAK,KAAK,aAAa,aAAc,KAAK,eAAe,EACjDD,GAER,KAAK,KAAK,gBAAgB,YAAY,EAGlD,CAEA,eAAsB,CACb,KAAK,kBACN,KAAK,gBAAkB,KAAK,KAAK,aAAa,YAAY,GAC9D,KAAK,gBAAgB,CACzB,CAEA,aAAoB,CAChB,KAAK,gBAAgB,CACzB,CACJ",
6
- "names": ["html", "host", "pendingLabel", "pending", "disabled", "currentAriaLabel"]
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
+ "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
+ "names": ["html", "host", "pending", "disabled", "pendingLabel", "currentAriaLabel", "shouldCacheAriaLabel", "cached", "current"]
7
7
  }
@@ -1,3 +1,14 @@
1
+ /**
2
+ * Copyright 2025 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
1
12
  import { FocusGroupConfig, FocusGroupController } from './FocusGroup.js';
2
13
  export type RovingTabindexConfig<T> = FocusGroupConfig<T>;
3
14
  interface UpdateTabIndexes {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["RovingTabindex.ts"],
4
- "sourcesContent": ["/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\nimport { FocusGroupConfig, FocusGroupController } from './FocusGroup.dev.js'\n\nexport type RovingTabindexConfig<T> = FocusGroupConfig<T>;\ninterface UpdateTabIndexes {\n tabIndex: number;\n removeTabIndex?: boolean;\n}\n\nexport class RovingTabindexController<\n T extends HTMLElement,\n> extends FocusGroupController<T> {\n protected override set focused(focused: boolean) {\n if (focused === this.focused) return;\n super.focused = focused;\n this.manageTabindexes();\n }\n\n protected override get focused(): boolean {\n return super.focused;\n }\n\n private managed = true;\n\n private manageIndexesAnimationFrame = 0;\n\n override clearElementCache(offset = 0): void {\n cancelAnimationFrame(this.manageIndexesAnimationFrame);\n super.clearElementCache(offset);\n if (!this.managed) return;\n\n this.manageIndexesAnimationFrame = requestAnimationFrame(() =>\n this.manageTabindexes()\n );\n }\n\n manageTabindexes(): void {\n if (this.focused && !this.hostDelegatesFocus) {\n this.updateTabindexes(() => ({ tabIndex: -1 }));\n } else {\n this.updateTabindexes((el: HTMLElement): UpdateTabIndexes => {\n return {\n removeTabIndex:\n el.contains(this.focusInElement) &&\n el !== this.focusInElement,\n tabIndex: el === this.focusInElement ? 0 : -1,\n };\n });\n }\n }\n\n updateTabindexes(getTabIndex: (el: HTMLElement) => UpdateTabIndexes): void {\n this.elements.forEach((el) => {\n const { tabIndex, removeTabIndex } = getTabIndex(el);\n if (!removeTabIndex) {\n if (this.focused) {\n if (el !== this.elements[this.currentIndex]) {\n el.tabIndex = tabIndex;\n }\n } else {\n el.tabIndex = tabIndex;\n }\n return;\n }\n const updatable = el as unknown as {\n requestUpdate?: () => void;\n };\n if (updatable.requestUpdate) updatable.requestUpdate();\n });\n }\n\n override manage(): void {\n this.managed = true;\n this.manageTabindexes();\n super.manage();\n }\n\n override unmanage(): void {\n this.managed = false;\n this.updateTabindexes(() => ({ tabIndex: 0 }));\n super.unmanage();\n }\n\n override hostUpdated(): void {\n super.hostUpdated();\n if (!this.host.hasUpdated) {\n this.manageTabindexes();\n }\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 */\nimport { FocusGroupConfig, FocusGroupController } from './FocusGroup.dev.js'\n\nexport type RovingTabindexConfig<T> = FocusGroupConfig<T>;\ninterface UpdateTabIndexes {\n tabIndex: number;\n removeTabIndex?: boolean;\n}\n\nexport class RovingTabindexController<\n T extends HTMLElement,\n> extends FocusGroupController<T> {\n protected override set focused(focused: boolean) {\n if (focused === this.focused) return;\n super.focused = focused;\n this.manageTabindexes();\n }\n\n protected override get focused(): boolean {\n return super.focused;\n }\n\n private managed = true;\n\n private manageIndexesAnimationFrame = 0;\n\n override clearElementCache(offset = 0): void {\n cancelAnimationFrame(this.manageIndexesAnimationFrame);\n super.clearElementCache(offset);\n if (!this.managed) return;\n\n this.manageIndexesAnimationFrame = requestAnimationFrame(() =>\n this.manageTabindexes()\n );\n }\n\n manageTabindexes(): void {\n if (this.focused && !this.hostDelegatesFocus) {\n this.updateTabindexes(() => ({ tabIndex: -1 }));\n } else {\n this.updateTabindexes((el: HTMLElement): UpdateTabIndexes => {\n return {\n removeTabIndex:\n el.contains(this.focusInElement) &&\n el !== this.focusInElement,\n tabIndex: el === this.focusInElement ? 0 : -1,\n };\n });\n }\n }\n\n updateTabindexes(getTabIndex: (el: HTMLElement) => UpdateTabIndexes): void {\n this.elements.forEach((el) => {\n const { tabIndex, removeTabIndex } = getTabIndex(el);\n if (!removeTabIndex) {\n if (this.focused) {\n if (el !== this.elements[this.currentIndex]) {\n el.tabIndex = tabIndex;\n }\n } else {\n el.tabIndex = tabIndex;\n }\n return;\n }\n const updatable = el as unknown as {\n requestUpdate?: () => void;\n };\n if (updatable.requestUpdate) updatable.requestUpdate();\n });\n }\n\n override manage(): void {\n this.managed = true;\n this.manageTabindexes();\n super.manage();\n }\n\n override unmanage(): void {\n this.managed = false;\n this.updateTabindexes(() => ({ tabIndex: 0 }));\n super.unmanage();\n }\n\n override hostUpdated(): void {\n super.hostUpdated();\n if (!this.host.hasUpdated) {\n this.manageTabindexes();\n }\n }\n}\n"],
5
5
  "mappings": ";AAWA,SAA2B,4BAA4B;AAQhD,aAAM,iCAEH,qBAAwB;AAAA,EAF3B;AAAA;AAaH,SAAQ,UAAU;AAElB,SAAQ,8BAA8B;AAAA;AAAA,EAZtC,IAAuB,QAAQ,SAAkB;AAC7C,QAAI,YAAY,KAAK,QAAS;AAC9B,UAAM,UAAU;AAChB,SAAK,iBAAiB;AAAA,EAC1B;AAAA,EAEA,IAAuB,UAAmB;AACtC,WAAO,MAAM;AAAA,EACjB;AAAA,EAMS,kBAAkB,SAAS,GAAS;AACzC,yBAAqB,KAAK,2BAA2B;AACrD,UAAM,kBAAkB,MAAM;AAC9B,QAAI,CAAC,KAAK,QAAS;AAEnB,SAAK,8BAA8B;AAAA,MAAsB,MACrD,KAAK,iBAAiB;AAAA,IAC1B;AAAA,EACJ;AAAA,EAEA,mBAAyB;AACrB,QAAI,KAAK,WAAW,CAAC,KAAK,oBAAoB;AAC1C,WAAK,iBAAiB,OAAO,EAAE,UAAU,GAAG,EAAE;AAAA,IAClD,OAAO;AACH,WAAK,iBAAiB,CAAC,OAAsC;AACzD,eAAO;AAAA,UACH,gBACI,GAAG,SAAS,KAAK,cAAc,KAC/B,OAAO,KAAK;AAAA,UAChB,UAAU,OAAO,KAAK,iBAAiB,IAAI;AAAA,QAC/C;AAAA,MACJ,CAAC;AAAA,IACL;AAAA,EACJ;AAAA,EAEA,iBAAiB,aAA0D;AACvE,SAAK,SAAS,QAAQ,CAAC,OAAO;AAC1B,YAAM,EAAE,UAAU,eAAe,IAAI,YAAY,EAAE;AACnD,UAAI,CAAC,gBAAgB;AACjB,YAAI,KAAK,SAAS;AACd,cAAI,OAAO,KAAK,SAAS,KAAK,YAAY,GAAG;AACzC,eAAG,WAAW;AAAA,UAClB;AAAA,QACJ,OAAO;AACH,aAAG,WAAW;AAAA,QAClB;AACA;AAAA,MACJ;AACA,YAAM,YAAY;AAGlB,UAAI,UAAU,cAAe,WAAU,cAAc;AAAA,IACzD,CAAC;AAAA,EACL;AAAA,EAES,SAAe;AACpB,SAAK,UAAU;AACf,SAAK,iBAAiB;AACtB,UAAM,OAAO;AAAA,EACjB;AAAA,EAES,WAAiB;AACtB,SAAK,UAAU;AACf,SAAK,iBAAiB,OAAO,EAAE,UAAU,EAAE,EAAE;AAC7C,UAAM,SAAS;AAAA,EACnB;AAAA,EAES,cAAoB;AACzB,UAAM,YAAY;AAClB,QAAI,CAAC,KAAK,KAAK,YAAY;AACvB,WAAK,iBAAiB;AAAA,IAC1B;AAAA,EACJ;AACJ;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["RovingTabindex.ts"],
4
- "sourcesContent": ["/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\nimport { FocusGroupConfig, FocusGroupController } from './FocusGroup.js';\n\nexport type RovingTabindexConfig<T> = FocusGroupConfig<T>;\ninterface UpdateTabIndexes {\n tabIndex: number;\n removeTabIndex?: boolean;\n}\n\nexport class RovingTabindexController<\n T extends HTMLElement,\n> extends FocusGroupController<T> {\n protected override set focused(focused: boolean) {\n if (focused === this.focused) return;\n super.focused = focused;\n this.manageTabindexes();\n }\n\n protected override get focused(): boolean {\n return super.focused;\n }\n\n private managed = true;\n\n private manageIndexesAnimationFrame = 0;\n\n override clearElementCache(offset = 0): void {\n cancelAnimationFrame(this.manageIndexesAnimationFrame);\n super.clearElementCache(offset);\n if (!this.managed) return;\n\n this.manageIndexesAnimationFrame = requestAnimationFrame(() =>\n this.manageTabindexes()\n );\n }\n\n manageTabindexes(): void {\n if (this.focused && !this.hostDelegatesFocus) {\n this.updateTabindexes(() => ({ tabIndex: -1 }));\n } else {\n this.updateTabindexes((el: HTMLElement): UpdateTabIndexes => {\n return {\n removeTabIndex:\n el.contains(this.focusInElement) &&\n el !== this.focusInElement,\n tabIndex: el === this.focusInElement ? 0 : -1,\n };\n });\n }\n }\n\n updateTabindexes(getTabIndex: (el: HTMLElement) => UpdateTabIndexes): void {\n this.elements.forEach((el) => {\n const { tabIndex, removeTabIndex } = getTabIndex(el);\n if (!removeTabIndex) {\n if (this.focused) {\n if (el !== this.elements[this.currentIndex]) {\n el.tabIndex = tabIndex;\n }\n } else {\n el.tabIndex = tabIndex;\n }\n return;\n }\n const updatable = el as unknown as {\n requestUpdate?: () => void;\n };\n if (updatable.requestUpdate) updatable.requestUpdate();\n });\n }\n\n override manage(): void {\n this.managed = true;\n this.manageTabindexes();\n super.manage();\n }\n\n override unmanage(): void {\n this.managed = false;\n this.updateTabindexes(() => ({ tabIndex: 0 }));\n super.unmanage();\n }\n\n override hostUpdated(): void {\n super.hostUpdated();\n if (!this.host.hasUpdated) {\n this.manageTabindexes();\n }\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 */\nimport { FocusGroupConfig, FocusGroupController } from './FocusGroup.js';\n\nexport type RovingTabindexConfig<T> = FocusGroupConfig<T>;\ninterface UpdateTabIndexes {\n tabIndex: number;\n removeTabIndex?: boolean;\n}\n\nexport class RovingTabindexController<\n T extends HTMLElement,\n> extends FocusGroupController<T> {\n protected override set focused(focused: boolean) {\n if (focused === this.focused) return;\n super.focused = focused;\n this.manageTabindexes();\n }\n\n protected override get focused(): boolean {\n return super.focused;\n }\n\n private managed = true;\n\n private manageIndexesAnimationFrame = 0;\n\n override clearElementCache(offset = 0): void {\n cancelAnimationFrame(this.manageIndexesAnimationFrame);\n super.clearElementCache(offset);\n if (!this.managed) return;\n\n this.manageIndexesAnimationFrame = requestAnimationFrame(() =>\n this.manageTabindexes()\n );\n }\n\n manageTabindexes(): void {\n if (this.focused && !this.hostDelegatesFocus) {\n this.updateTabindexes(() => ({ tabIndex: -1 }));\n } else {\n this.updateTabindexes((el: HTMLElement): UpdateTabIndexes => {\n return {\n removeTabIndex:\n el.contains(this.focusInElement) &&\n el !== this.focusInElement,\n tabIndex: el === this.focusInElement ? 0 : -1,\n };\n });\n }\n }\n\n updateTabindexes(getTabIndex: (el: HTMLElement) => UpdateTabIndexes): void {\n this.elements.forEach((el) => {\n const { tabIndex, removeTabIndex } = getTabIndex(el);\n if (!removeTabIndex) {\n if (this.focused) {\n if (el !== this.elements[this.currentIndex]) {\n el.tabIndex = tabIndex;\n }\n } else {\n el.tabIndex = tabIndex;\n }\n return;\n }\n const updatable = el as unknown as {\n requestUpdate?: () => void;\n };\n if (updatable.requestUpdate) updatable.requestUpdate();\n });\n }\n\n override manage(): void {\n this.managed = true;\n this.manageTabindexes();\n super.manage();\n }\n\n override unmanage(): void {\n this.managed = false;\n this.updateTabindexes(() => ({ tabIndex: 0 }));\n super.unmanage();\n }\n\n override hostUpdated(): void {\n super.hostUpdated();\n if (!this.host.hasUpdated) {\n this.manageTabindexes();\n }\n }\n}\n"],
5
5
  "mappings": "aAWA,OAA2B,wBAAAA,MAA4B,kBAQhD,aAAM,iCAEHA,CAAwB,CAF3B,kCAaH,KAAQ,QAAU,GAElB,KAAQ,4BAA8B,EAZtC,IAAuB,QAAQC,EAAkB,CACzCA,IAAY,KAAK,UACrB,MAAM,QAAUA,EAChB,KAAK,iBAAiB,EAC1B,CAEA,IAAuB,SAAmB,CACtC,OAAO,MAAM,OACjB,CAMS,kBAAkBC,EAAS,EAAS,CACzC,qBAAqB,KAAK,2BAA2B,EACrD,MAAM,kBAAkBA,CAAM,EACzB,KAAK,UAEV,KAAK,4BAA8B,sBAAsB,IACrD,KAAK,iBAAiB,CAC1B,EACJ,CAEA,kBAAyB,CACjB,KAAK,SAAW,CAAC,KAAK,mBACtB,KAAK,iBAAiB,KAAO,CAAE,SAAU,EAAG,EAAE,EAE9C,KAAK,iBAAkBC,IACZ,CACH,eACIA,EAAG,SAAS,KAAK,cAAc,GAC/BA,IAAO,KAAK,eAChB,SAAUA,IAAO,KAAK,eAAiB,EAAI,EAC/C,EACH,CAET,CAEA,iBAAiBC,EAA0D,CACvE,KAAK,SAAS,QAASD,GAAO,CAC1B,KAAM,CAAE,SAAAE,EAAU,eAAAC,CAAe,EAAIF,EAAYD,CAAE,EACnD,GAAI,CAACG,EAAgB,CACb,KAAK,QACDH,IAAO,KAAK,SAAS,KAAK,YAAY,IACtCA,EAAG,SAAWE,GAGlBF,EAAG,SAAWE,EAElB,MACJ,CACA,MAAME,EAAYJ,EAGdI,EAAU,eAAeA,EAAU,cAAc,CACzD,CAAC,CACL,CAES,QAAe,CACpB,KAAK,QAAU,GACf,KAAK,iBAAiB,EACtB,MAAM,OAAO,CACjB,CAES,UAAiB,CACtB,KAAK,QAAU,GACf,KAAK,iBAAiB,KAAO,CAAE,SAAU,CAAE,EAAE,EAC7C,MAAM,SAAS,CACnB,CAES,aAAoB,CACzB,MAAM,YAAY,EACb,KAAK,KAAK,YACX,KAAK,iBAAiB,CAE9B,CACJ",
6
6
  "names": ["FocusGroupController", "focused", "offset", "el", "getTabIndex", "tabIndex", "removeTabIndex", "updatable"]
7
7
  }