Add remaining components

This commit is contained in:
Josh Johnson 2017-08-29 12:56:54 +01:00
parent 4c7cc047dc
commit 809b62c60d
6 changed files with 132 additions and 44 deletions

View file

@ -4,6 +4,7 @@ import Store from './store/index';
import Dropdown from './components/dropdown';
import Container from './components/container';
import Input from './components/input';
import List from './components/list';
import {
addItem,
removeItem,
@ -496,11 +497,11 @@ class Choices {
let choiceListFragment = document.createDocumentFragment();
// Clear choices
this.choiceList.innerHTML = '';
this.choiceList.clear();
// Scroll back to top of choices list
if (this.config.resetScrollPosition) {
this.choiceList.scrollTop = 0;
this.choiceList.scrollTo(0);
}
// If we have grouped options
@ -518,11 +519,11 @@ class Choices {
// ...and we can select them
if (canAddItem.response) {
// ...append them and highlight the first choice
this.choiceList.appendChild(choiceListFragment);
this.choiceList.append(choiceListFragment);
this._highlightChoice();
} else {
// ...otherwise show a notice
this.choiceList.appendChild(this._getTemplate('notice', canAddItem.notice));
this.choiceList.append(this._getTemplate('notice', canAddItem.notice));
}
} else {
// Otherwise show a notice
@ -543,7 +544,7 @@ class Choices {
dropdownItem = this._getTemplate('notice', notice, 'no-choices');
}
this.choiceList.appendChild(dropdownItem);
this.choiceList.append(dropdownItem);
}
}
}
@ -554,7 +555,7 @@ class Choices {
const activeItems = this.store.getItemsFilteredByActive();
// Clear list
this.itemList.innerHTML = '';
this.itemList.clear();
if (activeItems && activeItems) {
// Create a fragment to store our list items
@ -564,7 +565,7 @@ class Choices {
// If we have items to add
if (itemListFragment.childNodes) {
// Update list
this.itemList.appendChild(itemListFragment);
this.itemList.append(itemListFragment);
}
}
}
@ -742,7 +743,7 @@ class Choices {
* @public
*/
showDropdown(focusInput = false) {
this.containerOuter.open(this.dropdown.getPosition());
this.containerOuter.open(this.dropdown.getVerticalPos());
this.dropdown.show();
this.input.activate(focusInput);
@ -1308,13 +1309,13 @@ class Choices {
* @private
*/
_handleLoadingState(isLoading = true) {
let placeholderItem = this.itemList.querySelector(`.${this.config.classNames.placeholder}`);
let placeholderItem = this.itemList.getChild(`.${this.config.classNames.placeholder}`);
if (isLoading) {
this.containerOuter.addLoadingState();
if (this.isSelectOneElement) {
if (!placeholderItem) {
placeholderItem = this._getTemplate('placeholder', this.config.loadingText);
this.itemList.appendChild(placeholderItem);
this.itemList.append(placeholderItem);
} else {
placeholderItem.innerHTML = this.config.loadingText;
}
@ -1520,7 +1521,7 @@ class Choices {
const activeItems = this.store.getItemsFilteredByActive();
const hasFocusedInput = this.input.isFocussed;
const hasActiveDropdown = this.dropdown.isActive;
const hasItems = this.itemList && this.itemList.children;
const hasItems = this.itemList.hasChildren;
const keyString = String.fromCharCode(e.keyCode);
const backKey = 46;
@ -1580,7 +1581,7 @@ class Choices {
if (hasActiveDropdown) {
e.preventDefault();
const highlighted = this.dropdown.getHighlightedChildren();
const highlighted = this.dropdown.getChild(`.${this.config.classNames.highlightedState}`);
// If we have a highlighted choice
if (highlighted) {
@ -1997,20 +1998,20 @@ class Choices {
return;
}
const dropdownHeight = this.choiceList.offsetHeight;
const dropdownHeight = this.choiceList.height;
const choiceHeight = choice.offsetHeight;
// Distance from bottom of element to top of parent
const choicePos = choice.offsetTop + choiceHeight;
// Scroll position of dropdown
const containerScrollPos = this.choiceList.scrollTop + dropdownHeight;
const containerScrollPos = this.choiceList.scrollPos + dropdownHeight;
// Difference between the choice and scroll position
const endPoint = direction > 0 ? (
(this.choiceList.scrollTop + choicePos) - containerScrollPos) :
(this.choiceList.scrollPos + choicePos) - containerScrollPos) :
choice.offsetTop;
const animateScroll = () => {
const strength = 4;
const choiceListScrollTop = this.choiceList.scrollTop;
const choiceListScrollTop = this.choiceList.scrollPos;
let continueAnimation = false;
let easing;
let distance;
@ -2019,7 +2020,7 @@ class Choices {
easing = (endPoint - choiceListScrollTop) / strength;
distance = easing > 1 ? easing : 1;
this.choiceList.scrollTop = choiceListScrollTop + distance;
this.choiceList.scrollTo(choiceListScrollTop + distance);
if (choiceListScrollTop < endPoint) {
continueAnimation = true;
}
@ -2027,7 +2028,7 @@ class Choices {
easing = (choiceListScrollTop - endPoint) / strength;
distance = easing > 1 ? easing : 1;
this.choiceList.scrollTop = choiceListScrollTop - distance;
this.choiceList.scrollTo(choiceListScrollTop - distance);
if (choiceListScrollTop > endPoint) {
continueAnimation = true;
}
@ -2604,8 +2605,8 @@ class Choices {
this.containerOuter = new Container(this, containerOuter);
this.containerInner = new Container(this, containerInner);
this.input = new Input(this, input);
this.choiceList = choiceList;
this.itemList = itemList;
this.choiceList = new List(this, choiceList);
this.itemList = new List(this, itemList);
this.dropdown = new Dropdown(this, dropdown, this.config.classNames);
// Hide passed input

View file

@ -1,6 +1,3 @@
/**
* Container
*/
export default class Container {
constructor(instance, element) {
this.instance = instance;
@ -11,28 +8,49 @@ export default class Container {
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);
}
/**
* Set focussed state
*/
onFocus() {
this.isFocussed = true;
}
/**
* Remove blurred state
*/
onBlur() {
this.isFocussed = false;
}
/**
* Determine whether container should be flipped
* based on passed dropdown position
* @param {Number} dropdownPos
* @returns
*/
shouldFlip(dropdownPos) {
if (!dropdownPos) {
return false;
@ -60,10 +78,17 @@ export default class Container {
return shouldFlip;
}
/**
* Set active descendant attribute
* @param {Number} activeDescendant ID of active descendant
*/
setActiveDescendant(activeDescendant) {
this.element.setAttribute('aria-activedescendant', activeDescendant);
}
/**
* Remove active descendant attribute
*/
removeActiveDescendant() {
this.element.removeAttribute('aria-activedescendant');
}
@ -106,6 +131,9 @@ export default class Container {
this.element.classList.remove(this.classNames.focusState);
}
/**
* Remove disabled state
*/
enable() {
this.element.classList.remove(this.config.classNames.disabledState);
this.element.removeAttribute('aria-disabled');
@ -115,6 +143,9 @@ export default class Container {
this.isDisabled = false;
}
/**
* Set disabled state
*/
disable() {
this.element.classList.add(this.config.classNames.disabledState);
this.element.setAttribute('aria-disabled', 'true');
@ -124,13 +155,21 @@ export default class Container {
this.isDisabled = true;
}
/**
* 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;
}
}

View file

@ -1,6 +1,3 @@
/**
* Dropdown
*/
export default class Dropdown {
constructor(instance, element, classNames) {
this.instance = instance;
@ -11,16 +8,23 @@ export default class Dropdown {
this.isActive = false;
}
getPosition() {
/**
* Determine how far the top of our element is from
* the top of the window
* @return {Number} Vertical position
*/
getVerticalPos() {
this.dimensions = this.element.getBoundingClientRect();
this.position = Math.ceil(this.dimensions.top + window.scrollY + this.element.offsetHeight);
return this.position;
}
getHighlightedChildren() {
return this.element.querySelector(
`.${this.classNames.highlightedState}`,
);
/**
* Find element that matches passed selector
* @return {HTMLElement}
*/
getChild(selector) {
return this.element.querySelector(selector);
}
/**

View file

@ -1,14 +1,11 @@
import { getWidthOfInput } from '../lib/utils';
/**
* Input
*/
export default class Input {
constructor(instance, element, classNames) {
constructor(instance, element) {
this.instance = instance;
this.element = element;
this.classNames = classNames;
this.isFocussed = this.element === document.activeElement;
this.isDisabled = false;
// Bind event listeners
this.onPaste = this.onPaste.bind(this);
@ -55,10 +52,16 @@ export default class Input {
}
}
/**
* Set focussed state
*/
onFocus() {
this.isFocussed = true;
}
/**
* Remove focussed state
*/
onBlur() {
this.isFocussed = false;
}
@ -80,10 +83,12 @@ export default class Input {
enable() {
this.element.removeAttribute('disabled');
this.isDisabled = false;
}
disable() {
this.element.setAttribute('disabled', '');
this.isDisabled = true;
}
focus() {

View file

@ -0,0 +1,39 @@
export default class List {
constructor(instance, element) {
this.instance = instance;
this.element = element;
this.classNames = this.instance.config.classNames;
this.scrollPos = this.element.scrollTop;
this.height = this.element.offsetHeight;
this.hasChildren = !!this.element.children;
}
/**
* Clear List contents
*/
clear() {
this.element.innerHTML = '';
}
/**
* Scroll to passed position on Y axis
*/
scrollTo(scrollPos) {
this.element.scrollTop = scrollPos;
}
/**
* Append node to element
*/
append(node) {
this.element.appendChild(node);
}
/**
* Find element that matches passed selector
* @return {HTMLElement}
*/
getChild(selector) {
return this.element.querySelector(selector);
}
}

View file

@ -124,11 +124,11 @@ describe('Choices', () => {
});
it('should create a choice list', function() {
expect(this.choices.choiceList).toEqual(jasmine.any(HTMLElement));
expect(this.choices.choiceList.element).toEqual(jasmine.any(HTMLElement));
});
it('should create an item list', function() {
expect(this.choices.itemList).toEqual(jasmine.any(HTMLElement));
expect(this.choices.itemList.element).toEqual(jasmine.any(HTMLElement));
});
it('should create an input', function() {
@ -219,7 +219,7 @@ describe('Choices', () => {
});
expect(
this.choices.currentState.items[this.choices.currentState.items.length - 1]
this.choices.currentState.items[this.choices.currentState.items.length - 1],
).not.toContain(this.choices.input.element.value);
});
@ -435,7 +435,7 @@ describe('Choices', () => {
expect(
document.activeElement === this.choices.input.element &&
container.classList.contains(openState)
container.classList.contains(openState),
).toBe(false);
});
@ -896,7 +896,7 @@ describe('Choices', () => {
expect(this.choices.input.element.disabled).toBe(true);
expect(
this.choices.containerOuter.element.classList.contains(
this.choices.config.classNames.disabledState
this.choices.config.classNames.disabledState,
),
).toBe(true);
expect(this.choices.containerOuter.element.getAttribute('aria-disabled')).toBe('true');
@ -1053,7 +1053,7 @@ describe('Choices', () => {
renderSelectedChoices: 'always',
renderChoiceLimit: -1,
});
const renderedChoices = this.choices.choiceList.querySelectorAll('.choices__item');
const renderedChoices = this.choices.choiceList.element.querySelectorAll('.choices__item');
expect(renderedChoices.length).toEqual(3);
});
@ -1062,7 +1062,7 @@ describe('Choices', () => {
renderSelectedChoices: 'auto',
renderChoiceLimit: -1,
});
const renderedChoices = this.choices.choiceList.querySelectorAll('.choices__item');
const renderedChoices = this.choices.choiceList.element.querySelectorAll('.choices__item');
expect(renderedChoices.length).toEqual(1);
});
@ -1099,7 +1099,7 @@ describe('Choices', () => {
renderChoiceLimit: 4,
});
const renderedChoices = this.choices.choiceList.querySelectorAll('.choices__item');
const renderedChoices = this.choices.choiceList.element.querySelectorAll('.choices__item');
expect(renderedChoices.length).toEqual(4);
});
});