Skip to content

Instantly share code, notes, and snippets.

@isocroft
Last active June 26, 2026 12:44
Show Gist options
  • Select an option

  • Save isocroft/e39c96f4eae67c431916636ad273e7fe to your computer and use it in GitHub Desktop.

Select an option

Save isocroft/e39c96f4eae67c431916636ad273e7fe to your computer and use it in GitHub Desktop.
A collection of very useful helper functions for frontend working with both React and Vanilla JS
import { lazy } from "react";
import type { JSX } from "react";
import type { UseQueryResult } from "@tanstack/react-query";
/**
* lazyWithRetry:
*
*
* @param {AsyncFunction<[], { default: () => JSX.Element }>} componentImport
* @param {String=} retryStorageKey
*
* @returns {Object}
*/
export const lazyWithRetry = <
D extends unknown,
E extends Error,
Props = {
queries: Record<string, UseQueryResult<D, E> | null>;
}
>(
componentImport: () => Promise<{
default: (props?: Props) => JSX.Element | null;
}>,
retryStorageKey = "page-has-been-force-refreshed"
) => {
return lazy<React.ComponentType<Props | undefined>>(async () => {
const pageHasAlreadyBeenForceRefreshed = JSON.parse(
window.sessionStorage.getItem(retryStorageKey) || "false"
) as boolean;
function onBeforeUnload(e: BeforeUnloadEvent) {
window.removeEventListener("beforeunload", onBeforeUnload);
window.sessionStorage.removeItem(retryStorageKey);
}
try {
/* @CHECK: https://gist.github.com/raphael-leger/4d703dea6c845788ff9eb36142374bdb#file-lazywithretry-js */
const component = await componentImport();
window.sessionStorage.setItem(retryStorageKey, "false");
return component;
} catch (error) {
if (!pageHasAlreadyBeenForceRefreshed) {
const $retryStorageKey = window.sessionStorage.getItem(retryStorageKey);
if ($retryStorageKey !== "false") {
/* @HINT: Assuming that the user is not on the latest version of the application. */
/* @HINT: Let's refresh the page immediately. */
window.sessionStorage.setItem(retryStorageKey, "true");
window.addEventListener("beforeunload", onBeforeUnload);
window.location.reload();
}
} else {
/* @HINT: If we get here, it means the page has already been reloaded */
/* @HINT: Assuming that user is already using the latest version of the application. */
/* @HINT: Let's let the application crash and raise the error. */
throw error;
}
/* @INFO: Instead of returning an empty JSX component, return a component with indeterminate spinner */
return { default: () => null };
}
});
};
/*!
* @EXAMPLE:
*
* const Settings = lazyWithRetry(() =>
* componentLoader(() => import("./pages/Settings/index"))
* );
*
* console.log(Settings); // { default: () => (<section>...</section>) }
*
*/
/**
* componentLoader:
*
*
* @param {AsyncFunction<[], { default: () => JSX.Element }>} lazyComponent
* @param {Number=} attemptsLeft
*
* @returns {Promise<*>}
*/
export function componentLoader<
D extends unknown,
E extends Error,
M = {
default: (injected?: {
queries: Record<string, UseQueryResult<D, E> | null>;
}) => JSX.Element | null;
}
>(lazyComponent: () => Promise<M>, attemptsLeft = 3) {
return new Promise<M>((resolve, reject) => {
/* @CHECK: https://medium.com/@botfather/react-loading-chunk-failed-error-88d0bb75b406 */
lazyComponent()
.then(resolve)
.catch((error) => {
window.setTimeout(() => {
if (attemptsLeft === 1) {
reject(error);
return;
}
componentLoader<D, E, M>(lazyComponent, attemptsLeft - 1).then(
resolve,
reject
);
}, 500);
});
});
}
/*!
* @EXAMPLE:
*
* const loaderPromise = componentLoader(() => import("./pages/Settings/index"))
*
* console.log(loaderPromise); // Promise{<pending>}
*
*/
/**
* verifyDOMElementIsWithinViewPort:
*
* @param {HTMLElement} element
*
* @returns {Boolean}
*/
export const verifyDOMElementIsWhollyWithinViewport = (element: HTMLElement) => {
if (!Boolean(element) || !(element instanceof HTMLElement)) {
throw new TypeError(
`verifyDOMElementIsWhollyWithinViewport(${element}): argument 1 is not a DOM element`
);
}
const rect = element.getBoundingClientRect();
const minimumYFrame = 0;
const minimumXFrame = 0;
const maximumYFrame = (window.innerHeight || document.documentElement.clientHeight);
const maximumXFrame = (window.innerWidth || document.documentElement.clientWidth);
return (
rect.top >= minimumYFrame &&
rect.left >= minimumXFrame &&
rect.bottom <= maximumYFrame &&
rect.right <= maximumXFrame
);
};
/*!
* @EXAMPLE:
*
* const isElementVisibleInViewport = verifyDOMElementIsWhollyWithinViewport(
* document.querySelector('[id="compactor"]')
* )
*
* console.log(sElementVisibleInViewport) // true
*/
/**
* verifyDOMElementIsNotWithinViewPort:
*
* @param {HTMLElement} element
*
* @returns {Boolean}
*/
export const verifyDOMElementIsNotWithinViewport = (element: HTMLElement) => {
if (!Boolean(element) || !(element instanceof HTMLElement)) {
throw new TypeError(
`verifyDOMElementIsNotWithinViewport(${element}): argument 1 is not a DOM element`
);
}
const rect = element.getBoundingClientRect();
const minimumYFrame = 0;
const minimumXFrame = 0;
const maximumYFrame = (window.innerHeight || document.documentElement.clientHeight);
const maximumXFrame = (window.innerWidth || document.documentElement.clientWidth);
return (
(rect.top < minimumYFrame &&
rect.bottom < minimumYFrame) ||
(rect.left < minimumXFrame &&
rect.right < minimumXFrame) ||
rect.y > maximumYFrame ||
rect.x > maximumXFrame
);
};
/*!
* @EXAMPLE:
*
* const isElementNotVisibleInViewport = verifyDOMElementIsNotWithinViewport(
* document.querySelector('[id="compactor"]')
* )
*
* console.log(isElementNotVisibleInViewport) // false
*/
/**
* verifyDOMElementIsPartiallyWithinViewPort:
*
* @param {HTMLElement} element
*
* @returns {Boolean}
*/
export const verifyDOMElementIsPartiallyWithinViewPort = (element: HTMLElement) => {
return !(
verifyDOMElementIsWhollyWithinViewport(element)
) && !(
verifyDOMElementIsNotWithinViewport(element)
);
};
/*!
* @EXAMPLE:
*
* const isElementPartialyVisibleInViewport = verifyDOMElementIsPartiallyWithinViewPort(
* document.querySelector('[id="compactor"]')
* )
*
* console.log(isElementPartialyVisibleInViewport) // true
*/
/**
* verifyDOMElementIsWhollyOrPartiallyWithinViewPort:
*
* @param {HTMLElement} element
*
* @returns {Boolean}
*/
export const verifyDOMElementIsWhollyOrPartiallyWithinViewPort = (element: HTMLElement) => {
return (
verifyDOMElementIsWhollyWithinViewport(element)
) && !(
verifyDOMElementIsNotWithinViewport(element)
);
};
/*!
* @EXAMPLE:
*
* const isElementPartialyOrWhollyVisibleInViewport = verifyDOMElementIsWhollyOrPartiallyWithinViewPort(
* document.querySelector('[id="compactor"]')
* )
*
* console.log(isElementPartialyOrWhollyVisibleInViewport) // true
*/
/**
* fileExtension:
*
* @param {String} urlOrFileType
*
* @returns {String}
*/
export const fileExtension = (urlOrFileType?: string | null): string => {
let extension = "blob";
if (
urlOrFileType === "image/png" ||
urlOrFileType === "image/jpeg" ||
urlOrFileType === "image/jpg" ||
urlOrFileType === "image/svg+xml" ||
urlOrFileType === "application/pdf" ||
urlOrFileType === "text/plain" ||
urlOrFileType === "application/json" ||
urlOrFileType === "text/javascript" ||
urlOrFileType === "text/css" ||
urlOrFileType === "text/csv" ||
urlOrFileType === "text/x-csv" ||
urlOrFileType === "application/vnd.ms-excel" ||
urlOrFileType === "application/csv" ||
urlOrFileType === "application/x-csv" ||
urlOrFileType === "text/comma-separated-values" ||
urlOrFileType === "text/x-comma-separated-values" ||
urlOrFileType === "text/tab-separated-values" ||
urlOrFileType === "application/octet-stream"
) {
[ extension ] = (urlOrFileType || "/").split("/").reverse();
if (extension === "octet-stream") {
extension = "blob";
}
if ([
"x-csv",
"vnd.ms-excel",
"tab-separated-values",
"comma-separated-values",
"x-comma-separated-values"].includes(extension)) {
extension = "csv";
}
} else if (typeof urlOrFileType === "string") {
const [ urlBaseName ] = urlOrFileType.split(/[#?]/);
[ extension ] = urlBaseName.split(".").reverse();
}
return extension === "javascript" ? "js" : extension;
};
/* @EXAMPLE: fileExtension("image/png") */
/**
* composeClasses:
*
* @param {Array.<*>} styles
*
* @returns {String}
*/
export const composeClasses = (...styles: unknown[]): string => {
return Array.from(new Set(styles.filter((item) => item).join(' ')));
}
/* @EXAMPLE: <AvatarWrapper className={composeClasses('text-align-center', 'position-relative')} /> */
/**
* isLocalHost:
*
* @returns {Boolean}
*
*/
export const isLocalHost = (): boolean => {
return window.location.port === ""
? ["http://localhost", "http://127.0.0.1"].includes(
window.location.origin.replace(/\:[\d$]{4,5}/, "")
)
: Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
)
};
/*!
* @EXAMPLE:
*
* const currentlyLocalHost = isLocalHost();
*
* console.log(currentlyLocalHost) // false
*
*/
/**
* isRemotsHost:
*
* @returns {Boolean}
*
*/
export const isRemoteHost = (): boolean => {
return window.location.origin.includes(process.env.REACT_APP_REMOTE_HOST);
};
/*!
* @EXAMPLE:
*
* const currentlyRemoteHost = isRemoteHost();
*
* console.log(currentlyRemoteHost) // true
*
*/
/**
* bloToDataURL:
*
* @param {Blob} blob
*
* @returns {Promise<String>}
*
*/
export const blobToDataURL = (blob: Blob): Promise<string> => {
return new Promise((fulfill: Function, reject: Function) => {
let reader: FileReader = new FileReader()
reader.onerror = (ev: ProgressEvent<FileReader>) =>
reject(ev.target?.error)
reader.onload = () => fulfill(reader.result)
reader.readAsDataURL(blob)
})
};
/*!
* @EXAMPLE:
*
* blobToDataURL(new Blob(['Hello World!'], { type: "text/plain" })).then(
* (dataURL) => {
* console.log(dataURL) // "data:text/plain,Hello%20World!"
* });
*
*
* blobToDataURL(new Blob(new Uint8Array([225, 120, 90]), { type: "image/gif" })).then(
* (dataURL) => {
* console.log(dataURL) // "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
* });
*
*/
/**
* dataURLtoObjectURL: converts a data URI to an object URL
*
*
* @param {String} dataURL
*
* @returns {String}
*
* @see https://en.wikipedia.org/wiki/Data_URI_scheme/
*/
export const dataURLtoObjectURL = (dataURL?: string): string => {
const [ mimeType, base64String ] = (dataURL || ",").split(",");
const [, contentTypeDataPrefix ] = mimeType.split(":") || [, ";"];
const [ contentType ] = contentTypeDataPrefix
? contentTypeDataPrefix.split(";")
: ["application/octet-stream"];
return URL.createObjectURL(
base64StringToBlob(base64String, contentType)
);
};
/*!
* @EXAMPLE:
*
*
* const objectURL = dataURLtoObjectURL(
* "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII="
* )
*
* console.log(objectURL) // "blob:https://coredium.com/aef24cd56bc355fea22b1"
*
*/
/**
* dataURLtoObjectBlob: converts a data URI to a blob
*
*
* @param {String} dataURL
*
* @returns {Blob}
*
* @see https://en.wikipedia.org/wiki/Data_URI_scheme/
* @see https://en.wikipedia.org/wiki/Binary_large_object/
*/
export const dataURLtoObjectBlob = (dataURL?: string): Blob => {
const [ mimeType, base64String ] = (dataURL || ",").split(",");
const [, contentTypeDataPrefix ] = mimeType.split(":") || [, ";"];
const [ contentType ] = contentTypeDataPrefix
? contentTypeDataPrefix.split(";")
: ["application/octet-stream"];
return base64StringToBlob(base64String, contentType);
};
/*!
* @EXAMPLE:
*
* const fileBlob = dataURItoObjectBlob(
* "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII="
* );
*
* console.log(fileBlob); // Blob {size: 68, type: 'image/png'}
*/
/**
* blobToFile:
*
* @param {Blob | Undefined} theBlob
* @param {String | Null | Undefined} fileName
* @param {Boolean} useCast
*
* @returns {File}
*
*/
export const blobToFile = (theBlob?: Blob, fileName?: string | null, useCast = false): File => {
const todaysDate = new Date();
const defaultFileName = `${todaysDate.getTime()}_${Math.random() * 1}`;
const defaultFileExtension = `.${fileExtension(theBlob?.type)}`;
const fullFileName = defaultFileName + defaultFileExtension;
if (!(theBlob instanceof window.Blob)) {
return new File([""], fullFileName);
}
const blob = <Blob & { lastModifiedDate: Date, name: string }>theBlob;
blob.lastModifiedDate = new Date();
blob.name = fileName || fullFileName;
return useCast ? <File>theBlob : new File([theBlob], fileName || fullFileName);
};
/*!
* @EXAMPLE:
*
*
* const file = blobToFile(new Blob(['hello!'], { type: 'text/plain' }), "text.txt");
*
* console.log(file) // File: {}
*
*/
/**
* base64StringToBlob:
*
* @param {String} base64Data
* @param {String} contentType
* @param {Number} sliceSize
*
* @returns {Blob}
*
*/
export const base64StringToBlob = (base64Data: string, contentType?: string | null, sliceSize = 512) => {
const $contentType = contentType || "";
const byteCharacters = atob(base64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
return new Blob(byteArrays, { type: $contentType });
};
/*!
* @EXAMPLE:
*
* const urlString = blobToDataURL(new Blob(['hello world'], { type: 'text/plain' }));
*
* console.log(urlString) //
*
*/
/**
* getJpegBlob:
*
* @param {HTMLCanvasElement | null} canvas
*
* @returns {Promise<Blob | null>}
*
*/
export function getJpegBlob(canvas: HTMLCanvasElement | null): Promise<Blob | null> {
/* @CHECK: https://stackoverflow.com/a/46182044/5221762 */
/* @NOTE: May require the `toBlob()` polyfill */
if (!HTMLCanvasElement.prototype.toBlob) {
window.Object!.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function (callback, type, quality) {
const canvas = this;
window.setTimeout(function() {
var binStr = atob( canvas.toDataURL(type, quality).split(',')[1] ),
len = binStr.length,
arr = new Uint8Array(len);
for (let index = 0; index < len; index++ ) {
arr[index] = binStr.charCodeAt(index);
}
callback( new Blob( [arr], {type: type || 'image/png'} ) );
}, 0);
}
});
}
return new Promise((resolve, reject) => {
try {
if (canvas) {
canvas.toBlob(blob => resolve(blob), 'image/jpeg', 0.95);
}
} catch (e) {
reject(e);
}
})
};
/*!
* @EXAMPLE:
*
* const blob = await getJpegBlob(window.document.getElementsByTagName('canvas')[0]);
*
* console.log(blob) // Blob: {}
*
*/
/**
* getJpegBytes:
*
* @param {HTMLCanvasElement | null} canvas
*
* @returns {Promise<string | ArrayBuffer | null>}
*
*/
export function getJpegBytes(canvas: HTMLCanvasElement | null): Promise<string | ArrayBuffer | null> {
return getJpegBlob(canvas).then((blob) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader()
fileReader.addEventListener('loadend', () => {
if (this.error) {
reject(this.error)
return;
}
resolve(this.result)
})
if (blob) {
fileReader.readAsArrayBuffer(blob);
}
})
})
};
/*!
* @EXAMPLE:
*
* const bytes = await getJpegBytes(window.document.getElementsByTagName('canvas')[0]);
*
* console.log(bytes); //
*
*/
/**
* isBase64String:
*
* @param {String} base64String
*
* @returns {Boolean}
*
*/
export const isBase64String = (base64String: string): boolean => {
let base64Regex = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/;
return typeof base64String !== 'string'
? false
: (base64String.length % 4 === 0) && base64Regex.test(base64String)
};
/*!
* @EXAMPLE:
*
* const isStringified = isBase64String("");
*
* console.log(isStringified) //
*
*/
/**
* getEmbedUrl:
*
* @param {String} url
* @param {Boolean} autoPlay
*
*
* @returns {String}
*
*/
export const getEmbedUrl = (url: string, autoPlay = false): string => {
if (!(Boolean(url)) || typeof url !== "string") {
throw new TypeError(
`getEmbedUrl("${url}", ${autoPlay}): argument 1 is not a string`
);
}
const $autoPlay = typeof autoPlay === "boolean" ? autoPlay : Boolean(autoPlay);
try {
const parsedUrl = new URL(url);
const domain = parsedUrl.hostname.replace('www.', '').replace('m.', '');
let videoId = '';
if (domain === 'youtube.com' || domain === 'youtu.be') {
if (parsedUrl.pathname.includes('/embed/')) {
return `${url}${$autoPlay ? '?&autoplay=1&mute=1' : ''}`;
}
if (parsedUrl.pathname.includes('/watch')) {
videoId = parsedUrl.searchParams.get('v') || '';
if (videoId === '') throw new SyntaxError("`videoId` is an empty string");
return `https://www.youtube.com/embed/${videoId}${$autoPlay ? '?&autoplay=1&mute=1' : ''}`;
}
if (domain === 'youtu.be') {
videoId = parsedUrl.pathname.replace('/', '');
if (videoId === '') throw new SyntaxError("`videoId` is an empty string");
return `https://www.youtube-nocookie.com/embed/${videoId}${$autoPlay ? '?&autoplay=1&mute=1' : ''}`;
}
} else if (domain === 'vimeo.com' || domain === 'player.vimeo.com') {
if (parsedUrl.pathname.includes('/video/')) {
return `${url}${$autoPlay ? '?&autoplay=1&muted=1&dnt=1' : ''}`;
}
videoId = parsedUrl.pathname.replace('/', '');
if (videoId === '') throw new SyntaxError("`videoId` is an empty string");
return `https://player.vimeo.com/video/${videoId}${$autoPlay ? '?&autoplay=1&muted=1&dnt=1' : ''}`;
}
} catch (error) {
const message = error instanceof SyntaxError
? "getEmbedUrl(...): Cannot create embed URL"
: "getEmbedUrl(...): possibly invalid URL as argument";
throw new TypeError(
message,
{ cause: error }
);
}
return '';
};
/*!
* @EXAMPLE:
*
* const embedURL = getEmbedUrl("https://youtube.com/watch?v=9JLpWR_yHZQ", true);
*
* console.log(embedURL) // "https://www.youtube-nocookie.com/embed/9JLpWR_yHZQ"
*
*/
/**
* sleepFor:
*
* @param {Number} durationInMilliSeconds
*
* @returns {void}
*
*/
export const sleepFor = (durationInMilliSeconds = 10) => {
return new Promise(
(resolve) => window.setTimeout(resolve, durationInMilliSeconds)
);
};
/*!
* @EXAMPLE:
*
* const promise = await sleepFor(2500);
*
* console.log(promise) // Promise {<pending>}
*
*/
/**
* waitFor:
*
*
* @param {Function} conditionCallback
* @param {Number} pollIntervalMilliSeconds
* @param {Number} timeoutAfterMilliSeconds
*
* @returns {Void}
*
* @see https://davidwalsh.name/waitfor/
*/
export const waitFor = async (
conditionCallback: () => boolean,
pollIntervalMilliSeconds = 50,
timeoutAfterMilliSeconds = 3000
) => {
const startTimeMilliSeconds = Date.now();
while (true) {
if (
typeof (timeoutAfterMilliSeconds) === "number"
&& Date.now() > startTimeMilliSeconds + timeoutAfterMilliSeconds) {
throw new Error("Condition not met bbefore timeout");
}
const result = conditionCallback();
if (result) {
return result;
}
await sleepFor(pollIntervalMilliSeconds);
}
};
/*!
* @EXAMPLE:
*
* const aFunction = () => {
* await waitFor(() => window.document.body.classList.has('loaded'), 100, 5000)
* };
*
*/
/**
* htmlEncode:
*
*
* @param {String} rawText
*
* @returns {String}
*
*/
export const htmlEncode = (rawText: string): string => {
if (typeof rawText !== "string") {
throw new TypeError(
`htmlEncode("${rawText}"): argument 1 is not a string`
);
}
return (rawText || "").replace(/[\u00A0-\u9999<>&]/gim, function (mark: string) {
return '&#' + mark.charCodeAt(0) + ';'
})
};
/*!
* @EXAMPLE:
*
* const encodedHTML = htmlEncode('<h1><img onerror="javascript:return null" /></h1>');
*
* console.log(encodedHTML); // ""
*
*/
/**
* htmlDecode:
*
*
* @param {String} encodedText
*
* @returns {String | Null}
*
*/
export const htmlDecode = (encodedText: string): string | null => {
if (typeof encodedText !== "string") {
throw new TypeError(
`htmlDecode("${encodedText}"): argument 1 is not a string`
);
}
const doc = new window.DOMParser().parseFromString(encodedText || "&nbsp;", 'text/html')
const docElem = doc.documentElement as Node
return docElem.textContent
};
/*!
* @EXAMPLE:
*
* const decodedHTML = htmlDecode("&lt;h1&gt;Hi there!&lt;/h1&gt;");
*
* console.log(decodedHTML); // "<h1>Hi there!</h1>"
*
*/
/**
* detectFullScreenTrigger:
*
*
* @param {Event} event
*
* @returns {"user-manual" | "programmatic" | "unknown"}
*
*/
export const detectFullScreenTrigger = (event: Event): string => {
if (
window.matchMedia &&
window.matchMedia('(display-mode: fullscreen)').matches
) {
// page entered fullscreen mode through the Web Application Manifest
return 'user-manual'
} else if (document.fullscreenEnabled && document.fullscreenElement) {
// page entered fullscreen mode through the Fullscreen API
return 'programmatic'
}
return 'unknown'
};
/* @EXAMPLE: document.onfullscreenchange = detectFullScreenTrigger; */
/**
* detectAppleIOS:
*
*
* @returns {Boolean}
*
*/
export const detectAppleIOS = (): boolean => {
const global: Window = window
const navigator: Navigator = global.navigator
const userAgent = navigator.userAgent.toLowerCase()
const vendor = navigator.vendor.toLowerCase()
return /iphone|ipad|ipod/.test(userAgent) && vendor.indexOf('apple') > -1
}
/* @EXAMPLE: const isIOS = detectAppleIOS() */
/**
* isInStandaloneMode:
*
*
* @returns {Boolean}
*
*/
export const isInStandaloneMode = (): boolean => {
const global: Window = window
const navigator: Navigator = global.navigator
const location: Location = global.location
/**
* @CHECK: https://stackoverflow.com/questions/21125337/how-to-detect-if-web-app-running-standalone-on-chrome-mobile
*/
if (detectAppleIOS() && navigator instanceof Navigator) {
return navigator.standalone === true
}
return (
location.search.indexOf('standalone=true') !== -1 &&
Boolean(global.matchMedia('(display-mode: standalone)').matches) &&
(global.screen.height - document.documentElement.clientHeight < 40 ||
global.screen.width - document.documentElement.clientHeight < 40)
)
};
/* @EXAMPLE: const standalone = isInStandaloneMode(); */
/**
* formatHTMLEntity:
*
*
* @param {String} textValue
* @param {String} entityHexValue
* @param {String} prefix
*
* @returns {String}
*
*/
export const formatHTMLEntity = (
textValue: string,
entityHexVal: string,
prefix: string = ''
): string => {
if (typeof textValue !== "string") {
throw new TypeError(
`formatHTMLEntity("${textValue}", "${entityHexValue}", "${prefix}"): argument 1 is not a string`
);
}
if (typeof entityHexValue !== "string") {
throw new TypeError(
`formatHTMLEntity("${textValue}", "${entityHexValue}", "${prefix}"): argument 2 is not a string`
);
}
if (typeof prefix !== "string") {
throw new TypeError(
`formatHTMLEntity("${textValue}", "${entityHexValue}", "${prefix}"): argument 3 is not a string`
);
}
const isNumeric = /^\d{2,5}$/.test(entityHexValue)
const number = parseInt(isNumeric ? "8" : entityHexValue, 16)
return (
(textValue ? textValue + ' ' : '') +
prefix + String.fromCharCode(number)
)
};
/* @EXAMPLE: <p className="wrapper">{formatHTMLEntity('View Full Project', '279D')}</p> */
/**
* isEmpty:
*
* @param {Object} objectValue
*
* @returns {Boolean}
*/
export function isEmpty<T>(objectValue: T): boolean {
if(!objectValue || typeof objectValue !== "object") {
return true;
}
for(const prop in objectValue) {
if(Object.prototype.hasOwnProperty.call(objectValue, prop)) {
return false;
}
}
return JSON.stringify(objectValue) === JSON.stringify({});
}
/* @EXAMPLE: isEmpty({}) */
/**
* slugify:
*
*
* @param {String} plainText
* @param {String} delimeter
*
* @returns {String}
*
*/
export const slugify = (plainText: string, delimeter = "_") => {
return (plainText || "")
.toString()
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toLowerCase()
.trim()
.replace(/[^a-z0-9 ]/g, "")
.replace(/\s+/g, delimeter);
};
/*!
* @EXAMPLE:
*
* const slugifiedText = slugify('Last Name');
*
* console.log(slugifiedText); // "last_name"
*
*/
/**
* unSlugifyText:
*
* @param {String} slugifiedText
* @param {String} delimeter
* @param {Boolean} shouldTrim
*
* @returns {String}
*
*/
export const unSlugifyText = (
slugifiedText: string,
delimeter = '_',
shouldTrim = false
): string => {
if (typeof text !== "string") {
throw new TypeError(
`unSlugifyText("${slugifiedText}", "${delimeter}", ${shouldTrim}): argument 1 is not a string`);
}
let $separator = separator;
if (typeof $separator !== "string") {
throw new TypeError(
`unSlugifyText("${slugifiedText}", "${delimeter}", ${shouldTrim}): argument 2 is not a string`
);
}
try {
return (slugifiedText || '')
.split(delimeter)
.map(
(slugPart) =>
`${slugPart.charAt(0).toUpperCase()}${slugPart.substring(1)}`
).join(Boolean(shouldTrim) ? '' : ' ')
} catch (error) {
throw new TypeError(
`unSlugifyText("${slugifiedText}", "${delimeter}", ${shouldTrim}): cannot return unslugify input string`,
{ cause: error }
);
}
};
/*!
* @EXAMPLE:
*
* const text = unSlugifyText('first_name');
*
* console.log(text); // "First_Name"
*
*/
/**
* getOrdinalSuffixForNumber:
*
*
* @param {Number} ordinal
* @param {Boolean} asWord
*
* @returns {String}
*
*/
export const getOrdinalSuffixForNumber = (ordinal: number, asWord = false): string => {
let ord = "th";
if (ordinal % 10 == 1 && ordinal % 100 != 11) {
ord = "st";
}
else if (ordinal % 10 == 2 && ordinal % 100 != 12) {
ord = asWord ? "ond" : "nd";
}
else if (ordinal % 10 == 3 && ordinal % 100 != 13) {
ord = "rd";
}
return ord;
};
/*!
* @EXAMPLE:
*
* const ordinalSuffix = getOrdinalSuffixForNumber(23);
*
* console.log(ordinalWithSuffix) // "rd"
*
* console.log(23 + ordinalWithSuffix) // "23rd"
*
*/
/**
* getShortSuffixForAmount:
*
* @param {Number} amount
*
* @returns {String}
*
*/
export const getShortSuffixForAmount = (amount: number): string => {
const strFigure = String(Number.isNaN(amount) ? false : Math.round(amount))
const [firstPart, ...remainingParts] = strFigure.match(
/\d{1,3}(?=(\d{3})*$)/g
) || ['']
const shortenedMap: { [key: number]: string } = {
1: 'K',
2: 'M',
3: 'B',
4: 'T',
5: 'Z',
}
return firstPart !== '' && remainingParts.length
? firstPart + shortenedMap[remainingParts.length]
: firstPart
};
/*!
* @EXAMPLE:
*
* const amountWithSuffix = getShortSuffixForAmount(305000);
*
* console.log(amountWithSuffix) // "305K"
*
*/
/**
* validateUIDString:
*
* @param {String} uidLineText
*
* @returns {Boolean}
*
*/
export const validateUIDString = (uidLineText: string): boolean => {
const uuidRegExp = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
const guidRegExp = /^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$/gi
let uid = uidLineText;
if (uidLineText.startsWith('{')) {
uid = uidLineText.substring(1, uidLineText.length - 1);
}
return uuidRegExp.test(uid) || guidRegExp.test(uid)
}
/*!
* @EXAMPLE:
*
* const isValidUUID = validateUIDString('a4caeacc-72cb-4824-80f8-b55961f148c6');
*
* console.log(isValidUUID); // true
*
*/
/**
* validateWebPageURL:
*
* @param {String}
urlString *
* @returns {Boolean}
*
*/
export const validateWebPageURL = (urlString: string): boolean => {
let result = null;
try {
result = urlString.match(
/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z0-9]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g
)
} catch (e) {
result = null
}
return result !== null;
}
/*!
* @EXAMPLE:
*
* const isWebPageURL = validateWebPageURL('https://www.example.com?id=98747904');
*
* console.log(isWebPageURL)); // true
*
*/
@isocroft

isocroft commented Apr 29, 2026

Copy link
Copy Markdown
Author

More from repo: // https://github.com/aardrian/youtube-vimeo-embed

import { getEmbedUrl } from "./extras.ts"

class YouTubeVimeoEmbed extends HTMLElement {
	constructor() {
		super();
	}
	connectedCallback() {
		var iframe = document.createElement('iframe');
		var theLink = this.querySelector('a');
		var vidPoster = this.getAttribute('data-poster');
		var vidSlug;
		var vidPreSlug = theLink.getAttribute('href').split('/')[3];
		var vidQueryString = '';
		var vidQueryCheck = vidPreSlug.indexOf('?');
		if (vidQueryCheck == -1) {
			vidSlug = vidPreSlug;
		} else {
			vidSlug = vidPreSlug.substring(0,vidQueryCheck);
			vidQueryString = '&'+vidPreSlug.substring(vidQueryCheck+1);
		}
		if (theLink.getAttribute('href').substring(8,13) == 'youtu') {
			var isYouTube = 1;
		} else {
			var isYouTube = 0;
		}
		iframe.setAttribute('title', theLink.textContent);
		iframe.setAttribute('allow', 'autoplay');
		iframe.setAttribute('allowfullscreen', '');
		iframe.setAttribute('loading', 'lazy');
		
		if (isYouTube) {
			iframe.setAttribute('src','https://www.youtube-nocookie.com/embed/'+vidSlug);
			var vidLink = getEmbedUrl(theLink.getAttribute('href'), true)+vidQueryString;
			if (vidPoster) {
				var bgImg = vidPoster;
			} else {
				var bgImg = 'https://i3.ytimg.com/vi/'+vidSlug+'/hqdefault.jpg';
			}
		} else {
			iframe.setAttribute('src','https://vimeo.com/'+vidSlug);
			var vidLink = getEmbedUrl(theLink.getAttribute('href'), true)+vidQueryString;
			if (vidPoster) {
			  var bgImg = vidPoster;
			} else {
			  var bgImg = 'https://vumbnail.com/'+vidSlug+'.jpg';
			}
		}
		if (this.getAttribute('data-ratio')) {
			iframe.setAttribute('style','aspect-ratio:'+this.getAttribute('data-ratio')+';width:100%;');
		} else {
			iframe.setAttribute('style','aspect-ratio:16/9;width:100%;');
		}
		iframe.setAttribute('srcdoc','<style>body{background-image:url('+bgImg+');background-repeat:no-repeat;background-size:cover;background-position:center center;display:grid;place-items:center;min-height:97dvh;overflow:hidden;}a{display:block;width:96px;height:96px;overflow:hidden;}a:focus{outline:none;}a:focus circle,a:hover circle{fill:#000;}a:focus circle:first-child + circle,a:hover circle:first-child + circle{stroke-dasharray:.4,.4;}a:focus polygon,a:hover polygon{stroke:#fff;stroke-width:.75;}</style><a href="'+vidLink+'"><svg viewBox="0 0 16 16" width="96" height="96" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><circle cx="50%" cy="50%" r="7.75" fill="none" stroke="#000" stroke-width=".5"/><circle cx="50%" cy="50%" r="7.25" fill="none" stroke="#fff" stroke-width=".5"/><circle cx="50%" cy="50%" r="7" fill="#0009"/><polygon points="12, 8 6, 4.5 6, 11.5" fill="#fff" stroke-linejoin="round"></polygon></svg>Play</a>');
		this.appendChild(iframe);
	}
}
window.customElements.define('youtube-vimeo-embed', YouTubeVimeoEmbed);

@isocroft

Copy link
Copy Markdown
Author

Example code on how to use on a web page to lazy load YouTube/Vimeo videos

<youtube-vimeo-embed data-poster="https://adrianroselli.com/wp-content/uploads/2024/06/web-components-logo.png" data-ratio="1/1">
  <p>
   <a href="https://youtu.be/Sq5oiHjwFxI?start=14">Kevin Powell: Creating Web Components with Dave Rupert</a>, lasting over an hour and a half.
  </p>
</youtube-vimeo-embed>

Next

<youtube-vimeo-embed>
  <p>
    YouTube: <a href="https://youtu.be/PLXAuxZKKjs?start=31">Overlays Underwhelm at WordPress Accessibility Day</a>, 46:48
  </p>
</youtube-vimeo-embed>

Next

<youtube-vimeo-embed data-ratio="21/9">
  <p>
    YouTube: <a href="https://youtu.be/DsoZI4TLqMc">The Boy and the Heron trailer</a>, 1:50
  </p>
</youtube-vimeo-embed>

See more here: https://adrianroselli.com/2024/06/youtube-and-vimeo-web-component.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment