/* * v2dom.ts * Copyright (C) 2024 veypi * 2024-10-23 15:54 * Distributed under terms of the GPL license. */ import proxy from "./proxy" interface buildOpts { id?: string typ?: string class?: string style?: string innerHtml?: string onclick?: any children?: childTyp[] updator?: (p: HTMLElement) => void } const typs = ['div', 'img', 'span', 'p', 'a'] type iterChild = [T[], (d: T) => HTMLElement] type childTyp = HTMLElement | iterChild export default (opts: buildOpts | string, inner?: string | childTyp[], updator?: (p: HTMLElement) => void) => { if (typeof opts === 'string') { if (typs.indexOf(opts) >= 0) { opts = { typ: opts } } else { opts = { class: opts } } } if (inner) { if (typeof inner == 'string') { opts.innerHtml = inner } else { opts.children = inner } } if (opts.updator) { updator = opts.updator } 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.innerHtml) { dom.innerHTML = opts.innerHtml } 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(() => { c = c as iterChild 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 (updator) { proxy.Listen(() => { updator(dom) }) } return dom }