sprae 8.1.3 → 9.0.1

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.
@@ -1 +0,0 @@
1
- (()=>{function t(){throw new Error("Cycle detected")}var e=Symbol.for("preact-signals");function r(){if(l>1)l--;else{for(var t,e=!1;void 0!==a;){var r=a;for(a=void 0,u++;void 0!==r;){var i=r.o;if(r.o=void 0,r.f&=-3,!(8&r.f)&&p(r))try{r.c()}catch(r){e||(t=r,e=!0)}r=i}}if(u=0,l--,e)throw t}}function i(t){if(l>0)return t();l++;try{return t()}finally{r()}}var n=void 0,o=0;function s(t){if(o>0)return t();var e=n;n=void 0,o++;try{return t()}finally{o--,n=e}}var a=void 0,l=0,u=0,f=0;function c(t){if(void 0!==n){var e=t.n;if(void 0===e||e.t!==n)return e={i:0,S:t,p:n.s,n:void 0,t:n,e:void 0,x:void 0,r:e},void 0!==n.s&&(n.s.n=e),n.s=e,t.n=e,32&n.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=n.s,e.n=void 0,n.s.n=e,n.s=e),e}}function h(t){this.v=t,this.i=0,this.n=void 0,this.t=void 0}function v(t){return new h(t)}function p(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 d(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 y(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 m(t){h.call(this,void 0),this.x=t,this.s=void 0,this.g=f-1,this.f=4}function g(t){var e=t.u;if(t.u=void 0,"function"==typeof e){l++;var i=n;n=void 0;try{e()}catch(e){throw t.f&=-2,t.f|=8,b(t),e}finally{n=i,r()}}}function b(t){for(var e=t.s;void 0!==e;e=e.n)e.S.U(e);t.x=void 0,t.s=void 0,g(t)}function A(t){if(n!==this)throw new Error("Out-of-order effect");y(this),n=t,this.f&=-2,8&this.f&&b(this),r()}function w(t){this.x=t,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32}function S(t){var e=new w(t);try{e.c()}catch(t){throw e.d(),t}return e.d.bind(e)}h.prototype.brand=e,h.prototype.h=function(){return!0},h.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)},h.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)}},h.prototype.subscribe=function(t){var e=this;return S((function(){var r=e.value,i=32&this.f;this.f&=-33;try{t(r)}finally{this.f|=i}}))},h.prototype.valueOf=function(){return this.value},h.prototype.toString=function(){return this.value+""},h.prototype.toJSON=function(){return this.value},h.prototype.peek=function(){return this.v},Object.defineProperty(h.prototype,"value",{get:function(){var t=c(this);return void 0!==t&&(t.i=this.i),this.v},set:function(e){if(n instanceof m&&function(){throw new Error("Computed cannot have side-effects")}(),e!==this.v){u>100&&t(),this.v=e,this.i++,f++,l++;try{for(var i=this.t;void 0!==i;i=i.x)i.t.N()}finally{r()}}}}),(m.prototype=new h).h=function(){if(this.f&=-3,1&this.f)return!1;if(32==(36&this.f))return!0;if(this.f&=-5,this.g===f)return!0;if(this.g=f,this.f|=1,this.i>0&&!p(this))return this.f&=-2,!0;var t=n;try{d(this),n=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 n=t,y(this),this.f&=-2,!0},m.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)}h.prototype.S.call(this,t)},m.prototype.U=function(t){if(void 0!==this.t&&(h.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)}},m.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(var t=this.t;void 0!==t;t=t.x)t.t.N()}},m.prototype.peek=function(){if(this.h()||t(),16&this.f)throw this.v;return this.v},Object.defineProperty(m.prototype,"value",{get:function(){1&this.f&&t();var e=c(this);if(this.h(),void 0!==e&&(e.i=this.i),16&this.f)throw this.v;return this.v}}),w.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()}},w.prototype.S=function(){1&this.f&&t(),this.f|=1,this.f&=-9,g(this),d(this),l++;var e=n;return n=this,A.bind(this,e)},w.prototype.N=function(){2&this.f||(this.f|=2,this.o=a,a=this)},w.prototype.d=function(){this.f|=8,1&this.f||b(this)};var k,x=Symbol.dispose||=Symbol("dispose"),O=Symbol("signals"),N=Symbol("length"),j={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};function E(t,e){if(t?.constructor!==Object&&!Array.isArray(t))return t;if(t[O]&&!e)return t;const r=t[O],n=Array.isArray(t),o=v(n?t.length:Object.values(t).length),a=e?Object.create((e=E(e))[O]):Array.isArray(t)?[]:{},l=a.constructor.prototype;if(e)for(let t in e)e[t];const u=new Proxy(t,{has:()=>!0,get(t,e){if(n){if("length"===e)return l[k]?o.peek():o.value;k=e}if(l[e])return l[e];if(e===O)return a;if(e===N)return o.value;const r=a[e]||f(e);return r?r.value:j[e]},set(t,e,r){if(n&&"length"===e)return i((()=>{for(let t=r,e=a.length;t<e;t++)delete u[t];o.value=a.length=t.length=r})),!0;let l=!1;const c=a[e]||f(e)||(l=!0,v()),h=c.peek();return r===h||(c._set?c._set(r):Array.isArray(r)&&Array.isArray(h)?s((()=>i((()=>{let i=0,n=r.length,o=t[e];for(;i<n;i++)h[i]=o[i]=r[i];h.length=n})))):c.value=E(t[e]=r)),n?e>=o.peek()&&(o.value=a.length=t.length=Number(e)+1):l&&o.value++,!0},deleteProperty(t,e){const r=a[e];if(r){const{_del:t}=r;delete r._del,delete a[e],t?.()}return delete t[e],n||o.value--,!0}});for(let e in t)a[e]=r?.[e]??f(e);function f(e){if(t.hasOwnProperty(e)){const i=Object.getOwnPropertyDescriptor(t,e);return i?.get?((a[e]=(r=i.get.bind(u),new m(r)))._set=i.set?.bind(u),a[e]):a[e]=i.value?.peek?i.value:v(E(i.value))}var r}return u}var _=Promise.prototype.then.bind(Promise.resolve()),$={},P={};$.if=(t,e,r)=>{let i=document.createTextNode(""),n=[L(t,e,":if")],o=[t],s=t;for(;(s=t.nextElementSibling)&&s.hasAttribute(":else");)s.removeAttribute(":else"),(e=s.getAttribute(":if"))?(s.removeAttribute(":if"),s.remove(),o.push(s),n.push(L(t,e,":else :if"))):(s.remove(),o.push(s),n.push((()=>1)));t.replaceWith(s=i);const a=S((()=>{let t=n.findIndex((t=>t(r)));o[t]!=s&&((s[T]||s).replaceWith(s=o[t]||i),R(s,r))}));return()=>{for(const t of o)t[x]?.();a()}};var T=Symbol(":each");$.each=(t,e,r)=>{const n=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(!n)return q(new Error,t,e,":each");const[o,a,l]=n,u=t[T]=document.createTextNode("");t.replaceWith(u);const f=L(t,l,":each");let c,h;return S((()=>{let n,l,v=f(r),p=c?.length||0;return v?"number"==typeof v?n=Array.from({length:v},((t,e)=>e)):Array.isArray(v)?n=v:"object"==typeof v?(l=Object.keys(v),n=Object.values(v),v[N]):q(Error("Bad items value"),t,e,":each"):n=[],s((()=>i((()=>{if(c&&c[O]){let t=n.length,e=0;for(;e<t;e++)c[e]=n[e];c.length=t}else{for(let t=0;t<p;t++)h[t]?._del();c=n,h=c[O]||[],l&&(p=0)}})))),S((()=>{let e=n.length;p!==e&&s((()=>i((()=>{for(let i=p;i<e;i++){const e=l?.[i]??i,n=t.cloneNode(!0),s=E({[o]:h[i]??c[i],[a]:e},r);u.before(n),R(n,s);const{_del:f}=h[i]||={};h[i]._del=()=>{delete c[i],f?.(),n[x](),n.remove()}}p=e}))))}))})),()=>i((()=>{for(let t of h)t?._del();h.length=0,c.length=0}))},$.with=(t,e,r)=>(R(t,E(L(t,e,":with")(r),r)),t[x]),$.ref=(t,e,r)=>{r[e]=t},P.render=(t,e,r)=>{let i=L(t,e,":render")(r);i||q(new Error("Template not found"),t,e,":render");let n=i.content.cloneNode(!0);return t.replaceChildren(n),R(t,r),t[x]},P.id=(t,e,r)=>{let i=L(t,e,":id");return S((()=>{return e=i(r),t.id=e||0===e?e:"";var e}))},P.class=(t,e,r)=>{let i=L(t,e,":class"),n=t.getAttribute("class");return S((()=>{let e=i(r),o=[n];e&&("string"==typeof e?o.push(e):Array.isArray(e)?o.push(...e):o.push(...Object.entries(e).map((([t,e])=>e?t:"")))),(o=o.filter(Boolean).join(" "))?t.setAttribute("class",o):t.removeAttribute("class")}))},P.style=(t,e,r)=>{let i=L(t,e,":style"),n=t.getAttribute("style")||"";return n.endsWith(";")||(n+="; "),S((()=>{let e=i(r);"string"==typeof e?t.setAttribute("style",n+e):s((()=>{t.setAttribute("style",n);for(let r in e)"symbol"!=typeof e[r]&&t.style.setProperty(r,e[r])}))}))},P.text=(t,e,r)=>{let i=L(t,e,":text");return S((()=>{let e=i(r);t.textContent=null==e?"":e}))},P[""]=(t,e,r)=>{let i=L(t,e,":");if(i)return S((()=>{let e=i(r);for(let r in e)F(t,r.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(t=>"-"+t.toLowerCase())),e[r])}))},P.value=(t,e,r)=>{let i,n,o=L(t,e,":value"),s="text"===t.type||""===t.type?e=>t.setAttribute("value",t.value=null==e?"":e):"TEXTAREA"===t.tagName||"text"===t.type||""===t.type?e=>(i=t.selectionStart,n=t.selectionEnd,t.setAttribute("value",t.value=null==e?"":e),i&&t.setSelectionRange(i,n)):"checkbox"===t.type?e=>(t.value=e?"on":"",F(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 S((()=>{s(o(r))}))};var C=(t,e,r,i)=>{let n=i.startsWith("on")&&i.slice(2),o=L(t,e,":"+i);if(o){if(n){let e,i=S((()=>{e&&(e(),e=null);let i=o(r);i&&(e=W(t,n,i))}));return()=>(e?.(),i())}return S((()=>{F(t,i,o(r))}))}},W=(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=D[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)},D={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=>K(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=>U.ctrl(t)&&e.every((e=>U[e]?U[e](t):t.key===e)),shift:(t,...e)=>t=>U.shift(t)&&e.every((e=>U[e]?U[e](t):t.key===e)),alt:(t,...e)=>t=>U.alt(t)&&e.every((e=>U[e]?U[e](t):t.key===e)),meta:(t,...e)=>t=>U.meta(t)&&e.every((e=>U[e]?U[e](t):t.key===e)),arrow:t=>U.arrow,enter:t=>U.enter,escape:t=>U.escape,tab:t=>U.tab,space:t=>U.space,backspace:t=>U.backspace,delete:t=>U.delete,digit:t=>U.digit,letter:t=>U.letter,character:t=>U.character},U={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))},K=(t,e)=>{let r;return i=>{clearTimeout(r),r=setTimeout((()=>{r=null,t(i)}),e)}},F=(t,e,r)=>{null==r||!1===r?t.removeAttribute(e):t.setAttribute(e,!0===r?"":"number"==typeof r||"string"==typeof r?r:"")},I={};function L(t,e,r){let i=I[e];if(!i)try{i=I[e]=new Function("__scope",`with (__scope) { let __; return ${e.trim()} };`)}catch(i){return q(i,t,e,r)}return n=>{let o;try{o=i.call(t,n)}catch(i){return q(i,t,e,r)}return o}}function q(t,e,r,i){Object.assign(t,{element:e,expression:r}),console.warn(`∴ ${t.message}\n\n${i}=${r?`"${r}"\n\n`:""}`,e),_((()=>{throw t}),0)}R.globals=j;var M=new WeakMap;function R(t,e){if(!t.children)return;if(M.has(t))return i((()=>Object.assign(M.get(t),e)));const r=E(e||{}),n=[],o=(t,e=t.parentNode)=>{for(let i in $){let o=":"+i;if(t.hasAttribute?.(o)){let s=t.getAttribute(o);if(t.removeAttribute(o),n.push($[i](t,s,r,i)),M.has(t))return;if(t.parentNode!==e)return!1}}if(t.attributes)for(let e=0;e<t.attributes.length;){let i=t.attributes[e],o=i.name[0];if(":"===o||"@"===o){t.removeAttribute(i.name);let e="@"===o?`${i.value.includes("await")?"async":""} event=>{${i.value}}`:i.value,s=i.name.slice(1).split(o);for(let i of s){"@"===o&&(i="on"+i);let s=P[i]||C;n.push(s(t,e,r,i))}}else e++}for(let e,r=0;e=t.children[r];r++)!1===o(e,t)&&r--};return o(t),M.has(t)||(M.set(t,r),n.length&&Object.defineProperty(t,x,{value:()=>{for(;n.length;)n.shift()?.();M.delete(t)}})),r}document.currentScript&&R(document.documentElement)})();
package/src/core.js DELETED
@@ -1,82 +0,0 @@
1
- import createState, { batch, sandbox, _dispose } from './state.signals-proxy.js';
2
- import defaultDirective, { primary, secondary } from './directives.js';
3
-
4
-
5
- // default root sandbox
6
- sprae.globals = sandbox
7
-
8
- // sprae element: apply directives
9
- const memo = new WeakMap
10
- export default function sprae(container, values) {
11
- if (!container.children) return // ignore what?
12
-
13
- if (memo.has(container)) return batch(() => Object.assign(memo.get(container), values))
14
-
15
- // take over existing state instead of creating clone
16
- const state = createState(values || {});
17
- const disposes = []
18
-
19
- // init directives on element
20
- const init = (el, parent = el.parentNode) => {
21
- // init primary attributes first
22
- for (let name in primary) {
23
- let attrName = ':' + name
24
- if (el.hasAttribute?.(attrName)) {
25
- let expr = el.getAttribute(attrName)
26
- el.removeAttribute(attrName)
27
-
28
- disposes.push(primary[name](el, expr, state, name))
29
-
30
- // stop if element was spraed by directive or skipped (detached) like in case of :if or :each
31
- if (memo.has(el)) return
32
- if (el.parentNode !== parent) return false
33
- }
34
- }
35
-
36
- // catch other attributes as secondary
37
- if (el.attributes) {
38
- for (let i = 0; i < el.attributes.length;) {
39
- let attr = el.attributes[i], prefix = attr.name[0]
40
-
41
- if (prefix === ':' || prefix === '@') {
42
- el.removeAttribute(attr.name)
43
- let expr = prefix === '@' ? `${attr.value.includes('await') ? 'async' : ''} event=>{${attr.value}}` : attr.value,
44
- names = attr.name.slice(1).split(prefix)
45
-
46
- // multiple attributes like :id:for="" or @click@touchstart
47
- for (let name of names) {
48
- // @click forwards to :onclick=event=>{...inline}
49
- if (prefix === '@') name = `on` + name
50
- let dir = secondary[name] || defaultDirective;
51
- disposes.push(dir(el, expr, state, name));
52
- // NOTE: secondary directives don't stop flow nor extend state, so no need to check
53
- }
54
- }
55
- else i++;
56
- }
57
- }
58
-
59
- for (let i = 0, child; child = el.children[i]; i++) {
60
- // if element was removed from parent (skipped) - reduce index
61
- if (init(child, el) === false) i--
62
- }
63
- }
64
-
65
- init(container);
66
-
67
- // if element was spraed by :with or :each instruction - skip
68
- if (memo.has(container)) return state //memo.get(container)
69
-
70
- // save
71
- memo.set(container, state);
72
-
73
- // expose dispose
74
- if (disposes.length) Object.defineProperty(container, _dispose, {
75
- value: () => {
76
- while (disposes.length) disposes.shift()?.();
77
- memo.delete(container);
78
- }
79
- });
80
-
81
- return state;
82
- }
package/src/directives.js DELETED
@@ -1,447 +0,0 @@
1
- // directives & parsing
2
- import sprae from './core.js'
3
- import createState, { effect, computed, untracked, batch, _dispose, _signals, _change } from './state.signals-proxy.js'
4
- import { getFirstProperty, queueMicrotask } from './util.js'
5
-
6
- // reserved directives - order matters!
7
- // primary initialized first by selector, secondary initialized by iterating attributes
8
- export const primary = {}, secondary = {}
9
-
10
- // :if is interchangeable with :each depending on order, :if :each or :each :if have different meanings
11
- // as for :if :with - :if must init first, since it is lazy, to avoid initializing component ahead of time by :with
12
- // we consider :with={x} :if={x} case insignificant
13
- primary['if'] = (el, expr, state) => {
14
- let holder = document.createTextNode(''),
15
- clauses = [parseExpr(el, expr, ':if')],
16
- els = [el], cur = el
17
-
18
- // consume all following siblings with :else, :if into a single group
19
- while (cur = el.nextElementSibling) {
20
- if (cur.hasAttribute(':else')) {
21
- cur.removeAttribute(':else');
22
- if (expr = cur.getAttribute(':if')) {
23
- cur.removeAttribute(':if'), cur.remove();
24
- els.push(cur); clauses.push(parseExpr(el, expr, ':else :if'));
25
- }
26
- else {
27
- cur.remove(); els.push(cur); clauses.push(() => 1);
28
- }
29
- }
30
- else break;
31
- }
32
-
33
- el.replaceWith(cur = holder)
34
-
35
- const dispose = effect(() => {
36
- // find matched clause (re-evaluates any time any clause updates)
37
- let i = clauses.findIndex(f => f(state))
38
- if (els[i] != cur) {
39
- ; (cur[_each] || cur).replaceWith(cur = els[i] || holder);
40
- // NOTE: it lazily initializes elements on insertion, it's safe to sprae multiple times
41
- // but :if must come first to avoid preliminary caching
42
- sprae(cur, state);
43
- }
44
- })
45
-
46
- return () => {
47
- for (const el of els) el[_dispose]?.() // dispose all internal spraes
48
- dispose() // dispose subscription
49
- }
50
- }
51
-
52
- const _each = Symbol(':each')
53
-
54
- // :each must init before :ref, :id or any others, since it defines scope
55
- primary['each'] = (tpl, expr, state) => {
56
- const each = parseForExpression(expr);
57
- if (!each) return exprError(new Error, tpl, expr, ':each');
58
-
59
- const [itemVar, idxVar, itemsExpr] = each;
60
-
61
- // we have to handle item :ref separately if don't create substates
62
- // const refName = tpl.getAttribute(':ref') || ''
63
- // if (refName) tpl.removeAttribute(':ref')
64
-
65
- // we need :if to be able to replace holder instead of tpl for :if :each case
66
- const holder = tpl[_each] = document.createTextNode('')
67
- tpl.replaceWith(holder)
68
-
69
- const evaluate = parseExpr(tpl, itemsExpr, ':each');
70
-
71
- // we re-create items any time new items are produced
72
- let curItems, signals
73
- effect(() => {
74
- let srcItems = evaluate(state), newItems, keys
75
- let prevl = curItems?.length || 0
76
-
77
- // convert items to array
78
- if (!srcItems) newItems = []
79
- else if (typeof srcItems === 'number') newItems = Array.from({ length: srcItems }, (_, i) => i)
80
- else if (Array.isArray(srcItems)) newItems = srcItems;
81
- else if (typeof srcItems === 'object') {
82
- keys = Object.keys(srcItems);
83
- newItems = Object.values(srcItems);
84
- srcItems[_change]; // subscribe to object new props changes
85
- }
86
- else {
87
- exprError(Error('Bad items value'), tpl, expr, ':each', srcItems)
88
- }
89
-
90
- untracked(() => batch(() => {
91
- // (re)init items (if plain array/obj passed instead of proxy-state)
92
- if (!curItems || !curItems[_signals]) {
93
- // manual dispose for plain (non-state) arrays - signals here is just holder for destructors
94
- for (let i = 0; i < prevl; i++) signals[i]?._del()
95
- // NOTE: new items are initialized in length effect below;
96
- curItems = newItems, signals = curItems[_signals] || []
97
- if (keys) prevl = 0 // NOTE: objects regenerate full list each time instead of subscription
98
- }
99
- // patch existing items and insert new items - init happens in length effect
100
- else {
101
- let newl = newItems.length, i = 0
102
- for (; i < newl; i++) curItems[i] = newItems[i]
103
- curItems.length = newl // dispose tail (done internally in state)
104
- }
105
- }))
106
-
107
- // length change effect
108
- return effect(() => {
109
- let newl = newItems.length; // indicate that we track length
110
- if (prevl !== newl) untracked(() => batch(() => {
111
- // init new items
112
- for (let i = prevl; i < newl; i++) {
113
- const idx = keys?.[i] ?? i
114
- const el = tpl.cloneNode(true),
115
- // inherited object instead of substate (less memory & faster)
116
- // scope = Object.create(state, {
117
- // [itemVar]: { get() { return curItems[i] } },
118
- // [idxVar]: { value: idx },
119
- // [refName]: { value: el },
120
- // })
121
- scope = createState({
122
- [itemVar]: signals[i] ?? curItems[i],
123
- [idxVar]: idx
124
- }, state)
125
- holder.before(el)
126
- sprae(el, scope)
127
- const { _del } = (signals[i] ||= {});
128
- signals[i]._del = () => { delete curItems[i]; _del?.(); el[_dispose](), el.remove() }
129
- }
130
- prevl = newl
131
- }))
132
- })
133
- })
134
-
135
- return () => batch(() => {
136
- for (let _i of signals) _i?._del()
137
- signals.length = 0
138
- curItems.length = 0
139
- })
140
- }
141
-
142
- // `:each` can redefine scope as `:each="a in {myScope}"`,
143
- // same time per-item scope as `:each="..." :with="{collapsed:true}"` is useful
144
- primary['with'] = (el, expr, rootState) => {
145
- let evaluate = parseExpr(el, expr, ':with')
146
- const localState = evaluate(rootState)
147
- let state = createState(localState, rootState)
148
- sprae(el, state)
149
- return el[_dispose];
150
- }
151
-
152
- // ref must be last within primaries, since that must be skipped by :each, but before secondaries
153
- primary['ref'] = (el, expr, state) => {
154
- state[expr] = el;
155
- }
156
-
157
-
158
- // This was taken AlpineJS, former VueJS 2.* core. Thanks Alpine & Vue!
159
- // takes instruction like `item in list`, returns ['item', '', 'list']
160
- function parseForExpression(expression) {
161
- let forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
162
- let stripParensRE = /^\s*\(|\)\s*$/g
163
- let forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
164
- let inMatch = expression.match(forAliasRE)
165
-
166
- if (!inMatch) return
167
-
168
- let items = inMatch[2].trim()
169
- let item = inMatch[1].replace(stripParensRE, '').trim()
170
- let iteratorMatch = item.match(forIteratorRE)
171
-
172
- if (iteratorMatch) return [
173
- item.replace(forIteratorRE, '').trim(),
174
- iteratorMatch[1].trim(),
175
- items
176
- ]
177
-
178
- return [item, '', items]
179
- }
180
-
181
- secondary['render'] = (el, expr, state) => {
182
- let evaluate = parseExpr(el, expr, ':render'),
183
- tpl = evaluate(state)
184
-
185
- if (!tpl) exprError(new Error('Template not found'), el, expr, ':render');
186
-
187
- let content = tpl.content.cloneNode(true);
188
- el.replaceChildren(content)
189
- sprae(el, state)
190
- return el[_dispose]
191
- }
192
-
193
- secondary['id'] = (el, expr, state) => {
194
- let evaluate = parseExpr(el, expr, ':id')
195
- const update = v => el.id = v || v === 0 ? v : ''
196
- return effect(() => update(evaluate(state)))
197
- }
198
-
199
- secondary['class'] = (el, expr, state) => {
200
- let evaluate = parseExpr(el, expr, ':class')
201
- let initClassName = el.getAttribute('class')
202
- return effect(() => {
203
- let v = evaluate(state)
204
- let className = [initClassName]
205
- if (v) {
206
- if (typeof v === 'string') className.push(v)
207
- else if (Array.isArray(v)) className.push(...v)
208
- else className.push(...Object.entries(v).map(([k, v]) => v ? k : ''))
209
- }
210
- if (className = className.filter(Boolean).join(' ')) el.setAttribute('class', className);
211
- else el.removeAttribute('class')
212
- })
213
- }
214
-
215
- secondary['style'] = (el, expr, state) => {
216
- let evaluate = parseExpr(el, expr, ':style')
217
- let initStyle = el.getAttribute('style') || ''
218
- if (!initStyle.endsWith(';')) initStyle += '; '
219
- return effect(() => {
220
- let v = evaluate(state)
221
- if (typeof v === 'string') el.setAttribute('style', initStyle + v)
222
- else {
223
- untracked(() => {
224
- el.setAttribute('style', initStyle)
225
- for (let k in v) if (typeof v[k] !== 'symbol') el.style.setProperty(k, v[k])
226
- })
227
- }
228
- })
229
- }
230
-
231
- secondary['text'] = (el, expr, state) => {
232
- let evaluate = parseExpr(el, expr, ':text')
233
- return effect(() => {
234
- let value = evaluate(state)
235
- el.textContent = value == null ? '' : value;
236
- })
237
- }
238
-
239
- // set props in-bulk or run effect
240
- secondary[''] = (el, expr, state) => {
241
- let evaluate = parseExpr(el, expr, ':')
242
- if (evaluate) return effect(() => {
243
- let value = evaluate(state)
244
- for (let key in value) attr(el, dashcase(key), value[key]);
245
- })
246
- }
247
-
248
- // connect expr to element value
249
- secondary['value'] = (el, expr, state) => {
250
- let evaluate = parseExpr(el, expr, ':value')
251
-
252
- let from, to
253
- let update = (
254
- el.type === 'text' || el.type === '' ? value => el.setAttribute('value', el.value = value == null ? '' : value) :
255
- el.tagName === 'TEXTAREA' || el.type === 'text' || el.type === '' ? value => (
256
- // we retain selection in input
257
- from = el.selectionStart, to = el.selectionEnd,
258
- el.setAttribute('value', el.value = value == null ? '' : value),
259
- from && el.setSelectionRange(from, to)
260
- ) :
261
- el.type === 'checkbox' ? value => (el.value = value ? 'on' : '', attr(el, 'checked', value)) :
262
- el.type === 'select-one' ? value => {
263
- for (let option in el.options) option.removeAttribute('selected')
264
- el.value = value;
265
- el.selectedOptions[0]?.setAttribute('selected', '')
266
- } :
267
- value => el.value = value
268
- )
269
-
270
- return effect(() => { update(evaluate(state)) })
271
- }
272
-
273
- // any unknown directive
274
- export default (el, expr, state, name) => {
275
- let evt = name.startsWith('on') && name.slice(2)
276
- let evaluate = parseExpr(el, expr, ':' + name)
277
-
278
- if (!evaluate) return
279
-
280
- if (evt) {
281
- let off, dispose = effect(() => {
282
- if (off) off(), off = null
283
- // we need anonymous callback to enable modifiers like prevent
284
- let value = evaluate(state)
285
- if (value) off = on(el, evt, value)
286
- })
287
- return () => (off?.(), dispose())
288
- }
289
-
290
- return effect(() => { attr(el, name, evaluate(state)) })
291
- }
292
-
293
- // bind event to a target
294
- const on = (el, e, fn) => {
295
- if (!fn) return
296
-
297
- const ctx = { evt: '', target: el, test: () => true };
298
-
299
- // onevt.debounce-108 -> evt.debounce-108
300
- ctx.evt = (e.startsWith('on') ? e.slice(2) : e).replace(/\.(\w+)?-?([-\w]+)?/g,
301
- (match, mod, param = '') => (ctx.test = mods[mod]?.(ctx, ...param.split('-')) || ctx.test, '')
302
- );
303
-
304
- // add listener applying the context
305
- const { evt, target, test, defer, stop, prevent, ...opts } = ctx;
306
-
307
- if (defer) fn = defer(fn)
308
-
309
- const cb = e => test(e) && (
310
- stop && e.stopPropagation(),
311
- prevent && e.preventDefault(),
312
- fn.call(target, e)
313
- )
314
-
315
- target.addEventListener(evt, cb, opts)
316
-
317
- // return off
318
- return () => (target.removeEventListener(evt, cb, opts))
319
- }
320
-
321
- // event modifiers
322
- const mods = {
323
- // actions
324
- prevent(ctx) { ctx.prevent = true },
325
- stop(ctx) { ctx.stop = true },
326
-
327
- // options
328
- once(ctx) { ctx.once = true; },
329
- passive(ctx) { ctx.passive = true; },
330
- capture(ctx) { ctx.capture = true; },
331
-
332
- // target
333
- window(ctx) { ctx.target = window },
334
- document(ctx) { ctx.target = document },
335
-
336
- throttle(ctx, limit) { ctx.defer = fn => throttle(fn, limit ? Number(limit) || 0 : 108) },
337
- debounce(ctx, wait) { ctx.defer = fn => debounce(fn, wait ? Number(wait) || 0 : 108) },
338
-
339
- // test
340
- outside: ctx => e => {
341
- let target = ctx.target
342
- if (target.contains(e.target)) return false
343
- if (e.target.isConnected === false) return false
344
- if (target.offsetWidth < 1 && target.offsetHeight < 1) return false
345
- return true
346
- },
347
- self: ctx => e => e.target === ctx.target,
348
-
349
- // keyboard
350
- ctrl: (ctx, ...param) => e => keys.ctrl(e) && param.every(p => keys[p] ? keys[p](e) : e.key === p),
351
- shift: (ctx, ...param) => e => keys.shift(e) && param.every(p => keys[p] ? keys[p](e) : e.key === p),
352
- alt: (ctx, ...param) => e => keys.alt(e) && param.every(p => keys[p] ? keys[p](e) : e.key === p),
353
- meta: (ctx, ...param) => e => keys.meta(e) && param.every(p => keys[p] ? keys[p](e) : e.key === p),
354
- arrow: ctx => keys.arrow,
355
- enter: ctx => keys.enter,
356
- escape: ctx => keys.escape,
357
- tab: ctx => keys.tab,
358
- space: ctx => keys.space,
359
- backspace: ctx => keys.backspace,
360
- delete: ctx => keys.delete,
361
- digit: ctx => keys.digit,
362
- letter: ctx => keys.letter,
363
- character: ctx => keys.character,
364
- };
365
-
366
- // key testers
367
- const keys = {
368
- ctrl: e => e.ctrlKey || e.key === 'Control' || e.key === 'Ctrl',
369
- shift: e => e.shiftKey || e.key === 'Shift',
370
- alt: e => e.altKey || e.key === 'Alt',
371
- meta: e => e.metaKey || e.key === 'Meta' || e.key === 'Command',
372
- arrow: e => e.key.startsWith('Arrow'),
373
- enter: e => e.key === 'Enter',
374
- escape: e => e.key.startsWith('Esc'),
375
- tab: e => e.key === 'Tab',
376
- space: e => e.key === ' ' || e.key === 'Space' || e.key === ' ',
377
- backspace: e => e.key === 'Backspace',
378
- delete: e => e.key === 'Delete',
379
- digit: e => /^\d$/.test(e.key),
380
- letter: e => /^[a-zA-Z]$/.test(e.key),
381
- character: e => /^\S$/.test(e.key)
382
- }
383
-
384
- // create delayed fns
385
- const throttle = (fn, limit) => {
386
- let pause, planned, block = (e) => {
387
- pause = true
388
- setTimeout(() => {
389
- pause = false
390
- // if event happened during blocked time, it schedules call by the end
391
- if (planned) return (planned = false, block(e), fn(e))
392
- }, limit)
393
- }
394
- return (e) => {
395
- if (pause) return (planned = true)
396
- block(e);
397
- return fn(e);
398
- }
399
- }
400
- const debounce = (fn, wait) => {
401
- let timeout
402
- return (e) => {
403
- clearTimeout(timeout)
404
- timeout = setTimeout(() => { timeout = null; fn(e) }, wait)
405
- }
406
- }
407
-
408
- // set attr
409
- const attr = (el, name, v) => {
410
- if (v == null || v === false) el.removeAttribute(name)
411
- else el.setAttribute(name, v === true ? '' : (typeof v === 'number' || typeof v === 'string') ? v : '')
412
- }
413
-
414
-
415
- // borrowed from alpine with improvements https://github.com/alpinejs/alpine/blob/main/packages/alpinejs/src/evaluator.js#L61
416
- // it seems to be more robust than subscript
417
- let evaluatorMemo = {}
418
- function parseExpr(el, expression, dir) {
419
- // guard static-time eval errors
420
- let evaluate = evaluatorMemo[expression]
421
-
422
- if (!evaluate) {
423
- try {
424
- evaluate = evaluatorMemo[expression] = new Function(`__scope`, `with (__scope) { let __; return ${expression.trim()} };`)
425
- } catch (e) {
426
- return exprError(e, el, expression, dir)
427
- }
428
- }
429
-
430
- // guard runtime eval errors
431
- return (state) => {
432
- let result
433
- try { result = evaluate.call(el, state); }
434
- catch (e) { return exprError(e, el, expression, dir) }
435
- return result
436
- }
437
- }
438
-
439
- function exprError(error, element, expression, directive) {
440
- Object.assign(error, { element, expression })
441
- console.warn(`∴ ${error.message}\n\n${directive}=${expression ? `"${expression}"\n\n` : ''}`, element)
442
- queueMicrotask(() => { throw error }, 0)
443
- }
444
-
445
- function dashcase(str) {
446
- return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => '-' + match.toLowerCase());
447
- };
package/src/domdiff.js DELETED
@@ -1,71 +0,0 @@
1
- // https://github.com/luwes/js-diff-benchmark/blob/master/libs/list-difference.js
2
- // this implementation is more persistent in terms of preserving in-between nodes
3
- // also it handles _dispose
4
-
5
- // a is old list, b is the new
6
- export default function (parent, a, b, before) {
7
- const aIdx = new Map();
8
- const bIdx = new Map();
9
- let i;
10
- let j;
11
-
12
- // Create a mapping from keys to their position in the new list
13
- for (i = 0; i < b.length; i++) {
14
- bIdx.set(b[i], i);
15
- }
16
-
17
- // Create a mapping from keys to their position in the old list
18
- for (i = 0; i < a.length; i++) {
19
- aIdx.set(a[i], i);
20
- // dispose a[i] if is going to disappear
21
- if (!bIdx.has(a[i])) a[i][Symbol.dispose]?.()
22
- }
23
-
24
- for (i = j = 0; i !== a.length || j !== b.length;) {
25
- var aElm = a[i], bElm = b[j];
26
- if (aElm === null) {
27
- // This is a element that has been moved to earlier in the list
28
- i++;
29
- } else if (b.length <= j) {
30
- // No more elements in new, this is a delete
31
- parent.removeChild(a[i]);
32
- i++;
33
- } else if (a.length <= i) {
34
- // No more elements in old, this is an addition
35
- parent.insertBefore(bElm, a[i] || before);
36
- j++;
37
- } else if (aElm === bElm) {
38
- // No difference, we move on
39
- i++; j++;
40
- } else {
41
- // Look for the current element at this location in the new list
42
- // This gives us the idx of where this element should be
43
- var curElmInNew = bIdx.get(aElm);
44
- // Look for the the wanted elment at this location in the old list
45
- // This gives us the idx of where the wanted element is now
46
- var wantedElmInOld = aIdx.get(bElm);
47
- if (curElmInNew === undefined) {
48
- // Current element is not in new list, it has been removed
49
- parent.removeChild(a[i]);
50
- i++;
51
- } else if (wantedElmInOld === undefined) {
52
- // New element is not in old list, it has been added
53
- parent.insertBefore(
54
- bElm,
55
- a[i] || before
56
- );
57
- j++;
58
- } else {
59
- // Element is in both lists, it has been moved
60
- parent.insertBefore(
61
- a[wantedElmInOld],
62
- a[i] || before
63
- );
64
- a[wantedElmInOld] = null;
65
- if (wantedElmInOld > i + 1) i++;
66
- j++;
67
- }
68
- }
69
- }
70
- return b;
71
- };
package/src/index.js DELETED
@@ -1,6 +0,0 @@
1
- import sprae from './core.js';
2
- import './directives.js';
3
- export default sprae;
4
-
5
- // autoinit
6
- if (document.currentScript) sprae(document.documentElement)