@spectrum-web-components/reactive-controllers
Advanced tools
Comparing version 0.2.2 to 0.2.3
{ | ||
"name": "@spectrum-web-components/reactive-controllers", | ||
"version": "0.2.2", | ||
"version": "0.2.3", | ||
"publishConfig": { | ||
@@ -50,3 +50,3 @@ "access": "public" | ||
"customElements": "custom-elements.json", | ||
"gitHead": "caf12727e7f91dcf961e1fadacc727eea9ece27b" | ||
"gitHead": "32e049a0da090ffc1a54cfe3234be4d5efc73339" | ||
} |
@@ -1,11 +0,3 @@ | ||
import type { ReactiveController, ReactiveElement } from 'lit'; | ||
declare type DirectionTypes = 'horizontal' | 'vertical' | 'both' | 'grid'; | ||
export declare type RovingTabindexConfig<T> = { | ||
focusInIndex?: (_elements: T[]) => number; | ||
direction?: DirectionTypes | (() => DirectionTypes); | ||
elementEnterAction?: (el: T) => void; | ||
elements: () => T[]; | ||
isFocusableElement?: (el: T) => boolean; | ||
listenerScope?: HTMLElement | (() => HTMLElement); | ||
}; | ||
import { FocusGroupConfig, FocusGroupController } from './FocusGroup.js'; | ||
export declare type RovingTabindexConfig<T> = FocusGroupConfig<T>; | ||
interface UpdateTabIndexes { | ||
@@ -15,39 +7,8 @@ tabIndex: number; | ||
} | ||
export declare class RovingTabindexController<T extends HTMLElement> implements ReactiveController { | ||
private cachedElements?; | ||
get currentIndex(): number; | ||
set currentIndex(currentIndex: number); | ||
private _currentIndex; | ||
get direction(): DirectionTypes; | ||
_direction: () => DirectionTypes; | ||
directionLength: number; | ||
elementEnterAction: (_el: T) => void; | ||
get elements(): T[]; | ||
private _elements; | ||
firstUpdated: boolean; | ||
private set focused(value); | ||
private get focused(); | ||
private _focused; | ||
get focusInElement(): T; | ||
get focusInIndex(): number; | ||
_focusInIndex: (_elements: T[]) => number; | ||
host: ReactiveElement; | ||
isFocusableElement: (_el: T) => boolean; | ||
isEventWithinListenerScope(event: Event): boolean; | ||
_listenerScope: () => HTMLElement; | ||
offset: number; | ||
export declare class RovingTabindexController<T extends HTMLElement> extends FocusGroupController<T> { | ||
protected set focused(focused: boolean); | ||
protected get focused(): boolean; | ||
private managed; | ||
constructor(host: ReactiveElement, { direction, elementEnterAction, elements, focusInIndex, isFocusableElement, listenerScope, }?: RovingTabindexConfig<T>); | ||
update({ elements }?: RovingTabindexConfig<T>): void; | ||
focus(options?: FocusOptions): void; | ||
private manageIndexesAnimationFrame; | ||
clearElementCache(offset?: number): void; | ||
setCurrentIndexCircularly(diff: number): void; | ||
hostContainsFocus(): void; | ||
hostNoLongerContainsFocus(): void; | ||
isRelatedTargetAnElement(event: FocusEvent): boolean; | ||
handleFocusin: (event: FocusEvent) => void; | ||
handleFocusout: (event: FocusEvent) => void; | ||
acceptsEventCode(code: string): boolean; | ||
handleKeydown: (event: KeyboardEvent) => void; | ||
manageTabindexes(): void; | ||
@@ -57,8 +18,4 @@ updateTabindexes(getTabIndex: (el: HTMLElement) => UpdateTabIndexes): void; | ||
unmanage(): void; | ||
addEventListeners(): void; | ||
removeEventListeners(): void; | ||
hostUpdated(): void; | ||
hostConnected(): void; | ||
hostDisconnected(): void; | ||
} | ||
export {}; |
@@ -1,160 +0,31 @@ | ||
export class RovingTabindexController { | ||
constructor(host, { direction, elementEnterAction, elements, focusInIndex, isFocusableElement, listenerScope, } = { elements: () => [] }) { | ||
this._currentIndex = -1; | ||
this._direction = () => 'both'; | ||
this.directionLength = 5; | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
this.elementEnterAction = (_el) => { | ||
return; | ||
}; | ||
this.firstUpdated = true; | ||
this._focused = false; | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
this._focusInIndex = (_elements) => 0; | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
this.isFocusableElement = (_el) => true; | ||
this._listenerScope = () => this.host; | ||
// When elements are virtualized, the delta between the first element | ||
// and the first rendered element. | ||
this.offset = 0; | ||
/* | ||
Copyright 2020 Adobe. All rights reserved. | ||
This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. You may obtain a copy | ||
of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software distributed under | ||
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
OF ANY KIND, either express or implied. See the License for the specific language | ||
governing permissions and limitations under the License. | ||
*/ | ||
import { FocusGroupController } from './FocusGroup.js'; | ||
export class RovingTabindexController extends FocusGroupController { | ||
constructor() { | ||
super(...arguments); | ||
this.managed = true; | ||
this.manageIndexesAnimationFrame = 0; | ||
this.handleFocusin = (event) => { | ||
if (!this.isEventWithinListenerScope(event)) | ||
return; | ||
if (this.isRelatedTargetAnElement(event)) { | ||
this.hostContainsFocus(); | ||
} | ||
const path = event.composedPath(); | ||
let targetIndex = -1; | ||
path.find((el) => { | ||
targetIndex = this.elements.indexOf(el); | ||
return targetIndex !== -1; | ||
}); | ||
this.currentIndex = targetIndex > -1 ? targetIndex : this.currentIndex; | ||
}; | ||
this.handleFocusout = (event) => { | ||
if (this.isRelatedTargetAnElement(event)) { | ||
this.hostNoLongerContainsFocus(); | ||
} | ||
}; | ||
this.handleKeydown = (event) => { | ||
if (!this.acceptsEventCode(event.code) || event.defaultPrevented) { | ||
return; | ||
} | ||
let diff = 0; | ||
switch (event.code) { | ||
case 'ArrowRight': | ||
diff += 1; | ||
break; | ||
case 'ArrowDown': | ||
diff += this.direction === 'grid' ? this.directionLength : 1; | ||
break; | ||
case 'ArrowLeft': | ||
diff -= 1; | ||
break; | ||
case 'ArrowUp': | ||
diff -= this.direction === 'grid' ? this.directionLength : 1; | ||
break; | ||
case 'End': | ||
this.currentIndex = 0; | ||
diff -= 1; | ||
break; | ||
case 'Home': | ||
this.currentIndex = this.elements.length - 1; | ||
diff += 1; | ||
break; | ||
} | ||
event.preventDefault(); | ||
if (this.direction === 'grid' && this.currentIndex + diff < 0) { | ||
this.currentIndex = 0; | ||
} | ||
else if (this.direction === 'grid' && | ||
this.currentIndex + diff > this.elements.length - 1) { | ||
this.currentIndex = this.elements.length - 1; | ||
} | ||
else { | ||
this.setCurrentIndexCircularly(diff); | ||
} | ||
// To allow the `focusInIndex` to be calculated with the "after" state of the keyboard interaction | ||
// do `elementEnterAction` _before_ focusing the next element. | ||
this.elementEnterAction(this.elements[this.currentIndex]); | ||
this.focus(); | ||
}; | ||
this.host = host; | ||
this.host.addController(this); | ||
this._elements = elements; | ||
this.isFocusableElement = isFocusableElement || this.isFocusableElement; | ||
if (typeof direction === 'string') { | ||
this._direction = () => direction; | ||
} | ||
else if (typeof direction === 'function') { | ||
this._direction = direction; | ||
} | ||
this.elementEnterAction = elementEnterAction || this.elementEnterAction; | ||
if (typeof focusInIndex === 'number') { | ||
this._focusInIndex = () => focusInIndex; | ||
} | ||
else if (typeof focusInIndex === 'function') { | ||
this._focusInIndex = focusInIndex; | ||
} | ||
if (typeof listenerScope === 'object') { | ||
this._listenerScope = () => listenerScope; | ||
} | ||
else if (typeof listenerScope === 'function') { | ||
this._listenerScope = listenerScope; | ||
} | ||
} | ||
get currentIndex() { | ||
if (this._currentIndex === -1) { | ||
this._currentIndex = this.focusInIndex; | ||
} | ||
return this._currentIndex - this.offset; | ||
} | ||
set currentIndex(currentIndex) { | ||
this._currentIndex = currentIndex + this.offset; | ||
} | ||
get direction() { | ||
return this._direction(); | ||
} | ||
get elements() { | ||
if (!this.cachedElements) { | ||
this.cachedElements = this._elements(); | ||
} | ||
return this.cachedElements; | ||
} | ||
set focused(focused) { | ||
if (focused === this.focused) | ||
return; | ||
this._focused = focused; | ||
super.focused = focused; | ||
this.manageTabindexes(); | ||
} | ||
get focused() { | ||
return this._focused; | ||
return super.focused; | ||
} | ||
get focusInElement() { | ||
return this.elements[this.focusInIndex]; | ||
} | ||
get focusInIndex() { | ||
return this._focusInIndex(this.elements); | ||
} | ||
isEventWithinListenerScope(event) { | ||
if (this._listenerScope() === this.host) | ||
return true; | ||
return event.composedPath().includes(this._listenerScope()); | ||
} | ||
update({ elements } = { elements: () => [] }) { | ||
this.unmanage(); | ||
this._elements = elements; | ||
this.clearElementCache(); | ||
this.manage(); | ||
} | ||
focus(options) { | ||
var _a; | ||
(_a = this.elements[this.currentIndex]) === null || _a === void 0 ? void 0 : _a.focus(options); | ||
} | ||
clearElementCache(offset = 0) { | ||
delete this.cachedElements; | ||
cancelAnimationFrame(this.manageIndexesAnimationFrame); | ||
this.offset = offset; | ||
super.clearElementCache(offset); | ||
if (!this.managed) | ||
@@ -164,47 +35,2 @@ return; | ||
} | ||
setCurrentIndexCircularly(diff) { | ||
const { length } = this.elements; | ||
let steps = length; | ||
// start at a possibly not 0 index | ||
let nextIndex = (length + this.currentIndex + diff) % length; | ||
while ( | ||
// don't cycle the elements more than once | ||
steps && | ||
this.elements[nextIndex] && | ||
!this.isFocusableElement(this.elements[nextIndex])) { | ||
nextIndex = (length + nextIndex + diff) % length; | ||
steps -= 1; | ||
} | ||
this.currentIndex = nextIndex; | ||
} | ||
hostContainsFocus() { | ||
this.host.addEventListener('focusout', this.handleFocusout); | ||
this.host.addEventListener('keydown', this.handleKeydown); | ||
this.focused = true; | ||
} | ||
hostNoLongerContainsFocus() { | ||
this.host.addEventListener('focusin', this.handleFocusin); | ||
this.host.removeEventListener('focusout', this.handleFocusout); | ||
this.host.removeEventListener('keydown', this.handleKeydown); | ||
this.currentIndex = this.focusInIndex; | ||
this.focused = false; | ||
} | ||
isRelatedTargetAnElement(event) { | ||
const relatedTarget = event.relatedTarget; | ||
return !this.elements.includes(relatedTarget); | ||
} | ||
acceptsEventCode(code) { | ||
if (code === 'End' || code === 'Home') { | ||
return true; | ||
} | ||
switch (this.direction) { | ||
case 'horizontal': | ||
return code === 'ArrowLeft' || code === 'ArrowRight'; | ||
case 'vertical': | ||
return code === 'ArrowUp' || code === 'ArrowDown'; | ||
case 'both': | ||
case 'grid': | ||
return code.startsWith('Arrow'); | ||
} | ||
} | ||
manageTabindexes() { | ||
@@ -240,3 +66,3 @@ if (this.focused) { | ||
this.manageTabindexes(); | ||
this.addEventListeners(); | ||
super.manage(); | ||
} | ||
@@ -246,25 +72,10 @@ unmanage() { | ||
this.updateTabindexes(() => ({ tabIndex: 0 })); | ||
this.removeEventListeners(); | ||
super.unmanage(); | ||
} | ||
addEventListeners() { | ||
this.host.addEventListener('focusin', this.handleFocusin); | ||
} | ||
removeEventListeners() { | ||
this.host.removeEventListener('focusin', this.handleFocusin); | ||
this.host.removeEventListener('focusout', this.handleFocusout); | ||
this.host.removeEventListener('keydown', this.handleKeydown); | ||
} | ||
hostUpdated() { | ||
if (this.firstUpdated) { | ||
if (!this.host.hasUpdated) { | ||
this.manageTabindexes(); | ||
this.firstUpdated = false; | ||
} | ||
} | ||
hostConnected() { | ||
this.addEventListeners(); | ||
} | ||
hostDisconnected() { | ||
this.removeEventListeners(); | ||
} | ||
} | ||
//# sourceMappingURL=RovingTabindex.js.map |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
79992
21
616
0