Choices/src/scripts/components/list.js
Josh Johnson ab22347d7b
Code refactoring (#735)
* Add placeholder options to demo page

* Use constant types in components

* Refactor adding predefined groups/items/choices

* Add 'highlighted' flag to Item type

* Fix dispatch param type

* Build

* Add jsdoc comments to utils

* Remove unused file

* Add default values to js doc comments

* Use Redux Action type

* Housekeeping

* Increase utils coverage

* Apply suggestions from code review

* Add _getTemplate unit tests
2019-11-03 17:45:16 +00:00

128 lines
2.9 KiB
JavaScript

import { SCROLLING_SPEED } from '../constants';
/**
* @typedef {import('../../../types/index').Choices.Choice} Choice
*/
export default class List {
/**
* @param {{ element: HTMLElement }} args
*/
constructor({ element }) {
this.element = element;
this.scrollPos = this.element.scrollTop;
this.height = this.element.offsetHeight;
}
clear() {
this.element.innerHTML = '';
}
/**
* @param {Element | DocumentFragment} node
*/
append(node) {
this.element.appendChild(node);
}
/**
* @param {string} selector
* @returns {Element | null}
*/
getChild(selector) {
return this.element.querySelector(selector);
}
/**
* @returns {boolean}
*/
hasChildren() {
return this.element.hasChildNodes();
}
scrollToTop() {
this.element.scrollTop = 0;
}
/**
* @param {HTMLElement} element
* @param {1 | -1} direction
*/
scrollToChildElement(element, direction) {
if (!element) {
return;
}
const listHeight = this.element.offsetHeight;
// Scroll position of dropdown
const listScrollPosition = this.element.scrollTop + listHeight;
const elementHeight = element.offsetHeight;
// Distance from bottom of element to top of parent
const elementPos = element.offsetTop + elementHeight;
// Difference between the element and scroll position
const destination =
direction > 0
? this.element.scrollTop + elementPos - listScrollPosition
: element.offsetTop;
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;
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;
this.element.scrollTop = scrollPos - distance;
}
/**
* @param {*} destination
* @param {*} direction
*/
_animateScroll(destination, direction) {
const strength = SCROLLING_SPEED;
const choiceListScrollTop = this.element.scrollTop;
let continueAnimation = false;
if (direction > 0) {
this._scrollDown(choiceListScrollTop, strength, destination);
if (choiceListScrollTop < destination) {
continueAnimation = true;
}
} else {
this._scrollUp(choiceListScrollTop, strength, destination);
if (choiceListScrollTop > destination) {
continueAnimation = true;
}
}
if (continueAnimation) {
requestAnimationFrame(() => {
this._animateScroll(destination, direction);
});
}
}
}