sprae 6.1.1 → 7.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/sprae.min.js CHANGED
@@ -1 +1 @@
1
- var e,t,r=Promise.prototype.then.bind(Promise.resolve()),n=new WeakMap,l=class extends Map{#e=new FinalizationRegistry((e=>super.delete(e)));get size(){return[...this].length}constructor(e=[]){super();for(const[t,r]of e)this.set(t,r)}get(e){return super.get(e)?.deref()}set(e,t){let r=super.get(e);return r&&this.#e.unregister(r),r=(e=>n.get(e)||(e=>{const t=new WeakRef(e);return n.set(e,t),t})(e))(t),this.#e.register(t,e,r),super.set(e,r)}},s=new Set,i=new WeakMap,o=new WeakMap,a=new WeakMap,u=Symbol("parent"),c={Array:Array,Object:Object,Number:Number,String:String,Boolean:Boolean,Date:Date,console:console,window:window,document:document,history:history,location:location},f={has:()=>!0,get(t,r){if("symbol"==typeof r)return t[r];if(!(r in t))return t[u]?.[r];if(r in Object.prototype||Array.isArray(t)&&r in Array.prototype&&"length"!==r)return t[r];let n=t[r];if(e){let n=i.get(t);n||i.set(t,n={}),n[r]?n[r].includes(e)||n[r].push(e):n[r]=[e]}if(n&&n.constructor===Object||Array.isArray(n)){let e=o.get(n);return e||o.set(n,e=new Proxy(n,f)),e}return n},set(e,t,r){if(!(t in e)&&e[u]&&t in e[u])return e[u][t]=r;if(!Array.isArray(e)&&Object.is(e[t],r))return!0;e[t]=r;let n=i.get(e)?.[t];if(n)for(let e of n)s.add(e);return h(),!0},deleteProperty:(e,t)=>(e[t]=void 0,delete e[t],!0)},p=(e,t)=>{if(o.has(e))return o.get(e);if(a.has(e))return e;let r=new Proxy(e,f);o.set(e,r),a.set(r,e);let n=Object.getOwnPropertyDescriptors(e);for(let t in n){let l=n[t];l.get&&l.get&&(l.get=l.get.bind(r),Object.defineProperty(e,t,l))}return e[u]=t?p(t):c,r},y=t=>{const r=()=>{let n=e;e=r,t(),e=n};return r(),r},h=()=>{t||(t=!0,r((()=>{for(let e of s)e.call();s.clear(),t=!1})))},d={},g={};d.if=(e,t)=>{let r=document.createTextNode(""),n=[j(e,t,":if")],l=[e],s=e;for(;(s=e.nextElementSibling)&&s.hasAttribute(":else");)s.removeAttribute(":else"),(t=s.getAttribute(":if"))?(s.removeAttribute(":if"),s.remove(),l.push(s),n.push(j(e,t,":else :if"))):(s.remove(),l.push(s),n.push((()=>1)));return e.replaceWith(s=r),e=>{let t=n.findIndex((t=>t(e)));l[t]!=s&&((s[m]||s).replaceWith(s=l[t]||r),O(s,e))}};var m=Symbol(":each");d.each=(e,t)=>{let r=function(e){let t=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,r=e.match(/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/);if(!r)return;let n=r[2].trim(),l=r[1].replace(/^\s*\(|\)\s*$/g,"").trim(),s=l.match(t);return s?[l.replace(t,"").trim(),s[1].trim(),n]:[l,"",n]}(t);if(!r)return E(new Error,e,t,":each");const n=e[m]=document.createTextNode("");e.replaceWith(n);const s=j(e,r[2],":each"),i=e.getAttribute(":key"),o=i?j(null,i,":each"):null;e.removeAttribute(":key");const a=e.getAttribute(":ref"),u=new l,c=new l;let f=[];return l=>{let i=s(l);i?"number"==typeof i?i=Array.from({length:i},((e,t)=>[t+1,t])):Array.isArray(i)?i=i.map(((e,t)=>[t+1,e])):"object"==typeof i?i=Object.entries(i):E(Error("Bad list value"),e,t,":each"):i=[];let y=[],h=[];for(let[t,n]of i){let s,i,f=o?.({[r[0]]:n,[r[1]]:t});null==f?s=e.cloneNode(!0):(s=c.get(f))||c.set(f,s=e.cloneNode(!0)),y.push(s),null!=f&&(i=u.get(f))?i[r[0]]=n:(i=p({[r[0]]:n,[a||""]:null,[r[1]]:t},l),null!=f&&u.set(f,i)),h.push(i)}!function(e,t,r,n){const l=new Map,s=new Map;let i,o;for(i=0;i<t.length;i++)l.set(t[i],i);for(i=0;i<r.length;i++)s.set(r[i],i);for(i=o=0;i!==t.length||o!==r.length;){var a=t[i],u=r[o];if(null===a)i++;else if(r.length<=o)e.removeChild(t[i]),i++;else if(t.length<=i)e.insertBefore(u,t[i]||n),o++;else if(a===u)i++,o++;else{var c=s.get(a),f=l.get(u);void 0===c?(e.removeChild(t[i]),i++):void 0===f?(e.insertBefore(u,t[i]||n),o++):(e.insertBefore(t[f],t[i]||n),t[f]=null,f>i+1&&i++,o++)}}}(n.parentNode,f,y,n),f=y;for(let e=0;e<y.length;e++)O(y[e],h[e])}},d.with=(e,t,r)=>{const n=j(e,t,":with")(r);O(e,p(n,r))},d.ref=(e,t,r)=>{r[t]=e},g.render=(e,t,r)=>{let n=j(e,t,":render")(r);n||E(new Error("Template not found"),e,t,":render");let l=n.content.cloneNode(!0);e.replaceChildren(l),O(e,r)},g.id=(e,t)=>{let r=j(e,t,":id");return t=>{return n=r(t),e.id=n||0===n?n:"";var n}},g.class=(e,t)=>{let r=j(e,t,":class"),n=e.getAttribute("class");return t=>{let l=r(t),s=[n];l&&("string"==typeof l?s.push(l):Array.isArray(l)?s.push(...l):s.push(...Object.entries(l).map((([e,t])=>t?e:"")))),(s=s.filter(Boolean).join(" "))?e.setAttribute("class",s):e.removeAttribute("class")}},g.style=(e,t)=>{let r=j(e,t,":style"),n=e.getAttribute("style")||"";return n.endsWith(";")||(n+="; "),t=>{let l=r(t);if("string"==typeof l)e.setAttribute("style",n+l);else{e.setAttribute("style",n);for(let t in l)e.style.setProperty(t,l[t])}}},g.text=(e,t)=>{let r=j(e,t,":text");return t=>{let n=r(t);e.textContent=null==n?"":n}},g[""]=(e,t)=>{let r=j(e,t,":");if(r)return t=>{let n=r(t);for(let t in n)W(e,t.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(e=>"-"+e.toLowerCase())),n[t])}},g.value=(e,t)=>{let r,n,l=j(e,t,":value"),s="text"===e.type||""===e.type?t=>e.setAttribute("value",e.value=null==t?"":t):"TEXTAREA"===e.tagName||"text"===e.type||""===e.type?t=>(r=e.selectionStart,n=e.selectionEnd,e.setAttribute("value",e.value=null==t?"":t),r&&e.setSelectionRange(r,n)):"checkbox"===e.type?t=>(e.value=t?"on":"",W(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 e=>s(l(e))};var b=(e,t,r,n)=>{let l=n.startsWith("on")&&n.slice(2),s=j(e,t,":"+n);if(s)return l?t=>{let r=s(t)||(()=>{});return v(e,l,r)}:t=>W(e,n,s(t))},v=(e,t,r)=>{if(!r)return;const n={evt:"",target:e,test:()=>!0};n.evt=(t.startsWith("on")?t.slice(2):t).replace(/\.(\w+)?-?([-\w]+)?/g,((e,t,r="")=>(n.test=A[t]?.(n,...r.split("-"))||n.test,"")));const{evt:l,target:s,test:i,defer:o,stop:a,prevent:u,...c}=n;o&&(r=o(r));const f=e=>i(e)&&(a&&e.stopPropagation(),u&&e.preventDefault(),r.call(s,e));return s.addEventListener(l,f,c),()=>s.removeEventListener(l,f,c)},A={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=>k(e,t?Number(t)||0:108)},debounce(e,t){e.defer=e=>x(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:e=>w.arrow,enter:e=>w.enter,escape:e=>w.escape,tab:e=>w.tab,space:e=>w.space,backspace:e=>w.backspace,delete:e=>w.delete,digit:e=>w.digit,letter:e=>w.letter,character:e=>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)},k=(e,t)=>{let r,n,l=s=>{r=!0,setTimeout((()=>{if(r=!1,n)return n=!1,l(s),e(s)}),t)};return t=>r?n=!0:(l(t),e(t))},x=(e,t)=>{let r;return n=>{clearTimeout(r),r=setTimeout((()=>{r=null,e(n)}),t)}},W=(e,t,r)=>{null==r||!1===r?e.removeAttribute(t):e.setAttribute(t,!0===r?"":"number"==typeof r||"string"==typeof r?r:"")},S={};function j(e,t,r){let n=S[t];if(!n)try{n=S[t]=new Function("__scope",`with (__scope) { return ${t.trim()} };`)}catch(n){return E(n,e,t,r)}return l=>{let s;try{s=n.call(e,l)}catch(n){return E(n,e,t,r)}return s}}function E(e,t,n,l){Object.assign(e,{element:t,expression:n}),console.warn(`∴ ${e.message}\n\n${l}=${n?`"${n}"\n\n`:""}`,t),r((()=>{throw e}),0)}O.globals=c;var N=new WeakMap;function O(e,t){if(!e.children)return;if(N.has(e))return Object.assign(N.get(e),t);const r=p(t||{}),n=[],l=(e,t=e.parentNode)=>{for(let l in d){let s=":"+l;if(e.hasAttribute?.(s)){let i=e.getAttribute(s);if(e.removeAttribute(s),n.push(d[l](e,i,r,l)),N.has(e))return;if(e.parentNode!==t)return!1}}if(e.attributes)for(let t=0;t<e.attributes.length;){let l=e.attributes[t],s=l.name[0];if(":"===s||"@"===s){e.removeAttribute(l.name);let t="@"===s?`event=>{${l.value}}`:l.value,i=l.name.slice(1).split(s);for(let l of i){"@"===s&&(l="on"+l);let i=g[l]||b;n.push(i(e,t,r,l))}}else t++}for(let t,r=0;t=e.children[r];r++)!1===l(t,e)&&r--};l(e);for(let e of n)if(e){let t;y((()=>{"function"==typeof t&&t(),t=e(r)}))}return N.set(e,r),r}var $=O;document.currentScript&&O(document.documentElement);export{$ as default};
1
+ function t(){throw new Error("Cycle detected")}var e=Symbol.for("preact-signals");function r(){if(o>1)o--;else{for(var t,e=!1;void 0!==n;){var r=n;for(n=void 0,s++;void 0!==r;){var i=r.o;if(r.o=void 0,r.f&=-3,!(8&r.f)&&c(r))try{r.c()}catch(r){e||(t=r,e=!0)}r=i}}if(s=0,o--,e)throw t}}var i=void 0,n=void 0,o=0,s=0,a=0;function l(t){if(void 0!==i){var e=t.n;if(void 0===e||e.t!==i)return e={i:0,S:t,p:i.s,n:void 0,t:i,e:void 0,x:void 0,r:e},void 0!==i.s&&(i.s.n=e),i.s=e,t.n=e,32&i.f&&t.S(e),e;if(-1===e.i)return e.i=0,void 0!==e.n&&(e.n.p=e.p,void 0!==e.p&&(e.p.n=e.n),e.p=i.s,e.n=void 0,i.s.n=e,i.s=e),e}}function u(t){this.v=t,this.i=0,this.n=void 0,this.t=void 0}function f(t){return new u(t)}function c(t){for(var e=t.s;void 0!==e;e=e.n)if(e.S.i!==e.i||!e.S.h()||e.S.i!==e.i)return!0;return!1}function h(t){for(var e=t.s;void 0!==e;e=e.n){var r=e.S.n;if(void 0!==r&&(e.r=r),e.S.n=e,e.i=-1,void 0===e.n){t.s=e;break}}}function v(t){for(var e=t.s,r=void 0;void 0!==e;){var i=e.p;-1===e.i?(e.S.U(e),void 0!==i&&(i.n=e.n),void 0!==e.n&&(e.n.p=i)):r=e,e.S.n=e.r,void 0!==e.r&&(e.r=void 0),e=i}t.s=r}function p(t){u.call(this,void 0),this.x=t,this.s=void 0,this.g=a-1,this.f=4}function d(t){var e=t.u;if(t.u=void 0,"function"==typeof e){o++;var n=i;i=void 0;try{e()}catch(e){throw t.f&=-2,t.f|=8,y(t),e}finally{i=n,r()}}}function y(t){for(var e=t.s;void 0!==e;e=e.n)e.S.U(e);t.x=void 0,t.s=void 0,d(t)}function g(t){if(i!==this)throw new Error("Out-of-order effect");v(this),i=t,this.f&=-2,8&this.f&&y(this),r()}function m(t){this.x=t,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32}function b(t){var e=new m(t);try{e.c()}catch(t){throw e.d(),t}return e.d.bind(e)}u.prototype.brand=e,u.prototype.h=function(){return!0},u.prototype.S=function(t){this.t!==t&&void 0===t.e&&(t.x=this.t,void 0!==this.t&&(this.t.e=t),this.t=t)},u.prototype.U=function(t){if(void 0!==this.t){var e=t.e,r=t.x;void 0!==e&&(e.x=r,t.e=void 0),void 0!==r&&(r.e=e,t.x=void 0),t===this.t&&(this.t=r)}},u.prototype.subscribe=function(t){var e=this;return b((function(){var r=e.value,i=32&this.f;this.f&=-33;try{t(r)}finally{this.f|=i}}))},u.prototype.valueOf=function(){return this.value},u.prototype.toString=function(){return this.value+""},u.prototype.toJSON=function(){return this.value},u.prototype.peek=function(){return this.v},Object.defineProperty(u.prototype,"value",{get:function(){var t=l(this);return void 0!==t&&(t.i=this.i),this.v},set:function(e){if(i instanceof p&&function(){throw new Error("Computed cannot have side-effects")}(),e!==this.v){s>100&&t(),this.v=e,this.i++,a++,o++;try{for(var n=this.t;void 0!==n;n=n.x)n.t.N()}finally{r()}}}}),(p.prototype=new u).h=function(){if(this.f&=-3,1&this.f)return!1;if(32==(36&this.f))return!0;if(this.f&=-5,this.g===a)return!0;if(this.g=a,this.f|=1,this.i>0&&!c(this))return this.f&=-2,!0;var t=i;try{h(this),i=this;var e=this.x();(16&this.f||this.v!==e||0===this.i)&&(this.v=e,this.f&=-17,this.i++)}catch(t){this.v=t,this.f|=16,this.i++}return i=t,v(this),this.f&=-2,!0},p.prototype.S=function(t){if(void 0===this.t){this.f|=36;for(var e=this.s;void 0!==e;e=e.n)e.S.S(e)}u.prototype.S.call(this,t)},p.prototype.U=function(t){if(void 0!==this.t&&(u.prototype.U.call(this,t),void 0===this.t)){this.f&=-33;for(var e=this.s;void 0!==e;e=e.n)e.S.U(e)}},p.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(var t=this.t;void 0!==t;t=t.x)t.t.N()}},p.prototype.peek=function(){if(this.h()||t(),16&this.f)throw this.v;return this.v},Object.defineProperty(p.prototype,"value",{get:function(){1&this.f&&t();var e=l(this);if(this.h(),void 0!==e&&(e.i=this.i),16&this.f)throw this.v;return this.v}}),m.prototype.c=function(){var t=this.S();try{if(8&this.f)return;if(void 0===this.x)return;var e=this.x();"function"==typeof e&&(this.u=e)}finally{t()}},m.prototype.S=function(){1&this.f&&t(),this.f|=1,this.f&=-9,d(this),h(this),o++;var e=i;return i=this,g.bind(this,e)},m.prototype.N=function(){2&this.f||(this.f|=2,this.o=n,n=this)},m.prototype.d=function(){this.f|=8,1&this.f||y(this)};var w,A={Array:Array,Object:Object,Number:Number,String:String,Boolean:Boolean,Date:Date,console:console,window:window,document:document,history:history,navigator:navigator,location:location,screen:screen,localStorage:localStorage,sessionStorage:sessionStorage,alert:alert,prompt:prompt,confirm:confirm,fetch:fetch,performance:performance,setTimeout:setTimeout,setInterval:setInterval,requestAnimationFrame:requestAnimationFrame},k=new WeakMap;function S(t,e){if(t?.constructor!==Object&&!Array.isArray(t))return t;if(k.has(t)&&!e)return t;const r=Array.isArray(t)&&f(t.length),i=e?Object.create(k.get(e=S(e))):Array.isArray(t)?[]:{},n=new Proxy(i,{has:()=>!0,get(t,e){let i;return i=r&&"length"===e?Array.prototype[w]?r.peek():r.value:(t[e]||o(e))?.valueOf(),r&&(w=e),i},set(t,e,i){if(r&&"length"===e)r.value=t.length=i;else{const r=t[e]||o(e);r?r._set?r._set(i):r.value=S(i?.valueOf()):t[e]=f(S(i?.valueOf()))}return r&&(w=null),!0}});for(let e in t)i[e]=o(e);function o(r){if(t.hasOwnProperty(r)){const e=Object.getOwnPropertyDescriptor(t,r);return e?.get?((i[r]=(o=e.get.bind(n),new p(o)))._set=e.set?.bind(n),i[r]):i[r]=e.value?.peek?e.value:f(S(e.value))}var o;return e?e[r]:A.hasOwnProperty(r)?A[r]:void 0}return k.set(n,i),n}var x=Promise.prototype.then.bind(Promise.resolve()),O=new WeakMap,N=class extends Map{#t=new FinalizationRegistry((t=>super.delete(t)));get size(){return[...this].length}constructor(t=[]){super();for(const[e,r]of t)this.set(e,r)}get(t){return super.get(t)?.deref()}set(t,e){let r=super.get(t);return r&&this.#t.unregister(r),r=(t=>O.get(t)||(t=>{const e=new WeakRef(t);return O.set(t,e),e})(t))(e),this.#t.register(e,t,r),super.set(t,r)}},E={},j={};E.if=(t,e)=>{let r=document.createTextNode(""),i=[U(t,e,":if")],n=[t],o=t;for(;(o=t.nextElementSibling)&&o.hasAttribute(":else");)o.removeAttribute(":else"),(e=o.getAttribute(":if"))?(o.removeAttribute(":if"),o.remove(),n.push(o),i.push(U(t,e,":else :if"))):(o.remove(),n.push(o),i.push((()=>1)));return t.replaceWith(o=r),t=>{let e=i.findIndex((e=>e(t)));n[e]!=o&&((o[W]||o).replaceWith(o=n[e]||r),R(o,t))}};var W=Symbol(":each");E.each=(t,e)=>{let r=function(t){let e=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,r=t.match(/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/);if(!r)return;let i=r[2].trim(),n=r[1].replace(/^\s*\(|\)\s*$/g,"").trim(),o=n.match(e);return o?[n.replace(e,"").trim(),o[1].trim(),i]:[n,"",i]}(e);if(!r)return F(new Error,t,e,":each");const i=t[W]=document.createTextNode("");t.replaceWith(i);const n=U(t,r[2],":each"),o=t.getAttribute(":key"),s=o?U(null,o,":each"):null;t.removeAttribute(":key");const a=t.getAttribute(":ref"),l=new N,u=new N;let f=[];return o=>{let c=n(o);c?"number"==typeof c?c=Array.from({length:c},((t,e)=>[e+1,e])):Array.isArray(c)?c=c.map(((t,e)=>[e+1,t])):"object"==typeof c?c=Object.entries(c):F(Error("Bad list value"),t,e,":each"):c=[];let h=[],v=[];for(let[e,i]of c){let n,f,c=s?.({[r[0]]:i,[r[1]]:e});null==c?n=t.cloneNode(!0):(n=u.get(c))||u.set(c,n=t.cloneNode(!0)),h.push(n),null!=c&&(f=l.get(c))?f[r[0]]=i:(f=S({[r[0]]:i,[a||""]:null,[r[1]]:e},o),null!=c&&l.set(c,f)),v.push(f)}!function(t,e,r,i){const n=new Map,o=new Map;let s,a;for(s=0;s<e.length;s++)n.set(e[s],s);for(s=0;s<r.length;s++)o.set(r[s],s);for(s=a=0;s!==e.length||a!==r.length;){var l=e[s],u=r[a];if(null===l)s++;else if(r.length<=a)t.removeChild(e[s]),s++;else if(e.length<=s)t.insertBefore(u,e[s]||i),a++;else if(l===u)s++,a++;else{var f=o.get(l),c=n.get(u);void 0===f?(t.removeChild(e[s]),s++):void 0===c?(t.insertBefore(u,e[s]||i),a++):(t.insertBefore(e[c],e[s]||i),e[c]=null,c>s+1&&s++,a++)}}}(i.parentNode,f,h,i),f=h;for(let t=0;t<h.length;t++)R(h[t],v[t])}},E.with=(t,e,r)=>{R(t,S(U(t,e,":with")(r),r))},E.ref=(t,e,r)=>{r[e]=t},j.render=(t,e,r)=>{let i=U(t,e,":render")(r);i||F(new Error("Template not found"),t,e,":render");let n=i.content.cloneNode(!0);t.replaceChildren(n),R(t,r)},j.id=(t,e)=>{let r=U(t,e,":id");return e=>{return i=r(e),t.id=i||0===i?i:"";var i}},j.class=(t,e)=>{let r=U(t,e,":class"),i=t.getAttribute("class");return e=>{let n=r(e),o=[i];n&&("string"==typeof n?o.push(n):Array.isArray(n)?o.push(...n):o.push(...Object.entries(n).map((([t,e])=>e?t:"")))),(o=o.filter(Boolean).join(" "))?t.setAttribute("class",o):t.removeAttribute("class")}},j.style=(t,e)=>{let r=U(t,e,":style"),i=t.getAttribute("style")||"";return i.endsWith(";")||(i+="; "),e=>{let n=r(e);if("string"==typeof n)t.setAttribute("style",i+n);else{t.setAttribute("style",i);for(let e in n)t.style.setProperty(e,n[e])}}},j.text=(t,e)=>{let r=U(t,e,":text");return e=>{let i=r(e);t.textContent=null==i?"":i}},j[""]=(t,e)=>{let r=U(t,e,":");if(r)return e=>{let i=r(e);for(let e in i)M(t,e.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(t=>"-"+t.toLowerCase())),i[e])}},j.value=(t,e)=>{let r,i,n=U(t,e,":value"),o="text"===t.type||""===t.type?e=>t.setAttribute("value",t.value=null==e?"":e):"TEXTAREA"===t.tagName||"text"===t.type||""===t.type?e=>(r=t.selectionStart,i=t.selectionEnd,t.setAttribute("value",t.value=null==e?"":e),r&&t.setSelectionRange(r,i)):"checkbox"===t.type?e=>(t.value=e?"on":"",M(t,"checked",e)):"select-one"===t.type?e=>{for(let e in t.options)e.removeAttribute("selected");t.value=e,t.selectedOptions[0]?.setAttribute("selected","")}:e=>t.value=e;return t=>o(n(t))};var C=(t,e,r,i)=>{let n=i.startsWith("on")&&i.slice(2),o=U(t,e,":"+i);if(o)return n?e=>{let r=o(e)||(()=>{});return $(t,n,r)}:e=>M(t,i,o(e))},$=(t,e,r)=>{if(!r)return;const i={evt:"",target:t,test:()=>!0};i.evt=(e.startsWith("on")?e.slice(2):e).replace(/\.(\w+)?-?([-\w]+)?/g,((t,e,r="")=>(i.test=T[e]?.(i,...r.split("-"))||i.test,"")));const{evt:n,target:o,test:s,defer:a,stop:l,prevent:u,...f}=i;a&&(r=a(r));const c=t=>s(t)&&(l&&t.stopPropagation(),u&&t.preventDefault(),r.call(o,t));return o.addEventListener(n,c,f),()=>o.removeEventListener(n,c,f)},T={prevent(t){t.prevent=!0},stop(t){t.stop=!0},once(t){t.once=!0},passive(t){t.passive=!0},capture(t){t.capture=!0},window(t){t.target=window},document(t){t.target=document},throttle(t,e){t.defer=t=>B(t,e?Number(e)||0:108)},debounce(t,e){t.defer=t=>D(t,e?Number(e)||0:108)},outside:t=>e=>{let r=t.target;return!(r.contains(e.target)||!1===e.target.isConnected||r.offsetWidth<1&&r.offsetHeight<1)},self:t=>e=>e.target===t.target,ctrl:(t,...e)=>t=>P.ctrl(t)&&e.every((e=>P[e]?P[e](t):t.key===e)),shift:(t,...e)=>t=>P.shift(t)&&e.every((e=>P[e]?P[e](t):t.key===e)),alt:(t,...e)=>t=>P.alt(t)&&e.every((e=>P[e]?P[e](t):t.key===e)),meta:(t,...e)=>t=>P.meta(t)&&e.every((e=>P[e]?P[e](t):t.key===e)),arrow:t=>P.arrow,enter:t=>P.enter,escape:t=>P.escape,tab:t=>P.tab,space:t=>P.space,backspace:t=>P.backspace,delete:t=>P.delete,digit:t=>P.digit,letter:t=>P.letter,character:t=>P.character},P={ctrl:t=>t.ctrlKey||"Control"===t.key||"Ctrl"===t.key,shift:t=>t.shiftKey||"Shift"===t.key,alt:t=>t.altKey||"Alt"===t.key,meta:t=>t.metaKey||"Meta"===t.key||"Command"===t.key,arrow:t=>t.key.startsWith("Arrow"),enter:t=>"Enter"===t.key,escape:t=>t.key.startsWith("Esc"),tab:t=>"Tab"===t.key,space:t=>" "===t.key||"Space"===t.key||" "===t.key,backspace:t=>"Backspace"===t.key,delete:t=>"Delete"===t.key,digit:t=>/^\d$/.test(t.key),letter:t=>/^[a-zA-Z]$/.test(t.key),character:t=>/^\S$/.test(t.key)},B=(t,e)=>{let r,i,n=o=>{r=!0,setTimeout((()=>{if(r=!1,i)return i=!1,n(o),t(o)}),e)};return e=>r?i=!0:(n(e),t(e))},D=(t,e)=>{let r;return i=>{clearTimeout(r),r=setTimeout((()=>{r=null,t(i)}),e)}},M=(t,e,r)=>{null==r||!1===r?t.removeAttribute(e):t.setAttribute(e,!0===r?"":"number"==typeof r||"string"==typeof r?r:"")},_={};function U(t,e,r){let i=_[e];if(!i)try{i=_[e]=new Function("__scope",`with (__scope) { return ${e.trim()} };`)}catch(i){return F(i,t,e,r)}return n=>{let o;try{o=i.call(t,n)}catch(i){return F(i,t,e,r)}return o}}function F(t,e,r,i){Object.assign(t,{element:e,expression:r}),console.warn(`∴ ${t.message}\n\n${i}=${r?`"${r}"\n\n`:""}`,e),x((()=>{throw t}),0)}R.globals=A;var K=new WeakMap;function R(t,e){if(!t.children)return;if(K.has(t))return function(t){if(o>0)return t();o++;try{return t()}finally{r()}}((()=>Object.assign(K.get(t),e)));const i=S(e||{}),n=[],s=(t,e=t.parentNode)=>{for(let r in E){let o=":"+r;if(t.hasAttribute?.(o)){let s=t.getAttribute(o);if(t.removeAttribute(o),n.push(E[r](t,s,i,r)),K.has(t))return;if(t.parentNode!==e)return!1}}if(t.attributes)for(let e=0;e<t.attributes.length;){let r=t.attributes[e],o=r.name[0];if(":"===o||"@"===o){t.removeAttribute(r.name);let e="@"===o?`${r.value.includes("await")?"async":""} event=>{${r.value}}`:r.value,s=r.name.slice(1).split(o);for(let r of s){"@"===o&&(r="on"+r);let s=j[r]||C;n.push(s(t,e,i,r))}}else e++}for(let e,r=0;e=t.children[r];r++)!1===s(e,t)&&r--};s(t);for(let t of n)if(t){let e;b((()=>{"function"==typeof e&&e(),e=t(i)}))}return K.set(t,i),i}var z=R;document.currentScript&&R(document.documentElement);export{z as default};
package/src/core.js CHANGED
@@ -1,26 +1,19 @@
1
- import { state as createState, fx, sandbox } from './state.js';
1
+ import createState, { fx, sandbox, batch } from './state.signals-proxy.js';
2
2
  import defaultDirective, { primary, secondary, on } from './directives.js';
3
3
 
4
- sprae.globals=sandbox
4
+ sprae.globals = sandbox
5
5
 
6
6
  // sprae element: apply directives
7
7
  const memo = new WeakMap
8
8
  export default function sprae(container, values) {
9
9
  if (!container.children) return
10
- if (memo.has(container)) return Object.assign( memo.get(container), values)
10
+ if (memo.has(container)) return batch(() => Object.assign(memo.get(container), values))
11
11
 
12
- // subscribe to reactives
13
- // for (let prop in values) {
14
- // if (observable(values[prop])) {
15
- // let value = values[prop]; values[prop] = null
16
- // sube(value, value => state[prop] = value)
17
- // }
18
- // }
19
12
  const state = createState(values || {});
20
13
  const updates = []
21
14
 
22
15
  // init directives on element
23
- const init = (el, parent=el.parentNode) => {
16
+ const init = (el, parent = el.parentNode) => {
24
17
  // init primary attributes first
25
18
  for (let name in primary) {
26
19
  let attrName = ':' + name
@@ -43,8 +36,8 @@ export default function sprae(container, values) {
43
36
 
44
37
  if (prefix === ':' || prefix === '@') {
45
38
  el.removeAttribute(attr.name)
46
- let expr = prefix === '@' ? `event=>{${attr.value}}` : attr.value,
47
- names = attr.name.slice(1).split(prefix)
39
+ let expr = prefix === '@' ? `${attr.value.includes('await') ? 'async' : ''} event=>{${attr.value}}` : attr.value,
40
+ names = attr.name.slice(1).split(prefix)
48
41
 
49
42
  // multiple attributes like :id:for="" or @click@touchstart
50
43
  for (let name of names) {
package/src/directives.js CHANGED
@@ -2,21 +2,20 @@
2
2
  import sprae from './core.js'
3
3
  import swap from './domdiff.js'
4
4
  // import swap from 'swapdom'
5
- import { state as createState} from './state.js'
5
+ import createState from './state.signals-proxy.js'
6
6
  import { queueMicrotask, WeakishMap } from './util.js'
7
7
 
8
8
  // reserved directives - order matters!
9
9
  // primary initialized first by selector, secondary initialized by iterating attributes
10
10
  export const primary = {}, secondary = {}
11
11
 
12
-
13
12
  // :if is interchangeable with :each depending on order, :if :each or :each :if have different meanings
14
13
  // as for :if :with - :if must init first, since it is lazy, to avoid initializing component ahead of time by :with
15
14
  // we consider :with={x} :if={x} case insignificant
16
15
  primary['if'] = (el, expr) => {
17
16
  let holder = document.createTextNode(''),
18
- clauses = [parseExpr(el, expr, ':if')],
19
- els = [el], cur = el
17
+ clauses = [parseExpr(el, expr, ':if')],
18
+ els = [el], cur = el
20
19
 
21
20
  while (cur = el.nextElementSibling) {
22
21
  if (cur.hasAttribute(':else')) {
@@ -37,7 +36,7 @@ primary['if'] = (el, expr) => {
37
36
  return (state) => {
38
37
  let i = clauses.findIndex(f => f(state))
39
38
  if (els[i] != cur) {
40
- ;(cur[_each] || cur).replaceWith(cur = els[i] || holder);
39
+ ; (cur[_each] || cur).replaceWith(cur = els[i] || holder);
41
40
  // NOTE: it lazily initializes elements on insertion, it's safe to sprae multiple times
42
41
  // but :if must come first to avoid preliminary caching
43
42
  sprae(cur, state);
@@ -74,8 +73,8 @@ primary['each'] = (tpl, expr) => {
74
73
  let list = evaluate(state)
75
74
 
76
75
  if (!list) list = []
77
- else if (typeof list === 'number') list = Array.from({length: list}, (_, i)=>[i+1, i])
78
- else if (Array.isArray(list)) list = list.map((item,i) => [i+1, item])
76
+ else if (typeof list === 'number') list = Array.from({ length: list }, (_, i) => [i + 1, i])
77
+ else if (Array.isArray(list)) list = list.map((item, i) => [i + 1, item])
79
78
  else if (typeof list === 'object') list = Object.entries(list)
80
79
  else exprError(Error('Bad list value'), tpl, expr, ':each', list)
81
80
 
@@ -83,7 +82,7 @@ primary['each'] = (tpl, expr) => {
83
82
  let newEls = [], elScopes = []
84
83
 
85
84
  for (let [idx, item] of list) {
86
- let el, scope, key = itemKey?.({[each[0]]: item, [each[1]]: idx})
85
+ let el, scope, key = itemKey?.({ [each[0]]: item, [each[1]]: idx })
87
86
 
88
87
  // we consider if data items are primitive, then nodes needn't be cached
89
88
  // since likely they're very simple to create
@@ -93,7 +92,7 @@ primary['each'] = (tpl, expr) => {
93
92
  newEls.push(el)
94
93
 
95
94
  if (key == null || !(scope = scopes.get(key))) {
96
- scope = createState({[each[0]]: item, [refExpr||'']: null, [each[1]]: idx}, state)
95
+ scope = createState({ [each[0]]: item, [refExpr || '']: null, [each[1]]: idx }, state)
97
96
  if (key != null) scopes.set(key, scope)
98
97
  }
99
98
  // need to explicitly set item to update existing children's values
@@ -130,6 +129,7 @@ primary['ref'] = (el, expr, state) => {
130
129
 
131
130
 
132
131
  // This was taken AlpineJS, former VueJS 2.* core. Thanks Alpine & Vue!
132
+ // takes instruction like `item in list`, returns ['item', '', 'list']
133
133
  function parseForExpression(expression) {
134
134
  let forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
135
135
  let stripParensRE = /^\s*\(|\)\s*$/g
@@ -148,12 +148,13 @@ function parseForExpression(expression) {
148
148
  items
149
149
  ]
150
150
 
151
+ // FIXME: it can possibly return index as second param
151
152
  return [item, '', items]
152
153
  }
153
154
 
154
155
  secondary['render'] = (el, expr, state) => {
155
156
  let evaluate = parseExpr(el, expr, ':render'),
156
- tpl = evaluate(state)
157
+ tpl = evaluate(state)
157
158
 
158
159
  if (!tpl) exprError(new Error('Template not found'), el, expr, ':render');
159
160
 
@@ -177,7 +178,7 @@ secondary['class'] = (el, expr) => {
177
178
  if (v) {
178
179
  if (typeof v === 'string') className.push(v)
179
180
  else if (Array.isArray(v)) className.push(...v)
180
- else className.push(...Object.entries(v).map(([k,v])=>v?k:''))
181
+ else className.push(...Object.entries(v).map(([k, v]) => v ? k : ''))
181
182
  }
182
183
  if (className = className.filter(Boolean).join(' ')) el.setAttribute('class', className);
183
184
  else el.removeAttribute('class')
@@ -222,19 +223,19 @@ secondary['value'] = (el, expr) => {
222
223
  let from, to
223
224
  let update = (
224
225
  el.type === 'text' || el.type === '' ? value => el.setAttribute('value', el.value = value == null ? '' : value) :
225
- el.tagName === 'TEXTAREA' || el.type === 'text' || el.type === '' ? value => (
226
- // we retain selection in input
227
- from = el.selectionStart, to = el.selectionEnd,
228
- el.setAttribute('value', el.value = value == null ? '' : value),
229
- from && el.setSelectionRange(from, to)
230
- ) :
231
- el.type === 'checkbox' ? value => (el.value = value ? 'on' : '', attr(el, 'checked', value)) :
232
- el.type === 'select-one' ? value => {
233
- for (let option in el.options) option.removeAttribute('selected')
234
- el.value = value;
235
- el.selectedOptions[0]?.setAttribute('selected', '')
236
- } :
237
- value => el.value = value
226
+ el.tagName === 'TEXTAREA' || el.type === 'text' || el.type === '' ? value => (
227
+ // we retain selection in input
228
+ from = el.selectionStart, to = el.selectionEnd,
229
+ el.setAttribute('value', el.value = value == null ? '' : value),
230
+ from && el.setSelectionRange(from, to)
231
+ ) :
232
+ el.type === 'checkbox' ? value => (el.value = value ? 'on' : '', attr(el, 'checked', value)) :
233
+ el.type === 'select-one' ? value => {
234
+ for (let option in el.options) option.removeAttribute('selected')
235
+ el.value = value;
236
+ el.selectedOptions[0]?.setAttribute('selected', '')
237
+ } :
238
+ value => el.value = value
238
239
  )
239
240
 
240
241
  return (state) => update(evaluate(state))
@@ -243,13 +244,13 @@ secondary['value'] = (el, expr) => {
243
244
  // any unknown directive
244
245
  export default (el, expr, state, name) => {
245
246
  let evt = name.startsWith('on') && name.slice(2)
246
- let evaluate = parseExpr(el, expr, ':'+name)
247
+ let evaluate = parseExpr(el, expr, ':' + name)
247
248
 
248
249
  if (!evaluate) return
249
250
 
250
251
  if (evt) return (state => {
251
252
  // we need anonymous callback to enable modifiers like prevent
252
- let value = evaluate(state) || (()=>{})
253
+ let value = evaluate(state) || (() => { })
253
254
  return on(el, evt, value)
254
255
  })
255
256
 
@@ -260,21 +261,21 @@ export default (el, expr, state, name) => {
260
261
  export const on = (el, e, fn) => {
261
262
  if (!fn) return
262
263
 
263
- const ctx = { evt:'', target:el, test:()=>true };
264
+ const ctx = { evt: '', target: el, test: () => true };
264
265
 
265
266
  // onevt.debounce-108 -> evt.debounce-108
266
267
  ctx.evt = (e.startsWith('on') ? e.slice(2) : e).replace(/\.(\w+)?-?([-\w]+)?/g,
267
- (match, mod, param='') => (ctx.test = mods[mod]?.(ctx, ...param.split('-')) || ctx.test, '')
268
+ (match, mod, param = '') => (ctx.test = mods[mod]?.(ctx, ...param.split('-')) || ctx.test, '')
268
269
  );
269
270
 
270
271
  // add listener applying the context
271
- const {evt, target, test, defer, stop, prevent, ...opts} = ctx;
272
+ const { evt, target, test, defer, stop, prevent, ...opts } = ctx;
272
273
 
273
274
  if (defer) fn = defer(fn)
274
275
 
275
276
  const cb = e => test(e) && (
276
- stop&&e.stopPropagation(),
277
- prevent&&e.preventDefault(),
277
+ stop && e.stopPropagation(),
278
+ prevent && e.preventDefault(),
278
279
  fn.call(target, e)
279
280
  )
280
281
 
@@ -365,7 +366,7 @@ const debounce = (fn, wait) => {
365
366
  let timeout
366
367
  return (e) => {
367
368
  clearTimeout(timeout)
368
- timeout = setTimeout(() => {timeout = null; fn(e)}, wait)
369
+ timeout = setTimeout(() => { timeout = null; fn(e) }, wait)
369
370
  }
370
371
  }
371
372
 
@@ -385,8 +386,8 @@ function parseExpr(el, expression, dir) {
385
386
 
386
387
  if (!evaluate) {
387
388
  try {
388
- evaluate = evaluatorMemo[expression] = new Function(`__scope`,`with (__scope) { return ${expression.trim()} };`)
389
- } catch ( e ) {
389
+ evaluate = evaluatorMemo[expression] = new Function(`__scope`, `with (__scope) { return ${expression.trim()} };`)
390
+ } catch (e) {
390
391
  return exprError(e, el, expression, dir)
391
392
  }
392
393
  }
@@ -401,11 +402,11 @@ function parseExpr(el, expression, dir) {
401
402
  }
402
403
 
403
404
  function exprError(error, element, expression, directive) {
404
- Object.assign( error, { element, expression } )
405
- console.warn(`∴ ${error.message}\n\n${directive}=${ expression ? `"${expression}"\n\n` : '' }`, element)
405
+ Object.assign(error, { element, expression })
406
+ console.warn(`∴ ${error.message}\n\n${directive}=${expression ? `"${expression}"\n\n` : ''}`, element)
406
407
  queueMicrotask(() => { throw error }, 0)
407
408
  }
408
409
 
409
410
  function dashcase(str) {
410
- return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => '-' + match.toLowerCase());
411
+ return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => '-' + match.toLowerCase());
411
412
  };
@@ -1,7 +1,10 @@
1
+ // proxy-based store implementation: more flexible, less optimal and with memory leak now
2
+ // FIXME: make simpler prototype chain, likely manual
3
+ // FIXME: detect circular updates
1
4
  import { queueMicrotask } from "./util.js"
2
5
 
3
6
  // currentFx stack of listeners
4
- let currentFx, batch = new Set, pendingUpdate
7
+ let currentFx, batch = [], pendingUpdate
5
8
 
6
9
  const targetFxs = new WeakMap
7
10
  const targetProxy = new WeakMap
@@ -14,19 +17,30 @@ export const sandbox = {
14
17
  console, window, document, history, location
15
18
  }
16
19
 
20
+ const callArg = (fn) => fn(), nop = () => { }
21
+ export { callArg as batch };
22
+
23
+ let lastProp
24
+
17
25
  const handler = {
18
- has(){
26
+ has() {
19
27
  // sandbox everything
20
28
  return true
21
29
  },
22
30
 
23
31
  get(target, prop) {
24
- if (typeof prop === 'symbol') return target[prop]
32
+ if (typeof prop === 'symbol') return lastProp = null, target[prop]
25
33
  if (!(prop in target)) return target[_parent]?.[prop]
34
+
35
+ // ignore .length effects called within .push
36
+ if (lastProp && prop === 'length') return lastProp = null, target[prop];
37
+
26
38
  // .constructor, .slice etc
27
- if (prop in Object.prototype || (Array.isArray(target) && prop in Array.prototype && prop !== 'length')) return target[prop];
39
+ if (Object.prototype[prop]) return target[prop]
40
+ if (Array.isArray(target) && Array.prototype[prop] && prop !== 'length') return lastProp = prop, target[prop];
28
41
 
29
42
  let value = target[prop]
43
+
30
44
  if (currentFx) {
31
45
  // get actual target from prototype chain
32
46
  // while (!target.hasOwnProperty(prop)) target = Object.getPrototypeOf(target)
@@ -61,7 +75,11 @@ const handler = {
61
75
  // whenever target prop is set, call all dependent fxs
62
76
  let propFxs = targetFxs.get(target)?.[prop]
63
77
 
64
- if (propFxs) for (let fx of propFxs) batch.add(fx)
78
+ if (propFxs) for (let fx of propFxs) {
79
+ // put fx latest
80
+ if (fx.planned != null) batch[fx.planned] = null
81
+ fx.planned = batch.push(fx) - 1
82
+ }
65
83
  planUpdate()
66
84
 
67
85
  // FIXME: unsubscribe / delete effects by setting null/undefined
@@ -77,7 +95,7 @@ const handler = {
77
95
  }
78
96
  }
79
97
 
80
- export const state = (obj, parent) => {
98
+ export default function state(obj, parent) {
81
99
  if (targetProxy.has(obj)) return targetProxy.get(obj)
82
100
  if (proxyTarget.has(obj)) return obj // is proxy already
83
101
 
@@ -85,7 +103,7 @@ export const state = (obj, parent) => {
85
103
  targetProxy.set(obj, proxy)
86
104
  proxyTarget.set(proxy, obj)
87
105
 
88
- // bind all getters here to proxy
106
+ // bind all getters & functions here to proxy
89
107
  // FIXME: alternatively we can store getters somewhere
90
108
  let descriptors = Object.getOwnPropertyDescriptors(obj)
91
109
  for (let name in descriptors) {
@@ -93,6 +111,9 @@ export const state = (obj, parent) => {
93
111
  if (desc.get) {
94
112
  if (desc.get) desc.get = desc.get.bind(proxy), Object.defineProperty(obj, name, desc)
95
113
  }
114
+ // else if (typeof desc.value === 'function') {
115
+ // desc.value = desc.value.bind(proxy), Object.defineProperty(obj, name, desc)
116
+ // }
96
117
  }
97
118
 
98
119
  // inherit from parent state
@@ -116,12 +137,12 @@ export const fx = (fn) => {
116
137
  }
117
138
 
118
139
  export const planUpdate = () => {
119
- if (!pendingUpdate) {
120
- pendingUpdate = true
121
- queueMicrotask(() => {
122
- for (let fx of batch) fx.call()
123
- batch.clear()
124
- pendingUpdate = false
125
- })
126
- }
140
+ // if (!pendingUpdate) {
141
+ // pendingUpdate = true
142
+ // queueMicrotask(() => {
143
+ let fx
144
+ while (batch.length) fx = batch.shift(), fx && (fx(), fx.planned = null)
145
+ // pendingUpdate = false
146
+ // })
147
+ // }
127
148
  }
@@ -0,0 +1,104 @@
1
+ // signals-based proxy
2
+ // + we need proxy for sandbox & arrays anyways
3
+ // + it seems faster than defining a bunch of props on sealed state object
4
+ // + we need support signal inputs
5
+ // + signals provide nice tracking mechanism, unlike own arrays
6
+ // + signals detect cycles
7
+ // + it's just robust
8
+ // ? must it modify initial store
9
+
10
+ import { signal, computed, effect, batch } from '@preact/signals-core'
11
+ // import { signal, computed } from 'usignal/sync'
12
+ // import { signal, computed } from '@webreflection/signal'
13
+
14
+ export { effect as fx, batch }
15
+
16
+ // default root sandbox
17
+ export const sandbox = {
18
+ Array, Object, Number, String, Boolean, Date,
19
+ console, window, document, history, navigator, location, screen, localStorage, sessionStorage,
20
+ alert, prompt, confirm, fetch, performance,
21
+ setTimeout, setInterval, requestAnimationFrame
22
+ }
23
+
24
+ const isObject = v => v?.constructor === Object
25
+ const memo = new WeakMap
26
+
27
+ // track last accessed property to figure out if .length was directly accessed from expression or via .push/etc method
28
+ let lastProp
29
+
30
+ export default function createState(values, parent) {
31
+ if (!isObject(values) && !Array.isArray(values)) return values;
32
+ if (memo.has(values) && !parent) return values;
33
+ // .length signal is stored outside, since cannot be replaced
34
+ const _len = Array.isArray(values) && signal(values.length),
35
+ // dict with signals storing values
36
+ signals = parent ? Object.create(memo.get(parent = createState(parent))) : Array.isArray(values) ? [] : {},
37
+ // proxy conducts prop access to signals
38
+ state = new Proxy(signals, {
39
+ // sandbox everything
40
+ has() { return true },
41
+ get(signals, key) {
42
+ // console.log('get', key)
43
+ let v
44
+ // if .length is read within .push/etc - peek signal (don't subscribe)
45
+ if (_len && key === 'length') v = Array.prototype[lastProp] ? _len.peek() : _len.value;
46
+ else v = (signals[key] || initSignal(key))?.valueOf()
47
+ if (_len) lastProp = key
48
+ return v
49
+ },
50
+ set(signals, key, v) {
51
+ // .length
52
+ if (_len && key === 'length') _len.value = signals.length = v;
53
+
54
+ else {
55
+ const s = signals[key] || initSignal(key)
56
+
57
+ // new unknown property
58
+ if (!s) signals[key] = signal(createState(v?.valueOf()))
59
+ // stashed _set for values with getter/setter
60
+ else if (s._set) s._set(v)
61
+ // FIXME: is there meaningful way to update same-signature object?
62
+ // else if (isObject(v) && isObject(s.value)) Object.assign(s.value, v)
63
+ // .x = y
64
+ else s.value = createState(v?.valueOf())
65
+ }
66
+
67
+ if (_len) lastProp = null
68
+ return true
69
+ }
70
+ })
71
+
72
+ // init signals placeholders (instead of ownKeys & getOwnPropertyDescriptor handlers)
73
+ for (let key in values) {
74
+ // FIXME: make lazy signals actually work (breaks 2 tests)
75
+ // signals[key] = null // make placeholder
76
+ signals[key] = initSignal(key)
77
+ }
78
+
79
+ // initialize signal for provided key
80
+ function initSignal(key) {
81
+ // init existing value
82
+ if (values.hasOwnProperty(key)) {
83
+ // create signal from descriptor
84
+ const desc = Object.getOwnPropertyDescriptor(values, key)
85
+ // getter turns into computed
86
+ if (desc?.get) {
87
+ // stash setter
88
+ (signals[key] = computed(desc.get.bind(state)))._set = desc.set?.bind(state);
89
+ return signals[key]
90
+ }
91
+ return signals[key] = desc.value?.peek ? desc.value : signal(createState(desc.value))
92
+ }
93
+
94
+ // touch parent
95
+ if (parent) return parent[key]
96
+
97
+ // Array, window etc
98
+ if (sandbox.hasOwnProperty(key)) return sandbox[key]
99
+ }
100
+
101
+ memo.set(state, signals)
102
+
103
+ return state
104
+ }
@@ -0,0 +1,82 @@
1
+ // signals-based store implementation
2
+ import { signal, computed, effect } from '@preact/signals-core'
3
+ // import { signal, computed } from 'usignal/sync'
4
+ // import { signal, computed } from '@webreflection/signal'
5
+
6
+ const isSignal = v => v?.peek
7
+ const isState = v => v?.[_st]
8
+ const isObject = v => v?.constructor === Object
9
+
10
+ const _st = Symbol('state')
11
+
12
+ export { effect as fx }
13
+
14
+ export default function createState(values, proto) {
15
+ if (isState(values) && !proto) return values;
16
+
17
+ // define signal accessors - creates signals for all object props
18
+
19
+ if (isObject(values)) {
20
+ const
21
+ state = Object.create(proto || Object.getPrototypeOf(values)),
22
+ signals = {},
23
+ descs = Object.getOwnPropertyDescriptors(values)
24
+
25
+ // define signal accessors for exported object
26
+ for (let key in descs) {
27
+ let desc = descs[key]
28
+
29
+ // getter turns into computed
30
+ if (desc.get) {
31
+ let s = signals[key] = computed(desc.get.bind(state))
32
+ Object.defineProperty(state, key, {
33
+ get() { return s.value },
34
+ set: desc.set?.bind(state),
35
+ configurable: false,
36
+ enumerable: true
37
+ })
38
+ }
39
+ // regular value creates signal accessor
40
+ else {
41
+ let value = desc.value
42
+ let s = signals[key] = isSignal(value) ? value :
43
+ signal(
44
+ // if initial value is an object - we turn it into sealed struct
45
+ isObject(value) ? Object.seal(createState(value)) :
46
+ Array.isArray(value) ? createState(value) :
47
+ value
48
+ )
49
+
50
+ // define property accessor on struct
51
+ Object.defineProperty(state, key, {
52
+ get() { return s.value },
53
+ set(v) {
54
+ if (isObject(v)) {
55
+ // new object can have another schema than the new one
56
+ // so if it throws due to new props access then we fall back to creating new struct
57
+ if (isObject(s.value)) try { Object.assign(s.value, v); return } catch (e) { }
58
+ s.value = Object.seal(createState(v));
59
+ }
60
+ else if (Array.isArray(v)) s.value = createState(v)
61
+ else s.value = v;
62
+
63
+ },
64
+ enumerable: true,
65
+ configurable: false
66
+ })
67
+ }
68
+ }
69
+
70
+ Object.defineProperty(state, _st, { configurable: false, enumerable: false, value: true })
71
+
72
+ return state
73
+ }
74
+
75
+ // for arrays we turn internals into signal structs
76
+ // FIXME: make proxy to intercept length and other single values
77
+ if (Array.isArray(values)) {
78
+ for (let i = 0; i < values.length; i++) values[i] = createState(values[i])
79
+ }
80
+
81
+ return values
82
+ }
package/.gitattributes DELETED
@@ -1,2 +0,0 @@
1
- # Auto detect text files and perform LF normalization
2
- * text=auto
@@ -1,30 +0,0 @@
1
- # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2
- # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3
-
4
- name: Tests
5
-
6
- on:
7
- push:
8
- branches: [ "main" ]
9
- pull_request:
10
- branches: [ "main" ]
11
-
12
- jobs:
13
- build:
14
-
15
- runs-on: ubuntu-latest
16
-
17
- strategy:
18
- matrix:
19
- node-version: [18.x]
20
-
21
- steps:
22
- - uses: actions/checkout@v3
23
- - name: Use Node.js ${{ matrix.node-version }}
24
- uses: actions/setup-node@v3
25
- with:
26
- node-version: ${{ matrix.node-version }}
27
- cache: 'npm'
28
- - run: npm ci
29
- - run: npm run build --if-present
30
- - run: npm test