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';
/**
* @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 }) {
Object.assign(this, { element, classNames, type, 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);
}
/**
* Add event listeners
*/
addEventListeners() {
this.element.addEventListener('focus', this._onFocus);
this.element.addEventListener('blur', this._onBlur);
}
/**
* Remove event listeners
*/
/** */
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
* Determine whether container should be flipped based on passed
* dropdown position
* @param {number} dropdownPos
* @returns {boolean}
*/
shouldFlip(dropdownPos) {
if (typeof dropdownPos !== 'number') {
@ -57,20 +62,19 @@ export default class Container {
}
/**
* Set active descendant attribute
* @param {Number} activeDescendant ID of active descendant
* @param {number} activeDescendantID
*/
setActiveDescendant(activeDescendantID) {
this.element.setAttribute('aria-activedescendant', activeDescendantID);
}
/**
* Remove active descendant attribute
*/
removeActiveDescendant() {
this.element.removeAttribute('aria-activedescendant');
}
/**
* @param {number} dropdownPos
*/
open(dropdownPos) {
this.element.classList.add(this.classNames.openState);
this.element.setAttribute('aria-expanded', 'true');
@ -109,9 +113,6 @@ export default class Container {
this.element.classList.remove(this.classNames.focusState);
}
/**
* Remove disabled state
*/
enable() {
this.element.classList.remove(this.classNames.disabledState);
this.element.removeAttribute('aria-disabled');
@ -121,9 +122,6 @@ export default class Container {
this.isDisabled = false;
}
/**
* Set disabled state
*/
disable() {
this.element.classList.add(this.classNames.disabledState);
this.element.setAttribute('aria-disabled', 'true');
@ -133,10 +131,16 @@ export default class Container {
this.isDisabled = true;
}
/**
* @param {Element} 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);
@ -144,34 +148,22 @@ export default class Container {
this.element.parentNode.removeChild(this.element);
}
/**
* Add loading state to element
*/
addLoadingState() {
this.element.classList.add(this.classNames.loadingState);
this.element.setAttribute('aria-busy', 'true');
this.isLoading = true;
}
/**
* Remove loading state from element
*/
removeLoadingState() {
this.element.classList.remove(this.classNames.loadingState);
this.element.removeAttribute('aria-busy');
this.isLoading = false;
}
/**
* Set focussed state
*/
_onFocus() {
this.isFocussed = true;
}
/**
* Remove blurred state
*/
_onBlur() {
this.isFocussed = false;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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