flyonui
Version:
The easiest, free and open-source Tailwind CSS component library with semantic classes.
239 lines (189 loc) • 6.17 kB
text/typescript
/*
* @version: 3.2.2
* @author: Preline Labs Ltd.
* @license: Licensed under MIT and Preline UI Fair Use License (https://preline.co/docs/license.html)
* Copyright 2024 Preline Labs Ltd.
*/
const stringToBoolean = (string: string): boolean => {
return string === 'true' ? true : false
}
const getClassProperty = (el: HTMLElement, prop: string, val = '') => {
return (window.getComputedStyle(el).getPropertyValue(prop) || val).replace(' ', '')
}
const getClassPropertyAlt = (el: HTMLElement, prop?: string, val: string = '') => {
let targetClass = ''
el.classList.forEach(c => {
if (c.includes(prop)) {
targetClass = c
}
})
return targetClass.match(/:(.*)]/) ? targetClass.match(/:(.*)]/)[1] : val
}
const getZIndex = (el: HTMLElement) => {
const computedStyle = window.getComputedStyle(el)
const zIndex = computedStyle.getPropertyValue('z-index')
return zIndex
}
const getHighestZIndex = (arr: HTMLElement[]) => {
let highestZIndex = Number.NEGATIVE_INFINITY
arr.forEach(el => {
let zIndex: string | number = getZIndex(el)
if (zIndex !== 'auto') {
zIndex = parseInt(zIndex, 10)
if (zIndex > highestZIndex) highestZIndex = zIndex
}
})
return highestZIndex
}
const isDirectChild = (parent: Element, child: HTMLElement) => {
const children = parent.children
for (let i = 0; i < children.length; i++) {
if (children[i] === child) return true
}
return false
}
const isEnoughSpace = (
el: HTMLElement,
toggle: HTMLElement,
preferredPosition: 'top' | 'bottom' | 'auto' = 'auto',
space = 10,
wrapper: HTMLElement | null = null
) => {
const referenceRect = toggle.getBoundingClientRect()
const wrapperRect = wrapper ? wrapper.getBoundingClientRect() : null
const viewportHeight = window.innerHeight
const spaceAbove = wrapperRect ? referenceRect.top - wrapperRect.top : referenceRect.top
const spaceBelow = (wrapper ? wrapperRect.bottom : viewportHeight) - referenceRect.bottom
const minimumSpaceRequired = el.clientHeight + space
if (preferredPosition === 'bottom') {
return spaceBelow >= minimumSpaceRequired
} else if (preferredPosition === 'top') {
return spaceAbove >= minimumSpaceRequired
} else {
return spaceAbove >= minimumSpaceRequired || spaceBelow >= minimumSpaceRequired
}
}
const isFocused = (target: HTMLElement) => {
return document.activeElement === target
}
const isFormElement = (target: HTMLElement) => {
return (
target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement
)
}
const isIOS = () => {
if (/iPad|iPhone|iPod/.test(navigator.platform)) {
return true
} else {
return navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform)
}
}
const isIpadOS = () => {
return navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform)
}
const isJson = (str: string) => {
if (typeof str !== 'string') return false
const firstChar = str.trim()[0]
const lastChar = str.trim().slice(-1)
if ((firstChar === '{' && lastChar === '}') || (firstChar === '[' && lastChar === ']')) {
try {
JSON.parse(str)
return true
} catch {
return false
}
}
return false
}
const isParentOrElementHidden = (element: any): any => {
if (!element) return false
const computedStyle = window.getComputedStyle(element)
if (computedStyle.display === 'none') return true
return isParentOrElementHidden(element.parentElement)
}
const isScrollable = (el: HTMLElement) => {
const style = window.getComputedStyle(el)
const overflowY = style.overflowY
const overflowX = style.overflowX
const canScrollVertically = (overflowY === 'scroll' || overflowY === 'auto') && el.scrollHeight > el.clientHeight
const canScrollHorizontally = (overflowX === 'scroll' || overflowX === 'auto') && el.scrollWidth > el.clientWidth
return canScrollVertically || canScrollHorizontally
}
const debounce = (func: Function, timeout = 200) => {
let timer: any
return (...args: any[]) => {
clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, timeout)
}
}
const dispatch = (evt: string, element: any, payload: any = null) => {
const event = new CustomEvent(evt, {
detail: { payload },
bubbles: true,
cancelable: true,
composed: false
})
element.dispatchEvent(event)
}
const afterTransition = (el: HTMLElement, callback: Function) => {
const handleEvent = () => {
callback()
el.removeEventListener('transitionend', handleEvent, true)
}
const computedStyle = window.getComputedStyle(el)
const transitionDuration = computedStyle.getPropertyValue('transition-duration')
const transitionProperty = computedStyle.getPropertyValue('transition-property')
const hasTransition = transitionProperty !== 'none' && parseFloat(transitionDuration) > 0
if (hasTransition) el.addEventListener('transitionend', handleEvent, true)
else callback()
}
const htmlToElement = (html: string): HTMLElement => {
const template = document.createElement('template')
html = html.trim()
template.innerHTML = html
return template.content.firstChild as HTMLElement
}
const classToClassList = (classes: string, target: HTMLElement, splitter = ' ', action: 'add' | 'remove' = 'add') => {
const classesToArray = classes.split(splitter)
classesToArray.forEach(cl => {
if (cl.trim()) {
action === 'add' ? target.classList.add(cl) : target.classList.remove(cl)
}
})
}
const menuSearchHistory = {
historyIndex: -1,
addHistory(index: number) {
this.historyIndex = index
},
existsInHistory(index: number) {
return index > this.historyIndex
},
clearHistory() {
this.historyIndex = -1
}
}
export {
afterTransition,
classToClassList,
debounce,
dispatch,
getClassProperty,
getClassPropertyAlt,
getHighestZIndex,
getZIndex,
htmlToElement,
isDirectChild,
isEnoughSpace,
isFocused,
isFormElement,
isIOS,
isIpadOS,
isJson,
isParentOrElementHidden,
isScrollable,
menuSearchHistory,
stringToBoolean
}