sprae 10.7.0 → 10.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/core.js CHANGED
@@ -12,8 +12,8 @@ export const memo = new WeakMap();
12
12
 
13
13
  // sprae element: apply directives
14
14
  export default function sprae(el, values) {
15
- // text nodes, comments etc - but collections are fine
16
- if (!el?.children) return
15
+ // text nodes, comments etc
16
+ if (!el?.childNodes) return
17
17
 
18
18
  // repeated call can be caused by :each with new objects with old keys needs an update
19
19
  if (memo.has(el)) {
@@ -38,35 +38,35 @@ export default function sprae(el, values) {
38
38
  return state;
39
39
 
40
40
  function init(el, parent = el.parentNode) {
41
- if (el.attributes) {
42
- // init generic-name attributes second
43
- for (let i = 0; i < el.attributes.length;) {
44
- let attr = el.attributes[i];
45
-
46
- if (attr.name[0] === ':') {
47
- el.removeAttribute(attr.name);
48
-
49
- // multiple attributes like :id:for=""
50
- let names = attr.name.slice(1).split(':')
51
-
52
- // NOTE: secondary directives don't stop flow nor extend state, so no need to check
53
- for (let name of names) {
54
- let dir = directive[name] || directive.default
55
- let evaluate = (dir.parse || parse)(attr.value, parse)
56
- let dispose = dir(el, evaluate, state, name);
57
- if (dispose) disposes.push(dispose);
58
- }
59
-
60
- // stop if element was spraed by internal directive
61
- if (memo.has(el)) return el[_dispose] && disposes.push(el[_dispose])
62
-
63
- // stop if element is skipped (detached) like in case of :if or :each
64
- if (el.parentNode !== parent) return
65
- } else i++;
66
- }
41
+ if (!el.childNodes) return // ignore text nodes, comments etc
42
+
43
+ // init generic-name attributes second
44
+ for (let i = 0; i < el.attributes?.length;) {
45
+ let attr = el.attributes[i];
46
+
47
+ if (attr.name[0] === ':') {
48
+ el.removeAttribute(attr.name);
49
+
50
+ // multiple attributes like :id:for=""
51
+ let names = attr.name.slice(1).split(':')
52
+
53
+ // NOTE: secondary directives don't stop flow nor extend state, so no need to check
54
+ for (let name of names) {
55
+ let dir = directive[name] || directive.default
56
+ let evaluate = (dir.parse || parse)(attr.value, parse)
57
+ let dispose = dir(el, evaluate, state, name);
58
+ if (dispose) disposes.push(dispose);
59
+ }
60
+
61
+ // stop if element was spraed by internal directive
62
+ if (memo.has(el)) return el[_dispose] && disposes.push(el[_dispose])
63
+
64
+ // stop if element is skipped (detached) like in case of :if or :each
65
+ if (el.parentNode !== parent) return
66
+ } else i++;
67
67
  }
68
68
 
69
- for (let child of [...el.children]) init(child, el);
69
+ for (let child of [...el.childNodes]) init(child, el);
70
70
  };
71
71
  }
72
72
 
@@ -97,3 +97,28 @@ sprae.use = s => {
97
97
  s.signal && use(s);
98
98
  s.compile && (compile = s.compile);
99
99
  }
100
+
101
+
102
+ // instantiated <template> fragment holder, like persisting fragment but with minimal API surface
103
+ export const frag = (tpl) => {
104
+ if (!tpl.nodeType) return tpl // existing tpl
105
+
106
+ // ensure at least one node
107
+ tpl.content.appendChild(document.createTextNode(''))
108
+
109
+ let content = tpl.content.cloneNode(true),
110
+ attributes = [...tpl.attributes],
111
+ childNodes = [...content.childNodes]
112
+
113
+ return {
114
+ childNodes,
115
+ content,
116
+ remove: () => content.append(...childNodes),
117
+ replaceWith(el) {
118
+ childNodes[0].before(el)
119
+ content.append(...childNodes)
120
+ },
121
+ attributes,
122
+ removeAttribute(name) { attributes.splice(attributes.findIndex(a => a.name === name), 1) }
123
+ }
124
+ }
package/directive/each.js CHANGED
@@ -1,13 +1,11 @@
1
- import sprae, { directive } from "../core.js";
1
+ import sprae, { directive, frag } from "../core.js";
2
2
  import store, { _change, _signals } from "../store.js";
3
3
  import { effect, untracked, computed } from '../signal.js';
4
4
 
5
5
 
6
- export const _each = Symbol(":each");
7
-
8
6
  directive.each = (tpl, [itemVar, idxVar, evaluate], state) => {
9
7
  // we need :if to be able to replace holder instead of tpl for :if :each case
10
- const holder = (tpl[_each] = document.createTextNode(""));
8
+ const holder = (document.createTextNode(""));
11
9
  tpl.replaceWith(holder);
12
10
 
13
11
  // we re-create items any time new items are produced
@@ -58,18 +56,14 @@ directive.each = (tpl, [itemVar, idxVar, evaluate], state) => {
58
56
  [itemVar]: cur[_signals]?.[idx] || cur[idx],
59
57
  [idxVar]: keys ? keys[idx] : idx
60
58
  }, state),
61
- el = (tpl.content || tpl).cloneNode(true),
62
- frag = tpl.content ?
63
- // fake fragment to allow sprae multiple elements
64
- { children: [...el.children], remove() { this.children.map(el => el.remove()) } } :
65
- el;
59
+ el = tpl.content ? frag(tpl) : tpl.cloneNode(true);
66
60
 
67
- holder.before(el);
68
- sprae(frag, scope);
61
+ holder.before(el.content || el);
62
+ sprae(el, scope);
69
63
 
70
64
  // signal/holder disposal removes element
71
65
  ((cur[_signals] ||= [])[i] ||= {})[Symbol.dispose] = () => {
72
- frag[Symbol.dispose](), frag.remove()
66
+ el[Symbol.dispose](), el.remove()
73
67
  };
74
68
  }
75
69
  }
package/directive/if.js CHANGED
@@ -1,5 +1,4 @@
1
- import sprae, { directive, memo } from "../core.js";
2
- import { _each } from './each.js';
1
+ import sprae, { directive, memo, frag } from "../core.js";
3
2
  import { effect } from "../signal.js";
4
3
 
5
4
  // :if is interchangeable with :each depending on order, :if :each or :each :if have different meanings
@@ -7,24 +6,21 @@ import { effect } from "../signal.js";
7
6
  // we consider :with={x} :if={x} case insignificant
8
7
  const _prevIf = Symbol("if");
9
8
  directive.if = (ifEl, evaluate, state) => {
10
- let parent = ifEl.parentNode,
11
- next = ifEl.nextElementSibling,
9
+ let next = ifEl.nextElementSibling,
12
10
  holder = document.createTextNode(''),
13
11
 
14
12
  // actual replaceable els (takes <template>)
15
- cur, ifs, elses, none = [];
13
+ none = [], cur = none, ifs, elses;
16
14
 
17
- ifEl.after(holder) // mark end of modifying section
15
+ ifEl.replaceWith(holder)
18
16
 
19
- ifEl.remove(), cur = none
20
-
21
- ifs = ifEl.content ? [...ifEl.content.childNodes] : [ifEl]
17
+ ifs = ifEl.content ? [frag(ifEl)] : [ifEl]
22
18
 
23
19
  if (next?.hasAttribute(":else")) {
24
20
  next.removeAttribute(":else");
25
21
  // if next is :else :if - leave it for its own :if handler
26
22
  if (next.hasAttribute(":if")) elses = none;
27
- else next.remove(), elses = next.content ? [...next.content.childNodes] : [next];
23
+ else next.remove(), elses = next.content ? [frag(next)] : [next];
28
24
  } else elses = none;
29
25
 
30
26
  // we mark all els as fake-spraed, because we have to sprae for real on insert
@@ -34,13 +30,10 @@ directive.if = (ifEl, evaluate, state) => {
34
30
  const newEls = evaluate(state) ? ifs : ifEl[_prevIf] ? none : elses;
35
31
  if (next) next[_prevIf] = newEls === ifs
36
32
  if (cur != newEls) {
37
- // :if :each
38
- if (cur[0]?.[_each]) cur = [cur[0][_each]]
39
-
40
33
  for (let el of cur) el.remove();
41
34
  cur = newEls;
42
35
  for (let el of cur) {
43
- parent.insertBefore(el, holder)
36
+ holder.before(el.content || el)
44
37
  memo.get(el) === null && memo.delete(el) // remove fake memo to sprae as new
45
38
  sprae(el, state)
46
39
  }
package/directive/text.js CHANGED
@@ -1,9 +1,14 @@
1
- import { directive } from "../core.js";
1
+ import { directive, frag } from "../core.js";
2
2
  import { effect } from "../signal.js";
3
3
 
4
4
  // set text content
5
5
  directive.text = (el, evaluate, state) => {
6
- if (el.content) el.replaceWith(el = document.createTextNode('')) // <template :text="abc"/>
6
+ // <template :text="a"/> or previously initialized template
7
+ if (el.content) {
8
+ let tplfrag = frag(el)
9
+ if (el !== tplfrag) el.replaceWith(tplfrag.content);
10
+ el = tplfrag.childNodes[0];
11
+ }
7
12
 
8
13
  return effect(() => {
9
14
  let value = evaluate(state);
package/dist/sprae.js CHANGED
@@ -126,7 +126,7 @@ var _dispose = Symbol.dispose ||= Symbol("dispose");
126
126
  var directive = {};
127
127
  var memo = /* @__PURE__ */ new WeakMap();
128
128
  function sprae(el, values) {
129
- if (!el?.children)
129
+ if (!el?.childNodes)
130
130
  return;
131
131
  if (memo.has(el)) {
132
132
  return Object.assign(memo.get(el), values);
@@ -142,28 +142,28 @@ function sprae(el, values) {
142
142
  };
143
143
  return state;
144
144
  function init(el2, parent = el2.parentNode) {
145
- if (el2.attributes) {
146
- for (let i = 0; i < el2.attributes.length; ) {
147
- let attr2 = el2.attributes[i];
148
- if (attr2.name[0] === ":") {
149
- el2.removeAttribute(attr2.name);
150
- let names = attr2.name.slice(1).split(":");
151
- for (let name of names) {
152
- let dir = directive[name] || directive.default;
153
- let evaluate = (dir.parse || parse)(attr2.value, parse);
154
- let dispose = dir(el2, evaluate, state, name);
155
- if (dispose)
156
- disposes.push(dispose);
157
- }
158
- if (memo.has(el2))
159
- return el2[_dispose] && disposes.push(el2[_dispose]);
160
- if (el2.parentNode !== parent)
161
- return;
162
- } else
163
- i++;
164
- }
145
+ if (!el2.childNodes)
146
+ return;
147
+ for (let i = 0; i < el2.attributes?.length; ) {
148
+ let attr2 = el2.attributes[i];
149
+ if (attr2.name[0] === ":") {
150
+ el2.removeAttribute(attr2.name);
151
+ let names = attr2.name.slice(1).split(":");
152
+ for (let name of names) {
153
+ let dir = directive[name] || directive.default;
154
+ let evaluate = (dir.parse || parse)(attr2.value, parse);
155
+ let dispose = dir(el2, evaluate, state, name);
156
+ if (dispose)
157
+ disposes.push(dispose);
158
+ }
159
+ if (memo.has(el2))
160
+ return el2[_dispose] && disposes.push(el2[_dispose]);
161
+ if (el2.parentNode !== parent)
162
+ return;
163
+ } else
164
+ i++;
165
165
  }
166
- for (let child of [...el2.children])
166
+ for (let child of [...el2.childNodes])
167
167
  init(child, el2);
168
168
  }
169
169
  ;
@@ -191,6 +191,25 @@ sprae.use = (s) => {
191
191
  s.signal && use(s);
192
192
  s.compile && (compile = s.compile);
193
193
  };
194
+ var frag = (tpl) => {
195
+ if (!tpl.nodeType)
196
+ return tpl;
197
+ tpl.content.appendChild(document.createTextNode(""));
198
+ let content = tpl.content.cloneNode(true), attributes = [...tpl.attributes], childNodes = [...content.childNodes];
199
+ return {
200
+ childNodes,
201
+ content,
202
+ remove: () => content.append(...childNodes),
203
+ replaceWith(el) {
204
+ childNodes[0].before(el);
205
+ content.append(...childNodes);
206
+ },
207
+ attributes,
208
+ removeAttribute(name) {
209
+ attributes.splice(attributes.findIndex((a) => a.name === name), 1);
210
+ }
211
+ };
212
+ };
194
213
 
195
214
  // node_modules/ulive/dist/ulive.es.js
196
215
  var ulive_es_exports = {};
@@ -256,10 +275,42 @@ var batch2 = (fn) => {
256
275
  };
257
276
  var untracked2 = (fn, prev, v) => (prev = current, current = null, v = fn(), current = prev, v);
258
277
 
278
+ // directive/if.js
279
+ var _prevIf = Symbol("if");
280
+ directive.if = (ifEl, evaluate, state) => {
281
+ let next = ifEl.nextElementSibling, holder = document.createTextNode(""), none = [], cur = none, ifs, elses;
282
+ ifEl.replaceWith(holder);
283
+ ifs = ifEl.content ? [frag(ifEl)] : [ifEl];
284
+ if (next?.hasAttribute(":else")) {
285
+ next.removeAttribute(":else");
286
+ if (next.hasAttribute(":if"))
287
+ elses = none;
288
+ else
289
+ next.remove(), elses = next.content ? [frag(next)] : [next];
290
+ } else
291
+ elses = none;
292
+ for (let el of [...ifs, ...elses])
293
+ memo.set(el, null);
294
+ return effect(() => {
295
+ const newEls = evaluate(state) ? ifs : ifEl[_prevIf] ? none : elses;
296
+ if (next)
297
+ next[_prevIf] = newEls === ifs;
298
+ if (cur != newEls) {
299
+ for (let el of cur)
300
+ el.remove();
301
+ cur = newEls;
302
+ for (let el of cur) {
303
+ holder.before(el.content || el);
304
+ memo.get(el) === null && memo.delete(el);
305
+ sprae(el, state);
306
+ }
307
+ }
308
+ });
309
+ };
310
+
259
311
  // directive/each.js
260
- var _each = Symbol(":each");
261
312
  directive.each = (tpl, [itemVar, idxVar, evaluate], state) => {
262
- const holder = tpl[_each] = document.createTextNode("");
313
+ const holder = document.createTextNode("");
263
314
  tpl.replaceWith(holder);
264
315
  let cur, keys2, prevl = 0;
265
316
  const items = computed(() => {
@@ -295,13 +346,11 @@ directive.each = (tpl, [itemVar, idxVar, evaluate], state) => {
295
346
  let idx = i, scope = store({
296
347
  [itemVar]: cur[_signals]?.[idx] || cur[idx],
297
348
  [idxVar]: keys2 ? keys2[idx] : idx
298
- }, state), el = (tpl.content || tpl).cloneNode(true), frag = tpl.content ? { children: [...el.children], remove() {
299
- this.children.map((el2) => el2.remove());
300
- } } : el;
301
- holder.before(el);
302
- sprae(frag, scope);
349
+ }, state), el = tpl.content ? frag(tpl) : tpl.cloneNode(true);
350
+ holder.before(el.content || el);
351
+ sprae(el, scope);
303
352
  ((cur[_signals] ||= [])[i] ||= {})[Symbol.dispose] = () => {
304
- frag[Symbol.dispose](), frag.remove();
353
+ el[Symbol.dispose](), el.remove();
305
354
  };
306
355
  }
307
356
  }
@@ -324,42 +373,6 @@ directive.each.parse = (expr, parse2) => {
324
373
  return [itemVar, idxVar, parse2(itemsExpr)];
325
374
  };
326
375
 
327
- // directive/if.js
328
- var _prevIf = Symbol("if");
329
- directive.if = (ifEl, evaluate, state) => {
330
- let parent = ifEl.parentNode, next = ifEl.nextElementSibling, holder = document.createTextNode(""), cur, ifs, elses, none = [];
331
- ifEl.after(holder);
332
- ifEl.remove(), cur = none;
333
- ifs = ifEl.content ? [...ifEl.content.childNodes] : [ifEl];
334
- if (next?.hasAttribute(":else")) {
335
- next.removeAttribute(":else");
336
- if (next.hasAttribute(":if"))
337
- elses = none;
338
- else
339
- next.remove(), elses = next.content ? [...next.content.childNodes] : [next];
340
- } else
341
- elses = none;
342
- for (let el of [...ifs, ...elses])
343
- memo.set(el, null);
344
- return effect(() => {
345
- const newEls = evaluate(state) ? ifs : ifEl[_prevIf] ? none : elses;
346
- if (next)
347
- next[_prevIf] = newEls === ifs;
348
- if (cur != newEls) {
349
- if (cur[0]?.[_each])
350
- cur = [cur[0][_each]];
351
- for (let el of cur)
352
- el.remove();
353
- cur = newEls;
354
- for (let el of cur) {
355
- parent.insertBefore(el, holder);
356
- memo.get(el) === null && memo.delete(el);
357
- sprae(el, state);
358
- }
359
- }
360
- });
361
- };
362
-
363
376
  // directive/ref.js
364
377
  directive.ref = (el, expr, state) => {
365
378
  state[expr] = el;
@@ -387,8 +400,12 @@ directive.html = (el, evaluate, state) => {
387
400
 
388
401
  // directive/text.js
389
402
  directive.text = (el, evaluate, state) => {
390
- if (el.content)
391
- el.replaceWith(el = document.createTextNode(""));
403
+ if (el.content) {
404
+ let tplfrag = frag(el);
405
+ if (el !== tplfrag)
406
+ el.replaceWith(tplfrag.content);
407
+ el = tplfrag.childNodes[0];
408
+ }
392
409
  return effect(() => {
393
410
  let value = evaluate(state);
394
411
  el.textContent = value == null ? "" : value;
package/dist/sprae.min.js CHANGED
@@ -1 +1 @@
1
- var e,t,r,l,n,s=Object.defineProperty,o=Symbol("signals"),a=Symbol("length");function i(t,r){if(!t)return t;if(t[o])return t;if(Array.isArray(t))return function(t){let r;if(t[o])return t;let l=e(t.length),n=Array(t.length).fill();const s=new Proxy(n,{get:(s,c)=>"symbol"==typeof c?c===a?l:c===o?n:n[c]:"length"===c?u[r]?l.peek():l.value:(r=c,n[c]?n[c].valueOf():c<n.length?(n[c]=e(i(t[c]))).value:void 0),set(e,t,r){if("length"===t){for(let e=r,t=n.length;e<t;e++)delete s[e];return l.value=n.length=r,!0}return c(n,t,r),t>=l.peek()&&(l.value=n.length=Number(t)+1),!0},deleteProperty:(e,t)=>(n[t]&&f(n,t),1)});return s}(t);if(t.constructor!==Object)return t;let l={...r?.[o]},s=e(Object.values(t).length);const p=new Proxy(l,{get:(e,t)=>t===a?s:t===o?l:l[t]?.valueOf(),set:(e,t,r,n)=>(n=l[t],c(l,t,r),n??++s.value,1),deleteProperty:(e,t)=>(l[t]&&(f(l,t),s.value--),1),ownKeys:()=>(s.value,Reflect.ownKeys(l))});for(let e in t){const r=Object.getOwnPropertyDescriptor(t,e);r?.get?(l[e]=n(r.get.bind(p)))._set=r.set?.bind(p):(l[e]=void 0,c(l,e,t[e]))}return p}var u={push:1,pop:1,shift:1,unshift:1,splice:1};function c(t,n,s){let o=t[n];if("_"===n[0])t[n]=s;else if(o)if(s===o.peek());else if(o._set)o._set(s);else if(Array.isArray(s)&&Array.isArray(o.peek())){const e=o.peek();e[a]?r((()=>{l((()=>{let t=0,r=s.length;for(;t<r;t++)e[t]=s[t];e.length=r}))})):o.value=s}else o.value=i(s);else t[n]=o=s?.peek?s:e(i(s))}function f(e,t){const r=e[t],l=r[Symbol.dispose];l&&delete r[Symbol.dispose],delete e[t],l?.()}var p=Symbol.dispose||=Symbol("dispose"),d={},y=new WeakMap;function h(e,t){if(!e?.children)return;if(y.has(e))return Object.assign(y.get(e),t);const r=i(t||{}),l=[];return function e(t,n=t.parentNode){if(t.attributes)for(let e=0;e<t.attributes.length;){let s=t.attributes[e];if(":"===s.name[0]){t.removeAttribute(s.name);let e=s.name.slice(1).split(":");for(let n of e){let e=d[n]||d.default,o=e(t,(e.parse||g)(s.value,g),r,n);o&&l.push(o)}if(y.has(t))return t[p]&&l.push(t[p]);if(t.parentNode!==n)return}else e++}for(let r of[...t.children])e(r,t)}(e),y.has(e)||y.set(e,r),e[p]=()=>{for(;l.length;)l.pop()();y.delete(e)},r}var m,v={},g=(e,t,r)=>{if(r=v[e=e.trim()])return r;try{r=m(e)}catch(r){b(r,t,e)}return v[e]=r},b=(e,t,r="")=>{throw Object.assign(e,{message:`∴ ${e.message}\n\n${t}${r?`="${r}"\n\n`:""}`,expr:r})};h.use=s=>{s.signal&&function(s){e=s.signal,t=s.effect,n=s.computed,l=s.batch||(e=>e()),r=s.untracked||l}(s),s.compile&&(m=s.compile)};var k,A,S={};((e,t)=>{for(var r in t)s(e,r,{get:t[r],enumerable:!0})})(S,{batch:()=>x,computed:()=>N,effect:()=>O,signal:()=>w,untracked:()=>j});var w=(e,t,r=new Set)=>((t={get value(){return k?.deps.push(r.add(k)),e},set value(t){if(t!==e){e=t;for(let e of r)A?A.add(e):e()}},peek:()=>e}).toJSON=t.then=t.toString=t.valueOf=()=>t.value,t),O=(e,t,r,l)=>(l=(r=l=>{t?.call?.(),l=k,k=r;try{t=e()}finally{k=l}}).deps=[],r(),e=>{for(t?.call?.();e=l.pop();)e.delete(r)}),N=(e,t=w(),r,l)=>((r={get value(){return l||=O((()=>t.value=e())),t.value},peek:t.peek}).toJSON=r.then=r.toString=r.valueOf=()=>r.value,r),x=e=>{let t=A;t||(A=new Set);try{e()}finally{if(!t){t=A,A=null;for(const e of t)e()}}},j=(e,t,r)=>(t=k,k=null,r=e(),k=t,r),$=Symbol(":each");d.each=(e,[l,s,u],c)=>{const f=e[$]=document.createTextNode("");e.replaceWith(f);let p,d,y=0;const m=n((()=>{d=null;let e=u(c);return"number"==typeof e&&(e=Array.from({length:e},((e,t)=>t+1))),e?.constructor===Object&&(d=Object.keys(e),e=Object.values(e)),e||[]})),v=()=>{r((()=>{let t=0,r=m.value,n=r.length;if(p&&!p[a]){for(let e of p[o]||[])e[Symbol.dispose]();p=null,y=0}if(n<y)p.length=n;else{if(p)for(;t<y;t++)p[t]=r[t];else p=r;for(;t<n;t++){p[t]=r[t];let n=t,a=i({[l]:p[o]?.[n]||p[n],[s]:d?d[n]:n},c),u=(e.content||e).cloneNode(!0),y=e.content?{children:[...u.children],remove(){this.children.map((e=>e.remove()))}}:u;f.before(u),h(y,a),((p[o]||=[])[t]||={})[Symbol.dispose]=()=>{y[Symbol.dispose](),y.remove()}}}y=n}))};let g=0;return t((()=>{m.value[a]?.value,g?g++:(v(),queueMicrotask((()=>(g&&v(),g=0))))}))},d.each.parse=(e,t)=>{let[r,l]=e.split(/\s+in\s+/),[n,s="$"]=r.split(/\s*,\s*/);return[n,s,t(l)]};var E=Symbol("if");d.if=(e,r,l)=>{let n,s,o,a=e.parentNode,i=e.nextElementSibling,u=document.createTextNode(""),c=[];e.after(u),e.remove(),n=c,s=e.content?[...e.content.childNodes]:[e],i?.hasAttribute(":else")?(i.removeAttribute(":else"),i.hasAttribute(":if")?o=c:(i.remove(),o=i.content?[...i.content.childNodes]:[i])):o=c;for(let e of[...s,...o])y.set(e,null);return t((()=>{const t=r(l)?s:e[E]?c:o;if(i&&(i[E]=t===s),n!=t){n[0]?.[$]&&(n=[n[0][$]]);for(let e of n)e.remove();n=t;for(let e of n)a.insertBefore(e,u),null===y.get(e)&&y.delete(e),h(e,l)}}))},d.ref=(e,t,r)=>{r[t]=e},d.ref.parse=e=>e,d.with=(e,r,l)=>{let n;return t((()=>{let t=r(l);h(e,n?t:n=i(t,l))}))},d.html=(e,t,r)=>{let l=t(r);if(!l)return;let n=(l.content||l).cloneNode(!0);e.replaceChildren(n),h(e,r)},d.text=(e,r,l)=>(e.content&&e.replaceWith(e=document.createTextNode("")),t((()=>{let t=r(l);e.textContent=null==t?"":t}))),d.class=(e,r,l)=>{let n=new Set;return t((()=>{let t=r(l),s=new Set;t&&("string"==typeof t?t.split(" ").map((e=>s.add(e))):Array.isArray(t)?t.map((e=>e&&s.add(e))):Object.entries(t).map((([e,t])=>t&&s.add(e))));for(let t of n)s.has(t)?s.delete(t):e.classList.remove(t);for(let t of n=s)e.classList.add(t)}))},d.style=(e,r,l)=>{let n=e.getAttribute("style")||"";return n.endsWith(";")||(n+="; "),t((()=>{let t=r(l);if("string"==typeof t)e.setAttribute("style",n+t);else{e.setAttribute("style",n);for(let r in t)e.style.setProperty(r,t[r])}}))},d.default=(e,r,l,n)=>{if(!n.startsWith("on"))return t((()=>{let t=r(l);if(n)W(e,n,t);else for(let r in t)W(e,D(r),t[r])}));const s=n.split("..").map((t=>{let r={evt:"",target:e,test:()=>!0};return r.evt=(t.startsWith("on")?t.slice(2):t).replace(/\.(\w+)?-?([-\w]+)?/g,((e,t,l="")=>(r.test=P[t]?.(r,...l.split("-"))||r.test,""))),r}));if(1==s.length)return t((()=>f(r(l),s[0])));let o,a,i,u=0;const c=e=>{i=f((t=>(i(),a=e?.(t),(u=++u%s.length)?c(a):o&&c(o))),s[u])};return t((()=>(o=r(l),!i&&c(o),()=>o=null)));function f(e,{evt:t,target:r,test:l,defer:n,stop:s,prevent:o,immediate:a,...i}){n&&(e=n(e));const u=r=>{try{l(r)&&(s&&(a?r.stopImmediatePropagation():r.stopPropagation()),o&&r.preventDefault(),e?.(r))}catch(r){b(r,`:on${t}`,e)}};return r.addEventListener(t,u,i),()=>r.removeEventListener(t,u,i)}};var P={prevent(e){e.prevent=!0},stop(e){e.stop=!0},immediate(e){e.immediate=!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=>C(e,t?Number(t)||0:108)},debounce(e,t){e.defer=e=>_(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=>T.ctrl(e)&&t.every((t=>T[t]?T[t](e):e.key===t)),shift:(e,...t)=>e=>T.shift(e)&&t.every((t=>T[t]?T[t](e):e.key===t)),alt:(e,...t)=>e=>T.alt(e)&&t.every((t=>T[t]?T[t](e):e.key===t)),meta:(e,...t)=>e=>T.meta(e)&&t.every((t=>T[t]?T[t](e):e.key===t)),arrow:()=>T.arrow,enter:()=>T.enter,esc:()=>T.esc,tab:()=>T.tab,space:()=>T.space,delete:()=>T.delete,digit:()=>T.digit,letter:()=>T.letter,char:()=>T.char},T={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,esc:e=>e.key.startsWith("Esc"),tab:e=>"Tab"===e.key,space:e=>" "===e.key||"Space"===e.key||" "===e.key,delete:e=>"Delete"===e.key||"Backspace"===e.key,digit:e=>/^\d$/.test(e.key),letter:e=>/^\p{L}$/gu.test(e.key),char:e=>/^\S$/.test(e.key)},W=(e,t,r)=>{null==r||!1===r?e.removeAttribute(t):e.setAttribute(t,!0===r?"":"number"==typeof r||"string"==typeof r?r:"")},C=(e,t)=>{let r,l,n=s=>{r=!0,setTimeout((()=>{if(r=!1,l)return l=!1,n(s),e(s)}),t)};return t=>r?l=!0:(n(t),e(t))},_=(e,t)=>{let r;return l=>{clearTimeout(r),r=setTimeout((()=>{r=null,e(l)}),t)}},D=e=>e.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(e=>"-"+e.toLowerCase()));d.value=(e,r,l)=>{let n,s,o="text"===e.type||""===e.type?t=>e.setAttribute("value",e.value=null==t?"":t):"TEXTAREA"===e.tagName||"text"===e.type||""===e.type?t=>(n=e.selectionStart,s=e.selectionEnd,e.setAttribute("value",e.value=null==t?"":t),n&&e.setSelectionRange(n,s)):"checkbox"===e.type?t=>(e.checked=t,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 t((()=>o(r(l))))},d.fx=(e,r,l)=>t((()=>r(l))),h.use(S),h.use({compile:e=>h.constructor("__scope",`with (__scope) { return ${e} };`)});var K=h;export{K as default};
1
+ var e,t,r,l,n,o=Object.defineProperty,s=Symbol("signals"),a=Symbol("length");function i(t,r){if(!t)return t;if(t[s])return t;if(Array.isArray(t))return function(t){let r;if(t[s])return t;let l=e(t.length),n=Array(t.length).fill();const o=new Proxy(n,{get:(o,c)=>"symbol"==typeof c?c===a?l:c===s?n:n[c]:"length"===c?u[r]?l.peek():l.value:(r=c,n[c]?n[c].valueOf():c<n.length?(n[c]=e(i(t[c]))).value:void 0),set(e,t,r){if("length"===t){for(let e=r,t=n.length;e<t;e++)delete o[e];return l.value=n.length=r,!0}return c(n,t,r),t>=l.peek()&&(l.value=n.length=Number(t)+1),!0},deleteProperty:(e,t)=>(n[t]&&f(n,t),1)});return o}(t);if(t.constructor!==Object)return t;let l={...r?.[s]},o=e(Object.values(t).length);const p=new Proxy(l,{get:(e,t)=>t===a?o:t===s?l:l[t]?.valueOf(),set:(e,t,r,n)=>(n=l[t],c(l,t,r),n??++o.value,1),deleteProperty:(e,t)=>(l[t]&&(f(l,t),o.value--),1),ownKeys:()=>(o.value,Reflect.ownKeys(l))});for(let e in t){const r=Object.getOwnPropertyDescriptor(t,e);r?.get?(l[e]=n(r.get.bind(p)))._set=r.set?.bind(p):(l[e]=void 0,c(l,e,t[e]))}return p}var u={push:1,pop:1,shift:1,unshift:1,splice:1};function c(t,n,o){let s=t[n];if("_"===n[0])t[n]=o;else if(s)if(o===s.peek());else if(s._set)s._set(o);else if(Array.isArray(o)&&Array.isArray(s.peek())){const e=s.peek();e[a]?r((()=>{l((()=>{let t=0,r=o.length;for(;t<r;t++)e[t]=o[t];e.length=r}))})):s.value=o}else s.value=i(o);else t[n]=s=o?.peek?o:e(i(o))}function f(e,t){const r=e[t],l=r[Symbol.dispose];l&&delete r[Symbol.dispose],delete e[t],l?.()}var p=Symbol.dispose||=Symbol("dispose"),d={},y=new WeakMap;function h(e,t){if(!e?.childNodes)return;if(y.has(e))return Object.assign(y.get(e),t);const r=i(t||{}),l=[];return function e(t,n=t.parentNode){if(t.childNodes){for(let e=0;e<t.attributes?.length;){let o=t.attributes[e];if(":"===o.name[0]){t.removeAttribute(o.name);let e=o.name.slice(1).split(":");for(let n of e){let e=d[n]||d.default,s=e(t,(e.parse||g)(o.value,g),r,n);s&&l.push(s)}if(y.has(t))return t[p]&&l.push(t[p]);if(t.parentNode!==n)return}else e++}for(let r of[...t.childNodes])e(r,t)}}(e),y.has(e)||y.set(e,r),e[p]=()=>{for(;l.length;)l.pop()();y.delete(e)},r}var m,v={},g=(e,t,r)=>{if(r=v[e=e.trim()])return r;try{r=m(e)}catch(r){b(r,t,e)}return v[e]=r},b=(e,t,r="")=>{throw Object.assign(e,{message:`∴ ${e.message}\n\n${t}${r?`="${r}"\n\n`:""}`,expr:r})};h.use=o=>{o.signal&&function(o){e=o.signal,t=o.effect,n=o.computed,l=o.batch||(e=>e()),r=o.untracked||l}(o),o.compile&&(m=o.compile)};var k,A,w=e=>{if(!e.nodeType)return e;e.content.appendChild(document.createTextNode(""));let t=e.content.cloneNode(!0),r=[...e.attributes],l=[...t.childNodes];return{childNodes:l,content:t,remove:()=>t.append(...l),replaceWith(e){l[0].before(e),t.append(...l)},attributes:r,removeAttribute(e){r.splice(r.findIndex((t=>t.name===e)),1)}}},S={};((e,t)=>{for(var r in t)o(e,r,{get:t[r],enumerable:!0})})(S,{batch:()=>W,computed:()=>x,effect:()=>O,signal:()=>N,untracked:()=>j});var N=(e,t,r=new Set)=>((t={get value(){return k?.deps.push(r.add(k)),e},set value(t){if(t!==e){e=t;for(let e of r)A?A.add(e):e()}},peek:()=>e}).toJSON=t.then=t.toString=t.valueOf=()=>t.value,t),O=(e,t,r,l)=>(l=(r=l=>{t?.call?.(),l=k,k=r;try{t=e()}finally{k=l}}).deps=[],r(),e=>{for(t?.call?.();e=l.pop();)e.delete(r)}),x=(e,t=N(),r,l)=>((r={get value(){return l||=O((()=>t.value=e())),t.value},peek:t.peek}).toJSON=r.then=r.toString=r.valueOf=()=>r.value,r),W=e=>{let t=A;t||(A=new Set);try{e()}finally{if(!t){t=A,A=null;for(const e of t)e()}}},j=(e,t,r)=>(t=k,k=null,r=e(),k=t,r),T=Symbol("if");d.if=(e,r,l)=>{let n,o,s=e.nextElementSibling,a=document.createTextNode(""),i=[],u=i;e.replaceWith(a),n=e.content?[w(e)]:[e],s?.hasAttribute(":else")?(s.removeAttribute(":else"),s.hasAttribute(":if")?o=i:(s.remove(),o=s.content?[w(s)]:[s])):o=i;for(let e of[...n,...o])y.set(e,null);return t((()=>{const t=r(l)?n:e[T]?i:o;if(s&&(s[T]=t===n),u!=t){for(let e of u)e.remove();u=t;for(let e of u)a.before(e.content||e),null===y.get(e)&&y.delete(e),h(e,l)}}))},d.each=(e,[l,o,u],c)=>{const f=document.createTextNode("");e.replaceWith(f);let p,d,y=0;const m=n((()=>{d=null;let e=u(c);return"number"==typeof e&&(e=Array.from({length:e},((e,t)=>t+1))),e?.constructor===Object&&(d=Object.keys(e),e=Object.values(e)),e||[]})),v=()=>{r((()=>{let t=0,r=m.value,n=r.length;if(p&&!p[a]){for(let e of p[s]||[])e[Symbol.dispose]();p=null,y=0}if(n<y)p.length=n;else{if(p)for(;t<y;t++)p[t]=r[t];else p=r;for(;t<n;t++){p[t]=r[t];let n=t,a=i({[l]:p[s]?.[n]||p[n],[o]:d?d[n]:n},c),u=e.content?w(e):e.cloneNode(!0);f.before(u.content||u),h(u,a),((p[s]||=[])[t]||={})[Symbol.dispose]=()=>{u[Symbol.dispose](),u.remove()}}}y=n}))};let g=0;return t((()=>{m.value[a]?.value,g?g++:(v(),queueMicrotask((()=>(g&&v(),g=0))))}))},d.each.parse=(e,t)=>{let[r,l]=e.split(/\s+in\s+/),[n,o="$"]=r.split(/\s*,\s*/);return[n,o,t(l)]},d.ref=(e,t,r)=>{r[t]=e},d.ref.parse=e=>e,d.with=(e,r,l)=>{let n;return t((()=>{let t=r(l);h(e,n?t:n=i(t,l))}))},d.html=(e,t,r)=>{let l=t(r);if(!l)return;let n=(l.content||l).cloneNode(!0);e.replaceChildren(n),h(e,r)},d.text=(e,r,l)=>{if(e.content){let t=w(e);e!==t&&e.replaceWith(t.content),e=t.childNodes[0]}return t((()=>{let t=r(l);e.textContent=null==t?"":t}))},d.class=(e,r,l)=>{let n=new Set;return t((()=>{let t=r(l),o=new Set;t&&("string"==typeof t?t.split(" ").map((e=>o.add(e))):Array.isArray(t)?t.map((e=>e&&o.add(e))):Object.entries(t).map((([e,t])=>t&&o.add(e))));for(let t of n)o.has(t)?o.delete(t):e.classList.remove(t);for(let t of n=o)e.classList.add(t)}))},d.style=(e,r,l)=>{let n=e.getAttribute("style")||"";return n.endsWith(";")||(n+="; "),t((()=>{let t=r(l);if("string"==typeof t)e.setAttribute("style",n+t);else{e.setAttribute("style",n);for(let r in t)e.style.setProperty(r,t[r])}}))},d.default=(e,r,l,n)=>{if(!n.startsWith("on"))return t((()=>{let t=r(l);if(n)E(e,n,t);else for(let r in t)E(e,D(r),t[r])}));const o=n.split("..").map((t=>{let r={evt:"",target:e,test:()=>!0};return r.evt=(t.startsWith("on")?t.slice(2):t).replace(/\.(\w+)?-?([-\w]+)?/g,((e,t,l="")=>(r.test=$[t]?.(r,...l.split("-"))||r.test,""))),r}));if(1==o.length)return t((()=>f(r(l),o[0])));let s,a,i,u=0;const c=e=>{i=f((t=>(i(),a=e?.(t),(u=++u%o.length)?c(a):s&&c(s))),o[u])};return t((()=>(s=r(l),!i&&c(s),()=>s=null)));function f(e,{evt:t,target:r,test:l,defer:n,stop:o,prevent:s,immediate:a,...i}){n&&(e=n(e));const u=r=>{try{l(r)&&(o&&(a?r.stopImmediatePropagation():r.stopPropagation()),s&&r.preventDefault(),e?.(r))}catch(r){b(r,`:on${t}`,e)}};return r.addEventListener(t,u,i),()=>r.removeEventListener(t,u,i)}};var $={prevent(e){e.prevent=!0},stop(e){e.stop=!0},immediate(e){e.immediate=!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=>P(e,t?Number(t)||0:108)},debounce(e,t){e.defer=e=>_(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=>C.ctrl(e)&&t.every((t=>C[t]?C[t](e):e.key===t)),shift:(e,...t)=>e=>C.shift(e)&&t.every((t=>C[t]?C[t](e):e.key===t)),alt:(e,...t)=>e=>C.alt(e)&&t.every((t=>C[t]?C[t](e):e.key===t)),meta:(e,...t)=>e=>C.meta(e)&&t.every((t=>C[t]?C[t](e):e.key===t)),arrow:()=>C.arrow,enter:()=>C.enter,esc:()=>C.esc,tab:()=>C.tab,space:()=>C.space,delete:()=>C.delete,digit:()=>C.digit,letter:()=>C.letter,char:()=>C.char},C={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,esc:e=>e.key.startsWith("Esc"),tab:e=>"Tab"===e.key,space:e=>" "===e.key||"Space"===e.key||" "===e.key,delete:e=>"Delete"===e.key||"Backspace"===e.key,digit:e=>/^\d$/.test(e.key),letter:e=>/^\p{L}$/gu.test(e.key),char:e=>/^\S$/.test(e.key)},E=(e,t,r)=>{null==r||!1===r?e.removeAttribute(t):e.setAttribute(t,!0===r?"":"number"==typeof r||"string"==typeof r?r:"")},P=(e,t)=>{let r,l,n=o=>{r=!0,setTimeout((()=>{if(r=!1,l)return l=!1,n(o),e(o)}),t)};return t=>r?l=!0:(n(t),e(t))},_=(e,t)=>{let r;return l=>{clearTimeout(r),r=setTimeout((()=>{r=null,e(l)}),t)}},D=e=>e.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(e=>"-"+e.toLowerCase()));d.value=(e,r,l)=>{let n,o,s="text"===e.type||""===e.type?t=>e.setAttribute("value",e.value=null==t?"":t):"TEXTAREA"===e.tagName||"text"===e.type||""===e.type?t=>(n=e.selectionStart,o=e.selectionEnd,e.setAttribute("value",e.value=null==t?"":t),n&&e.setSelectionRange(n,o)):"checkbox"===e.type?t=>(e.checked=t,E(e,"checked",t)):"select-one"===e.type?t=>{for(let t in e.options)t.removeAttribute("selected");e.value=t,e.selectedOptions[0]?.setAttribute("selected","")}:t=>e.value=t;return t((()=>s(r(l))))},d.fx=(e,r,l)=>t((()=>r(l))),h.use(S),h.use({compile:e=>h.constructor("__scope",`with (__scope) { return ${e} };`)});var K=h;export{K as default};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sprae",
3
3
  "description": "DOM microhydration.",
4
- "version": "10.7.0",
4
+ "version": "10.8.0",
5
5
  "main": "./sprae.js",
6
6
  "module": "./sprae.js",
7
7
  "type": "module",
package/readme.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  _Sprae_ is open & minimalistic progressive enhancement framework.<br/>
6
6
  Perfect for small-scale websites, static pages, landings, prototypes, or lightweight UI.<br/>
7
-
7
+ A light, fast and sweet alternative to alpine, petit-vue etc.
8
8
 
9
9
  ## Usage
10
10
 
@@ -24,7 +24,7 @@ Perfect for small-scale websites, static pages, landings, prototypes, or lightwe
24
24
  </script>
25
25
  ```
26
26
 
27
- Sprae evaluates `:`-directives and evaporates them, returning reactive state.
27
+ Sprae evaluates `:`-directives and evaporates them, returning reactive state for updates.
28
28
 
29
29
  ## Directives
30
30
 
@@ -59,9 +59,6 @@ Multiply element.
59
59
  <dt :text="item.term"/>
60
60
  <dd :text="item.definition"/>
61
61
  </template>
62
-
63
- <!-- prevent FOUC -->
64
- <style>[:each] {visibility: hidden}</style>
65
62
  ```
66
63
 
67
64
  #### `:text="value"`
@@ -271,7 +268,8 @@ Trigger when element is connected / disconnected from DOM.
271
268
 
272
269
  ## Signals
273
270
 
274
- Sprae can take signal values. Signals provider can be switched to any preact-flavored implementation:
271
+ Sprae uses signals for reactivity and can take signal values as inputs.
272
+ Signals provider can be switched to any preact-flavored implementation:
275
273
 
276
274
  ```js
277
275
  import sprae from 'sprae';
@@ -346,6 +344,7 @@ import * as signals from '@preact/signals'
346
344
  import compile from 'subscript'
347
345
 
348
346
  // standard directives
347
+ import 'sprae/directive/default.js'
349
348
  import 'sprae/directive/if.js'
350
349
  import 'sprae/directive/text.js'
351
350
 
@@ -361,20 +360,25 @@ sprae.use(signals)
361
360
  sprae.use({ compile })
362
361
  ```
363
362
 
364
- <!-- ## Dispose
365
-
366
- To destroy state and detach sprae handlers, call `element[Symbol.dispose]()`. -->
363
+ ## Hints
367
364
 
365
+ * To prevent [FOUC](https://en.wikipedia.org/wiki/Flash_of_unstyled_content) add `<style>[:each],[:if],[:else],[:text] {visibility: hidden}</style>`
366
+ * Attributes order matters, eg. `<li :each="value in values" :text="value.name"></li>` is not the same as `<li :text="value.name" :each="value in values"></li>`
367
+ * To destroy state and detach sprae handlers, call `element[Symbol.dispose]()`.
368
+ * State getters/setters work as computed effects, eg. `sprae(el, { x:1, get x2(){ return this.x * 2} })`.
369
+ * `this` keyword is not used, to get access to current element use `ref` as `<input :ref="el" :text="el.value"/>`
370
+ * Async/await is not supported in attributes, it's a strong signal you need to put these methods into state.
368
371
 
369
372
  ## Justification
370
373
 
371
374
  * [Template-parts](https://github.com/dy/template-parts) is stuck with native HTML quirks ([parsing table](https://github.com/github/template-parts/issues/24), [SVG attributes](https://github.com/github/template-parts/issues/25), [liquid syntax](https://shopify.github.io/liquid/tags/template/#raw) conflict etc).
372
375
  * [Alpine](https://github.com/alpinejs/alpine) / [petite-vue](https://github.com/vuejs/petite-vue) / [lucia](https://github.com/aidenyabi/lucia) escape native HTML quirks, but have excessive API (`:`, `x-`, `{}`, `@`, `$`), tend to [self-encapsulate](https://github.com/alpinejs/alpine/discussions/3223) and not care about size/performance.
373
376
 
374
- _Sprae_ holds open & minimalistic philosophy:
377
+ _Sprae_ holds open, sweet & minimalistic philosophy:
375
378
  * Slim `:` API and _signals_ reactivity.
376
379
  * Pluggable directives & configurable internals.
377
380
  * Small, safe & performant.
381
+ * Bits of organic sugar.
378
382
  * Aims at making developers happy 🫰
379
383
 
380
384
  <!--