And more...

This commit is contained in:
Josh Johnson 2019-12-14 16:40:15 +00:00
parent 585ee6457f
commit 3f850f61d6
4 changed files with 78 additions and 107 deletions

View file

@ -1503,7 +1503,8 @@ class Choices {
}: Pick<KeyDownAction, 'event' | 'activeItems' | 'hasActiveDropdown'>): void { }: Pick<KeyDownAction, 'event' | 'activeItems' | 'hasActiveDropdown'>): void {
const { target } = event; const { target } = event;
const { ENTER_KEY: enterKey } = KEY_CODES; const { ENTER_KEY: enterKey } = KEY_CODES;
const targetWasButton = target && target.hasAttribute('data-button'); const targetWasButton =
target && (target as HTMLElement).hasAttribute('data-button');
if (this._isTextElement && target && target.value) { if (this._isTextElement && target && target.value) {
const { value } = this.input; const { value } = this.input;
@ -1518,7 +1519,7 @@ class Choices {
} }
if (targetWasButton) { if (targetWasButton) {
this._handleButtonAction(activeItems, target); this._handleButtonAction(activeItems, target as HTMLElement);
event.preventDefault(); event.preventDefault();
} }
@ -1618,6 +1619,10 @@ class Choices {
hasFocusedInput, hasFocusedInput,
activeItems, activeItems,
}: Partial<KeyDownAction>): void { }: Partial<KeyDownAction>): void {
if (!event || event.type !== 'KeyboardEvent' || !event.target) {
return;
}
const { target } = event; const { target } = event;
// If backspace or delete key is pressed and the input has no value // If backspace or delete key is pressed and the input has no value
if (hasFocusedInput && !target.value && !this._isSelectOneElement) { if (hasFocusedInput && !target.value && !this._isSelectOneElement) {
@ -1635,7 +1640,7 @@ class Choices {
_onTouchEnd(event: TouchEvent): void { _onTouchEnd(event: TouchEvent): void {
const { target } = event || event.touches[0]; const { target } = event || event.touches[0];
const touchWasWithinContainer = const touchWasWithinContainer =
this._wasTap && this.containerOuter.element.contains(target); this._wasTap && this.containerOuter.element.contains(target as Node);
if (touchWasWithinContainer) { if (touchWasWithinContainer) {
const containerWasExactTarget = const containerWasExactTarget =
@ -1829,7 +1834,7 @@ class Choices {
} }
_highlightChoice(el: HTMLElement | null = null): void { _highlightChoice(el: HTMLElement | null = null): void {
const choices = Array.from( const choices: HTMLElement[] = Array.from(
this.dropdown.element.querySelectorAll('[data-choice-selectable]'), this.dropdown.element.querySelectorAll('[data-choice-selectable]'),
); );
@ -2059,7 +2064,10 @@ class Choices {
} }
} }
_getTemplate<K extends keyof Templates>(template: K, ...args): Templates[K] { _getTemplate<K extends keyof Templates>(
template: K,
...args: any
): HTMLElement | HTMLOptionElement {
const { classNames } = this.config; const { classNames } = this.config;
return this._templates[template].call(this, classNames, ...args); return this._templates[template].call(this, classNames, ...args);

View file

@ -4,7 +4,7 @@ import { ClassNames, Item, Choice } from '../interfaces';
export default class WrappedSelect extends WrappedElement { export default class WrappedSelect extends WrappedElement {
element: HTMLSelectElement; element: HTMLSelectElement;
classNames: ClassNames; classNames: ClassNames;
template: () => HTMLElement; template: (data: object) => HTMLOptionElement;
constructor({ constructor({
element, element,
@ -13,7 +13,7 @@ export default class WrappedSelect extends WrappedElement {
}: { }: {
element: HTMLSelectElement; element: HTMLSelectElement;
classNames: ClassNames; classNames: ClassNames;
template: () => HTMLElement; template: (data: object) => HTMLOptionElement;
}) { }) {
super({ element, classNames }); super({ element, classNames });
this.template = template; this.template = template;
@ -37,7 +37,7 @@ export default class WrappedSelect extends WrappedElement {
set options(options: Item[] | Choice[]): void { set options(options: Item[] | Choice[]): void {
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
const addOptionToFragment = data => { const addOptionToFragment = (data): void => {
// Create a standard select option // Create a standard select option
const option = this.template(data); const option = this.template(data);
// Append it to fragment // Append it to fragment

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import Choices from './choices'; import Choices from './choices';
export namespace Types { export namespace Types {

View file

@ -1,24 +1,17 @@
/** import { EventMap } from '../interfaces';
* @param {number} min
* @param {number} max /* eslint-disable @typescript-eslint/no-explicit-any */
* @returns {number}
*/ export const getRandomNumber = (min: number, max: number): number =>
export const getRandomNumber = (min, max) =>
Math.floor(Math.random() * (max - min) + min); Math.floor(Math.random() * (max - min) + min);
/** export const generateChars = (length: number): string =>
* @param {number} length
* @returns {string}
*/
export const generateChars = length =>
Array.from({ length }, () => getRandomNumber(0, 36).toString(36)).join(''); Array.from({ length }, () => getRandomNumber(0, 36).toString(36)).join('');
/** export const generateId = (
* @param {HTMLInputElement | HTMLSelectElement} element element: HTMLInputElement | HTMLSelectElement,
* @param {string} prefix prefix: string,
* @returns {string} ): string => {
*/
export const generateId = (element, prefix) => {
let id = let id =
element.id || element.id ||
(element.name && `${element.name}-${generateChars(2)}`) || (element.name && `${element.name}-${generateChars(2)}`) ||
@ -29,46 +22,31 @@ export const generateId = (element, prefix) => {
return id; return id;
}; };
/** export const getType = (obj: any): string =>
* @param {any} obj Object.prototype.toString.call(obj).slice(8, -1);
* @returns {string}
*/
export const getType = obj => Object.prototype.toString.call(obj).slice(8, -1);
/** export const isType = (type: string, obj: any): boolean =>
* @param {string} type
* @param {any} obj
* @returns {boolean}
*/
export const isType = (type, obj) =>
obj !== undefined && obj !== null && getType(obj) === type; obj !== undefined && obj !== null && getType(obj) === type;
/** export const wrap = (
* @param {HTMLElement} element element: HTMLElement,
* @param {HTMLElement} [wrapper={HTMLDivElement}] wrapper: HTMLElement = document.createElement('div'),
* @returns {HTMLElement} ): HTMLElement => {
*/
export const wrap = (element, wrapper = document.createElement('div')) => {
if (element.nextSibling) { if (element.nextSibling) {
element.parentNode.insertBefore(wrapper, element.nextSibling); element.parentNode &&
element.parentNode.insertBefore(wrapper, element.nextSibling);
} else { } else {
element.parentNode.appendChild(wrapper); element.parentNode && element.parentNode.appendChild(wrapper);
} }
return wrapper.appendChild(element); return wrapper.appendChild(element);
}; };
/** export const getAdjacentEl = (
* @param {Element} startEl startEl: Element,
* @param {string} selector selector: string,
* @param {1 | -1} direction direction = 1,
* @returns {Element | undefined} ): Element => {
*/
export const getAdjacentEl = (startEl, selector, direction = 1) => {
if (!(startEl instanceof Element) || typeof selector !== 'string') {
return undefined;
}
const prop = `${direction > 0 ? 'next' : 'previous'}ElementSibling`; const prop = `${direction > 0 ? 'next' : 'previous'}ElementSibling`;
let sibling = startEl[prop]; let sibling = startEl[prop];
@ -82,13 +60,11 @@ export const getAdjacentEl = (startEl, selector, direction = 1) => {
return sibling; return sibling;
}; };
/** export const isScrolledIntoView = (
* @param {Element} element element: HTMLElement,
* @param {Element} parent parent: HTMLElement,
* @param {-1 | 1} direction direction = 1,
* @returns {boolean} ): boolean => {
*/
export const isScrolledIntoView = (element, parent, direction = 1) => {
if (!element) { if (!element) {
return false; return false;
} }
@ -108,11 +84,7 @@ export const isScrolledIntoView = (element, parent, direction = 1) => {
return isVisible; return isVisible;
}; };
/** export const sanitise = <T>(value: T | string): T | string => {
* @param {any} value
* @returns {any}
*/
export const sanitise = value => {
if (typeof value !== 'string') { if (typeof value !== 'string') {
return value; return value;
} }
@ -124,13 +96,10 @@ export const sanitise = value => {
.replace(/"/g, '&quot;'); .replace(/"/g, '&quot;');
}; };
/** export const strToEl = ((): ((str: string) => Element) => {
* @returns {() => (str: string) => Element}
*/
export const strToEl = (() => {
const tmpEl = document.createElement('div'); const tmpEl = document.createElement('div');
return str => { return (str): Element => {
const cleanedInput = str.trim(); const cleanedInput = str.trim();
tmpEl.innerHTML = cleanedInput; tmpEl.innerHTML = cleanedInput;
const firldChild = tmpEl.children[0]; const firldChild = tmpEl.children[0];
@ -143,33 +112,31 @@ export const strToEl = (() => {
}; };
})(); })();
/** interface RecordToCompare {
* @param {{ label?: string, value: string }} a value: string;
* @param {{ label?: string, value: string }} b label?: string;
* @returns {number} }
*/
export const sortByAlpha = ( export const sortByAlpha = (
{ value, label = value }, { value, label = value }: RecordToCompare,
{ value: value2, label: label2 = value2 }, { value: value2, label: label2 = value2 }: RecordToCompare,
) => ): number =>
label.localeCompare(label2, [], { label.localeCompare(label2, [], {
sensitivity: 'base', sensitivity: 'base',
ignorePunctuation: true, ignorePunctuation: true,
numeric: true, numeric: true,
}); });
/** interface RecordToSort {
* @param {{ score: number }} a score: number;
* @param {{ score: number }} b }
*/ export const sortByScore = (a: RecordToSort, b: RecordToSort): number =>
export const sortByScore = (a, b) => a.score - b.score; a.score - b.score;
/** export const dispatchEvent = (
* @param {HTMLElement} element element: HTMLElement,
* @param {string} type type: keyof EventMap,
* @param {object} customArgs customArgs: object | null = null,
*/ ): boolean => {
export const dispatchEvent = (element, type, customArgs = null) => {
const event = new CustomEvent(type, { const event = new CustomEvent(type, {
detail: customArgs, detail: customArgs,
bubbles: true, bubbles: true,
@ -179,13 +146,11 @@ export const dispatchEvent = (element, type, customArgs = null) => {
return element.dispatchEvent(event); return element.dispatchEvent(event);
}; };
/** export const existsInArray = (
* @param {array} array array: any[],
* @param {any} value value: string,
* @param {string} [key="value"] key = 'value',
* @returns {boolean} ): boolean =>
*/
export const existsInArray = (array, value, key = 'value') =>
array.some(item => { array.some(item => {
if (typeof value === 'string') { if (typeof value === 'string') {
return item[key] === value.trim(); return item[key] === value.trim();
@ -194,19 +159,16 @@ export const existsInArray = (array, value, key = 'value') =>
return item[key] === value; return item[key] === value;
}); });
/** export const cloneObject = (obj: object): object =>
* @param {any} obj JSON.parse(JSON.stringify(obj));
* @returns {any}
*/
export const cloneObject = obj => JSON.parse(JSON.stringify(obj));
/** /**
* Returns an array of keys present on the first but missing on the second object * Returns an array of keys present on the first but missing on the second object
* @param {object} a
* @param {object} b
* @returns {string[]}
*/ */
export const diff = (a, b) => { export const diff = (
a: Record<string, any>,
b: Record<string, any>,
): string[] => {
const aKeys = Object.keys(a).sort(); const aKeys = Object.keys(a).sort();
const bKeys = Object.keys(b).sort(); const bKeys = Object.keys(b).sort();