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/package.json +9 -1
- package/readme.md +91 -44
- package/sprae.auto.js +601 -244
- package/sprae.auto.min.js +1 -1
- package/sprae.js +601 -244
- package/sprae.min.js +1 -1
- package/src/core.js +6 -13
- package/src/directives.js +38 -37
- package/src/{state.js → state.proxy.js} +36 -15
- package/src/state.signals-proxy.js +104 -0
- package/src/state.signals.js +82 -0
- package/.gitattributes +0 -2
- package/.github/workflows/node.js.yml +0 -30
- package/examples/todomvc.html +0 -98
- package/r&d.md +0 -620
- package/test/attrs.js +0 -754
- package/test/events.js +0 -138
- package/test/index.html +0 -36
- package/test/register.cjs +0 -20
- package/test/state.js +0 -296
- package/test/test.js +0 -13
- package/todo.md +0 -54
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
|
|
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(
|
|
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 === '@' ?
|
|
47
|
-
|
|
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
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
389
|
-
} catch (
|
|
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(
|
|
405
|
-
console.warn(`∴ ${error.message}\n\n${directive}=${
|
|
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
|
-
|
|
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 =
|
|
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 (
|
|
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)
|
|
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
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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,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
|