testing moving the functionality into small modules #5
51
modules/utils/createElementStatusModes.mjs
Normal file
51
modules/utils/createElementStatusModes.mjs
Normal file
@ -0,0 +1,51 @@
|
||||
//@ts-check
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates exclusive states for an HTML element. These states are added as classes
|
||||
* and can be used to drive CSS changes.
|
||||
* @param {string[]} allModes A string list of all possible modes. It's advised
|
||||
* to prepend (`state-` or `mode-` to each for clarity)
|
||||
* @param {Element} [element] the element to add the classes to. Defaults to the
|
||||
* document's body
|
||||
*/
|
||||
export const createElementStatusModes = (allModes, element = document.body) => {
|
||||
/**
|
||||
* @param {any} mode
|
||||
* @returns {mode is number}
|
||||
*/
|
||||
const isValidIndex = (mode) =>
|
||||
typeof mode === "number" && mode >= 0 && mode < allModes.length;
|
||||
|
||||
/**
|
||||
* Sets a status mode (class name) on the element.
|
||||
* Pass a falsy value to clear all modes
|
||||
* @param {number|null|undefined|false} mode
|
||||
*/
|
||||
const set = (mode = false) => {
|
||||
mode = isValidIndex(mode) ? mode : -1;
|
||||
const modeClass = allModes[mode];
|
||||
if (modeClass && element.classList.contains(modeClass)) {
|
||||
return;
|
||||
}
|
||||
element.classList.remove(...allModes);
|
||||
element.classList.add(modeClass);
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies which of the given classes is set.
|
||||
* @returns {string|undefined} the class if there is one
|
||||
*/
|
||||
const get = () =>
|
||||
allModes.find((className) => element.classList.contains(className));
|
||||
|
||||
/**
|
||||
* @param {number} state
|
||||
*/
|
||||
const is = (state) =>
|
||||
isValidIndex(state) && document.body.classList.contains(allModes[state]);
|
||||
|
||||
return { set, get, is };
|
||||
};
|
||||
|
||||
export default createElementStatusModes;
|
@ -1,14 +0,0 @@
|
||||
//@ts-check
|
||||
|
||||
/**
|
||||
* Creates a helper to add or remove global classes that begin with `is-`
|
||||
* @param {string} name
|
||||
*/
|
||||
export const documentMode = (name) => ({
|
||||
on: () => document.body.classList.add(`is-${name}`),
|
||||
off: () => document.body.classList.remove(`is-${name}`),
|
||||
toggle: () => document.body.classList.toggle(`is-${name}`),
|
||||
has: () => document.body.classList.contains(`is-${name}`),
|
||||
});
|
||||
|
||||
export default documentMode;
|
@ -1,35 +1,16 @@
|
||||
//@ts-check
|
||||
import {createElementStatusModes} from "./createElementStatusModes.mjs"
|
||||
|
||||
/**
|
||||
* Creates a document state object that can toggle between exclusive states.
|
||||
* All passed states' css classnames will be prepended with `mode-`.
|
||||
* @see {createElementStatusModes}
|
||||
* @param {string[]} states
|
||||
*/
|
||||
export const documentState = (states) => {
|
||||
const all = states.map((state) => `mode-${state}`);
|
||||
|
||||
/**
|
||||
* @param {any} state
|
||||
* @returns {state is number}
|
||||
*/
|
||||
const isValidIndex = (state) =>
|
||||
typeof state === "number" && state >= 0 && state < all.length;
|
||||
|
||||
/**
|
||||
* @param {number} state
|
||||
*/
|
||||
const is = (state) =>
|
||||
isValidIndex(state) && document.body.classList.contains(all[state]);
|
||||
|
||||
/**
|
||||
* @param {number|undefined|null|false} state
|
||||
*/
|
||||
const set = (state = false) => {
|
||||
document.body.classList.remove(...all);
|
||||
isValidIndex(state) && document.body.classList.add(all[state]);
|
||||
};
|
||||
|
||||
return { set, is };
|
||||
return createElementStatusModes(all, document.body)
|
||||
};
|
||||
|
||||
export default documentState;
|
||||
|
15
modules/utils/elementMode.mjs
Normal file
15
modules/utils/elementMode.mjs
Normal file
@ -0,0 +1,15 @@
|
||||
//@ts-check
|
||||
|
||||
/**
|
||||
* Creates a helper to add or remove global classes that begin with `is-`
|
||||
* @param {string} name name of the state
|
||||
* @param {Element} [element] defaults to the document body
|
||||
*/
|
||||
export const elementMode = (name, element = document.body) => ({
|
||||
on: () => element.classList.add(`is-${name}`),
|
||||
off: () => element.classList.remove(`is-${name}`),
|
||||
toggle: () => element.classList.toggle(`is-${name}`),
|
||||
has: () => element.classList.contains(`is-${name}`),
|
||||
});
|
||||
|
||||
export default elementMode;
|
11
modules/utils/escapeRegExp.mjs
Normal file
11
modules/utils/escapeRegExp.mjs
Normal file
@ -0,0 +1,11 @@
|
||||
//@ts-check
|
||||
|
||||
/**
|
||||
* Escapes a string so it can be used in a regular expression
|
||||
* @param {string} text
|
||||
*/
|
||||
export function escapeRegExp(text) {
|
||||
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
}
|
||||
|
||||
export default escapeRegExp
|
22
modules/utils/getAspectRatio.mjs
Normal file
22
modules/utils/getAspectRatio.mjs
Normal file
@ -0,0 +1,22 @@
|
||||
//@ts-check
|
||||
|
||||
/**
|
||||
* @typedef {{width: number;height: number}} Size
|
||||
*/
|
||||
|
||||
/**
|
||||
* Calculates an ideal ratio
|
||||
* @param {Size} initial the initial size
|
||||
* @param {Size} current the current size
|
||||
* @returns
|
||||
*/
|
||||
export const getAspectRatio = (initial, current) => {
|
||||
const ratioW = current.width / initial.width;
|
||||
const ratioH = current.height / initial.height;
|
||||
const ratio = Math.min(ratioW, ratioH);
|
||||
const width = initial.width * ratio;
|
||||
const height = initial.height * ratio;
|
||||
return { width, height, ratio };
|
||||
};
|
||||
|
||||
export default getAspectRatio
|
19
modules/utils/getElement.mjs
Normal file
19
modules/utils/getElement.mjs
Normal file
@ -0,0 +1,19 @@
|
||||
//@ts-check
|
||||
import {isElement} from "./isElement.mjs";
|
||||
|
||||
/**
|
||||
* Little utility so people can pass css selectors or elements in initialization
|
||||
* options.
|
||||
* A minimal replacement to a more full fledged selector engine like jQuery
|
||||
* @param {string|HTMLElement} elementOrString
|
||||
* @returns {HTMLElement | null}
|
||||
*/
|
||||
export const getElement = (elementOrString) => {
|
||||
const element =
|
||||
elementOrString && typeof elementOrString === "string"
|
||||
? /** @type {HTMLElement}*/ (document.querySelector(elementOrString))
|
||||
: elementOrString;
|
||||
return isElement(element) ? element : null;
|
||||
};
|
||||
|
||||
export default getElement
|
16
modules/utils/getLocale.mjs
Normal file
16
modules/utils/getLocale.mjs
Normal file
@ -0,0 +1,16 @@
|
||||
//@ts-check
|
||||
|
||||
/**
|
||||
* Retrieves the current locale. Only works if `navigator` is available.
|
||||
* Otherwise, returns the `defaultLang` passed property
|
||||
* @param {string} [defaultLang] defaults to an empty string
|
||||
* @returns The browser locale, formatted as `xx_XX`
|
||||
*/
|
||||
export const getLocale = (defaultLang = "") =>
|
||||
(typeof navigator !== "undefined" &&
|
||||
(navigator.languages ? navigator.languages[0] : navigator.language)
|
||||
.split(".")[0]
|
||||
.replace("-", "_")) ||
|
||||
defaultLang;
|
||||
|
||||
export default getLocale;
|
14
modules/utils/getRandomId.mjs
Normal file
14
modules/utils/getRandomId.mjs
Normal file
@ -0,0 +1,14 @@
|
||||
//@ts-check
|
||||
|
||||
/**
|
||||
* short random string for ids - not guaranteed to be unique
|
||||
* @see https://www.codemzy.com/blog/random-unique-id-javascript
|
||||
* @param {number} length the length of the id
|
||||
*/
|
||||
export const getRandomId = function (length = 6) {
|
||||
return Math.random()
|
||||
.toString(36)
|
||||
.substring(2, length + 2);
|
||||
};
|
||||
|
||||
export default getRandomId
|
@ -2,27 +2,34 @@
|
||||
|
||||
import { changeTitle } from "./changeTitle.mjs";
|
||||
//import { createCustomElement } from "./createCustomElement.mjs";
|
||||
import { createElementStatusModes } from "./createElementStatusModes.mjs";
|
||||
import { createTrackedResponse } from "./createTrackedResponse.mjs";
|
||||
import { decodeContentLength } from "./decodeContentLength.mjs";
|
||||
import { deferredPromise } from "./deferredPromise.mjs";
|
||||
import { documentMode } from "./documentMode.mjs";
|
||||
import { documentState } from "./documentState.mjs";
|
||||
import { elementMode } from "./elementMode.mjs";
|
||||
import { escapeRegExp } from "./escapeRegExp.mjs";
|
||||
import { fetchContentLength } from "./fetchContentLength.mjs";
|
||||
import { fetchHeaders } from "./fetchHeaders.mjs";
|
||||
import { fetchMarkdown } from "./fetchMarkdown.mjs";
|
||||
import { fetchText } from "./fetchText.mjs";
|
||||
import { generateDomFromString } from "./generateDomFromString.mjs";
|
||||
import { getAspectRatio } from "./getAspectRatio.mjs";
|
||||
import {
|
||||
getCurrentHashUrl,
|
||||
hasCurrentHashUrl,
|
||||
hasNoHashUrl,
|
||||
} from "./getCurrentHashUrl.mjs";
|
||||
import { getElement } from "./getElement.mjs";
|
||||
import { getElementByCSSSelector } from "./getElementByCSSSelector.mjs";
|
||||
import { getElementById } from "./getElementById.mjs";
|
||||
import { getFirstTitleContent } from "./getFirstTitleContent.mjs";
|
||||
import { getLocale } from "./getLocale.mjs";
|
||||
import { getPageUniqueId } from "./getPageUniqueId.mjs";
|
||||
import { getRandomId } from "./getRandomId.mjs";
|
||||
import { getReasonableUuid } from "./getReasonableUuid.mjs";
|
||||
import { identity, awaitedIdentity } from "./identity.mjs";
|
||||
import { isElement } from "./isElement.mjs";
|
||||
import { html } from "./html.mjs";
|
||||
import { isExternalUrl } from "./isExternalUrl.mjs";
|
||||
import { isLocalHost } from "./isLocalHost.mjs";
|
||||
@ -59,6 +66,7 @@ import {
|
||||
} from "./querySelectorAll.mjs";
|
||||
import { retryPromise } from "./retryPromise.mjs";
|
||||
import { rewriteLocalUrls } from "./rewriteLocalUrls.mjs";
|
||||
import { throttle } from "./throttle.mjs";
|
||||
import { today } from "./today.mjs";
|
||||
import { UnreachableCaseError } from "./UnreachableCaseError.mjs";
|
||||
import { wait } from "./wait.mjs";
|
||||
@ -66,27 +74,34 @@ import { waitIfLocalHost } from "./waitIfLocalHost.mjs";
|
||||
|
||||
export {
|
||||
changeTitle,
|
||||
createElementStatusModes,
|
||||
createTrackedResponse,
|
||||
decodeContentLength,
|
||||
deferredPromise,
|
||||
documentMode,
|
||||
elementMode as documentMode,
|
||||
documentState,
|
||||
escapeRegExp,
|
||||
fetchContentLength,
|
||||
fetchHeaders,
|
||||
fetchMarkdown,
|
||||
fetchText,
|
||||
generateDomFromString,
|
||||
getAspectRatio,
|
||||
getCurrentHashUrl,
|
||||
hasCurrentHashUrl,
|
||||
hasNoHashUrl,
|
||||
getElement,
|
||||
getElementByCSSSelector,
|
||||
getElementById,
|
||||
getFirstTitleContent,
|
||||
getLocale,
|
||||
getPageUniqueId,
|
||||
getRandomId,
|
||||
getReasonableUuid,
|
||||
html,
|
||||
identity,
|
||||
awaitedIdentity,
|
||||
isElement,
|
||||
isExternalUrl,
|
||||
isLocalHost,
|
||||
isNotNull,
|
||||
@ -118,6 +133,7 @@ export {
|
||||
querySelectorAll,
|
||||
retryPromise,
|
||||
rewriteLocalUrls,
|
||||
throttle,
|
||||
today,
|
||||
UnreachableCaseError,
|
||||
wait,
|
||||
|
12
modules/utils/isElement.mjs
Normal file
12
modules/utils/isElement.mjs
Normal file
@ -0,0 +1,12 @@
|
||||
//@ts-check
|
||||
|
||||
/**
|
||||
* Verifies an element is actually an element.
|
||||
* @param {any} element
|
||||
* @returns {element is HTMLElement}
|
||||
*/
|
||||
export const isElement = (element) => {
|
||||
return element instanceof Element || element instanceof Document;
|
||||
};
|
||||
|
||||
export default isElement
|
52
modules/utils/throttle.mjs
Normal file
52
modules/utils/throttle.mjs
Normal file
@ -0,0 +1,52 @@
|
||||
//@ts-check
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a throttled function that only invokes the provided function at most
|
||||
* once per within a given number of milliseconds.
|
||||
*
|
||||
* @template {(...args: any) => any } F
|
||||
* @template {ReturnType<F>} R
|
||||
* @template {Parameters<F>} P
|
||||
* @param {F} func
|
||||
* @param {number} [firingRateMs] Firing rate (50ms by default)
|
||||
*/
|
||||
export const throttle = (func, firingRateMs = 50) => {
|
||||
/** @type {R} */
|
||||
let lastResult;
|
||||
|
||||
/** @type {number} */
|
||||
let last = 0;
|
||||
|
||||
/** @type {null|P} */
|
||||
let funcArguments;
|
||||
|
||||
/** @type {number} */
|
||||
let timeoutID = 0;
|
||||
|
||||
const call = () => {
|
||||
timeoutID = 0;
|
||||
last = +new Date();
|
||||
lastResult = func.apply(null, funcArguments);
|
||||
funcArguments = null;
|
||||
};
|
||||
|
||||
/***
|
||||
* @param {P} args
|
||||
*/
|
||||
const throttled = (...args) => {
|
||||
funcArguments = args;
|
||||
const delta = new Date().getTime() - last;
|
||||
if (!timeoutID)
|
||||
if (delta >= firingRateMs) {
|
||||
call();
|
||||
} else {
|
||||
timeoutID = setTimeout(call, firingRateMs - delta);
|
||||
}
|
||||
return lastResult;
|
||||
};
|
||||
|
||||
return throttled;
|
||||
};
|
||||
|
||||
export default throttle
|
Loading…
Reference in New Issue
Block a user