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.
- package/core.js +129 -0
- package/directive/aria.js +10 -0
- package/directive/class.js +17 -0
- package/directive/data.js +10 -0
- package/directive/default.js +148 -0
- package/directive/each.js +61 -0
- package/directive/fx.js +6 -0
- package/directive/html.js +11 -0
- package/directive/if.js +40 -0
- package/directive/ref.js +10 -0
- package/directive/scope.js +11 -0
- package/directive/style.js +16 -0
- package/directive/text.js +12 -0
- package/directive/value.js +31 -0
- package/dist/sprae.js +636 -860
- package/dist/sprae.min.js +1 -1
- package/package.json +19 -13
- package/readme.md +274 -175
- package/sprae.js +14 -0
- package/dist/sprae.auto.js +0 -976
- package/dist/sprae.auto.min.js +0 -1
- package/src/core.js +0 -82
- package/src/directives.js +0 -447
- package/src/domdiff.js +0 -71
- package/src/index.js +0 -6
- package/src/state.proxy.js +0 -150
- package/src/state.signals-proxy.js +0 -151
- package/src/state.signals.js +0 -82
- package/src/util.js +0 -13
package/dist/sprae.auto.min.js
DELETED
|
@@ -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
|
-
};
|