@jsweb/ui 0.2.0 → 1.0.0
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/dist/src/index.d.ts +2 -2
- package/dist/src/parser.d.ts +1 -1
- package/dist/ui.es.js +1 -1
- package/dist/ui.es.js.map +1 -1
- package/dist/ui.umd.js +1 -1
- package/dist/ui.umd.js.map +1 -1
- package/index.html +2 -2
- package/package.json +1 -1
- package/src/index.ts +3 -3
- package/src/parser.ts +19 -17
package/dist/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { reactive, effect, track, trigger } from './reactivity';
|
|
2
2
|
import { evaluate, evaluateEvent } from './evaluator';
|
|
3
|
-
import {
|
|
4
|
-
export { reactive, effect, track, trigger, evaluate, evaluateEvent,
|
|
3
|
+
import { createScope, parseNode } from './parser';
|
|
4
|
+
export { reactive, effect, track, trigger, evaluate, evaluateEvent, createScope, parseNode, };
|
package/dist/src/parser.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ export type Context = Record<string, any>;
|
|
|
2
2
|
export declare function cleanupTree(node: Node): void;
|
|
3
3
|
export declare function createContext(scope: any, context?: Context | null): Context;
|
|
4
4
|
export declare function parseNode(node: Node, context: Context): void;
|
|
5
|
-
export declare function
|
|
5
|
+
export declare function createScope(selectorOrElement: string | HTMLElement, context?: Context): void;
|
package/dist/ui.es.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e=null,t=/* @__PURE__ */new WeakMap,n=/* @__PURE__ */new WeakMap,o=/* @__PURE__ */new WeakMap,r=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 i(e){const t=new r(e);return t.run(),t.effect()}function s(n,r){if(e){let i=t.get(n);i||(i=/* @__PURE__ */new Map,t.set(n,i));let s=i.get(r);s||(s=/* @__PURE__ */new Set,i.set(r,s));const c=o.get(e);c&&(s.add(c),c.deps.add(s))}}function c(e,n){const o=t.get(e);if(!o)return;const r=o.get(n);r&&new Set(r).forEach(e=>e.run())}function u(e){if("object"!=typeof e||null===e)return e;if(Object.hasOwn(e,"_isReactive"))return e;const t=n.get(e);if(t)return t;const o=new Proxy(e,{get(e,t,n){if("_isReactive"===t)return!0;s(e,t);const o=Reflect.get(e,t,n);return"object"==typeof o&&null!==o?u(o):o},set(e,t,n,o){const r=Array.isArray(e),i=Reflect.get(e,t,o),s=r&&String(Number(t))===t?Number(t)<e.length:Object.hasOwn(e,t),u=Reflect.set(e,t,n,o);return s?i!==n&&c(e,t):(c(e,t),r&&"length"!==t&&c(e,"length")),u}});return n.set(e,o),o}function f(e,t={}){try{return new Function(`with(this) { return ${e} }`).call(t)}catch(n){return void console.error(`[jsweb/ui] Error evaluating expression: ${e}`,n)}}function a(e,t={},n){try{const o=e.trim(),r=/^[a-zA-Z_$][0-9a-zA-Z_$.]*$/.test(o);new Function("$event",`with(this) { ${r?`${o} instanceof Function ? ${o}.call(this, $event) : ${o}`:o} }`).call(t,n)}catch(o){console.error(`[jsweb/ui] Error evaluating event expression: ${e}`,o)}}function l(e){const t=e;t._effects&&(t._effects.forEach(e=>e()),t._effects=[]);const n=Array.from(e.childNodes);for(const o of n)l(o)}function d(e,t=null){const n=e._isReactive?e:u(e);return new Proxy(n,{get:(e,n)=>"_isContext"===n||(n in e?Reflect.get(e,n,e):t&&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):t&&n in t?Reflect.set(t,n,o,t):Reflect.set(e,n,o,e),has:(e,n)=>n in e||!(!t||!(n in t))})}function p(e,t){if(e.nodeType!==Node.ELEMENT_NODE)return;const n=e,o=function(e,t){const n=["ui:scope",":scope"],o=g(e,n);if(!o)return t;const r=f(o,t)||{};return
|
|
1
|
+
var e=null,t=/* @__PURE__ */new WeakMap,n=/* @__PURE__ */new WeakMap,o=/* @__PURE__ */new WeakMap,r=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 i(e){const t=new r(e);return t.run(),t.effect()}function s(n,r){if(e){let i=t.get(n);i||(i=/* @__PURE__ */new Map,t.set(n,i));let s=i.get(r);s||(s=/* @__PURE__ */new Set,i.set(r,s));const c=o.get(e);c&&(s.add(c),c.deps.add(s))}}function c(e,n){const o=t.get(e);if(!o)return;const r=o.get(n);r&&new Set(r).forEach(e=>e.run())}function u(e){if("object"!=typeof e||null===e)return e;if(Object.hasOwn(e,"_isReactive"))return e;const t=n.get(e);if(t)return t;const o=new Proxy(e,{get(e,t,n){if("_isReactive"===t)return!0;s(e,t);const o=Reflect.get(e,t,n);return"object"==typeof o&&null!==o?u(o):o},set(e,t,n,o){const r=Array.isArray(e),i=Reflect.get(e,t,o),s=r&&String(Number(t))===t?Number(t)<e.length:Object.hasOwn(e,t),u=Reflect.set(e,t,n,o);return s?i!==n&&c(e,t):(c(e,t),r&&"length"!==t&&c(e,"length")),u}});return n.set(e,o),o}function f(e,t={}){try{return new Function(`with(this) { return ${e} }`).call(t)}catch(n){return void console.error(`[jsweb/ui] Error evaluating expression: ${e}`,n)}}function a(e,t={},n){try{const o=e.trim(),r=/^[a-zA-Z_$][0-9a-zA-Z_$.]*$/.test(o);new Function("$event",`with(this) { ${r?`${o} instanceof Function ? ${o}.call(this, $event) : ${o}`:o} }`).call(t,n)}catch(o){console.error(`[jsweb/ui] Error evaluating event expression: ${e}`,o)}}function l(e){const t=e;t._effects&&(t._effects.forEach(e=>e()),t._effects=[]);const n=Array.from(e.childNodes);for(const o of n)l(o)}function d(e,t=null){const n=e._isReactive?e:u(e);return new Proxy(n,{get:(e,n)=>"_isContext"===n||(n in e?Reflect.get(e,n,e):t&&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):t&&n in t?Reflect.set(t,n,o,t):Reflect.set(e,n,o,e),has:(e,n)=>n in e||!(!t||!(n in t))})}function p(e,t){if(e.nodeType!==Node.ELEMENT_NODE)return;const n=e,o=function(e,t){const n=["ui:scope",":scope"],o=g(e,n);if(!o)return t;const r=f(o,t)||{};return w(e,n),d(r,t)}(n,t),r=["ui:for",":for"],i=g(n,r);if(i)return w(n,r),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[,r,i]=o,s=["ui:key",":key"],c=g(e,s);w(e,s);const a=crypto.randomUUID(),h=document.createComment(` ui:for ${a} `);e.replaceWith(h);let m=[];v(h,()=>{const t=f(i,n);if(!Array.isArray(t))return m.forEach(e=>{e.el.remove(),l(e.el)}),void(m=[]);const o=[],s=/* @__PURE__ */new Map;m.forEach(e=>s.set(e.key,e)),t.forEach((t,i)=>{const a={[r]:t,$index:i};let l=i;c&&(l=f(c,d(a,n)));let h=s.get(l);if(h)h.scope[r]=t,h.scope.$index=i,s.delete(l);else{const t=e.cloneNode(!0),o=u(a);p(t,d(o,n)),h={key:l,el:t,scope:o}}o.push(h)}),s.forEach(e=>{e.el.remove(),l(e.el)});let a=h.nextSibling;o.forEach(e=>{a===e.el?a=a.nextSibling:h.parentNode?.insertBefore(e.el,a)}),m=o})}(n,i,o);const s=["ui:if",":if"],c=g(n,s);c&&(w(n,s),function(e,t,n){if(!e.parentNode)return;const o=crypto.randomUUID(),r=document.createComment(` ui:if ${o} `);e.before(r),v(r,()=>{f(t,n)?e.parentNode||r.parentNode?.insertBefore(e,r.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:r}=o,i=["ui:text",":text"].includes(n),s=["ui:bind",":bind"].includes(n),c=n.startsWith("ui:")||n.startsWith(":"),u=n.startsWith("ui@")||n.startsWith("@");i?m(e,r,t):s?b(e,r,t):c?y(e,n.split(":").pop(),r,t):u&&E(e,n,r,t),e.removeAttribute(n)}}(n,o);const a=Array.from(n.childNodes);for(const u of a)p(u,o)}function h(e,t={}){const n="string"==typeof e?document.querySelector(e):e;n?p(n,t):console.warn("[jsweb/ui] Element not found:",e)}function v(e,t){const n=i(t),o=e;o._effects??=[],o._effects.push(n.stop)}function g(e,t){for(const n of t){const t=e.getAttribute(n);if(null!==t)return t}return null}function w(e,t){for(const n of t)e.removeAttribute(n)}function m(e,t,n){v(e,()=>{const o=f(t,n);e.textContent=null!=o?String(o):""})}function b(e,t,n){const o=e instanceof HTMLInputElement&&"checkbox"===e.type,r=e instanceof HTMLInputElement&&"radio"===e.type;v(e,()=>{const i=f(t,n);if(o)e.checked=!!i;else if(r)e.checked=e.value===String(i);else{e.value=null==i?"":String(i)}});const i=o||r||e instanceof HTMLSelectElement?"change":"input";e.addEventListener(i,e=>{a(o?`${t} = $event.target.checked`:`${t} = $event.target.value`,n,e)})}function y(e,t,n,o){v(e,()=>{const r=f(n,o);null==r||!1===r?e.removeAttribute(t):!0===r?e.setAttribute(t,""):e.setAttribute(t,String(r))})}function E(e,t,n,o){const[r,...i]=t.split("@").pop().split("."),s=i.includes("outside"),c=s?document:e,u=t=>{if(!e.isConnected)return;const r=t.target instanceof Node;s&&r&&e.contains(t.target)||i.includes("self")&&t.target!==e||(i.includes("prevent")&&t.preventDefault(),i.includes("stop")&&t.stopPropagation(),a(n,o,t))};if(c.addEventListener(r,u),s){const t=e;t._effects??=[],t._effects.push(()=>c.removeEventListener(r,u))}}if("undefined"!=typeof window){const e=window;e.jsweb=e.jsweb||{},e.jsweb.ui={createScope:h,reactive:u,effect:i,track:s,trigger:c,evaluate:f,evaluateEvent:a,parseNode:p}}export{h as createScope,i as effect,f as evaluate,a as evaluateEvent,p as parseNode,u as reactive,s as track,c as trigger};
|
|
2
2
|
//# sourceMappingURL=ui.es.js.map
|
package/dist/ui.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.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): any {\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 const res = Reflect.get(obj, key, receiver)\n // deep reactivity\n if (typeof res === 'object' && res !== null) {\n return reactive(res)\n }\n return 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","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 (error) {\n console.error(`[jsweb/ui] Error evaluating expression: ${expression}`, error)\n return undefined\n }\n}\n\nexport function evaluateEvent(\n expression: string,\n context: Record<string, any> = {},\n $event: Event,\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\n const fn = new Function('$event', `with(this) { ${result} }`)\n fn.call(context, $event)\n } catch (error) {\n console.error(\n `[jsweb/ui] Error evaluating event expression: ${expression}`,\n error,\n )\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) {\n cleanupTree(child)\n }\n}\n\nexport function createContext(\n scope: any,\n context: Context | null = null,\n): 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 (context && 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 (context && 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 (context && 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 currentContext = processScope(el, context)\n\n const forAttr = getDirectiveValue(el, ['ui:for', ':for'])\n if (forAttr) {\n removeDirectiveAttributes(el, ['ui:for', ':for'])\n processFor(el, forAttr, currentContext)\n return\n }\n\n const ifAttr = getDirectiveValue(el, ['ui:if', ':if'])\n if (ifAttr) {\n removeDirectiveAttributes(el, ['ui:if', ':if'])\n processIf(el, ifAttr, currentContext)\n }\n\n processAttributes(el, currentContext)\n\n const children = Array.from(el.childNodes)\n for (const child of children) {\n parseNode(child, currentContext)\n }\n}\n\nexport function createComponent(\n selectorOrElement: string | HTMLElement,\n rootContext: Context = {},\n) {\n const el =\n typeof selectorOrElement === 'string'\n ? document.querySelector(selectorOrElement)\n : selectorOrElement\n\n if (el) {\n parseNode(el, rootContext)\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 removeDirectiveAttributes(el, attrs)\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 keyExpr = 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 (keyExpr) {\n const tempContext = createContext(scope, context)\n key = evaluate(keyExpr, 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 isAttrBind = name.startsWith('ui:') || name.startsWith(':')\n const isEvent = name.startsWith('ui@') || name.startsWith('@')\n\n if (isText) {\n processTextBinding(el, value, context)\n } else if (isTwoWayBind) {\n processTwoWayBinding(el, value, context)\n } else if (isAttrBind) {\n const bound = name.split(':').pop()!\n processAttrBinding(el, bound, value, context)\n } else if (isEvent) {\n processEventBinding(el, name, value, context)\n }\n\n el.removeAttribute(name)\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 if (isCheckbox) {\n evaluateEvent(`${expr} = $event.target.checked`, context, $event)\n } else {\n evaluateEvent(`${expr} = $event.target.value`, context, $event)\n }\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 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(expr, context, $event)\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, effect, track, trigger } from './reactivity'\nimport { evaluate, evaluateEvent } from './evaluator'\nimport { createComponent, parseNode } from './parser'\n\nexport {\n reactive,\n effect,\n track,\n trigger,\n evaluate,\n evaluateEvent,\n createComponent,\n parseNode,\n}\n\nif (typeof window !== 'undefined') {\n const w = window as any\n w.jsweb = w.jsweb || {}\n w.jsweb.ui = {\n createComponent,\n reactive,\n effect,\n track,\n trigger,\n evaluate,\n evaluateEvent,\n parseNode,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ui.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): any {\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 const res = Reflect.get(obj, key, receiver)\n // deep reactivity\n if (typeof res === 'object' && res !== null) {\n return reactive(res)\n }\n return 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","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 (error) {\n console.error(`[jsweb/ui] Error evaluating expression: ${expression}`, error)\n return undefined\n }\n}\n\nexport function evaluateEvent(\n expression: string,\n context: Record<string, any> = {},\n $event: Event,\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\n const fn = new Function('$event', `with(this) { ${result} }`)\n fn.call(context, $event)\n } catch (error) {\n console.error(\n `[jsweb/ui] Error evaluating event expression: ${expression}`,\n error,\n )\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) {\n cleanupTree(child)\n }\n}\n\nexport function createContext(\n scope: any,\n context: Context | null = null,\n): 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 (context && 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 (context && 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 (context && 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\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) {\n parseNode(child, scope)\n }\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 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 removeDirectiveAttributes(el, attrs)\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 isAttrBind = name.startsWith('ui:') || name.startsWith(':')\n const isEvent = name.startsWith('ui@') || name.startsWith('@')\n\n if (isText) {\n processTextBinding(el, value, context)\n } else if (isTwoWayBind) {\n processTwoWayBinding(el, value, context)\n } else if (isAttrBind) {\n const bound = name.split(':').pop()!\n processAttrBinding(el, bound, value, context)\n } else if (isEvent) {\n processEventBinding(el, name, value, context)\n }\n\n el.removeAttribute(name)\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 if (isCheckbox) {\n evaluateEvent(`${expr} = $event.target.checked`, context, $event)\n } else {\n evaluateEvent(`${expr} = $event.target.value`, context, $event)\n }\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 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(expr, context, $event)\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, effect, track, trigger } from './reactivity'\nimport { evaluate, evaluateEvent } from './evaluator'\nimport { createScope, parseNode } from './parser'\n\nexport {\n reactive,\n effect,\n track,\n trigger,\n evaluate,\n evaluateEvent,\n createScope,\n parseNode,\n}\n\nif (typeof window !== 'undefined') {\n const w = window as any\n w.jsweb = w.jsweb || {}\n w.jsweb.ui = {\n createScope,\n reactive,\n effect,\n track,\n trigger,\n evaluate,\n evaluateEvent,\n parseNode,\n }\n}\n"],"mappings":"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"}
|
package/dist/ui.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,r=new WeakMap,i=class{active=!0;deps=new Set;constructor(e){this.fn=e}run(){if(!this.active)return this.fn();this.cleanup(),t=Symbol(),r.set(t,this);try{return this.fn()}finally{r.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 s(e){const t=new i(e);return t.run(),t.effect()}function c(e,o){if(t){let i=n.get(e);i||(i=new Map,n.set(e,i));let s=i.get(o);s||(s=new Set,i.set(o,s));const c=r.get(t);c&&(s.add(c),c.deps.add(s))}}function f(e,t){const o=n.get(e);if(!o)return;const r=o.get(t);r&&new Set(r).forEach(e=>e.run())}function u(e){if("object"!=typeof e||null===e)return e;if(Object.hasOwn(e,"_isReactive"))return e;const t=o.get(e);if(t)return t;const n=new Proxy(e,{get(e,t,n){if("_isReactive"===t)return!0;c(e,t);const o=Reflect.get(e,t,n);return"object"==typeof o&&null!==o?u(o):o},set(e,t,n,o){const r=Array.isArray(e),i=Reflect.get(e,t,o),s=r&&String(Number(t))===t?Number(t)<e.length:Object.hasOwn(e,t),c=Reflect.set(e,t,n,o);return s?i!==n&&f(e,t):(f(e,t),r&&"length"!==t&&f(e,"length")),c}});return o.set(e,n),n}function a(e,t={}){try{return new Function(`with(this) { return ${e} }`).call(t)}catch(n){return void console.error(`[jsweb/ui] Error evaluating expression: ${e}`,n)}}function l(e,t={},n){try{const o=e.trim(),r=/^[a-zA-Z_$][0-9a-zA-Z_$.]*$/.test(o);new Function("$event",`with(this) { ${r?`${o} instanceof Function ? ${o}.call(this, $event) : ${o}`:o} }`).call(t,n)}catch(o){console.error(`[jsweb/ui] Error evaluating event expression: ${e}`,o)}}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=null){const n=e._isReactive?e:u(e);return new Proxy(n,{get:(e,n)=>"_isContext"===n||(n in e?Reflect.get(e,n,e):t&&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):t&&n in t?Reflect.set(t,n,o,t):Reflect.set(e,n,o,e),has:(e,n)=>n in e||!(!t||!(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 r=a(o,t)||{};return
|
|
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,r=new WeakMap,i=class{active=!0;deps=new Set;constructor(e){this.fn=e}run(){if(!this.active)return this.fn();this.cleanup(),t=Symbol(),r.set(t,this);try{return this.fn()}finally{r.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 s(e){const t=new i(e);return t.run(),t.effect()}function c(e,o){if(t){let i=n.get(e);i||(i=new Map,n.set(e,i));let s=i.get(o);s||(s=new Set,i.set(o,s));const c=r.get(t);c&&(s.add(c),c.deps.add(s))}}function f(e,t){const o=n.get(e);if(!o)return;const r=o.get(t);r&&new Set(r).forEach(e=>e.run())}function u(e){if("object"!=typeof e||null===e)return e;if(Object.hasOwn(e,"_isReactive"))return e;const t=o.get(e);if(t)return t;const n=new Proxy(e,{get(e,t,n){if("_isReactive"===t)return!0;c(e,t);const o=Reflect.get(e,t,n);return"object"==typeof o&&null!==o?u(o):o},set(e,t,n,o){const r=Array.isArray(e),i=Reflect.get(e,t,o),s=r&&String(Number(t))===t?Number(t)<e.length:Object.hasOwn(e,t),c=Reflect.set(e,t,n,o);return s?i!==n&&f(e,t):(f(e,t),r&&"length"!==t&&f(e,"length")),c}});return o.set(e,n),n}function a(e,t={}){try{return new Function(`with(this) { return ${e} }`).call(t)}catch(n){return void console.error(`[jsweb/ui] Error evaluating expression: ${e}`,n)}}function l(e,t={},n){try{const o=e.trim(),r=/^[a-zA-Z_$][0-9a-zA-Z_$.]*$/.test(o);new Function("$event",`with(this) { ${r?`${o} instanceof Function ? ${o}.call(this, $event) : ${o}`:o} }`).call(t,n)}catch(o){console.error(`[jsweb/ui] Error evaluating event expression: ${e}`,o)}}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=null){const n=e._isReactive?e:u(e);return new Proxy(n,{get:(e,n)=>"_isContext"===n||(n in e?Reflect.get(e,n,e):t&&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):t&&n in t?Reflect.set(t,n,o,t):Reflect.set(e,n,o,e),has:(e,n)=>n in e||!(!t||!(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 r=a(o,t)||{};return y(e,n),p(r,t)}(n,t),r=["ui:for",":for"],i=b(n,r);if(i)return y(n,r),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[,r,i]=o,s=["ui:key",":key"],c=b(e,s);y(e,s);const f=crypto.randomUUID(),l=document.createComment(` ui:for ${f} `);e.replaceWith(l);let g=[];v(l,()=>{const t=a(i,n);if(!Array.isArray(t))return g.forEach(e=>{e.el.remove(),d(e.el)}),void(g=[]);const o=[],s=new Map;g.forEach(e=>s.set(e.key,e)),t.forEach((t,i)=>{const f={[r]:t,$index:i};let l=i;c&&(l=a(c,p(f,n)));let d=s.get(l);if(d)d.scope[r]=t,d.scope.$index=i,s.delete(l);else{const t=e.cloneNode(!0),o=u(f);h(t,p(o,n)),d={key:l,el:t,scope:o}}o.push(d)}),s.forEach(e=>{e.el.remove(),d(e.el)});let f=l.nextSibling;o.forEach(e=>{f===e.el?f=f.nextSibling:l.parentNode?.insertBefore(e.el,f)}),g=o})}(n,i,o);const s=["ui:if",":if"],c=b(n,s);c&&(y(n,s),function(e,t,n){if(!e.parentNode)return;const o=crypto.randomUUID(),r=document.createComment(` ui:if ${o} `);e.before(r),v(r,()=>{a(t,n)?e.parentNode||r.parentNode?.insertBefore(e,r.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:r}=o,i=["ui:text",":text"].includes(n),s=["ui:bind",":bind"].includes(n),c=n.startsWith("ui:")||n.startsWith(":"),f=n.startsWith("ui@")||n.startsWith("@");i?m(e,r,t):s?w(e,r,t):c?E(e,n.split(":").pop(),r,t):f&&$(e,n,r,t),e.removeAttribute(n)}}(n,o);const f=Array.from(n.childNodes);for(const u of f)h(u,o)}function g(e,t={}){const n="string"==typeof e?document.querySelector(e):e;n?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 m(e,t,n){v(e,()=>{const o=a(t,n);e.textContent=null!=o?String(o):""})}function w(e,t,n){const o=e instanceof HTMLInputElement&&"checkbox"===e.type,r=e instanceof HTMLInputElement&&"radio"===e.type;v(e,()=>{const i=a(t,n);if(o)e.checked=!!i;else if(r)e.checked=e.value===String(i);else{e.value=null==i?"":String(i)}});const i=o||r||e instanceof HTMLSelectElement?"change":"input";e.addEventListener(i,e=>{l(o?`${t} = $event.target.checked`:`${t} = $event.target.value`,n,e)})}function E(e,t,n,o){v(e,()=>{const r=a(n,o);null==r||!1===r?e.removeAttribute(t):!0===r?e.setAttribute(t,""):e.setAttribute(t,String(r))})}function $(e,t,n,o){const[r,...i]=t.split("@").pop().split("."),s=i.includes("outside"),c=s?document:e,f=t=>{if(!e.isConnected)return;const r=t.target instanceof Node;s&&r&&e.contains(t.target)||i.includes("self")&&t.target!==e||(i.includes("prevent")&&t.preventDefault(),i.includes("stop")&&t.stopPropagation(),l(n,o,t))};if(c.addEventListener(r,f),s){const t=e;t._effects??=[],t._effects.push(()=>c.removeEventListener(r,f))}}if("undefined"!=typeof window){const e=window;e.jsweb=e.jsweb||{},e.jsweb.ui={createScope:g,reactive:u,effect:s,track:c,trigger:f,evaluate:a,evaluateEvent:l,parseNode:h}}e.createScope=g,e.effect=s,e.evaluate=a,e.evaluateEvent=l,e.parseNode=h,e.reactive=u,e.track=c,e.trigger=f});
|
|
2
2
|
//# sourceMappingURL=ui.umd.js.map
|
package/dist/ui.umd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.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): any {\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 const res = Reflect.get(obj, key, receiver)\n // deep reactivity\n if (typeof res === 'object' && res !== null) {\n return reactive(res)\n }\n return 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","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 (error) {\n console.error(`[jsweb/ui] Error evaluating expression: ${expression}`, error)\n return undefined\n }\n}\n\nexport function evaluateEvent(\n expression: string,\n context: Record<string, any> = {},\n $event: Event,\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\n const fn = new Function('$event', `with(this) { ${result} }`)\n fn.call(context, $event)\n } catch (error) {\n console.error(\n `[jsweb/ui] Error evaluating event expression: ${expression}`,\n error,\n )\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) {\n cleanupTree(child)\n }\n}\n\nexport function createContext(\n scope: any,\n context: Context | null = null,\n): 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 (context && 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 (context && 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 (context && 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 currentContext = processScope(el, context)\n\n const forAttr = getDirectiveValue(el, ['ui:for', ':for'])\n if (forAttr) {\n removeDirectiveAttributes(el, ['ui:for', ':for'])\n processFor(el, forAttr, currentContext)\n return\n }\n\n const ifAttr = getDirectiveValue(el, ['ui:if', ':if'])\n if (ifAttr) {\n removeDirectiveAttributes(el, ['ui:if', ':if'])\n processIf(el, ifAttr, currentContext)\n }\n\n processAttributes(el, currentContext)\n\n const children = Array.from(el.childNodes)\n for (const child of children) {\n parseNode(child, currentContext)\n }\n}\n\nexport function createComponent(\n selectorOrElement: string | HTMLElement,\n rootContext: Context = {},\n) {\n const el =\n typeof selectorOrElement === 'string'\n ? document.querySelector(selectorOrElement)\n : selectorOrElement\n\n if (el) {\n parseNode(el, rootContext)\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 removeDirectiveAttributes(el, attrs)\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 keyExpr = 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 (keyExpr) {\n const tempContext = createContext(scope, context)\n key = evaluate(keyExpr, 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 isAttrBind = name.startsWith('ui:') || name.startsWith(':')\n const isEvent = name.startsWith('ui@') || name.startsWith('@')\n\n if (isText) {\n processTextBinding(el, value, context)\n } else if (isTwoWayBind) {\n processTwoWayBinding(el, value, context)\n } else if (isAttrBind) {\n const bound = name.split(':').pop()!\n processAttrBinding(el, bound, value, context)\n } else if (isEvent) {\n processEventBinding(el, name, value, context)\n }\n\n el.removeAttribute(name)\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 if (isCheckbox) {\n evaluateEvent(`${expr} = $event.target.checked`, context, $event)\n } else {\n evaluateEvent(`${expr} = $event.target.value`, context, $event)\n }\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 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(expr, context, $event)\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, effect, track, trigger } from './reactivity'\nimport { evaluate, evaluateEvent } from './evaluator'\nimport { createComponent, parseNode } from './parser'\n\nexport {\n reactive,\n effect,\n track,\n trigger,\n evaluate,\n evaluateEvent,\n createComponent,\n parseNode,\n}\n\nif (typeof window !== 'undefined') {\n const w = window as any\n w.jsweb = w.jsweb || {}\n w.jsweb.ui = {\n createComponent,\n reactive,\n effect,\n track,\n trigger,\n evaluate,\n evaluateEvent,\n parseNode,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ui.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): any {\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 const res = Reflect.get(obj, key, receiver)\n // deep reactivity\n if (typeof res === 'object' && res !== null) {\n return reactive(res)\n }\n return 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","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 (error) {\n console.error(`[jsweb/ui] Error evaluating expression: ${expression}`, error)\n return undefined\n }\n}\n\nexport function evaluateEvent(\n expression: string,\n context: Record<string, any> = {},\n $event: Event,\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\n const fn = new Function('$event', `with(this) { ${result} }`)\n fn.call(context, $event)\n } catch (error) {\n console.error(\n `[jsweb/ui] Error evaluating event expression: ${expression}`,\n error,\n )\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) {\n cleanupTree(child)\n }\n}\n\nexport function createContext(\n scope: any,\n context: Context | null = null,\n): 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 (context && 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 (context && 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 (context && 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\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) {\n parseNode(child, scope)\n }\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 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 removeDirectiveAttributes(el, attrs)\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 isAttrBind = name.startsWith('ui:') || name.startsWith(':')\n const isEvent = name.startsWith('ui@') || name.startsWith('@')\n\n if (isText) {\n processTextBinding(el, value, context)\n } else if (isTwoWayBind) {\n processTwoWayBinding(el, value, context)\n } else if (isAttrBind) {\n const bound = name.split(':').pop()!\n processAttrBinding(el, bound, value, context)\n } else if (isEvent) {\n processEventBinding(el, name, value, context)\n }\n\n el.removeAttribute(name)\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 if (isCheckbox) {\n evaluateEvent(`${expr} = $event.target.checked`, context, $event)\n } else {\n evaluateEvent(`${expr} = $event.target.value`, context, $event)\n }\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 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(expr, context, $event)\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, effect, track, trigger } from './reactivity'\nimport { evaluate, evaluateEvent } from './evaluator'\nimport { createScope, parseNode } from './parser'\n\nexport {\n reactive,\n effect,\n track,\n trigger,\n evaluate,\n evaluateEvent,\n createScope,\n parseNode,\n}\n\nif (typeof window !== 'undefined') {\n const w = window as any\n w.jsweb = w.jsweb || {}\n w.jsweb.ui = {\n createScope,\n reactive,\n effect,\n track,\n trigger,\n evaluate,\n evaluateEvent,\n parseNode,\n }\n}\n"],"mappings":"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"}
|
package/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>JS Web UI Test</title>
|
|
7
7
|
<script type="module">
|
|
8
|
-
import {
|
|
8
|
+
import { createScope, reactive } from '/src/index.ts'
|
|
9
9
|
|
|
10
10
|
const scope = reactive({
|
|
11
11
|
count: 0,
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
|
|
45
45
|
window.scope = scope
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
createScope('body', { scope })
|
|
48
48
|
</script>
|
|
49
49
|
</head>
|
|
50
50
|
<body>
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { reactive, effect, track, trigger } from './reactivity'
|
|
2
2
|
import { evaluate, evaluateEvent } from './evaluator'
|
|
3
|
-
import {
|
|
3
|
+
import { createScope, parseNode } from './parser'
|
|
4
4
|
|
|
5
5
|
export {
|
|
6
6
|
reactive,
|
|
@@ -9,7 +9,7 @@ export {
|
|
|
9
9
|
trigger,
|
|
10
10
|
evaluate,
|
|
11
11
|
evaluateEvent,
|
|
12
|
-
|
|
12
|
+
createScope,
|
|
13
13
|
parseNode,
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -17,7 +17,7 @@ if (typeof window !== 'undefined') {
|
|
|
17
17
|
const w = window as any
|
|
18
18
|
w.jsweb = w.jsweb || {}
|
|
19
19
|
w.jsweb.ui = {
|
|
20
|
-
|
|
20
|
+
createScope,
|
|
21
21
|
reactive,
|
|
22
22
|
effect,
|
|
23
23
|
track,
|
package/src/parser.ts
CHANGED
|
@@ -53,32 +53,34 @@ export function parseNode(node: Node, context: Context) {
|
|
|
53
53
|
if (node.nodeType !== Node.ELEMENT_NODE) return
|
|
54
54
|
|
|
55
55
|
const el = node as HTMLElement
|
|
56
|
-
const
|
|
56
|
+
const scope = processScope(el, context)
|
|
57
57
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
const forAttrs = ['ui:for', ':for']
|
|
59
|
+
const forDirective = getDirectiveValue(el, forAttrs)
|
|
60
|
+
if (forDirective) {
|
|
61
|
+
removeDirectiveAttributes(el, forAttrs)
|
|
62
|
+
processFor(el, forDirective, scope)
|
|
62
63
|
return
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
const ifAttrs = ['ui:if', ':if']
|
|
67
|
+
const ifDirective = getDirectiveValue(el, ifAttrs)
|
|
68
|
+
if (ifDirective) {
|
|
69
|
+
removeDirectiveAttributes(el, ifAttrs)
|
|
70
|
+
processIf(el, ifDirective, scope)
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
processAttributes(el,
|
|
73
|
+
processAttributes(el, scope)
|
|
72
74
|
|
|
73
75
|
const children = Array.from(el.childNodes)
|
|
74
76
|
for (const child of children) {
|
|
75
|
-
parseNode(child,
|
|
77
|
+
parseNode(child, scope)
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
export function
|
|
81
|
+
export function createScope(
|
|
80
82
|
selectorOrElement: string | HTMLElement,
|
|
81
|
-
|
|
83
|
+
context: Context = {},
|
|
82
84
|
) {
|
|
83
85
|
const el =
|
|
84
86
|
typeof selectorOrElement === 'string'
|
|
@@ -86,7 +88,7 @@ export function createComponent(
|
|
|
86
88
|
: selectorOrElement
|
|
87
89
|
|
|
88
90
|
if (el) {
|
|
89
|
-
parseNode(el,
|
|
91
|
+
parseNode(el, context)
|
|
90
92
|
} else {
|
|
91
93
|
console.warn('[jsweb/ui] Element not found:', selectorOrElement)
|
|
92
94
|
}
|
|
@@ -134,7 +136,7 @@ function processFor(el: HTMLElement, expr: string, context: Context) {
|
|
|
134
136
|
const [, itemName, listName] = match
|
|
135
137
|
|
|
136
138
|
const keyAttr = ['ui:key', ':key']
|
|
137
|
-
const
|
|
139
|
+
const keyDirective = getDirectiveValue(el, keyAttr)
|
|
138
140
|
removeDirectiveAttributes(el, keyAttr)
|
|
139
141
|
|
|
140
142
|
const uuid = crypto.randomUUID()
|
|
@@ -168,9 +170,9 @@ function processFor(el: HTMLElement, expr: string, context: Context) {
|
|
|
168
170
|
const scope = { [itemName]: item, $index: index }
|
|
169
171
|
let key: any = index
|
|
170
172
|
|
|
171
|
-
if (
|
|
173
|
+
if (keyDirective) {
|
|
172
174
|
const tempContext = createContext(scope, context)
|
|
173
|
-
key = evaluate(
|
|
175
|
+
key = evaluate(keyDirective, tempContext)
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
let node = oldNodesByKey.get(key)
|