Files
2024-09-01 22:30:11 -04:00

292 lines
9.0 KiB
JavaScript

const DIRECT_ATTRIBUTE_MAP = {
cellpadding: 'cellPadding',
cellspacing: 'cellSpacing',
colspan: 'colSpan',
frameborder: 'frameBorder',
height: 'height',
maxlength: 'maxLength',
nonce: 'nonce',
role: 'role',
rowspan: 'rowSpan',
type: 'type',
usemap: 'useMap',
valign: 'vAlign',
width: 'width',
}
const RGX_NUMERIC_STYLE_UNIT = 'px'
const RGX_NUMERIC_STYLE =
/^((max|min)?(width|height)|margin|padding|(margin|padding)?(left|top|bottom|right)|fontsize|borderwidth)$/i
const RGX_DEFAULT_VALUE_PROP = /input|textarea|select/i
function localAssertNotFalsy(input, errorMsg = `Input is not of type.`) {
if (input == null) {
throw new Error(errorMsg)
}
return input
}
const RGX_STRING_VALID = '[a-z0-9_-]'
const RGX_TAG = new RegExp(`^([a-z]${RGX_STRING_VALID}*)(\\.|\\[|\\#|$)`, 'i')
const RGX_ATTR_ID = new RegExp(`#(${RGX_STRING_VALID}+)`, 'gi')
const RGX_ATTR_CLASS = new RegExp(`(^|\\S)\\.([a-z0-9_\\-\\.]+)`, 'gi')
const RGX_STRING_CONTENT_TO_SQUARES = '(.*?)(\\[|\\])'
const RGX_ATTRS_MAYBE_OPEN = new RegExp(`\\[${RGX_STRING_CONTENT_TO_SQUARES}`, 'gi')
const RGX_ATTRS_FOLLOW_OPEN = new RegExp(`^${RGX_STRING_CONTENT_TO_SQUARES}`, 'gi')
export function query(selectors, parent = document) {
return Array.from(parent.querySelectorAll(selectors)).filter((n) => !!n)
}
export function queryOne(selectors, parent = document) {
var _a
return (_a = parent.querySelector(selectors)) !== null && _a !== void 0 ? _a : null
}
export function createText(text) {
return document.createTextNode(text)
}
export function getClosestOrSelf(element, query) {
const el = element
return (
((el === null || el === void 0 ? void 0 : el.closest) &&
((el.matches(query) && el) || el.closest(query))) ||
null
)
}
export function createElement(selectorOrMarkup, attrs) {
const frag = getHtmlFragment(selectorOrMarkup)
let element = frag === null || frag === void 0 ? void 0 : frag.firstElementChild
let selector = ''
if (!element) {
selector = selectorOrMarkup.replace(/[\r\n]\s*/g, '')
const tag = getSelectorTag(selector) || 'div'
element = document.createElement(tag)
selector = selector.replace(RGX_TAG, '$2')
selector = selector.replace(RGX_ATTR_ID, '[id="$1"]')
selector = selector.replace(
RGX_ATTR_CLASS,
(match, p1, p2) => `${p1}[class="${p2.replace(/\./g, ' ')}"]`,
)
}
const selectorAttrs = getSelectorAttributes(selector)
if (selectorAttrs) {
for (const attr of selectorAttrs) {
let matches = attr.substring(1, attr.length - 1).split('=')
let key = localAssertNotFalsy(matches.shift())
let value = matches.join('=')
if (value === undefined) {
setAttribute(element, key, true)
} else {
value = value.replace(/^['"](.*)['"]$/, '$1')
setAttribute(element, key, value)
}
}
}
if (attrs) {
setAttributes(element, attrs)
}
return element
}
function getSelectorTag(str) {
return tryMatch(str, RGX_TAG)
}
function getSelectorAttributes(selector) {
RGX_ATTRS_MAYBE_OPEN.lastIndex = 0
let attrs = []
let result
while ((result = RGX_ATTRS_MAYBE_OPEN.exec(selector))) {
let attr = result[0]
if (attr.endsWith(']')) {
attrs.push(attr)
} else {
attr =
result[0] + getOpenAttributesRecursive(selector.substr(RGX_ATTRS_MAYBE_OPEN.lastIndex), 2)
RGX_ATTRS_MAYBE_OPEN.lastIndex += attr.length - result[0].length
attrs.push(attr)
}
}
return attrs
}
function getOpenAttributesRecursive(selectorSubstring, openCount) {
let matches = selectorSubstring.match(RGX_ATTRS_FOLLOW_OPEN)
let result = ''
if (matches && matches.length) {
result = matches[0]
openCount += result.endsWith(']') ? -1 : 1
if (openCount > 0) {
result += getOpenAttributesRecursive(selectorSubstring.substr(result.length), openCount)
}
}
return result
}
function tryMatch(str, rgx, index = 1) {
var _a
let found = ''
try {
found = ((_a = str.match(rgx)) === null || _a === void 0 ? void 0 : _a[index]) || ''
} catch (e) {
found = ''
}
return found
}
export function setAttributes(element, data) {
let attr
for (attr in data) {
if (data.hasOwnProperty(attr)) {
setAttribute(element, attr, data[attr])
}
}
}
function getHtmlFragment(value) {
if (value.match(/^\s*<.*?>[\s\S]*<\/[a-z0-9]+>\s*$/)) {
return document.createRange().createContextualFragment(value.trim())
}
return null
}
function getChild(value) {
if (value instanceof Node) {
return value
}
if (typeof value === 'string') {
let child = getHtmlFragment(value)
if (child) {
return child
}
if (getSelectorTag(value)) {
return createElement(value)
}
return createText(value)
}
if (value && typeof value.toElement === 'function') {
return value.toElement()
}
return null
}
export function setAttribute(element, attribute, value) {
let isRemoving = value == null
if (attribute === 'default') {
attribute = RGX_DEFAULT_VALUE_PROP.test(element.nodeName) ? 'value' : 'text'
}
if (attribute === 'text') {
empty(element).appendChild(createText(value != null ? String(value) : ''))
} else if (attribute === 'html') {
empty(element).innerHTML += value != null ? String(value) : ''
} else if (attribute == 'style') {
if (typeof value === 'string') {
element.style.cssText = isRemoving ? '' : value != null ? String(value) : ''
} else {
for (const [styleKey, styleValue] of Object.entries(value)) {
element.style[styleKey] = styleValue
}
}
} else if (attribute == 'events') {
for (const [key, fn] of Object.entries(value)) {
addEvent(element, key, fn)
}
} else if (attribute === 'parent') {
value.appendChild(element)
} else if (attribute === 'child' || attribute === 'children') {
if (typeof value === 'string' && /^\[[^\[\]]+\]$/.test(value)) {
const parseable = value.replace(/^\[([^\[\]]+)\]$/, '["$1"]').replace(/,/g, '","')
try {
const parsed = JSON.parse(parseable)
value = parsed
} catch (e) {
console.error(e)
}
}
if (attribute === 'children') {
empty(element)
}
let children = value instanceof Array ? value : [value]
for (let child of children) {
child = getChild(child)
if (child instanceof Node) {
if (element instanceof HTMLTemplateElement) {
element.content.appendChild(child)
} else {
element.appendChild(child)
}
}
}
} else if (attribute == 'for') {
element.htmlFor = value != null ? String(value) : ''
if (isRemoving) {
element.removeAttribute('for')
}
} else if (attribute === 'class' || attribute === 'className' || attribute === 'classes') {
element.className = isRemoving ? '' : Array.isArray(value) ? value.join(' ') : String(value)
} else if (attribute === 'dataset') {
if (typeof value !== 'object') {
console.error('Expecting an object for dataset')
return
}
for (const [key, val] of Object.entries(value)) {
element.dataset[key] = String(val)
}
} else if (attribute == 'onclick' && typeof value === 'function') {
element.addEventListener('click', value)
} else if (['checked', 'disabled', 'readonly', 'required', 'selected'].includes(attribute)) {
element[attribute] = !!value
if (!value) {
element.removeAttribute(attribute)
} else {
element.setAttribute(attribute, attribute)
}
} else if (DIRECT_ATTRIBUTE_MAP.hasOwnProperty(attribute)) {
if (isRemoving) {
element.removeAttribute(DIRECT_ATTRIBUTE_MAP[attribute])
} else {
element.setAttribute(DIRECT_ATTRIBUTE_MAP[attribute], String(value))
}
} else if (isRemoving) {
element.removeAttribute(attribute)
} else {
let oldVal = element.getAttribute(attribute)
if (oldVal !== value) {
element.setAttribute(attribute, String(value))
}
}
}
function addEvent(element, key, fn) {
element.addEventListener(key, fn)
}
function setStyles(element, styles = null) {
if (styles) {
for (let name in styles) {
setStyle(element, name, styles[name])
}
}
return element
}
function setStyle(element, name, value) {
name = name.indexOf('float') > -1 ? 'cssFloat' : name
if (name.indexOf('-') != -1) {
name = name.replace(/-\D/g, (match) => {
return match.charAt(1).toUpperCase()
})
}
if (value == String(Number(value)) && RGX_NUMERIC_STYLE.test(name)) {
value = value + RGX_NUMERIC_STYLE_UNIT
}
if (name === 'display' && typeof value !== 'string') {
value = !!value ? null : 'none'
}
element.style[name] = value === null ? null : String(value)
return element
}
export function empty(element) {
while (element.firstChild) {
element.removeChild(element.firstChild)
}
return element
}
export function appendChildren(el, children) {
children = !Array.isArray(children) ? [children] : children
for (let child of children) {
child = getChild(child)
if (child instanceof Node) {
if (el instanceof HTMLTemplateElement) {
el.content.appendChild(child)
} else {
el.appendChild(child)
}
}
}
}