sprae 9.1.0 → 10.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/core.js +66 -88
- package/directive/aria.js +2 -1
- package/directive/class.js +8 -6
- package/directive/data.js +4 -3
- package/directive/default.js +19 -16
- package/directive/each.js +84 -47
- package/directive/fx.js +2 -1
- package/directive/if.js +10 -8
- package/directive/ref.js +9 -8
- package/directive/style.js +6 -4
- package/directive/text.js +4 -3
- package/directive/value.js +2 -1
- package/directive/with.js +16 -0
- package/dist/sprae.js +451 -560
- package/dist/sprae.min.js +1 -1
- package/package.json +6 -5
- package/readme.md +74 -88
- package/signal.js +10 -0
- package/sprae.js +3 -4
- package/store.js +156 -0
- package/directive/scope.js +0 -10
package/dist/sprae.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=Object.defineProperty,t=(e,r,a,l=null)=>{let s,n,o,i=0,c=a.length,u=r.length,{remove:p,same:f,insert:d,replace:v}=t;for(;i<c&&i<u&&f(r[i],a[i]);)i++;for(;i<c&&i<u&&f(a[c-1],r[u-1]);)l=a[(--u,--c)];if(i==u)for(;i<c;)d(l,a[i++],e);else{for(s=r[i];i<c;)o=a[i++],n=s?s.nextSibling:l,f(s,o)?s=n:i<c&&f(a[i],n)?(v(s,o,e),s=n):d(s,o,e);for(;!f(s,l);)n=s.nextSibling,p(s,e),s=n}return a};t.same=(e,t)=>e==t,t.replace=(e,t,r)=>r.replaceChild(t,e),t.insert=(e,t,r)=>r.insertBefore(t,e),t.remove=(e,t)=>t.removeChild(e);var r,a=t,l={};((t,r)=>{for(var a in r)e(t,a,{get:r[a],enumerable:!0})})(l,{batch:()=>u,computed:()=>c,current:()=>r,effect:()=>i,signal:()=>o,untracked:()=>p});var s,n,o=(e,t,a=new Set)=>((t={get value(){return r?.deps.push(a.add(r)),e},set value(t){if(t!==e){e=t;for(let e of a)e(t)}},peek:()=>e}).toJSON=t.then=t.toString=t.valueOf=()=>t.value,t),i=(e,t,a,l)=>(l=(a=l=>{t?.call?.(),l=r,r=a;try{t=e()}finally{r=l}}).deps=[],a(),e=>{for(t?.call?.();e=l.pop();)e.delete(a)}),c=(e,t=o(),r,a)=>((r={get value(){return a||=i((()=>t.value=e())),t.value},peek:t.peek}).toJSON=r.then=r.toString=r.valueOf=()=>r.value,r),u=e=>e(),p=(e,t,a)=>(t=r,r=null,a=e(),r=t,a),f=e=>(s=0,n=e,e=h(),n[s]?d():e||""),d=(e="Bad syntax",t=n.slice(0,s).split("\n"),r=t.pop())=>{let a=n.slice(s-108,s).split("\n").pop(),l=n.slice(s,s+108).split("\n").shift();throw EvalError(`${e} at ${t.length}:${r.length} \`${s>=108?"…":""}${a}┃${l}\``,"font-weight: bold")},v=(e,t=s,r)=>{for(;r=e(n.charCodeAt(s));)s+=r;return n.slice(t,s)},y=(e=1,t=s)=>(s+=e,n.slice(t,s)),h=(e=0,t,r,a,l,n)=>{for(;(r=f.space())&&(l=((n=m[r])&&n(a,e))??(!a&&v(f.id)));)a=l;return t&&(r==t?s++:d()),a},m=(f.id=e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192&&215!=e&&247!=e,f.space=e=>{for(;(e=n.charCodeAt(s))<=32;)s++;return e},[]),g=(e,t=32,r,a=e.charCodeAt(0),l=e.length,o=m[a],i=e.toUpperCase()!==e)=>m[a]=(a,c,u=s)=>c<t&&(l<2||n.substr(s,l)==e)&&(!i||!f.id(n.charCodeAt(s+l)))&&(s+=l,r(a,c))||(s=u,o?.(a,c)),b=(e,t,r=!1)=>g(e,t,((a,l)=>a&&(l=h(t-(r?.5:0)))&&[e,a,l])),k=(e,t,r)=>g(e,t,(a=>r?a&&[e,a]:!a&&(a=h(t-.5))&&[e,a])),A=(e,t)=>{g(e,t,((r,a)=>(a=h(t),(!r||r[0]!==e)&&(r=[e,r]),r.push(a),r)))},N=(e,t)=>g(e[0],t,(t=>!t&&[e,h(0,e.charCodeAt(1))])),O=(e,t)=>g(e[0],t,(t=>t&&[e[0],t,h(0,e.charCodeAt(1))])),w=f,C=e=>Array.isArray(e)?e[0]?S[e[0]](...e.slice(1)):()=>e[1]:C.id(e),S=(C.id=e=>t=>t?.[e],{}),x=(e,t,r=S[e])=>S[e]=(...e)=>t(...e)||r&&r(...e),$=(e,t,r,a,l)=>"()"===e[0]?$(e[1],t,r):"string"==typeof e?r=>t(r,e,r):"."===e[0]?(a=C(e[1]),l=e[2],e=>t(a(e),l,e)):"["===e[0]?(a=C(e[1]),l=C(e[2]),e=>t(a(e),l(e),e)):r?(e=C(e),r=>t([e(r)],0,r)):()=>d("Bad left value"),E=C,T=(e,t)=>[,(e=+v((e=>46===e||e>=48&&e<=57||(69===e||101===e?2:0))))!=e?d():e];m[46]=e=>!e&&T();for(let e=48;e<=57;e++)m[e]=e=>e?d():T();var W={n:"\n",r:"\r",t:"\t",b:"\b",f:"\f",v:"\v"},j=e=>(t,r,a="")=>{for(t&&d("Unexpected string"),y();(r=n.charCodeAt(s))-e;)92===r?(y(),r=y(),a+=W[r]||r):a+=y();return y()||d("Bad string"),[,a]};m[34]=j(34),m[39]=j(39),O("()",17),x("(",((e,t,r)=>(r=t?","===t[0]?(t=t.slice(1).map((e=>e?C(e):err())),e=>t.map((t=>t(e)))):(t=C(t),e=>[t(e)]):()=>[],$(e,((e,t,a)=>e[t](...r(a))),!0)))),O("[]",17),x("[",((e,t)=>t?(e=C(e),t=C(t),r=>e(r)[t(r)]):err())),b(".",17),x(".",((e,t)=>(e=C(e),t=t[0]?t:t[1],r=>e(r)[t]))),N("()",17),x("()",(e=>(!e&&d("Empty ()"),C(e))));var L=(...e)=>(e=e.map(C),t=>e.map((e=>e(t))).pop());A(",",1),x(",",L),A(";",1),x(";",L),b("*",12),x("*",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)*t(r)))),b("/",12),x("/",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)/t(r)))),b("%",12),x("%",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)%t(r)))),b("*=",2,!0),x("*=",((e,t)=>(t=C(t),$(e,((e,r,a)=>e[r]*=t(a)))))),b("/=",2,!0),x("/=",((e,t)=>(t=C(t),$(e,((e,r,a)=>e[r]/=t(a)))))),b("%=",2,!0),x("%=",((e,t)=>(t=C(t),$(e,((e,r,a)=>e[r]%=t(a)))))),k("+",14),x("+",((e,t)=>!t&&(e=C(e),t=>+e(t)))),k("-",14),x("-",((e,t)=>!t&&(e=C(e),t=>-e(t)))),b("+",11),x("+",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)+t(r)))),b("-",11),x("-",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)-t(r)))),b("+=",2,!0),x("+=",((e,t)=>(t=C(t),$(e,((e,r,a)=>e[r]+=t(a)))))),b("-=",2,!0),x("-=",((e,t)=>(t=C(t),$(e,((e,r,a)=>e[r]-=t(a)))))),g("++",15,(e=>e?["++-",e]:["++",h(14)])),x("++",(e=>$(e,((e,t,r)=>++e[t])))),x("++-",(e=>$(e,((e,t,r)=>e[t]++)))),g("--",15,(e=>e?["--+",e]:["--",h(14)])),x("--",(e=>$(e,((e,t,r)=>--e[t])))),x("--+",(e=>$(e,((e,t,r)=>e[t]--)))),k("~",14),x("~",((e,t)=>!t&&(e=C(e),t=>~e(t)))),b("|",5),x("|",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)|t(r)))),b("&",7),x("&",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)&t(r)))),b("^",6),x("^",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)^t(r)))),b(">>",10),x(">>",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)>>t(r)))),b("<<",10),x("<<",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)<<t(r)))),b("==",8),x("==",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)==t(r)))),b("!=",8),x("!=",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)!=t(r)))),b(">",9),x(">",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)>t(r)))),b("<",9),x("<",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)<t(r)))),b(">=",9),x(">=",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)>=t(r)))),b("<=",9),x("<=",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)<=t(r)))),k("!",14),x("!",((e,t)=>!t&&(e=C(e),t=>!e(t)))),b("||",3),x("||",((e,t)=>(e=C(e),t=C(t),r=>e(r)||t(r)))),b("&&",4),x("&&",((e,t)=>(e=C(e),t=C(t),r=>e(r)&&t(r)))),b("=",2,!0),x("=",((e,t)=>(t=C(t),$(e,((e,r,a)=>e[r]=t(a)))))),g("/*",20,((e,t)=>(v((e=>42!==e&&47!==n.charCodeAt(s+1))),y(2),e||h(t)||[]))),g("//",20,((e,t)=>(v((e=>e>=32)),e||h(t)||[""]))),b("**",13,!0),x("**",((e,t)=>t&&(e=C(e),t=C(t),r=>e(r)**t(r)))),g("?",2,((e,t,r)=>e&&(t=h(1.5,58))&&["?",e,t,h(1.5)])),x("?",((e,t,r)=>(e=C(e),t=C(t),r=C(r),a=>e(a)?t(a):r(a)))),g("true",20,(e=>e?err():[,!0])),g("false",20,(e=>e?err():[,!1])),N("[]",20),x("[]",((e,t)=>(e=(e=e?","===e[0]?e.slice(1):[e]:[]).map((e=>"..."===e[0]?(e=C(e[1]),t=>e(t)):(e=C(e),t=>[e(t)]))),t=>e.flatMap((e=>e(t)))))),N("{}",20),x("{}",((e,t)=>(e=(e=e?","!==e[0]?[e]:e.slice(1):[]).map((e=>C("string"==typeof e?[":",e,e]:e))),t=>Object.fromEntries(e.flatMap((e=>e(t))))))),b(":",1.5,!0),x(":",((e,t)=>(t=C(t),Array.isArray(e)?(e=C(e),r=>[[e(r),t(r)]]):r=>[[e,t(r)]]))),b("=>",2,!0),x("=>",((e,t)=>(e=(e="()"===e[0]?e[1]:e)?e=","===e[0]?e.slice(1):[e]:[],t=C("{}"===t[0]?t[1]:t),(r=null)=>(r=Object.create(r),(...a)=>(e.map(((e,t)=>r[e]=a[t])),t(r)))))),b(""),g("?.",17,(e=>e&&["?.",e])),x("?.",(e=>(e=C(e),t=>e(t)||(()=>{})))),g("?.",17,((e,t)=>e&&!(t=h(17))?.map&&["?.",e,t])),x("?.",((e,t)=>t&&(e=C(e),r=>e(r)?.[t]))),x("(",((e,t,r,a,l,s)=>"?."===e[0]&&(e[2]||Array.isArray(e[1]))&&(a=t?","===t[0]?(t=t.slice(1).map(C),e=>t.map((t=>t(e)))):(t=C(t),e=>[t(e)]):()=>[],!e[2]&&(e=e[1]),l="["===e[0]?C(e[2]):()=>e[2],r=C(e[1]),e=>r(e)?.[l(e)]?.(...a(e))))),k("...",14),x("...",(e=>(e=C(e),t=>Object.entries(e(t))))),b("in",9),x("in",((e,t)=>t&&(e=E(e),t=E(t),r=>e(r)in t(r)))),b("===",8),b("!==",9),x("===",((e,t)=>(e=E(e),t=E(t),r=>e(r)===t(r)))),x("!==",((e,t)=>(e=E(e),t=E(t),r=>e(r)!==t(r)))),b("??",3),x("??",((e,t)=>t&&(e=E(e),t=E(t),r=>e(r)??t(r)))),b("??=",2,!0),x("??=",((e,t)=>(t=E(t),$(e,((e,r,a)=>e[r]??=t(a)))))),b("||=",2,!0),x("||=",((e,t)=>(t=E(t),$(e,((e,r,a)=>e[r]||=t(a)))))),b("&&=",2,!0),x("&&=",((e,t)=>(t=E(t),$(e,((e,r,a)=>e[r]&&=t(a)))))),g("undefined",20,(e=>e?d():[,void 0])),g("NaN",20,(e=>e?d():[,NaN])),g("null",20,(e=>e?d():[,null]));var B=Symbol.dispose||=Symbol("dispose"),{signal:D,effect:M,batch:K,computed:P,untracked:J}=l,R={},U=new WeakMap;function Z(e,t){if(!e.children)return;if(U.has(e)){const[r,a]=U.get(e);for(let e in t)r[e]=t[e];J((()=>{for(let e of a)e()}))}const r=t||{},a=[],l=(e,t=e.parentNode)=>{if(e.attributes)for(let l=0;l<e.attributes.length;){let s=e.attributes[l];if(":"===s.name[0]){e.removeAttribute(s.name);let l=s.name.slice(1).split(":");for(let t of l){let l=(R[t]||R.default)(e,s.value,r,t);l&&(l[B]=M(l),a.push(l))}if(U.has(e))return;if(e.parentNode!==t)return!1}else l++}for(let t,r=0;t=e.children[r];r++)!1===l(t,e)&&r--};return l(e),U.has(e)||(U.set(e,[r,a]),e.classList?.add("∴"),e[B]=()=>{for(;a.length;)a.pop()[B]();e.classList.remove("∴"),U.delete(e);let t=e.getElementsByClassName("∴");for(;t.length;)t[0][B]?.()}),r}var z={},H=(e,t,r)=>{if(r=z[e=e.trim()])return r;try{r=E(w(e))}catch(r){throw Object.assign(r,{message:`∴ ${r.message}\n\n${t}${e?`="${e}"\n\n`:""}`,expr:e})}return z[e]=r},X=a,_=(e,t)=>e?.replace?e.replace(/\$<([^>]+)>/g,((e,r)=>t[r]?.valueOf?.()??"")):e;Z.use=e=>{e.signal&&(D=e.signal,M=e.effect,P=e.computed,K=e.batch||(e=>e()),J=e.untracked||K),e.swap&&(X=e.swap)};var q=Symbol(":each"),F={};R.each=(e,t,r,a)=>{let[l,s]=t.split(/\s+in\s+/),[n,o="_$"]=l.split(/\s*,\s*/);const i=e[q]=document.createTextNode("");e.replaceWith(i);const c=H(s,a),u=new WeakMap;e.removeAttribute(":key");let p=[];return()=>{let t=c(r)?.valueOf(),a=[];"number"==typeof t&&(t=Array.from({length:t},((e,t)=>t)));const l=new WeakSet;for(let s in t){let i=t[s],c=Object.create(r,{[o]:{value:s}});c[n]=i;let p,f=i.key??i.id??i;null==f?p=e.cloneNode(!0):(Object(f)!==f&&(f=F[f]||=Object(f)),l.has(f)?(console.warn("Duplicate key",f),p=e.cloneNode(!0)):(l.add(f),p=u.get(f)||u.set(f,e.cloneNode(!0)).get(f))),p.content&&(p=p.content.cloneNode(!0)),Z(p,c),11===p.nodeType?a.push(...p.childNodes):a.push(p)}X(i.parentNode,p,p=a,i)}};var G=Symbol("if");R.if=(e,t,r,a)=>{let l,s,n,o=e.parentNode,i=e.nextElementSibling,c=document.createTextNode(""),u=H(t,a),p=[];return e.after(c),e.content?(l=p,e.remove(),s=[...e.content.childNodes]):s=l=[e],i?.hasAttribute(":else")?(i.removeAttribute(":else"),i.hasAttribute(":if")?n=p:(i.remove(),n=i.content?[...i.content.childNodes]:[i])):n=p,()=>{const t=u(r)?.valueOf()?s:e[G]?p:n;if(i&&(i[G]=t===s),l!=t){l[0]?.[q]&&(l=[l[0][q]]),X(o,l,l=t,c);for(let e of l)Z(e,r)}}},R.ref=(e,t,r)=>{let a;return()=>{a&&delete r[a],r[a=_(t,r)]=e}},R.scope=(e,t,r,a)=>{let l=H(t,a);return()=>{Z(e,{...r,...l(r)?.valueOf?.()||{}})}},R.html=(e,t,r,a)=>{let l=H(t,a)(r);if(!l)return;let s=(l.content||l).cloneNode(!0);e.replaceChildren(s),Z(e,r)},R.text=(e,t,r)=>{let a=H(t,"text");return e.content&&e.replaceWith(e=document.createTextNode("")),()=>{let t=a(r)?.valueOf();e.textContent=null==t?"":t}},R.class=(e,t,r)=>{let a=H(t,"class"),l=new Set;return()=>{let t=a(r),s=new Set;t&&("string"==typeof t?_(t?.valueOf?.(),r).split(" ").map((e=>s.add(e))):Array.isArray(t)?t.map((e=>(e=_(e?.valueOf?.(),r))&&s.add(e))):Object.entries(t).map((([e,t])=>t?.valueOf?.()&&s.add(e))));for(let t of l)s.has(t)?s.delete(t):e.classList.remove(t);for(let t of l=s)e.classList.add(t)}},R.style=(e,t,r)=>{let a=H(t,"style"),l=e.getAttribute("style")||"";return l.endsWith(";")||(l+="; "),()=>{let t=a(r)?.valueOf();if("string"==typeof t)e.setAttribute("style",l+_(t,r));else{e.setAttribute("style",l);for(let a in t)e.style.setProperty(a,_(t[a],r))}}},R.default=(e,t,r,a)=>{let l=a.startsWith("on")&&a.slice(2),s=H(t,a);if(l){let t;return()=>(t?.(),t=I(e,l,s(r)))}return()=>{let t=s(r)?.valueOf();if(a)Y(e,a,_(t,r));else for(let a in t)Y(e,re(a),_(t[a],r))}};var I=(e,t,r=(()=>{}))=>{const a={evt:"",target:e,test:()=>!0};a.evt=t.replace(/\.(\w+)?-?([-\w]+)?/g,((e,t,r="")=>(a.test=Q[t]?.(a,...r.split("-"))||a.test,"")));const{evt:l,target:s,test:n,defer:o,stop:i,prevent:c,...u}=a;o&&(r=o(r));const p=e=>n(e)&&(i&&e.stopPropagation(),c&&e.preventDefault(),r.call(s,e));return s.addEventListener(l,p,u),()=>s.removeEventListener(l,p,u)},Q={prevent(e){e.prevent=!0},stop(e){e.stop=!0},once(e){e.once=!0},passive(e){e.passive=!0},capture(e){e.capture=!0},window(e){e.target=window},document(e){e.target=document},throttle(e,t){e.defer=e=>ee(e,t?Number(t)||0:108)},debounce(e,t){e.defer=e=>te(e,t?Number(t)||0:108)},outside:e=>t=>{let r=e.target;return!(r.contains(t.target)||!1===t.target.isConnected||r.offsetWidth<1&&r.offsetHeight<1)},self:e=>t=>t.target===e.target,ctrl:(e,...t)=>e=>V.ctrl(e)&&t.every((t=>V[t]?V[t](e):e.key===t)),shift:(e,...t)=>e=>V.shift(e)&&t.every((t=>V[t]?V[t](e):e.key===t)),alt:(e,...t)=>e=>V.alt(e)&&t.every((t=>V[t]?V[t](e):e.key===t)),meta:(e,...t)=>e=>V.meta(e)&&t.every((t=>V[t]?V[t](e):e.key===t)),arrow:()=>V.arrow,enter:()=>V.enter,escape:()=>V.escape,tab:()=>V.tab,space:()=>V.space,backspace:()=>V.backspace,delete:()=>V.delete,digit:()=>V.digit,letter:()=>V.letter,character:()=>V.character},V={ctrl:e=>e.ctrlKey||"Control"===e.key||"Ctrl"===e.key,shift:e=>e.shiftKey||"Shift"===e.key,alt:e=>e.altKey||"Alt"===e.key,meta:e=>e.metaKey||"Meta"===e.key||"Command"===e.key,arrow:e=>e.key.startsWith("Arrow"),enter:e=>"Enter"===e.key,escape:e=>e.key.startsWith("Esc"),tab:e=>"Tab"===e.key,space:e=>" "===e.key||"Space"===e.key||" "===e.key,backspace:e=>"Backspace"===e.key,delete:e=>"Delete"===e.key,digit:e=>/^\d$/.test(e.key),letter:e=>/^[a-zA-Z]$/.test(e.key),character:e=>/^\S$/.test(e.key)},Y=(e,t,r)=>{null==r||!1===r?e.removeAttribute(t):e.setAttribute(t,!0===r?"":"number"==typeof r||"string"==typeof r?r:"")},ee=(e,t)=>{let r,a,l=s=>{r=!0,setTimeout((()=>{if(r=!1,a)return a=!1,l(s),e(s)}),t)};return t=>r?a=!0:(l(t),e(t))},te=(e,t)=>{let r;return a=>{clearTimeout(r),r=setTimeout((()=>{r=null,e(a)}),t)}},re=e=>e.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(e=>"-"+e.toLowerCase()));R.value=(e,t,r)=>{let a,l,s=H(t,"value"),n="text"===e.type||""===e.type?t=>e.setAttribute("value",e.value=null==t?"":t):"TEXTAREA"===e.tagName||"text"===e.type||""===e.type?t=>(a=e.selectionStart,l=e.selectionEnd,e.setAttribute("value",e.value=null==t?"":t),a&&e.setSelectionRange(a,l)):"checkbox"===e.type?t=>(e.checked=t,Y(e,"checked",t)):"select-one"===e.type?t=>{for(let t in e.options)t.removeAttribute("selected");e.value=t,e.selectedOptions[0]?.setAttribute("selected","")}:t=>e.value=t;return()=>n(s(r)?.valueOf?.())},R.fx=(e,t,r,a)=>{let l=H(t,a);return()=>l(r)};export{K as batch,H as compile,P as computed,Z as default,R as directive,M as effect,_ as ipol,D as signal,X as swap,J as untracked};
|
|
1
|
+
var e,t,r,l,n,s=Object.defineProperty,a=Symbol("signals"),o=Symbol("length");function i(t,r){if(!t)return t;if(t[a]&&!r)return t;if(Array.isArray(t))return function(t){let r;if(t[a])return t;let l=e(t.length),n=Array(t.length).fill(null);const s=new Proxy(n,{get:(s,c)=>c===o?l:c===a?n:"length"===c?Array.prototype[r]?l.peek():l.value:(r=c,n[c]?n[c].valueOf():c<n.length?(n[c]=e(i(t[c]))).value:void 0),set(e,t,r){if("length"===t){for(let e=r,t=n.length;e<t;e++)delete s[e];return l.value=n.length=r,!0}return c(n,t,r),t>=l.peek()&&(l.value=n.length=Number(t)+1),!0},deleteProperty:(e,t)=>(u(n,t),!0)});return s}(t);if(t.constructor!==Object)return t;let l=e(Object.values(t).length);r||={};const s=new Proxy(r,{get:(e,t)=>t===o?l:t===a?r:r[t]?.valueOf(),set:(e,t,n,s)=>(s=r[t],c(r,t,n),s||++l.value),deleteProperty:(e,t)=>u(r,t)&&l.value--,ownKeys:()=>(l.value,Reflect.ownKeys(r))});if(t[a])for(let e in t)r[e]=t[a][e];else for(let e in t){const l=Object.getOwnPropertyDescriptor(t,e);l?.get?(r[e]=n(l.get.bind(s)))._set=l.set?.bind(s):(r[e]=null,c(r,e,t[e]))}return s}function c(t,n,s){let a=t[n];if(a)if(s===a.peek());else if(a._set)a._set(s);else if(Array.isArray(s)&&Array.isArray(a.peek())){const e=a.peek();e[o]?r((()=>{l((()=>{let t=0,r=s.length;for(;t<r;t++)e[t]=s[t];e.length=r}))})):a.value=s}else a.value=i(s);else t[n]=a=s?.peek?s:e(i(s))}function u(e,t){const r=e[t];if(r){const l=r[Symbol.dispose];return l&&delete r[Symbol.dispose],delete e[t],l?.(),!0}}var f=Symbol.dispose||=Symbol("dispose"),p={},d=new WeakMap;function y(e,t){let r;e?.[Symbol.iterator]||(e=[e]);for(let l of e)l?.children&&(d.has(l)?Object.assign(d.get(l),t):(r||=i(t||{}),v(l,r),d.has(l)||d.set(l,r)));return r}function v(e,t,r=e.parentNode,l=[]){if(e.attributes)for(let n=0;n<e.attributes.length;){let s=e.attributes[n];if(":"===s.name[0]){e.removeAttribute(s.name);let n=s.name.slice(1).split(":");for(let r of n){let n=p[r]||p.default,a=(n.parse||g)(s.value,g),o=n(e,a,t,r);o&&l.push(o)}if(d.has(e))return;if(e.parentNode!==r)return}else n++}for(let r of[...e.children])v(r,t,e);e.classList?.add("∴"),e[f]=()=>{for(;l.length;)l.pop()();e.classList.remove("∴"),d.delete(e);let t=e.getElementsByClassName("∴");for(;t.length;)t[0][f]?.()}}var h,m={},g=(e,t,r)=>{if(r=m[e=e.trim()])return r;try{r=h(e)}catch(r){throw Object.assign(r,{message:`∴ ${r.message}\n\n${t}${e?`="${e}"\n\n`:""}`,expr:e})}return m[e]=r};y.use=s=>{s.signal&&function(s){e=s.signal,t=s.effect,n=s.computed,l=s.batch||(e=>e()),r=s.untracked||l}(s),s.compile&&(h=s.compile)};var b,k,A={};((e,t)=>{for(var r in t)s(e,r,{get:t[r],enumerable:!0})})(A,{batch:()=>N,computed:()=>O,effect:()=>w,signal:()=>S,untracked:()=>x});var S=(e,t,r=new Set)=>((t={get value(){return b?.deps.push(r.add(b)),e},set value(t){if(t!==e){e=t;for(let e of r)k?k.add(e):e()}},peek:()=>e}).toJSON=t.then=t.toString=t.valueOf=()=>t.value,t),w=(e,t,r,l)=>(l=(r=l=>{t?.call?.(),l=b,b=r;try{t=e()}finally{b=l}}).deps=[],r(),e=>{for(t?.call?.();e=l.pop();)e.delete(r)}),O=(e,t=S(),r,l)=>((r={get value(){return l||=w((()=>t.value=e())),t.value},peek:t.peek}).toJSON=r.then=r.toString=r.valueOf=()=>r.value,r),N=e=>{let t=k;t||(k=new Set);try{e()}finally{if(!t){t=k,k=null;for(const e of t)e()}}},x=(e,t,r)=>(t=b,b=null,r=e(),b=t,r),j=(e,t,r,l=null,{remove:n,insert:s}=j)=>{let a,o,i,c=0,u=new Set(r);for(;i=t[c++];)u.has(i)?a=a||i:n(i,e);for(a=a||l,c=0;i=r[c++];)o=a?a.nextSibling:l,a===i?a=o:(r[c]===o&&(a=o),s(i,a,e));return r};j.insert=(e,t,r)=>r.insertBefore(e,t),j.remove=(e,t)=>t.removeChild(e);var C=j,E=Symbol(":each");p.each=(e,[l,s,i],c)=>{const u=e[E]=document.createTextNode("");e.replaceWith(u);let f,p,d=0;const v=n((()=>{p=null;let e=i(c);return"number"==typeof e&&(e=Array.from({length:e},((e,t)=>t+1))),e?.constructor===Object&&(p=Object.keys(e),e=Object.values(e)),e||[]})),h=()=>{r((()=>{let t=0,r=v.value,n=r.length;if(f&&!f[o]){for(let e of f[a]||[])e[Symbol.dispose]();f=null,d=0}if(n<d)f.length=n;else{if(f)for(;t<d;t++)f[t]=r[t];else f=r;for(;t<n;t++){f[t]=r[t];let n=t,o=Object.create(c,{[l]:{get:()=>f[n]},[s]:{value:p?p[n]:n}}),i=(e.content||e).cloneNode(!0),d=e.content?[...i.childNodes]:[i];u.before(i),y(d,o),((f[a]||=[])[t]||={})[Symbol.dispose]=()=>{for(let e of d)e[Symbol.dispose](),e.remove()}}}d=n}))};let m=0;return t((()=>{f||v.value[o]?.value,m?m++:(h(),queueMicrotask((()=>(m&&h(),m=0))))}))},p.each.parse=(e,t)=>{let[r,l]=e.split(/\s+in\s+/),[n,s="$"]=r.split(/\s*,\s*/);return[n,s,t(l)]};var $=Symbol("if");p.if=(e,r,l)=>{let n,s,a,o=e.parentNode,i=e.nextElementSibling,c=document.createTextNode(""),u=[];return e.after(c),e.content?(n=u,e.remove(),s=[...e.content.childNodes]):s=n=[e],i?.hasAttribute(":else")?(i.removeAttribute(":else"),i.hasAttribute(":if")?a=u:(i.remove(),a=i.content?[...i.content.childNodes]:[i])):a=u,t((()=>{const t=r(l)?s:e[$]?u:a;if(i&&(i[$]=t===s),n!=t){n[0]?.[E]&&(n=[n[0][E]]);for(let e of n)e.remove();n=t;for(let e of n)o.insertBefore(e,c),y(e,l)}}))},p.default=(e,r,l,n)=>{let s,a=n.startsWith("on")&&n.slice(2);return t(a?()=>(s?.(),s=P(e,a,r(l))):()=>{let t=r(l);if(n)L(e,n,B(t,l));else for(let r in t)L(e,K(r),B(t[r],l))})};var P=(e,t,r=(()=>{}))=>{const l={evt:"",target:e,test:()=>!0};l.evt=t.replace(/\.(\w+)?-?([-\w]+)?/g,((e,t,r="")=>(l.test=T[t]?.(l,...r.split("-"))||l.test,"")));const{evt:n,target:s,test:a,defer:o,stop:i,prevent:c,...u}=l;o&&(r=o(r));const f=e=>a(e)&&(i&&e.stopPropagation(),c&&e.preventDefault(),r.call(s,e));return s.addEventListener(n,f,u),()=>s.removeEventListener(n,f,u)},T={prevent(e){e.prevent=!0},stop(e){e.stop=!0},once(e){e.once=!0},passive(e){e.passive=!0},capture(e){e.capture=!0},window(e){e.target=window},document(e){e.target=document},throttle(e,t){e.defer=e=>_(e,t?Number(t)||0:108)},debounce(e,t){e.defer=e=>D(e,t?Number(t)||0:108)},outside:e=>t=>{let r=e.target;return!(r.contains(t.target)||!1===t.target.isConnected||r.offsetWidth<1&&r.offsetHeight<1)},self:e=>t=>t.target===e.target,ctrl:(e,...t)=>e=>W.ctrl(e)&&t.every((t=>W[t]?W[t](e):e.key===t)),shift:(e,...t)=>e=>W.shift(e)&&t.every((t=>W[t]?W[t](e):e.key===t)),alt:(e,...t)=>e=>W.alt(e)&&t.every((t=>W[t]?W[t](e):e.key===t)),meta:(e,...t)=>e=>W.meta(e)&&t.every((t=>W[t]?W[t](e):e.key===t)),arrow:()=>W.arrow,enter:()=>W.enter,escape:()=>W.escape,tab:()=>W.tab,space:()=>W.space,backspace:()=>W.backspace,delete:()=>W.delete,digit:()=>W.digit,letter:()=>W.letter,character:()=>W.character},W={ctrl:e=>e.ctrlKey||"Control"===e.key||"Ctrl"===e.key,shift:e=>e.shiftKey||"Shift"===e.key,alt:e=>e.altKey||"Alt"===e.key,meta:e=>e.metaKey||"Meta"===e.key||"Command"===e.key,arrow:e=>e.key.startsWith("Arrow"),enter:e=>"Enter"===e.key,escape:e=>e.key.startsWith("Esc"),tab:e=>"Tab"===e.key,space:e=>" "===e.key||"Space"===e.key||" "===e.key,backspace:e=>"Backspace"===e.key,delete:e=>"Delete"===e.key,digit:e=>/^\d$/.test(e.key),letter:e=>/^[a-zA-Z]$/.test(e.key),character:e=>/^\S$/.test(e.key)},L=(e,t,r)=>{null==r||!1===r?e.removeAttribute(t):e.setAttribute(t,!0===r?"":"number"==typeof r||"string"==typeof r?r:"")},_=(e,t)=>{let r,l,n=s=>{r=!0,setTimeout((()=>{if(r=!1,l)return l=!1,n(s),e(s)}),t)};return t=>r?l=!0:(n(t),e(t))},D=(e,t)=>{let r;return l=>{clearTimeout(r),r=setTimeout((()=>{r=null,e(l)}),t)}},K=e=>e.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(e=>"-"+e.toLowerCase())),B=(e,t)=>e?.replace?e.replace(/\$<([^>]+)>/g,((e,r)=>t[r]??"")):e;p.ref=(e,t,r)=>{Object.defineProperty(r,B(t,r),{value:e})},p.ref.parse=e=>e,p.with=(e,r,l)=>{let n,s;return t((()=>{s=r(l),Object.assign(n||=y(e,i(s,Object.create(l[a]))),s)}))},p.html=(e,t,r)=>{let l=t(r);if(!l)return;let n=(l.content||l).cloneNode(!0);e.replaceChildren(n),y(e,r)},p.text=(e,r,l)=>(e.content&&e.replaceWith(e=document.createTextNode("")),t((()=>{let t=r(l);e.textContent=null==t?"":t}))),p.class=(e,r,l)=>{let n=new Set;return t((()=>{let t=r(l),s=new Set;t&&("string"==typeof t?B(t,l).split(" ").map((e=>s.add(e))):Array.isArray(t)?t.map((e=>(e=B(e,l))&&s.add(e))):Object.entries(t).map((([e,t])=>t&&s.add(e))));for(let t of n)s.has(t)?s.delete(t):e.classList.remove(t);for(let t of n=s)e.classList.add(t)}))},p.style=(e,r,l)=>{let n=e.getAttribute("style")||"";return n.endsWith(";")||(n+="; "),t((()=>{let t=r(l);if("string"==typeof t)e.setAttribute("style",n+B(t,l));else{e.setAttribute("style",n);for(let r in t)e.style.setProperty(r,B(t[r],l))}}))},p.value=(e,r,l)=>{let n,s,a="text"===e.type||""===e.type?t=>e.setAttribute("value",e.value=null==t?"":t):"TEXTAREA"===e.tagName||"text"===e.type||""===e.type?t=>(n=e.selectionStart,s=e.selectionEnd,e.setAttribute("value",e.value=null==t?"":t),n&&e.setSelectionRange(n,s)):"checkbox"===e.type?t=>(e.checked=t,L(e,"checked",t)):"select-one"===e.type?t=>{for(let t in e.options)t.removeAttribute("selected");e.value=t,e.selectedOptions[0]?.setAttribute("selected","")}:t=>e.value=t;return t((()=>a(r(l))))},p.fx=(e,r,l)=>t((()=>r(l))),y.use(A),y.use({compile:e=>y.constructor("__scope",`with (__scope) { return ${e} };`)}),y.use({swap:C});var M=y;export{M as default};
|
package/package.json
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sprae",
|
|
3
3
|
"description": "DOM microhydration.",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "10.0.0",
|
|
5
5
|
"main": "./sprae.js",
|
|
6
6
|
"module": "./sprae.js",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"files": [
|
|
9
9
|
"core.js",
|
|
10
10
|
"sprae.js",
|
|
11
|
+
"store.js",
|
|
12
|
+
"signal.js",
|
|
11
13
|
"directive",
|
|
12
14
|
"dist"
|
|
13
15
|
],
|
|
14
16
|
"dependencies": {
|
|
15
|
-
"
|
|
16
|
-
"swapdom": "^1.2.1"
|
|
17
|
+
"ulive": "^1.0.2"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
19
20
|
"@preact/signals": "^1.1.3",
|
|
@@ -22,10 +23,10 @@
|
|
|
22
23
|
"esbuild": "^0.15.14",
|
|
23
24
|
"hyperf": "^1.6.2",
|
|
24
25
|
"jsdom": "^21.1.0",
|
|
25
|
-
"
|
|
26
|
+
"signal-polyfill": "^0.1.1",
|
|
27
|
+
"subscript": "^8.3.5",
|
|
26
28
|
"terser": "^5.15.1",
|
|
27
29
|
"tst": "^7.1.1",
|
|
28
|
-
"ulive": "^1.0.1",
|
|
29
30
|
"usignal": "^0.9.0",
|
|
30
31
|
"wait-please": "^3.1.0"
|
|
31
32
|
},
|
package/readme.md
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> DOM tree microhydration
|
|
4
4
|
|
|
5
|
-
_Sprae_ is
|
|
6
|
-
|
|
7
|
-
Perfect for small-scale websites, prototypes, or lightweight UI.<br/>
|
|
5
|
+
_Sprae_ is open & minimalistic progressive enhancement framework.<br/>
|
|
6
|
+
Perfect for small-scale websites, static pages, landings, prototypes, or lightweight UI.<br/>
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
## Usage
|
|
@@ -15,17 +14,17 @@ Perfect for small-scale websites, prototypes, or lightweight UI.<br/>
|
|
|
15
14
|
</div>
|
|
16
15
|
|
|
17
16
|
<script type="module">
|
|
18
|
-
import sprae
|
|
17
|
+
import sprae from 'sprae'
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
sprae(container, { user: { name } })
|
|
19
|
+
// init
|
|
20
|
+
const state = sprae(container, { user: { name: 'Kitty' } })
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
// update
|
|
23
|
+
state.user.name = 'Dolly'
|
|
24
24
|
</script>
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Sprae evaluates `:`-directives and evaporates them,
|
|
28
|
-
|
|
27
|
+
Sprae evaluates `:`-directives and evaporates them, returning reactive state.
|
|
29
28
|
|
|
30
29
|
## Directives
|
|
31
30
|
|
|
@@ -43,21 +42,17 @@ Control flow of elements.
|
|
|
43
42
|
```
|
|
44
43
|
|
|
45
44
|
|
|
46
|
-
#### `:each="item, index in items"`
|
|
45
|
+
#### `:each="item, index? in items"`
|
|
47
46
|
|
|
48
|
-
Multiply element.
|
|
47
|
+
Multiply element.
|
|
49
48
|
|
|
50
49
|
```html
|
|
51
50
|
<ul><li :each="item in items" :text="item"/></ul>
|
|
52
51
|
|
|
53
52
|
<!-- cases -->
|
|
54
|
-
<li :each="item, idx in
|
|
55
|
-
<li :each="
|
|
56
|
-
<li :each="idx in number" />
|
|
57
|
-
|
|
58
|
-
<!-- by condition -->
|
|
59
|
-
<li :if="items" :each="item in items" :text="item" />
|
|
60
|
-
<li :else>Empty list</li>
|
|
53
|
+
<li :each="item, idx in array" />
|
|
54
|
+
<li :each="value, key in object" />
|
|
55
|
+
<li :each="count, idx in number" />
|
|
61
56
|
|
|
62
57
|
<!-- fragment -->
|
|
63
58
|
<template :each="item in items">
|
|
@@ -82,22 +77,28 @@ Welcome, <template :text="user.name" />.
|
|
|
82
77
|
|
|
83
78
|
#### `:class="value"`
|
|
84
79
|
|
|
85
|
-
Set class value
|
|
80
|
+
Set class value.
|
|
86
81
|
|
|
87
82
|
```html
|
|
88
|
-
<!--
|
|
83
|
+
<!-- appends class -->
|
|
84
|
+
<div class="foo" :class="bar"></div>
|
|
85
|
+
|
|
86
|
+
<!-- interpolation -->
|
|
89
87
|
<div :class="'foo $<bar>'"></div>
|
|
90
88
|
|
|
91
|
-
<!-- array/object a-la clsx -->
|
|
89
|
+
<!-- array/object, a-la clsx -->
|
|
92
90
|
<div :class="[foo && 'foo', {bar: bar}]"></div>
|
|
93
91
|
```
|
|
94
92
|
|
|
95
93
|
#### `:style="value"`
|
|
96
94
|
|
|
97
|
-
Set style value
|
|
95
|
+
Set style value.
|
|
98
96
|
|
|
99
97
|
```html
|
|
100
|
-
<!--
|
|
98
|
+
<!-- extends style -->
|
|
99
|
+
<div style="foo: bar" :style="'baz: qux'">
|
|
100
|
+
|
|
101
|
+
<!-- interpolation -->
|
|
101
102
|
<div :style="'foo: $<bar>'"></div>
|
|
102
103
|
|
|
103
104
|
<!-- object -->
|
|
@@ -109,16 +110,19 @@ Set style value, extends existing `style`.
|
|
|
109
110
|
|
|
110
111
|
#### `:value="value"`
|
|
111
112
|
|
|
112
|
-
Set value of an input, textarea or select.
|
|
113
|
+
Set value of an input, textarea or select.
|
|
113
114
|
|
|
114
115
|
```html
|
|
115
116
|
<input :value="value" />
|
|
116
117
|
<textarea :value="value" />
|
|
117
118
|
|
|
118
|
-
<!-- selects right option -->
|
|
119
|
+
<!-- selects right option & handles selected attr -->
|
|
119
120
|
<select :value="selected">
|
|
120
121
|
<option :each="i in 5" :value="i" :text="i"></option>
|
|
121
122
|
</select>
|
|
123
|
+
|
|
124
|
+
<!-- handles checked attr -->
|
|
125
|
+
<input type="checkbox" :value="checked" />
|
|
122
126
|
```
|
|
123
127
|
|
|
124
128
|
#### `:[prop]="value"`, `:="values"`
|
|
@@ -135,33 +139,32 @@ Set any attribute(s).
|
|
|
135
139
|
<input :="{ id: name, name, type: 'text', value }" />
|
|
136
140
|
```
|
|
137
141
|
|
|
138
|
-
#### `:
|
|
142
|
+
#### `:with="values"`
|
|
139
143
|
|
|
140
|
-
Define
|
|
144
|
+
Define values for a subtree.
|
|
141
145
|
|
|
142
146
|
```html
|
|
143
|
-
<x :
|
|
144
|
-
|
|
145
|
-
<y :scope="{ baz: 'qux' }" :text="foo + baz"></y>
|
|
147
|
+
<x :with="{ foo: signal('bar') }">
|
|
148
|
+
<y :with="{ baz: 'qux' }" :text="foo + baz"></y>
|
|
146
149
|
</x>
|
|
147
150
|
```
|
|
148
151
|
|
|
149
152
|
#### `:ref="name"`
|
|
150
153
|
|
|
151
|
-
Expose element
|
|
154
|
+
Expose element with `name`.
|
|
152
155
|
|
|
153
156
|
```html
|
|
154
157
|
<textarea :ref="text" placeholder="Enter text..."></textarea>
|
|
155
158
|
|
|
156
159
|
<!-- iterable items -->
|
|
157
160
|
<li :each="item in items" :ref="item">
|
|
158
|
-
<input :onfocus..onblur
|
|
161
|
+
<input :onfocus..onblur="e => (item.classList.add('editing'), e => item.classList.remove('editing'))"/>
|
|
159
162
|
</li>
|
|
160
163
|
```
|
|
161
164
|
|
|
162
165
|
#### `:fx="code"`
|
|
163
166
|
|
|
164
|
-
Run effect, not changing any attribute
|
|
167
|
+
Run effect, not changing any attribute.
|
|
165
168
|
|
|
166
169
|
```html
|
|
167
170
|
<div :fx="a.value ? foo() : bar()" />
|
|
@@ -172,7 +175,7 @@ Run effect, not changing any attribute.<br/>Optional cleanup is called in-betwee
|
|
|
172
175
|
|
|
173
176
|
#### `:on[event]="handler"`
|
|
174
177
|
|
|
175
|
-
Attach event(s) listener with
|
|
178
|
+
Attach event(s) listener with optional modifiers.
|
|
176
179
|
|
|
177
180
|
```html
|
|
178
181
|
<input type="checkbox" :onchange="e => isChecked = e.target.value">
|
|
@@ -183,7 +186,7 @@ Attach event(s) listener with possible modifiers.
|
|
|
183
186
|
<!-- events sequence -->
|
|
184
187
|
<button :onfocus..onblur="e => ( handleFocus(), e => handleBlur())">
|
|
185
188
|
|
|
186
|
-
<!--
|
|
189
|
+
<!-- modifiers -->
|
|
187
190
|
<button :onclick.throttle-500="handler">Not too often</button>
|
|
188
191
|
```
|
|
189
192
|
|
|
@@ -211,7 +214,7 @@ Hello, <template :html="user.name">Guest</template>.
|
|
|
211
214
|
|
|
212
215
|
<!-- instantiate template -->
|
|
213
216
|
<template :ref="tpl"><span :text="foo"></span></template>
|
|
214
|
-
<div :html="tpl" :
|
|
217
|
+
<div :html="tpl" :with="{foo:'bar'}">...inserted here...</div>
|
|
215
218
|
```
|
|
216
219
|
|
|
217
220
|
#### `:data="values"` 🔌
|
|
@@ -265,34 +268,39 @@ Trigger when element is connected / disconnected from DOM.
|
|
|
265
268
|
```
|
|
266
269
|
-->
|
|
267
270
|
|
|
268
|
-
##
|
|
269
|
-
|
|
270
|
-
_Sprae_ can be reconfigured to use alternative signals provider, expressions evaluator or directives.
|
|
271
|
+
## Signals
|
|
271
272
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
Sprae uses [standard signals](https://github.com/proposal-signals/proposal-signals) for reactivity, but can be switched to any preact-flavored signals library:
|
|
273
|
+
Sprae can take signal values. Signals provider can be switched to any preact-flavored implementation:
|
|
275
274
|
|
|
276
275
|
```js
|
|
277
|
-
import sprae
|
|
276
|
+
import sprae from 'sprae';
|
|
277
|
+
import { signal, computed, effect, batch, untracked } from 'sprae/signal';
|
|
278
278
|
import * as signals from '@preact/signals-core';
|
|
279
279
|
|
|
280
|
+
// switch provider to @preact/signals-core
|
|
280
281
|
sprae.use(signals);
|
|
281
282
|
|
|
282
|
-
|
|
283
|
+
// use signal as state value
|
|
284
|
+
const name = signal('Kitty')
|
|
285
|
+
sprae(el, { name });
|
|
286
|
+
|
|
287
|
+
// update state
|
|
288
|
+
name.value = 'Dolly';
|
|
283
289
|
```
|
|
284
290
|
|
|
285
291
|
Provider | Size | Feature
|
|
286
292
|
:---|:---|:---
|
|
287
|
-
[`ulive`](https://ghub.io/ulive) | 350b | Minimal implementation, basic performance, good for small states
|
|
288
|
-
[`@webreflection/signal`](https://ghib.io/@webreflection/signal) | 531b | Class-based, better performance, good for small-medium states
|
|
289
|
-
[`usignal`](https://ghib.io/usignal) | 850b | Class-based with optimizations, good for medium states
|
|
290
|
-
[`@preact/signals-core`](https://ghub.io/@preact/signals-core) | 1.47kb | Best performance, good for any states
|
|
293
|
+
[`ulive`](https://ghub.io/ulive) (default) | 350b | Minimal implementation, basic performance, good for small states.
|
|
294
|
+
[`@webreflection/signal`](https://ghib.io/@webreflection/signal) | 531b | Class-based, better performance, good for small-medium states.
|
|
295
|
+
[`usignal`](https://ghib.io/usignal) | 850b | Class-based with optimizations, good for medium states.
|
|
296
|
+
[`@preact/signals-core`](https://ghub.io/@preact/signals-core) | 1.47kb | Best performance, good for any states, industry standard.
|
|
297
|
+
[`signal-polyfill`](https://github.com/tc39/proposal-signals) | 2.5kb | Proposal signals. Use via [adapter](https://gist.github.com/dy/bbac687464ccf5322ab0e2fd0680dc4d).
|
|
291
298
|
|
|
292
299
|
|
|
293
|
-
|
|
300
|
+
## Evaluator
|
|
294
301
|
|
|
295
|
-
Expressions use _new Function_ as default evaluator, which is fast & compact way, but violates "unsafe-eval" CSP.
|
|
302
|
+
Expressions use _new Function_ as default evaluator, which is fast & compact way, but violates "unsafe-eval" CSP.
|
|
303
|
+
To make eval stricter & safer, as well as sandbox expressions, an alternative evaluator can be used, eg. _justin_:
|
|
296
304
|
|
|
297
305
|
```js
|
|
298
306
|
import sprae from 'sprae'
|
|
@@ -301,7 +309,7 @@ import justin from 'subscript/justin'
|
|
|
301
309
|
sprae.use({compile: justin}) // set up justin as default compiler
|
|
302
310
|
```
|
|
303
311
|
|
|
304
|
-
[_Justin_](https://github.com/dy/subscript?tab=readme-ov-file#justin) is minimal JS subset
|
|
312
|
+
[_Justin_](https://github.com/dy/subscript?tab=readme-ov-file#justin) is minimal JS subset that avoids "unsafe-eval" CSP and provides sandboxing.
|
|
305
313
|
|
|
306
314
|
###### Operators:
|
|
307
315
|
|
|
@@ -316,65 +324,43 @@ sprae.use({compile: justin}) // set up justin as default compiler
|
|
|
316
324
|
`true false null undefined NaN`
|
|
317
325
|
|
|
318
326
|
|
|
319
|
-
##
|
|
327
|
+
## Custom Build
|
|
320
328
|
|
|
321
|
-
|
|
329
|
+
_Sprae_ can be tailored to project needs via `sprae/core`:
|
|
322
330
|
|
|
323
331
|
```js
|
|
324
|
-
|
|
332
|
+
// sprae.custom.js
|
|
333
|
+
import sprae, { directive } from 'sprae/core'
|
|
334
|
+
import { effect } from 'sprae/signal'
|
|
335
|
+
import * as signals from '@preact/signals'
|
|
336
|
+
import compile from 'subscript'
|
|
325
337
|
|
|
326
338
|
// include directives
|
|
327
339
|
import 'sprae/directive/if.js'
|
|
328
340
|
import 'sprae/directive/text.js'
|
|
329
341
|
|
|
330
|
-
//
|
|
342
|
+
// custom directive :id="expression"
|
|
331
343
|
directive.id = (el, evaluate, state) => {
|
|
332
|
-
|
|
344
|
+
effect(() => el.id = evaluate(state))
|
|
333
345
|
}
|
|
334
|
-
```
|
|
335
346
|
|
|
336
|
-
|
|
347
|
+
// configure signals
|
|
348
|
+
sprae.use(signals)
|
|
337
349
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
DOM diffing uses [swapdom](https://github.com/dy/swapdom), but can be reconfigured to [list-difference](https://github.com/paldepind/list-difference/), [udomdiff](https://github.com/WebReflection/udomdiff), [domdiff](https://github.com/WebReflection/domdiff), or any other ([benchmark](https://github.com/luwes/js-diff-benchmark)):
|
|
342
|
-
|
|
343
|
-
```js
|
|
344
|
-
import sprae from 'sprae';
|
|
345
|
-
import domdiff from 'list-difference';
|
|
346
|
-
|
|
347
|
-
// swap(parentNode, prevEls, newEls, endNode?)
|
|
348
|
-
sprae.use({ swap: domdiff });
|
|
350
|
+
// configure compiler
|
|
351
|
+
sprae.use({ compile })
|
|
349
352
|
```
|
|
350
|
-
-->
|
|
351
|
-
|
|
352
353
|
|
|
353
354
|
<!-- ## Dispose
|
|
354
355
|
|
|
355
356
|
To destroy state and detach sprae handlers, call `element[Symbol.dispose]()`. -->
|
|
356
357
|
|
|
357
358
|
|
|
358
|
-
<!--
|
|
359
|
-
## v9 changes
|
|
360
|
-
|
|
361
|
-
* No autoinit → use manual init via `import sprae from 'sprae'; sprae(document.body, state)`.
|
|
362
|
-
* No default globals (`console`, `setTimeout` etc) - pass to state if required.
|
|
363
|
-
* ``:class="`abc ${def}`"`` → `:class="'abc $<def>'"` (_justin_)
|
|
364
|
-
* `:with={x:'x'}` -> `:scope={x:'x'}`
|
|
365
|
-
* No reactive store → use signals for reactive values.
|
|
366
|
-
* `:render="tpl"` → `:html="tpl"`
|
|
367
|
-
* `@click="event.target"` → `:onclick="event => event.target"`
|
|
368
|
-
* Async props / events are not supported, pass async functions via state.
|
|
369
|
-
* Directives order matters, eg. `<a :if :each :scope />` !== `<a :scope :each :if />`
|
|
370
|
-
* Only one directive per `<template>`, eg. `<template :each />`, not `<template :if :each/>`
|
|
371
|
-
-->
|
|
372
|
-
|
|
373
359
|
## Justification
|
|
374
360
|
|
|
375
|
-
[Template-parts](https://github.com/dy/template-parts)
|
|
361
|
+
[Template-parts](https://github.com/dy/template-parts) is stuck with native HTML quirks ([parsing table](https://github.com/github/template-parts/issues/24), [SVG attributes](https://github.com/github/template-parts/issues/25), [liquid syntax](https://shopify.github.io/liquid/tags/template/#raw) conflict etc). [Alpine](https://github.com/alpinejs/alpine) / [petite-vue](https://github.com/vuejs/petite-vue) / [lucia](https://github.com/aidenyabi/lucia) escape native HTML quirks, but have excessive API (`:`, `x-`, `{}`, `@`, `$`) and tend to [self-encapsulate](https://github.com/alpinejs/alpine/discussions/3223).
|
|
376
362
|
|
|
377
|
-
_Sprae_ holds open & minimalistic philosophy, combining _`:`-directives_ with _signals_.
|
|
363
|
+
_Sprae_ holds open & minimalistic philosophy, combining _`:`-directives_ with emerging _signals_.
|
|
378
364
|
|
|
379
365
|
<!--
|
|
380
366
|
| | [AlpineJS](https://github.com/alpinejs/alpine) | [Petite-Vue](https://github.com/vuejs/petite-vue) | Sprae |
|
package/signal.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// signals adapter - allows switching signals implementation and not depend on core
|
|
2
|
+
export let signal, effect, untracked, batch, computed;
|
|
3
|
+
|
|
4
|
+
export function use(s) {
|
|
5
|
+
signal = s.signal
|
|
6
|
+
effect = s.effect
|
|
7
|
+
computed = s.computed
|
|
8
|
+
batch = s.batch || (fn => fn())
|
|
9
|
+
untracked = s.untracked || batch
|
|
10
|
+
}
|
package/sprae.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import sprae from './core.js'
|
|
2
2
|
|
|
3
|
-
import * as signals from '
|
|
4
|
-
import swap from 'swapdom/
|
|
3
|
+
import * as signals from 'ulive'
|
|
4
|
+
import swap from 'swapdom/deflate'
|
|
5
5
|
|
|
6
6
|
// default directives
|
|
7
7
|
import './directive/if.js'
|
|
8
8
|
import './directive/each.js'
|
|
9
9
|
import './directive/ref.js'
|
|
10
|
-
import './directive/
|
|
10
|
+
import './directive/with.js'
|
|
11
11
|
import './directive/html.js'
|
|
12
12
|
import './directive/text.js'
|
|
13
13
|
import './directive/class.js'
|
|
@@ -26,4 +26,3 @@ sprae.use({ compile: expr => sprae.constructor(`__scope`, `with (__scope) { retu
|
|
|
26
26
|
sprae.use({ swap })
|
|
27
27
|
|
|
28
28
|
export default sprae
|
|
29
|
-
export { signal, computed, effect, batch, untracked } from './core.js'
|
package/store.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// signals-based proxy
|
|
2
|
+
import { signal, computed, effect, batch, untracked } from './signal.js'
|
|
3
|
+
|
|
4
|
+
export const _signals = Symbol('signals'), _change = Symbol('length');
|
|
5
|
+
|
|
6
|
+
// object store is not lazy
|
|
7
|
+
export default function store(values, signals) {
|
|
8
|
+
if (!values) return values
|
|
9
|
+
|
|
10
|
+
// ignore existing state as argument
|
|
11
|
+
if (values[_signals] && !signals) return values;
|
|
12
|
+
|
|
13
|
+
// redirect for optimized array store
|
|
14
|
+
if (Array.isArray(values)) return list(values)
|
|
15
|
+
|
|
16
|
+
// ignore non-objects
|
|
17
|
+
if (values.constructor !== Object) return values;
|
|
18
|
+
|
|
19
|
+
// NOTE: if you decide to unlazy values, think about large arrays - init upfront can be costly
|
|
20
|
+
let _len = signal(Object.values(values).length)
|
|
21
|
+
|
|
22
|
+
signals ||= {}
|
|
23
|
+
// proxy conducts prop access to signals
|
|
24
|
+
const state = new Proxy(signals, {
|
|
25
|
+
get: (_, key) => key === _change ? _len : key === _signals ? signals : signals[key]?.valueOf(),
|
|
26
|
+
set: (_, key, v, s) => (s = signals[key], set(signals, key, v), s || (++_len.value)), // bump length for new signal
|
|
27
|
+
deleteProperty: (_, key) => del(signals, key) && _len.value--,
|
|
28
|
+
ownKeys() {
|
|
29
|
+
// subscribe to length when object is spread
|
|
30
|
+
_len.value
|
|
31
|
+
return Reflect.ownKeys(signals);
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// take over existing store signals instead of creating new ones
|
|
36
|
+
if (values[_signals]) for (let key in values) signals[key] = values[_signals][key];
|
|
37
|
+
else for (let key in values) {
|
|
38
|
+
const desc = Object.getOwnPropertyDescriptor(values, key)
|
|
39
|
+
|
|
40
|
+
// getter turns into computed
|
|
41
|
+
if (desc?.get) {
|
|
42
|
+
// stash setter
|
|
43
|
+
(signals[key] = computed(desc.get.bind(state)))._set = desc.set?.bind(state);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// init blank signal - make sure we don't take prototype one
|
|
47
|
+
signals[key] = null
|
|
48
|
+
set(signals, key, values[key]);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return state
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
// array store - signals are lazy since arrays can be very large & expensive
|
|
57
|
+
export function list(values) {
|
|
58
|
+
// track last accessed property to find out if .length was directly accessed from expression or via .push/etc method
|
|
59
|
+
let lastProp
|
|
60
|
+
|
|
61
|
+
// ignore existing state as argument
|
|
62
|
+
if (values[_signals]) return values;
|
|
63
|
+
|
|
64
|
+
// .length signal is stored separately, since it cannot be replaced on array
|
|
65
|
+
let _len = signal(values.length),
|
|
66
|
+
// gotta fill with null since proto methods like .reduce may fail
|
|
67
|
+
signals = Array(values.length).fill(null);
|
|
68
|
+
|
|
69
|
+
// proxy conducts prop access to signals
|
|
70
|
+
const state = new Proxy(signals, {
|
|
71
|
+
get(_, key) {
|
|
72
|
+
if (key === _change) return _len
|
|
73
|
+
if (key === _signals) return signals
|
|
74
|
+
|
|
75
|
+
// console.log('get', key)
|
|
76
|
+
// if .length is read within .push/etc - peek signal to avoid recursive subscription
|
|
77
|
+
if (key === 'length') return (Array.prototype[lastProp]) ? _len.peek() : _len.value;
|
|
78
|
+
|
|
79
|
+
lastProp = key;
|
|
80
|
+
|
|
81
|
+
if (signals[key]) return signals[key].valueOf()
|
|
82
|
+
|
|
83
|
+
// I hope reading values here won't diverge from signals
|
|
84
|
+
if (key < signals.length) return (signals[key] = signal(store(values[key]))).value
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
set(_, key, v) {
|
|
88
|
+
// .length
|
|
89
|
+
if (key === 'length') {
|
|
90
|
+
// force cleaning up tail
|
|
91
|
+
for (let i = v, l = signals.length; i < l; i++) delete state[i]
|
|
92
|
+
_len.value = signals.length = v;
|
|
93
|
+
return true
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
set(signals, key, v)
|
|
97
|
+
|
|
98
|
+
// force changing length, if eg. a=[]; a[1]=1 - need to come after setting the item
|
|
99
|
+
if (key >= _len.peek()) _len.value = signals.length = Number(key) + 1
|
|
100
|
+
|
|
101
|
+
return true
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
deleteProperty: (_, key) => (del(signals, key), true),
|
|
105
|
+
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
return state
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// set/update signal value
|
|
112
|
+
function set(signals, key, v) {
|
|
113
|
+
let s = signals[key]
|
|
114
|
+
|
|
115
|
+
// new property
|
|
116
|
+
if (!s) {
|
|
117
|
+
// preserve signal value as is
|
|
118
|
+
signals[key] = s = v?.peek ? v : signal(store(v))
|
|
119
|
+
}
|
|
120
|
+
// skip unchanged (although can be handled by last condition - we skip a few checks this way)
|
|
121
|
+
else if (v === s.peek());
|
|
122
|
+
// stashed _set for value with getter/setter
|
|
123
|
+
else if (s._set) s._set(v)
|
|
124
|
+
// patch array
|
|
125
|
+
else if (Array.isArray(v) && Array.isArray(s.peek())) {
|
|
126
|
+
const cur = s.peek()
|
|
127
|
+
// if we update plain array (stored in signal) - take over value instead
|
|
128
|
+
if (cur[_change]) untracked(() => {
|
|
129
|
+
batch(() => {
|
|
130
|
+
let i = 0, l = v.length;
|
|
131
|
+
for (; i < l; i++) cur[i] = v[i]
|
|
132
|
+
cur.length = l // forces deleting tail signals
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
else {
|
|
136
|
+
s.value = v
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// .x = y
|
|
140
|
+
else {
|
|
141
|
+
s.value = store(v)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// delete signal
|
|
146
|
+
function del(signals, key) {
|
|
147
|
+
// console.log('delete', key)
|
|
148
|
+
const s = signals[key]
|
|
149
|
+
if (s) {
|
|
150
|
+
const del = s[Symbol.dispose]
|
|
151
|
+
if (del) delete s[Symbol.dispose]
|
|
152
|
+
delete signals[key]
|
|
153
|
+
del?.()
|
|
154
|
+
return true
|
|
155
|
+
}
|
|
156
|
+
}
|
package/directive/scope.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import sprae, { directive } from "../core.js";
|
|
2
|
-
|
|
3
|
-
// `:each` can redefine scope as `:each="a in {myScope}"`,
|
|
4
|
-
// same time per-item scope as `:each="..." :scope="{collapsed:true}"` is useful
|
|
5
|
-
directive.scope = (el, evaluate, rootState) => {
|
|
6
|
-
// local state may contain signals that update, so we take them over
|
|
7
|
-
return () => {
|
|
8
|
-
sprae(el, { ...rootState, ...(evaluate(rootState)?.valueOf?.() || {}) });
|
|
9
|
-
}
|
|
10
|
-
};
|