mirror of https://github.com/veypi/OneAuth.git
feat: v2dom
parent
713f352bc8
commit
4904d1cc76
@ -1,200 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
vclass?: [() => string]
|
|
||||||
attrs?: attrs
|
|
||||||
style?: string
|
|
||||||
inner?: string | computedFn
|
|
||||||
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 (opts.inner) {
|
|
||||||
inner = opts.inner
|
|
||||||
}
|
|
||||||
if (inner) {
|
|
||||||
if (typeof inner == 'string') {
|
|
||||||
dom.innerHTML = inner
|
|
||||||
} else if (typeof inner == 'function') {
|
|
||||||
proxy.Listen(dom, () => {
|
|
||||||
let res = inner()
|
|
||||||
if (typeof res === 'string') {
|
|
||||||
dom.innerHTML = res
|
|
||||||
} else if (res === undefined) {
|
|
||||||
} else {
|
|
||||||
dom.innerHTML = ''
|
|
||||||
dom.appendChild(res)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} 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.vclass) {
|
|
||||||
for (let vc of opts.vclass) {
|
|
||||||
let oldvc: string[] = []
|
|
||||||
proxy.Listen(dom, () => {
|
|
||||||
dom.classList.remove(...oldvc)
|
|
||||||
oldvc = vc().split(' ')
|
|
||||||
dom.classList.add(...oldvc)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (opts.attrs) {
|
|
||||||
for (let a in opts.attrs) {
|
|
||||||
let attr = opts.attrs[a]
|
|
||||||
if (typeof attr === 'function') {
|
|
||||||
proxy.Listen(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
|
|
||||||
proxy.Listen(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) {
|
|
||||||
proxy.Listen(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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* index.ts
|
||||||
|
* Copyright (C) 2024 veypi <i@veypi.com>
|
||||||
|
* 2024-10-29 06:21
|
||||||
|
* Distributed under terms of the GPL license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import proxy from './proxy'
|
||||||
|
import { v, vspan, vfor } from './v2dom'
|
||||||
|
|
||||||
|
export {
|
||||||
|
v,
|
||||||
|
vfor,
|
||||||
|
vspan,
|
||||||
|
proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
export default v
|
@ -0,0 +1,240 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
vclass?: [() => string]
|
||||||
|
attrs?: attrs
|
||||||
|
style?: string
|
||||||
|
onclick?: any
|
||||||
|
children?: childTyp<T>[] | childTyp<T>
|
||||||
|
updator?: (p: HTMLElement) => void
|
||||||
|
vbind?: [Object, string]
|
||||||
|
}
|
||||||
|
|
||||||
|
const typs = ['div', 'img', 'span', 'p', 'a', 'input']
|
||||||
|
|
||||||
|
type vforChild<T> = { obj: T[], fn: (d: T, idx?: number) => HTMLElement, iterID: string, ifnone: HTMLElement }
|
||||||
|
type computedFn = () => string | HTMLElement
|
||||||
|
type childTyp<T> = HTMLElement | vforChild<T> | string | computedFn
|
||||||
|
type attrs = { [key: string]: string | number | (() => string) }
|
||||||
|
|
||||||
|
export const v = <T>(opts: buildOpts<T> | string, children?: childTyp<T> | childTyp<T>[], attrs?: attrs) => {
|
||||||
|
if (typeof opts === 'string') {
|
||||||
|
if (typs.indexOf(opts) >= 0) {
|
||||||
|
opts = { typ: opts }
|
||||||
|
} else {
|
||||||
|
opts = { class: opts }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (children) {
|
||||||
|
opts.children = children
|
||||||
|
}
|
||||||
|
if (attrs) {
|
||||||
|
opts.attrs = attrs
|
||||||
|
}
|
||||||
|
return vbase(opts)
|
||||||
|
}
|
||||||
|
export const vspan = (content: string, Class?: string, attrs?: attrs) => {
|
||||||
|
return vbase({ typ: 'span', class: Class, children: [content], attrs })
|
||||||
|
}
|
||||||
|
export const vbase = <T>(opts: buildOpts<T>) => {
|
||||||
|
let dom = document.createElement(opts.typ || 'div')
|
||||||
|
if (opts.id) {
|
||||||
|
dom.id = opts.id
|
||||||
|
}
|
||||||
|
if (opts.class) {
|
||||||
|
dom.classList.add(...opts.class.split(' '))
|
||||||
|
}
|
||||||
|
if (opts.vclass) {
|
||||||
|
for (let vc of opts.vclass) {
|
||||||
|
let oldvc: string[] = []
|
||||||
|
proxy.DomListen(dom, () => {
|
||||||
|
dom.classList.remove(...oldvc)
|
||||||
|
oldvc = vc().split(' ')
|
||||||
|
dom.classList.add(...oldvc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (opts.attrs) {
|
||||||
|
for (let a in opts.attrs) {
|
||||||
|
let attr = opts.attrs[a]
|
||||||
|
if (typeof attr === 'function') {
|
||||||
|
proxy.DomListen(dom, () => {
|
||||||
|
dom.setAttribute(a, attr())
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
dom.setAttribute(a, String(attr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (opts.onclick) {
|
||||||
|
dom.onclick = opts.onclick
|
||||||
|
}
|
||||||
|
if (opts.children) {
|
||||||
|
handleChild(dom, opts.children, true)
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
proxy.DomListen(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 handleChild(dom: HTMLElement, c: any, is_only = false, is_listen = false) {
|
||||||
|
if (is_only) {
|
||||||
|
dom.innerHTML = ''
|
||||||
|
}
|
||||||
|
if (typeof c === 'function') {
|
||||||
|
if (!is_listen) {
|
||||||
|
proxy.DomListen(dom, () => {
|
||||||
|
handleChild(dom, c(), is_only, true)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
handleChild(dom, c(), is_only, true)
|
||||||
|
}
|
||||||
|
} else if (typeof c === 'string' || typeof c === 'number') {
|
||||||
|
dom.innerHTML = String(c)
|
||||||
|
} else if (Array.isArray(c)) {
|
||||||
|
for (let cc of c) {
|
||||||
|
handleChild(dom, cc, is_only && c.length === 1, is_listen)
|
||||||
|
}
|
||||||
|
} else if (c && c.iterID) {
|
||||||
|
handleChildVfor(dom, c as vforChild<any>, is_listen)
|
||||||
|
} else if (c instanceof Element) {
|
||||||
|
dom.appendChild(c)
|
||||||
|
} else if (typeof c === 'undefined' || c === null) {
|
||||||
|
} else {
|
||||||
|
console.warn('unknown child type', c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const vfor = <T>(obj: T[], fn: (d: T, idx?: number) => HTMLElement, ifnone?: () => HTMLElement): vforChild<T> => {
|
||||||
|
let iterID = proxy.GenUniqueID()
|
||||||
|
let dom = document.createElement('div') as HTMLElement
|
||||||
|
dom.style.display = 'none'
|
||||||
|
dom.style.visibility = 'hidden'
|
||||||
|
dom.style.height = '0'
|
||||||
|
dom.style.width = '0'
|
||||||
|
if (ifnone) {
|
||||||
|
dom = ifnone()
|
||||||
|
}
|
||||||
|
dom.setAttribute('vbind-iternone', iterID)
|
||||||
|
return {
|
||||||
|
obj,
|
||||||
|
fn,
|
||||||
|
iterID,
|
||||||
|
ifnone: dom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleChildVfor(dom: HTMLElement, data: vforChild<any>, is_listen = false) {
|
||||||
|
let fn = () => {
|
||||||
|
let itemIDs: string[] = []
|
||||||
|
for (let i = 0; i < data.obj.length; i++) {
|
||||||
|
let line = data.obj[i]
|
||||||
|
let itemID = line[proxy.DataID] || ''
|
||||||
|
let nextID = ''
|
||||||
|
if (i < data.obj.length - 1) {
|
||||||
|
nextID = data.obj[i + 1][proxy.DataID] || ''
|
||||||
|
}
|
||||||
|
itemIDs.push(itemID)
|
||||||
|
let inserted = -1
|
||||||
|
let subdom: HTMLElement | null = null
|
||||||
|
let beforeChild: HTMLElement | null = null
|
||||||
|
let iterFound = false
|
||||||
|
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') === data.iterID) {
|
||||||
|
iterFound = true
|
||||||
|
if (child.getAttribute('vbind-iteridx') === itemID) {
|
||||||
|
// 找到已存在dom
|
||||||
|
inserted = childID
|
||||||
|
subdom = child as HTMLElement
|
||||||
|
} else if (child.getAttribute('vbind-iteridx') === nextID && nextID != '') {
|
||||||
|
// 用于交换位置
|
||||||
|
beforeChild = child as HTMLElement
|
||||||
|
}
|
||||||
|
} else if (child.getAttribute('vbind-iternone') === data.iterID) {
|
||||||
|
if (inserted == -1) {
|
||||||
|
beforeChild = child as HTMLElement
|
||||||
|
}
|
||||||
|
} else if (iterFound) {
|
||||||
|
if (!beforeChild) {
|
||||||
|
beforeChild = child as HTMLElement
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inserted == -1) {
|
||||||
|
subdom = data.fn(line, i)
|
||||||
|
subdom.setAttribute('vbind-iteridx', itemID)
|
||||||
|
subdom.setAttribute('vbind-iter', data.iterID)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let noneExist = false
|
||||||
|
for (let child of dom.children) {
|
||||||
|
if (child.getAttribute('vbind-iter') === data.iterID && itemIDs.indexOf(child.getAttribute('vbind-iteridx') || '') == -1) {
|
||||||
|
dom.removeChild(child)
|
||||||
|
}
|
||||||
|
if (child.getAttribute('vbind-iternone') === data.iterID) {
|
||||||
|
noneExist = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.obj.length == 0) {
|
||||||
|
if (!noneExist) {
|
||||||
|
dom.appendChild(data.ifnone)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (noneExist) {
|
||||||
|
dom.removeChild(data.ifnone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_listen) {
|
||||||
|
fn()
|
||||||
|
} else {
|
||||||
|
proxy.DomListen(dom, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue