sprae 10.12.2 → 10.13.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
@@ -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
 
@@ -51,12 +52,12 @@ export default function sprae(el, values) {
51
52
  // multiple attributes like :id:for=""
52
53
  let names = attr.name.slice(1).split(':')
53
54
 
54
- // NOTE: secondary directives don't stop flow nor extend state, so no need to check
55
55
  for (let name of names) {
56
56
  let dir = directive[name] || directive.default
57
57
  let evaluate = (dir.parse || parse)(attr.value)
58
- let dispose = dir(el, evaluate, state, name);
59
- 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
60
61
  }
61
62
 
62
63
  // stop if element was spraed by internal directive
@@ -72,7 +73,7 @@ export default function sprae(el, values) {
72
73
  }
73
74
 
74
75
 
75
- // compiler
76
+ // parse expression into evaluator fn
76
77
  const evalMemo = {};
77
78
  export const parse = (expr, dir, fn) => {
78
79
  if (fn = evalMemo[expr = expr.trim()]) return fn
@@ -120,6 +121,7 @@ export const frag = (tpl) => {
120
121
  content.append(...childNodes)
121
122
  },
122
123
  attributes,
123
- 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() { }
124
126
  }
125
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 }) {
@@ -69,6 +68,7 @@ const mods = {
69
68
  // target
70
69
  window(ctx) { ctx.target = window; },
71
70
  document(ctx) { ctx.target = document; },
71
+ parent(ctx) { ctx.target = ctx.target.parentNode; },
72
72
 
73
73
  throttle(ctx, limit) { ctx.defer = (fn) => throttle(fn, limit ? Number(limit) || 0 : 108); },
74
74
  debounce(ctx, wait) { ctx.defer = (fn) => debounce(fn, wait ? Number(wait) || 0 : 108); },
@@ -153,5 +153,5 @@ const debounce = (fn, wait) => {
153
153
  };
154
154
 
155
155
  export const dashcase = (str) => {
156
- return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match, i) => (i?'-':'') + match.toLowerCase());
156
+ return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match, i) => (i ? '-' : '') + match.toLowerCase());
157
157
  }
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) => {
@@ -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,17 +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
- // disable effects on removed elements to avoid internal effects from triggering on possibly null values
29
- if (curEl) curEl.remove(), curEl[Symbol.dispose]?.()
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]?.();
30
29
  if (curEl = newEl) {
31
30
  holder.before(curEl.content || curEl)
32
31
  memo.get(curEl) === null && memo.delete(curEl) // remove fake memo to sprae as new
33
32
  sprae(curEl, state)
34
33
  }
35
34
  }
36
- });
35
+ };
37
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
@@ -171,8 +171,9 @@ function sprae(el, values) {
171
171
  for (let name of names) {
172
172
  let dir = directive[name] || directive.default;
173
173
  let evaluate = (dir.parse || parse)(attr2.value);
174
- let dispose = dir(el2, evaluate, state, name);
175
- 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));
176
177
  }
177
178
  if (memo.has(el2)) return el2[_dispose] && disposes.push(el2[_dispose]);
178
179
  if (el2.parentNode !== parent) return;
@@ -219,6 +220,8 @@ var frag = (tpl) => {
219
220
  attributes,
220
221
  removeAttribute(name) {
221
222
  attributes.splice(attributes.findIndex((a) => a.name === name), 1);
223
+ },
224
+ setAttributeNode() {
222
225
  }
223
226
  };
224
227
  };
@@ -234,7 +237,7 @@ directive.if = (el, evaluate, state) => {
234
237
  next.removeAttribute(":else");
235
238
  if (!next.hasAttribute(":if")) next.remove(), elseEl = next.content ? frag(next) : next, memo.set(elseEl, null);
236
239
  }
237
- return effect(() => {
240
+ return () => {
238
241
  const newEl = evaluate(state) ? ifEl : el[_prevIf] ? null : elseEl;
239
242
  if (next) next[_prevIf] = newEl === ifEl;
240
243
  if (curEl != newEl) {
@@ -245,7 +248,7 @@ directive.if = (el, evaluate, state) => {
245
248
  sprae(curEl, state);
246
249
  }
247
250
  }
248
- });
251
+ };
249
252
  };
250
253
 
251
254
  // directive/each.js
@@ -297,13 +300,13 @@ directive.each = (tpl, [itemVar, idxVar, evaluate], state) => {
297
300
  });
298
301
  };
299
302
  let planned = 0;
300
- return effect(() => {
303
+ return () => {
301
304
  items.value[_change]?.value;
302
305
  if (!planned) {
303
306
  update();
304
307
  queueMicrotask(() => (planned && update(), planned = 0));
305
308
  } else planned++;
306
- });
309
+ };
307
310
  };
308
311
  directive.each.parse = (expr) => {
309
312
  let [leftSide, itemsExpr] = expr.split(/\s+in\s+/);
@@ -320,10 +323,10 @@ directive.ref.parse = (expr) => expr;
320
323
  // directive/with.js
321
324
  directive.with = (el, evaluate, rootState) => {
322
325
  let state;
323
- return effect(() => {
326
+ return () => {
324
327
  let values = evaluate(rootState);
325
328
  sprae(el, state ? values : state = store(values, rootState));
326
- });
329
+ };
327
330
  };
328
331
 
329
332
  // directive/html.js
@@ -338,16 +341,16 @@ directive.html = (el, evaluate, state) => {
338
341
  // directive/text.js
339
342
  directive.text = (el, evaluate, state) => {
340
343
  if (el.content) el.replaceWith(el = frag(el).childNodes[0]);
341
- return effect(() => {
344
+ return () => {
342
345
  let value = evaluate(state);
343
346
  el.textContent = value == null ? "" : value;
344
- });
347
+ };
345
348
  };
346
349
 
347
350
  // directive/class.js
348
351
  directive.class = (el, evaluate, state) => {
349
352
  let cur = /* @__PURE__ */ new Set();
350
- return effect(() => {
353
+ return () => {
351
354
  let v = evaluate(state);
352
355
  let clsx = /* @__PURE__ */ new Set();
353
356
  if (v) {
@@ -358,29 +361,29 @@ directive.class = (el, evaluate, state) => {
358
361
  for (let cls of cur) if (clsx.has(cls)) clsx.delete(cls);
359
362
  else el.classList.remove(cls);
360
363
  for (let cls of cur = clsx) el.classList.add(cls);
361
- });
364
+ };
362
365
  };
363
366
 
364
367
  // directive/style.js
365
368
  directive.style = (el, evaluate, state) => {
366
369
  let initStyle = el.getAttribute("style");
367
- return effect(() => {
370
+ return () => {
368
371
  let v = evaluate(state);
369
372
  if (typeof v === "string") el.setAttribute("style", initStyle + (initStyle.endsWith(";") ? "" : "; ") + v);
370
373
  else {
371
374
  if (initStyle) el.setAttribute("style", initStyle);
372
375
  for (let k in v) k[0] == "-" ? el.style.setProperty(k, v[k]) : el.style[k] = v[k];
373
376
  }
374
- });
377
+ };
375
378
  };
376
379
 
377
380
  // directive/default.js
378
381
  directive.default = (target, evaluate, state, name) => {
379
- if (!name.startsWith("on")) return effect(() => {
382
+ if (!name.startsWith("on")) return () => {
380
383
  let value = evaluate(state);
381
384
  if (name) attr(target, name, value);
382
385
  else for (let key in value) attr(target, dashcase(key), value[key]);
383
- });
386
+ };
384
387
  const ctxs = name.split("..").map((e) => {
385
388
  let ctx = { evt: "", target, test: () => true };
386
389
  ctx.evt = (e.startsWith("on") ? e.slice(2) : e).replace(
@@ -389,12 +392,12 @@ directive.default = (target, evaluate, state, name) => {
389
392
  );
390
393
  return ctx;
391
394
  });
392
- if (ctxs.length == 1) return effect(() => addListener(evaluate(state), ctxs[0]));
395
+ if (ctxs.length == 1) return () => addListener(evaluate(state), ctxs[0]);
393
396
  let startFn, nextFn, off, idx = 0;
394
397
  const nextListener = (fn) => {
395
398
  off = addListener((e) => (off(), nextFn = fn?.(e), (idx = ++idx % ctxs.length) ? nextListener(nextFn) : startFn && nextListener(startFn)), ctxs[idx]);
396
399
  };
397
- return effect(() => (startFn = evaluate(state), !off && nextListener(startFn), () => startFn = null));
400
+ return () => (startFn = evaluate(state), !off && nextListener(startFn), () => startFn = null);
398
401
  function addListener(fn, { evt, target: target2, test, defer, stop, prevent, immediate, ...opts }) {
399
402
  if (defer) fn = defer(fn);
400
403
  const cb = (e) => {
@@ -437,6 +440,9 @@ var mods = {
437
440
  document(ctx) {
438
441
  ctx.target = document;
439
442
  },
443
+ parent(ctx) {
444
+ ctx.target = ctx.target.parentNode;
445
+ },
440
446
  throttle(ctx, limit) {
441
447
  ctx.defer = (fn) => throttle(fn, limit ? Number(limit) || 0 : 108);
442
448
  },
@@ -531,7 +537,7 @@ directive.value = (el, [getValue, setValue], state) => {
531
537
  if (el.type?.startsWith("select")) sprae(el, state);
532
538
  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);
533
539
  el.oninput = el.onchange = handleChange;
534
- return effect(() => update(getValue(state)));
540
+ return () => update(getValue(state));
535
541
  };
536
542
  directive.value.parse = (expr) => {
537
543
  let evaluate = [parse(expr)];
@@ -550,7 +556,7 @@ directive.value.parse = (expr) => {
550
556
 
551
557
  // directive/fx.js
552
558
  directive.fx = (el, evaluate, state) => {
553
- return effect(() => evaluate(state));
559
+ return () => evaluate(state);
554
560
  };
555
561
 
556
562
  // sprae.js