@jsweb/ui 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.es.js","names":[],"sources":["../src/reactivity.ts","../src/evaluator.ts","../src/parser.ts","../src/index.ts"],"sourcesContent":["let activeEffect: symbol | null = null\nconst targetMap = new WeakMap<\n object,\n Map<string | symbol, Set<ReactiveEffect>>\n>()\nconst proxyMap = new WeakMap<object, any>()\nconst effectMap = new WeakMap<symbol, ReactiveEffect>()\n\nexport class ReactiveEffect {\n active = true\n deps: Set<Set<ReactiveEffect>> = new Set()\n\n constructor(public fn: () => void) {}\n\n run() {\n if (!this.active) return this.fn()\n\n this.cleanup()\n\n activeEffect = Symbol()\n effectMap.set(activeEffect, this)\n\n try {\n return this.fn()\n } finally {\n effectMap.delete(activeEffect)\n activeEffect = null\n }\n }\n\n stop() {\n if (this.active) {\n this.cleanup()\n this.active = false\n }\n }\n\n cleanup() {\n this.deps.forEach((dep) => dep.delete(this))\n this.deps.clear()\n }\n\n effect() {\n return {\n run: () => this.run(),\n stop: () => this.stop(),\n }\n }\n}\n\nexport function effect(fn: () => void) {\n const ref = new ReactiveEffect(fn)\n ref.run()\n return ref.effect()\n}\n\nexport function track(target: object, key: string | symbol) {\n if (activeEffect) {\n let depsMap = targetMap.get(target)\n if (!depsMap) {\n depsMap = new Map()\n targetMap.set(target, depsMap)\n }\n\n let dep = depsMap.get(key)\n if (!dep) {\n dep = new Set()\n depsMap.set(key, dep)\n }\n\n const active = effectMap.get(activeEffect)\n if (active) {\n dep.add(active)\n active.deps.add(dep)\n }\n }\n}\n\nexport function trigger(target: object, key: string | symbol) {\n const depsMap = targetMap.get(target)\n if (!depsMap) return\n\n const dep = depsMap.get(key)\n if (dep) {\n const effects = new Set(dep)\n effects.forEach((effect) => effect.run())\n }\n}\n\nexport function reactive<T extends object>(target: T): T {\n const notObject = typeof target !== 'object' || target === null\n if (notObject) return target\n\n if (\n target instanceof Map ||\n target instanceof Set ||\n target instanceof WeakMap ||\n target instanceof WeakSet ||\n target instanceof Date ||\n target instanceof RegExp ||\n (typeof Node === 'function' && target instanceof Node)\n ) {\n return target\n }\n\n const isReactive = Object.hasOwn(target, '_isReactive')\n if (isReactive) return target\n\n const existingProxy = proxyMap.get(target)\n if (existingProxy) return existingProxy\n\n const proxy = new Proxy(target, {\n get(obj, key, receiver) {\n if (key === '_isReactive') return true\n track(obj, key)\n\n const res = Reflect.get(obj, key, receiver)\n // deep reactivity\n return typeof res === 'object' && res !== null ? reactive(res) : res\n },\n set(obj, key, value, receiver) {\n const isArray = Array.isArray(obj)\n const oldValue = Reflect.get(obj, key, receiver)\n const hadKey =\n isArray && String(Number(key)) === key\n ? Number(key) < obj.length\n : Object.hasOwn(obj, key)\n\n const result = Reflect.set(obj, key, value, receiver)\n\n if (!hadKey) {\n trigger(obj, key)\n if (isArray && key !== 'length') {\n trigger(obj, 'length')\n }\n } else if (oldValue !== value) {\n trigger(obj, key)\n }\n\n return result\n },\n })\n\n proxyMap.set(target, proxy)\n return proxy\n}\n\nexport function traverse(value: any, seen = new Set()) {\n if (typeof value !== 'object' || value === null || seen.has(value)) {\n return value\n }\n seen.add(value)\n for (const key in value) {\n traverse(value[key], seen)\n }\n return value\n}\n\nexport function watch(\n source: any | (() => any),\n cb: (newValue: any, oldValue: any) => void,\n options?: { immediate?: boolean },\n) {\n let oldValue: any\n let isFirstRun = true\n\n const getter = source instanceof Function ? source : () => traverse(source)\n\n const runner = effect(() => {\n const newValue = getter()\n\n if (isFirstRun) {\n isFirstRun = false\n oldValue = newValue\n if (options?.immediate) {\n cb(newValue, undefined)\n }\n } else {\n cb(newValue, oldValue)\n oldValue = newValue\n }\n })\n\n return runner.stop\n}\n","export function evaluate(\n expression: string,\n context: Record<string, any> = {},\n) {\n try {\n const fn = new Function(`with(this) { return ${expression} }`)\n return fn.call(context)\n } catch {\n return undefined\n }\n}\n\nexport function evaluateEvent(\n $event: Event,\n expression: string,\n context: Record<string, any> = {},\n) {\n try {\n const exp = expression.trim()\n const isIdentifier = /^[a-zA-Z_$][0-9a-zA-Z_$.]*$/.test(exp)\n const code = `${exp} instanceof Function ? ${exp}.call(this, $event) : ${exp}`\n const result = isIdentifier ? code : exp\n const fn = new Function('$event', `with(this) { ${result} }`)\n\n fn.call(context, $event)\n } catch {\n console.warn(`[jsweb/ui] Error evaluating event: ${expression}`)\n }\n}\n","import { effect, reactive } from './reactivity'\nimport { evaluate, evaluateEvent } from './evaluator'\n\nexport type Context = Record<string, any>\n\ninterface BoundNode extends Node {\n _effects?: Array<() => void>\n}\n\nexport function cleanupTree(node: Node) {\n const bNode = node as BoundNode\n if (bNode._effects) {\n bNode._effects.forEach((stop) => stop())\n bNode._effects = []\n }\n const children = Array.from(node.childNodes)\n for (const child of children) cleanupTree(child)\n}\n\nexport function createContext(scope: any, context: Context = {}): Context {\n const reactiveScope = scope._isReactive ? scope : reactive(scope)\n\n return new Proxy(reactiveScope, {\n get(target, prop) {\n if (prop === '_isContext') return true\n if (prop in target) return Reflect.get(target, prop, target)\n if (prop in context) {\n return Reflect.get(context, prop, context)\n }\n return Reflect.get(target, prop, target)\n },\n set(target, prop, value) {\n if (prop in target) return Reflect.set(target, prop, value, target)\n if (prop in context) {\n return Reflect.set(context, prop, value, context)\n }\n return Reflect.set(target, prop, value, target)\n },\n has(target, prop) {\n if (prop in target) return true\n if (prop in context) return true\n return false\n },\n })\n}\n\nexport function parseNode(node: Node, context: Context) {\n if (node.nodeType !== Node.ELEMENT_NODE) return\n\n const el = node as HTMLElement\n const scope = processScope(el, context)\n if (!scope) return\n\n const forAttrs = ['ui:for', ':for']\n const forDirective = getDirectiveValue(el, forAttrs)\n if (forDirective) {\n removeDirectiveAttributes(el, forAttrs)\n processFor(el, forDirective, scope)\n return\n }\n\n const ifAttrs = ['ui:if', ':if']\n const ifDirective = getDirectiveValue(el, ifAttrs)\n if (ifDirective) {\n removeDirectiveAttributes(el, ifAttrs)\n processIf(el, ifDirective, scope)\n }\n\n processAttributes(el, scope)\n\n const children = Array.from(el.childNodes)\n for (const child of children) parseNode(child, scope)\n}\n\nexport function createScope(\n selectorOrElement: string | HTMLElement,\n context: Context = {},\n) {\n const el =\n typeof selectorOrElement === 'string'\n ? document.querySelector(selectorOrElement)\n : selectorOrElement\n\n if (el) {\n if (!context.$emit) {\n context.$emit = (eventName: string, detail?: any) => {\n el.dispatchEvent(\n new CustomEvent(eventName, { detail, bubbles: true, composed: true }),\n )\n }\n }\n\n if (!context.$refs) {\n context.$refs = new Map<string, any>()\n }\n\n parseNode(el, context)\n } else {\n console.warn('[jsweb/ui] Element not found:', selectorOrElement)\n }\n}\n\nfunction bindEffect(node: Node, fn: () => void) {\n const e = effect(fn)\n const bNode = node as BoundNode\n bNode._effects ??= []\n bNode._effects.push(e.stop)\n}\n\nfunction getDirectiveValue(el: HTMLElement, names: string[]) {\n for (const name of names) {\n const value = el.getAttribute(name)\n if (value !== null) return value\n }\n return null\n}\n\nfunction removeDirectiveAttributes(el: HTMLElement, names: string[]) {\n for (const name of names) {\n el.removeAttribute(name)\n }\n}\n\nfunction processScope(el: HTMLElement, context: Context) {\n const attrs = ['ui:scope', ':scope']\n const directive = getDirectiveValue(el, attrs)\n if (!directive) return context\n\n const scope = evaluate(directive, context)\n if (!scope) return undefined\n\n removeDirectiveAttributes(el, attrs)\n\n if (!scope.$emit) {\n scope.$emit = (event: string, detail?: any) => {\n el.dispatchEvent(\n new CustomEvent(event, { detail, bubbles: true, composed: true }),\n )\n }\n }\n\n if (!scope.$refs) {\n scope.$refs = context.$refs ?? new Map<string, any>()\n }\n\n return createContext(scope, context)\n}\n\nfunction processFor(el: HTMLElement, expr: string, context: Context) {\n const parent = el.parentNode\n if (!parent) return\n\n const match = /^\\s*(.+)\\s+(?:in|of)\\s+(.+)\\s*$/.exec(expr)\n if (!match) {\n return console.warn(`[jsweb/ui] Invalid ui:for expression: ${expr}`)\n }\n const [, itemName, listName] = match\n\n const keyAttr = ['ui:key', ':key']\n const keyDirective = getDirectiveValue(el, keyAttr)\n removeDirectiveAttributes(el, keyAttr)\n\n const uuid = crypto.randomUUID()\n const comment = document.createComment(` ui:for ${uuid} `)\n el.replaceWith(comment)\n\n interface RenderedNode {\n key: any\n el: HTMLElement\n scope: any\n }\n let renderedNodes: RenderedNode[] = []\n\n bindEffect(comment, () => {\n const list = evaluate(listName, context)\n\n if (!Array.isArray(list)) {\n renderedNodes.forEach((node) => {\n node.el.remove()\n cleanupTree(node.el)\n })\n renderedNodes = []\n return\n }\n\n const newNodes: RenderedNode[] = []\n const oldNodesByKey = new Map<any, RenderedNode>()\n renderedNodes.forEach((node) => oldNodesByKey.set(node.key, node))\n\n list.forEach((item, index) => {\n let key: any = index\n\n if (keyDirective) {\n const tempContext = createContext(\n { [itemName]: item, $index: index },\n context,\n )\n key = evaluate(keyDirective, tempContext)\n }\n\n const scope = { [itemName]: item, $index: index, $key: key }\n\n let node = oldNodesByKey.get(key)\n if (node) {\n // Reuse node\n node.scope[itemName] = item\n node.scope.$index = index\n node.scope.$key = key\n oldNodesByKey.delete(key)\n } else {\n // Create new node\n const clone = el.cloneNode(true) as HTMLElement\n const reactiveScope = reactive(scope)\n const localContext = createContext(reactiveScope, context)\n parseNode(clone, localContext)\n node = { key, el: clone, scope: reactiveScope }\n }\n\n newNodes.push(node)\n })\n\n // Remove un-reused nodes\n oldNodesByKey.forEach((node) => {\n node.el.remove()\n cleanupTree(node.el)\n })\n\n // Reorder and insert new DOM nodes\n let currentAnchor = comment.nextSibling\n newNodes.forEach((node) => {\n if (currentAnchor === node.el) {\n currentAnchor = currentAnchor.nextSibling\n } else {\n comment.parentNode?.insertBefore(node.el, currentAnchor)\n }\n })\n\n renderedNodes = newNodes\n })\n}\n\nfunction processIf(el: HTMLElement, expr: string, context: Context) {\n const parent = el.parentNode\n if (!parent) return\n\n const uuid = crypto.randomUUID()\n const comment = document.createComment(` ui:if ${uuid} `)\n el.before(comment)\n\n bindEffect(comment, () => {\n const val = evaluate(expr, context)\n if (val) {\n if (!el.parentNode) {\n comment.parentNode?.insertBefore(el, comment.nextSibling)\n }\n } else if (el.parentNode) {\n el.remove()\n }\n })\n}\n\nfunction processAttributes(el: HTMLElement, context: Context) {\n const attrs = Array.from(el.attributes)\n\n for (const attr of attrs) {\n const { name, value } = attr\n const isText = ['ui:text', ':text'].includes(name)\n const isTwoWayBind = ['ui:bind', ':bind'].includes(name)\n const isClassBind = ['ui:class', ':class'].includes(name)\n const isStyleBind = ['ui:style', ':style'].includes(name)\n const isRef = ['ui:ref', ':ref'].includes(name)\n const isAttrBind = name.startsWith('ui:') || name.startsWith(':')\n const isEvent = name.startsWith('ui@') || name.startsWith('@')\n\n if (isText) {\n processTextBinding(el, value, context)\n el.removeAttribute(name)\n } else if (isTwoWayBind) {\n processTwoWayBinding(el, value, context)\n el.removeAttribute(name)\n } else if (isClassBind) {\n processClassBinding(el, value, context)\n el.removeAttribute(name)\n } else if (isStyleBind) {\n processStyleBinding(el, value, context)\n el.removeAttribute(name)\n } else if (isRef) {\n processRef(el, value, context)\n el.removeAttribute(name)\n } else if (isAttrBind) {\n const bound = name.split(':').pop()!\n processAttrBinding(el, bound, value, context)\n el.removeAttribute(name)\n } else if (isEvent) {\n processEventBinding(el, name, value, context)\n el.removeAttribute(name)\n }\n }\n}\n\nfunction processRef(el: HTMLElement, expr: string, context: Context) {\n const refName = expr.trim().replace(/^['\"]|['\"]$/g, '')\n if (!refName) return\n\n const refs = context.$refs as Map<string, any>\n if (!refs) return\n\n const key = context.$key\n\n if (key !== undefined) {\n let group = refs.get(refName)\n if (!(group instanceof Map)) {\n group = new Map<any, HTMLElement>()\n refs.set(refName, group)\n }\n group.set(key, el)\n } else {\n refs.set(refName, el)\n }\n\n const bNode = el as BoundNode\n bNode._effects ??= []\n bNode._effects.push(() => {\n if (key !== undefined) {\n const group = refs.get(refName)\n if (group instanceof Map) {\n group.delete(key)\n if (group.size === 0) {\n refs.delete(refName)\n }\n }\n } else if (refs.get(refName) === el) {\n refs.delete(refName)\n }\n })\n}\n\nfunction processTextBinding(el: HTMLElement, expr: string, context: Context) {\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n el.textContent = val !== undefined && val !== null ? String(val) : ''\n })\n}\n\nfunction processTwoWayBinding(el: HTMLElement, expr: string, context: Context) {\n const isCheckbox = el instanceof HTMLInputElement && el.type === 'checkbox'\n const isRadio = el instanceof HTMLInputElement && el.type === 'radio'\n\n // 1. Reactive state to DOM\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n if (isCheckbox) {\n el.checked = !!val\n } else if (isRadio) {\n el.checked = el.value === String(val)\n } else {\n const target = el as\n | HTMLInputElement\n | HTMLSelectElement\n | HTMLTextAreaElement\n target.value = val == null ? '' : String(val)\n }\n })\n\n // 2. DOM to Reactive state\n const isChange = isCheckbox || isRadio || el instanceof HTMLSelectElement\n const eventName = isChange ? 'change' : 'input'\n el.addEventListener(eventName, ($event) => {\n const target = isCheckbox ? 'checked' : 'value'\n const value = `$event.target.${target}`\n evaluateEvent($event, `${expr} = ${value}`, context)\n })\n}\n\nfunction processAttrBinding(\n el: HTMLElement,\n attr: string,\n expr: string,\n context: Context,\n) {\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n if (val === null || val === undefined || val === false) {\n el.removeAttribute(attr)\n } else if (val === true) {\n el.setAttribute(attr, '')\n } else {\n el.setAttribute(attr, String(val))\n }\n })\n}\n\nfunction processClassBinding(el: HTMLElement, expr: string, context: Context) {\n let oldClasses = new Set<string>()\n\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n const newClasses = new Set<string>()\n const addClass = (c: string) => c && newClasses.add(c)\n const addClasses = (c: string) => c.split(/\\s+/).forEach(addClass)\n\n if (typeof val === 'string') addClasses(val)\n else if (Array.isArray(val)) {\n val.flat().forEach((c: any) => {\n if (typeof c === 'string') addClasses(c)\n })\n } else if (typeof val === 'object' && val !== null) {\n Object.entries(val).forEach(([c, condition]: [string, any]) => {\n if (condition) addClasses(c)\n })\n }\n\n oldClasses.forEach((c) => {\n if (!newClasses.has(c)) el.classList.remove(c)\n })\n newClasses.forEach((c) => {\n if (!oldClasses.has(c)) el.classList.add(c)\n })\n\n oldClasses = newClasses\n })\n}\n\nfunction processStyleBinding(el: HTMLElement, expr: string, context: Context) {\n let oldStyles: Record<string, any> = {}\n\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n const newStyles = typeof val === 'object' && val !== null ? val : {}\n\n for (const key in oldStyles) {\n if (!(key in newStyles)) {\n ;(el.style as any)[key] = ''\n }\n }\n\n for (const key in newStyles) {\n if (oldStyles[key] !== newStyles[key]) {\n ;(el.style as any)[key] = newStyles[key]\n }\n }\n\n oldStyles = { ...newStyles }\n })\n}\n\nfunction processEventBinding(\n el: HTMLElement,\n evt: string,\n expr: string,\n context: Context,\n) {\n const refs = evt.split('@').pop()!\n const [name, ...modifiers] = refs.split('.')\n\n const isOutside = modifiers.includes('outside')\n const target = isOutside ? document : el\n\n const handler: EventListener = ($event: Event) => {\n if (!el.isConnected) return\n\n const isTargetNode = $event.target instanceof Node\n\n if (isOutside && isTargetNode && el.contains($event.target)) return\n if (modifiers.includes('self') && $event.target !== el) return\n\n if (modifiers.includes('prevent')) $event.preventDefault()\n if (modifiers.includes('stop')) $event.stopPropagation()\n\n evaluateEvent($event, expr, context)\n }\n\n target.addEventListener(name, handler)\n\n if (isOutside) {\n const bNode = el as BoundNode\n bNode._effects ??= []\n bNode._effects.push(() => target.removeEventListener(name, handler))\n }\n}\n","import { reactive, watch } from './reactivity'\nimport { createScope } from './parser'\n\nexport { reactive, watch, createScope }\n\nif (typeof window !== 'undefined') {\n const w = window as any\n w.jsweb = w.jsweb || {}\n w.jsweb.ui = { createScope, reactive, watch }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.es.js","names":[],"sources":["../src/reactivity.ts","../src/evaluator.ts","../src/parser.ts","../src/index.ts"],"sourcesContent":["let activeEffect: symbol | null = null\nconst targetMap = new WeakMap<\n object,\n Map<string | symbol, Set<ReactiveEffect>>\n>()\nconst proxyMap = new WeakMap<object, any>()\nconst effectMap = new WeakMap<symbol, ReactiveEffect>()\n\nexport class ReactiveEffect {\n active = true\n deps: Set<Set<ReactiveEffect>> = new Set()\n\n constructor(public fn: () => void) {}\n\n run() {\n if (!this.active) return this.fn()\n\n this.cleanup()\n\n activeEffect = Symbol()\n effectMap.set(activeEffect, this)\n\n try {\n return this.fn()\n } finally {\n effectMap.delete(activeEffect)\n activeEffect = null\n }\n }\n\n stop() {\n if (this.active) {\n this.cleanup()\n this.active = false\n }\n }\n\n cleanup() {\n this.deps.forEach((dep) => dep.delete(this))\n this.deps.clear()\n }\n\n effect() {\n return {\n run: () => this.run(),\n stop: () => this.stop(),\n }\n }\n}\n\nexport function effect(fn: () => void) {\n const ref = new ReactiveEffect(fn)\n ref.run()\n return ref.effect()\n}\n\nexport function track(target: object, key: string | symbol) {\n if (activeEffect) {\n let depsMap = targetMap.get(target)\n if (!depsMap) {\n depsMap = new Map()\n targetMap.set(target, depsMap)\n }\n\n let dep = depsMap.get(key)\n if (!dep) {\n dep = new Set()\n depsMap.set(key, dep)\n }\n\n const active = effectMap.get(activeEffect)\n if (active) {\n dep.add(active)\n active.deps.add(dep)\n }\n }\n}\n\nexport function trigger(target: object, key: string | symbol) {\n const depsMap = targetMap.get(target)\n if (!depsMap) return\n\n const dep = depsMap.get(key)\n if (dep) {\n const effects = new Set(dep)\n effects.forEach((effect) => effect.run())\n }\n}\n\nexport interface ScopeContext {\n /** Elemento DOM raiz associado ao escopo (somente leitura) */\n readonly $el: HTMLElement\n /** Map nativo indexando elementos referenciados via ui:ref / :ref (somente leitura) */\n readonly $refs: Map<string, any>\n /** Despacha CustomEvents nativos (bubbles: true, composed: true) */\n $emit: (event: string, detail?: any) => void\n /** Índice numérico da iteração atual em loops ui:for / :for */\n $index?: number\n /** Chave de identificação da iteração em loops ui:for / :for */\n $key?: any\n}\n\nexport class Scope {\n /** Elemento DOM raiz ao qual o escopo foi acoplado (somente leitura) */\n declare readonly $el: HTMLElement\n\n /** Map nativo indexando elementos referenciados via ui:ref / :ref (somente leitura) */\n protected readonly $refs: Map<string, any> = new Map<string, any>()\n\n /** Despacha CustomEvents nativos (bubbles: true, composed: true) */\n protected $emit(event: string, detail?: any): void {\n const target = this.$el || (typeof window !== 'undefined' ? window : null)\n target?.dispatchEvent(\n new CustomEvent(event, { detail, bubbles: true, composed: true }),\n )\n }\n\n declare $index?: number\n declare $key?: any\n\n constructor(init?: Record<string, any>) {\n if (init && typeof init === 'object') {\n Object.assign(this, init)\n }\n }\n}\n\nexport function reactive<T extends any[]>(target: T): T\nexport function reactive<T extends object>(\n target: T & ThisType<T & ScopeContext>,\n): T & ScopeContext\nexport function reactive<T extends object>(target: T): any {\n const notObject = typeof target !== 'object' || target === null\n if (notObject) return target\n\n if (\n target instanceof Map ||\n target instanceof Set ||\n target instanceof WeakMap ||\n target instanceof WeakSet ||\n target instanceof Date ||\n target instanceof RegExp ||\n (typeof Node === 'function' && target instanceof Node)\n ) {\n return target\n }\n\n const isReactive = Object.hasOwn(target, '_isReactive')\n if (isReactive) return target\n\n const existingProxy = proxyMap.get(target)\n if (existingProxy) return existingProxy\n\n const proxy = new Proxy(target, {\n get(obj, key, receiver) {\n if (key === '_isReactive') return true\n track(obj, key)\n\n const res = Reflect.get(obj, key, receiver)\n // deep reactivity\n return typeof res === 'object' && res !== null ? reactive(res) : res\n },\n set(obj, key, value, receiver) {\n const isArray = Array.isArray(obj)\n const oldValue = Reflect.get(obj, key, receiver)\n const hadKey =\n isArray && String(Number(key)) === key\n ? Number(key) < obj.length\n : Object.hasOwn(obj, key)\n\n const result = Reflect.set(obj, key, value, receiver)\n\n if (!hadKey) {\n trigger(obj, key)\n if (isArray && key !== 'length') {\n trigger(obj, 'length')\n }\n } else if (oldValue !== value) {\n trigger(obj, key)\n }\n\n return result\n },\n })\n\n proxyMap.set(target, proxy)\n return proxy\n}\n\nexport function traverse(value: any, seen = new Set()) {\n if (typeof value !== 'object' || value === null || seen.has(value)) {\n return value\n }\n seen.add(value)\n for (const key in value) {\n traverse(value[key], seen)\n }\n return value\n}\n\nexport function watch<T>(\n source: (() => T) | any,\n cb: (newValue: T, oldValue: T | undefined) => void,\n options?: { immediate?: boolean },\n): () => void {\n let oldValue: any\n let isFirstRun = true\n\n const getter = source instanceof Function ? source : () => traverse(source)\n\n const runner = effect(() => {\n const newValue = getter()\n\n if (isFirstRun) {\n isFirstRun = false\n oldValue = newValue\n if (options?.immediate) {\n cb(newValue, undefined)\n }\n } else {\n cb(newValue, oldValue)\n oldValue = newValue\n }\n })\n\n return runner.stop\n}\n","export function evaluate(\n expression: string,\n context: Record<string, any> = {},\n) {\n try {\n const fn = new Function(`with(this) { return ${expression} }`)\n return fn.call(context)\n } catch {\n return undefined\n }\n}\n\nexport function evaluateEvent(\n $event: Event,\n expression: string,\n context: Record<string, any> = {},\n) {\n try {\n const exp = expression.trim()\n const isIdentifier = /^[a-zA-Z_$][0-9a-zA-Z_$.]*$/.test(exp)\n const code = `${exp} instanceof Function ? ${exp}.call(this, $event) : ${exp}`\n const result = isIdentifier ? code : exp\n const fn = new Function('$event', `with(this) { ${result} }`)\n\n fn.call(context, $event)\n } catch {\n console.warn(`[jsweb/ui] Error evaluating event: ${expression}`)\n }\n}\n","import { effect, reactive, type ScopeContext } from './reactivity'\nimport { evaluate, evaluateEvent } from './evaluator'\n\nexport type Context = Record<string, any>\n\ninterface BoundNode extends Node {\n _effects?: Array<() => void>\n}\n\nexport function cleanupTree(node: Node) {\n const bNode = node as BoundNode\n if (bNode._effects) {\n bNode._effects.forEach((stop) => stop())\n bNode._effects = []\n }\n const children = Array.from(node.childNodes)\n for (const child of children) cleanupTree(child)\n}\n\nexport function createContext(scope: any, context: Context = {}): Context {\n const reactiveScope = scope._isReactive ? scope : reactive(scope)\n\n return new Proxy(reactiveScope, {\n get(target, prop) {\n if (prop === '_isContext') return true\n if (prop in target) return Reflect.get(target, prop, target)\n if (prop in context) {\n return Reflect.get(context, prop, context)\n }\n return Reflect.get(target, prop, target)\n },\n set(target, prop, value) {\n if (prop in target) return Reflect.set(target, prop, value, target)\n if (prop in context) {\n return Reflect.set(context, prop, value, context)\n }\n return Reflect.set(target, prop, value, target)\n },\n has(target, prop) {\n if (prop in target) return true\n if (prop in context) return true\n return false\n },\n })\n}\n\nexport function parseNode(node: Node, context: Context) {\n if (node.nodeType !== Node.ELEMENT_NODE) return\n\n const el = node as HTMLElement\n const scope = processScope(el, context)\n if (!scope) return\n\n const forAttrs = ['ui:for', ':for']\n const forDirective = getDirectiveValue(el, forAttrs)\n if (forDirective) {\n removeDirectiveAttributes(el, forAttrs)\n processFor(el, forDirective, scope)\n return\n }\n\n const ifAttrs = ['ui:if', ':if']\n const ifDirective = getDirectiveValue(el, ifAttrs)\n if (ifDirective) {\n removeDirectiveAttributes(el, ifAttrs)\n processIf(el, ifDirective, scope)\n }\n\n processAttributes(el, scope)\n\n const children = Array.from(el.childNodes)\n for (const child of children) parseNode(child, scope)\n}\n\nexport function createScope<T extends object = Context>(\n selectorOrElement: string | HTMLElement,\n context?: T & ThisType<T & ScopeContext>,\n) {\n const el =\n typeof selectorOrElement === 'string'\n ? document.querySelector(selectorOrElement)\n : selectorOrElement\n\n if (el) {\n const ctx = (context ?? {}) as Context\n ctx.$el = el\n\n if (!ctx.$emit) {\n ctx.$emit = (eventName: string, detail?: any) => {\n el.dispatchEvent(\n new CustomEvent(eventName, { detail, bubbles: true, composed: true }),\n )\n }\n }\n\n if (!ctx.$refs) {\n ctx.$refs = new Map<string, any>()\n }\n\n parseNode(el, ctx)\n } else {\n console.warn('[jsweb/ui] Element not found:', selectorOrElement)\n }\n}\n\nfunction bindEffect(node: Node, fn: () => void) {\n const e = effect(fn)\n const bNode = node as BoundNode\n bNode._effects ??= []\n bNode._effects.push(e.stop)\n}\n\nfunction getDirectiveValue(el: HTMLElement, names: string[]) {\n for (const name of names) {\n const value = el.getAttribute(name)\n if (value !== null) return value\n }\n return null\n}\n\nfunction removeDirectiveAttributes(el: HTMLElement, names: string[]) {\n for (const name of names) {\n el.removeAttribute(name)\n }\n}\n\nfunction processScope(el: HTMLElement, context: Context) {\n const attrs = ['ui:scope', ':scope']\n const directive = getDirectiveValue(el, attrs)\n if (!directive) return context\n\n const scope = evaluate(directive, context)\n if (!scope) return undefined\n\n removeDirectiveAttributes(el, attrs)\n\n scope.$el = el\n\n if (!scope.$emit) {\n scope.$emit = (event: string, detail?: any) => {\n el.dispatchEvent(\n new CustomEvent(event, { detail, bubbles: true, composed: true }),\n )\n }\n }\n\n if (!scope.$refs) {\n scope.$refs = context.$refs ?? new Map<string, any>()\n }\n\n return createContext(scope, context)\n}\n\nfunction processFor(el: HTMLElement, expr: string, context: Context) {\n const parent = el.parentNode\n if (!parent) return\n\n const match = /^\\s*(.+)\\s+(?:in|of)\\s+(.+)\\s*$/.exec(expr)\n if (!match) {\n return console.warn(`[jsweb/ui] Invalid ui:for expression: ${expr}`)\n }\n const [, itemName, listName] = match\n\n const keyAttr = ['ui:key', ':key']\n const keyDirective = getDirectiveValue(el, keyAttr)\n removeDirectiveAttributes(el, keyAttr)\n\n const uuid = crypto.randomUUID()\n const comment = document.createComment(` ui:for ${uuid} `)\n el.replaceWith(comment)\n\n interface RenderedNode {\n key: any\n el: HTMLElement\n scope: any\n }\n let renderedNodes: RenderedNode[] = []\n\n bindEffect(comment, () => {\n const list = evaluate(listName, context)\n\n if (!Array.isArray(list)) {\n renderedNodes.forEach((node) => {\n node.el.remove()\n cleanupTree(node.el)\n })\n renderedNodes = []\n return\n }\n\n const newNodes: RenderedNode[] = []\n const oldNodesByKey = new Map<any, RenderedNode>()\n renderedNodes.forEach((node) => oldNodesByKey.set(node.key, node))\n\n list.forEach((item, index) => {\n let key: any = index\n\n if (keyDirective) {\n const tempContext = createContext(\n { [itemName]: item, $index: index },\n context,\n )\n key = evaluate(keyDirective, tempContext)\n }\n\n let node = oldNodesByKey.get(key)\n if (node) {\n // Reuse node\n node.scope[itemName] = item\n node.scope.$index = index\n node.scope.$key = key\n node.scope.$el = node.el\n oldNodesByKey.delete(key)\n } else {\n // Create new node\n const clone = el.cloneNode(true) as HTMLElement\n const scope = {\n [itemName]: item,\n $index: index,\n $key: key,\n $el: clone,\n }\n const reactiveScope = reactive(scope)\n const localContext = createContext(reactiveScope, context)\n parseNode(clone, localContext)\n node = { key, el: clone, scope: reactiveScope }\n }\n\n newNodes.push(node)\n })\n\n // Remove un-reused nodes\n oldNodesByKey.forEach((node) => {\n node.el.remove()\n cleanupTree(node.el)\n })\n\n // Reorder and insert new DOM nodes\n let currentAnchor = comment.nextSibling\n newNodes.forEach((node) => {\n if (currentAnchor === node.el) {\n currentAnchor = currentAnchor.nextSibling\n } else {\n comment.parentNode?.insertBefore(node.el, currentAnchor)\n }\n })\n\n renderedNodes = newNodes\n })\n}\n\nfunction processIf(el: HTMLElement, expr: string, context: Context) {\n const parent = el.parentNode\n if (!parent) return\n\n const uuid = crypto.randomUUID()\n const comment = document.createComment(` ui:if ${uuid} `)\n el.before(comment)\n\n bindEffect(comment, () => {\n const val = evaluate(expr, context)\n if (val) {\n if (!el.parentNode) {\n comment.parentNode?.insertBefore(el, comment.nextSibling)\n }\n } else if (el.parentNode) {\n el.remove()\n }\n })\n}\n\nfunction processAttributes(el: HTMLElement, context: Context) {\n const attrs = Array.from(el.attributes)\n\n for (const attr of attrs) {\n const { name, value } = attr\n const isText = ['ui:text', ':text'].includes(name)\n const isTwoWayBind = ['ui:bind', ':bind'].includes(name)\n const isClassBind = ['ui:class', ':class'].includes(name)\n const isStyleBind = ['ui:style', ':style'].includes(name)\n const isRef = ['ui:ref', ':ref'].includes(name)\n const isAttrBind = name.startsWith('ui:') || name.startsWith(':')\n const isEvent = name.startsWith('ui@') || name.startsWith('@')\n\n if (isText) {\n processTextBinding(el, value, context)\n el.removeAttribute(name)\n } else if (isTwoWayBind) {\n processTwoWayBinding(el, value, context)\n el.removeAttribute(name)\n } else if (isClassBind) {\n processClassBinding(el, value, context)\n el.removeAttribute(name)\n } else if (isStyleBind) {\n processStyleBinding(el, value, context)\n el.removeAttribute(name)\n } else if (isRef) {\n processRef(el, value, context)\n el.removeAttribute(name)\n } else if (isAttrBind) {\n const bound = name.split(':').pop()!\n processAttrBinding(el, bound, value, context)\n el.removeAttribute(name)\n } else if (isEvent) {\n processEventBinding(el, name, value, context)\n el.removeAttribute(name)\n }\n }\n}\n\nfunction processRef(el: HTMLElement, expr: string, context: Context) {\n const refName = expr.trim().replace(/^['\"]|['\"]$/g, '')\n if (!refName) return\n\n const refs = context.$refs as Map<string, any>\n if (!refs) return\n\n const key = context.$key\n\n if (key !== undefined) {\n let group = refs.get(refName)\n if (!(group instanceof Map)) {\n group = new Map<any, HTMLElement>()\n refs.set(refName, group)\n }\n group.set(key, el)\n } else {\n refs.set(refName, el)\n }\n\n const bNode = el as BoundNode\n bNode._effects ??= []\n bNode._effects.push(() => {\n if (key !== undefined) {\n const group = refs.get(refName)\n if (group instanceof Map) {\n group.delete(key)\n if (group.size === 0) {\n refs.delete(refName)\n }\n }\n } else if (refs.get(refName) === el) {\n refs.delete(refName)\n }\n })\n}\n\nfunction processTextBinding(el: HTMLElement, expr: string, context: Context) {\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n el.textContent = val !== undefined && val !== null ? String(val) : ''\n })\n}\n\nfunction processTwoWayBinding(el: HTMLElement, expr: string, context: Context) {\n const isCheckbox = el instanceof HTMLInputElement && el.type === 'checkbox'\n const isRadio = el instanceof HTMLInputElement && el.type === 'radio'\n\n // 1. Reactive state to DOM\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n if (isCheckbox) {\n el.checked = !!val\n } else if (isRadio) {\n el.checked = el.value === String(val)\n } else {\n const target = el as\n | HTMLInputElement\n | HTMLSelectElement\n | HTMLTextAreaElement\n target.value = val == null ? '' : String(val)\n }\n })\n\n // 2. DOM to Reactive state\n const isChange = isCheckbox || isRadio || el instanceof HTMLSelectElement\n const eventName = isChange ? 'change' : 'input'\n el.addEventListener(eventName, ($event) => {\n const target = isCheckbox ? 'checked' : 'value'\n const value = `$event.target.${target}`\n evaluateEvent($event, `${expr} = ${value}`, context)\n })\n}\n\nfunction processAttrBinding(\n el: HTMLElement,\n attr: string,\n expr: string,\n context: Context,\n) {\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n if (val === null || val === undefined || val === false) {\n el.removeAttribute(attr)\n } else if (val === true) {\n el.setAttribute(attr, '')\n } else {\n el.setAttribute(attr, String(val))\n }\n })\n}\n\nfunction processClassBinding(el: HTMLElement, expr: string, context: Context) {\n let oldClasses = new Set<string>()\n\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n const newClasses = new Set<string>()\n const addClass = (c: string) => c && newClasses.add(c)\n const addClasses = (c: string) => c.split(/\\s+/).forEach(addClass)\n\n if (typeof val === 'string') addClasses(val)\n else if (Array.isArray(val)) {\n val.flat().forEach((c: any) => {\n if (typeof c === 'string') addClasses(c)\n })\n } else if (typeof val === 'object' && val !== null) {\n Object.entries(val).forEach(([c, condition]: [string, any]) => {\n if (condition) addClasses(c)\n })\n }\n\n oldClasses.forEach((c) => {\n if (!newClasses.has(c)) el.classList.remove(c)\n })\n newClasses.forEach((c) => {\n if (!oldClasses.has(c)) el.classList.add(c)\n })\n\n oldClasses = newClasses\n })\n}\n\nfunction processStyleBinding(el: HTMLElement, expr: string, context: Context) {\n let oldStyles: Record<string, any> = {}\n\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n const newStyles = typeof val === 'object' && val !== null ? val : {}\n\n for (const key in oldStyles) {\n if (!(key in newStyles)) {\n ;(el.style as any)[key] = ''\n }\n }\n\n for (const key in newStyles) {\n if (oldStyles[key] !== newStyles[key]) {\n ;(el.style as any)[key] = newStyles[key]\n }\n }\n\n oldStyles = { ...newStyles }\n })\n}\n\nfunction processEventBinding(\n el: HTMLElement,\n evt: string,\n expr: string,\n context: Context,\n) {\n const refs = evt.split('@').pop()!\n const [name, ...modifiers] = refs.split('.')\n\n const isOutside = modifiers.includes('outside')\n const target = isOutside ? document : el\n\n const handler: EventListener = ($event: Event) => {\n if (!el.isConnected) return\n\n const isTargetNode = $event.target instanceof Node\n\n if (isOutside && isTargetNode && el.contains($event.target)) return\n if (modifiers.includes('self') && $event.target !== el) return\n\n if (modifiers.includes('prevent')) $event.preventDefault()\n if (modifiers.includes('stop')) $event.stopPropagation()\n\n evaluateEvent($event, expr, context)\n }\n\n target.addEventListener(name, handler)\n\n if (isOutside) {\n const bNode = el as BoundNode\n bNode._effects ??= []\n bNode._effects.push(() => target.removeEventListener(name, handler))\n }\n}\n","import { reactive, watch, Scope, type ScopeContext } from './reactivity'\nimport { createScope, type Context } from './parser'\n\nexport { reactive, watch, createScope, Scope }\nexport type { ScopeContext, Context }\n\nif (typeof window !== 'undefined') {\n const w = window as any\n w.jsweb = w.jsweb || {}\n w.jsweb.ui = { createScope, reactive, watch, Scope }\n}\n"],"mappings":"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"}
package/dist/index.umd.js CHANGED
@@ -1,2 +1,2 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self)["@jsweb/ui"]={})}(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});var t=null,n=new WeakMap,o=new WeakMap,i=new WeakMap,s=class{fn;active=!0;deps=new Set;constructor(e){this.fn=e}run(){if(!this.active)return this.fn();this.cleanup(),t=Symbol(),i.set(t,this);try{return this.fn()}finally{i.delete(t),t=null}}stop(){this.active&&(this.cleanup(),this.active=!1)}cleanup(){this.deps.forEach(e=>e.delete(this)),this.deps.clear()}effect(){return{run:()=>this.run(),stop:()=>this.stop()}}};function c(e){const t=new s(e);return t.run(),t.effect()}function r(e,t){const o=n.get(e);if(!o)return;const i=o.get(t);i&&new Set(i).forEach(e=>e.run())}function f(e){if("object"!=typeof e||null===e)return e;if(e instanceof Map||e instanceof Set||e instanceof WeakMap||e instanceof WeakSet||e instanceof Date||e instanceof RegExp||"function"==typeof Node&&e instanceof Node)return e;if(Object.hasOwn(e,"_isReactive"))return e;const s=o.get(e);if(s)return s;const c=new Proxy(e,{get(e,o,s){if("_isReactive"===o)return!0;!function(e,o){if(t){let s=n.get(e);s||(s=new Map,n.set(e,s));let c=s.get(o);c||(c=new Set,s.set(o,c));const r=i.get(t);r&&(c.add(r),r.deps.add(c))}}(e,o);const c=Reflect.get(e,o,s);return"object"==typeof c&&null!==c?f(c):c},set(e,t,n,o){const i=Array.isArray(e),s=Reflect.get(e,t,o),c=i&&String(Number(t))===t?Number(t)<e.length:Object.hasOwn(e,t),f=Reflect.set(e,t,n,o);return c?s!==n&&r(e,t):(r(e,t),i&&"length"!==t&&r(e,"length")),f}});return o.set(e,c),c}function u(e,t=new Set){if("object"!=typeof e||null===e||t.has(e))return e;t.add(e);for(const n in e)u(e[n],t);return e}function a(e,t,n){let o,i=!0;const s=e instanceof Function?e:()=>u(e);return c(()=>{const e=s();i?(i=!1,o=e,n?.immediate&&t(e,void 0)):(t(e,o),o=e)}).stop}function l(e,t={}){try{return new Function(`with(this) { return ${e} }`).call(t)}catch{return}}function d(e,t,n={}){try{const o=t.trim(),i=/^[a-zA-Z_$][0-9a-zA-Z_$.]*$/.test(o);new Function("$event",`with(this) { ${i?`${o} instanceof Function ? ${o}.call(this, $event) : ${o}`:o} }`).call(n,e)}catch{console.warn(`[jsweb/ui] Error evaluating event: ${t}`)}}function p(e){const t=e;t._effects&&(t._effects.forEach(e=>e()),t._effects=[]);const n=Array.from(e.childNodes);for(const o of n)p(o)}function h(e,t={}){const n=e._isReactive?e:f(e);return new Proxy(n,{get:(e,n)=>"_isContext"===n||(n in e?Reflect.get(e,n,e):n in t?Reflect.get(t,n,t):Reflect.get(e,n,e)),set:(e,n,o)=>n in e?Reflect.set(e,n,o,e):n in t?Reflect.set(t,n,o,t):Reflect.set(e,n,o,e),has:(e,n)=>n in e||n in t})}function m(e,t){if(e.nodeType!==Node.ELEMENT_NODE)return;const n=e,o=function(e,t){const n=["ui:scope",":scope"],o=y(e,n);if(!o)return t;const i=l(o,t);if(!i)return;g(e,n),i.$emit||(i.$emit=(t,n)=>{e.dispatchEvent(new CustomEvent(t,{detail:n,bubbles:!0,composed:!0}))});i.$refs||(i.$refs=t.$refs??new Map);return h(i,t)}(n,t);if(!o)return;const i=["ui:for",":for"],s=y(n,i);if(s)return g(n,i),void function(e,t,n){if(!e.parentNode)return;const o=/^\s*(.+)\s+(?:in|of)\s+(.+)\s*$/.exec(t);if(!o)return console.warn(`[jsweb/ui] Invalid ui:for expression: ${t}`);const[,i,s]=o,c=["ui:key",":key"],r=y(e,c);g(e,c);const u=crypto.randomUUID(),a=document.createComment(` ui:for ${u} `);e.replaceWith(a);let d=[];v(a,()=>{const t=l(s,n);if(!Array.isArray(t))return d.forEach(e=>{e.el.remove(),p(e.el)}),void(d=[]);const o=[],c=new Map;d.forEach(e=>c.set(e.key,e)),t.forEach((t,s)=>{let u=s;if(r){const e=h({[i]:t,$index:s},n);u=l(r,e)}const a={[i]:t,$index:s,$key:u};let d=c.get(u);if(d)d.scope[i]=t,d.scope.$index=s,d.scope.$key=u,c.delete(u);else{const t=e.cloneNode(!0),o=f(a);m(t,h(o,n)),d={key:u,el:t,scope:o}}o.push(d)}),c.forEach(e=>{e.el.remove(),p(e.el)});let u=a.nextSibling;o.forEach(e=>{u===e.el?u=u.nextSibling:a.parentNode?.insertBefore(e.el,u)}),d=o})}(n,s,o);const c=["ui:if",":if"],r=y(n,c);r&&(g(n,c),function(e,t,n){if(!e.parentNode)return;const o=crypto.randomUUID(),i=document.createComment(` ui:if ${o} `);e.before(i),v(i,()=>{l(t,n)?e.parentNode||i.parentNode?.insertBefore(e,i.nextSibling):e.parentNode&&e.remove()})}(n,r,o)),function(e,t){const n=Array.from(e.attributes);for(const o of n){const{name:n,value:i}=o,s=["ui:text",":text"].includes(n),c=["ui:bind",":bind"].includes(n),r=["ui:class",":class"].includes(n),f=["ui:style",":style"].includes(n),u=["ui:ref",":ref"].includes(n),a=n.startsWith("ui:")||n.startsWith(":"),l=n.startsWith("ui@")||n.startsWith("@");s?($(e,i,t),e.removeAttribute(n)):c?(E(e,i,t),e.removeAttribute(n)):r?(S(e,i,t),e.removeAttribute(n)):f?(x(e,i,t),e.removeAttribute(n)):u?(w(e,i,t),e.removeAttribute(n)):a?(A(e,n.split(":").pop(),i,t),e.removeAttribute(n)):l&&(j(e,n,i,t),e.removeAttribute(n))}}(n,o);const u=Array.from(n.childNodes);for(const f of u)m(f,o)}function b(e,t={}){const n="string"==typeof e?document.querySelector(e):e;n?(t.$emit||(t.$emit=(e,t)=>{n.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0}))}),t.$refs||(t.$refs=new Map),m(n,t)):console.warn("[jsweb/ui] Element not found:",e)}function v(e,t){const n=c(t),o=e;o._effects??=[],o._effects.push(n.stop)}function y(e,t){for(const n of t){const t=e.getAttribute(n);if(null!==t)return t}return null}function g(e,t){for(const n of t)e.removeAttribute(n)}function w(e,t,n){const o=t.trim().replace(/^['"]|['"]$/g,"");if(!o)return;const i=n.$refs;if(!i)return;const s=n.$key;if(void 0!==s){let t=i.get(o);t instanceof Map||(t=new Map,i.set(o,t)),t.set(s,e)}else i.set(o,e);const c=e;c._effects??=[],c._effects.push(()=>{if(void 0!==s){const e=i.get(o);e instanceof Map&&(e.delete(s),0===e.size&&i.delete(o))}else i.get(o)===e&&i.delete(o)})}function $(e,t,n){v(e,()=>{const o=l(t,n);e.textContent=null!=o?String(o):""})}function E(e,t,n){const o=e instanceof HTMLInputElement&&"checkbox"===e.type,i=e instanceof HTMLInputElement&&"radio"===e.type;v(e,()=>{const s=l(t,n);if(o)e.checked=!!s;else if(i)e.checked=e.value===String(s);else{e.value=null==s?"":String(s)}});const s=o||i||e instanceof HTMLSelectElement?"change":"input";e.addEventListener(s,e=>{d(e,`${t} = ${"$event.target."+(o?"checked":"value")}`,n)})}function A(e,t,n,o){v(e,()=>{const i=l(n,o);null==i||!1===i?e.removeAttribute(t):!0===i?e.setAttribute(t,""):e.setAttribute(t,String(i))})}function S(e,t,n){let o=new Set;v(e,()=>{const i=l(t,n),s=new Set,c=e=>e&&s.add(e),r=e=>e.split(/\s+/).forEach(c);"string"==typeof i?r(i):Array.isArray(i)?i.flat().forEach(e=>{"string"==typeof e&&r(e)}):"object"==typeof i&&null!==i&&Object.entries(i).forEach(([e,t])=>{t&&r(e)}),o.forEach(t=>{s.has(t)||e.classList.remove(t)}),s.forEach(t=>{o.has(t)||e.classList.add(t)}),o=s})}function x(e,t,n){let o={};v(e,()=>{const i=l(t,n),s="object"==typeof i&&null!==i?i:{};for(const t in o)t in s||(e.style[t]="");for(const t in s)o[t]!==s[t]&&(e.style[t]=s[t]);o={...s}})}function j(e,t,n,o){const[i,...s]=t.split("@").pop().split("."),c=s.includes("outside"),r=c?document:e,f=t=>{if(!e.isConnected)return;const i=t.target instanceof Node;c&&i&&e.contains(t.target)||s.includes("self")&&t.target!==e||(s.includes("prevent")&&t.preventDefault(),s.includes("stop")&&t.stopPropagation(),d(t,n,o))};if(r.addEventListener(i,f),c){const t=e;t._effects??=[],t._effects.push(()=>r.removeEventListener(i,f))}}if("undefined"!=typeof window){const e=window;e.jsweb=e.jsweb||{},e.jsweb.ui={createScope:b,reactive:f,watch:a}}e.createScope=b,e.reactive=f,e.watch=a});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self)["@jsweb/ui"]={})}(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});var t=null,n=new WeakMap,o=new WeakMap,s=new WeakMap,i=class{fn;active=!0;deps=new Set;constructor(e){this.fn=e}run(){if(!this.active)return this.fn();this.cleanup(),t=Symbol(),s.set(t,this);try{return this.fn()}finally{s.delete(t),t=null}}stop(){this.active&&(this.cleanup(),this.active=!1)}cleanup(){this.deps.forEach(e=>e.delete(this)),this.deps.clear()}effect(){return{run:()=>this.run(),stop:()=>this.stop()}}};function c(e){const t=new i(e);return t.run(),t.effect()}function r(e,t){const o=n.get(e);if(!o)return;const s=o.get(t);s&&new Set(s).forEach(e=>e.run())}var f=class{$refs=new Map;$emit(e,t){(this.$el||("undefined"!=typeof window?window:null))?.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0}))}constructor(e){e&&"object"==typeof e&&Object.assign(this,e)}};function u(e){if("object"!=typeof e||null===e)return e;if(e instanceof Map||e instanceof Set||e instanceof WeakMap||e instanceof WeakSet||e instanceof Date||e instanceof RegExp||"function"==typeof Node&&e instanceof Node)return e;if(Object.hasOwn(e,"_isReactive"))return e;const i=o.get(e);if(i)return i;const c=new Proxy(e,{get(e,o,i){if("_isReactive"===o)return!0;!function(e,o){if(t){let i=n.get(e);i||(i=new Map,n.set(e,i));let c=i.get(o);c||(c=new Set,i.set(o,c));const r=s.get(t);r&&(c.add(r),r.deps.add(c))}}(e,o);const c=Reflect.get(e,o,i);return"object"==typeof c&&null!==c?u(c):c},set(e,t,n,o){const s=Array.isArray(e),i=Reflect.get(e,t,o),c=s&&String(Number(t))===t?Number(t)<e.length:Object.hasOwn(e,t),f=Reflect.set(e,t,n,o);return c?i!==n&&r(e,t):(r(e,t),s&&"length"!==t&&r(e,"length")),f}});return o.set(e,c),c}function a(e,t=new Set){if("object"!=typeof e||null===e||t.has(e))return e;t.add(e);for(const n in e)a(e[n],t);return e}function l(e,t,n){let o,s=!0;const i=e instanceof Function?e:()=>a(e);return c(()=>{const e=i();s?(s=!1,o=e,n?.immediate&&t(e,void 0)):(t(e,o),o=e)}).stop}function d(e,t={}){try{return new Function(`with(this) { return ${e} }`).call(t)}catch{return}}function p(e,t,n={}){try{const o=t.trim(),s=/^[a-zA-Z_$][0-9a-zA-Z_$.]*$/.test(o);new Function("$event",`with(this) { ${s?`${o} instanceof Function ? ${o}.call(this, $event) : ${o}`:o} }`).call(n,e)}catch{console.warn(`[jsweb/ui] Error evaluating event: ${t}`)}}function h(e){const t=e;t._effects&&(t._effects.forEach(e=>e()),t._effects=[]);const n=Array.from(e.childNodes);for(const o of n)h(o)}function b(e,t={}){const n=e._isReactive?e:u(e);return new Proxy(n,{get:(e,n)=>"_isContext"===n||(n in e?Reflect.get(e,n,e):n in t?Reflect.get(t,n,t):Reflect.get(e,n,e)),set:(e,n,o)=>n in e?Reflect.set(e,n,o,e):n in t?Reflect.set(t,n,o,t):Reflect.set(e,n,o,e),has:(e,n)=>n in e||n in t})}function m(e,t){if(e.nodeType!==Node.ELEMENT_NODE)return;const n=e,o=function(e,t){const n=["ui:scope",":scope"],o=w(e,n);if(!o)return t;const s=d(o,t);if(!s)return;g(e,n),s.$el=e,s.$emit||(s.$emit=(t,n)=>{e.dispatchEvent(new CustomEvent(t,{detail:n,bubbles:!0,composed:!0}))});s.$refs||(s.$refs=t.$refs??new Map);return b(s,t)}(n,t);if(!o)return;const s=["ui:for",":for"],i=w(n,s);if(i)return g(n,s),void function(e,t,n){if(!e.parentNode)return;const o=/^\s*(.+)\s+(?:in|of)\s+(.+)\s*$/.exec(t);if(!o)return console.warn(`[jsweb/ui] Invalid ui:for expression: ${t}`);const[,s,i]=o,c=["ui:key",":key"],r=w(e,c);g(e,c);const f=crypto.randomUUID(),a=document.createComment(` ui:for ${f} `);e.replaceWith(a);let l=[];y(a,()=>{const t=d(i,n);if(!Array.isArray(t))return l.forEach(e=>{e.el.remove(),h(e.el)}),void(l=[]);const o=[],c=new Map;l.forEach(e=>c.set(e.key,e)),t.forEach((t,i)=>{let f=i;if(r){const e=b({[s]:t,$index:i},n);f=d(r,e)}let a=c.get(f);if(a)a.scope[s]=t,a.scope.$index=i,a.scope.$key=f,a.scope.$el=a.el,c.delete(f);else{const o=e.cloneNode(!0),c=u({[s]:t,$index:i,$key:f,$el:o});m(o,b(c,n)),a={key:f,el:o,scope:c}}o.push(a)}),c.forEach(e=>{e.el.remove(),h(e.el)});let f=a.nextSibling;o.forEach(e=>{f===e.el?f=f.nextSibling:a.parentNode?.insertBefore(e.el,f)}),l=o})}(n,i,o);const c=["ui:if",":if"],r=w(n,c);r&&(g(n,c),function(e,t,n){if(!e.parentNode)return;const o=crypto.randomUUID(),s=document.createComment(` ui:if ${o} `);e.before(s),y(s,()=>{d(t,n)?e.parentNode||s.parentNode?.insertBefore(e,s.nextSibling):e.parentNode&&e.remove()})}(n,r,o)),function(e,t){const n=Array.from(e.attributes);for(const o of n){const{name:n,value:s}=o,i=["ui:text",":text"].includes(n),c=["ui:bind",":bind"].includes(n),r=["ui:class",":class"].includes(n),f=["ui:style",":style"].includes(n),u=["ui:ref",":ref"].includes(n),a=n.startsWith("ui:")||n.startsWith(":"),l=n.startsWith("ui@")||n.startsWith("@");i?(E(e,s,t),e.removeAttribute(n)):c?(S(e,s,t),e.removeAttribute(n)):r?(j(e,s,t),e.removeAttribute(n)):f?(x(e,s,t),e.removeAttribute(n)):u?($(e,s,t),e.removeAttribute(n)):a?(A(e,n.split(":").pop(),s,t),e.removeAttribute(n)):l&&(M(e,n,s,t),e.removeAttribute(n))}}(n,o);const f=Array.from(n.childNodes);for(const u of f)m(u,o)}function v(e,t){const n="string"==typeof e?document.querySelector(e):e;if(n){const e=t??{};e.$el=n,e.$emit||(e.$emit=(e,t)=>{n.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0}))}),e.$refs||(e.$refs=new Map),m(n,e)}else console.warn("[jsweb/ui] Element not found:",e)}function y(e,t){const n=c(t),o=e;o._effects??=[],o._effects.push(n.stop)}function w(e,t){for(const n of t){const t=e.getAttribute(n);if(null!==t)return t}return null}function g(e,t){for(const n of t)e.removeAttribute(n)}function $(e,t,n){const o=t.trim().replace(/^['"]|['"]$/g,"");if(!o)return;const s=n.$refs;if(!s)return;const i=n.$key;if(void 0!==i){let t=s.get(o);t instanceof Map||(t=new Map,s.set(o,t)),t.set(i,e)}else s.set(o,e);const c=e;c._effects??=[],c._effects.push(()=>{if(void 0!==i){const e=s.get(o);e instanceof Map&&(e.delete(i),0===e.size&&s.delete(o))}else s.get(o)===e&&s.delete(o)})}function E(e,t,n){y(e,()=>{const o=d(t,n);e.textContent=null!=o?String(o):""})}function S(e,t,n){const o=e instanceof HTMLInputElement&&"checkbox"===e.type,s=e instanceof HTMLInputElement&&"radio"===e.type;y(e,()=>{const i=d(t,n);if(o)e.checked=!!i;else if(s)e.checked=e.value===String(i);else{e.value=null==i?"":String(i)}});const i=o||s||e instanceof HTMLSelectElement?"change":"input";e.addEventListener(i,e=>{p(e,`${t} = ${"$event.target."+(o?"checked":"value")}`,n)})}function A(e,t,n,o){y(e,()=>{const s=d(n,o);null==s||!1===s?e.removeAttribute(t):!0===s?e.setAttribute(t,""):e.setAttribute(t,String(s))})}function j(e,t,n){let o=new Set;y(e,()=>{const s=d(t,n),i=new Set,c=e=>e&&i.add(e),r=e=>e.split(/\s+/).forEach(c);"string"==typeof s?r(s):Array.isArray(s)?s.flat().forEach(e=>{"string"==typeof e&&r(e)}):"object"==typeof s&&null!==s&&Object.entries(s).forEach(([e,t])=>{t&&r(e)}),o.forEach(t=>{i.has(t)||e.classList.remove(t)}),i.forEach(t=>{o.has(t)||e.classList.add(t)}),o=i})}function x(e,t,n){let o={};y(e,()=>{const s=d(t,n),i="object"==typeof s&&null!==s?s:{};for(const t in o)t in i||(e.style[t]="");for(const t in i)o[t]!==i[t]&&(e.style[t]=i[t]);o={...i}})}function M(e,t,n,o){const[s,...i]=t.split("@").pop().split("."),c=i.includes("outside"),r=c?document:e,f=t=>{if(!e.isConnected)return;const s=t.target instanceof Node;c&&s&&e.contains(t.target)||i.includes("self")&&t.target!==e||(i.includes("prevent")&&t.preventDefault(),i.includes("stop")&&t.stopPropagation(),p(t,n,o))};if(r.addEventListener(s,f),c){const t=e;t._effects??=[],t._effects.push(()=>r.removeEventListener(s,f))}}if("undefined"!=typeof window){const e=window;e.jsweb=e.jsweb||{},e.jsweb.ui={createScope:v,reactive:u,watch:l,Scope:f}}e.Scope=f,e.createScope=v,e.reactive=u,e.watch=l});
2
2
  //# sourceMappingURL=index.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.umd.js","names":[],"sources":["../src/reactivity.ts","../src/evaluator.ts","../src/parser.ts","../src/index.ts"],"sourcesContent":["let activeEffect: symbol | null = null\nconst targetMap = new WeakMap<\n object,\n Map<string | symbol, Set<ReactiveEffect>>\n>()\nconst proxyMap = new WeakMap<object, any>()\nconst effectMap = new WeakMap<symbol, ReactiveEffect>()\n\nexport class ReactiveEffect {\n active = true\n deps: Set<Set<ReactiveEffect>> = new Set()\n\n constructor(public fn: () => void) {}\n\n run() {\n if (!this.active) return this.fn()\n\n this.cleanup()\n\n activeEffect = Symbol()\n effectMap.set(activeEffect, this)\n\n try {\n return this.fn()\n } finally {\n effectMap.delete(activeEffect)\n activeEffect = null\n }\n }\n\n stop() {\n if (this.active) {\n this.cleanup()\n this.active = false\n }\n }\n\n cleanup() {\n this.deps.forEach((dep) => dep.delete(this))\n this.deps.clear()\n }\n\n effect() {\n return {\n run: () => this.run(),\n stop: () => this.stop(),\n }\n }\n}\n\nexport function effect(fn: () => void) {\n const ref = new ReactiveEffect(fn)\n ref.run()\n return ref.effect()\n}\n\nexport function track(target: object, key: string | symbol) {\n if (activeEffect) {\n let depsMap = targetMap.get(target)\n if (!depsMap) {\n depsMap = new Map()\n targetMap.set(target, depsMap)\n }\n\n let dep = depsMap.get(key)\n if (!dep) {\n dep = new Set()\n depsMap.set(key, dep)\n }\n\n const active = effectMap.get(activeEffect)\n if (active) {\n dep.add(active)\n active.deps.add(dep)\n }\n }\n}\n\nexport function trigger(target: object, key: string | symbol) {\n const depsMap = targetMap.get(target)\n if (!depsMap) return\n\n const dep = depsMap.get(key)\n if (dep) {\n const effects = new Set(dep)\n effects.forEach((effect) => effect.run())\n }\n}\n\nexport function reactive<T extends object>(target: T): T {\n const notObject = typeof target !== 'object' || target === null\n if (notObject) return target\n\n if (\n target instanceof Map ||\n target instanceof Set ||\n target instanceof WeakMap ||\n target instanceof WeakSet ||\n target instanceof Date ||\n target instanceof RegExp ||\n (typeof Node === 'function' && target instanceof Node)\n ) {\n return target\n }\n\n const isReactive = Object.hasOwn(target, '_isReactive')\n if (isReactive) return target\n\n const existingProxy = proxyMap.get(target)\n if (existingProxy) return existingProxy\n\n const proxy = new Proxy(target, {\n get(obj, key, receiver) {\n if (key === '_isReactive') return true\n track(obj, key)\n\n const res = Reflect.get(obj, key, receiver)\n // deep reactivity\n return typeof res === 'object' && res !== null ? reactive(res) : res\n },\n set(obj, key, value, receiver) {\n const isArray = Array.isArray(obj)\n const oldValue = Reflect.get(obj, key, receiver)\n const hadKey =\n isArray && String(Number(key)) === key\n ? Number(key) < obj.length\n : Object.hasOwn(obj, key)\n\n const result = Reflect.set(obj, key, value, receiver)\n\n if (!hadKey) {\n trigger(obj, key)\n if (isArray && key !== 'length') {\n trigger(obj, 'length')\n }\n } else if (oldValue !== value) {\n trigger(obj, key)\n }\n\n return result\n },\n })\n\n proxyMap.set(target, proxy)\n return proxy\n}\n\nexport function traverse(value: any, seen = new Set()) {\n if (typeof value !== 'object' || value === null || seen.has(value)) {\n return value\n }\n seen.add(value)\n for (const key in value) {\n traverse(value[key], seen)\n }\n return value\n}\n\nexport function watch(\n source: any | (() => any),\n cb: (newValue: any, oldValue: any) => void,\n options?: { immediate?: boolean },\n) {\n let oldValue: any\n let isFirstRun = true\n\n const getter = source instanceof Function ? source : () => traverse(source)\n\n const runner = effect(() => {\n const newValue = getter()\n\n if (isFirstRun) {\n isFirstRun = false\n oldValue = newValue\n if (options?.immediate) {\n cb(newValue, undefined)\n }\n } else {\n cb(newValue, oldValue)\n oldValue = newValue\n }\n })\n\n return runner.stop\n}\n","export function evaluate(\n expression: string,\n context: Record<string, any> = {},\n) {\n try {\n const fn = new Function(`with(this) { return ${expression} }`)\n return fn.call(context)\n } catch {\n return undefined\n }\n}\n\nexport function evaluateEvent(\n $event: Event,\n expression: string,\n context: Record<string, any> = {},\n) {\n try {\n const exp = expression.trim()\n const isIdentifier = /^[a-zA-Z_$][0-9a-zA-Z_$.]*$/.test(exp)\n const code = `${exp} instanceof Function ? ${exp}.call(this, $event) : ${exp}`\n const result = isIdentifier ? code : exp\n const fn = new Function('$event', `with(this) { ${result} }`)\n\n fn.call(context, $event)\n } catch {\n console.warn(`[jsweb/ui] Error evaluating event: ${expression}`)\n }\n}\n","import { effect, reactive } from './reactivity'\nimport { evaluate, evaluateEvent } from './evaluator'\n\nexport type Context = Record<string, any>\n\ninterface BoundNode extends Node {\n _effects?: Array<() => void>\n}\n\nexport function cleanupTree(node: Node) {\n const bNode = node as BoundNode\n if (bNode._effects) {\n bNode._effects.forEach((stop) => stop())\n bNode._effects = []\n }\n const children = Array.from(node.childNodes)\n for (const child of children) cleanupTree(child)\n}\n\nexport function createContext(scope: any, context: Context = {}): Context {\n const reactiveScope = scope._isReactive ? scope : reactive(scope)\n\n return new Proxy(reactiveScope, {\n get(target, prop) {\n if (prop === '_isContext') return true\n if (prop in target) return Reflect.get(target, prop, target)\n if (prop in context) {\n return Reflect.get(context, prop, context)\n }\n return Reflect.get(target, prop, target)\n },\n set(target, prop, value) {\n if (prop in target) return Reflect.set(target, prop, value, target)\n if (prop in context) {\n return Reflect.set(context, prop, value, context)\n }\n return Reflect.set(target, prop, value, target)\n },\n has(target, prop) {\n if (prop in target) return true\n if (prop in context) return true\n return false\n },\n })\n}\n\nexport function parseNode(node: Node, context: Context) {\n if (node.nodeType !== Node.ELEMENT_NODE) return\n\n const el = node as HTMLElement\n const scope = processScope(el, context)\n if (!scope) return\n\n const forAttrs = ['ui:for', ':for']\n const forDirective = getDirectiveValue(el, forAttrs)\n if (forDirective) {\n removeDirectiveAttributes(el, forAttrs)\n processFor(el, forDirective, scope)\n return\n }\n\n const ifAttrs = ['ui:if', ':if']\n const ifDirective = getDirectiveValue(el, ifAttrs)\n if (ifDirective) {\n removeDirectiveAttributes(el, ifAttrs)\n processIf(el, ifDirective, scope)\n }\n\n processAttributes(el, scope)\n\n const children = Array.from(el.childNodes)\n for (const child of children) parseNode(child, scope)\n}\n\nexport function createScope(\n selectorOrElement: string | HTMLElement,\n context: Context = {},\n) {\n const el =\n typeof selectorOrElement === 'string'\n ? document.querySelector(selectorOrElement)\n : selectorOrElement\n\n if (el) {\n if (!context.$emit) {\n context.$emit = (eventName: string, detail?: any) => {\n el.dispatchEvent(\n new CustomEvent(eventName, { detail, bubbles: true, composed: true }),\n )\n }\n }\n\n if (!context.$refs) {\n context.$refs = new Map<string, any>()\n }\n\n parseNode(el, context)\n } else {\n console.warn('[jsweb/ui] Element not found:', selectorOrElement)\n }\n}\n\nfunction bindEffect(node: Node, fn: () => void) {\n const e = effect(fn)\n const bNode = node as BoundNode\n bNode._effects ??= []\n bNode._effects.push(e.stop)\n}\n\nfunction getDirectiveValue(el: HTMLElement, names: string[]) {\n for (const name of names) {\n const value = el.getAttribute(name)\n if (value !== null) return value\n }\n return null\n}\n\nfunction removeDirectiveAttributes(el: HTMLElement, names: string[]) {\n for (const name of names) {\n el.removeAttribute(name)\n }\n}\n\nfunction processScope(el: HTMLElement, context: Context) {\n const attrs = ['ui:scope', ':scope']\n const directive = getDirectiveValue(el, attrs)\n if (!directive) return context\n\n const scope = evaluate(directive, context)\n if (!scope) return undefined\n\n removeDirectiveAttributes(el, attrs)\n\n if (!scope.$emit) {\n scope.$emit = (event: string, detail?: any) => {\n el.dispatchEvent(\n new CustomEvent(event, { detail, bubbles: true, composed: true }),\n )\n }\n }\n\n if (!scope.$refs) {\n scope.$refs = context.$refs ?? new Map<string, any>()\n }\n\n return createContext(scope, context)\n}\n\nfunction processFor(el: HTMLElement, expr: string, context: Context) {\n const parent = el.parentNode\n if (!parent) return\n\n const match = /^\\s*(.+)\\s+(?:in|of)\\s+(.+)\\s*$/.exec(expr)\n if (!match) {\n return console.warn(`[jsweb/ui] Invalid ui:for expression: ${expr}`)\n }\n const [, itemName, listName] = match\n\n const keyAttr = ['ui:key', ':key']\n const keyDirective = getDirectiveValue(el, keyAttr)\n removeDirectiveAttributes(el, keyAttr)\n\n const uuid = crypto.randomUUID()\n const comment = document.createComment(` ui:for ${uuid} `)\n el.replaceWith(comment)\n\n interface RenderedNode {\n key: any\n el: HTMLElement\n scope: any\n }\n let renderedNodes: RenderedNode[] = []\n\n bindEffect(comment, () => {\n const list = evaluate(listName, context)\n\n if (!Array.isArray(list)) {\n renderedNodes.forEach((node) => {\n node.el.remove()\n cleanupTree(node.el)\n })\n renderedNodes = []\n return\n }\n\n const newNodes: RenderedNode[] = []\n const oldNodesByKey = new Map<any, RenderedNode>()\n renderedNodes.forEach((node) => oldNodesByKey.set(node.key, node))\n\n list.forEach((item, index) => {\n let key: any = index\n\n if (keyDirective) {\n const tempContext = createContext(\n { [itemName]: item, $index: index },\n context,\n )\n key = evaluate(keyDirective, tempContext)\n }\n\n const scope = { [itemName]: item, $index: index, $key: key }\n\n let node = oldNodesByKey.get(key)\n if (node) {\n // Reuse node\n node.scope[itemName] = item\n node.scope.$index = index\n node.scope.$key = key\n oldNodesByKey.delete(key)\n } else {\n // Create new node\n const clone = el.cloneNode(true) as HTMLElement\n const reactiveScope = reactive(scope)\n const localContext = createContext(reactiveScope, context)\n parseNode(clone, localContext)\n node = { key, el: clone, scope: reactiveScope }\n }\n\n newNodes.push(node)\n })\n\n // Remove un-reused nodes\n oldNodesByKey.forEach((node) => {\n node.el.remove()\n cleanupTree(node.el)\n })\n\n // Reorder and insert new DOM nodes\n let currentAnchor = comment.nextSibling\n newNodes.forEach((node) => {\n if (currentAnchor === node.el) {\n currentAnchor = currentAnchor.nextSibling\n } else {\n comment.parentNode?.insertBefore(node.el, currentAnchor)\n }\n })\n\n renderedNodes = newNodes\n })\n}\n\nfunction processIf(el: HTMLElement, expr: string, context: Context) {\n const parent = el.parentNode\n if (!parent) return\n\n const uuid = crypto.randomUUID()\n const comment = document.createComment(` ui:if ${uuid} `)\n el.before(comment)\n\n bindEffect(comment, () => {\n const val = evaluate(expr, context)\n if (val) {\n if (!el.parentNode) {\n comment.parentNode?.insertBefore(el, comment.nextSibling)\n }\n } else if (el.parentNode) {\n el.remove()\n }\n })\n}\n\nfunction processAttributes(el: HTMLElement, context: Context) {\n const attrs = Array.from(el.attributes)\n\n for (const attr of attrs) {\n const { name, value } = attr\n const isText = ['ui:text', ':text'].includes(name)\n const isTwoWayBind = ['ui:bind', ':bind'].includes(name)\n const isClassBind = ['ui:class', ':class'].includes(name)\n const isStyleBind = ['ui:style', ':style'].includes(name)\n const isRef = ['ui:ref', ':ref'].includes(name)\n const isAttrBind = name.startsWith('ui:') || name.startsWith(':')\n const isEvent = name.startsWith('ui@') || name.startsWith('@')\n\n if (isText) {\n processTextBinding(el, value, context)\n el.removeAttribute(name)\n } else if (isTwoWayBind) {\n processTwoWayBinding(el, value, context)\n el.removeAttribute(name)\n } else if (isClassBind) {\n processClassBinding(el, value, context)\n el.removeAttribute(name)\n } else if (isStyleBind) {\n processStyleBinding(el, value, context)\n el.removeAttribute(name)\n } else if (isRef) {\n processRef(el, value, context)\n el.removeAttribute(name)\n } else if (isAttrBind) {\n const bound = name.split(':').pop()!\n processAttrBinding(el, bound, value, context)\n el.removeAttribute(name)\n } else if (isEvent) {\n processEventBinding(el, name, value, context)\n el.removeAttribute(name)\n }\n }\n}\n\nfunction processRef(el: HTMLElement, expr: string, context: Context) {\n const refName = expr.trim().replace(/^['\"]|['\"]$/g, '')\n if (!refName) return\n\n const refs = context.$refs as Map<string, any>\n if (!refs) return\n\n const key = context.$key\n\n if (key !== undefined) {\n let group = refs.get(refName)\n if (!(group instanceof Map)) {\n group = new Map<any, HTMLElement>()\n refs.set(refName, group)\n }\n group.set(key, el)\n } else {\n refs.set(refName, el)\n }\n\n const bNode = el as BoundNode\n bNode._effects ??= []\n bNode._effects.push(() => {\n if (key !== undefined) {\n const group = refs.get(refName)\n if (group instanceof Map) {\n group.delete(key)\n if (group.size === 0) {\n refs.delete(refName)\n }\n }\n } else if (refs.get(refName) === el) {\n refs.delete(refName)\n }\n })\n}\n\nfunction processTextBinding(el: HTMLElement, expr: string, context: Context) {\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n el.textContent = val !== undefined && val !== null ? String(val) : ''\n })\n}\n\nfunction processTwoWayBinding(el: HTMLElement, expr: string, context: Context) {\n const isCheckbox = el instanceof HTMLInputElement && el.type === 'checkbox'\n const isRadio = el instanceof HTMLInputElement && el.type === 'radio'\n\n // 1. Reactive state to DOM\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n if (isCheckbox) {\n el.checked = !!val\n } else if (isRadio) {\n el.checked = el.value === String(val)\n } else {\n const target = el as\n | HTMLInputElement\n | HTMLSelectElement\n | HTMLTextAreaElement\n target.value = val == null ? '' : String(val)\n }\n })\n\n // 2. DOM to Reactive state\n const isChange = isCheckbox || isRadio || el instanceof HTMLSelectElement\n const eventName = isChange ? 'change' : 'input'\n el.addEventListener(eventName, ($event) => {\n const target = isCheckbox ? 'checked' : 'value'\n const value = `$event.target.${target}`\n evaluateEvent($event, `${expr} = ${value}`, context)\n })\n}\n\nfunction processAttrBinding(\n el: HTMLElement,\n attr: string,\n expr: string,\n context: Context,\n) {\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n if (val === null || val === undefined || val === false) {\n el.removeAttribute(attr)\n } else if (val === true) {\n el.setAttribute(attr, '')\n } else {\n el.setAttribute(attr, String(val))\n }\n })\n}\n\nfunction processClassBinding(el: HTMLElement, expr: string, context: Context) {\n let oldClasses = new Set<string>()\n\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n const newClasses = new Set<string>()\n const addClass = (c: string) => c && newClasses.add(c)\n const addClasses = (c: string) => c.split(/\\s+/).forEach(addClass)\n\n if (typeof val === 'string') addClasses(val)\n else if (Array.isArray(val)) {\n val.flat().forEach((c: any) => {\n if (typeof c === 'string') addClasses(c)\n })\n } else if (typeof val === 'object' && val !== null) {\n Object.entries(val).forEach(([c, condition]: [string, any]) => {\n if (condition) addClasses(c)\n })\n }\n\n oldClasses.forEach((c) => {\n if (!newClasses.has(c)) el.classList.remove(c)\n })\n newClasses.forEach((c) => {\n if (!oldClasses.has(c)) el.classList.add(c)\n })\n\n oldClasses = newClasses\n })\n}\n\nfunction processStyleBinding(el: HTMLElement, expr: string, context: Context) {\n let oldStyles: Record<string, any> = {}\n\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n const newStyles = typeof val === 'object' && val !== null ? val : {}\n\n for (const key in oldStyles) {\n if (!(key in newStyles)) {\n ;(el.style as any)[key] = ''\n }\n }\n\n for (const key in newStyles) {\n if (oldStyles[key] !== newStyles[key]) {\n ;(el.style as any)[key] = newStyles[key]\n }\n }\n\n oldStyles = { ...newStyles }\n })\n}\n\nfunction processEventBinding(\n el: HTMLElement,\n evt: string,\n expr: string,\n context: Context,\n) {\n const refs = evt.split('@').pop()!\n const [name, ...modifiers] = refs.split('.')\n\n const isOutside = modifiers.includes('outside')\n const target = isOutside ? document : el\n\n const handler: EventListener = ($event: Event) => {\n if (!el.isConnected) return\n\n const isTargetNode = $event.target instanceof Node\n\n if (isOutside && isTargetNode && el.contains($event.target)) return\n if (modifiers.includes('self') && $event.target !== el) return\n\n if (modifiers.includes('prevent')) $event.preventDefault()\n if (modifiers.includes('stop')) $event.stopPropagation()\n\n evaluateEvent($event, expr, context)\n }\n\n target.addEventListener(name, handler)\n\n if (isOutside) {\n const bNode = el as BoundNode\n bNode._effects ??= []\n bNode._effects.push(() => target.removeEventListener(name, handler))\n }\n}\n","import { reactive, watch } from './reactivity'\nimport { createScope } from './parser'\n\nexport { reactive, watch, createScope }\n\nif (typeof window !== 'undefined') {\n const w = window as any\n w.jsweb = w.jsweb || {}\n w.jsweb.ui = { createScope, reactive, watch }\n}\n"],"mappings":"mSAAA,IAAI,EAA8B,KAC5B,EAAY,IAAI,QAIhB,EAAW,IAAI,QACf,EAAY,IAAI,QAET,EAAb,MAIqB,GAHnB,QAAS,EACT,KAAiC,IAAI,IAErC,WAAA,CAAY,GAAO,KAAA,GAAA,CAAiB,CAEpC,GAAA,GACE,IAAK,KAAK,OAAQ,OAAO,KAAK,KAE9B,KAAK,UAEL,EAAe,SACf,EAAU,IAAI,EAAc,MAE5B,IACE,OAAO,KAAK,IACd,CAAA,QACE,EAAU,OAAO,GACjB,EAAe,IACjB,CACF,CAEA,IAAA,GACM,KAAK,SACP,KAAK,UACL,KAAK,QAAS,EAElB,CAEA,OAAA,GACE,KAAK,KAAK,QAAS,GAAQ,EAAI,OAAO,OACtC,KAAK,KAAK,OACZ,CAEA,MAAA,GACE,MAAO,CACL,IAAA,IAAW,KAAK,MAChB,KAAA,IAAY,KAAK,OAErB,GAGF,SAAgB,EAAO,GACrB,MAAM,EAAM,IAAI,EAAe,GAE/B,OADA,EAAI,MACG,EAAI,QACb,CAwBA,SAAgB,EAAQ,EAAgB,GACtC,MAAM,EAAU,EAAU,IAAI,GAC9B,IAAK,EAAS,OAEd,MAAM,EAAM,EAAQ,IAAI,GACpB,GAEF,IADoB,IAAI,GAChB,QAAS,GAAW,EAAO,MAEvC,CAEA,SAAgB,EAA2B,GAEzC,GADoC,iBAAX,GAAkC,OAAX,EACjC,OAAO,EAEtB,GACE,aAAkB,KAClB,aAAkB,KAClB,aAAkB,SAClB,aAAkB,SAClB,aAAkB,MAClB,aAAkB,QACD,mBAAT,MAAuB,aAAkB,KAEjD,OAAO,EAIT,GADmB,OAAO,OAAO,EAAQ,eACzB,OAAO,EAEvB,MAAM,EAAgB,EAAS,IAAI,GACnC,GAAI,EAAe,OAAO,EAE1B,MAAM,EAAQ,IAAI,MAAM,EAAQ,CAC9B,GAAA,CAAI,EAAK,EAAK,GACZ,GAAY,gBAAR,EAAuB,OAAO,GAzDxC,SAAsB,EAAgB,GACpC,GAAI,EAAc,CAChB,IAAI,EAAU,EAAU,IAAI,GACvB,IACH,EAAU,IAAI,IACd,EAAU,IAAI,EAAQ,IAGxB,IAAI,EAAM,EAAQ,IAAI,GACjB,IACH,EAAM,IAAI,IACV,EAAQ,IAAI,EAAK,IAGnB,MAAM,EAAS,EAAU,IAAI,GACzB,IACF,EAAI,IAAI,GACR,EAAO,KAAK,IAAI,GAEpB,CACF,CAsCM,CAAM,EAAK,GAEX,MAAM,EAAM,QAAQ,IAAI,EAAK,EAAK,GAElC,MAAsB,iBAAR,GAA4B,OAAR,EAAe,EAAS,GAAO,CACnE,EACA,GAAA,CAAI,EAAK,EAAK,EAAO,GACnB,MAAM,EAAU,MAAM,QAAQ,GACxB,EAAW,QAAQ,IAAI,EAAK,EAAK,GACjC,EACJ,GAAW,OAAO,OAAO,MAAU,EAC/B,OAAO,GAAO,EAAI,OAClB,OAAO,OAAO,EAAK,GAEnB,EAAS,QAAQ,IAAI,EAAK,EAAK,EAAO,GAW5C,OATK,EAKM,IAAa,GACtB,EAAQ,EAAK,IALb,EAAQ,EAAK,GACT,GAAmB,WAAR,GACb,EAAQ,EAAK,WAMV,CACT,IAIF,OADA,EAAS,IAAI,EAAQ,GACd,CACT,CAEA,SAAgB,EAAS,EAAY,EAAO,IAAI,KAC9C,GAAqB,iBAAV,GAAgC,OAAV,GAAkB,EAAK,IAAI,GAC1D,OAAO,EAET,EAAK,IAAI,GACT,IAAK,MAAM,KAAO,EAChB,EAAS,EAAM,GAAM,GAEvB,OAAO,CACT,CAEA,SAAgB,EACd,EACA,EACA,GAEA,IAAI,EACA,GAAa,EAEjB,MAAM,EAAS,aAAkB,SAAW,EAAA,IAAe,EAAS,GAiBpE,OAfe,EAAA,KACb,MAAM,EAAW,IAEb,GACF,GAAa,EACb,EAAW,EACP,GAAS,WACX,EAAG,OAAU,KAGf,EAAG,EAAU,GACb,EAAW,KAID,IAChB,CCxLA,SAAgB,EACd,EACA,EAA+B,CAAC,GAEhC,IAEE,OAAO,IADQ,SAAS,uBAAuB,OACrC,KAAK,EACjB,CAAA,MACE,MACF,CACF,CAEA,SAAgB,EACd,EACA,EACA,EAA+B,CAAC,GAEhC,IACE,MAAM,EAAM,EAAW,OACjB,EAAe,8BAA8B,KAAK,GAKxD,IAFe,SAAS,SAAU,gBADnB,EADF,GAAG,2BAA6B,0BAA4B,IACpC,OAGlC,KAAK,EAAS,EACnB,CAAA,MACE,QAAQ,KAAK,sCAAsC,IACrD,CACF,CCnBA,SAAgB,EAAY,GAC1B,MAAM,EAAQ,EACV,EAAM,WACR,EAAM,SAAS,QAAS,GAAS,KACjC,EAAM,SAAW,IAEnB,MAAM,EAAW,MAAM,KAAK,EAAK,YACjC,IAAK,MAAM,KAAS,EAAU,EAAY,EAC5C,CAEA,SAAgB,EAAc,EAAY,EAAmB,CAAC,GAC5D,MAAM,EAAgB,EAAM,YAAc,EAAQ,EAAS,GAE3D,OAAO,IAAI,MAAM,EAAe,CAC9B,IAAA,CAAI,EAAQ,IACG,eAAT,IACA,KAAQ,EAAe,QAAQ,IAAI,EAAQ,EAAM,GACjD,KAAQ,EACH,QAAQ,IAAI,EAAS,EAAM,GAE7B,QAAQ,IAAI,EAAQ,EAAM,IAEnC,IAAA,CAAI,EAAQ,EAAM,IACZ,KAAQ,EAAe,QAAQ,IAAI,EAAQ,EAAM,EAAO,GACxD,KAAQ,EACH,QAAQ,IAAI,EAAS,EAAM,EAAO,GAEpC,QAAQ,IAAI,EAAQ,EAAM,EAAO,GAE1C,IAAA,CAAI,EAAQ,IACN,KAAQ,GACR,KAAQ,GAIlB,CAEA,SAAgB,EAAU,EAAY,GACpC,GAAI,EAAK,WAAa,KAAK,aAAc,OAEzC,MAAM,EAAK,EACL,EAyER,SAAsB,EAAiB,GACrC,MAAM,EAAQ,CAAC,WAAY,UACrB,EAAY,EAAkB,EAAI,GACxC,IAAK,EAAW,OAAO,EAEvB,MAAM,EAAQ,EAAS,EAAW,GAClC,IAAK,EAAO,OAEZ,EAA0B,EAAI,GAEzB,EAAM,QACT,EAAM,MAAA,CAAS,EAAe,KAC5B,EAAG,cACD,IAAI,YAAY,EAAO,CAAE,SAAQ,SAAS,EAAM,UAAU,OAK3D,EAAM,QACT,EAAM,MAAQ,EAAQ,OAAS,IAAI,KAGrC,OAAO,EAAc,EAAO,EAC9B,CAhGgB,CAAa,EAAI,GAC/B,IAAK,EAAO,OAEZ,MAAM,EAAW,CAAC,SAAU,QACtB,EAAe,EAAkB,EAAI,GAC3C,GAAI,EAGF,OAFA,EAA0B,EAAI,QA4FlC,SAAoB,EAAiB,EAAc,GAEjD,IADe,EAAG,WACL,OAEb,MAAM,EAAQ,kCAAkC,KAAK,GACrD,IAAK,EACH,OAAO,QAAQ,KAAK,yCAAyC,KAE/D,MAAM,CAAG,EAAU,GAAY,EAEzB,EAAU,CAAC,SAAU,QACrB,EAAe,EAAkB,EAAI,GAC3C,EAA0B,EAAI,GAE9B,MAAM,EAAO,OAAO,aACd,EAAU,SAAS,cAAc,WAAW,MAClD,EAAG,YAAY,GAOf,IAAI,EAAgC,GAEpC,EAAW,EAAA,KACT,MAAM,EAAO,EAAS,EAAU,GAEhC,IAAK,MAAM,QAAQ,GAMjB,OALA,EAAc,QAAS,IACrB,EAAK,GAAG,SACR,EAAY,EAAK,WAEnB,EAAgB,IAIlB,MAAM,EAA2B,GAC3B,EAAgB,IAAI,IAC1B,EAAc,QAAS,GAAS,EAAc,IAAI,EAAK,IAAK,IAE5D,EAAK,QAAA,CAAS,EAAM,KAClB,IAAI,EAAW,EAEf,GAAI,EAAc,CAChB,MAAM,EAAc,EAClB,CAAG,CAAA,GAAW,EAAM,OAAQ,GAC5B,GAEF,EAAM,EAAS,EAAc,EAC/B,CAEA,MAAM,EAAQ,CAAG,CAAA,GAAW,EAAM,OAAQ,EAAO,KAAM,GAEvD,IAAI,EAAO,EAAc,IAAI,GAC7B,GAAI,EAEF,EAAK,MAAM,GAAY,EACvB,EAAK,MAAM,OAAS,EACpB,EAAK,MAAM,KAAO,EAClB,EAAc,OAAO,OAChB,CAEL,MAAM,EAAQ,EAAG,WAAU,GACrB,EAAgB,EAAS,GAE/B,EAAU,EADW,EAAc,EAAe,IAElD,EAAO,CAAE,MAAK,GAAI,EAAO,MAAO,EAClC,CAEA,EAAS,KAAK,KAIhB,EAAc,QAAS,IACrB,EAAK,GAAG,SACR,EAAY,EAAK,MAInB,IAAI,EAAgB,EAAQ,YAC5B,EAAS,QAAS,IACZ,IAAkB,EAAK,GACzB,EAAgB,EAAc,YAE9B,EAAQ,YAAY,aAAa,EAAK,GAAI,KAI9C,EAAgB,GAEpB,CAtLI,CAAW,EAAI,EAAc,GAI/B,MAAM,EAAU,CAAC,QAAS,OACpB,EAAc,EAAkB,EAAI,GACtC,IACF,EAA0B,EAAI,GAiLlC,SAAmB,EAAiB,EAAc,GAEhD,IADe,EAAG,WACL,OAEb,MAAM,EAAO,OAAO,aACd,EAAU,SAAS,cAAc,UAAU,MACjD,EAAG,OAAO,GAEV,EAAW,EAAA,KACG,EAAS,EAAM,GAEpB,EAAG,YACN,EAAQ,YAAY,aAAa,EAAI,EAAQ,aAEtC,EAAG,YACZ,EAAG,UAGT,CAlMI,CAAU,EAAI,EAAa,IAoM/B,SAA2B,EAAiB,GAC1C,MAAM,EAAQ,MAAM,KAAK,EAAG,YAE5B,IAAK,MAAM,KAAQ,EAAO,CACxB,MAAM,KAAE,EAAA,MAAM,GAAU,EAClB,EAAS,CAAC,UAAW,SAAS,SAAS,GACvC,EAAe,CAAC,UAAW,SAAS,SAAS,GAC7C,EAAc,CAAC,WAAY,UAAU,SAAS,GAC9C,EAAc,CAAC,WAAY,UAAU,SAAS,GAC9C,EAAQ,CAAC,SAAU,QAAQ,SAAS,GACpC,EAAa,EAAK,WAAW,QAAU,EAAK,WAAW,KACvD,EAAU,EAAK,WAAW,QAAU,EAAK,WAAW,KAEtD,GACF,EAAmB,EAAI,EAAO,GAC9B,EAAG,gBAAgB,IACV,GACT,EAAqB,EAAI,EAAO,GAChC,EAAG,gBAAgB,IACV,GACT,EAAoB,EAAI,EAAO,GAC/B,EAAG,gBAAgB,IACV,GACT,EAAoB,EAAI,EAAO,GAC/B,EAAG,gBAAgB,IACV,GACT,EAAW,EAAI,EAAO,GACtB,EAAG,gBAAgB,IACV,GAET,EAAmB,EADL,EAAK,MAAM,KAAK,MACA,EAAO,GACrC,EAAG,gBAAgB,IACV,IACT,EAAoB,EAAI,EAAM,EAAO,GACrC,EAAG,gBAAgB,GAEvB,CACF,CAtOE,CAAkB,EAAI,GAEtB,MAAM,EAAW,MAAM,KAAK,EAAG,YAC/B,IAAK,MAAM,KAAS,EAAU,EAAU,EAAO,EACjD,CAEA,SAAgB,EACd,EACA,EAAmB,CAAC,GAEpB,MAAM,EACyB,iBAAtB,EACH,SAAS,cAAc,GACvB,EAEF,GACG,EAAQ,QACX,EAAQ,MAAA,CAAS,EAAmB,KAClC,EAAG,cACD,IAAI,YAAY,EAAW,CAAE,SAAQ,SAAS,EAAM,UAAU,OAK/D,EAAQ,QACX,EAAQ,MAAQ,IAAI,KAGtB,EAAU,EAAI,IAEd,QAAQ,KAAK,gCAAiC,EAElD,CAEA,SAAS,EAAW,EAAY,GAC9B,MAAM,EAAI,EAAO,GACX,EAAQ,EACd,EAAM,WAAa,GACnB,EAAM,SAAS,KAAK,EAAE,KACxB,CAEA,SAAS,EAAkB,EAAiB,GAC1C,IAAK,MAAM,KAAQ,EAAO,CACxB,MAAM,EAAQ,EAAG,aAAa,GAC9B,GAAc,OAAV,EAAgB,OAAO,CAC7B,CACA,OAAO,IACT,CAEA,SAAS,EAA0B,EAAiB,GAClD,IAAK,MAAM,KAAQ,EACjB,EAAG,gBAAgB,EAEvB,CAmLA,SAAS,EAAW,EAAiB,EAAc,GACjD,MAAM,EAAU,EAAK,OAAO,QAAQ,eAAgB,IACpD,IAAK,EAAS,OAEd,MAAM,EAAO,EAAQ,MACrB,IAAK,EAAM,OAEX,MAAM,EAAM,EAAQ,KAEpB,QAAY,IAAR,EAAmB,CACrB,IAAI,EAAQ,EAAK,IAAI,GACf,aAAiB,MACrB,EAAQ,IAAI,IACZ,EAAK,IAAI,EAAS,IAEpB,EAAM,IAAI,EAAK,EACjB,MACE,EAAK,IAAI,EAAS,GAGpB,MAAM,EAAQ,EACd,EAAM,WAAa,GACnB,EAAM,SAAS,KAAA,KACb,QAAY,IAAR,EAAmB,CACrB,MAAM,EAAQ,EAAK,IAAI,GACnB,aAAiB,MACnB,EAAM,OAAO,GACM,IAAf,EAAM,MACR,EAAK,OAAO,GAGlB,MAAW,EAAK,IAAI,KAAa,GAC/B,EAAK,OAAO,IAGlB,CAEA,SAAS,EAAmB,EAAiB,EAAc,GACzD,EAAW,EAAA,KACT,MAAM,EAAM,EAAS,EAAM,GAC3B,EAAG,YAAc,QAAoC,OAAO,GAAO,IAEvE,CAEA,SAAS,EAAqB,EAAiB,EAAc,GAC3D,MAAM,EAAa,aAAc,kBAAgC,aAAZ,EAAG,KAClD,EAAU,aAAc,kBAAgC,UAAZ,EAAG,KAGrD,EAAW,EAAA,KACT,MAAM,EAAM,EAAS,EAAM,GAC3B,GAAI,EACF,EAAG,UAAY,OACV,GAAI,EACT,EAAG,QAAU,EAAG,QAAU,OAAO,OAC5B,CACU,EAIR,MAAe,MAAP,EAAc,GAAK,OAAO,EAC3C,IAKF,MAAM,EADW,GAAc,GAAW,aAAc,kBAC3B,SAAW,QACxC,EAAG,iBAAiB,EAAY,IAG9B,EAAc,EAAQ,GAAG,OAAU,kBAFpB,EAAa,UAAY,WAEI,IAEhD,CAEA,SAAS,EACP,EACA,EACA,EACA,GAEA,EAAW,EAAA,KACT,MAAM,EAAM,EAAS,EAAM,GACvB,UAA6C,IAAR,EACvC,EAAG,gBAAgB,IACF,IAAR,EACT,EAAG,aAAa,EAAM,IAEtB,EAAG,aAAa,EAAM,OAAO,KAGnC,CAEA,SAAS,EAAoB,EAAiB,EAAc,GAC1D,IAAI,EAAa,IAAI,IAErB,EAAW,EAAA,KACT,MAAM,EAAM,EAAS,EAAM,GACrB,EAAa,IAAI,IACjB,EAAY,GAAc,GAAK,EAAW,IAAI,GAC9C,EAAc,GAAc,EAAE,MAAM,OAAO,QAAQ,GAEtC,iBAAR,EAAkB,EAAW,GAC/B,MAAM,QAAQ,GACrB,EAAI,OAAO,QAAS,IACD,iBAAN,GAAgB,EAAW,KAEhB,iBAAR,GAA4B,OAAR,GACpC,OAAO,QAAQ,GAAK,QAAA,EAAU,EAAG,MAC3B,GAAW,EAAW,KAI9B,EAAW,QAAS,IACb,EAAW,IAAI,IAAI,EAAG,UAAU,OAAO,KAE9C,EAAW,QAAS,IACb,EAAW,IAAI,IAAI,EAAG,UAAU,IAAI,KAG3C,EAAa,GAEjB,CAEA,SAAS,EAAoB,EAAiB,EAAc,GAC1D,IAAI,EAAiC,CAAC,EAEtC,EAAW,EAAA,KACT,MAAM,EAAM,EAAS,EAAM,GACrB,EAA2B,iBAAR,GAA4B,OAAR,EAAe,EAAM,CAAC,EAEnE,IAAK,MAAM,KAAO,EACV,KAAO,IACV,EAAI,MAAc,GAAO,IAI9B,IAAK,MAAM,KAAO,EACZ,EAAU,KAAS,EAAU,KAC9B,EAAI,MAAc,GAAO,EAAU,IAIxC,EAAY,IAAK,IAErB,CAEA,SAAS,EACP,EACA,EACA,EACA,GAGA,MAAO,KAAS,GADH,EAAI,MAAM,KAAK,MACM,MAAM,KAElC,EAAY,EAAU,SAAS,WAC/B,EAAS,EAAY,SAAW,EAEhC,EAA0B,IAC9B,IAAK,EAAG,YAAa,OAErB,MAAM,EAAe,EAAO,kBAAkB,KAE1C,GAAa,GAAgB,EAAG,SAAS,EAAO,SAChD,EAAU,SAAS,SAAW,EAAO,SAAW,IAEhD,EAAU,SAAS,YAAY,EAAO,iBACtC,EAAU,SAAS,SAAS,EAAO,kBAEvC,EAAc,EAAQ,EAAM,KAK9B,GAFA,EAAO,iBAAiB,EAAM,GAE1B,EAAW,CACb,MAAM,EAAQ,EACd,EAAM,WAAa,GACnB,EAAM,SAAS,KAAA,IAAW,EAAO,oBAAoB,EAAM,GAC7D,CACF,CC1dA,GAAsB,oBAAX,OAAwB,CACjC,MAAM,EAAI,OACV,EAAE,MAAQ,EAAE,OAAS,CAAC,EACtB,EAAE,MAAM,GAAK,CAAE,cAAa,WAAU,QACxC"}
1
+ {"version":3,"file":"index.umd.js","names":[],"sources":["../src/reactivity.ts","../src/evaluator.ts","../src/parser.ts","../src/index.ts"],"sourcesContent":["let activeEffect: symbol | null = null\nconst targetMap = new WeakMap<\n object,\n Map<string | symbol, Set<ReactiveEffect>>\n>()\nconst proxyMap = new WeakMap<object, any>()\nconst effectMap = new WeakMap<symbol, ReactiveEffect>()\n\nexport class ReactiveEffect {\n active = true\n deps: Set<Set<ReactiveEffect>> = new Set()\n\n constructor(public fn: () => void) {}\n\n run() {\n if (!this.active) return this.fn()\n\n this.cleanup()\n\n activeEffect = Symbol()\n effectMap.set(activeEffect, this)\n\n try {\n return this.fn()\n } finally {\n effectMap.delete(activeEffect)\n activeEffect = null\n }\n }\n\n stop() {\n if (this.active) {\n this.cleanup()\n this.active = false\n }\n }\n\n cleanup() {\n this.deps.forEach((dep) => dep.delete(this))\n this.deps.clear()\n }\n\n effect() {\n return {\n run: () => this.run(),\n stop: () => this.stop(),\n }\n }\n}\n\nexport function effect(fn: () => void) {\n const ref = new ReactiveEffect(fn)\n ref.run()\n return ref.effect()\n}\n\nexport function track(target: object, key: string | symbol) {\n if (activeEffect) {\n let depsMap = targetMap.get(target)\n if (!depsMap) {\n depsMap = new Map()\n targetMap.set(target, depsMap)\n }\n\n let dep = depsMap.get(key)\n if (!dep) {\n dep = new Set()\n depsMap.set(key, dep)\n }\n\n const active = effectMap.get(activeEffect)\n if (active) {\n dep.add(active)\n active.deps.add(dep)\n }\n }\n}\n\nexport function trigger(target: object, key: string | symbol) {\n const depsMap = targetMap.get(target)\n if (!depsMap) return\n\n const dep = depsMap.get(key)\n if (dep) {\n const effects = new Set(dep)\n effects.forEach((effect) => effect.run())\n }\n}\n\nexport interface ScopeContext {\n /** Elemento DOM raiz associado ao escopo (somente leitura) */\n readonly $el: HTMLElement\n /** Map nativo indexando elementos referenciados via ui:ref / :ref (somente leitura) */\n readonly $refs: Map<string, any>\n /** Despacha CustomEvents nativos (bubbles: true, composed: true) */\n $emit: (event: string, detail?: any) => void\n /** Índice numérico da iteração atual em loops ui:for / :for */\n $index?: number\n /** Chave de identificação da iteração em loops ui:for / :for */\n $key?: any\n}\n\nexport class Scope {\n /** Elemento DOM raiz ao qual o escopo foi acoplado (somente leitura) */\n declare readonly $el: HTMLElement\n\n /** Map nativo indexando elementos referenciados via ui:ref / :ref (somente leitura) */\n protected readonly $refs: Map<string, any> = new Map<string, any>()\n\n /** Despacha CustomEvents nativos (bubbles: true, composed: true) */\n protected $emit(event: string, detail?: any): void {\n const target = this.$el || (typeof window !== 'undefined' ? window : null)\n target?.dispatchEvent(\n new CustomEvent(event, { detail, bubbles: true, composed: true }),\n )\n }\n\n declare $index?: number\n declare $key?: any\n\n constructor(init?: Record<string, any>) {\n if (init && typeof init === 'object') {\n Object.assign(this, init)\n }\n }\n}\n\nexport function reactive<T extends any[]>(target: T): T\nexport function reactive<T extends object>(\n target: T & ThisType<T & ScopeContext>,\n): T & ScopeContext\nexport function reactive<T extends object>(target: T): any {\n const notObject = typeof target !== 'object' || target === null\n if (notObject) return target\n\n if (\n target instanceof Map ||\n target instanceof Set ||\n target instanceof WeakMap ||\n target instanceof WeakSet ||\n target instanceof Date ||\n target instanceof RegExp ||\n (typeof Node === 'function' && target instanceof Node)\n ) {\n return target\n }\n\n const isReactive = Object.hasOwn(target, '_isReactive')\n if (isReactive) return target\n\n const existingProxy = proxyMap.get(target)\n if (existingProxy) return existingProxy\n\n const proxy = new Proxy(target, {\n get(obj, key, receiver) {\n if (key === '_isReactive') return true\n track(obj, key)\n\n const res = Reflect.get(obj, key, receiver)\n // deep reactivity\n return typeof res === 'object' && res !== null ? reactive(res) : res\n },\n set(obj, key, value, receiver) {\n const isArray = Array.isArray(obj)\n const oldValue = Reflect.get(obj, key, receiver)\n const hadKey =\n isArray && String(Number(key)) === key\n ? Number(key) < obj.length\n : Object.hasOwn(obj, key)\n\n const result = Reflect.set(obj, key, value, receiver)\n\n if (!hadKey) {\n trigger(obj, key)\n if (isArray && key !== 'length') {\n trigger(obj, 'length')\n }\n } else if (oldValue !== value) {\n trigger(obj, key)\n }\n\n return result\n },\n })\n\n proxyMap.set(target, proxy)\n return proxy\n}\n\nexport function traverse(value: any, seen = new Set()) {\n if (typeof value !== 'object' || value === null || seen.has(value)) {\n return value\n }\n seen.add(value)\n for (const key in value) {\n traverse(value[key], seen)\n }\n return value\n}\n\nexport function watch<T>(\n source: (() => T) | any,\n cb: (newValue: T, oldValue: T | undefined) => void,\n options?: { immediate?: boolean },\n): () => void {\n let oldValue: any\n let isFirstRun = true\n\n const getter = source instanceof Function ? source : () => traverse(source)\n\n const runner = effect(() => {\n const newValue = getter()\n\n if (isFirstRun) {\n isFirstRun = false\n oldValue = newValue\n if (options?.immediate) {\n cb(newValue, undefined)\n }\n } else {\n cb(newValue, oldValue)\n oldValue = newValue\n }\n })\n\n return runner.stop\n}\n","export function evaluate(\n expression: string,\n context: Record<string, any> = {},\n) {\n try {\n const fn = new Function(`with(this) { return ${expression} }`)\n return fn.call(context)\n } catch {\n return undefined\n }\n}\n\nexport function evaluateEvent(\n $event: Event,\n expression: string,\n context: Record<string, any> = {},\n) {\n try {\n const exp = expression.trim()\n const isIdentifier = /^[a-zA-Z_$][0-9a-zA-Z_$.]*$/.test(exp)\n const code = `${exp} instanceof Function ? ${exp}.call(this, $event) : ${exp}`\n const result = isIdentifier ? code : exp\n const fn = new Function('$event', `with(this) { ${result} }`)\n\n fn.call(context, $event)\n } catch {\n console.warn(`[jsweb/ui] Error evaluating event: ${expression}`)\n }\n}\n","import { effect, reactive, type ScopeContext } from './reactivity'\nimport { evaluate, evaluateEvent } from './evaluator'\n\nexport type Context = Record<string, any>\n\ninterface BoundNode extends Node {\n _effects?: Array<() => void>\n}\n\nexport function cleanupTree(node: Node) {\n const bNode = node as BoundNode\n if (bNode._effects) {\n bNode._effects.forEach((stop) => stop())\n bNode._effects = []\n }\n const children = Array.from(node.childNodes)\n for (const child of children) cleanupTree(child)\n}\n\nexport function createContext(scope: any, context: Context = {}): Context {\n const reactiveScope = scope._isReactive ? scope : reactive(scope)\n\n return new Proxy(reactiveScope, {\n get(target, prop) {\n if (prop === '_isContext') return true\n if (prop in target) return Reflect.get(target, prop, target)\n if (prop in context) {\n return Reflect.get(context, prop, context)\n }\n return Reflect.get(target, prop, target)\n },\n set(target, prop, value) {\n if (prop in target) return Reflect.set(target, prop, value, target)\n if (prop in context) {\n return Reflect.set(context, prop, value, context)\n }\n return Reflect.set(target, prop, value, target)\n },\n has(target, prop) {\n if (prop in target) return true\n if (prop in context) return true\n return false\n },\n })\n}\n\nexport function parseNode(node: Node, context: Context) {\n if (node.nodeType !== Node.ELEMENT_NODE) return\n\n const el = node as HTMLElement\n const scope = processScope(el, context)\n if (!scope) return\n\n const forAttrs = ['ui:for', ':for']\n const forDirective = getDirectiveValue(el, forAttrs)\n if (forDirective) {\n removeDirectiveAttributes(el, forAttrs)\n processFor(el, forDirective, scope)\n return\n }\n\n const ifAttrs = ['ui:if', ':if']\n const ifDirective = getDirectiveValue(el, ifAttrs)\n if (ifDirective) {\n removeDirectiveAttributes(el, ifAttrs)\n processIf(el, ifDirective, scope)\n }\n\n processAttributes(el, scope)\n\n const children = Array.from(el.childNodes)\n for (const child of children) parseNode(child, scope)\n}\n\nexport function createScope<T extends object = Context>(\n selectorOrElement: string | HTMLElement,\n context?: T & ThisType<T & ScopeContext>,\n) {\n const el =\n typeof selectorOrElement === 'string'\n ? document.querySelector(selectorOrElement)\n : selectorOrElement\n\n if (el) {\n const ctx = (context ?? {}) as Context\n ctx.$el = el\n\n if (!ctx.$emit) {\n ctx.$emit = (eventName: string, detail?: any) => {\n el.dispatchEvent(\n new CustomEvent(eventName, { detail, bubbles: true, composed: true }),\n )\n }\n }\n\n if (!ctx.$refs) {\n ctx.$refs = new Map<string, any>()\n }\n\n parseNode(el, ctx)\n } else {\n console.warn('[jsweb/ui] Element not found:', selectorOrElement)\n }\n}\n\nfunction bindEffect(node: Node, fn: () => void) {\n const e = effect(fn)\n const bNode = node as BoundNode\n bNode._effects ??= []\n bNode._effects.push(e.stop)\n}\n\nfunction getDirectiveValue(el: HTMLElement, names: string[]) {\n for (const name of names) {\n const value = el.getAttribute(name)\n if (value !== null) return value\n }\n return null\n}\n\nfunction removeDirectiveAttributes(el: HTMLElement, names: string[]) {\n for (const name of names) {\n el.removeAttribute(name)\n }\n}\n\nfunction processScope(el: HTMLElement, context: Context) {\n const attrs = ['ui:scope', ':scope']\n const directive = getDirectiveValue(el, attrs)\n if (!directive) return context\n\n const scope = evaluate(directive, context)\n if (!scope) return undefined\n\n removeDirectiveAttributes(el, attrs)\n\n scope.$el = el\n\n if (!scope.$emit) {\n scope.$emit = (event: string, detail?: any) => {\n el.dispatchEvent(\n new CustomEvent(event, { detail, bubbles: true, composed: true }),\n )\n }\n }\n\n if (!scope.$refs) {\n scope.$refs = context.$refs ?? new Map<string, any>()\n }\n\n return createContext(scope, context)\n}\n\nfunction processFor(el: HTMLElement, expr: string, context: Context) {\n const parent = el.parentNode\n if (!parent) return\n\n const match = /^\\s*(.+)\\s+(?:in|of)\\s+(.+)\\s*$/.exec(expr)\n if (!match) {\n return console.warn(`[jsweb/ui] Invalid ui:for expression: ${expr}`)\n }\n const [, itemName, listName] = match\n\n const keyAttr = ['ui:key', ':key']\n const keyDirective = getDirectiveValue(el, keyAttr)\n removeDirectiveAttributes(el, keyAttr)\n\n const uuid = crypto.randomUUID()\n const comment = document.createComment(` ui:for ${uuid} `)\n el.replaceWith(comment)\n\n interface RenderedNode {\n key: any\n el: HTMLElement\n scope: any\n }\n let renderedNodes: RenderedNode[] = []\n\n bindEffect(comment, () => {\n const list = evaluate(listName, context)\n\n if (!Array.isArray(list)) {\n renderedNodes.forEach((node) => {\n node.el.remove()\n cleanupTree(node.el)\n })\n renderedNodes = []\n return\n }\n\n const newNodes: RenderedNode[] = []\n const oldNodesByKey = new Map<any, RenderedNode>()\n renderedNodes.forEach((node) => oldNodesByKey.set(node.key, node))\n\n list.forEach((item, index) => {\n let key: any = index\n\n if (keyDirective) {\n const tempContext = createContext(\n { [itemName]: item, $index: index },\n context,\n )\n key = evaluate(keyDirective, tempContext)\n }\n\n let node = oldNodesByKey.get(key)\n if (node) {\n // Reuse node\n node.scope[itemName] = item\n node.scope.$index = index\n node.scope.$key = key\n node.scope.$el = node.el\n oldNodesByKey.delete(key)\n } else {\n // Create new node\n const clone = el.cloneNode(true) as HTMLElement\n const scope = {\n [itemName]: item,\n $index: index,\n $key: key,\n $el: clone,\n }\n const reactiveScope = reactive(scope)\n const localContext = createContext(reactiveScope, context)\n parseNode(clone, localContext)\n node = { key, el: clone, scope: reactiveScope }\n }\n\n newNodes.push(node)\n })\n\n // Remove un-reused nodes\n oldNodesByKey.forEach((node) => {\n node.el.remove()\n cleanupTree(node.el)\n })\n\n // Reorder and insert new DOM nodes\n let currentAnchor = comment.nextSibling\n newNodes.forEach((node) => {\n if (currentAnchor === node.el) {\n currentAnchor = currentAnchor.nextSibling\n } else {\n comment.parentNode?.insertBefore(node.el, currentAnchor)\n }\n })\n\n renderedNodes = newNodes\n })\n}\n\nfunction processIf(el: HTMLElement, expr: string, context: Context) {\n const parent = el.parentNode\n if (!parent) return\n\n const uuid = crypto.randomUUID()\n const comment = document.createComment(` ui:if ${uuid} `)\n el.before(comment)\n\n bindEffect(comment, () => {\n const val = evaluate(expr, context)\n if (val) {\n if (!el.parentNode) {\n comment.parentNode?.insertBefore(el, comment.nextSibling)\n }\n } else if (el.parentNode) {\n el.remove()\n }\n })\n}\n\nfunction processAttributes(el: HTMLElement, context: Context) {\n const attrs = Array.from(el.attributes)\n\n for (const attr of attrs) {\n const { name, value } = attr\n const isText = ['ui:text', ':text'].includes(name)\n const isTwoWayBind = ['ui:bind', ':bind'].includes(name)\n const isClassBind = ['ui:class', ':class'].includes(name)\n const isStyleBind = ['ui:style', ':style'].includes(name)\n const isRef = ['ui:ref', ':ref'].includes(name)\n const isAttrBind = name.startsWith('ui:') || name.startsWith(':')\n const isEvent = name.startsWith('ui@') || name.startsWith('@')\n\n if (isText) {\n processTextBinding(el, value, context)\n el.removeAttribute(name)\n } else if (isTwoWayBind) {\n processTwoWayBinding(el, value, context)\n el.removeAttribute(name)\n } else if (isClassBind) {\n processClassBinding(el, value, context)\n el.removeAttribute(name)\n } else if (isStyleBind) {\n processStyleBinding(el, value, context)\n el.removeAttribute(name)\n } else if (isRef) {\n processRef(el, value, context)\n el.removeAttribute(name)\n } else if (isAttrBind) {\n const bound = name.split(':').pop()!\n processAttrBinding(el, bound, value, context)\n el.removeAttribute(name)\n } else if (isEvent) {\n processEventBinding(el, name, value, context)\n el.removeAttribute(name)\n }\n }\n}\n\nfunction processRef(el: HTMLElement, expr: string, context: Context) {\n const refName = expr.trim().replace(/^['\"]|['\"]$/g, '')\n if (!refName) return\n\n const refs = context.$refs as Map<string, any>\n if (!refs) return\n\n const key = context.$key\n\n if (key !== undefined) {\n let group = refs.get(refName)\n if (!(group instanceof Map)) {\n group = new Map<any, HTMLElement>()\n refs.set(refName, group)\n }\n group.set(key, el)\n } else {\n refs.set(refName, el)\n }\n\n const bNode = el as BoundNode\n bNode._effects ??= []\n bNode._effects.push(() => {\n if (key !== undefined) {\n const group = refs.get(refName)\n if (group instanceof Map) {\n group.delete(key)\n if (group.size === 0) {\n refs.delete(refName)\n }\n }\n } else if (refs.get(refName) === el) {\n refs.delete(refName)\n }\n })\n}\n\nfunction processTextBinding(el: HTMLElement, expr: string, context: Context) {\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n el.textContent = val !== undefined && val !== null ? String(val) : ''\n })\n}\n\nfunction processTwoWayBinding(el: HTMLElement, expr: string, context: Context) {\n const isCheckbox = el instanceof HTMLInputElement && el.type === 'checkbox'\n const isRadio = el instanceof HTMLInputElement && el.type === 'radio'\n\n // 1. Reactive state to DOM\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n if (isCheckbox) {\n el.checked = !!val\n } else if (isRadio) {\n el.checked = el.value === String(val)\n } else {\n const target = el as\n | HTMLInputElement\n | HTMLSelectElement\n | HTMLTextAreaElement\n target.value = val == null ? '' : String(val)\n }\n })\n\n // 2. DOM to Reactive state\n const isChange = isCheckbox || isRadio || el instanceof HTMLSelectElement\n const eventName = isChange ? 'change' : 'input'\n el.addEventListener(eventName, ($event) => {\n const target = isCheckbox ? 'checked' : 'value'\n const value = `$event.target.${target}`\n evaluateEvent($event, `${expr} = ${value}`, context)\n })\n}\n\nfunction processAttrBinding(\n el: HTMLElement,\n attr: string,\n expr: string,\n context: Context,\n) {\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n if (val === null || val === undefined || val === false) {\n el.removeAttribute(attr)\n } else if (val === true) {\n el.setAttribute(attr, '')\n } else {\n el.setAttribute(attr, String(val))\n }\n })\n}\n\nfunction processClassBinding(el: HTMLElement, expr: string, context: Context) {\n let oldClasses = new Set<string>()\n\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n const newClasses = new Set<string>()\n const addClass = (c: string) => c && newClasses.add(c)\n const addClasses = (c: string) => c.split(/\\s+/).forEach(addClass)\n\n if (typeof val === 'string') addClasses(val)\n else if (Array.isArray(val)) {\n val.flat().forEach((c: any) => {\n if (typeof c === 'string') addClasses(c)\n })\n } else if (typeof val === 'object' && val !== null) {\n Object.entries(val).forEach(([c, condition]: [string, any]) => {\n if (condition) addClasses(c)\n })\n }\n\n oldClasses.forEach((c) => {\n if (!newClasses.has(c)) el.classList.remove(c)\n })\n newClasses.forEach((c) => {\n if (!oldClasses.has(c)) el.classList.add(c)\n })\n\n oldClasses = newClasses\n })\n}\n\nfunction processStyleBinding(el: HTMLElement, expr: string, context: Context) {\n let oldStyles: Record<string, any> = {}\n\n bindEffect(el, () => {\n const val = evaluate(expr, context)\n const newStyles = typeof val === 'object' && val !== null ? val : {}\n\n for (const key in oldStyles) {\n if (!(key in newStyles)) {\n ;(el.style as any)[key] = ''\n }\n }\n\n for (const key in newStyles) {\n if (oldStyles[key] !== newStyles[key]) {\n ;(el.style as any)[key] = newStyles[key]\n }\n }\n\n oldStyles = { ...newStyles }\n })\n}\n\nfunction processEventBinding(\n el: HTMLElement,\n evt: string,\n expr: string,\n context: Context,\n) {\n const refs = evt.split('@').pop()!\n const [name, ...modifiers] = refs.split('.')\n\n const isOutside = modifiers.includes('outside')\n const target = isOutside ? document : el\n\n const handler: EventListener = ($event: Event) => {\n if (!el.isConnected) return\n\n const isTargetNode = $event.target instanceof Node\n\n if (isOutside && isTargetNode && el.contains($event.target)) return\n if (modifiers.includes('self') && $event.target !== el) return\n\n if (modifiers.includes('prevent')) $event.preventDefault()\n if (modifiers.includes('stop')) $event.stopPropagation()\n\n evaluateEvent($event, expr, context)\n }\n\n target.addEventListener(name, handler)\n\n if (isOutside) {\n const bNode = el as BoundNode\n bNode._effects ??= []\n bNode._effects.push(() => target.removeEventListener(name, handler))\n }\n}\n","import { reactive, watch, Scope, type ScopeContext } from './reactivity'\nimport { createScope, type Context } from './parser'\n\nexport { reactive, watch, createScope, Scope }\nexport type { ScopeContext, Context }\n\nif (typeof window !== 'undefined') {\n const w = window as any\n w.jsweb = w.jsweb || {}\n w.jsweb.ui = { createScope, reactive, watch, Scope }\n}\n"],"mappings":"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"}
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsweb/ui",
3
- "version": "1.2.8",
3
+ "version": "1.3.1",
4
4
  "description": "JS Web Microframework",
5
5
  "keywords": [
6
6
  "js",
@@ -1,3 +1,4 @@
1
- import { reactive, watch } from './reactivity';
2
- import { createScope } from './parser';
3
- export { reactive, watch, createScope };
1
+ import { reactive, watch, Scope, ScopeContext } from './reactivity';
2
+ import { createScope, Context } from './parser';
3
+ export { reactive, watch, createScope, Scope };
4
+ export type { ScopeContext, Context };
@@ -1,5 +1,6 @@
1
+ import { ScopeContext } from './reactivity';
1
2
  export type Context = Record<string, any>;
2
3
  export declare function cleanupTree(node: Node): void;
3
4
  export declare function createContext(scope: any, context?: Context): Context;
4
5
  export declare function parseNode(node: Node, context: Context): void;
5
- export declare function createScope(selectorOrElement: string | HTMLElement, context?: Context): void;
6
+ export declare function createScope<T extends object = Context>(selectorOrElement: string | HTMLElement, context?: T & ThisType<T & ScopeContext>): void;
@@ -17,8 +17,32 @@ export declare function effect(fn: () => void): {
17
17
  };
18
18
  export declare function track(target: object, key: string | symbol): void;
19
19
  export declare function trigger(target: object, key: string | symbol): void;
20
- export declare function reactive<T extends object>(target: T): T;
20
+ export interface ScopeContext {
21
+ /** Elemento DOM raiz associado ao escopo (somente leitura) */
22
+ readonly $el: HTMLElement;
23
+ /** Map nativo indexando elementos referenciados via ui:ref / :ref (somente leitura) */
24
+ readonly $refs: Map<string, any>;
25
+ /** Despacha CustomEvents nativos (bubbles: true, composed: true) */
26
+ $emit: (event: string, detail?: any) => void;
27
+ /** Índice numérico da iteração atual em loops ui:for / :for */
28
+ $index?: number;
29
+ /** Chave de identificação da iteração em loops ui:for / :for */
30
+ $key?: any;
31
+ }
32
+ export declare class Scope {
33
+ /** Elemento DOM raiz ao qual o escopo foi acoplado (somente leitura) */
34
+ readonly $el: HTMLElement;
35
+ /** Map nativo indexando elementos referenciados via ui:ref / :ref (somente leitura) */
36
+ protected readonly $refs: Map<string, any>;
37
+ /** Despacha CustomEvents nativos (bubbles: true, composed: true) */
38
+ protected $emit(event: string, detail?: any): void;
39
+ $index?: number;
40
+ $key?: any;
41
+ constructor(init?: Record<string, any>);
42
+ }
43
+ export declare function reactive<T extends any[]>(target: T): T;
44
+ export declare function reactive<T extends object>(target: T & ThisType<T & ScopeContext>): T & ScopeContext;
21
45
  export declare function traverse(value: any, seen?: Set<unknown>): any;
22
- export declare function watch(source: any | (() => any), cb: (newValue: any, oldValue: any) => void, options?: {
46
+ export declare function watch<T>(source: (() => T) | any, cb: (newValue: T, oldValue: T | undefined) => void, options?: {
23
47
  immediate?: boolean;
24
48
  }): () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsweb/ui",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "JS Web Microframework",
5
5
  "keywords": [
6
6
  "js",
@@ -38,7 +38,7 @@
38
38
  "format": "prettier --write .",
39
39
  "test": "echo 'test'",
40
40
  "preversion": "npm run build",
41
- "postversion": "git push && git push --tags"
41
+ "push": "git push && git push --tags && npm publish"
42
42
  },
43
43
  "devDependencies": {
44
44
  "prettier": "^3.8.3",
package/publish.js CHANGED
@@ -26,8 +26,10 @@ pkgInfo.exports = {
26
26
  writeFileSync(target, JSON.stringify(pkgInfo, null, 2))
27
27
 
28
28
  // 4. Copia arquivos de metadados importantes para o NPM
29
- copyFileSync(resolve(root, 'README.md'), resolve(root, 'dist/README.md'))
30
29
  copyFileSync(resolve(root, 'LICENSE'), resolve(root, 'dist/LICENSE'))
30
+ copyFileSync(resolve(root, 'SKILL.md'), resolve(root, 'dist/SKILL.md'))
31
+ copyFileSync(resolve(root, 'README.md'), resolve(root, 'dist/README.md'))
32
+ copyFileSync(resolve(root, 'CHANGELOG.md'), resolve(root, 'dist/CHANGELOG.md'))
31
33
 
32
34
  console.log(
33
35
  '✅ Arquivo package.json mínimo e metadados preparados na pasta dist/',
package/src/index.ts CHANGED
@@ -1,10 +1,11 @@
1
- import { reactive, watch } from './reactivity'
2
- import { createScope } from './parser'
1
+ import { reactive, watch, Scope, type ScopeContext } from './reactivity'
2
+ import { createScope, type Context } from './parser'
3
3
 
4
- export { reactive, watch, createScope }
4
+ export { reactive, watch, createScope, Scope }
5
+ export type { ScopeContext, Context }
5
6
 
6
7
  if (typeof window !== 'undefined') {
7
8
  const w = window as any
8
9
  w.jsweb = w.jsweb || {}
9
- w.jsweb.ui = { createScope, reactive, watch }
10
+ w.jsweb.ui = { createScope, reactive, watch, Scope }
10
11
  }
package/src/parser.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { effect, reactive } from './reactivity'
1
+ import { effect, reactive, type ScopeContext } from './reactivity'
2
2
  import { evaluate, evaluateEvent } from './evaluator'
3
3
 
4
4
  export type Context = Record<string, any>
@@ -72,9 +72,9 @@ export function parseNode(node: Node, context: Context) {
72
72
  for (const child of children) parseNode(child, scope)
73
73
  }
74
74
 
75
- export function createScope(
75
+ export function createScope<T extends object = Context>(
76
76
  selectorOrElement: string | HTMLElement,
77
- context: Context = {},
77
+ context?: T & ThisType<T & ScopeContext>,
78
78
  ) {
79
79
  const el =
80
80
  typeof selectorOrElement === 'string'
@@ -82,19 +82,22 @@ export function createScope(
82
82
  : selectorOrElement
83
83
 
84
84
  if (el) {
85
- if (!context.$emit) {
86
- context.$emit = (eventName: string, detail?: any) => {
85
+ const ctx = (context ?? {}) as Context
86
+ ctx.$el = el
87
+
88
+ if (!ctx.$emit) {
89
+ ctx.$emit = (eventName: string, detail?: any) => {
87
90
  el.dispatchEvent(
88
91
  new CustomEvent(eventName, { detail, bubbles: true, composed: true }),
89
92
  )
90
93
  }
91
94
  }
92
95
 
93
- if (!context.$refs) {
94
- context.$refs = new Map<string, any>()
96
+ if (!ctx.$refs) {
97
+ ctx.$refs = new Map<string, any>()
95
98
  }
96
99
 
97
- parseNode(el, context)
100
+ parseNode(el, ctx)
98
101
  } else {
99
102
  console.warn('[jsweb/ui] Element not found:', selectorOrElement)
100
103
  }
@@ -131,6 +134,8 @@ function processScope(el: HTMLElement, context: Context) {
131
134
 
132
135
  removeDirectiveAttributes(el, attrs)
133
136
 
137
+ scope.$el = el
138
+
134
139
  if (!scope.$emit) {
135
140
  scope.$emit = (event: string, detail?: any) => {
136
141
  el.dispatchEvent(
@@ -198,18 +203,23 @@ function processFor(el: HTMLElement, expr: string, context: Context) {
198
203
  key = evaluate(keyDirective, tempContext)
199
204
  }
200
205
 
201
- const scope = { [itemName]: item, $index: index, $key: key }
202
-
203
206
  let node = oldNodesByKey.get(key)
204
207
  if (node) {
205
208
  // Reuse node
206
209
  node.scope[itemName] = item
207
210
  node.scope.$index = index
208
211
  node.scope.$key = key
212
+ node.scope.$el = node.el
209
213
  oldNodesByKey.delete(key)
210
214
  } else {
211
215
  // Create new node
212
216
  const clone = el.cloneNode(true) as HTMLElement
217
+ const scope = {
218
+ [itemName]: item,
219
+ $index: index,
220
+ $key: key,
221
+ $el: clone,
222
+ }
213
223
  const reactiveScope = reactive(scope)
214
224
  const localContext = createContext(reactiveScope, context)
215
225
  parseNode(clone, localContext)
package/src/reactivity.ts CHANGED
@@ -87,7 +87,49 @@ export function trigger(target: object, key: string | symbol) {
87
87
  }
88
88
  }
89
89
 
90
- export function reactive<T extends object>(target: T): T {
90
+ export interface ScopeContext {
91
+ /** Elemento DOM raiz associado ao escopo (somente leitura) */
92
+ readonly $el: HTMLElement
93
+ /** Map nativo indexando elementos referenciados via ui:ref / :ref (somente leitura) */
94
+ readonly $refs: Map<string, any>
95
+ /** Despacha CustomEvents nativos (bubbles: true, composed: true) */
96
+ $emit: (event: string, detail?: any) => void
97
+ /** Índice numérico da iteração atual em loops ui:for / :for */
98
+ $index?: number
99
+ /** Chave de identificação da iteração em loops ui:for / :for */
100
+ $key?: any
101
+ }
102
+
103
+ export class Scope {
104
+ /** Elemento DOM raiz ao qual o escopo foi acoplado (somente leitura) */
105
+ declare readonly $el: HTMLElement
106
+
107
+ /** Map nativo indexando elementos referenciados via ui:ref / :ref (somente leitura) */
108
+ protected readonly $refs: Map<string, any> = new Map<string, any>()
109
+
110
+ /** Despacha CustomEvents nativos (bubbles: true, composed: true) */
111
+ protected $emit(event: string, detail?: any): void {
112
+ const target = this.$el || (typeof window !== 'undefined' ? window : null)
113
+ target?.dispatchEvent(
114
+ new CustomEvent(event, { detail, bubbles: true, composed: true }),
115
+ )
116
+ }
117
+
118
+ declare $index?: number
119
+ declare $key?: any
120
+
121
+ constructor(init?: Record<string, any>) {
122
+ if (init && typeof init === 'object') {
123
+ Object.assign(this, init)
124
+ }
125
+ }
126
+ }
127
+
128
+ export function reactive<T extends any[]>(target: T): T
129
+ export function reactive<T extends object>(
130
+ target: T & ThisType<T & ScopeContext>,
131
+ ): T & ScopeContext
132
+ export function reactive<T extends object>(target: T): any {
91
133
  const notObject = typeof target !== 'object' || target === null
92
134
  if (notObject) return target
93
135
 
@@ -156,11 +198,11 @@ export function traverse(value: any, seen = new Set()) {
156
198
  return value
157
199
  }
158
200
 
159
- export function watch(
160
- source: any | (() => any),
161
- cb: (newValue: any, oldValue: any) => void,
201
+ export function watch<T>(
202
+ source: (() => T) | any,
203
+ cb: (newValue: T, oldValue: T | undefined) => void,
162
204
  options?: { immediate?: boolean },
163
- ) {
205
+ ): () => void {
164
206
  let oldValue: any
165
207
  let isFirstRun = true
166
208
 
@@ -1,19 +0,0 @@
1
- name: NPM Publish
2
-
3
- on:
4
- push:
5
- tags:
6
- - v*
7
-
8
- jobs:
9
- publish:
10
- runs-on: ubuntu-latest
11
- steps:
12
- - uses: actions/checkout@v6
13
- - uses: actions/setup-node@v6
14
- with:
15
- node-version: 22
16
- registry-url: https://registry.npmjs.org/
17
- - run: npm i
18
- - run: npm run build
19
- - run: npm publish ./dist --access public