@jsweb/ui 1.2.6 → 1.2.8

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.
package/README.md CHANGED
@@ -32,17 +32,17 @@ npm i @jsweb/ui
32
32
 
33
33
  O framework utiliza um sistema de atributos customizados para declaratividade no HTML.
34
34
 
35
- | Diretiva | Descrição | Exemplo |
36
- | :------------------ | :------------------------------------------------------------------------------ | :------------------------------------ |
37
- | `ui:scope` | Define o objeto de estado para o elemento e seus filhos. | `<div ui:scope="{ count: 0 }">` |
38
- | `ui:text` | Sincroniza o `textContent` com uma variável. | `<span ui:text="count"></span>` |
39
- | `:attr` | Shorthand para bind de atributos HTML nativos. | `<button :disabled="count > 10">` |
40
- | `:class` / `:style` | Bind dinâmico avançado para classes CSS e Estilos Inline (dicionários, arrays). | `<div :class="{ active: isActive }">` |
41
- | `@event` | Shorthand para event listeners (com suporte a modificadores). | `<button @click.prevent="save">` |
42
- | `$emit` | Despacha CustomEvents a partir do escopo atual. (Exposto no contexto) | `<button @click="$emit('custom')">` |
43
- | `:bind` | Two-way data binding para inputs, checkboxes, radios e selects. | `<input :bind="name">` |
44
- | `ui:if` | Adiciona/Remove o elemento do DOM (via Comment Node placeholder). | `<div ui:if="count > 0">` |
45
- | `ui:for` | Renderiza uma lista de elementos a partir de um array. | `<li ui:for="item in items">` |
35
+ | Diretiva | Descrição | Exemplo |
36
+ | :-------------------- | :------------------------------------------------------------------------------ | :------------------------------------ |
37
+ | `ui:scope` / `:scope` | Define o objeto de estado para o elemento e seus filhos. | `<div :scope="{ count: 0 }">` |
38
+ | `ui:text` / `:text` | Sincroniza o `textContent` com uma variável. | `<span :text="count"></span>` |
39
+ | `:attr` | Shorthand para bind de atributos HTML nativos. | `<button :disabled="count > 10">` |
40
+ | `:class` / `:style` | Bind dinâmico avançado para classes CSS e Estilos Inline (dicionários, arrays). | `<div :class="{ active: isActive }">` |
41
+ | `@event` | Shorthand para event listeners (com suporte a modificadores). | `<button @click.prevent="save">` |
42
+ | `$emit` | Despacha CustomEvents a partir do escopo atual. (Exposto no contexto) | `<button @click="$emit('custom')">` |
43
+ | `:bind` | Two-way data binding para inputs, checkboxes, radios e selects. | `<input :bind="name">` |
44
+ | `ui:if` / `:if` | Adiciona/Remove o elemento do DOM (via Comment Node placeholder). | `<div :if="count > 0">` |
45
+ | `ui:for` / `:for` | Renderiza uma lista de elementos a partir de um array. | `<li :for="item in items">` |
46
46
 
47
47
  ## Exemplo de Uso
48
48
 
@@ -86,9 +86,9 @@ O framework utiliza um sistema de atributos customizados para declaratividade no
86
86
  ### TypeScript / ESM
87
87
 
88
88
  ```typescript
89
- import { createScope } from '@jsweb/ui'
89
+ import { createScope, reactive, watch } from '@jsweb/ui'
90
90
 
91
- const scope = {
91
+ const scope = reactive({
92
92
  count: 0,
93
93
  inc: 'Incremento',
94
94
  dec: 'Decremento',
@@ -98,7 +98,11 @@ const scope = {
98
98
  decrement() {
99
99
  this.count--
100
100
  },
101
- }
101
+ })
102
+
103
+ watch(() => scope.count, (newVal, oldVal) => {
104
+ console.log(`Contador mudou de ${oldVal} para ${newVal}`)
105
+ })
102
106
 
103
107
  createScope('#container', { scope })
104
108
  ```
package/index.es.js ADDED
@@ -0,0 +1,2 @@
1
+ var e=null,t=/* @__PURE__ */new WeakMap,n=/* @__PURE__ */new WeakMap,o=/* @__PURE__ */new WeakMap,i=class{active=!0;deps=/* @__PURE__ */new Set;constructor(e){this.fn=e}run(){if(!this.active)return this.fn();this.cleanup(),e=Symbol(),o.set(e,this);try{return this.fn()}finally{o.delete(e),e=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 s(e){const t=new i(e);return t.run(),t.effect()}function r(e,n){const o=t.get(e);if(!o)return;const i=o.get(n);i&&new Set(i).forEach(e=>e.run())}function c(i){if("object"!=typeof i||null===i)return i;if(Object.hasOwn(i,"_isReactive"))return i;const s=n.get(i);if(s)return s;const u=new Proxy(i,{get(n,i,s){if("_isReactive"===i)return!0;!function(n,i){if(e){let s=t.get(n);s||(s=/* @__PURE__ */new Map,t.set(n,s));let r=s.get(i);r||(r=/* @__PURE__ */new Set,s.set(i,r));const c=o.get(e);c&&(r.add(c),c.deps.add(r))}}(n,i);const r=Reflect.get(n,i,s);return"object"==typeof r&&null!==r?c(r):r},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),u=Reflect.set(e,t,n,o);return c?s!==n&&r(e,t):(r(e,t),i&&"length"!==t&&r(e,"length")),u}});return n.set(i,u),u}function u(e,t=/* @__PURE__ */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 f(e,t,n){let o,i=!0;const r=e instanceof Function?e:()=>u(e);return s(()=>{const e=r();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 a(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 d(e){const t=e;t._effects&&(t._effects.forEach(e=>e()),t._effects=[]);const n=Array.from(e.childNodes);for(const o of n)d(o)}function p(e,t={}){const n=e._isReactive?e:c(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 h(e,t){if(e.nodeType!==Node.ELEMENT_NODE)return;const n=e,o=function(e,t){const n=["ui:scope",":scope"],o=b(e,n);if(!o)return t;const i=l(o,t);if(!i)return;y(e,n),i.$emit||(i.$emit=(t,n)=>{e.dispatchEvent(new CustomEvent(t,{detail:n,bubbles:!0,composed:!0}))});return p(i,t)}(n,t);if(!o)return;const i=["ui:for",":for"],s=b(n,i);if(s)return y(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,r=["ui:key",":key"],u=b(e,r);y(e,r);const f=crypto.randomUUID(),a=document.createComment(` ui:for ${f} `);e.replaceWith(a);let m=[];v(a,()=>{const t=l(s,n);if(!Array.isArray(t))return m.forEach(e=>{e.el.remove(),d(e.el)}),void(m=[]);const o=[],r=/* @__PURE__ */new Map;m.forEach(e=>r.set(e.key,e)),t.forEach((t,s)=>{const f={[i]:t,$index:s};let a=s;u&&(a=l(u,p(f,n)));let d=r.get(a);if(d)d.scope[i]=t,d.scope.$index=s,r.delete(a);else{const t=e.cloneNode(!0),o=c(f);h(t,p(o,n)),d={key:a,el:t,scope:o}}o.push(d)}),r.forEach(e=>{e.el.remove(),d(e.el)});let f=a.nextSibling;o.forEach(e=>{f===e.el?f=f.nextSibling:a.parentNode?.insertBefore(e.el,f)}),m=o})}(n,s,o);const r=["ui:if",":if"],u=b(n,r);u&&(y(n,r),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,u,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),r=["ui:bind",":bind"].includes(n),c=["ui:class",":class"].includes(n),u=["ui:style",":style"].includes(n),f=n.startsWith("ui:")||n.startsWith(":"),l=n.startsWith("ui@")||n.startsWith("@");s?(g(e,i,t),e.removeAttribute(n)):r?(w(e,i,t),e.removeAttribute(n)):c?($(e,i,t),e.removeAttribute(n)):u?(A(e,i,t),e.removeAttribute(n)):f?(E(e,n.split(":").pop(),i,t),e.removeAttribute(n)):l&&(S(e,n,i,t),e.removeAttribute(n))}}(n,o);const f=Array.from(n.childNodes);for(const c of f)h(c,o)}function m(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}))}),h(n,t)):console.warn("[jsweb/ui] Element not found:",e)}function v(e,t){const n=s(t),o=e;o._effects??=[],o._effects.push(n.stop)}function b(e,t){for(const n of t){const t=e.getAttribute(n);if(null!==t)return t}return null}function y(e,t){for(const n of t)e.removeAttribute(n)}function g(e,t,n){v(e,()=>{const o=l(t,n);e.textContent=null!=o?String(o):""})}function w(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=>{a(e,`${t} = ${"$event.target."+(o?"checked":"value")}`,n)})}function E(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 $(e,t,n){let o=/* @__PURE__ */new Set;v(e,()=>{const i=l(t,n),s=/* @__PURE__ */new Set,r=e=>e&&s.add(e),c=e=>e.split(/\s+/).forEach(r);"string"==typeof i?c(i):Array.isArray(i)?i.flat().forEach(e=>{"string"==typeof e&&c(e)}):"object"==typeof i&&null!==i&&Object.entries(i).forEach(([e,t])=>{t&&c(e)}),o.forEach(t=>{s.has(t)||e.classList.remove(t)}),s.forEach(t=>{o.has(t)||e.classList.add(t)}),o=s})}function A(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 S(e,t,n,o){const[i,...s]=t.split("@").pop().split("."),r=s.includes("outside"),c=r?document:e,u=t=>{if(!e.isConnected)return;const i=t.target instanceof Node;r&&i&&e.contains(t.target)||s.includes("self")&&t.target!==e||(s.includes("prevent")&&t.preventDefault(),s.includes("stop")&&t.stopPropagation(),a(t,n,o))};if(c.addEventListener(i,u),r){const t=e;t._effects??=[],t._effects.push(()=>c.removeEventListener(i,u))}}if("undefined"!=typeof window){const e=window;e.jsweb=e.jsweb||{},e.jsweb.ui={createScope:m,reactive:c,watch:f}}export{m as createScope,c as reactive,f as watch};
2
+ //# sourceMappingURL=index.es.js.map
@@ -0,0 +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 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 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 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 const scope = { [itemName]: item, $index: index }\n let key: any = index\n\n if (keyDirective) {\n const tempContext = createContext(scope, context)\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 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 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 (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 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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"}
package/index.umd.js ADDED
@@ -0,0 +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{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 r(e){const t=new s(e);return t.run(),t.effect()}function c(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(Object.hasOwn(e,"_isReactive"))return e;const s=o.get(e);if(s)return s;const r=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 r=s.get(o);r||(r=new Set,s.set(o,r));const c=i.get(t);c&&(r.add(c),c.deps.add(r))}}(e,o);const r=Reflect.get(e,o,s);return"object"==typeof r&&null!==r?f(r):r},set(e,t,n,o){const i=Array.isArray(e),s=Reflect.get(e,t,o),r=i&&String(Number(t))===t?Number(t)<e.length:Object.hasOwn(e,t),f=Reflect.set(e,t,n,o);return r?s!==n&&c(e,t):(c(e,t),i&&"length"!==t&&c(e,"length")),f}});return o.set(e,r),r}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 l(e,t,n){let o,i=!0;const s=e instanceof Function?e:()=>u(e);return r(()=>{const e=s();i?(i=!1,o=e,n?.immediate&&t(e,void 0)):(t(e,o),o=e)}).stop}function a(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 b(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=a(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}))});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,r=["ui:key",":key"],c=y(e,r);g(e,r);const u=crypto.randomUUID(),l=document.createComment(` ui:for ${u} `);e.replaceWith(l);let d=[];v(l,()=>{const t=a(s,n);if(!Array.isArray(t))return d.forEach(e=>{e.el.remove(),p(e.el)}),void(d=[]);const o=[],r=new Map;d.forEach(e=>r.set(e.key,e)),t.forEach((t,s)=>{const u={[i]:t,$index:s};let l=s;c&&(l=a(c,h(u,n)));let d=r.get(l);if(d)d.scope[i]=t,d.scope.$index=s,r.delete(l);else{const t=e.cloneNode(!0),o=f(u);b(t,h(o,n)),d={key:l,el:t,scope:o}}o.push(d)}),r.forEach(e=>{e.el.remove(),p(e.el)});let u=l.nextSibling;o.forEach(e=>{u===e.el?u=u.nextSibling:l.parentNode?.insertBefore(e.el,u)}),d=o})}(n,s,o);const r=["ui:if",":if"],c=y(n,r);c&&(g(n,r),function(e,t,n){if(!e.parentNode)return;const o=crypto.randomUUID(),i=document.createComment(` ui:if ${o} `);e.before(i),v(i,()=>{a(t,n)?e.parentNode||i.parentNode?.insertBefore(e,i.nextSibling):e.parentNode&&e.remove()})}(n,c,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),r=["ui:bind",":bind"].includes(n),c=["ui:class",":class"].includes(n),f=["ui:style",":style"].includes(n),u=n.startsWith("ui:")||n.startsWith(":"),l=n.startsWith("ui@")||n.startsWith("@");s?(w(e,i,t),e.removeAttribute(n)):r?(E(e,i,t),e.removeAttribute(n)):c?(A(e,i,t),e.removeAttribute(n)):f?(S(e,i,t),e.removeAttribute(n)):u?($(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)b(f,o)}function m(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}))}),b(n,t)):console.warn("[jsweb/ui] Element not found:",e)}function v(e,t){const n=r(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){v(e,()=>{const o=a(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=a(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 $(e,t,n,o){v(e,()=>{const i=a(n,o);null==i||!1===i?e.removeAttribute(t):!0===i?e.setAttribute(t,""):e.setAttribute(t,String(i))})}function A(e,t,n){let o=new Set;v(e,()=>{const i=a(t,n),s=new Set,r=e=>e&&s.add(e),c=e=>e.split(/\s+/).forEach(r);"string"==typeof i?c(i):Array.isArray(i)?i.flat().forEach(e=>{"string"==typeof e&&c(e)}):"object"==typeof i&&null!==i&&Object.entries(i).forEach(([e,t])=>{t&&c(e)}),o.forEach(t=>{s.has(t)||e.classList.remove(t)}),s.forEach(t=>{o.has(t)||e.classList.add(t)}),o=s})}function S(e,t,n){let o={};v(e,()=>{const i=a(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("."),r=s.includes("outside"),c=r?document:e,f=t=>{if(!e.isConnected)return;const i=t.target instanceof Node;r&&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(c.addEventListener(i,f),r){const t=e;t._effects??=[],t._effects.push(()=>c.removeEventListener(i,f))}}if("undefined"!=typeof window){const e=window;e.jsweb=e.jsweb||{},e.jsweb.ui={createScope:m,reactive:f,watch:l}}e.createScope=m,e.reactive=f,e.watch=l});
2
+ //# sourceMappingURL=index.umd.js.map
@@ -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 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 // getter\n const desc = Object.getOwnPropertyDescriptor(obj, key)\n if (desc?.get) return desc.get.call(obj)\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 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 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 const scope = { [itemName]: item, $index: index }\n let key: any = index\n\n if (keyDirective) {\n const tempContext = createContext(scope, context)\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 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 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 (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 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.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 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 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 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 const scope = { [itemName]: item, $index: index }\n let key: any = index\n\n if (keyDirective) {\n const tempContext = createContext(scope, context)\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 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 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 (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 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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsweb/ui",
3
- "version": "1.2.6",
3
+ "version": "1.2.8",
4
4
  "description": "JS Web Microframework",
5
5
  "keywords": [
6
6
  "js",
@@ -21,30 +21,14 @@
21
21
  "license": "MIT",
22
22
  "author": "Alex Bruno Cáceres <email@alexbruno.dev>",
23
23
  "type": "module",
24
- "main": "dist/ui.umd.js",
25
- "module": "dist/ui.es.js",
26
- "types": "dist/index.d.ts",
24
+ "main": "index.umd.js",
25
+ "module": "index.es.js",
26
+ "types": "index.d.ts",
27
27
  "exports": {
28
28
  ".": {
29
- "import": "./dist/ui.es.js",
30
- "require": "./dist/ui.umd.js",
31
- "types": "./dist/index.d.ts"
29
+ "import": "./index.es.js",
30
+ "require": "./index.umd.js",
31
+ "types": "./index.d.ts"
32
32
  }
33
- },
34
- "scripts": {
35
- "dev": "vite",
36
- "build": "tsc && vite build && node publish.js",
37
- "preview": "vite preview",
38
- "format": "prettier --write .",
39
- "test": "echo 'test'",
40
- "preversion": "npm run build",
41
- "postversion": "git push && git push --tags"
42
- },
43
- "devDependencies": {
44
- "prettier": "^3.8.3",
45
- "terser": "^5.46.2",
46
- "typescript": "^6.0.3",
47
- "vite": "^8.0.10",
48
- "vite-plugin-dts": "^5.0.0"
49
33
  }
50
- }
34
+ }
@@ -1,21 +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
20
- env:
21
- NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
package/.prettierignore DELETED
@@ -1,3 +0,0 @@
1
- node_modules
2
- dist
3
- .gemini
package/.prettierrc DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "semi": false,
3
- "singleQuote": true,
4
- "tabWidth": 2,
5
- "printWidth": 80,
6
- "overrides": [{ "files": "*.md", "options": { "parser": "markdown" } }]
7
- }
package/HISTORY.md DELETED
@@ -1,36 +0,0 @@
1
- # JS Web UI - Histórico de Desenvolvimento
2
-
3
- Este documento serve como contexto histórico de todas as funcionalidades implementadas até o momento no **@jsweb/ui**, um micro-framework reativo, livre de dependências, de alta performance e sem Virtual DOM.
4
-
5
- ## 🧠 1. Core de Reatividade (`src/reactivity.ts`)
6
- Construímos um motor baseado em **Signals** usando `Proxy` para interceptar leituras (`track`) e escritas (`trigger`).
7
- - **Arquitetura `ReactiveEffect`:** A reatividade é gerenciada pela classe `ReactiveEffect`, permitindo controle refinado sobre o ciclo de vida dos efeitos (`stop`, `cleanup`) e garantindo limpeza eficiente de dependências para evitar vazamentos de memória. Também inclui micro-otimizações no rastreamento do Proxy para evitar buscas redundantes.
8
- - **Deep Reactivity:** A reatividade funciona recursivamente em objetos profundamente aninhados.
9
- - **Arrays e Mutabilidade:** Tratamento especial para arrays, onde a adição ou remoção de itens notifica dependências sobre a propriedade `length`, o que garante que loops reajam adequadamente a `.push`, `.pop`, etc.
10
- - **Transparência:** O usuário final trabalha com dados mutáveis puros sem a necessidade de getters/setters explícitos (ex: `state.count++` em vez de `state.count.value++`).
11
-
12
- ## ⚙️ 2. Motor de Avaliação (`src/evaluator.ts`)
13
- As expressões declaradas no HTML (ex: `:text="count + 1"`) são avaliadas de forma dinâmica.
14
- - **Execução Sandboxed Contextual:** Utilização do bloco `with(this)` dentro de `new Function` para permitir que expressões acessem propriedades do contexto diretamente, sem poluir o escopo global.
15
- - **Passagem Implícita e Explícita de Eventos:** O método `evaluateEvent` permite o uso de `$event` explícito (ex: `@click="log($event)"`) e também mapeia automaticamente a injeção do evento caso o usuário declare apenas o nome da função (ex: `@click="log"`).
16
-
17
- ## 🔤 3. Parser de Diretivas e DOM (`src/parser.ts`)
18
- Em vez de Virtual DOM, manipulamos o DOM real empacotando atualizações através da função `effect`. O Parser lê o HTML e amarra os `effects`.
19
-
20
- - **`:scope` (ou `ui:scope`):** Criação de escopos aninhados utilizando um esquema de herança de contextos (`createContext`) suportado por Proxies, permitindo "sombreamento" de propriedades corretas.
21
- - **`:text`:** Renderização de conteúdo reativo como `textContent`.
22
- - **`:if`:** Renderização condicional. O framework usa "âncoras" (`Comment Nodes`) para substituir dinamicamente o elemento no DOM quando a condição é falsa e restaurá-lo na posição exata quando for verdadeira.
23
- - **`:for` e Reconciliação:** Renderização de listas utilizando algoritmo de *diffing*. O motor rastreia chaves (`:key` ou fallback para índice) de cada elemento gerado e reutiliza os mesmos nós DOM (`RenderedNode`). Isso traz performance massiva e garante que atributos nativos do navegador (ex: foco de um input) não se percam em mudanças reativas do array.
24
- - **Atributos Genéricos (`:attr`):** Transformação dinâmica de qualquer atributo. Valores booleanos injetam/removem o atributo (ex: `disabled`).
25
- - **Classes e Estilos Dinâmicos (`:class`, `:style`):** Suporte nativo a dicionários, arrays e strings lógicas para manipulação avançada de classes e estilos (ex: `:class="{ 'is-active': active }"`).
26
- - **Eventos (`@event` e Modificadores):** Adição simples de ouvintes a qualquer evento DOM nativo. Inclui suporte nativo a **modificadores encadeados** com sintaxe de ponto (ex: `@submit.prevent`, `@click.stop`, `@click.self`, `@click.outside`) para um controle declarativo do comportamento do evento.
27
- - **Eventos Customizados (`$emit`):** Injeção automática da função `$emit` em todos os escopos criados. Permite que componentes despachem `CustomEvents` nativos na árvore do DOM, facilitando a comunicação entre escopos (`@custom-event`).
28
- - **Two-way Data Binding (`:bind`):** Suporte total a reatividade bidirecional (Tela <-> Estado) para `input[text]`, `input[checkbox]`, `input[radio]`, `<select>` e `<textarea>`. Sincroniza em tempo real tanto via evento `input` quanto `change`.
29
-
30
- ## 📦 4. Build e Bundling (`vite.config.ts`)
31
- - O framework está formatado como uma biblioteca agnóstica para ser consumida como script direto ou módulo ESM via NPM.
32
- - **Vite + Terser:** Optamos explicitamente por usar o *terser* em vez do *oxc* na etapa de minificação para atingir o nível máximo de compressão (cerca de ~1.8kB gzipped no estágio atual).
33
- - Geração automática de pacotes de tipagem (`dts`).
34
-
35
- ## 🎯 Próximos Passos (Backlog Futuro Sugerido)
36
- Para outros agentes, o framework atingiu um nível excepcional de maturidade (versão 0.2.0-ready). Os próximos passos lógicos envolvem a criação de testes unitários extensivos e a refatoração ou criação de um sistema robusto de roteamento e componentização via Web Components nativos, integrando esta sintaxe ao shadow DOM.
package/PROJECT.md DELETED
@@ -1,90 +0,0 @@
1
- # Especificação Técnica: Micro-Framework JS/TS (Codinome: @jsweb/ui)
2
-
3
- ## 1. Visão Geral
4
-
5
- **@jsweb/ui** é um micro-framework frontend focado em _Progressive Enhancement_ e DX (Developer Experience). Ele deve oferecer a reatividade moderna de frameworks como Vue 3 (Composition API) e a simplicidade de uso direto no HTML do Alpine.js, sem a necessidade obrigatória de um build step, mas totalmente otimizado para árvores de dependência (tree-shaking) quando usado em ambientes build-tooling.
6
-
7
- ## 2. Pilares Arquiteturais
8
-
9
- - **No Virtual DOM:** Utilização de reatividade de grão fino (Fine-grained reactivity) via `Proxy` ou `Signals`. Atualizações diretas no DOM real.
10
- - **Dual Distribution:**
11
- - **Standalone:** Arquivo único (IIFE/UMD) para inclusão via `<script src="...">`.
12
- - **Module:** Pacote ESM com exports nomeados para suporte a Tree-Shaking.
13
- - **Hybrid Context:** Suporte a definição de estado via Objetos Literais (POJOs) ou Classes TypeScript.
14
- - **Template Engine:** Baseado em atributos customizados no HTML (`ui:*` para diretivas e `ui@*` para eventos, com shorthands `@`, `:`).
15
-
16
- ## 3. Especificações do Motor (Core)
17
-
18
- ### A. Sistema de Reatividade
19
-
20
- - **Mecanismo:** Proxy-based em conjunto com a classe `ReactiveEffect`. O estado é interceptado para disparar "efeitos" com gerenciamento preciso de dependências, controle de ciclo de vida (`stop`, `cleanup`) e otimizado contra vazamento de memória.
21
- - **Global State:** Deve ser possível exportar um objeto reativo de um arquivo e importá-lo em múltiplos componentes/contextos, tornando-o um estado compartilhado.
22
- - **Global Effect:** Deve ser possível criar efeitos globais que reajam a mudanças em qualquer estado compartilhado.
23
- - **Local State:** Deve ser possível criar estados locais que reajam a mudanças apenas dentro do escopo do componente.
24
- - **Local Effect:** Deve ser possível criar efeitos locais que reajam a mudanças apenas dentro do escopo do componente.
25
- - **Lifecycle:** Deve ser possível criar efeitos que reajam a mudanças no ciclo de vida do componente.
26
- - **Cleanup:** Deve ser possível limpar os efeitos quando os componentes forem removidos do DOM.
27
- - **Watchers:** Deve ser possível criar watchers que reajam a mudanças em propriedades específicas do estado.
28
- - **Computed:** Deve ser possível criar propriedades computadas que reajam a mudanças em propriedades específicas do estado.
29
- - **Composition API:** Deve ser possível usar a Composition API para criar efeitos e reatividade e aninhar efeitos e reatividade em outros efeitos e reatividade.
30
-
31
- ### B. Avaliador de Expressões (The Evaluator)
32
-
33
- - **Implementação:** Uso de `new Function()` com `with(this)`.
34
- - **Estratégia de Execução:** Para avaliar expressões declaradas no HTML de forma encapsulada (sandboxed):
35
- 1. O motor encapsula o objeto/escopo em um Proxy de Contexto para resolução de dependências.
36
- 2. Constrói a função dinâmica: `new Function('with(this) { ... }')`.
37
- 3. Executa a função passando o escopo reativo atrelado ao `this`.
38
- 4. Para eventos, também expõe a variável nativa `$event`.
39
-
40
- ### C. Parser de Template
41
-
42
- - **Traversal:** Utilizar `TreeWalker` ou recursão otimizada para identificar diretivas.
43
- - **Limpeza:** Atributos `ui:*`, `ui:@*`, `@*` e `:*` devem ser removidos do DOM após a inicialização para manter o HTML limpo.
44
-
45
- ## 4. Sintaxe e Diretivas (v0.1.0)
46
-
47
- | Diretiva | Descrição | Exemplo |
48
- | :------------------- | :----------------------------------------------------------------------------- | :------------------------------------ |
49
- | `ui:scope` | Define o objeto de estado para o elemento e seus filhos. | `<div ui:scope="{ count: 0 }">` |
50
- | `ui:text` | Sincroniza o `textContent` com uma variável. | `<span ui:text="count"></span>` |
51
- | `:attr` | Shorthand para bind de atributos HTML nativos. | `<button :disabled="count > 10">` |
52
- | `:class` / `:style` | Bind dinâmico avançado para classes CSS e Estilos Inline (dicionários, arrays).| `<div :class="{ active: isActive }">` |
53
- | `@event` | Shorthand para event listeners (com suporte a modificadores). | `<button @click.prevent="save">` |
54
- | `$emit` | Despacha CustomEvents a partir do escopo atual. (Exposto no contexto) | `<button @click="$emit('custom')">` |
55
- | `:bind` | Two-way data binding para inputs, checkboxes, radios e selects. | `<input :bind="name">` |
56
- | `ui:if` | Adiciona/Remove o elemento do DOM (via Comment Node placeholder). | `<div ui:if="count > 0">` |
57
- | `ui:for` | Renderiza uma lista de elementos a partir de um array. | `<li ui:for="item in items">` |
58
-
59
- ## 5. Requisitos de Engenharia (Instruções para a IA)
60
-
61
- - **Linguagem:** TypeScript Estrito.
62
- - **Bundle Tool:** Vite (configurado para `build.lib` com formatos `es` e `umd`).
63
- - **Memory Management:** Garantir o `cleanup` de event listeners e observadores quando elementos `ui:if` ou `ui:for` forem removidos.
64
- - **Zero Dependencies:** O core não deve ter dependências externas de runtime.
65
- - **Estilo de Código:** Funcional, modular, com comentários JSDoc claros para explicar o funcionamento interno do Proxy, do Parser e das diretivas.
66
-
67
- ---
68
-
69
- ### Stack de Build (Vite)
70
-
71
- Para o `vite.config.ts`, utilize esta abordagem para satisfazer os requisitos de "Standalone" e "Module":
72
-
73
- ```typescript
74
- import { defineConfig } from 'vite'
75
- import dts from 'vite-plugin-dts'
76
-
77
- export default defineConfig({
78
- build: {
79
- lib: {
80
- entry: './src/index.ts',
81
- name: 'jswebui',
82
- fileName: (format) => `ui.${format}.js`,
83
- formats: ['es', 'umd'],
84
- },
85
- sourcemap: true,
86
- minify: 'terser',
87
- },
88
- plugins: [dts()], // Gera os tipos .d.ts automaticamente
89
- })
90
- ```
package/dist/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 jsweb
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
package/dist/README.md DELETED
@@ -1,104 +0,0 @@
1
- # @jsweb/ui
2
-
3
- ## Introdução
4
-
5
- O `@jsweb/ui` é um micro-framework frontend escrito em TypeScript, projetado para ser uma ferramenta leve, rápida e flexível para o desenvolvimento de interfaces de usuário. Ele combina a reatividade moderna de frameworks como Vue 3 (Composition API) com a simplicidade de uso direto no HTML, semelhante ao Alpine.js.
6
-
7
- ### Pilares Arquiteturais
8
-
9
- - **Sem Virtual DOM**: Utiliza reatividade de grão fino (Fine-grained reactivity) via `Proxy` para atualizações diretas no DOM real.
10
- - **Distribuição Dupla**:
11
- - **Standalone**: Arquivo único (IIFE/UMD) para inclusão via `<script src="...">`.
12
- - **Module**: Pacote ESM com exports nomeados para suporte a Tree-Shaking.
13
- - **Contexto Híbrido**: Suporta definição de estado via Objetos Literais (POJOs) ou Classes TypeScript.
14
- - **Template Engine**: Baseado em atributos customizados no HTML (`ui:*` para diretivas e `ui@*` para eventos, com shorthands `@`, `:`).
15
- - Suporte completo a **modificadores de eventos** encadeados (`.prevent`, `.stop`, `.self`, `.outside`).
16
-
17
- ## Instalação
18
-
19
- ### NPM
20
-
21
- ```bash
22
- npm i @jsweb/ui
23
- ```
24
-
25
- ### CDN
26
-
27
- ```html
28
- <script src="https://unpkg.com/@jsweb/ui"></script>
29
- ```
30
-
31
- ## Diretivas Disponíveis (v0.1.0)
32
-
33
- O framework utiliza um sistema de atributos customizados para declaratividade no HTML.
34
-
35
- | Diretiva | Descrição | Exemplo |
36
- | :------------------ | :------------------------------------------------------------------------------ | :------------------------------------ |
37
- | `ui:scope` | Define o objeto de estado para o elemento e seus filhos. | `<div ui:scope="{ count: 0 }">` |
38
- | `ui:text` | Sincroniza o `textContent` com uma variável. | `<span ui:text="count"></span>` |
39
- | `:attr` | Shorthand para bind de atributos HTML nativos. | `<button :disabled="count > 10">` |
40
- | `:class` / `:style` | Bind dinâmico avançado para classes CSS e Estilos Inline (dicionários, arrays). | `<div :class="{ active: isActive }">` |
41
- | `@event` | Shorthand para event listeners (com suporte a modificadores). | `<button @click.prevent="save">` |
42
- | `$emit` | Despacha CustomEvents a partir do escopo atual. (Exposto no contexto) | `<button @click="$emit('custom')">` |
43
- | `:bind` | Two-way data binding para inputs, checkboxes, radios e selects. | `<input :bind="name">` |
44
- | `ui:if` | Adiciona/Remove o elemento do DOM (via Comment Node placeholder). | `<div ui:if="count > 0">` |
45
- | `ui:for` | Renderiza uma lista de elementos a partir de um array. | `<li ui:for="item in items">` |
46
-
47
- ## Exemplo de Uso
48
-
49
- ### HTML
50
-
51
- ```html
52
- <!DOCTYPE html>
53
- <html lang="en">
54
- <head>
55
- <meta charset="UTF-8" />
56
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
57
- <title>JS Web UI</title>
58
- <script src="https://unpkg.com/@jsweb/ui"></script>
59
- <script>
60
- const scope = {
61
- count: 0,
62
- inc: 'Incremento',
63
- dec: 'Decremento',
64
- increment() {
65
- this.count++
66
- },
67
- decrement() {
68
- this.count--
69
- },
70
- }
71
-
72
- jsweb.ui.createScope('body', { scope })
73
- </script>
74
- </head>
75
- <body>
76
- <div ui:scope="scope">
77
- <h1>JS Web UI</h1>
78
- <p>Contador: <span ui:text="count"></span></p>
79
- <button ui:text="inc" @click="increment()"></button>
80
- <button ui:text="dec" @click="decrement()"></button>
81
- </div>
82
- </body>
83
- </html>
84
- ```
85
-
86
- ### TypeScript / ESM
87
-
88
- ```typescript
89
- import { createScope } from '@jsweb/ui'
90
-
91
- const scope = {
92
- count: 0,
93
- inc: 'Incremento',
94
- dec: 'Decremento',
95
- increment() {
96
- this.count++
97
- },
98
- decrement() {
99
- this.count--
100
- },
101
- }
102
-
103
- createScope('#container', { scope })
104
- ```