update repo structure

This commit is contained in:
Rene Haas
2022-07-30 19:45:11 +02:00
parent 908f692569
commit 843d627715
158 changed files with 26998 additions and 9790 deletions
+107
View File
@@ -0,0 +1,107 @@
// @ts-ignore
import {
createDiv,
appendChildren,
parent,
style,
on,
off,
addClass,
WH,
XY,
clientSize,
each,
} from './support';
type ResizeListener = (width: number, height: number) => void;
export const resize = (element: HTMLElement) => {
const resizeListeners: ResizeListener[] = [];
const strMouseTouchDownEvent = 'mousedown touchstart';
const strMouseTouchUpEvent = 'mouseup touchend';
const strMouseTouchMoveEvent = 'mousemove touchmove';
const strSelectStartEvent = 'selectstart';
const dragStartSize: WH<number> = { w: 0, h: 0 };
const dragStartPosition: XY<number> = { x: 0, y: 0 };
const resizeBtn = createDiv('resizeBtn');
appendChildren(element, resizeBtn);
addClass(element, 'resizer');
let dragResizeBtn: HTMLElement | undefined;
let dragResizer: HTMLElement | undefined;
const onSelectStart = (event: Event) => {
event.preventDefault();
return false;
};
const resizerResize = (event: MouseEvent | TouchEvent) => {
const isTouchEvent = (event as TouchEvent).touches !== undefined;
const mouseOffsetHolder = isTouchEvent
? (event as TouchEvent).touches[0]
: (event as MouseEvent);
const sizeStyle = {
width: dragStartSize.w + mouseOffsetHolder.pageX - dragStartPosition.x,
height: dragStartSize.h + mouseOffsetHolder.pageY - dragStartPosition.y,
};
style(dragResizer, sizeStyle);
each(resizeListeners, (listener: ResizeListener) => {
if (listener) {
listener(sizeStyle.width, sizeStyle.height);
}
});
event.stopPropagation();
};
const resizerResized = () => {
off(document, strSelectStartEvent, onSelectStart);
off(document, strMouseTouchMoveEvent, resizerResize);
off(document, strMouseTouchUpEvent, resizerResized);
dragResizer = undefined;
dragResizeBtn = undefined;
};
on(
resizeBtn,
strMouseTouchDownEvent,
(event: MouseEvent | TouchEvent) => {
const { currentTarget } = event;
const correctButton = (event as MouseEvent).buttons === 1 || event.which === 1;
const isTouchEvent = (event as TouchEvent).touches !== undefined;
const mouseOffsetHolder = isTouchEvent
? (event as TouchEvent).touches[0]
: (event as MouseEvent);
if (correctButton || isTouchEvent) {
dragStartPosition.x = mouseOffsetHolder.pageX;
dragStartPosition.y = mouseOffsetHolder.pageY;
dragResizeBtn = currentTarget as HTMLElement;
dragResizer = parent(currentTarget as HTMLElement) as HTMLElement;
const cSize = clientSize(element);
dragStartSize.w = cSize.w;
dragStartSize.h = cSize.h;
on(document, strSelectStartEvent, onSelectStart, { _passive: false });
on(document, strMouseTouchMoveEvent, resizerResize, { _passive: false });
on(document, strMouseTouchUpEvent, resizerResized, { _passive: false });
event.preventDefault();
event.stopPropagation();
}
},
{ _passive: false }
);
return {
addResizeListener(listener: ResizeListener) {
resizeListeners.push(listener);
},
};
};
+110
View File
@@ -0,0 +1,110 @@
function isEvent(obj: any): obj is Event {
return obj instanceof Event || !!obj.target;
}
// eslint-disable-next-line
const noop = <T>(): T => {
return {} as T;
};
const getSelectOptions = (selectElement: HTMLSelectElement) =>
Array.from(selectElement.options).map((option) => option.value);
export const generateSelectCallback =
(
targetElms: HTMLElement[] | HTMLElement | null,
callback: (
targetAffectedElm: HTMLElement,
possibleValues: string[],
selectedValue: string
) => any
) =>
(event: Event | HTMLSelectElement | null) => {
const target: HTMLSelectElement | null = isEvent(event)
? (event.target as HTMLSelectElement)
: event;
if (target) {
const selectedOption = target.value;
const selectOptions = getSelectOptions(target);
const elmsArr = Array.isArray(targetElms) ? targetElms : [targetElms];
elmsArr.forEach((elm) => {
if (elm) {
callback(elm, selectOptions, selectedOption);
}
});
}
};
export const generateClassChangeSelectCallback = (targetElms: HTMLElement[] | HTMLElement | null) =>
generateSelectCallback(targetElms, (targetAffectedElm, possibleValues, selectedValue) => {
possibleValues.forEach((clazz) => targetAffectedElm.classList.remove(clazz));
targetAffectedElm.classList.add(selectedValue);
});
export const selectOption = (
select: HTMLSelectElement | null,
selectedOption: string | number
): boolean => {
if (!select) {
return false;
}
const options = getSelectOptions(select);
const currValue = select.value;
if (selectedOption === currValue) {
return false;
}
if (typeof selectedOption === 'string' && options.includes(selectedOption)) {
select.value = selectedOption;
} else if (
typeof selectedOption === 'number' &&
options.length < selectedOption &&
selectedOption > -1
) {
select.selectedIndex = selectedOption;
}
let event;
if (typeof Event === 'function') {
event = new Event('change');
} else {
event = document.createEvent('Event');
event.initEvent('change', true, true);
}
select.dispatchEvent(event);
return true;
};
export const iterateSelect = async <T>(
select: HTMLSelectElement | null,
options?: {
filter?: (value: string, index: number, array: string[]) => boolean;
beforeEach?: () => T | Promise<T>;
check?: (input: T, selectedOptions: string) => void | Promise<void>;
afterEach?: () => void | Promise<void>;
}
) => {
if (select) {
const { beforeEach = noop, check = noop, afterEach = noop, filter } = options || {};
const selectOptions = getSelectOptions(select);
const selectOptionsReversed = getSelectOptions(select).reverse();
const iterateOptions = [...selectOptions, ...selectOptionsReversed].filter(
filter || (() => true)
);
for (let i = 0; i < iterateOptions.length; i++) {
const option = iterateOptions[i];
// eslint-disable-next-line
const beforeEachObj: T = await beforeEach();
if (selectOption(select, option)) {
// eslint-disable-next-line
await check(beforeEachObj, option);
// eslint-disable-next-line
await afterEach();
}
}
}
};
+33
View File
@@ -0,0 +1,33 @@
import { waitFor, waitForOptions } from '@testing-library/dom';
const getTestResultElm = () => document.getElementById('testResult');
export const setTestResult = (result: boolean | null) => {
const elm = getTestResultElm();
if (elm) {
if (typeof result === 'boolean') {
if (result) {
if (elm.getAttribute('class') === 'failed') {
return;
}
}
elm.setAttribute('class', result ? 'passed' : 'failed');
} else {
elm.removeAttribute('class');
}
}
};
export const testPassed = (): boolean => {
const elm = getTestResultElm();
return elm ? elm.getAttribute('class') === 'passed' : false;
};
export const waitForOrFailTest = <T>(callback: () => T | Promise<T>, options?: waitForOptions) =>
waitFor(callback, {
...options,
onTimeout(error: Error): Error {
setTestResult(false);
return error;
},
});
+1
View File
@@ -0,0 +1 @@
export const timeout = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
+3
View File
@@ -0,0 +1,3 @@
export * from './Select';
export * from './TestResult';
export * from './Timeout';
@@ -0,0 +1,79 @@
import { isString } from '../utils/types';
import { each } from '../utils/array';
import { keys } from '../utils/object';
type ClassContainingElement = Node | Element | false | null | undefined;
type ClassName = string | false | null | undefined;
const rnothtmlwhite = /[^\x20\t\r\n\f]+/g;
const classListAction = (
elm: ClassContainingElement,
className: ClassName,
action: (elmClassList: DOMTokenList, clazz: string) => boolean | void
): boolean => {
const classList = elm && (elm as Element).classList;
let clazz: string;
let i = 0;
let result = false;
if (classList && className && isString(className)) {
const classes: Array<string> = className.match(rnothtmlwhite) || [];
result = classes.length > 0;
while ((clazz = classes[i++])) {
result = !!action(classList, clazz) && result;
}
}
return result;
};
/**
* Check whether the given element has the given class name(s).
* @param elm The element.
* @param className The class name(s).
*/
export const hasClass = (elm: ClassContainingElement, className: ClassName): boolean =>
classListAction(elm, className, (classList, clazz) => classList.contains(clazz));
/**
* Removes the given class name(s) from the given element.
* @param elm The element.
* @param className The class name(s) which shall be removed. (separated by spaces)
*/
export const removeClass = (elm: ClassContainingElement, className: ClassName): void => {
classListAction(elm, className, (classList, clazz) => classList.remove(clazz));
};
/**
* Adds the given class name(s) to the given element.
* @param elm The element.
* @param className The class name(s) which shall be added. (separated by spaces)
* @returns A function which removes the added class name(s).
*/
export const addClass = (elm: ClassContainingElement, className: ClassName): (() => void) => {
classListAction(elm, className, (classList, clazz) => classList.add(clazz));
return removeClass.bind(0, elm, className);
};
/**
* Takes two className strings, compares them and returns the difference as array.
* @param classNameA ClassName A.
* @param classNameB ClassName B.
*/
export const diffClass = (classNameA: ClassName, classNameB: ClassName) => {
const classNameASplit = classNameA && classNameA.split(' ');
const classNameBSplit = classNameB && classNameB.split(' ');
const tempObj = {};
each(classNameASplit, (className) => {
tempObj[className] = 1;
});
each(classNameBSplit, (className) => {
if (tempObj[className]) {
delete tempObj[className];
} else {
tempObj[className] = 1;
}
});
return keys(tempObj);
};
@@ -0,0 +1,25 @@
import { each } from '../utils/array';
import { contents } from './traversal';
import { removeElements } from './manipulation';
/**
* Creates a div DOM node.
*/
export const createDiv = (classNames?: string): HTMLDivElement => {
const div = document.createElement('div');
if (classNames) {
div.setAttribute('class', classNames);
}
return div;
};
/**
* Creates DOM nodes modeled after the passed html string and returns the root dom nodes as a array.
* @param html The html string after which the DOM nodes shall be created.
*/
export const createDOM = (html: string): ReadonlyArray<Node> => {
const createdDiv = createDiv();
createdDiv.innerHTML = html.trim();
return each(contents(createdDiv), (elm) => removeElements(elm));
};
@@ -0,0 +1,68 @@
export interface WH<T = number> {
w: T;
h: T;
}
const elementHasDimensions = (elm: HTMLElement): boolean =>
!!(elm.offsetWidth || elm.offsetHeight || elm.getClientRects().length);
const zeroObj: WH = {
w: 0,
h: 0,
};
/**
* Returns the window inner- width and height.
*/
export const windowSize = (): WH => ({
w: window.innerWidth,
h: window.innerHeight,
});
/**
* Returns the scroll- width and height of the passed element. If the element is null the width and height values are 0.
* @param elm The element of which the scroll- width and height shall be returned.
*/
export const offsetSize = (elm: HTMLElement | null | undefined): WH =>
elm
? {
w: elm.offsetWidth,
h: elm.offsetHeight,
}
: zeroObj;
/**
* Returns the client- width and height of the passed element. If the element is null the width and height values are 0.
* @param elm The element of which the client- width and height shall be returned.
*/
export const clientSize = (elm: HTMLElement | false | null | undefined): WH =>
elm
? {
w: elm.clientWidth,
h: elm.clientHeight,
}
: zeroObj;
/**
* Returns the client- width and height of the passed element. If the element is null the width and height values are 0.
* @param elm The element of which the client- width and height shall be returned.
*/
export const scrollSize = (elm: HTMLElement | false | null | undefined): WH =>
elm
? {
w: elm.scrollWidth,
h: elm.scrollHeight,
}
: zeroObj;
/**
* Returns the BoundingClientRect of the passed element.
* @param elm The element of which the BoundingClientRect shall be returned.
*/
export const getBoundingClientRect = (elm: HTMLElement): DOMRect => elm.getBoundingClientRect();
/**
* Determines whether the passed element has any dimensions.
* @param elm The element.
*/
export const hasDimensions = (elm: HTMLElement | false | null | undefined): boolean =>
elm ? elementHasDimensions(elm as HTMLElement) : false;
@@ -0,0 +1,110 @@
import { isUndefined } from '../utils/types';
import { each, push, runEachAndClear } from '../utils/array';
let passiveEventsSupport: boolean;
const supportPassiveEvents = (): boolean => {
if (isUndefined(passiveEventsSupport)) {
passiveEventsSupport = false;
try {
/* eslint-disable */
// @ts-ignore
window.addEventListener(
'test',
null,
Object.defineProperty({}, 'passive', {
get() {
passiveEventsSupport = true;
},
})
);
/* eslint-enable */
} catch (e) {}
}
return passiveEventsSupport;
};
const splitEventNames = (eventNames: string) => eventNames.split(' ');
export interface OnOptions {
_capture?: boolean;
_passive?: boolean;
_once?: boolean;
}
/**
* Removes the passed event listener for the passed events with the passed options.
* @param target The element from which the listener shall be removed.
* @param eventNames The eventsnames for which the listener shall be removed.
* @param listener The listener which shall be removed.
* @param capture The options of the removed listener.
*/
export const off = <T extends Event = Event>(
target: EventTarget,
eventNames: string,
listener: (event: T) => any,
capture?: boolean
): void => {
each(splitEventNames(eventNames), (eventName) => {
target.removeEventListener(eventName, listener as EventListener, capture);
});
};
/**
* Adds the passed event listener for the passed eventnames with the passed options.
* @param target The element to which the listener shall be added.
* @param eventNames The eventsnames for which the listener shall be called.
* @param listener The listener which is called on the eventnames.
* @param options The options of the added listener.
*/
export const on = <T extends Event = Event>(
target: EventTarget,
eventNames: string,
listener: (event: T) => any,
options?: OnOptions
): (() => void) => {
const doSupportPassiveEvents = supportPassiveEvents();
const passive = (doSupportPassiveEvents && options && options._passive) ?? doSupportPassiveEvents;
const capture = (options && options._capture) || false;
const once = (options && options._once) || false;
const offListeners: (() => void)[] = [];
const nativeOptions: AddEventListenerOptions | boolean = doSupportPassiveEvents
? {
passive,
capture,
}
: capture;
each(splitEventNames(eventNames), (eventName) => {
const finalListener = (
once
? (evt: T) => {
target.removeEventListener(eventName, finalListener, capture);
listener && listener(evt);
}
: listener
) as EventListener;
push(offListeners, off.bind(null, target, eventName, finalListener, capture));
target.addEventListener(eventName, finalListener, nativeOptions);
});
return runEachAndClear.bind(0, offListeners);
};
/**
* Shorthand for the stopPropagation event Method.
* @param evt The event of which the stopPropagation method shall be called.
*/
export const stopPropagation = (evt: Event): void => evt.stopPropagation();
/**
* Shorthand for the preventDefault event Method.
* @param evt The event of which the preventDefault method shall be called.
*/
export const preventDefault = (evt: Event): void => evt.preventDefault();
/**
* Shorthand for the stopPropagation and preventDefault event Method.
* @param evt The event of which the stopPropagation and preventDefault methods shall be called.
*/
export const stopAndPrevent = (evt: Event): void =>
(stopPropagation(evt) as undefined) || (preventDefault(evt) as undefined);
@@ -0,0 +1,8 @@
export * from './class';
export * from './create';
export * from './dimensions';
export * from './events';
export * from './style';
export * from './manipulation';
export * from './offset';
export * from './traversal';
@@ -0,0 +1,110 @@
import { isArrayLike } from '../utils/types';
import { each, from } from '../utils/array';
import { parent } from './traversal';
type NodeCollection = ArrayLike<Node> | Node | false | null | undefined;
/**
* Inserts Nodes before the given preferredAnchor element.
* @param parentElm The parent of the preferredAnchor element or the element which shall be the parent of the inserted Nodes.
* @param preferredAnchor The element before which the Nodes shall be inserted or null if the elements shall be appended at the end.
* @param insertedElms The Nodes which shall be inserted.
*/
const before = (
parentElm: Node | false | null | undefined,
preferredAnchor: Node | false | null | undefined,
insertedElms: NodeCollection
): void => {
if (insertedElms && parentElm) {
let anchor: Node | false | null | undefined = preferredAnchor;
let fragment: DocumentFragment | Node | null | undefined;
if (isArrayLike(insertedElms)) {
fragment = document.createDocumentFragment();
// append all insertedElms to the fragment and if one of these is the anchor, change the anchor
each(insertedElms, (insertedElm) => {
if (insertedElm === anchor) {
anchor = insertedElm.previousSibling;
}
fragment!.appendChild(insertedElm);
});
} else {
fragment = insertedElms;
}
// if the preferred anchor isn't null set it to a valid anchor
if (preferredAnchor) {
if (!anchor) {
anchor = parentElm.firstChild;
} else if (anchor !== preferredAnchor) {
anchor = anchor.nextSibling;
}
}
parentElm.insertBefore(fragment, anchor || null);
}
};
/**
* Appends the given children at the end of the given Node.
* @param node The Node to which the children shall be appended.
* @param children The Nodes which shall be appended.
*/
export const appendChildren = (
node: Node | false | null | undefined,
children: NodeCollection
): void => {
before(node, null, children);
};
/**
* Prepends the given children at the start of the given Node.
* @param node The Node to which the children shall be prepended.
* @param children The Nodes which shall be prepended.
*/
export const prependChildren = (
node: Node | false | null | undefined,
children: NodeCollection
): void => {
before(node, node && node.firstChild, children);
};
/**
* Inserts the given Nodes before the given Node.
* @param node The Node before which the given Nodes shall be inserted.
* @param insertedNodes The Nodes which shall be inserted.
*/
export const insertBefore = (
node: Node | false | null | undefined,
insertedNodes: NodeCollection
): void => {
before(parent(node), node, insertedNodes);
};
/**
* Inserts the given Nodes after the given Node.
* @param node The Node after which the given Nodes shall be inserted.
* @param insertedNodes The Nodes which shall be inserted.
*/
export const insertAfter = (
node: Node | false | null | undefined,
insertedNodes: NodeCollection
): void => {
before(parent(node), node && node.nextSibling, insertedNodes);
};
/**
* Removes the given Nodes from their parent.
* @param nodes The Nodes which shall be removed.
*/
export const removeElements = (nodes: NodeCollection): void => {
if (isArrayLike(nodes)) {
each(from(nodes), (e) => removeElements(e));
} else if (nodes) {
const parentElm = parent(nodes);
if (parentElm) {
parentElm.removeChild(nodes);
}
}
};
@@ -0,0 +1,37 @@
import { getBoundingClientRect } from './dimensions';
export interface XY<T = number> {
x: T;
y: T;
}
const zeroObj: XY = {
x: 0,
y: 0,
};
/**
* Returns the offset- left and top coordinates of the passed element relative to the document. If the element is null the top and left values are 0.
* @param elm The element of which the offset- top and left coordinates shall be returned.
*/
export const absoluteCoordinates = (elm: HTMLElement | null | undefined): XY => {
const rect = elm ? getBoundingClientRect(elm) : 0;
return rect
? {
x: rect.left + window.pageYOffset,
y: rect.top + window.pageXOffset,
}
: zeroObj;
};
/**
* Returns the offset- left and top coordinates of the passed element. If the element is null the top and left values are 0.
* @param elm The element of which the offset- top and left coordinates shall be returned.
*/
export const offsetCoordinates = (elm: HTMLElement | null | undefined): XY =>
elm
? {
x: elm.offsetLeft,
y: elm.offsetTop,
}
: zeroObj;
@@ -0,0 +1,141 @@
import { each, keys } from '../utils';
import { isString, isNumber, isArray, isUndefined } from '../utils/types';
export interface TRBL {
t: number;
r: number;
b: number;
l: number;
}
type StyleObject<CustomCssProps = ''> = {
[Key in keyof CSSStyleDeclaration | (CustomCssProps extends string ? CustomCssProps : '')]?:
| string
| number;
};
const cssNumber = {
// animationiterationcount: 1,
// columncount: 1,
// fillopacity: 1,
// flexgrow: 1,
// flexshrink: 1,
// fontweight: 1,
// lineheight: 1,
// order: 1,
// orphans: 1,
// widows: 1,
// zoom: 1,
opacity: 1,
zindex: 1,
};
const parseToZeroOrNumber = (value: string, toFloat?: boolean): number => {
/* istanbul ignore next */
const num = toFloat ? parseFloat(value) : parseInt(value, 10);
// num === num means num is not NaN
/* istanbul ignore next */
return num === num ? num : 0; // eslint-disable-line no-self-compare
};
const adaptCSSVal = (prop: string, val: string | number): string | number =>
!cssNumber[prop.toLowerCase()] && isNumber(val) ? `${val}px` : val;
const getCSSVal = (elm: HTMLElement, computedStyle: CSSStyleDeclaration, prop: string): string =>
/* istanbul ignore next */
computedStyle != null
? computedStyle[prop] || computedStyle.getPropertyValue(prop)
: elm.style[prop];
const setCSSVal = (elm: HTMLElement, prop: string, val: string | number): void => {
try {
const { style: elmStyle } = elm;
if (!isUndefined(elmStyle[prop])) {
elmStyle[prop] = adaptCSSVal(prop, val);
} else {
elmStyle.setProperty(prop, val as string);
}
} catch (e) {}
};
/**
* Gets or sets the passed styles to the passed element.
* @param elm The element to which the styles shall be applied to / be read from.
* @param styles The styles which shall be set or read.
*/
export function style<CustomCssProps>(
elm: HTMLElement | false | null | undefined,
styles: StyleObject<CustomCssProps>
): void;
export function style<CustomCssProps>(
elm: HTMLElement | false | null | undefined,
styles: string
): string;
export function style<CustomCssProps>(
elm: HTMLElement | false | null | undefined,
styles: Array<string> | string
): { [key: string]: string };
export function style<CustomCssProps>(
elm: HTMLElement | false | null | undefined,
styles: StyleObject<CustomCssProps> | Array<string> | string
): { [key: string]: string } | string | void {
const getSingleStyle = isString(styles);
const getStyles = isArray(styles) || getSingleStyle;
if (getStyles) {
let getStylesResult: string | Record<string, any> = getSingleStyle ? '' : {};
if (elm) {
const computedStyle: CSSStyleDeclaration = window.getComputedStyle(elm, null);
getStylesResult = getSingleStyle
? getCSSVal(elm, computedStyle, styles as string)
: (styles as Array<string>).reduce((result, key) => {
result[key] = getCSSVal(elm, computedStyle, key as string);
return result;
}, getStylesResult);
}
return getStylesResult;
}
elm && each(keys(styles), (key) => setCSSVal(elm, key, styles[key]));
}
/**
* Hides the passed element (display: none).
* @param elm The element which shall be hidden.
*/
export const hide = (elm: HTMLElement | false | null | undefined): void => {
style(elm, { display: 'none' });
};
/**
* Shows the passed element (display: block).
* @param elm The element which shall be shown.
*/
export const show = (elm: HTMLElement | false | null | undefined): void => {
style(elm, { display: 'block' });
};
/**
* Returns the top right bottom left values of the passed css property.
* @param elm The element of which the values shall be returned.
* @param propertyPrefix The css property prefix. (e.g. "border")
* @param propertySuffix The css property suffix. (e.g. "width")
*/
export const topRightBottomLeft = (
elm?: HTMLElement | false | null | undefined,
propertyPrefix?: string,
propertySuffix?: string
): TRBL => {
const finalPrefix = propertyPrefix ? `${propertyPrefix}-` : '';
const finalSuffix = propertySuffix ? `-${propertySuffix}` : '';
const top = `${finalPrefix}top${finalSuffix}`;
const right = `${finalPrefix}right${finalSuffix}`;
const bottom = `${finalPrefix}bottom${finalSuffix}`;
const left = `${finalPrefix}left${finalSuffix}`;
const result = style(elm, [top, right, bottom, left]);
return {
t: parseToZeroOrNumber(result[top]),
r: parseToZeroOrNumber(result[right]),
b: parseToZeroOrNumber(result[bottom]),
l: parseToZeroOrNumber(result[left]),
};
};
@@ -0,0 +1,120 @@
import { isElement } from '../utils/types';
import { push, from } from '../utils/array';
type InputElementType = Node | Element | Node | false | null | undefined;
type OutputElementType = Node | Element | null;
const elmPrototype = Element.prototype;
/**
* Find all elements with the passed selector, outgoing (and including) the passed element or the document if no element was provided.
* @param selector The selector which has to be searched by.
* @param elm The element from which the search shall be outgoing.
*/
const find = (selector: string, elm?: InputElementType): Element[] => {
const arr: Array<Element> = [];
const rootElm = elm ? (isElement(elm) ? elm : null) : document;
return rootElm ? push(arr, rootElm.querySelectorAll(selector)) : arr;
};
/**
* Find the first element with the passed selector, outgoing (and including) the passed element or the document if no element was provided.
* @param selector The selector which has to be searched by.
* @param elm The element from which the search shall be outgoing.
*/
const findFirst = (selector: string, elm?: InputElementType): OutputElementType => {
const rootElm = elm ? (isElement(elm) ? elm : null) : document;
return rootElm ? rootElm.querySelector(selector) : null;
};
/**
* Determines whether the passed element is matching with the passed selector.
* @param elm The element which has to be compared with the passed selector.
* @param selector The selector which has to be compared with the passed element. Additional selectors: ':visible' and ':hidden'.
*/
const is = (elm: InputElementType, selector: string): boolean => {
if (isElement(elm)) {
/* istanbul ignore next */
// eslint-disable-next-line
// @ts-ignore
const fn: (...args: any) => boolean = elmPrototype.matches || elmPrototype.msMatchesSelector;
return fn.call(elm, selector);
}
return false;
};
/**
* Returns the children (no text-nodes or comments) of the passed element which are matching the passed selector. An empty array is returned if the passed element is null.
* @param elm The element of which the children shall be returned.
* @param selector The selector which must match with the children elements.
*/
const children = (elm: InputElementType, selector?: string): ReadonlyArray<Element> => {
const childs: Array<Element> = [];
return isElement(elm)
? push(
childs,
from(elm.children).filter((child) => (selector ? is(child, selector) : child))
)
: childs;
};
/**
* Returns the childNodes (incl. text-nodes or comments etc.) of the passed element. An empty array is returned if the passed element is null.
* @param elm The element of which the childNodes shall be returned.
*/
const contents = (elm: InputElementType): ReadonlyArray<ChildNode> =>
elm ? from(elm.childNodes) : [];
/**
* Returns the parent element of the passed element, or null if the passed element is null.
* @param elm The element of which the parent element shall be returned.
*/
const parent = (elm: InputElementType): OutputElementType => (elm ? elm.parentElement : null);
const closest = (elm: InputElementType, selector: string): OutputElementType => {
if (isElement(elm)) {
const closestFn = elmPrototype.closest;
if (closestFn) {
return closestFn.call(elm, selector);
}
do {
if (is(elm, selector)) {
return elm as Element;
}
elm = parent(elm);
} while (elm);
}
return null;
};
/**
* Determines whether the given element lies between two selectors in the DOM.
* @param elm The element.
* @param highBoundarySelector The high boundary selector.
* @param deepBoundarySelector The deep boundary selector.
*/
const liesBetween = (
elm: InputElementType,
highBoundarySelector: string,
deepBoundarySelector: string
): boolean => {
const closestHighBoundaryElm = elm && closest(elm, highBoundarySelector);
const closestDeepBoundaryElm = elm && findFirst(deepBoundarySelector, closestHighBoundaryElm);
const deepBoundaryIsValid =
closest(closestDeepBoundaryElm, highBoundarySelector) === closestHighBoundaryElm;
return closestHighBoundaryElm && closestDeepBoundaryElm
? closestHighBoundaryElm === elm ||
closestDeepBoundaryElm === elm ||
(deepBoundaryIsValid &&
closest(closest(elm, deepBoundarySelector), highBoundarySelector) !==
closestHighBoundaryElm)
: false;
};
export { find, findFirst, is, children, contents, parent, liesBetween, closest };
@@ -0,0 +1,2 @@
export * from './dom';
export * from './utils';
@@ -0,0 +1,118 @@
import { isArrayLike, isString } from './types';
type PlainObject = Record<string, any>;
type RunEachItem = ((...args: any) => any | any[]) | null | undefined;
/**
* Iterates through a array or object
* @param arrayLikeOrObject The array or object through which shall be iterated.
* @param callback The function which is responsible for the iteration.
* If the function returns true its treated like a "continue" statement.
* If the function returns false its treated like a "break" statement.
*/
export function each<T>(
array: Array<T> | ReadonlyArray<T>,
callback: (value: T, indexOrKey: number, source: Array<T>) => boolean | unknown
): Array<T> | ReadonlyArray<T>;
export function each<T>(
array: Array<T> | ReadonlyArray<T> | false | null | undefined,
callback: (value: T, indexOrKey: number, source: Array<T>) => boolean | unknown
): Array<T> | ReadonlyArray<T> | false | null | undefined;
export function each<T>(
arrayLikeObject: ArrayLike<T>,
callback: (value: T, indexOrKey: number, source: ArrayLike<T>) => boolean | unknown
): ArrayLike<T>;
export function each<T>(
arrayLikeObject: ArrayLike<T> | false | null | undefined,
callback: (value: T, indexOrKey: number, source: ArrayLike<T>) => boolean | unknown
): ArrayLike<T> | false | null | undefined;
export function each(
obj: PlainObject,
callback: (value: any, indexOrKey: string, source: PlainObject) => boolean | unknown
): PlainObject;
export function each(
obj: PlainObject | false | null | undefined,
callback: (value: any, indexOrKey: string, source: PlainObject) => boolean | unknown
): PlainObject | false | null | undefined;
export function each<T>(
source: Array<T> | ArrayLike<T> | ReadonlyArray<T> | PlainObject | false | null | undefined,
callback: (value: T, indexOrKey: any, source: any) => boolean | unknown
): Array<T> | ArrayLike<T> | ReadonlyArray<T> | PlainObject | false | null | undefined {
if (isArrayLike(source)) {
for (let i = 0; i < source.length; i++) {
if (callback(source[i], i, source) === false) {
break;
}
}
} else if (source) {
each(Object.keys(source), (key) => callback(source[key], key, source));
}
return source;
}
/**
* Returns the index of the given inside the given array or -1 if the given item isn't part of the given array.
* @param arr The array.
* @param item The item.
* @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the search starts at index 0.
*/
export const indexOf = <T = any>(arr: T[], item: T, fromIndex?: number): number =>
arr.indexOf(item, fromIndex);
/**
* Pushesh all given items into the given array and returns it.
* @param array The array the items shall be pushed into.
* @param items The items which shall be pushed into the array.
*/
export const push = <T>(array: T[], items: T | ArrayLike<T>, arrayIsSingleItem?: boolean): T[] => {
!arrayIsSingleItem && !isString(items) && isArrayLike(items)
? Array.prototype.push.apply(array, items as T[])
: array.push(items as T);
return array;
};
/**
* Creates a shallow-copied Array instance from an array-like or iterable object.
* @param arr The object from which the array instance shall be created.
*/
export const from = <T = any>(arr?: ArrayLike<T> | Set<T>) => {
const original = Array.from;
const result: T[] = [];
if (original && arr) {
return original(arr);
}
if (arr instanceof Set) {
arr.forEach((value) => {
push(result, value);
});
} else {
each(arr, (elm) => {
push(result, elm);
});
}
return result;
};
/**
* Check whether the passed array is empty.
* @param array The array which shall be checked.
*/
export const isEmptyArray = (array: any[] | null | undefined): boolean =>
!!array && array.length === 0;
/**
* Calls all functions in the passed array/set of functions.
* @param arr The array filled with function which shall be called.
* @param args The args with which each function is called.
* @param keep True when the Set / array should not be cleared afterwards, false otherwise.
*/
export const runEachAndClear = (arr: RunEachItem[], args?: any[], keep?: boolean): void => {
// eslint-disable-next-line prefer-spread
const runFn = (fn: RunEachItem) => fn && fn.apply(undefined, args || []);
each(arr, runFn);
!keep && ((arr as any[]).length = 0);
};
@@ -0,0 +1,3 @@
export * from './array';
export * from './object';
export * from './types';
@@ -0,0 +1,103 @@
import { isArray, isFunction, isPlainObject, isNull } from './types';
import { each } from './array';
/**
* Determines whether the passed object has a property with the passed name.
* @param obj The object.
* @param prop The name of the property.
*/
export const hasOwnProperty = (obj: any, prop: string | number | symbol): boolean =>
Object.prototype.hasOwnProperty.call(obj, prop);
/**
* Returns the names of the enumerable string properties and methods of an object.
* @param obj The object of which the properties shall be returned.
*/
export const keys = (obj: any): Array<string> => (obj ? Object.keys(obj) : []);
type AssignDeep = {
<T, U>(target: T, object1: U): T & U;
<T, U, V>(target: T, object1: U, object2: V): T & U & V;
<T, U, V, W>(target: T, object1: U, object2: V, object3: W): T & U & V & W;
<T, U, V, W, X>(target: T, object1: U, object2: V, object3: W, object4: X): T & U & V & W & X;
<T, U, V, W, X, Y>(target: T, object1: U, object2: V, object3: W, object4: X, object5: Y): T &
U &
V &
W &
X &
Y;
<T, U, V, W, X, Y, Z>(
target: T,
object1?: U,
object2?: V,
object3?: W,
object4?: X,
object5?: Y,
object6?: Z
): T & U & V & W & X & Y & Z;
};
// https://github.com/jquery/jquery/blob/master/src/core.js#L116
export const assignDeep: AssignDeep = <T, U, V, W, X, Y, Z>(
target: T,
object1?: U,
object2?: V,
object3?: W,
object4?: X,
object5?: Y,
object6?: Z
): T & U & V & W & X & Y & Z => {
const sources: Array<any> = [object1, object2, object3, object4, object5, object6];
// Handle case when target is a string or something (possible in deep copy)
if ((typeof target !== 'object' || isNull(target)) && !isFunction(target)) {
target = {} as T;
}
each(sources, (source) => {
// Extend the base object
each(keys(source), (key) => {
const copy: any = source[key];
// Prevent Object.prototype pollution
// Prevent never-ending loop
if (target === copy) {
return true;
}
const copyIsArray = isArray(copy);
// Recurse if we're merging plain objects or arrays
if (copy && (isPlainObject(copy) || copyIsArray)) {
const src = target[key];
let clone: any = src;
// Ensure proper type for the source value
if (copyIsArray && !isArray(src)) {
clone = [];
} else if (!copyIsArray && !isPlainObject(src)) {
clone = {};
}
// Never move original objects, clone them
target[key] = assignDeep(clone, copy) as any;
} else {
target[key] = copy;
}
});
});
// Return the modified object
return target as any;
};
/**
* Returns true if the given object is empty, false otherwise.
* @param obj The Object.
*/
export const isEmptyObject = (obj: any): boolean => {
/* eslint-disable no-restricted-syntax, guard-for-in */
for (const name in obj) return false;
return true;
/* eslint-enable */
};
@@ -0,0 +1,97 @@
type PlainObject<T = any> = Record<string, T>;
const ElementNodeType = Node.ELEMENT_NODE;
const { toString, hasOwnProperty } = Object.prototype;
export const isUndefined = (obj: any): obj is undefined => obj === undefined;
export const isNull = (obj: any): obj is null => obj === null;
export const type = (obj: any): string =>
isUndefined(obj) || isNull(obj)
? `${obj}`
: toString
.call(obj)
.replace(/^\[object (.+)\]$/, '$1')
.toLowerCase();
export const isNumber = (obj: any): obj is number => typeof obj === 'number';
export const isString = (obj: any): obj is string => typeof obj === 'string';
export const isBoolean = (obj: any): obj is boolean => typeof obj === 'boolean';
export const isFunction = (obj: any): obj is (...args: any[]) => any => typeof obj === 'function';
export const isArray = <T = any>(obj: any): obj is Array<T> => Array.isArray(obj);
export const isObject = (obj: any): boolean =>
typeof obj === 'object' && !isArray(obj) && !isNull(obj);
/**
* Returns true if the given object is array like, false otherwise.
* @param obj The Object
*/
export const isArrayLike = <T extends PlainObject = any>(obj: any): obj is ArrayLike<T> => {
const length = !!obj && obj.length;
const lengthCorrectFormat = isNumber(length) && length > -1 && length % 1 == 0; // eslint-disable-line eqeqeq
return isArray(obj) || (!isFunction(obj) && lengthCorrectFormat)
? length > 0 && isObject(obj)
? length - 1 in obj
: true
: false;
};
/**
* Returns true if the given object is a "plain" (e.g. { key: value }) object, false otherwise.
* @param obj The Object.
*/
export const isPlainObject = <T = any>(obj: any): obj is PlainObject<T> => {
if (!obj || !isObject(obj) || type(obj) !== 'object') return false;
let key;
const cstr = 'constructor';
const ctor = obj[cstr];
const ctorProto = ctor && ctor.prototype;
const hasOwnConstructor = hasOwnProperty.call(obj, cstr);
const hasIsPrototypeOf = ctorProto && hasOwnProperty.call(ctorProto, 'isPrototypeOf');
if (ctor && !hasOwnConstructor && !hasIsPrototypeOf) {
return false;
}
/* eslint-disable no-restricted-syntax */
for (key in obj) {
/**/
}
/* eslint-enable */
return isUndefined(key) || hasOwnProperty.call(obj, key);
};
/**
* Checks whether the given object is a HTMLElement.
* @param obj The object which shall be checked.
*/
export const isHTMLElement = (obj: any): obj is HTMLElement => {
const instanceofObj = HTMLElement;
return obj
? instanceofObj
? obj instanceof instanceofObj
: obj.nodeType === ElementNodeType
: false;
};
/**
* Checks whether the given object is a Element.
* @param obj The object which shall be checked.
*/
export const isElement = (obj: any): obj is Element => {
const instanceofObj = Element;
return obj
? instanceofObj
? obj instanceof instanceofObj
: obj.nodeType === ElementNodeType
: false;
};