sprae 10.12.1 → 10.12.3

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
@@ -1,9 +1,10 @@
1
- import { use } from "./signal.js";
1
+ import { use, effect } from "./signal.js";
2
2
  import store, { _signals } from './store.js';
3
3
 
4
4
  // polyfill
5
5
  const _dispose = (Symbol.dispose ||= Symbol("dispose"));
6
6
 
7
+
7
8
  // reserved directives - order matters!
8
9
  export const directive = {};
9
10
 
@@ -33,6 +34,7 @@ export default function sprae(el, values) {
33
34
  el[_dispose] = () => {
34
35
  while (disposes.length) disposes.pop()();
35
36
  memo.delete(el);
37
+ el[_dispose] = null;
36
38
  }
37
39
 
38
40
  return state;
@@ -50,12 +52,12 @@ export default function sprae(el, values) {
50
52
  // multiple attributes like :id:for=""
51
53
  let names = attr.name.slice(1).split(':')
52
54
 
53
- // NOTE: secondary directives don't stop flow nor extend state, so no need to check
54
55
  for (let name of names) {
55
56
  let dir = directive[name] || directive.default
56
57
  let evaluate = (dir.parse || parse)(attr.value)
57
- let dispose = dir(el, evaluate, state, name);
58
- if (dispose) disposes.push(dispose);
58
+ let fn = dir(el, evaluate, state, name);
59
+ if (fn) disposes.push(effect(fn))
60
+ disposes.push(() => el.setAttributeNode(attr)) // recover attribute
59
61
  }
60
62
 
61
63
  // stop if element was spraed by internal directive
@@ -71,7 +73,7 @@ export default function sprae(el, values) {
71
73
  }
72
74
 
73
75
 
74
- // compiler
76
+ // parse expression into evaluator fn
75
77
  const evalMemo = {};
76
78
  export const parse = (expr, dir, fn) => {
77
79
  if (fn = evalMemo[expr = expr.trim()]) return fn
@@ -119,6 +121,7 @@ export const frag = (tpl) => {
119
121
  content.append(...childNodes)
120
122
  },
121
123
  attributes,
122
- removeAttribute(name) { attributes.splice(attributes.findIndex(a => a.name === name), 1) }
124
+ removeAttribute(name) { attributes.splice(attributes.findIndex(a => a.name === name), 1) },
125
+ setAttributeNode() { }
123
126
  }
124
127
  }
package/directive/aria.js CHANGED
@@ -1,10 +1,9 @@
1
1
  import { directive } from "../core.js";
2
2
  import { attr, dashcase } from './default.js'
3
- import { effect } from "../signal.js";
4
3
 
5
4
  directive['aria'] = (el, evaluate, state) => {
6
5
  const update = (value) => {
7
6
  for (let key in value) attr(el, 'aria-' + dashcase(key), value[key] == null ? null : value[key] + '');
8
7
  }
9
- return effect(() => update(evaluate(state)))
8
+ return () => update(evaluate(state))
10
9
  }
@@ -1,9 +1,8 @@
1
1
  import { directive } from "../core.js";
2
- import { effect } from "../signal.js";
3
2
 
4
3
  directive.class = (el, evaluate, state) => {
5
4
  let cur = new Set
6
- return effect(() => {
5
+ return () => {
7
6
  let v = evaluate(state);
8
7
  let clsx = new Set;
9
8
  if (v) {
@@ -13,5 +12,5 @@ directive.class = (el, evaluate, state) => {
13
12
  }
14
13
  for (let cls of cur) if (clsx.has(cls)) clsx.delete(cls); else el.classList.remove(cls);
15
14
  for (let cls of cur = clsx) el.classList.add(cls)
16
- });
15
+ };
17
16
  };
package/directive/data.js CHANGED
@@ -1,9 +1,8 @@
1
1
  import { directive } from "../core.js";
2
- import { effect } from "../signal.js";
3
2
 
4
3
  directive['data'] = (el, evaluate, state) => {
5
- return effect(() => {
4
+ return () => {
6
5
  let value = evaluate(state)
7
6
  for (let key in value) el.dataset[key] = value[key];
8
- })
7
+ }
9
8
  }
@@ -1,14 +1,13 @@
1
1
  import { directive, err } from "../core.js";
2
- import { effect } from "../signal.js";
3
2
 
4
3
  // set generic property directive
5
4
  directive.default = (target, evaluate, state, name) => {
6
5
  // simple prop
7
- if (!name.startsWith('on')) return effect(() => {
6
+ if (!name.startsWith('on')) return () => {
8
7
  let value = evaluate(state);
9
8
  if (name) attr(target, name, value)
10
9
  else for (let key in value) attr(target, dashcase(key), value[key]);
11
- });
10
+ };
12
11
 
13
12
  // bind event to a target
14
13
  // NOTE: if you decide to remove chain of events, thing again - that's unique feature of sprae, don't diminish your own value.
@@ -22,7 +21,7 @@ directive.default = (target, evaluate, state, name) => {
22
21
  });
23
22
 
24
23
  // single event
25
- if (ctxs.length == 1) return effect(() => addListener(evaluate(state), ctxs[0]))
24
+ if (ctxs.length == 1) return () => addListener(evaluate(state), ctxs[0])
26
25
 
27
26
  // events cycler
28
27
  let startFn, nextFn, off, idx = 0
@@ -32,11 +31,11 @@ directive.default = (target, evaluate, state, name) => {
32
31
  ), ctxs[idx]);
33
32
  }
34
33
 
35
- return effect(() => (
34
+ return () => (
36
35
  startFn = evaluate(state),
37
36
  !off && nextListener(startFn),
38
37
  () => startFn = null // nil startFn to autodispose chain
39
- ))
38
+ )
40
39
 
41
40
  // add listener with the context
42
41
  function addListener(fn, { evt, target, test, defer, stop, prevent, immediate, ...opts }) {
@@ -153,5 +152,5 @@ const debounce = (fn, wait) => {
153
152
  };
154
153
 
155
154
  export const dashcase = (str) => {
156
- return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match, i) => (i?'-':'') + match.toLowerCase());
155
+ return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match, i) => (i ? '-' : '') + match.toLowerCase());
157
156
  }
package/directive/each.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import sprae, { directive, frag, parse } from "../core.js";
2
2
  import store, { _change, _signals } from "../store.js";
3
- import { effect, untracked, computed } from '../signal.js';
3
+ import { untracked, computed } from '../signal.js';
4
4
 
5
5
 
6
6
  directive.each = (tpl, [itemVar, idxVar, evaluate], state) => {
@@ -63,7 +63,7 @@ directive.each = (tpl, [itemVar, idxVar, evaluate], state) => {
63
63
 
64
64
  // signal/holder disposal removes element
65
65
  ((cur[_signals] ||= [])[i] ||= {})[Symbol.dispose] = () => {
66
- el[Symbol.dispose](), el.remove()
66
+ el[Symbol.dispose]?.(), el.remove()
67
67
  };
68
68
  }
69
69
  }
@@ -73,7 +73,7 @@ directive.each = (tpl, [itemVar, idxVar, evaluate], state) => {
73
73
  }
74
74
 
75
75
  let planned = 0
76
- return effect(() => {
76
+ return () => {
77
77
  // subscribe to items change (.length) - we do it every time (not just on init) since preact unsubscribes unused signals
78
78
  items.value[_change]?.value
79
79
 
@@ -82,7 +82,7 @@ directive.each = (tpl, [itemVar, idxVar, evaluate], state) => {
82
82
  update()
83
83
  queueMicrotask(() => (planned && update(), planned = 0))
84
84
  } else planned++
85
- })
85
+ }
86
86
  }
87
87
 
88
88
 
package/directive/fx.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { directive } from "../core.js";
2
- import { effect } from "../signal.js";
3
2
 
4
3
  directive.fx = (el, evaluate, state) => {
5
- return effect(() => evaluate(state));
4
+ return () => evaluate(state);
6
5
  };
package/directive/if.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import sprae, { directive, memo, frag } from "../core.js";
2
- import { effect } from "../signal.js";
3
2
 
4
3
  // :if is interchangeable with :each depending on order, :if :each or :each :if have different meanings
5
4
  // as for :if :with - :if must init first, since it is lazy, to avoid initializing component ahead of time by :with
@@ -21,16 +20,17 @@ directive.if = (el, evaluate, state) => {
21
20
  if (!next.hasAttribute(":if")) next.remove(), elseEl = next.content ? frag(next) : next, memo.set(elseEl, null)
22
21
  }
23
22
 
24
- return effect(() => {
23
+ return () => {
25
24
  const newEl = evaluate(state) ? ifEl : el[_prevIf] ? null : elseEl;
26
25
  if (next) next[_prevIf] = newEl === ifEl
27
26
  if (curEl != newEl) {
28
- curEl?.remove()
27
+ // disable effects on child elements to avoid internal effects from triggering on value changes when element's not matched
28
+ if (curEl) curEl.remove(), curEl[Symbol.dispose]?.();
29
29
  if (curEl = newEl) {
30
30
  holder.before(curEl.content || curEl)
31
31
  memo.get(curEl) === null && memo.delete(curEl) // remove fake memo to sprae as new
32
32
  sprae(curEl, state)
33
33
  }
34
34
  }
35
- });
35
+ };
36
36
  };
@@ -1,15 +1,14 @@
1
1
  import { directive } from "../core.js";
2
- import { effect } from "../signal.js";
3
2
 
4
3
  directive.style = (el, evaluate, state) => {
5
4
  let initStyle = el.getAttribute("style");
6
5
 
7
- return effect(() => {
6
+ return () => {
8
7
  let v = evaluate(state);
9
8
  if (typeof v === "string") el.setAttribute("style", initStyle + (initStyle.endsWith(';') ? '' : '; ') + v);
10
9
  else {
11
10
  if (initStyle) el.setAttribute("style", initStyle);
12
11
  for (let k in v) k[0] == '-' ? (el.style.setProperty(k, v[k])) : el.style[k] = v[k]
13
12
  }
14
- });
13
+ };
15
14
  };
package/directive/text.js CHANGED
@@ -1,13 +1,12 @@
1
1
  import { directive, frag } from "../core.js";
2
- import { effect } from "../signal.js";
3
2
 
4
3
  // set text content
5
4
  directive.text = (el, evaluate, state) => {
6
5
  // <template :text="a"/> or previously initialized template
7
6
  if (el.content) el.replaceWith(el = frag(el).childNodes[0])
8
7
 
9
- return effect(() => {
8
+ return () => {
10
9
  let value = evaluate(state);
11
10
  el.textContent = value == null ? "" : value;
12
- });
11
+ };
13
12
  };
@@ -1,7 +1,6 @@
1
1
  import sprae from "../core.js";
2
2
  import { directive, parse } from "../core.js";
3
3
  import { attr } from './default.js';
4
- import { effect } from "../signal.js";
5
4
 
6
5
  // connect expr to element value
7
6
  directive.value = (el, [getValue, setValue], state) => {
@@ -38,7 +37,7 @@ directive.value = (el, [getValue, setValue], state) => {
38
37
 
39
38
  el.oninput = el.onchange = handleChange; // hope user doesn't redefine these manually - it saves 5 loc
40
39
 
41
- return effect(() => update(getValue(state)));
40
+ return () => update(getValue(state));
42
41
  };
43
42
 
44
43
  directive.value.parse = expr => {
package/directive/with.js CHANGED
@@ -1,11 +1,10 @@
1
1
  import sprae, { directive } from "../core.js";
2
2
  import store, { _signals } from '../store.js';
3
- import { effect } from "../signal.js";
4
3
 
5
4
  directive.with = (el, evaluate, rootState) => {
6
5
  let state
7
- return effect(() => {
6
+ return () => {
8
7
  let values = evaluate(rootState);
9
8
  sprae(el, state ? values : state = store(values, rootState))
10
- })
9
+ }
11
10
  };
package/dist/sprae.js CHANGED
@@ -158,6 +158,7 @@ function sprae(el, values) {
158
158
  el[_dispose] = () => {
159
159
  while (disposes.length) disposes.pop()();
160
160
  memo.delete(el);
161
+ el[_dispose] = null;
161
162
  };
162
163
  return state;
163
164
  function init(el2, parent = el2.parentNode) {
@@ -170,8 +171,9 @@ function sprae(el, values) {
170
171
  for (let name of names) {
171
172
  let dir = directive[name] || directive.default;
172
173
  let evaluate = (dir.parse || parse)(attr2.value);
173
- let dispose = dir(el2, evaluate, state, name);
174
- if (dispose) disposes.push(dispose);
174
+ let fn = dir(el2, evaluate, state, name);
175
+ if (fn) disposes.push(effect(fn));
176
+ disposes.push(() => el2.setAttributeNode(attr2));
175
177
  }
176
178
  if (memo.has(el2)) return el2[_dispose] && disposes.push(el2[_dispose]);
177
179
  if (el2.parentNode !== parent) return;
@@ -218,6 +220,8 @@ var frag = (tpl) => {
218
220
  attributes,
219
221
  removeAttribute(name) {
220
222
  attributes.splice(attributes.findIndex((a) => a.name === name), 1);
223
+ },
224
+ setAttributeNode() {
221
225
  }
222
226
  };
223
227
  };
@@ -233,18 +237,18 @@ directive.if = (el, evaluate, state) => {
233
237
  next.removeAttribute(":else");
234
238
  if (!next.hasAttribute(":if")) next.remove(), elseEl = next.content ? frag(next) : next, memo.set(elseEl, null);
235
239
  }
236
- return effect(() => {
240
+ return () => {
237
241
  const newEl = evaluate(state) ? ifEl : el[_prevIf] ? null : elseEl;
238
242
  if (next) next[_prevIf] = newEl === ifEl;
239
243
  if (curEl != newEl) {
240
- curEl?.remove();
244
+ if (curEl) curEl.remove(), curEl[Symbol.dispose]?.();
241
245
  if (curEl = newEl) {
242
246
  holder.before(curEl.content || curEl);
243
247
  memo.get(curEl) === null && memo.delete(curEl);
244
248
  sprae(curEl, state);
245
249
  }
246
250
  }
247
- });
251
+ };
248
252
  };
249
253
 
250
254
  // directive/each.js
@@ -288,7 +292,7 @@ directive.each = (tpl, [itemVar, idxVar, evaluate], state) => {
288
292
  holder.before(el.content || el);
289
293
  sprae(el, scope);
290
294
  ((_b = cur[_a = _signals] || (cur[_a] = []))[i] || (_b[i] = {}))[Symbol.dispose] = () => {
291
- el[Symbol.dispose](), el.remove();
295
+ el[Symbol.dispose]?.(), el.remove();
292
296
  };
293
297
  }
294
298
  }
@@ -296,13 +300,13 @@ directive.each = (tpl, [itemVar, idxVar, evaluate], state) => {
296
300
  });
297
301
  };
298
302
  let planned = 0;
299
- return effect(() => {
303
+ return () => {
300
304
  items.value[_change]?.value;
301
305
  if (!planned) {
302
306
  update();
303
307
  queueMicrotask(() => (planned && update(), planned = 0));
304
308
  } else planned++;
305
- });
309
+ };
306
310
  };
307
311
  directive.each.parse = (expr) => {
308
312
  let [leftSide, itemsExpr] = expr.split(/\s+in\s+/);
@@ -319,10 +323,10 @@ directive.ref.parse = (expr) => expr;
319
323
  // directive/with.js
320
324
  directive.with = (el, evaluate, rootState) => {
321
325
  let state;
322
- return effect(() => {
326
+ return () => {
323
327
  let values = evaluate(rootState);
324
328
  sprae(el, state ? values : state = store(values, rootState));
325
- });
329
+ };
326
330
  };
327
331
 
328
332
  // directive/html.js
@@ -337,16 +341,16 @@ directive.html = (el, evaluate, state) => {
337
341
  // directive/text.js
338
342
  directive.text = (el, evaluate, state) => {
339
343
  if (el.content) el.replaceWith(el = frag(el).childNodes[0]);
340
- return effect(() => {
344
+ return () => {
341
345
  let value = evaluate(state);
342
346
  el.textContent = value == null ? "" : value;
343
- });
347
+ };
344
348
  };
345
349
 
346
350
  // directive/class.js
347
351
  directive.class = (el, evaluate, state) => {
348
352
  let cur = /* @__PURE__ */ new Set();
349
- return effect(() => {
353
+ return () => {
350
354
  let v = evaluate(state);
351
355
  let clsx = /* @__PURE__ */ new Set();
352
356
  if (v) {
@@ -357,29 +361,29 @@ directive.class = (el, evaluate, state) => {
357
361
  for (let cls of cur) if (clsx.has(cls)) clsx.delete(cls);
358
362
  else el.classList.remove(cls);
359
363
  for (let cls of cur = clsx) el.classList.add(cls);
360
- });
364
+ };
361
365
  };
362
366
 
363
367
  // directive/style.js
364
368
  directive.style = (el, evaluate, state) => {
365
369
  let initStyle = el.getAttribute("style");
366
- return effect(() => {
370
+ return () => {
367
371
  let v = evaluate(state);
368
372
  if (typeof v === "string") el.setAttribute("style", initStyle + (initStyle.endsWith(";") ? "" : "; ") + v);
369
373
  else {
370
374
  if (initStyle) el.setAttribute("style", initStyle);
371
375
  for (let k in v) k[0] == "-" ? el.style.setProperty(k, v[k]) : el.style[k] = v[k];
372
376
  }
373
- });
377
+ };
374
378
  };
375
379
 
376
380
  // directive/default.js
377
381
  directive.default = (target, evaluate, state, name) => {
378
- if (!name.startsWith("on")) return effect(() => {
382
+ if (!name.startsWith("on")) return () => {
379
383
  let value = evaluate(state);
380
384
  if (name) attr(target, name, value);
381
385
  else for (let key in value) attr(target, dashcase(key), value[key]);
382
- });
386
+ };
383
387
  const ctxs = name.split("..").map((e) => {
384
388
  let ctx = { evt: "", target, test: () => true };
385
389
  ctx.evt = (e.startsWith("on") ? e.slice(2) : e).replace(
@@ -388,12 +392,12 @@ directive.default = (target, evaluate, state, name) => {
388
392
  );
389
393
  return ctx;
390
394
  });
391
- if (ctxs.length == 1) return effect(() => addListener(evaluate(state), ctxs[0]));
395
+ if (ctxs.length == 1) return () => addListener(evaluate(state), ctxs[0]);
392
396
  let startFn, nextFn, off, idx = 0;
393
397
  const nextListener = (fn) => {
394
398
  off = addListener((e) => (off(), nextFn = fn?.(e), (idx = ++idx % ctxs.length) ? nextListener(nextFn) : startFn && nextListener(startFn)), ctxs[idx]);
395
399
  };
396
- return effect(() => (startFn = evaluate(state), !off && nextListener(startFn), () => startFn = null));
400
+ return () => (startFn = evaluate(state), !off && nextListener(startFn), () => startFn = null);
397
401
  function addListener(fn, { evt, target: target2, test, defer, stop, prevent, immediate, ...opts }) {
398
402
  if (defer) fn = defer(fn);
399
403
  const cb = (e) => {
@@ -530,7 +534,7 @@ directive.value = (el, [getValue, setValue], state) => {
530
534
  if (el.type?.startsWith("select")) sprae(el, state);
531
535
  const handleChange = el.type === "checkbox" ? (e) => setValue(state, el.checked) : el.type === "select-multiple" ? (e) => setValue(state, [...el.selectedOptions].map((o) => o.value)) : (e) => setValue(state, el.value);
532
536
  el.oninput = el.onchange = handleChange;
533
- return effect(() => update(getValue(state)));
537
+ return () => update(getValue(state));
534
538
  };
535
539
  directive.value.parse = (expr) => {
536
540
  let evaluate = [parse(expr)];
@@ -549,7 +553,7 @@ directive.value.parse = (expr) => {
549
553
 
550
554
  // directive/fx.js
551
555
  directive.fx = (el, evaluate, state) => {
552
- return effect(() => evaluate(state));
556
+ return () => evaluate(state);
553
557
  };
554
558
 
555
559
  // sprae.js