Choices/src/scripts/components/container.ts
Josh Johnson 68313da412
Convert to typescript (#795)
* Typescript config setup

* Add type annotations to components

* Further type additions

* And more...

* Add types to actions

* Add types to templates

* Further type checks

* Further type additons

* Install fuse latest

* Housekeeping

* Remove old type definitions

* Fix remaning type issues

* Fix some failing tests

* Remove types workflow

* Fix failing unit tests

* Resolve back space event regression

* Convert cypress files to .ts

* Fix eslint issues

* Remove cachebusting urls

* Resolve delete button bug

* Resolve regression bugs

* Fix lint script

* Fix lint workflow

* Pass args instead of object to keyboard handlers

* Flatten misc reducer

* Resolve keyboad action test failures

* Use Pick instead of Partial

* Use interfaces in action tests

* Update firefox image

* Incorporate #791

* Incorporate #788
2019-12-23 18:22:54 +00:00

169 lines
4.4 KiB
TypeScript

import { wrap } from '../lib/utils';
import { SELECT_ONE_TYPE } from '../constants';
import { PassedElement, ClassNames, Options } from '../interfaces';
export default class Container {
element: HTMLElement;
type: PassedElement['type'];
classNames: ClassNames;
position: Options['position'];
isOpen: boolean;
isFlipped: boolean;
isFocussed: boolean;
isDisabled: boolean;
isLoading: boolean;
constructor({
element,
type,
classNames,
position,
}: {
element: HTMLElement;
type: PassedElement['type'];
classNames: ClassNames;
position: Options['position'];
}) {
this.element = element;
this.classNames = classNames;
this.type = type;
this.position = position;
this.isOpen = false;
this.isFlipped = false;
this.isFocussed = false;
this.isDisabled = false;
this.isLoading = false;
this._onFocus = this._onFocus.bind(this);
this._onBlur = this._onBlur.bind(this);
}
addEventListeners(): void {
this.element.addEventListener('focus', this._onFocus);
this.element.addEventListener('blur', this._onBlur);
}
removeEventListeners(): void {
this.element.removeEventListener('focus', this._onFocus);
this.element.removeEventListener('blur', this._onBlur);
}
/**
* Determine whether container should be flipped based on passed
* dropdown position
*/
shouldFlip(dropdownPos: number): boolean {
if (typeof dropdownPos !== 'number') {
return false;
}
// If flip is enabled and the dropdown bottom position is
// greater than the window height flip the dropdown.
let shouldFlip = false;
if (this.position === 'auto') {
shouldFlip = !window.matchMedia(`(min-height: ${dropdownPos + 1}px)`)
.matches;
} else if (this.position === 'top') {
shouldFlip = true;
}
return shouldFlip;
}
setActiveDescendant(activeDescendantID: string): void {
this.element.setAttribute('aria-activedescendant', activeDescendantID);
}
removeActiveDescendant(): void {
this.element.removeAttribute('aria-activedescendant');
}
open(dropdownPos: number): void {
this.element.classList.add(this.classNames.openState);
this.element.setAttribute('aria-expanded', 'true');
this.isOpen = true;
if (this.shouldFlip(dropdownPos)) {
this.element.classList.add(this.classNames.flippedState);
this.isFlipped = true;
}
}
close(): void {
this.element.classList.remove(this.classNames.openState);
this.element.setAttribute('aria-expanded', 'false');
this.removeActiveDescendant();
this.isOpen = false;
// A dropdown flips if it does not have space within the page
if (this.isFlipped) {
this.element.classList.remove(this.classNames.flippedState);
this.isFlipped = false;
}
}
focus(): void {
if (!this.isFocussed) {
this.element.focus();
}
}
addFocusState(): void {
this.element.classList.add(this.classNames.focusState);
}
removeFocusState(): void {
this.element.classList.remove(this.classNames.focusState);
}
enable(): void {
this.element.classList.remove(this.classNames.disabledState);
this.element.removeAttribute('aria-disabled');
if (this.type === SELECT_ONE_TYPE) {
this.element.setAttribute('tabindex', '0');
}
this.isDisabled = false;
}
disable(): void {
this.element.classList.add(this.classNames.disabledState);
this.element.setAttribute('aria-disabled', 'true');
if (this.type === SELECT_ONE_TYPE) {
this.element.setAttribute('tabindex', '-1');
}
this.isDisabled = true;
}
wrap(element: HTMLSelectElement | HTMLInputElement | HTMLElement): void {
wrap(element, this.element);
}
unwrap(element: HTMLElement): void {
if (this.element.parentNode) {
// Move passed element outside this element
this.element.parentNode.insertBefore(element, this.element);
// Remove this element
this.element.parentNode.removeChild(this.element);
}
}
addLoadingState(): void {
this.element.classList.add(this.classNames.loadingState);
this.element.setAttribute('aria-busy', 'true');
this.isLoading = true;
}
removeLoadingState(): void {
this.element.classList.remove(this.classNames.loadingState);
this.element.removeAttribute('aria-busy');
this.isLoading = false;
}
_onFocus(): void {
this.isFocussed = true;
}
_onBlur(): void {
this.isFocussed = false;
}
}