, start?: number, end?: number ): T[] {
11 | return Array.prototype.slice.call( arrayLike, start, end );
12 | }
13 |
--------------------------------------------------------------------------------
/src/js/utils/dom/addClass/addClass.test.ts:
--------------------------------------------------------------------------------
1 | import { addClass } from './addClass';
2 |
3 |
4 | describe( 'addClass', () => {
5 | beforeEach( () => {
6 | document.body.innerHTML = '';
7 | } );
8 |
9 | test( 'can add a class to the element.', () => {
10 | const container = document.getElementById( 'container' );
11 | addClass( container, 'active' );
12 | expect( container.classList.contains( 'active' ) ).toBe( true );
13 | } );
14 |
15 | test( 'can add classes to the element.', () => {
16 | const container = document.getElementById( 'container' );
17 |
18 | addClass( container, [ 'active', 'visible' ] );
19 |
20 | expect( container.classList.contains( 'active' ) ).toBe( true );
21 | expect( container.classList.contains( 'visible' ) ).toBe( true );
22 | } );
23 | } );
24 |
--------------------------------------------------------------------------------
/src/js/utils/dom/addClass/addClass.ts:
--------------------------------------------------------------------------------
1 | import { isString } from '../../type/type';
2 | import { toggleClass } from '../toggleClass/toggleClass';
3 |
4 |
5 | /**
6 | * Adds classes to the element.
7 | *
8 | * @param elm - An element to add classes to.
9 | * @param classes - Classes to add.
10 | */
11 | export function addClass( elm: Element, classes: string | string[] ): void {
12 | toggleClass( elm, isString( classes ) ? classes.split( ' ' ) : classes, true );
13 | }
14 |
--------------------------------------------------------------------------------
/src/js/utils/dom/append/append.test.ts:
--------------------------------------------------------------------------------
1 | import { append } from './append';
2 |
3 |
4 | describe( 'append', () => {
5 | test( 'can append a child element to a parent element.', () => {
6 | const div = document.createElement( 'div' );
7 | const span = document.createElement( 'span' );
8 |
9 | append( div, span );
10 | expect( div.firstElementChild ).toBe( span );
11 | } );
12 |
13 | test( 'can append children to a parent element.', () => {
14 | const div = document.createElement( 'div' );
15 | const span1 = document.createElement( 'span' );
16 | const span2 = document.createElement( 'span' );
17 | const span3 = document.createElement( 'span' );
18 |
19 | append( div, [ span1, span2, span3 ] );
20 |
21 | expect( div.children[ 0 ] ).toBe( span1 );
22 | expect( div.children[ 1 ] ).toBe( span2 );
23 | expect( div.children[ 2 ] ).toBe( span3 );
24 | } );
25 | } );
26 |
--------------------------------------------------------------------------------
/src/js/utils/dom/append/append.ts:
--------------------------------------------------------------------------------
1 | import { forEach } from '../../array';
2 |
3 |
4 | /**
5 | * Appends children to the parent element.
6 | *
7 | * @param parent - A parent element.
8 | * @param children - A child or children to append to the parent.
9 | */
10 | export function append( parent: Element, children: Node | Node[] ): void {
11 | forEach( children, parent.appendChild.bind( parent ) );
12 | }
13 |
--------------------------------------------------------------------------------
/src/js/utils/dom/before/before.test.ts:
--------------------------------------------------------------------------------
1 | import { before } from './before';
2 |
3 |
4 | describe( 'before', () => {
5 | beforeEach( () => {
6 | document.body.textContent = '';
7 | } );
8 |
9 | test( 'can insert a node before the reference node.', () => {
10 | const ref = document.createElement( 'a' );
11 | const span1 = document.createElement( 'span' );
12 | const span2 = document.createElement( 'span' );
13 |
14 | document.body.appendChild( ref );
15 |
16 | before( span1, ref );
17 | expect( document.body.firstChild ).toBe( span1 );
18 | expect( span1.nextSibling ).toBe( ref );
19 |
20 | before( span2, ref );
21 | expect( document.body.firstChild ).toBe( span1 );
22 | expect( span1.nextSibling ).toBe( span2 );
23 | expect( span2.nextSibling ).toBe( ref );
24 | } );
25 |
26 | test( 'can insert nodes before the reference node.', () => {
27 | const ref = document.createElement( 'a' );
28 | const span1 = document.createElement( 'span' );
29 | const span2 = document.createElement( 'span' );
30 | const span3 = document.createElement( 'span' );
31 |
32 | document.body.appendChild( ref );
33 |
34 | before( [ span1, span2, span3 ], ref );
35 |
36 | expect( document.body.children[ 0 ] ).toBe( span1 );
37 | expect( document.body.children[ 1 ] ).toBe( span2 );
38 | expect( document.body.children[ 2 ] ).toBe( span3 );
39 | expect( document.body.children[ 3 ] ).toBe( ref );
40 | } );
41 | } );
42 |
--------------------------------------------------------------------------------
/src/js/utils/dom/before/before.ts:
--------------------------------------------------------------------------------
1 | import { forEach } from '../../array';
2 |
3 |
4 | /**
5 | * Inserts a node or nodes before the specified reference node.
6 | *
7 | * @param nodes - A node or nodes to insert.
8 | * @param ref - A reference node.
9 | */
10 | export function before( nodes: Node | Node[], ref: Node | null ): void {
11 | forEach( nodes, node => {
12 | const parent = ( ref || node ).parentNode;
13 |
14 | if ( parent ) {
15 | parent.insertBefore( node, ref );
16 | }
17 | } );
18 | }
19 |
--------------------------------------------------------------------------------
/src/js/utils/dom/child/child.test.ts:
--------------------------------------------------------------------------------
1 | import { child } from './child';
2 |
3 |
4 | describe( 'child', () => {
5 | beforeEach( () => {
6 | document.body.innerHTML = `
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | `;
16 | } );
17 |
18 | test( 'can return the child that matches the specified selector.', () => {
19 | const container = document.getElementById( 'container' );
20 | const span1 = child( container, 'span' );
21 | expect( span1.id ).toBe( 'span1' );
22 |
23 | const span2 = child( container, '#span2' );
24 | expect( span2.id ).toBe( 'span2' );
25 |
26 | const active = child( container, '.active' );
27 | expect( active.id ).toBe( 'span1' );
28 | } );
29 |
30 | test( 'can return the firstElementChild if the selector is omitted.', () => {
31 | const container = document.getElementById( 'container' );
32 | const span1 = child( container );
33 | expect( span1.id ).toBe( 'span1' );
34 | } );
35 |
36 | test( 'should rerun undefined if no element is found.', () => {
37 | const container = document.getElementById( 'container' );
38 | const elm = child( container, 'nothing' );
39 | expect( elm ).toBeUndefined();
40 | } );
41 | } );
42 |
--------------------------------------------------------------------------------
/src/js/utils/dom/child/child.ts:
--------------------------------------------------------------------------------
1 | import { children } from '../children/children';
2 |
3 |
4 | /**
5 | * Returns a child element that matches the specified tag or class name.
6 | *
7 | * @param parent - A parent element.
8 | * @param selector - A selector to filter children.
9 | *
10 | * @return A matched child element if available, or otherwise `undefined`.
11 | */
12 | export function child( parent: HTMLElement, selector?: string ): E | undefined {
13 | return selector ? children( parent, selector )[ 0 ] : parent.firstElementChild as E;
14 | }
15 |
--------------------------------------------------------------------------------
/src/js/utils/dom/children/children.test.ts:
--------------------------------------------------------------------------------
1 | import { children } from './children';
2 |
3 |
4 | describe( 'children', () => {
5 | beforeEach( () => {
6 | document.body.innerHTML = `
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | `;
16 | } );
17 |
18 | test( 'can return children that have the specified tag name.', () => {
19 | const container = document.getElementById( 'container' );
20 | const spans = children( container, 'span' );
21 |
22 | expect( spans.length ).toBe( 3 );
23 | expect( spans[ 0 ].id ).toBe( 'span1' );
24 | expect( spans[ 1 ].id ).toBe( 'span2' );
25 | expect( spans[ 2 ].id ).toBe( 'span3' );
26 | } );
27 |
28 | test( 'can return children that have the specified class name.', () => {
29 | const container = document.getElementById( 'container' );
30 | const spans = children( container, '.active' );
31 |
32 | expect( spans.length ).toBe( 2 );
33 | expect( spans[ 0 ].id ).toBe( 'span1' );
34 | expect( spans[ 1 ].id ).toBe( 'button1' );
35 | } );
36 |
37 | test( 'should rerun an empty array if no element is found.', () => {
38 | const container = document.getElementById( 'container' );
39 | const elms = children( container, '.nothing' );
40 | expect( elms.length ).toBe( 0 );
41 | } );
42 | } );
43 |
--------------------------------------------------------------------------------
/src/js/utils/dom/children/children.ts:
--------------------------------------------------------------------------------
1 | import { slice } from '../../arrayLike';
2 | import { matches } from '../matches/matches';
3 |
4 |
5 | /**
6 | * Finds children that has the specified tag or class name.
7 | *
8 | * @param parent - A parent element.
9 | * @param selector - Optional. A selector to filter children.
10 | *
11 | * @return An array with filtered children.
12 | */
13 | export function children( parent: HTMLElement, selector?: string ): E[] {
14 | const children = parent ? slice( parent.children ) as E[] : [];
15 | return selector ? children.filter( child => matches( child, selector ) ) : children;
16 | }
17 |
--------------------------------------------------------------------------------
/src/js/utils/dom/closest/closest.test.ts:
--------------------------------------------------------------------------------
1 | import { closest } from './closest';
2 |
3 |
4 | describe.each( [ [ 'native' ], [ 'polyfill' ] ] )( 'closest (%s)', ( env ) => {
5 | if ( env === 'polyfill' ) {
6 | // Forces to disable the native method.
7 | Element.prototype.closest = null as any;
8 | }
9 |
10 | beforeEach( () => {
11 | document.body.innerHTML = `
12 |
13 |
14 |
15 | start
16 |
17 |
18 |
19 | `;
20 | } );
21 |
22 | test( 'can find the closest element.', () => {
23 | const from = document.getElementById( 'start' );
24 |
25 | if ( from ) {
26 | expect( closest( from, '#inner' )?.id ).toBe( 'inner' );
27 | expect( closest( from, '#outer' )?.id ).toBe( 'outer' );
28 | expect( closest( from, 'div' )?.id ).toBe( 'inner' );
29 | expect( closest( from, '.wrapper' )?.id ).toBe( 'outer' );
30 | } else {
31 | fail();
32 | }
33 | } );
34 |
35 | test( 'should include the provided element itself.', () => {
36 | const from = document.getElementById( 'start' );
37 |
38 | if ( from ) {
39 | expect( closest( from, 'span' )?.id ).toBe( 'start' );
40 | } else {
41 | fail();
42 | }
43 | } );
44 |
45 | test( 'should return null if no element is found.', () => {
46 | const from = document.getElementById( 'start' );
47 |
48 | if ( from ) {
49 | expect( closest( from, 'invalid' ) ).toBeNull();
50 | } else {
51 | fail();
52 | }
53 | } );
54 | } );
55 |
--------------------------------------------------------------------------------
/src/js/utils/dom/closest/closest.ts:
--------------------------------------------------------------------------------
1 | import { isFunction } from '../../type/type';
2 | import { matches } from '../matches/matches';
3 |
4 |
5 | /**
6 | * Starts from the provided element, searches for the first element that matches the selector in ascendants.
7 | *
8 | * @param from - An element to search from.
9 | * @param selector - A selector.
10 | *
11 | * @return The found element if available, or `null`.
12 | */
13 | export function closest( from: HTMLElement, selector: string ): HTMLElement | null {
14 | if ( isFunction( from.closest ) ) {
15 | return from.closest( selector );
16 | }
17 |
18 | let elm: HTMLElement | null = from;
19 |
20 | while ( elm && elm.nodeType === 1 ) {
21 | if ( matches( elm, selector ) ) {
22 | break;
23 | }
24 |
25 | elm = elm.parentElement;
26 | }
27 |
28 | return elm;
29 | }
--------------------------------------------------------------------------------
/src/js/utils/dom/create/create.test.ts:
--------------------------------------------------------------------------------
1 | import { create } from './create';
2 |
3 |
4 | describe( 'create', () => {
5 | test( 'can create an element by a tag name.', () => {
6 | const div = create( 'div' );
7 | const iframe = create( 'iframe' );
8 |
9 | expect( div instanceof HTMLDivElement ).toBe( true );
10 | expect( iframe instanceof HTMLIFrameElement ).toBe( true );
11 | } );
12 |
13 | test( 'can create an element with setting attributes.', () => {
14 | const iframe = create( 'iframe', { width: 100, height: 200 } );
15 |
16 | expect( iframe.getAttribute( 'width' ) ).toBe( '100' );
17 | expect( iframe.getAttribute( 'height' ) ).toBe( '200' );
18 | } );
19 | } );
20 |
--------------------------------------------------------------------------------
/src/js/utils/dom/create/create.ts:
--------------------------------------------------------------------------------
1 | import { isString } from '../../type/type';
2 | import { addClass } from '../addClass/addClass';
3 | import { append } from '../append/append';
4 | import { setAttribute } from '../setAttribute/setAttribute';
5 |
6 |
7 | export function create(
8 | tag: K,
9 | attrs?: Record | string,
10 | parent?: HTMLElement
11 | ): HTMLElementTagNameMap[ K ];
12 |
13 | export function create(
14 | tag: string,
15 | attrs?: Record | string,
16 | parent?: HTMLElement
17 | ): HTMLElement;
18 |
19 | /**
20 | * Creates a HTML element.
21 | *
22 | * @param tag - A tag name.
23 | * @param attrs - Optional. An object with attributes to apply the created element to, or a string with classes.
24 | * @param parent - Optional. A parent element where the created element is appended.
25 | */
26 | export function create(
27 | tag: K,
28 | attrs?: Record | string,
29 | parent?: HTMLElement
30 | ): HTMLElementTagNameMap[ K ] {
31 | const elm = document.createElement( tag );
32 |
33 | if ( attrs ) {
34 | isString( attrs ) ? addClass( elm, attrs ) : setAttribute( elm, attrs );
35 | }
36 |
37 | parent && append( parent, elm );
38 |
39 | return elm;
40 | }
41 |
--------------------------------------------------------------------------------
/src/js/utils/dom/display/display.test.ts:
--------------------------------------------------------------------------------
1 | import { display } from './display';
2 |
3 |
4 | describe( 'display', () => {
5 | test( 'can set a new display value.', () => {
6 | const div = document.createElement( 'div' );
7 |
8 | display( div, 'none' );
9 | expect( div.style.display ).toBe( 'none' );
10 |
11 | display( div, 'flex' );
12 | expect( div.style.display ).toBe( 'flex' );
13 |
14 | display( div, '' );
15 | expect( div.style.display ).toBe( '' );
16 | } );
17 | } );
18 |
--------------------------------------------------------------------------------
/src/js/utils/dom/display/display.ts:
--------------------------------------------------------------------------------
1 | import { style } from '../style/style';
2 |
3 |
4 | /**
5 | * Sets the `display` CSS value to the element.
6 | *
7 | * @param elm - An element to set a new value to.
8 | * @param display - A new `display` value.
9 | */
10 | export function display( elm: HTMLElement, display: string ): void {
11 | style( elm, 'display', display );
12 | }
13 |
--------------------------------------------------------------------------------
/src/js/utils/dom/focus/focus.test.ts:
--------------------------------------------------------------------------------
1 | import { focus } from './focus';
2 |
3 |
4 | describe( 'focus', () => {
5 | test( 'can make an element focused if it is focusable.', () => {
6 | const div = document.createElement( 'div' );
7 |
8 | div.tabIndex = 0;
9 | document.body.appendChild( div );
10 |
11 | expect( document.activeElement ).not.toBe( div );
12 |
13 | focus( div );
14 |
15 | expect( document.activeElement ).toBe( div );
16 | } );
17 | } );
18 |
--------------------------------------------------------------------------------
/src/js/utils/dom/focus/focus.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Focuses the provided element without scrolling the ascendant element.
3 | *
4 | * @param elm - An element to focus.
5 | */
6 | export function focus( elm: HTMLElement ): void {
7 | elm[ 'setActive' ] && elm[ 'setActive' ]() || elm.focus( { preventScroll: true } );
8 | }
9 |
--------------------------------------------------------------------------------
/src/js/utils/dom/getAttribute/getAttribute.test.ts:
--------------------------------------------------------------------------------
1 | import { getAttribute } from './getAttribute';
2 |
3 |
4 | describe( 'getAttribute', () => {
5 | beforeEach( () => {
6 | document.body.innerHTML = '';
7 | } );
8 |
9 | test( 'can set an attribute to an element.', () => {
10 | const container = document.getElementById( 'container' );
11 |
12 | container.setAttribute( 'aria-hidden', 'true' );
13 | container.setAttribute( 'tabindex', '-1' );
14 |
15 | expect( getAttribute( container, 'id' ) ).toBe( 'container' );
16 | expect( getAttribute( container, 'aria-hidden' ) ).toBe( 'true' );
17 | expect( getAttribute( container, 'tabindex' ) ).toBe( '-1' );
18 | } );
19 | } );
20 |
--------------------------------------------------------------------------------
/src/js/utils/dom/getAttribute/getAttribute.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns the specified attribute value.
3 | *
4 | * @param elm - An element.
5 | * @param attr - An attribute to get.
6 | */
7 | export function getAttribute( elm: Element, attr: string ): string | null {
8 | return elm.getAttribute( attr );
9 | }
10 |
--------------------------------------------------------------------------------
/src/js/utils/dom/hasClass/hasClass.test.ts:
--------------------------------------------------------------------------------
1 | import { hasClass } from './hasClass';
2 |
3 |
4 | describe( 'hasClass', () => {
5 | test( 'can return true if the element contains the specified class.', () => {
6 | const container = document.createElement( 'div' );
7 | container.classList.add( 'active' );
8 | container.classList.add( 'visible' );
9 |
10 | expect( hasClass( container, 'active' ) ).toBe( true );
11 | expect( hasClass( container, 'visible' ) ).toBe( true );
12 | } );
13 |
14 | test( 'can return false if the element does not contain the specified class.', () => {
15 | const container = document.createElement( 'div' );
16 |
17 | expect( hasClass( container, 'active' ) ).toBe( false );
18 | expect( hasClass( container, 'visible' ) ).toBe( false );
19 | } );
20 | } );
21 |
--------------------------------------------------------------------------------
/src/js/utils/dom/hasClass/hasClass.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Checks if the element contains the specified class or not.
3 | *
4 | * @param elm - An element to check.
5 | * @param className - A class name that may be contained by the element.
6 | *
7 | * @return `true` if the element contains the class, or otherwise `false`.
8 | */
9 | export function hasClass( elm: Element, className: string ): boolean {
10 | return elm && elm.classList.contains( className );
11 | }
12 |
--------------------------------------------------------------------------------
/src/js/utils/dom/index.ts:
--------------------------------------------------------------------------------
1 | export { addClass } from './addClass/addClass';
2 | export { append } from './append/append';
3 | export { before } from './before/before';
4 | export { child } from './child/child';
5 | export { children } from './children/children';
6 | export { create } from './create/create';
7 | export { display } from './display/display';
8 | export { focus } from './focus/focus';
9 | export { getAttribute } from './getAttribute/getAttribute';
10 | export { hasClass } from './hasClass/hasClass';
11 | export { matches } from './matches/matches';
12 | export { measure } from './measure/measure';
13 | export { parseHtml } from './parseHtml/parseHtml';
14 | export { prevent } from './prevent/prevent';
15 | export { query } from './query/query';
16 | export { queryAll } from './queryAll/queryAll';
17 | export { rect } from './rect/rect';
18 | export { remove } from './remove/remove';
19 | export { removeAttribute } from './removeAttribute/removeAttribute';
20 | export { removeClass } from './removeClass/removeClass';
21 | export { setAttribute } from './setAttribute/setAttribute';
22 | export { style } from './style/style';
23 | export { timeOf } from './timeOf/timeOf';
24 | export { toggleClass } from './toggleClass/toggleClass';
25 | export { unit } from './unit/unit';
26 |
--------------------------------------------------------------------------------
/src/js/utils/dom/matches/matches.test.ts:
--------------------------------------------------------------------------------
1 | import { matches } from './matches';
2 |
3 |
4 | describe( 'children', () => {
5 | beforeEach( () => {
6 | document.body.innerHTML = `
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | `;
16 | } );
17 |
18 | test( 'can test if the selector matches the element or not.', () => {
19 | const container = document.getElementById( 'container' );
20 |
21 | expect( matches( container, 'div' ) ).toBe( true );
22 | expect( matches( container, '#container' ) ).toBe( true );
23 | expect( matches( container, 'span' ) ).toBe( false );
24 |
25 | const span = container.firstElementChild;
26 |
27 | expect( matches( span, 'span' ) ).toBe( true );
28 | expect( matches( span, '#span1' ) ).toBe( true );
29 | expect( matches( span, '.active' ) ).toBe( true );
30 | expect( matches( span, '#container .active' ) ).toBe( true );
31 |
32 | expect( matches( span, '#container' ) ).toBe( false );
33 | expect( matches( span, '#span2' ) ).toBe( false );
34 | } );
35 | } );
36 |
--------------------------------------------------------------------------------
/src/js/utils/dom/matches/matches.ts:
--------------------------------------------------------------------------------
1 | import { isHTMLElement } from '../../type/type';
2 |
3 |
4 | /**
5 | * Checks if the element can be selected by the provided selector or not.
6 | *
7 | * @param elm - An element to check.
8 | * @param selector - A selector to test.
9 | *
10 | * @return `true` if the selector matches the element, or otherwise `false`.
11 | */
12 | export function matches( elm: Element | EventTarget, selector: string ): boolean {
13 | return isHTMLElement( elm ) && ( elm[ 'msMatchesSelector' ] || elm.matches ).call( elm, selector );
14 | }
15 |
--------------------------------------------------------------------------------
/src/js/utils/dom/measure/measure.ts:
--------------------------------------------------------------------------------
1 | import { isString } from '../../type/type';
2 | import { create } from '../create/create';
3 | import { rect } from '../rect/rect';
4 | import { remove } from '../remove/remove';
5 |
6 |
7 | /**
8 | * Attempts to convert the provided value to pixel as the relative value to the parent element.
9 | *
10 | * @param parent - A parent element.
11 | * @param value - A value to convert.
12 | *
13 | * @return A converted value in pixel. Unhandled values will become 0.
14 | */
15 | export function measure( parent: HTMLElement, value: number | string ): number {
16 | if ( isString( value ) ) {
17 | const div = create( 'div', { style: `width: ${ value }; position: absolute;` }, parent );
18 | value = rect( div ).width;
19 | remove( div );
20 | }
21 |
22 | return value;
23 | }
24 |
--------------------------------------------------------------------------------
/src/js/utils/dom/normalizeKey/normalizeKey.test.ts:
--------------------------------------------------------------------------------
1 | import { fire } from '../../../test';
2 | import { NORMALIZATION_MAP, normalizeKey } from './normalizeKey';
3 |
4 |
5 | describe( 'normalizeKey', () => {
6 | test( 'can normalize a key into a standard name.', () => {
7 | const keys = Object.keys( NORMALIZATION_MAP );
8 | const callback = jest.fn();
9 |
10 | keys.forEach( key => {
11 | expect( normalizeKey( key ) ).toBe( NORMALIZATION_MAP[ key ] );
12 | callback();
13 | } );
14 |
15 | expect( callback ).toHaveBeenCalled();
16 | } );
17 |
18 | test( 'can return a normalized key from a Keyboard event object.', done => {
19 | window.addEventListener( 'keydown', e => {
20 | expect( normalizeKey( e ) ).toBe( 'ArrowUp' );
21 | done();
22 | } );
23 |
24 | fire( window, 'keydown', { key: 'Up' } );
25 | } );
26 |
27 | test( 'should do the provided key as is if the normalization map does not include the passed key.', () => {
28 | expect( normalizeKey( 'a' ) ).toBe( 'a' );
29 | expect( normalizeKey( 'F1' ) ).toBe( 'F1' );
30 | } );
31 | } );
--------------------------------------------------------------------------------
/src/js/utils/dom/normalizeKey/normalizeKey.ts:
--------------------------------------------------------------------------------
1 | import { ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP } from '../../../constants/arrows';
2 | import { isString } from '../../type/type';
3 |
4 |
5 | /**
6 | * The map to associate a non-standard name to the standard one.
7 | *
8 | * @since 4.0.0
9 | */
10 | export const NORMALIZATION_MAP = {
11 | Spacebar: ' ',
12 | Right : ARROW_RIGHT,
13 | Left : ARROW_LEFT,
14 | Up : ARROW_UP,
15 | Down : ARROW_DOWN,
16 | };
17 |
18 | /**
19 | * Normalizes the key.
20 | *
21 | * @param key - A string or a KeyboardEvent object.
22 | *
23 | * @return A normalized key.
24 | */
25 | export function normalizeKey( key: string | KeyboardEvent ): string {
26 | key = isString( key ) ? key : key.key;
27 | return NORMALIZATION_MAP[ key ] || key;
28 | }
--------------------------------------------------------------------------------
/src/js/utils/dom/parseHtml/parseHtml.test.ts:
--------------------------------------------------------------------------------
1 | import { parseHtml } from './parseHtml';
2 |
3 |
4 | describe( 'parseHtml', () => {
5 | test( 'can parse the provided HTML string.', () => {
6 | const div = parseHtml( 'content
' );
7 |
8 | expect( div.id ).toBe( 'container' );
9 | expect( div.classList.contains( 'active' ) ).toBe( true );
10 | expect( div.firstElementChild.tagName.toUpperCase() ).toBe( 'SPAN' );
11 | expect( div.firstElementChild.textContent ).toBe( 'content' );
12 | } );
13 |
14 | test( 'can parse the provided SVG string.', () => {
15 | const svg = parseHtml( '' );
16 |
17 | expect( svg instanceof SVGElement ).toBe( true );
18 | expect( svg.id ).toBe( 'icon' );
19 | expect( svg.firstElementChild.tagName.toUpperCase() ).toBe( 'PATH' );
20 | } );
21 | } );
22 |
--------------------------------------------------------------------------------
/src/js/utils/dom/parseHtml/parseHtml.ts:
--------------------------------------------------------------------------------
1 | import { child } from '../child/child';
2 |
3 |
4 | /**
5 | * Parses the provided HTML string and returns the first element.
6 | *
7 | * @param html - An HTML string to parse.
8 | *
9 | * @return An Element on success, or otherwise `undefined`.
10 | */
11 | export function parseHtml( html: string ): E | undefined {
12 | return child( new DOMParser().parseFromString( html, 'text/html' ).body );
13 | }
14 |
--------------------------------------------------------------------------------
/src/js/utils/dom/prevent/prevent.test.ts:
--------------------------------------------------------------------------------
1 | import { fire } from '../../../test';
2 | import { prevent } from './prevent';
3 |
4 |
5 | describe( 'prevent', () => {
6 | test( 'can prevent the default browser action of an event.', done => {
7 | window.addEventListener( 'click', e => {
8 | prevent( e );
9 | expect( e.defaultPrevented ).toBe( true );
10 | done();
11 | } );
12 |
13 | fire( window, 'click', { timeStamp: 123 }, { cancelable: true } );
14 | } );
15 | } );
--------------------------------------------------------------------------------
/src/js/utils/dom/prevent/prevent.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Call the `preventDefault()` of the provided event.
3 | *
4 | * @param e - An Event object.
5 | * @param stopPropagation - Optional. Whether to stop the event propagation or not.
6 | */
7 | export function prevent( e: Event, stopPropagation?: boolean ): void {
8 | e.preventDefault();
9 |
10 | if ( stopPropagation ) {
11 | e.stopPropagation();
12 | e.stopImmediatePropagation();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/js/utils/dom/query/query.test.ts:
--------------------------------------------------------------------------------
1 | import { query } from './query';
2 |
3 |
4 | describe( 'query', () => {
5 | beforeEach( () => {
6 | document.body.innerHTML = `
7 |
8 | 1
9 |
10 |
11 | 2
12 |
13 |
14 | 3
15 |
16 | `;
17 | } );
18 |
19 | test( 'can return the first element that matches the specified selector.', () => {
20 | const div1 = query( document, 'div' );
21 | expect( div1.id ).toBe( 'div1' );
22 |
23 | const div3 = query( document, '#div3' );
24 | expect( div3.id ).toBe( 'div3' );
25 | } );
26 |
27 | test( 'can accept a parent element to start find an element from.', () => {
28 | const div2 = query( document, '#div2' );
29 | const span2 = query( div2, 'span' );
30 |
31 | expect( span2.textContent ).toBe( '2' );
32 | } );
33 |
34 | test( 'should return `null` if nothing matches the selector.', () => {
35 | expect( query( document, '#nothing' ) ).toBeNull();
36 | } );
37 | } );
38 |
--------------------------------------------------------------------------------
/src/js/utils/dom/query/query.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns an element that matches the provided selector.
3 | *
4 | * @param parent - A parent element to start searching from.
5 | * @param selector - A selector to query.
6 | *
7 | * @return A found element or `null`.
8 | */
9 | export function query( parent: Element | Document, selector: string ): E | null {
10 | return parent && parent.querySelector( selector );
11 | }
12 |
--------------------------------------------------------------------------------
/src/js/utils/dom/queryAll/queryAll.test.ts:
--------------------------------------------------------------------------------
1 | import { queryAll } from './queryAll';
2 |
3 |
4 | describe( 'queryAll', () => {
5 | beforeEach( () => {
6 | document.body.innerHTML = `
7 |
8 | 1
9 |
10 |
11 | 2
12 |
13 |
14 | 3
15 |
16 | `;
17 | } );
18 |
19 | test( 'can get elements that match the selector.', () => {
20 | const divs = queryAll( document.body, 'div' );
21 | expect( divs.length ).toBe( 3 );
22 | expect( divs[ 0 ].id ).toBe( 'div1' );
23 | expect( divs[ 1 ].id ).toBe( 'div2' );
24 | expect( divs[ 2 ].id ).toBe( 'div3' );
25 |
26 | const spans = queryAll( document.body, '#div1 span' );
27 |
28 | expect( spans.length ).toBe( 1 );
29 | expect( spans[ 0 ].textContent ).toBe( '1' );
30 | } );
31 | } );
32 |
--------------------------------------------------------------------------------
/src/js/utils/dom/queryAll/queryAll.ts:
--------------------------------------------------------------------------------
1 | import { slice } from '../../arrayLike';
2 |
3 |
4 | /**
5 | * Returns elements that match the provided selector.
6 | *
7 | * @param parent - A parent element to start searching from.
8 | * @param selector - A selector to query.
9 | *
10 | * @return An array with matched elements.
11 | */
12 | export function queryAll( parent: Element | Document, selector?: string ): E[] {
13 | return selector ? slice( parent.querySelectorAll( selector ) ) : [];
14 | }
15 |
--------------------------------------------------------------------------------
/src/js/utils/dom/rect/rect.test.ts:
--------------------------------------------------------------------------------
1 | import { rect } from './rect';
2 |
3 |
4 | describe( 'rect', () => {
5 | test( 'can return a DOMRect object.', () => {
6 | const div = document.createElement( 'div' );
7 |
8 | expect( rect( div ).width ).toBe( 0 );
9 | expect( rect( div ).left ).toBe( 0 );
10 | } );
11 | } );
12 |
--------------------------------------------------------------------------------
/src/js/utils/dom/rect/rect.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns a DOMRect object of the provided element.
3 | *
4 | * @param target - An element.
5 | */
6 | export function rect( target: Element ): DOMRect {
7 | return target.getBoundingClientRect();
8 | }
9 |
--------------------------------------------------------------------------------
/src/js/utils/dom/remove/remove.test.ts:
--------------------------------------------------------------------------------
1 | import { remove } from './remove';
2 |
3 |
4 | describe( 'remove', () => {
5 | test( 'can remove an element from its parent.', () => {
6 | const div = document.createElement( 'div' );
7 | const span = document.createElement( 'span' );
8 |
9 | div.appendChild( span );
10 | expect( div.firstElementChild ).toBe( span );
11 |
12 | remove( span );
13 | expect( div.children.length ).toBe( 0 );
14 | } );
15 |
16 | test( 'can remove elements from its parent.', () => {
17 | const div = document.createElement( 'div' );
18 | const span1 = document.createElement( 'span' );
19 | const span2 = document.createElement( 'span' );
20 | const span3 = document.createElement( 'span' );
21 |
22 | div.appendChild( span1 );
23 | div.appendChild( span2 );
24 | div.appendChild( span3 );
25 | expect( div.children[ 0 ] ).toBe( span1 );
26 | expect( div.children[ 1 ] ).toBe( span2 );
27 | expect( div.children[ 2 ] ).toBe( span3 );
28 |
29 | remove( [ span1, span2, span3 ] );
30 | expect( div.children.length ).toBe( 0 );
31 | } );
32 |
33 | test( 'can remove a text node from its parent.', () => {
34 | const span = document.createElement( 'span' );
35 | const node = document.createTextNode( 'sample' );
36 |
37 | span.appendChild( node );
38 | expect( span.textContent ).toBe( 'sample' );
39 |
40 | remove( node );
41 | expect( span.textContent ).toBe( '' );
42 | } );
43 | } );
44 |
--------------------------------------------------------------------------------
/src/js/utils/dom/remove/remove.ts:
--------------------------------------------------------------------------------
1 | import { forEach } from '../../array';
2 |
3 |
4 | /**
5 | * Removes the provided node from its parent.
6 | *
7 | * @param nodes - A node or nodes to remove.
8 | */
9 | export function remove( nodes: Node | Node[] ): void {
10 | forEach( nodes, node => {
11 | if ( node && node.parentNode ) {
12 | node.parentNode.removeChild( node );
13 | }
14 | } );
15 | }
16 |
--------------------------------------------------------------------------------
/src/js/utils/dom/removeAttribute/removeAttribute.test.ts:
--------------------------------------------------------------------------------
1 | import { removeAttribute } from './removeAttribute';
2 |
3 |
4 | describe( 'removeAttribute', () => {
5 | test( 'can remove an attribute from an element.', () => {
6 | const div = document.createElement( 'div' );
7 |
8 | div.setAttribute( 'aria-hidden', 'true' );
9 | div.setAttribute( 'tabindex', '-1' );
10 |
11 | removeAttribute( div, 'aria-hidden' );
12 | expect( div.getAttribute( 'aria-hidden' ) ).toBeNull();
13 | expect( div.getAttribute( 'tabindex' ) ).not.toBeNull();
14 |
15 | removeAttribute( div, 'tabindex' );
16 | expect( div.getAttribute( 'tabindex' ) ).toBeNull();
17 | } );
18 |
19 | test( 'can remove attributes from an element.', () => {
20 | const div = document.createElement( 'div' );
21 |
22 | div.setAttribute( 'aria-hidden', 'true' );
23 | div.setAttribute( 'tabindex', '-1' );
24 |
25 | removeAttribute( div, [ 'aria-hidden', 'tabindex' ] );
26 | expect( div.getAttribute( 'aria-hidden' ) ).toBeNull();
27 | expect( div.getAttribute( 'tabindex' ) ).toBeNull();
28 | } );
29 |
30 | test( 'can remove attributes from elements.', () => {
31 | const div1 = document.createElement( 'div1' );
32 | const div2 = document.createElement( 'div2' );
33 | const div3 = document.createElement( 'div2' );
34 | const divs = [ div1, div2, div3 ];
35 | const callback = jest.fn();
36 |
37 | divs.forEach( div => {
38 | div.setAttribute( 'aria-hidden', 'true' );
39 | div.setAttribute( 'tabindex', '-1' );
40 | } );
41 |
42 | removeAttribute( divs, [ 'aria-hidden', 'tabindex' ] );
43 |
44 | divs.forEach( div => {
45 | expect( div.getAttribute( 'aria-hidden' ) ).toBeNull();
46 | expect( div.getAttribute( 'tabindex' ) ).toBeNull();
47 | callback();
48 | } );
49 |
50 | expect( callback ).toHaveBeenCalledTimes( divs.length );
51 | } );
52 | } );
53 |
--------------------------------------------------------------------------------
/src/js/utils/dom/removeAttribute/removeAttribute.ts:
--------------------------------------------------------------------------------
1 | import { forEach } from '../../array';
2 |
3 |
4 | /**
5 | * Removes attributes from the element.
6 | *
7 | * @param elms - An element or elements.
8 | * @param attrs - An attribute or attributes to remove.
9 | */
10 | export function removeAttribute( elms: Element | Element[], attrs: string | string[] ): void {
11 | forEach( elms, elm => {
12 | forEach( attrs, attr => {
13 | elm && elm.removeAttribute( attr );
14 | } );
15 | } );
16 | }
17 |
--------------------------------------------------------------------------------
/src/js/utils/dom/removeClass/removeClass.test.ts:
--------------------------------------------------------------------------------
1 | import { removeClass } from './removeClass';
2 |
3 |
4 | describe( 'removeClass', () => {
5 | beforeEach( () => {
6 | document.body.innerHTML = '';
7 | } );
8 |
9 | test( 'can remove a class from the element.', () => {
10 | const container = document.getElementById( 'container' );
11 |
12 | container.classList.add( 'active' );
13 | expect( container.classList.contains( 'active' ) ).toBe( true );
14 |
15 | removeClass( container, 'active' );
16 | expect( container.classList.contains( 'active' ) ).toBe( false );
17 | } );
18 |
19 | test( 'can remove classes from the element.', () => {
20 | const container = document.getElementById( 'container' );
21 |
22 | container.classList.add( 'active' );
23 | container.classList.add( 'visible' );
24 |
25 | expect( container.classList.contains( 'active' ) ).toBe( true );
26 | expect( container.classList.contains( 'visible' ) ).toBe( true );
27 |
28 | removeClass( container, [ 'active', 'visible' ] );
29 |
30 | expect( container.classList.contains( 'active' ) ).toBe( false );
31 | expect( container.classList.contains( 'visible' ) ).toBe( false );
32 | } );
33 | } );
34 |
--------------------------------------------------------------------------------
/src/js/utils/dom/removeClass/removeClass.ts:
--------------------------------------------------------------------------------
1 | import { toggleClass } from '../toggleClass/toggleClass';
2 |
3 |
4 | /**
5 | * Removes classes from the element.
6 | *
7 | * @param elm - An element to remove classes from.
8 | * @param classes - Classes to remove.
9 | */
10 | export function removeClass( elm: Element, classes: string | string[] ): void {
11 | toggleClass( elm, classes, false );
12 | }
13 |
--------------------------------------------------------------------------------
/src/js/utils/dom/setAttribute/setAttribute.ts:
--------------------------------------------------------------------------------
1 | import { forEach } from '../../array';
2 | import { forOwn } from '../../object';
3 | import { isNull, isObject } from '../../type/type';
4 | import { removeAttribute } from '../removeAttribute/removeAttribute';
5 |
6 |
7 | export function setAttribute( elms: Element | Element[], attr: string, value: string | number | boolean ): void;
8 | export function setAttribute( elms: Element | Element[], attrs: Record ): void;
9 |
10 | /**
11 | * Sets attribute/attributes to the element or elements.
12 | * If the value is `null` or an empty string, the attribute will be removed.
13 | *
14 | * @param elms - An element or an array with elements.
15 | * @param attrs - An attribute name of an object with pairs of a name and a value.
16 | * @param value - A value to set.
17 | */
18 | export function setAttribute(
19 | elms: Element | Element[],
20 | attrs: string | Record,
21 | value?: string | number | boolean
22 | ): void {
23 | if ( isObject( attrs ) ) {
24 | forOwn( attrs, ( value, name ) => {
25 | setAttribute( elms, name, value );
26 | } );
27 | } else {
28 | forEach( elms, elm => {
29 | isNull( value ) || value === '' ? removeAttribute( elm, attrs ) : elm.setAttribute( attrs, String( value ) );
30 | } );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/js/utils/dom/style/style.test.ts:
--------------------------------------------------------------------------------
1 | import { style } from './style';
2 |
3 |
4 | describe( 'styles', () => {
5 | test( 'can set an inline style', () => {
6 | const div = document.createElement( 'div' );
7 | style( div, 'color', 'red' );
8 | style( div, 'backgroundColor', 'white' );
9 | style( div, 'fontSize', '1rem' );
10 |
11 | expect( div.style.color ).toBe( 'red' );
12 | expect( div.style.backgroundColor ).toBe( 'white' );
13 | expect( div.style.fontSize ).toBe( '1rem' );
14 | } );
15 |
16 | test( 'can return a computed style', () => {
17 | const div = document.createElement( 'div' );
18 | div.style.color = 'red';
19 | expect( style( div, 'color' ) ).toBe( 'red' );
20 | } );
21 | } );
22 |
--------------------------------------------------------------------------------
/src/js/utils/dom/style/style.ts:
--------------------------------------------------------------------------------
1 | import { isNull, isUndefined } from '../../type/type';
2 |
3 |
4 | export function style(
5 | elm: HTMLElement,
6 | prop: K,
7 | ): CSSStyleDeclaration[ K ];
8 |
9 | export function style(
10 | elm: HTMLElement,
11 | prop: string,
12 | ): string;
13 |
14 | export function style(
15 | elm: HTMLElement,
16 | prop: string,
17 | value: string | number
18 | ): void;
19 |
20 |
21 | /**
22 | * Applies inline styles to the provided element by an object literal.
23 | *
24 | * @param elm - An element to apply styles to.
25 | * @param prop - An object literal with styles or a property name.
26 | * @param value - A value to set.
27 | */
28 | export function style(
29 | elm: HTMLElement,
30 | prop: string,
31 | value?: string | number
32 | ): string | void {
33 | if ( isUndefined( value ) ) {
34 | return getComputedStyle( elm )[ prop ];
35 | }
36 |
37 | if ( ! isNull( value ) ) {
38 | elm.style[ prop ] = `${ value }`;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/js/utils/dom/timeOf/timeOf.test.ts:
--------------------------------------------------------------------------------
1 | import { fire } from '../../../test';
2 | import { timeOf } from './timeOf';
3 |
4 |
5 | describe( 'timeOf', () => {
6 | test( 'can extract a timestamp from an event object.', done => {
7 | window.addEventListener( 'click', e => {
8 | expect( timeOf( e ) ).toBe( 123 );
9 | done();
10 | } );
11 |
12 | fire( window, 'click', { timeStamp: 123 } );
13 | } );
14 | } );
--------------------------------------------------------------------------------
/src/js/utils/dom/timeOf/timeOf.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Extracts the timestamp from the event object.
3 | *
4 | * @param e - An Event object.
5 | */
6 | export function timeOf( e: Event ): number {
7 | return e.timeStamp;
8 | }
--------------------------------------------------------------------------------
/src/js/utils/dom/toggleClass/toggleClass.test.ts:
--------------------------------------------------------------------------------
1 | import { toggleClass } from './toggleClass';
2 |
3 |
4 | describe( 'toggleClass', () => {
5 | beforeEach( () => {
6 | document.body.innerHTML = '';
7 | } );
8 |
9 | test( 'can add a class to the element.', () => {
10 | const container = document.getElementById( 'container' );
11 | toggleClass( container, 'active', true );
12 | expect( container.classList.contains( 'active' ) ).toBe( true );
13 | } );
14 |
15 | test( 'can add classes to the element.', () => {
16 | const container = document.getElementById( 'container' );
17 |
18 | toggleClass( container, [ 'active', 'visible' ], true );
19 |
20 | expect( container.classList.contains( 'active' ) ).toBe( true );
21 | expect( container.classList.contains( 'visible' ) ).toBe( true );
22 | } );
23 |
24 | test( 'can remove a class from the element.', () => {
25 | const container = document.getElementById( 'container' );
26 | container.classList.add( 'active' );
27 | expect( container.classList.contains( 'active' ) ).toBe( true );
28 |
29 | toggleClass( container, 'active', false );
30 | expect( container.classList.contains( 'active' ) ).toBe( false );
31 | } );
32 |
33 | test( 'can remove classes from the element.', () => {
34 | const container = document.getElementById( 'container' );
35 | container.classList.add( 'active' );
36 | container.classList.add( 'visible' );
37 |
38 | expect( container.classList.contains( 'active' ) ).toBe( true );
39 | expect( container.classList.contains( 'visible' ) ).toBe( true );
40 |
41 | toggleClass( container, [ 'active', 'visible' ], false );
42 |
43 | expect( container.classList.contains( 'active' ) ).toBe( false );
44 | expect( container.classList.contains( 'visible' ) ).toBe( false );
45 | } );
46 | } );
47 |
--------------------------------------------------------------------------------
/src/js/utils/dom/toggleClass/toggleClass.ts:
--------------------------------------------------------------------------------
1 | import { forEach } from '../../array';
2 |
3 |
4 | /**
5 | * Toggles the provided class or classes by following the `add` boolean.
6 | *
7 | * @param elm - An element whose classes are toggled.
8 | * @param classes - A class or class names.
9 | * @param add - Whether to add or remove a class.
10 | */
11 | export function toggleClass( elm: Element, classes: string | string[], add: boolean ): void {
12 | if ( elm ) {
13 | forEach( classes, name => {
14 | if ( name ) {
15 | elm.classList[ add ? 'add' : 'remove' ]( name );
16 | }
17 | } );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/js/utils/dom/unit/unit.test.ts:
--------------------------------------------------------------------------------
1 | import { unit } from './unit';
2 |
3 |
4 | describe( 'unit', () => {
5 | test( 'can append `px` if the value is number.', () => {
6 | expect( unit( 1 ) ).toBe( '1px' );
7 | expect( unit( 1.8 ) ).toBe( '1.8px' );
8 | } );
9 |
10 | test( 'should return the value itself if it is string.', () => {
11 | expect( unit( '10vh' ) ).toBe( '10vh' );
12 | expect( unit( '10em' ) ).toBe( '10em' );
13 | } );
14 | } );
15 |
--------------------------------------------------------------------------------
/src/js/utils/dom/unit/unit.ts:
--------------------------------------------------------------------------------
1 | import { isString } from '../../type/type';
2 |
3 |
4 | /**
5 | * Appends `px` to the provided number.
6 | * If the value is already string, just returns it.
7 | *
8 | * @param value - A value to append `px` to.
9 | *
10 | * @return A string with the CSS unit.
11 | */
12 | export function unit( value: number | string ): string {
13 | return isString( value ) ? value : value ? `${ value }px` : '';
14 | }
15 |
--------------------------------------------------------------------------------
/src/js/utils/error/assert/assert.ts:
--------------------------------------------------------------------------------
1 | import { PROJECT_CODE } from '../../../constants/project';
2 |
3 |
4 | /**
5 | * Throws an error if the provided condition is falsy.
6 | *
7 | * @param condition - If falsy, an error is thrown.
8 | * @param message - Optional. A message to display.
9 | */
10 | export function assert( condition: any, message?: string ): void {
11 | if ( ! condition ) {
12 | throw new Error( `[${ PROJECT_CODE }] ${ message || '' }` );
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/js/utils/error/error/error.ts:
--------------------------------------------------------------------------------
1 | import { PROJECT_CODE } from '../../../constants/project';
2 |
3 |
4 | /**
5 | * Displays the error message on the console.
6 | *
7 | * @param message - A message.
8 | */
9 | export function error( message: string ): void {
10 | console.error( `[${ PROJECT_CODE }] ${ message }` );
11 | }
12 |
--------------------------------------------------------------------------------
/src/js/utils/error/index.ts:
--------------------------------------------------------------------------------
1 | export { assert } from './assert/assert';
2 | export { error } from './error/error';
3 |
--------------------------------------------------------------------------------
/src/js/utils/function/apply/apply.test.ts:
--------------------------------------------------------------------------------
1 | import { apply } from './apply';
2 |
3 |
4 | describe( 'apply', () => {
5 | test( 'can bind arguments to the function.', () => {
6 | function sum( a: number, b: number, c = 0, d = 0 ): number {
7 | return a + b + c + d;
8 | }
9 |
10 | // The type should be ( b: number, c?: number, d?: number ) => number.
11 | const sum1 = apply( sum, 1 );
12 | const sum2 = apply( sum, 1, 1 );
13 | const sum3 = apply( sum, 1, 1, 1 );
14 | const sum4 = apply( sum, 1, 1, 1, 1 );
15 |
16 | expect( sum1( 1, 1, 1 ) ).toBe( 4 );
17 | expect( sum2( 1, 1 ) ).toBe( 4 );
18 | expect( sum3( 1 ) ).toBe( 4 );
19 | expect( sum4() ).toBe( 4 );
20 |
21 | expect( sum1( 2 ) ).toBe( 3 ); // 1, 2, 0, 0
22 | expect( sum1( 2, 2 ) ).toBe( 5 ); // 1, 2, 2, 0
23 | expect( sum1( 2, 2, 2 ) ).toBe( 7 ); // 1, 2, 2, 2
24 | } );
25 | } );
--------------------------------------------------------------------------------
/src/js/utils/function/apply/apply.ts:
--------------------------------------------------------------------------------
1 | import { AnyFunction, ShiftN } from '../../../types';
2 | import { slice } from '../../arrayLike';
3 |
4 |
5 | /**
6 | * Create a function where provided arguments are bound.
7 | * `this` parameter will be always null.
8 | *
9 | * @param func - A function.
10 | * @param args - Arguments to bind to the function.
11 | *
12 | * @return A function where arguments are bound.
13 | */
14 | export function apply(
15 | func: F,
16 | ...args: A
17 | ): ( ...args: ShiftN, A["length"]> ) => ReturnType;
18 |
19 | /**
20 | * Create a function where provided arguments are bound.
21 | * `this` parameter will be always null.
22 | *
23 | * @param func - A function.
24 | */
25 | export function apply( func: AnyFunction ): any {
26 | // eslint-disable-next-line prefer-rest-params, prefer-spread
27 | return func.bind( null, ...slice( arguments, 1 ) );
28 | }
29 |
--------------------------------------------------------------------------------
/src/js/utils/function/index.ts:
--------------------------------------------------------------------------------
1 | export { apply } from './apply/apply';
2 | export { nextTick } from './nextTick/nextTick';
3 | export { noop } from './noop/noop';
4 | export { raf } from './raf/raf';
5 |
--------------------------------------------------------------------------------
/src/js/utils/function/nextTick/nextTick.ts:
--------------------------------------------------------------------------------
1 | import { AnyFunction } from '../../../types';
2 |
3 |
4 | /**
5 | * Invokes the callback on the next tick.
6 | *
7 | * @param callback - A callback function.
8 | */
9 | export const nextTick: ( callback: AnyFunction ) => ReturnType = setTimeout;
10 |
--------------------------------------------------------------------------------
/src/js/utils/function/noop/noop.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * No operation.
3 | */
4 | export const noop = (): void => {}; // eslint-disable-line no-empty-function, @typescript-eslint/no-empty-function
5 |
--------------------------------------------------------------------------------
/src/js/utils/function/raf/raf.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The alias of `window.requestAnimationFrame()`.
3 | */
4 | export function raf( func: FrameRequestCallback ): number {
5 | return requestAnimationFrame( func );
6 | }
7 |
--------------------------------------------------------------------------------
/src/js/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './array';
2 | export * from './arrayLike';
3 | export * from './dom';
4 | export * from './error';
5 | export * from './function';
6 | export * from './math';
7 | export * from './object';
8 | export * from './string';
9 | export * from './type/type';
10 |
--------------------------------------------------------------------------------
/src/js/utils/math/approximatelyEqual/approximatelyEqual.test.ts:
--------------------------------------------------------------------------------
1 | import { approximatelyEqual } from './approximatelyEqual';
2 |
3 |
4 | describe( 'approximatelyEqual', () => {
5 | test( 'can tell if 2 numbers are approximately equal or not.', () => {
6 | expect( approximatelyEqual( 1, 1, 1 ) ).toBe( true );
7 | expect( approximatelyEqual( 1, 0.9, 1 ) ).toBe( true );
8 | expect( approximatelyEqual( 1, 1.9, 1 ) ).toBe( true );
9 |
10 | expect( approximatelyEqual( 1, 2, 1 ) ).toBe( false );
11 | expect( approximatelyEqual( 1, 0, 1 ) ).toBe( false );
12 |
13 | expect( approximatelyEqual( 1, 2, 2 ) ).toBe( true );
14 | expect( approximatelyEqual( 1, 0, 2 ) ).toBe( true );
15 | } );
16 | } );
17 |
--------------------------------------------------------------------------------
/src/js/utils/math/approximatelyEqual/approximatelyEqual.ts:
--------------------------------------------------------------------------------
1 | import { abs } from '../math/math';
2 |
3 |
4 | /**
5 | * Checks if the provided 2 numbers are approximately equal or not.
6 | *
7 | * @param x - A number.
8 | * @param y - Another number to compare.
9 | * @param epsilon - An accuracy that defines the approximation.
10 | *
11 | * @return `true` if 2 numbers are considered to be equal, or otherwise `false`.
12 | */
13 | export function approximatelyEqual( x: number, y: number, epsilon: number ): boolean {
14 | return abs( x - y ) < epsilon;
15 | }
16 |
--------------------------------------------------------------------------------
/src/js/utils/math/between/between.test.ts:
--------------------------------------------------------------------------------
1 | import { between } from './between';
2 |
3 |
4 | describe( 'between', () => {
5 | test( 'can check a number is between 2 numbers inclusively.', () => {
6 | expect( between( 0, 0, 1 ) ).toBe( true );
7 | expect( between( 1, 0, 1 ) ).toBe( true );
8 |
9 | expect( between( 1, 2, 3 ) ).toBe( false );
10 |
11 | expect( between( 1, 0, 2 ) ).toBe( true );
12 | expect( between( 1, 2, 0 ) ).toBe( true );
13 | } );
14 |
15 | test( 'can check a number is between 2 numbers exclusively.', () => {
16 | expect( between( 0, 0, 1, true ) ).toBe( false );
17 | expect( between( 1, 0, 1, true ) ).toBe( false );
18 |
19 | expect( between( 1, 2, 3, true ) ).toBe( false );
20 |
21 | expect( between( 1, 0, 2, true ) ).toBe( true );
22 | expect( between( 1, 2, 0, true ) ).toBe( true );
23 | } );
24 | } );
25 |
--------------------------------------------------------------------------------
/src/js/utils/math/between/between.ts:
--------------------------------------------------------------------------------
1 | import { max, min } from '../math/math';
2 |
3 |
4 | /**
5 | * Checks if the subject number is between `x` and `y`.
6 | *
7 | * @param number - A subject number to check.
8 | * @param x - A min or max number.
9 | * @param y - A max or min number.
10 | * @param exclusive - Optional. Whether to exclude `x` or `y`.
11 | */
12 | export function between( number: number, x: number, y: number, exclusive?: boolean ): boolean {
13 | const minimum = min( x, y );
14 | const maximum = max( x, y );
15 | return exclusive
16 | ? minimum < number && number < maximum
17 | : minimum <= number && number <= maximum;
18 | }
19 |
--------------------------------------------------------------------------------
/src/js/utils/math/clamp/clamp.test.ts:
--------------------------------------------------------------------------------
1 | import { clamp } from './clamp';
2 |
3 |
4 | describe( 'clamp', () => {
5 | test( 'can clamp a number', () => {
6 | expect( clamp( 0, 0, 1 ) ).toBe( 0 );
7 | expect( clamp( 1, 0, 1 ) ).toBe( 1 );
8 |
9 | expect( clamp( 1, 2, 3 ) ).toBe( 2 );
10 | expect( clamp( 1, 0, 0 ) ).toBe( 0 );
11 |
12 | expect( clamp( 3, 0, 1 ) ).toBe( 1 );
13 | expect( clamp( 3, 4, 5 ) ).toBe( 4 );
14 | } );
15 | } );
16 |
--------------------------------------------------------------------------------
/src/js/utils/math/clamp/clamp.ts:
--------------------------------------------------------------------------------
1 | import { max, min } from '../math/math';
2 |
3 |
4 | /**
5 | * Clamps a number.
6 | *
7 | * @param number - A subject number to check.
8 | * @param x - A min or max number.
9 | * @param y - A min or max number.
10 | *
11 | * @return A clamped number.
12 | */
13 | export function clamp( number: number, x: number, y: number ): number {
14 | const minimum = min( x, y );
15 | const maximum = max( x, y );
16 | return min( max( minimum, number ), maximum );
17 | }
18 |
--------------------------------------------------------------------------------
/src/js/utils/math/index.ts:
--------------------------------------------------------------------------------
1 | export { approximatelyEqual } from './approximatelyEqual/approximatelyEqual';
2 | export { between } from './between/between';
3 | export { clamp } from './clamp/clamp';
4 | export { sign } from './sign/sign';
5 |
6 | export * from './math/math';
7 |
--------------------------------------------------------------------------------
/src/js/utils/math/math/math.ts:
--------------------------------------------------------------------------------
1 | export const { min, max, floor, ceil, abs } = Math;
2 |
--------------------------------------------------------------------------------
/src/js/utils/math/sign/sign.test.ts:
--------------------------------------------------------------------------------
1 | import { sign } from './sign';
2 |
3 |
4 | describe( 'sign', () => {
5 | test( 'can return the sign of the number', () => {
6 | expect( sign( 0 ) ).toBe( 0 );
7 | expect( sign( 1 ) ).toBe( 1 );
8 | expect( sign( -1 ) ).toBe( -1 );
9 |
10 | expect( sign( 100 ) ).toBe( 1 );
11 | expect( sign( -100 ) ).toBe( -1 );
12 |
13 | expect( sign( 0.5 ) ).toBe( 1 );
14 | expect( sign( -0.5 ) ).toBe( -1 );
15 |
16 | expect( sign( Infinity ) ).toBe( 1 );
17 | expect( sign( -Infinity ) ).toBe( -1 );
18 | } );
19 | } );
20 |
--------------------------------------------------------------------------------
/src/js/utils/math/sign/sign.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns the sign of the provided number.
3 | *
4 | * @param x - A number.
5 | *
6 | * @return `1` for positive numbers, `-1` for negative numbers, or `0` for `0`.
7 | */
8 | export function sign( x: number ): number {
9 | return +( x > 0 ) - +( x < 0 );
10 | }
11 |
--------------------------------------------------------------------------------
/src/js/utils/object/assign/assign.test.ts:
--------------------------------------------------------------------------------
1 | import { assign } from './assign';
2 |
3 |
4 | describe( 'assign', () => {
5 | test( 'can assign own enumerable properties of the source object to the target.', () => {
6 | const object = { a: 1, b: '2' };
7 | const source = { a: 2, c: true };
8 | const assigned = assign( object, source );
9 |
10 | expect( assigned ).toStrictEqual( { a: 2, b: '2', c: true } );
11 | } );
12 |
13 | test( 'can assign properties of multiple sources to the target.', () => {
14 | const object = { a: 1, b: '2' };
15 | const source1 = { a: 2, c: true };
16 | const source2 = { d: 3, e: '3' };
17 | const source3 = { e: Infinity };
18 | const assigned = assign( object, source1, source2, source3 );
19 |
20 | expect( assigned ).toStrictEqual( { a: 2, b: '2', c: true, d: 3, e: Infinity } );
21 | } );
22 |
23 | test( 'should assign a nested object as a reference.', () => {
24 | const object = { a: { b: 1 } };
25 | const source = { a: { b: 2 } };
26 | const assigned = assign( object, source );
27 |
28 | expect( assigned ).toStrictEqual( { a: { b: 2 } } );
29 | expect( source.a ).toBe( assigned.a );
30 | } );
31 | } );
32 |
--------------------------------------------------------------------------------
/src/js/utils/object/assign/assign.ts:
--------------------------------------------------------------------------------
1 | import { Cast, Head, Push, Resolve, Shift } from '../../../types';
2 | import { slice } from '../../arrayLike';
3 | import { forOwn } from '../forOwn/forOwn';
4 |
5 |
6 | /**
7 | * Assigns U to T.
8 | *
9 | * @typeParam T - An object to assign to.
10 | * @typeParam U - An object to assign.
11 | *
12 | * @return An assigned object type.
13 | */
14 | export type Assign = Omit & U;
15 |
16 | /**
17 | * Recursively assigns U[] to T.
18 | *
19 | * @typeParam T - An object to assign to.
20 | * @typeParam U - A tuple contains objects.
21 | *
22 | * @return An assigned object type.
23 | */
24 | export type Assigned = {
25 | 0: T,
26 | 1: Assigned>, Shift, N, Push>,
27 | }[ C['length'] extends N ? 0 : 1 ] extends infer A ? Cast : never;
28 |
29 | export function assign( object: T ): T;
30 |
31 | export function assign(
32 | object: T,
33 | ...sources: U
34 | ): Resolve>
35 |
36 | /**
37 | * Assigns all own enumerable properties of all source objects to the provided object.
38 | *
39 | * @param object - An object to assign properties to.
40 | *
41 | * @return An object assigned properties of the sources to.
42 | */
43 | export function assign( object: T ): any {
44 | // eslint-disable-next-line prefer-rest-params, prefer-spread
45 | slice( arguments, 1 ).forEach( source => {
46 | forOwn( source, ( value, key ) => {
47 | object[ key ] = source[ key ];
48 | } );
49 | } );
50 |
51 | return object;
52 | }
53 |
--------------------------------------------------------------------------------
/src/js/utils/object/forOwn/forOwn.test.ts:
--------------------------------------------------------------------------------
1 | import { forOwn } from './forOwn';
2 |
3 |
4 | describe( 'forOwn', () => {
5 | test( 'can iterate an object by own enumerable properties.', () => {
6 | const object = { a: 1, b: 2, c: 3 };
7 | let counter = 0;
8 |
9 | forOwn( object, ( value, key ) => {
10 | counter++;
11 | expect( object[ key ] ).toBe( value );
12 | } );
13 |
14 | expect( counter ).toBe( Object.keys( object ).length );
15 | } );
16 |
17 | test( 'can iterate an object from the end.', () => {
18 | const object = { a: 1, b: 2, c: 3 };
19 | const values: number[] = [];
20 |
21 | forOwn( object, ( value ) => {
22 | values.push( value );
23 | }, true );
24 |
25 | expect( values ).toEqual( [ 3, 2, 1 ] );
26 | } );
27 |
28 | test( 'should not handle inherited properties.', () => {
29 | class Constructor {
30 | a = 1;
31 | b = 2;
32 | }
33 |
34 | Constructor.prototype[ 'c' ] = 3;
35 |
36 | const object = {};
37 |
38 | forOwn( new Constructor(), ( value, key ) => {
39 | object[ key ] = value;
40 | } );
41 |
42 | expect( object ).toStrictEqual( { a: 1, b: 2 } );
43 | } );
44 | } );
45 |
--------------------------------------------------------------------------------
/src/js/utils/object/forOwn/forOwn.ts:
--------------------------------------------------------------------------------
1 | import { ownKeys } from '../ownKeys/ownKeys';
2 |
3 |
4 | /**
5 | * Iterates over the provided object by own enumerable keys with calling the iteratee function.
6 | *
7 | * @param object - An object to iterate over.
8 | * @param iteratee - An iteratee function that takes `value` and `key` as arguments.
9 | * @param right - If `true`, the method iterates over the object from the end like `forEachRight()`.
10 | *
11 | * @return A provided object itself.
12 | */
13 | export function forOwn(
14 | object: T,
15 | iteratee: ( value: T[ keyof T ], key: string ) => boolean | void,
16 | right?: boolean
17 | ): T {
18 | if ( object ) {
19 | ( right ? ownKeys( object ).reverse() : ownKeys( object ) ).forEach( key => {
20 | key !== '__proto__' && iteratee( object[ key ], key );
21 | } );
22 | }
23 |
24 | return object;
25 | }
26 |
--------------------------------------------------------------------------------
/src/js/utils/object/index.ts:
--------------------------------------------------------------------------------
1 | export { assign } from './assign/assign';
2 | export { forOwn } from './forOwn/forOwn';
3 | export { merge } from './merge/merge';
4 | export { omit } from './omit/omit';
5 | export { ownKeys } from './ownKeys/ownKeys';
6 |
--------------------------------------------------------------------------------
/src/js/utils/object/merge/merge.test.ts:
--------------------------------------------------------------------------------
1 | import { merge } from './merge';
2 |
3 |
4 | describe( 'merge', () => {
5 | test( 'can merge 2 objects.', () => {
6 | const object = { a: 1, b: '2' };
7 | const source = { a: 2, c: true };
8 |
9 | expect( merge( object, source ) ).toStrictEqual( { a: 2, b: '2', c: true } );
10 |
11 | // Should not change the source
12 | expect( source ).toStrictEqual( { a: 2, c: true } );
13 | } );
14 |
15 | test( 'can merge 2 objects recursively.', () => {
16 | const object = { a: 1, b: { c: 2, d: 3 } };
17 | const source = { b: { d: 4, e: 5 }, f: true };
18 |
19 | expect( merge( object, source ) ).toStrictEqual( {
20 | a: 1,
21 | b: { c: 2, d: 4, e: 5 },
22 | f: true,
23 | } );
24 | } );
25 |
26 | test( 'can merge multiple objects recursively.', () => {
27 | const object = { a: 1, b: { c: 2, d: 3 } };
28 | const source1 = { b: { d: 4, e: 5 }, f: true };
29 | const source2 = { b: { d: '4', g: 6 }, h: [ 1, 2, 3 ] };
30 | const source3 = { h: [ 4, 5, 6 ], i: Infinity };
31 | const source4 = { a: '1' };
32 | const merged = merge( object, source1, source2, source3, source4 );
33 |
34 | expect( merged ).toStrictEqual( {
35 | a: '1',
36 | b: { c: 2, d: '4', e: 5, g: 6 },
37 | f: true,
38 | h: [ 4, 5, 6 ],
39 | i: Infinity,
40 | } );
41 | } );
42 |
43 | test( 'should disconnect reference of arrays.', () => {
44 | const array = [ 1, 2, 3 ];
45 | const object = {};
46 | const source = { array };
47 | const merged = merge( object, source );
48 |
49 | expect( merged ).toStrictEqual( { array: [ 1, 2, 3 ] } );
50 | expect( merged.array ).not.toBe( array );
51 | } );
52 | } );
53 |
--------------------------------------------------------------------------------
/src/js/utils/object/omit/omit.test.ts:
--------------------------------------------------------------------------------
1 | import { omit } from './omit';
2 |
3 |
4 | describe( 'omit', () => {
5 | function hasOwn( object: object, key: string ): boolean {
6 | return Object.prototype.hasOwnProperty.call( object, key );
7 | }
8 |
9 | test( 'can delete specified key.', () => {
10 | const object = { a: 1, b: 2, c: 3 };
11 |
12 | expect( hasOwn( object, 'a' ) ).toBe( true );
13 | expect( hasOwn( object, 'b' ) ).toBe( true );
14 |
15 | omit( object, 'a' );
16 | expect( hasOwn( object, 'a' ) ).toBe( false );
17 |
18 | omit( object, 'b' );
19 | expect( hasOwn( object, 'b' ) ).toBe( false );
20 | } );
21 |
22 | test( 'can delete specified keys.', () => {
23 | const object = { a: 1, b: 2, c: 3 };
24 |
25 | omit( object, [ 'a', 'b' ] );
26 | expect( hasOwn( object, 'a' ) ).toBe( false );
27 | expect( hasOwn( object, 'b' ) ).toBe( false );
28 | } );
29 |
30 | test( 'can delete all own enumerable keys.', () => {
31 | const object = { a: 1, b: 2, c: 3 };
32 |
33 | omit( object );
34 | expect( hasOwn( object, 'a' ) ).toBe( false );
35 | expect( hasOwn( object, 'b' ) ).toBe( false );
36 | expect( hasOwn( object, 'c' ) ).toBe( false );
37 | expect( Object.keys( object ).length ).toBe( 0 );
38 | } );
39 |
40 | test( 'should not delete inherited keys.', () => {
41 | const parent = { a: 1, b: 2, c: 3 };
42 | const object = Object.create( parent );
43 |
44 | omit( object );
45 |
46 | expect( hasOwn( parent, 'a' ) ).toBe( true );
47 | expect( hasOwn( parent, 'b' ) ).toBe( true );
48 | expect( hasOwn( parent, 'c' ) ).toBe( true );
49 |
50 | expect( object.a ).toBe( 1 );
51 | expect( object.b ).toBe( 2 );
52 | expect( object.c ).toBe( 3 );
53 | } );
54 | } );
55 |
--------------------------------------------------------------------------------
/src/js/utils/object/omit/omit.ts:
--------------------------------------------------------------------------------
1 | import { forEach } from '../../array';
2 | import { ownKeys } from '../ownKeys/ownKeys';
3 |
4 |
5 | /**
6 | * Deletes specified own keys from the object.
7 | *
8 | * @param object - An object.
9 | * @param keys - A key or keys to delete. If not specified, all own enumerable keys will be deleted.
10 | */
11 | export function omit( object: object, keys?: string | string[] ): void {
12 | forEach( keys || ownKeys( object ), key => {
13 | delete object[ key ];
14 | } );
15 | }
--------------------------------------------------------------------------------
/src/js/utils/object/ownKeys/ownKeys.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * An alias of `Object.keys()`
3 | */
4 | export const ownKeys = Object.keys;
--------------------------------------------------------------------------------
/src/js/utils/string/camelToKebab/camelToKebab.test.ts:
--------------------------------------------------------------------------------
1 | import { camelToKebab } from './camelToKebab';
2 |
3 |
4 | describe( 'camelToKebab', () => {
5 | test( 'can convert a string in the camel case to the kebab case.', () => {
6 | expect( camelToKebab( 'maxWidth' ) ).toBe( 'max-width' );
7 | expect( camelToKebab( 'borderLeftWidth' ) ).toBe( 'border-left-width' );
8 | expect( camelToKebab( 'listStyleType' ) ).toBe( 'list-style-type' );
9 |
10 | expect( camelToKebab( 'ButtonElement' ) ).toBe( 'button-element' );
11 | } );
12 |
13 | test( 'should do nothing if the string is already described in the kebab case.', () => {
14 | expect( camelToKebab( 'max-width' ) ).toBe( 'max-width' );
15 | expect( camelToKebab( 'border-left-width' ) ).toBe( 'border-left-width' );
16 | } );
17 | } );
18 |
--------------------------------------------------------------------------------
/src/js/utils/string/camelToKebab/camelToKebab.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Converts the provided string in the camel case to the kebab case.
3 | *
4 | * @param string - A string to convert.
5 | */
6 | export function camelToKebab( string: string ): string {
7 | return string.replace( /([a-z0-9])([A-Z])/g, '$1-$2' ).toLowerCase();
8 | }
9 |
--------------------------------------------------------------------------------
/src/js/utils/string/format/format.test.ts:
--------------------------------------------------------------------------------
1 | import { format } from './format';
2 |
3 |
4 | describe( 'format', () => {
5 | test( 'can replace %s with provided replacements', () => {
6 | expect( format( '%s results', 10 ) ).toBe( '10 results' );
7 | expect( format( '%s/%s', [ 1, 10 ] ) ).toBe( '1/10' );
8 | } );
9 | } );
10 |
--------------------------------------------------------------------------------
/src/js/utils/string/format/format.ts:
--------------------------------------------------------------------------------
1 | import { forEach } from '../../array';
2 |
3 |
4 | /**
5 | * Formats a string.
6 | *
7 | * @param string - A string to format.
8 | * @param replacements - A replacement or replacements.
9 | *
10 | * @return A formatted string.
11 | */
12 | export function format( string: string, replacements: string | number | Array ): string {
13 | forEach( replacements, replacement => {
14 | string = string.replace( '%s', `${ replacement }` );
15 | } );
16 |
17 | return string;
18 | }
19 |
--------------------------------------------------------------------------------
/src/js/utils/string/index.ts:
--------------------------------------------------------------------------------
1 | export { camelToKebab } from './camelToKebab/camelToKebab';
2 | export { format } from './format/format';
3 | export { pad } from './pad/pad';
4 | export { uniqueId } from './uniqueId/uniqueId';
5 |
--------------------------------------------------------------------------------
/src/js/utils/string/pad/pad.test.ts:
--------------------------------------------------------------------------------
1 | import { pad } from './pad';
2 |
3 |
4 | describe( 'pad', () => {
5 | test( 'can pad a number with 0.', () => {
6 | expect( pad( 1 ) ).toBe( '01' );
7 | expect( pad( 5 ) ).toBe( '05' );
8 | } );
9 |
10 | test( 'should not pad if the number is greater than 9.', () => {
11 | expect( pad( 10 ) ).toBe( '10' );
12 | expect( pad( 11 ) ).toBe( '11' );
13 | } );
14 | } );
15 |
--------------------------------------------------------------------------------
/src/js/utils/string/pad/pad.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Pads the number with 0.
3 | *
4 | * @param number - A number to pad.
5 | *
6 | * @return string - Padded number.
7 | */
8 | export function pad( number: number ): string {
9 | return number < 10 ? `0${ number }` : `${ number }`;
10 | }
11 |
--------------------------------------------------------------------------------
/src/js/utils/string/uniqueId/uniqueId.test.ts:
--------------------------------------------------------------------------------
1 | import { uniqueId } from './uniqueId';
2 |
3 |
4 | describe( 'uniqueId', () => {
5 | test( 'can generate a sequential unique ID.', () => {
6 | expect( uniqueId( 'container-' ) ).toBe( 'container-01' );
7 | expect( uniqueId( 'container-' ) ).toBe( 'container-02' );
8 |
9 | expect( uniqueId( 'button-' ) ).toBe( 'button-01' );
10 | expect( uniqueId( 'button-' ) ).toBe( 'button-02' );
11 |
12 | expect( uniqueId( 'container-' ) ).toBe( 'container-03' );
13 | expect( uniqueId( 'container-' ) ).toBe( 'container-04' );
14 |
15 | expect( uniqueId( 'button-' ) ).toBe( 'button-03' );
16 | expect( uniqueId( 'button-' ) ).toBe( 'button-04' );
17 | } );
18 | } );
19 |
--------------------------------------------------------------------------------
/src/js/utils/string/uniqueId/uniqueId.ts:
--------------------------------------------------------------------------------
1 | import { pad } from '../pad/pad';
2 |
3 |
4 | /**
5 | * Stores unique IDs.
6 | *
7 | * @since 3.0.0
8 | */
9 | const ids: Record = {};
10 |
11 | /**
12 | * Returns a sequential unique ID as "{ prefix }-{ number }".
13 | *
14 | * @param prefix - A prefix for the ID.
15 | */
16 | export function uniqueId( prefix: string ): string {
17 | return `${ prefix }${ pad( ( ids[ prefix ] = ( ids[ prefix ] || 0 ) + 1 ) ) }`;
18 | }
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "es6",
5 | "sourceMap": true,
6 | "mapRoot": "./",
7 | "moduleResolution": "node",
8 | "esModuleInterop": true,
9 | "skipLibCheck": true,
10 | "strict": true,
11 | "strictNullChecks": false,
12 | "suppressImplicitAnyIndexErrors": true,
13 | "declarationDir": "./dist/types",
14 | "declaration": true,
15 | "lib": [
16 | "dom",
17 | "es6"
18 | ],
19 | },
20 | "include": [
21 | "src/js/**/*.ts",
22 | ],
23 | "exclude": [
24 | "node_modules",
25 | "src/js/**/*.test.ts"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------