import { VNode } from 'preact' export const debounce = (func: Function, wait: number) => { let timeout return function (this: any, ...args: any[]) { let isCancelled = false clearTimeout(timeout) timeout = setTimeout(() => { timeout = null if (!isCancelled) { func.apply(this, args) } isCancelled = false }, wait) return (runInstant?: boolean) => { isCancelled = true if (runInstant) { func.apply(this, args) } } } } export const createAriaHider = () => { const originalValues: Array = [] const rootNodes: Element[] = [] document.querySelectorAll('body > *:not([role="dialog"])').forEach((node) => { const attr = node.getAttribute('aria-hidden') const alreadyHidden = attr !== null && attr !== 'false' if (alreadyHidden) { return } if (attr !== null) { originalValues.push(attr) } rootNodes.push(node) node.setAttribute('aria-hidden', 'true') }) return () => { rootNodes.forEach((node, index) => { const originalValue = originalValues[index] if (originalValue === null) { node.removeAttribute('aria-hidden') } else { node.setAttribute('aria-hidden', originalValue) } }) } } export const propIsTrue = (prop) => prop === 'true' || prop === true export const millisecondsToSeconds = (milliseconds: number) => { return Math.ceil(milliseconds / 1000) } export const microsecondsToMilliseconds = (microseconds: number) => { return Math.ceil(microseconds / 1000) } export const getTimeFromSeconds = (seconds) => { const minutes = Math.floor(seconds / 60) const secondsPartial = seconds - minutes * 60 return { minutes, seconds: secondsPartial } } export const formatBytes = (bytes, decimals = 2) => { if (bytes === 0) return '0 Bytes' const k = 1024 const dm = decimals < 0 ? 0 : decimals const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}` } export const keyCodes = { 27: 'Escape', 35: 'End', 36: 'Home', 37: 'ArrowLeft', 38: 'ArrowUp', 39: 'ArrowRight', 40: 'ArrowDown', } export const keyNames = { Escape: 'Escape', End: 'End', Home: 'Home', ArrowLeft: 'ArrowLeft', ArrowUp: 'ArrowUp', ArrowRight: 'ArrowRight', ArrowDown: 'ArrowDown', } export const getKey = (e) => (e.code ? keyNames[e.code] : keyCodes[e.keyCode]) export const focusElement = (el) => { if (el) { el.focus() } } export const runIfElementContainsOrHasFocus = (el, callback) => { if ( el && (el.contains(document.activeElement) || el === document.activeElement) ) { callback() } } // Compare two dates to decide which relative date to return export const getRelativeDate = (date, currentDate) => { const msMidnightToday = new Date(currentDate).setHours(0, 0, 0, 0) const msDate = date.getTime() const dayInMs = 86400000 if (msDate >= msMidnightToday + dayInMs) { // Future date return date } else if (msDate > msMidnightToday) { // Today return 'today' } else if (msDate > msMidnightToday - dayInMs) { // Yesterday return 'yesterday' } else { // Older date return date } } export const pick = (obj, keys) => keys.reduce((accum, key) => { if (key in obj) accum[key] = obj[key] return accum }, {}) export const omit = (obj, keys) => Object.keys(obj).reduce((accum, key) => { if (!keys.includes(key)) accum[key] = obj[key] return accum }, {}) type VDOMChild = string | number | VNode export function childIsVNode(child: VDOMChild): child is VNode { return typeof child === 'object' } export type ValueOf = T[keyof T]