Add jsdoc comments to components

This commit is contained in:
Josh Johnson 2019-11-02 21:25:28 +00:00
parent 15a1e9c173
commit 31a86cc4b6
8 changed files with 187 additions and 81 deletions

View file

@ -1,42 +1,47 @@
import { wrap } from '../lib/utils'; import { wrap } from '../lib/utils';
/**
* @typedef {import('../../../types/index').Choices.passedElement} passedElement
* @typedef {import('../../../types/index').Choices.ClassNames} ClassNames
*/
export default class Container { export default class Container {
/**
* @param {{
* element: HTMLElement,
* type: passedElement['type'],
* classNames: ClassNames,
* position
* }} args
*/
constructor({ element, type, classNames, position }) { constructor({ element, type, classNames, position }) {
Object.assign(this, { element, classNames, type, position }); this.element = element;
this.classNames = classNames;
this.type = type;
this.position = position;
this.isOpen = false; this.isOpen = false;
this.isFlipped = false; this.isFlipped = false;
this.isFocussed = false; this.isFocussed = false;
this.isDisabled = false; this.isDisabled = false;
this.isLoading = false; this.isLoading = false;
this._onFocus = this._onFocus.bind(this); this._onFocus = this._onFocus.bind(this);
this._onBlur = this._onBlur.bind(this); this._onBlur = this._onBlur.bind(this);
} }
/**
* Add event listeners
*/
addEventListeners() { addEventListeners() {
this.element.addEventListener('focus', this._onFocus); this.element.addEventListener('focus', this._onFocus);
this.element.addEventListener('blur', this._onBlur); this.element.addEventListener('blur', this._onBlur);
} }
/**
* Remove event listeners
*/
/** */
removeEventListeners() { removeEventListeners() {
this.element.removeEventListener('focus', this._onFocus); this.element.removeEventListener('focus', this._onFocus);
this.element.removeEventListener('blur', this._onBlur); this.element.removeEventListener('blur', this._onBlur);
} }
/** /**
* Determine whether container should be flipped * Determine whether container should be flipped based on passed
* based on passed dropdown position * dropdown position
* @param {Number} dropdownPos * @param {number} dropdownPos
* @returns * @returns {boolean}
*/ */
shouldFlip(dropdownPos) { shouldFlip(dropdownPos) {
if (typeof dropdownPos !== 'number') { if (typeof dropdownPos !== 'number') {
@ -57,20 +62,19 @@ export default class Container {
} }
/** /**
* Set active descendant attribute * @param {number} activeDescendantID
* @param {Number} activeDescendant ID of active descendant
*/ */
setActiveDescendant(activeDescendantID) { setActiveDescendant(activeDescendantID) {
this.element.setAttribute('aria-activedescendant', activeDescendantID); this.element.setAttribute('aria-activedescendant', activeDescendantID);
} }
/**
* Remove active descendant attribute
*/
removeActiveDescendant() { removeActiveDescendant() {
this.element.removeAttribute('aria-activedescendant'); this.element.removeAttribute('aria-activedescendant');
} }
/**
* @param {number} dropdownPos
*/
open(dropdownPos) { open(dropdownPos) {
this.element.classList.add(this.classNames.openState); this.element.classList.add(this.classNames.openState);
this.element.setAttribute('aria-expanded', 'true'); this.element.setAttribute('aria-expanded', 'true');
@ -109,9 +113,6 @@ export default class Container {
this.element.classList.remove(this.classNames.focusState); this.element.classList.remove(this.classNames.focusState);
} }
/**
* Remove disabled state
*/
enable() { enable() {
this.element.classList.remove(this.classNames.disabledState); this.element.classList.remove(this.classNames.disabledState);
this.element.removeAttribute('aria-disabled'); this.element.removeAttribute('aria-disabled');
@ -121,9 +122,6 @@ export default class Container {
this.isDisabled = false; this.isDisabled = false;
} }
/**
* Set disabled state
*/
disable() { disable() {
this.element.classList.add(this.classNames.disabledState); this.element.classList.add(this.classNames.disabledState);
this.element.setAttribute('aria-disabled', 'true'); this.element.setAttribute('aria-disabled', 'true');
@ -133,10 +131,16 @@ export default class Container {
this.isDisabled = true; this.isDisabled = true;
} }
/**
* @param {Element} element
*/
wrap(element) { wrap(element) {
wrap(element, this.element); wrap(element, this.element);
} }
/**
* @param {Element} element
*/
unwrap(element) { unwrap(element) {
// Move passed element outside this element // Move passed element outside this element
this.element.parentNode.insertBefore(element, this.element); this.element.parentNode.insertBefore(element, this.element);
@ -144,34 +148,22 @@ export default class Container {
this.element.parentNode.removeChild(this.element); this.element.parentNode.removeChild(this.element);
} }
/**
* Add loading state to element
*/
addLoadingState() { addLoadingState() {
this.element.classList.add(this.classNames.loadingState); this.element.classList.add(this.classNames.loadingState);
this.element.setAttribute('aria-busy', 'true'); this.element.setAttribute('aria-busy', 'true');
this.isLoading = true; this.isLoading = true;
} }
/**
* Remove loading state from element
*/
removeLoadingState() { removeLoadingState() {
this.element.classList.remove(this.classNames.loadingState); this.element.classList.remove(this.classNames.loadingState);
this.element.removeAttribute('aria-busy'); this.element.removeAttribute('aria-busy');
this.isLoading = false; this.isLoading = false;
} }
/**
* Set focussed state
*/
_onFocus() { _onFocus() {
this.isFocussed = true; this.isFocussed = true;
} }
/**
* Remove blurred state
*/
_onBlur() { _onBlur() {
this.isFocussed = false; this.isFocussed = false;
} }

View file

@ -1,13 +1,26 @@
export default class Dropdown { /**
constructor({ element, type, classNames }) { * @typedef {import('../../../types/index').Choices.passedElement} passedElement
Object.assign(this, { element, type, classNames }); * @typedef {import('../../../types/index').Choices.ClassNames} ClassNames
*/
export default class Dropdown {
/**
* @param {{
* element: HTMLElement,
* type: passedElement['type'],
* classNames: ClassNames,
* }} args
*/
constructor({ element, type, classNames }) {
this.element = element;
this.classNames = classNames;
this.type = type;
this.isActive = false; this.isActive = false;
} }
/** /**
* Bottom position of dropdown in viewport coordinates * Bottom position of dropdown in viewport coordinates
* @type {number} Vertical position * @returns {number} Vertical position
*/ */
get distanceFromTopWindow() { get distanceFromTopWindow() {
return this.element.getBoundingClientRect().bottom; return this.element.getBoundingClientRect().bottom;
@ -15,7 +28,8 @@ export default class Dropdown {
/** /**
* Find element that matches passed selector * Find element that matches passed selector
* @return {HTMLElement} * @param {string} selector
* @returns {HTMLElement}
*/ */
getChild(selector) { getChild(selector) {
return this.element.querySelector(selector); return this.element.querySelector(selector);
@ -23,8 +37,7 @@ export default class Dropdown {
/** /**
* Show dropdown to user by adding active state class * Show dropdown to user by adding active state class
* @return {Object} Class instance * @returns {this}
* @public
*/ */
show() { show() {
this.element.classList.add(this.classNames.activeState); this.element.classList.add(this.classNames.activeState);
@ -36,8 +49,7 @@ export default class Dropdown {
/** /**
* Hide dropdown from user * Hide dropdown from user
* @return {Object} Class instance * @returns {this}
* @public
*/ */
hide() { hide() {
this.element.classList.remove(this.classNames.activeState); this.element.classList.remove(this.classNames.activeState);

View file

@ -1,11 +1,18 @@
import { sanitise } from '../lib/utils'; import { sanitise } from '../lib/utils';
/**
* @typedef {import('../../../types/index').Choices.passedElement} passedElement
* @typedef {import('../../../types/index').Choices.ClassNames} ClassNames
*/
export default class Input { export default class Input {
/** /**
* * @param {{
* @typedef {import('../../../types/index').Choices.passedElement} passedElement * element: HTMLInputElement,
* @typedef {import('../../../types/index').Choices.ClassNames} ClassNames * type: passedElement['type'],
* @param {{element: HTMLInputElement, type: passedElement['type'], classNames: ClassNames, preventPaste: boolean }} p * classNames: ClassNames,
* preventPaste: boolean
* }} args
*/ */
constructor({ element, type, classNames, preventPaste }) { constructor({ element, type, classNames, preventPaste }) {
this.element = element; this.element = element;
@ -21,14 +28,23 @@ export default class Input {
this._onBlur = this._onBlur.bind(this); this._onBlur = this._onBlur.bind(this);
} }
/**
* @param {string} placeholder
*/
set placeholder(placeholder) { set placeholder(placeholder) {
this.element.placeholder = placeholder; this.element.placeholder = placeholder;
} }
/**
* @returns {string}
*/
get value() { get value() {
return sanitise(this.element.value); return sanitise(this.element.value);
} }
/**
* @param {string} value
*/
set value(value) { set value(value) {
this.element.value = value; this.element.value = value;
} }
@ -83,8 +99,8 @@ export default class Input {
/** /**
* Set value of input to blank * Set value of input to blank
* @return {Object} Class instance * @param {boolean} setWidth
* @public * @returns {this}
*/ */
clear(setWidth = true) { clear(setWidth = true) {
if (this.element.value) { if (this.element.value) {
@ -109,6 +125,9 @@ export default class Input {
style.width = `${value.length + 1}ch`; style.width = `${value.length + 1}ch`;
} }
/**
* @param {string} activeDescendantID
*/
setActiveDescendant(activeDescendantID) { setActiveDescendant(activeDescendantID) {
this.element.setAttribute('aria-activedescendant', activeDescendantID); this.element.setAttribute('aria-activedescendant', activeDescendantID);
} }
@ -123,6 +142,9 @@ export default class Input {
} }
} }
/**
* @param {Event} event
*/
_onPaste(event) { _onPaste(event) {
if (this.preventPaste) { if (this.preventPaste) {
event.preventDefault(); event.preventDefault();

View file

@ -1,9 +1,14 @@
import { SCROLLING_SPEED } from '../constants'; import { SCROLLING_SPEED } from '../constants';
/**
* @typedef {import('../../../types/index').Choices.Choice} Choice
*/
export default class List { export default class List {
/**
* @param {{ element: HTMLElement }} args
*/
constructor({ element }) { constructor({ element }) {
Object.assign(this, { element }); this.element = element;
this.scrollPos = this.element.scrollTop; this.scrollPos = this.element.scrollTop;
this.height = this.element.offsetHeight; this.height = this.element.offsetHeight;
} }
@ -12,14 +17,24 @@ export default class List {
this.element.innerHTML = ''; this.element.innerHTML = '';
} }
/**
* @param {Element} node
*/
append(node) { append(node) {
this.element.appendChild(node); this.element.appendChild(node);
} }
/**
* @param {string} selector
* @returns {Element}
*/
getChild(selector) { getChild(selector) {
return this.element.querySelector(selector); return this.element.querySelector(selector);
} }
/**
* @returns {boolean}
*/
hasChildren() { hasChildren() {
return this.element.hasChildNodes(); return this.element.hasChildNodes();
} }
@ -28,28 +43,37 @@ export default class List {
this.element.scrollTop = 0; this.element.scrollTop = 0;
} }
scrollToChoice(choice, direction) { /**
if (!choice) { * @param {HTMLElement} element
* @param {1 | -1} direction
*/
scrollToChoice(element, direction) {
if (!element) {
return; return;
} }
const dropdownHeight = this.element.offsetHeight; const dropdownHeight = this.element.offsetHeight;
const choiceHeight = choice.offsetHeight; const elementHeight = element.offsetHeight;
// Distance from bottom of element to top of parent // Distance from bottom of element to top of parent
const choicePos = choice.offsetTop + choiceHeight; const elementPos = element.offsetTop + elementHeight;
// Scroll position of dropdown // Scroll position of dropdown
const containerScrollPos = this.element.scrollTop + dropdownHeight; const containerScrollPos = this.element.scrollTop + dropdownHeight;
// Difference between the choice and scroll position // Difference between the element and scroll position
const destination = const destination =
direction > 0 direction > 0
? this.element.scrollTop + choicePos - containerScrollPos ? this.element.scrollTop + elementPos - containerScrollPos
: choice.offsetTop; : element.offsetTop;
requestAnimationFrame(time => { requestAnimationFrame(() => {
this._animateScroll(time, destination, direction); this._animateScroll(destination, direction);
}); });
} }
/**
* @param {number} scrollPos
* @param {number} strength
* @param {number} destination
*/
_scrollDown(scrollPos, strength, destination) { _scrollDown(scrollPos, strength, destination) {
const easing = (destination - scrollPos) / strength; const easing = (destination - scrollPos) / strength;
const distance = easing > 1 ? easing : 1; const distance = easing > 1 ? easing : 1;
@ -57,6 +81,11 @@ export default class List {
this.element.scrollTop = scrollPos + distance; this.element.scrollTop = scrollPos + distance;
} }
/**
* @param {number} scrollPos
* @param {number} strength
* @param {number} destination
*/
_scrollUp(scrollPos, strength, destination) { _scrollUp(scrollPos, strength, destination) {
const easing = (scrollPos - destination) / strength; const easing = (scrollPos - destination) / strength;
const distance = easing > 1 ? easing : 1; const distance = easing > 1 ? easing : 1;
@ -64,7 +93,11 @@ export default class List {
this.element.scrollTop = scrollPos - distance; this.element.scrollTop = scrollPos - distance;
} }
_animateScroll(time, destination, direction) { /**
* @param {*} destination
* @param {*} direction
*/
_animateScroll(destination, direction) {
const strength = SCROLLING_SPEED; const strength = SCROLLING_SPEED;
const choiceListScrollTop = this.element.scrollTop; const choiceListScrollTop = this.element.scrollTop;
let continueAnimation = false; let continueAnimation = false;
@ -85,7 +118,7 @@ export default class List {
if (continueAnimation) { if (continueAnimation) {
requestAnimationFrame(() => { requestAnimationFrame(() => {
this._animateScroll(time, destination, direction); this._animateScroll(destination, direction);
}); });
} }
} }

View file

@ -1,8 +1,20 @@
import { dispatchEvent } from '../lib/utils'; import { dispatchEvent } from '../lib/utils';
/**
* @typedef {import('../../../types/index').Choices.passedElement} passedElement
* @typedef {import('../../../types/index').Choices.ClassNames} ClassNames
*/
export default class WrappedElement { export default class WrappedElement {
/**
* @param {{
* element: HTMLElement,
* classNames: ClassNames,
* }} args
*/
constructor({ element, classNames }) { constructor({ element, classNames }) {
Object.assign(this, { element, classNames }); this.element = element;
this.classNames = classNames;
if (!(element instanceof Element)) { if (!(element instanceof Element)) {
throw new TypeError('Invalid element passed'); throw new TypeError('Invalid element passed');

View file

@ -1,6 +1,17 @@
import WrappedElement from './wrapped-element'; import WrappedElement from './wrapped-element';
/**
* @typedef {import('../../../types/index').Choices.ClassNames} ClassNames
*/
export default class WrappedInput extends WrappedElement { export default class WrappedInput extends WrappedElement {
/**
* @param {{
* element: HTMLInputElement,
* classNames: ClassNames,
* delimiter: string
* }} args
*/
constructor({ element, classNames, delimiter }) { constructor({ element, classNames, delimiter }) {
super({ element, classNames }); super({ element, classNames });
this.delimiter = delimiter; this.delimiter = delimiter;

View file

@ -1,6 +1,18 @@
import WrappedElement from './wrapped-element'; import WrappedElement from './wrapped-element';
/**
* @typedef {import('../../../types/index').Choices.ClassNames} ClassNames
*/
export default class WrappedSelect extends WrappedElement { export default class WrappedSelect extends WrappedElement {
/**
* @param {{
* element: HTMLSelectElement,
* classNames: ClassNames,
* delimiter: string
* template: function
* }} args
*/
constructor({ element, classNames, template }) { constructor({ element, classNames, template }) {
super({ element, classNames }); super({ element, classNames });
this.template = template; this.template = template;
@ -14,14 +26,23 @@ export default class WrappedSelect extends WrappedElement {
); );
} }
/**
* @returns {Element[]}
*/
get optionGroups() { get optionGroups() {
return Array.from(this.element.getElementsByTagName('OPTGROUP')); return Array.from(this.element.getElementsByTagName('OPTGROUP'));
} }
/**
* @returns {object[]}
*/
get options() { get options() {
return Array.from(this.element.options); return Array.from(this.element.options);
} }
/**
* @param {object[]} options
*/
set options(options) { set options(options) {
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
const addOptionToFragment = data => { const addOptionToFragment = data => {
@ -37,6 +58,9 @@ export default class WrappedSelect extends WrappedElement {
this.appendDocFragment(fragment); this.appendDocFragment(fragment);
} }
/**
* @param {DocumentFragment} fragment
*/
appendDocFragment(fragment) { appendDocFragment(fragment) {
this.element.innerHTML = ''; this.element.innerHTML = '';
this.element.appendChild(fragment); this.element.appendChild(fragment);

View file

@ -36,7 +36,7 @@ export default class Store {
/** /**
* Get store object (wrapping Redux method) * Get store object (wrapping Redux method)
* @return {object} State * @returns {object} State
*/ */
get state() { get state() {
return this._store.getState(); return this._store.getState();
@ -44,7 +44,7 @@ export default class Store {
/** /**
* Get items from store * Get items from store
* @return {Item[]} Item objects * @returns {Item[]} Item objects
*/ */
get items() { get items() {
return this.state.items; return this.state.items;
@ -52,7 +52,7 @@ export default class Store {
/** /**
* Get active items from store * Get active items from store
* @return {Item[]} Item objects * @returns {Item[]} Item objects
*/ */
get activeItems() { get activeItems() {
return this.items.filter(item => item.active === true); return this.items.filter(item => item.active === true);
@ -60,7 +60,7 @@ export default class Store {
/** /**
* Get highlighted items from store * Get highlighted items from store
* @return {Item[]} Item objects * @returns {Item[]} Item objects
*/ */
get highlightedActiveItems() { get highlightedActiveItems() {
return this.items.filter(item => item.active && item.highlighted); return this.items.filter(item => item.active && item.highlighted);
@ -68,7 +68,7 @@ export default class Store {
/** /**
* Get choices from store * Get choices from store
* @return {Choice[]} Option objects * @returns {Choice[]} Option objects
*/ */
get choices() { get choices() {
return this.state.choices; return this.state.choices;
@ -76,7 +76,7 @@ export default class Store {
/** /**
* Get active choices from store * Get active choices from store
* @return {Choice[]} Option objects * @returns {Choice[]} Option objects
*/ */
get activeChoices() { get activeChoices() {
return this.choices.filter(choice => choice.active === true); return this.choices.filter(choice => choice.active === true);
@ -84,7 +84,7 @@ export default class Store {
/** /**
* Get selectable choices from store * Get selectable choices from store
* @return {Choice[]} Option objects * @returns {Choice[]} Option objects
*/ */
get selectableChoices() { get selectableChoices() {
return this.choices.filter(choice => choice.disabled !== true); return this.choices.filter(choice => choice.disabled !== true);
@ -92,7 +92,7 @@ export default class Store {
/** /**
* Get choices that can be searched (excluding placeholders) * Get choices that can be searched (excluding placeholders)
* @return {Choice[]} Option objects * @returns {Choice[]} Option objects
*/ */
get searchableChoices() { get searchableChoices() {
return this.selectableChoices.filter(choice => choice.placeholder !== true); return this.selectableChoices.filter(choice => choice.placeholder !== true);
@ -100,7 +100,7 @@ export default class Store {
/** /**
* Get placeholder choice from store * Get placeholder choice from store
* @return {Choice | undefined} Found placeholder * @returns {Choice | undefined} Found placeholder
*/ */
get placeholderChoice() { get placeholderChoice() {
return [...this.choices] return [...this.choices]
@ -110,7 +110,7 @@ export default class Store {
/** /**
* Get groups from store * Get groups from store
* @return {Group[]} Group objects * @returns {Group[]} Group objects
*/ */
get groups() { get groups() {
return this.state.groups; return this.state.groups;
@ -118,7 +118,7 @@ export default class Store {
/** /**
* Get active groups from store * Get active groups from store
* @return {Group[]} Group objects * @returns {Group[]} Group objects
*/ */
get activeGroups() { get activeGroups() {
const { groups, choices } = this; const { groups, choices } = this;
@ -135,7 +135,7 @@ export default class Store {
/** /**
* Get loading state from store * Get loading state from store
* @return {boolean} Loading State * @returns {boolean} Loading State
*/ */
isLoading() { isLoading() {
return this.state.general.loading; return this.state.general.loading;
@ -144,7 +144,7 @@ export default class Store {
/** /**
* Get single choice by it's ID * Get single choice by it's ID
* @param {string} id * @param {string} id
* @return {Choice | undefined} Found choice * @returns {Choice | undefined} Found choice
*/ */
getChoiceById(id) { getChoiceById(id) {
return this.activeChoices.find(choice => choice.id === parseInt(id, 10)); return this.activeChoices.find(choice => choice.id === parseInt(id, 10));
@ -153,7 +153,7 @@ export default class Store {
/** /**
* Get group by group id * Get group by group id
* @param {string} id Group ID * @param {string} id Group ID
* @return {Group | undefined} Group data * @returns {Group | undefined} Group data
*/ */
getGroupById(id) { getGroupById(id) {
return this.groups.find(group => group.id === parseInt(id, 10)); return this.groups.find(group => group.id === parseInt(id, 10));