You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/oaer/lib/components/v2dom.ts

199 lines
5.8 KiB
TypeScript

/*
* v2dom.ts
* Copyright (C) 2024 veypi <i@veypi.com>
* 2024-10-23 15:54
* Distributed under terms of the GPL license.
*/
import proxy from "./proxy"
interface buildOpts<T> {
id?: string
typ?: string
class?: string
attrs?: attrs
style?: string
innerHtml?: string
onclick?: any
children?: childTyp<T>[]
updator?: (p: HTMLElement) => void
vbind?: [Object, string]
}
const typs = ['div', 'img', 'span', 'p', 'a', 'input']
type iterChild<T> = [T[], (d: T) => HTMLElement]
type childTyp<T> = HTMLElement | iterChild<T>
type computedFn = () => any
type attrs = { [key: string]: string | number | computedFn }
export default <T>(opts: buildOpts<T> | string, inner?: string | computedFn | childTyp<T>[], attrs?: attrs) => {
if (typeof opts === 'string') {
if (typs.indexOf(opts) >= 0) {
opts = { typ: opts }
} else {
opts = { class: opts }
}
}
let dom = document.createElement(opts.typ || 'div')
if (inner) {
if (typeof inner == 'string') {
opts.innerHtml = inner
} else if (typeof inner == 'function') {
addListener(dom, () => {
let res = inner()
if (typeof res === 'string') {
dom.innerHTML = res
} else {
dom.innerHTML = ''
dom.appendChild(inner())
}
})
} else {
opts.children = inner
}
}
if (attrs) {
opts.attrs = attrs
}
if (opts.id) {
dom.id = opts.id
}
if (opts.class) {
dom.classList.add(...opts.class.split(' '))
}
if (opts.innerHtml) {
dom.innerHTML = opts.innerHtml
}
if (opts.attrs) {
for (let a in opts.attrs) {
let attr = opts.attrs[a]
if (typeof attr === 'function') {
addListener(dom, () => {
dom.setAttribute(a, attr())
})
} else {
dom.setAttribute(a, String(attr))
}
}
}
if (opts.onclick) {
dom.onclick = opts.onclick
}
if (opts.children) {
let tmpID = ''
for (let c of opts.children) {
if (Object.prototype.toString.call(c) === '[object Array]') {
const iterID = proxy.generateUniqueId()
const iterLast = tmpID
addListener(dom, () => {
c = c as iterChild<T>
let itemIDs: string[] = []
for (let i = 0; i < c[0].length; i++) {
let line = c[0][i]
//@ts-ignore
let itemID = line[proxy.DataID] || ''
let nextID = ''
if (i < c[0].length - 1) {
//@ts-ignore
nextID = c[0][i + 1][proxy.DataID] || ''
}
itemIDs.push(itemID)
let inserted = -2
let subdom: HTMLElement | null = null
let beforeChild: HTMLElement | null = null
for (let childID = 0; childID < dom.children.length; childID++) {
let child = dom.children[childID]
// console.log(`${inserted} ${childID} ${itemID} ${nextID}`)
if (child.getAttribute('vbind-iter') === iterID) {
if (child.getAttribute('vbind-proxy') === itemID) {
inserted = childID
subdom = child as HTMLElement
} else if (child.getAttribute('vbind-proxy') === nextID && nextID != '') {
// 用于交换位置
if (inserted != childID - 1) {
beforeChild = child as HTMLElement
}
}
} else if (child.getAttribute('vbind-iterlast') === iterID) {
if (inserted == -2) {
beforeChild = child as HTMLElement
}
// 找到第一个不是这个循环块的dom元素时退出
break
}
}
if (inserted == -2) {
subdom = c[1](line)
subdom.setAttribute('vbind-proxy', itemID)
subdom.setAttribute('vbind-iter', iterID)
if (iterLast != '') {
subdom.setAttribute('vbind-iterlast', iterLast)
}
if (beforeChild) {
dom.insertBefore(subdom, beforeChild)
// console.log(`${iterID} insert new`, subdom)
} else {
// console.log(`${iterID} append new`, subdom)
dom.appendChild(subdom)
}
} else if (beforeChild) {
// console.log(`${iterID} insert old`, subdom)
dom.insertBefore(subdom!, beforeChild)
} else {
// console.log(`${iterID} none`, subdom)
}
}
for (let child of dom.children) {
if (child.getAttribute('vbind-iter') === iterID && itemIDs.indexOf(child.getAttribute('vbind-proxy') || '') == -1) {
dom.removeChild(child)
}
}
})
tmpID = iterID
} else {
c = c as HTMLElement
if (tmpID != '') {
c.setAttribute('vbind-iterlast', tmpID)
}
dom.appendChild(c)
}
}
}
if (opts.style) {
const regex = /([a-zA-Z-]+)\s*:\s*([^;]+);?/g;
let match: any
while ((match = regex.exec(opts.style)) !== null) {
const key = match[1].trim();
const value = match[2].trim();
dom.style.setProperty(key, value)
}
}
dom.setAttribute('voa', '1')
if (opts.updator) {
addListener(dom, () => {
opts.updator!(dom)
})
}
if (opts.vbind && opts.typ === 'input') {
dom.setAttribute('value', Reflect.get(opts.vbind[0], opts.vbind[1]))
dom.addEventListener('input', (e) => {
// @ts-ignore
Reflect.set(opts.vbind[0], opts.vbind[1], e.target.value)
})
}
return dom
}
function addListener(dom: HTMLElement, fn: () => void) {
let fns = dom.getAttribute('vbind-fns')
let fnid = proxy.Listen(fn)
if (fns) {
fns += ',' + fnid
} else {
fns = String(fnid)
}
dom.setAttribute('vbind-fns', fns)
}