Choices/src/scripts/components/container.js
Josh Johnson e6882f3e4b
Add missing type definitions + rename sortFn (#734)
* Add wrapped element getters + fix some types

* Remove comment

* Add missing config options to types

* Add types to constants

* Rename sortFn to sorter

* Update PR template

* Add refactor to PR template

* Add passed element types to constants

* Add js doc comments to actions

* Add "returns" to js doc comments

* Add missing choice prop to type

* Add types to store.js

* Add jsdoc comments to components

* Ignore strict null checks

* Move loading action into misc.js

* Further type def additions

* Rename itemCompare to valueCompare

* Update badges

* Rename scrollToChoice to scrollToChildElement
2019-11-03 13:18:16 +00:00

171 lines
4.2 KiB
JavaScript

import { wrap } from '../lib/utils';
/**
* @typedef {import('../../../types/index').Choices.passedElement} passedElement
* @typedef {import('../../../types/index').Choices.ClassNames} ClassNames
*/
export default class Container {
/**
* @param {{
* element: HTMLElement,
* type: passedElement['type'],
* classNames: ClassNames,
* position
* }} args
*/
constructor({ element, type, classNames, 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() {
this.element.addEventListener('focus', this._onFocus);
this.element.addEventListener('blur', this._onBlur);
}
removeEventListeners() {
this.element.removeEventListener('focus', this._onFocus);
this.element.removeEventListener('blur', this._onBlur);
}
/**
* Determine whether container should be flipped based on passed
* dropdown position
* @param {number} dropdownPos
* @returns {boolean}
*/
shouldFlip(dropdownPos) {
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;
}
/**
* @param {string} activeDescendantID
*/
setActiveDescendant(activeDescendantID) {
this.element.setAttribute('aria-activedescendant', activeDescendantID);
}
removeActiveDescendant() {
this.element.removeAttribute('aria-activedescendant');
}
/**
* @param {number} dropdownPos
*/
open(dropdownPos) {
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() {
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() {
if (!this.isFocussed) {
this.element.focus();
}
}
addFocusState() {
this.element.classList.add(this.classNames.focusState);
}
removeFocusState() {
this.element.classList.remove(this.classNames.focusState);
}
enable() {
this.element.classList.remove(this.classNames.disabledState);
this.element.removeAttribute('aria-disabled');
if (this.type === 'select-one') {
this.element.setAttribute('tabindex', '0');
}
this.isDisabled = false;
}
disable() {
this.element.classList.add(this.classNames.disabledState);
this.element.setAttribute('aria-disabled', 'true');
if (this.type === 'select-one') {
this.element.setAttribute('tabindex', '-1');
}
this.isDisabled = true;
}
/**
* @param {HTMLElement} element
*/
wrap(element) {
wrap(element, this.element);
}
/**
* @param {Element} element
*/
unwrap(element) {
// Move passed element outside this element
this.element.parentNode.insertBefore(element, this.element);
// Remove this element
this.element.parentNode.removeChild(this.element);
}
addLoadingState() {
this.element.classList.add(this.classNames.loadingState);
this.element.setAttribute('aria-busy', 'true');
this.isLoading = true;
}
removeLoadingState() {
this.element.classList.remove(this.classNames.loadingState);
this.element.removeAttribute('aria-busy');
this.isLoading = false;
}
_onFocus() {
this.isFocussed = true;
}
_onBlur() {
this.isFocussed = false;
}
}