From 31a86cc4b6093edd0727d697e764281b1faaf1dd Mon Sep 17 00:00:00 2001 From: Josh Johnson Date: Sat, 2 Nov 2019 21:25:28 +0000 Subject: [PATCH] Add jsdoc comments to components --- src/scripts/components/container.js | 68 ++++++++++------------- src/scripts/components/dropdown.js | 30 +++++++--- src/scripts/components/input.js | 34 ++++++++++-- src/scripts/components/list.js | 59 +++++++++++++++----- src/scripts/components/wrapped-element.js | 14 ++++- src/scripts/components/wrapped-input.js | 11 ++++ src/scripts/components/wrapped-select.js | 24 ++++++++ src/scripts/store/store.js | 28 +++++----- 8 files changed, 187 insertions(+), 81 deletions(-) diff --git a/src/scripts/components/container.js b/src/scripts/components/container.js index 5e8d0ce..65ea665 100644 --- a/src/scripts/components/container.js +++ b/src/scripts/components/container.js @@ -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; } diff --git a/src/scripts/components/dropdown.js b/src/scripts/components/dropdown.js index 01d4bdb..0f7140d 100644 --- a/src/scripts/components/dropdown.js +++ b/src/scripts/components/dropdown.js @@ -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); diff --git a/src/scripts/components/input.js b/src/scripts/components/input.js index f6a7a41..db67510 100644 --- a/src/scripts/components/input.js +++ b/src/scripts/components/input.js @@ -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(); diff --git a/src/scripts/components/list.js b/src/scripts/components/list.js index be801f7..fb02a94 100644 --- a/src/scripts/components/list.js +++ b/src/scripts/components/list.js @@ -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); }); } } diff --git a/src/scripts/components/wrapped-element.js b/src/scripts/components/wrapped-element.js index a4fb2a8..29ce069 100644 --- a/src/scripts/components/wrapped-element.js +++ b/src/scripts/components/wrapped-element.js @@ -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'); diff --git a/src/scripts/components/wrapped-input.js b/src/scripts/components/wrapped-input.js index 8fdf5c6..4657b7e 100644 --- a/src/scripts/components/wrapped-input.js +++ b/src/scripts/components/wrapped-input.js @@ -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; diff --git a/src/scripts/components/wrapped-select.js b/src/scripts/components/wrapped-select.js index 129f81f..748cfaa 100644 --- a/src/scripts/components/wrapped-select.js +++ b/src/scripts/components/wrapped-select.js @@ -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); diff --git a/src/scripts/store/store.js b/src/scripts/store/store.js index 77afa8e..364a7f6 100644 --- a/src/scripts/store/store.js +++ b/src/scripts/store/store.js @@ -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));