sprae 9.0.0 → 9.1.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,7 +1,3 @@
1
- import swapdom from 'swapdom'
2
- import * as signals from 'ulive'
3
- import justin from 'subscript/justin.js'
4
-
5
1
  // polyfill
6
2
  const _dispose = (Symbol.dispose ||= Symbol("dispose"));
7
3
 
@@ -9,13 +5,14 @@ const _dispose = (Symbol.dispose ||= Symbol("dispose"));
9
5
  const SPRAE = `∴`
10
6
 
11
7
  // signals impl
12
- export let { signal, effect, batch, computed, untracked } = signals;
8
+ export let signal, effect, batch, computed, untracked
13
9
 
14
10
  // reserved directives - order matters!
15
11
  export const directive = {};
16
12
 
17
13
  // sprae element: apply directives
18
14
  const memo = new WeakMap();
15
+
19
16
  export default function sprae(container, values) {
20
17
  if (!container.children) return // text nodes, comments etc
21
18
 
@@ -23,9 +20,10 @@ export default function sprae(container, values) {
23
20
  if (memo.has(container)) {
24
21
  const [state, effects] = memo.get(container)
25
22
  // we rewrite signals instead of update, because user should have what he provided
26
- // console.log(container, state, values)
27
- for (let k in values) state[k] = values[k]
28
- for (let fx of effects) fx()
23
+ for (let k in values) { state[k] = values[k] }
24
+ // since we call direct updates here, we have to make sure
25
+ // we don't subscribe outer effect, as in case of :each
26
+ untracked(() => { for (let fx of effects) fx() })
29
27
  }
30
28
 
31
29
  // take over existing state instead of creating clone
@@ -47,7 +45,9 @@ export default function sprae(container, values) {
47
45
 
48
46
  // NOTE: secondary directives don't stop flow nor extend state, so no need to check
49
47
  for (let name of names) {
50
- let update = (directive[name] || directive.default)(el, attr.value, state, name);
48
+ let dir = directive[name] || directive.default
49
+ let evaluate = (dir.parse || parse)(attr.value, parse)
50
+ let update = dir(el, evaluate, state, name);
51
51
  if (update) {
52
52
  update[_dispose] = effect(update);
53
53
  effects.push(update);
@@ -81,6 +81,7 @@ export default function sprae(container, values) {
81
81
  while (effects.length) effects.pop()[_dispose]();
82
82
  container.classList.remove(SPRAE)
83
83
  memo.delete(container);
84
+ // NOTE: each child disposes own children etc.
84
85
  let els = container.getElementsByClassName(SPRAE);
85
86
  while (els.length) els[0][_dispose]?.()
86
87
  }
@@ -91,29 +92,31 @@ export default function sprae(container, values) {
91
92
  // default compiler
92
93
  const evalMemo = {};
93
94
 
94
- export let compile = (expr, dir, evaluate) => {
95
- if (evaluate = evalMemo[expr = expr.trim()]) return evaluate
95
+ const parse = (expr, dir, fn) => {
96
+ if (fn = evalMemo[expr = expr.trim()]) return fn
96
97
 
97
98
  // static-time errors
98
- try {
99
- // evaluate = new Function(`__scope`, `with (__scope) { return ${expr} };`);
100
- evaluate = justin(expr);
101
- }
102
- catch (e) { throw Object.assign(e, { message: `${SPRAE} ${e.message}\n\n${dir}${expr ? `="${expr}"\n\n` : ""}`, expr }) }
99
+ try { fn = compile(expr); }
100
+ catch (e) { throw Object.assign(e, { message: `∴ ${e.message}\n\n${dir}${expr ? `="${expr}"\n\n` : ""}`, expr }) }
101
+
102
+ fn.expr = expr
103
103
 
104
104
  // runtime errors
105
- return evalMemo[expr] = evaluate
105
+ return evalMemo[expr] = fn
106
106
  }
107
107
 
108
+ // default compiler is simple new Function (tiny obfuscation against direct new Function detection)
109
+ export let compile
110
+
108
111
  // DOM swapper
109
- export let swap = swapdom
112
+ export let swap
110
113
 
111
114
  // interpolate a$<b> fields from context
112
115
  export const ipol = (v, state) => {
113
116
  return v?.replace ? v.replace(/\$<([^>]+)>/g, (match, field) => state[field]?.valueOf?.() ?? '') : v
114
117
  };
115
118
 
116
- // configure signals/compiler/differ
119
+ // configure signals/compile/differ
117
120
  // it's more compact than using sprae.signal = signal etc.
118
121
  sprae.use = s => {
119
122
  s.signal && (
@@ -124,4 +127,5 @@ sprae.use = s => {
124
127
  untracked = s.untracked || batch
125
128
  );
126
129
  s.swap && (swap = s.swap)
130
+ s.compile && (compile = s.compile)
127
131
  }
package/directive/aria.js CHANGED
@@ -1,8 +1,7 @@
1
- import { directive, compile } from "../core.js";
1
+ import { directive } from "../core.js";
2
2
  import { attr, dashcase } from './default.js'
3
3
 
4
- directive['aria'] = (el, expr, state) => {
5
- let evaluate = compile(expr, 'aria')
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
  }
@@ -1,7 +1,6 @@
1
- import { directive, compile, ipol } from "../core.js";
1
+ import { directive, ipol } from "../core.js";
2
2
 
3
- directive.class = (el, expr, state) => {
4
- let evaluate = compile(expr, 'class');
3
+ directive.class = (el, evaluate, state) => {
5
4
  let cur = new Set
6
5
  return () => {
7
6
  let v = evaluate(state);
package/directive/data.js CHANGED
@@ -1,8 +1,6 @@
1
- import { directive, compile } from "../core.js";
2
-
3
- directive['data'] = (el, expr, state) => {
4
- let evaluate = compile(expr, 'data')
1
+ import { directive } from "../core.js";
5
2
 
3
+ directive['data'] = (el, evaluate, state) => {
6
4
  return () => {
7
5
  let value = evaluate(state)?.valueOf()
8
6
  for (let key in value) el.dataset[key] = value[key];
@@ -1,15 +1,14 @@
1
- import { directive, compile, ipol } from "../core.js";
1
+ import { directive, ipol } from "../core.js";
2
2
 
3
3
  // set generic property directive
4
- directive.default = (el, expr, state, name) => {
4
+ directive.default = (el, evaluate, state, name) => {
5
5
  let evt = name.startsWith("on") && name.slice(2);
6
- let evaluate = compile(expr, name);
7
6
 
8
7
  if (evt) {
9
8
  let off
10
9
  return () => (
11
10
  off?.(), // intermediate teardown
12
- off = on(el, evt, evaluate(state))
11
+ off = on(el, evt, evaluate(state)?.valueOf())
13
12
  );
14
13
  }
15
14
 
package/directive/each.js CHANGED
@@ -1,64 +1,61 @@
1
- import sprae, { directive, compile, swap } from "../core.js";
1
+ import sprae, { directive, swap } from "../core.js";
2
2
 
3
3
  export const _each = Symbol(":each");
4
4
 
5
- const keys = {}; // boxed primitives pool
5
+ const keys = {}, _key = Symbol('key');
6
6
 
7
7
  // :each must init before :ref, :id or any others, since it defines scope
8
- directive.each = (tpl, expr, state, name) => {
9
- let [leftSide, itemsExpr] = expr.split(/\s+in\s+/);
10
- let [itemVar, idxVar = "_$"] = leftSide.split(/\s*,\s*/);
11
-
8
+ (directive.each = (tpl, [itemVar, idxVar, evaluate], state) => {
12
9
  // we need :if to be able to replace holder instead of tpl for :if :each case
13
- const holder = (tpl[_each] = document.createTextNode(""));
10
+ const holder = (tpl[_each] = document.createTextNode("")), parent = tpl.parentNode;
14
11
  tpl.replaceWith(holder);
15
12
 
16
- const evaluate = compile(itemsExpr, name);
17
- const memo = new WeakMap;
18
-
19
- tpl.removeAttribute(':key')
13
+ // key -> el
14
+ const elCache = new WeakMap, stateCache = new WeakMap
20
15
 
21
16
  let cur = [];
22
17
 
18
+ const remove = el => {
19
+ el.remove()
20
+ el[Symbol.dispose]?.()
21
+ if (el[_key]) {
22
+ elCache.delete(el[_key])
23
+ stateCache.delete(el[_key])
24
+ }
25
+ }, { insert, replace } = swap
26
+
27
+ const options = { remove, insert, replace }
28
+
29
+ // naive approach: whenever items change we replace full list
23
30
  return () => {
24
- // naive approach: whenever items change we replace full list
25
31
  let items = evaluate(state)?.valueOf(), els = [];
26
- if (typeof items === "number") items = Array.from({ length: items }, (_, i) => i);
27
32
 
28
- const count = new WeakSet
29
- for (let idx in items) {
30
- let item = items[idx]
31
- // creating via prototype is faster in both creation time & reading time
32
- let substate = Object.create(state, { [idxVar]: { value: idx } });
33
- substate[itemVar] = item; // can be changed by subsequent updates, need to be writable
34
- item = item.peek?.() ?? item; // unwrap signal
35
- let key = item.key ?? item.id ?? item;
36
- let el;
33
+ if (typeof items === "number") items = Array.from({ length: items }, (_, i) => i)
37
34
 
38
- if (key == null) el = tpl.cloneNode(true)
39
- else {
40
- // make sure key is object
41
- if (Object(key) !== key) key = (keys[key] ||= Object(key));
35
+ // let c = 0, inc = () => { if (c++ > 100) throw 'Inf recursion' }
36
+ const count = new WeakMap
37
+ for (let idx in items) {
38
+ let el, item = items[idx], key = item?.key ?? item?.id ?? item ?? idx
39
+ key = (Object(key) !== key) ? (keys[key] ||= Object(key)) : item
42
40
 
43
- if (count.has(key)) {
44
- console.warn('Duplicate key', key), el = tpl.cloneNode(true);
45
- }
46
- else {
47
- console.log(key, count.has(key))
48
- count.add(key);
49
- el = memo.get(key) || memo.set(key, tpl.cloneNode(true)).get(key);
50
- }
51
- }
41
+ if (key == null || count.has(key) || tpl.content) el = (tpl.content || tpl).cloneNode(true)
42
+ else count.set(key, 1), (el = elCache.get(key) || (elCache.set(key, tpl.cloneNode(true)), elCache.get(key)))[_key] = key;
52
43
 
53
- if (el.content) el = el.content.cloneNode(true) // <template>
44
+ // creating via prototype is faster in both creation time & reading time
45
+ let substate = stateCache.get(key) || (stateCache.set(key, Object.create(state, { [idxVar]: { value: idx } })), stateCache.get(key));
46
+ substate[itemVar] = item; // can be changed by subsequent updates, need to be writable
54
47
 
55
- sprae(el, substate)
48
+ sprae(el, substate);
56
49
 
57
50
  // document fragment
58
- if (el.nodeType === 11) els.push(...el.childNodes);
59
- else els.push(el);
51
+ if (el.nodeType === 11) els.push(...el.childNodes); else els.push(el);
60
52
  }
61
53
 
62
- swap(holder.parentNode, cur, cur = els, holder);
63
- };
64
- };
54
+ swap(parent, cur, cur = els, holder, options);
55
+ }
56
+ }).parse = (expr, parse) => {
57
+ let [leftSide, itemsExpr] = expr.split(/\s+in\s+/);
58
+ let [itemVar, idxVar = "$"] = leftSide.split(/\s*,\s*/);
59
+
60
+ return [itemVar, idxVar, parse(itemsExpr)]
61
+ }
package/directive/fx.js CHANGED
@@ -1,6 +1,5 @@
1
- import { directive, compile } from "../core.js";
1
+ import { directive } from "../core.js";
2
2
 
3
- directive.fx = (el, expr, state, name) => {
4
- let evaluate = compile(expr, name);
3
+ directive.fx = (el, evaluate, state) => {
5
4
  return () => evaluate(state);
6
5
  };
package/directive/html.js CHANGED
@@ -1,7 +1,7 @@
1
- import sprae, { directive, compile } from "../core.js";
1
+ import sprae, { directive } from "../core.js";
2
2
 
3
- directive.html = (el, expr, state, name) => {
4
- let evaluate = compile(expr, name), tpl = evaluate(state);
3
+ directive.html = (el, evaluate, state) => {
4
+ let tpl = evaluate(state);
5
5
 
6
6
  if (!tpl) return
7
7
 
package/directive/if.js CHANGED
@@ -1,17 +1,15 @@
1
- import sprae, { compile, directive, swap } from "../core.js";
1
+ import sprae, { directive, swap } from "../core.js";
2
2
  import { _each } from './each.js';
3
3
 
4
4
  // :if is interchangeable with :each depending on order, :if :each or :each :if have different meanings
5
5
  // as for :if :scope - :if must init first, since it is lazy, to avoid initializing component ahead of time by :scope
6
6
  // we consider :scope={x} :if={x} case insignificant
7
7
  const _prevIf = Symbol("if");
8
- directive.if = (ifEl, expr, state, name) => {
8
+ directive.if = (ifEl, evaluate, state) => {
9
9
  let parent = ifEl.parentNode,
10
10
  next = ifEl.nextElementSibling,
11
11
  holder = document.createTextNode(''),
12
12
 
13
- evaluate = compile(expr, name),
14
-
15
13
  // actual replaceable els (takes <template>)
16
14
  cur, ifs, elses, none = [];
17
15
 
package/directive/ref.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { directive, ipol } from "../core.js";
2
2
 
3
3
  // ref must be last within primaries, since that must be skipped by :each, but before secondaries
4
- directive.ref = (el, expr, state) => {
4
+ (directive.ref = (el, expr, state) => {
5
5
  let prev;
6
6
  return () => {
7
7
  if (prev) delete state[prev]
8
8
  state[prev = ipol(expr, state)] = el;
9
9
  }
10
- };
10
+ }).parse = expr => expr
@@ -1,9 +1,8 @@
1
- import sprae, { directive, compile } from "../core.js";
1
+ import sprae, { directive } from "../core.js";
2
2
 
3
3
  // `:each` can redefine scope as `:each="a in {myScope}"`,
4
4
  // same time per-item scope as `:each="..." :scope="{collapsed:true}"` is useful
5
- directive.scope = (el, expr, rootState, name) => {
6
- let evaluate = compile(expr, name);
5
+ directive.scope = (el, evaluate, rootState) => {
7
6
  // local state may contain signals that update, so we take them over
8
7
  return () => {
9
8
  sprae(el, { ...rootState, ...(evaluate(rootState)?.valueOf?.() || {}) });
@@ -1,7 +1,6 @@
1
- import { directive, compile, ipol } from "../core.js";
1
+ import { directive, ipol } from "../core.js";
2
2
 
3
- directive.style = (el, expr, state) => {
4
- let evaluate = compile(expr, 'style');
3
+ directive.style = (el, evaluate, state) => {
5
4
  let initStyle = el.getAttribute("style") || "";
6
5
  if (!initStyle.endsWith(";")) initStyle += "; ";
7
6
 
package/directive/text.js CHANGED
@@ -1,8 +1,7 @@
1
- import { directive, compile } from "../core.js";
1
+ import { directive } from "../core.js";
2
2
 
3
3
  // set text content
4
- directive.text = (el, expr, state) => {
5
- let evaluate = compile(expr, 'text');
4
+ directive.text = (el, evaluate, state) => {
6
5
  if (el.content) el.replaceWith(el = document.createTextNode('')) // <template :text="abc"/>
7
6
 
8
7
  return () => {
@@ -1,10 +1,8 @@
1
- import { directive, compile } from "../core.js";
1
+ import { directive } from "../core.js";
2
2
  import { attr } from './default.js';
3
3
 
4
4
  // connect expr to element value
5
- directive.value = (el, expr, state) => {
6
- let evaluate = compile(expr, 'value');
7
-
5
+ directive.value = (el, evaluate, state) => {
8
6
  let from, to;
9
7
  let update = el.type === "text" || el.type === ""
10
8
  ? (value) => el.setAttribute("value", (el.value = value == null ? "" : value))
package/dist/sprae.js CHANGED
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
 
7
7
  // node_modules/swapdom/swap-inflate.js
8
8
  var swap = (parent, a, b, end = null) => {
9
- let i = 0, cur2, next, bi, n = b.length, m = a.length, { remove, same, insert, replace } = swap;
9
+ let i = 0, cur2, next2, bi, n = b.length, m = a.length, { remove, same, insert, replace } = swap;
10
10
  while (i < n && i < m && same(a[i], b[i]))
11
11
  i++;
12
12
  while (i < n && i < m && same(b[n - 1], a[m - 1]))
@@ -17,16 +17,16 @@ var swap = (parent, a, b, end = null) => {
17
17
  else {
18
18
  cur2 = a[i];
19
19
  while (i < n) {
20
- bi = b[i++], next = cur2 ? cur2.nextSibling : end;
20
+ bi = b[i++], next2 = cur2 ? cur2.nextSibling : end;
21
21
  if (same(cur2, bi))
22
- cur2 = next;
23
- else if (i < n && same(b[i], next))
24
- replace(cur2, bi, parent), cur2 = next;
22
+ cur2 = next2;
23
+ else if (i < n && same(b[i], next2))
24
+ replace(cur2, bi, parent), cur2 = next2;
25
25
  else
26
26
  insert(cur2, bi, parent);
27
27
  }
28
28
  while (!same(cur2, end))
29
- next = cur2.nextSibling, remove(cur2, parent), cur2 = next;
29
+ next2 = cur2.nextSibling, remove(cur2, parent), cur2 = next2;
30
30
  }
31
31
  return b;
32
32
  };
@@ -123,32 +123,29 @@ var parse = (s) => (idx = 0, cur = s, s = expr(), cur[idx] ? err2() : s || "");
123
123
  var err2 = (msg = "Bad syntax", lines = cur.slice(0, idx).split("\n"), last2 = lines.pop()) => {
124
124
  let before = cur.slice(idx - 108, idx).split("\n").pop();
125
125
  let after = cur.slice(idx, idx + 108).split("\n").shift();
126
- throw EvalError(`${msg} at ${lines.length}:${last2.length} \`${idx >= 108 ? "\u2026" : ""}${before}\u25B6${after}\``, "font-weight: bold");
126
+ throw EvalError(`${msg} at ${lines.length}:${last2.length} \`${idx >= 108 ? "\u2026" : ""}${before}\u2503${after}\``, "font-weight: bold");
127
127
  };
128
- var skip = (is = 1, from = idx, l) => {
129
- if (typeof is == "number")
130
- idx += is;
131
- else
132
- while (l = is(cur.charCodeAt(idx)))
133
- idx += l;
128
+ var next = (is, from = idx, l) => {
129
+ while (l = is(cur.charCodeAt(idx)))
130
+ idx += l;
134
131
  return cur.slice(from, idx);
135
132
  };
133
+ var skip = (n = 1, from = idx) => (idx += n, cur.slice(from, idx));
136
134
  var expr = (prec = 0, end, cc, token2, newNode, fn) => {
137
- while ((cc = parse.space()) && (newNode = ((fn = lookup[cc]) && fn(token2, prec)) ?? (!token2 && parse.id())))
135
+ while ((cc = parse.space()) && (newNode = ((fn = lookup[cc]) && fn(token2, prec)) ?? (!token2 && next(parse.id))))
138
136
  token2 = newNode;
139
137
  if (end)
140
138
  cc == end ? idx++ : err2();
141
139
  return token2;
142
140
  };
143
- var isId = (c) => c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || c == 36 || c == 95 || c >= 192 && c != 215 && c != 247;
144
- var id = parse.id = () => skip(isId);
141
+ var id = parse.id = (c) => c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || c == 36 || c == 95 || c >= 192 && c != 215 && c != 247;
145
142
  var space = parse.space = (cc) => {
146
143
  while ((cc = cur.charCodeAt(idx)) <= SPACE)
147
144
  idx++;
148
145
  return cc;
149
146
  };
150
147
  var lookup = [];
151
- var token = (op, prec = SPACE, map, c = op.charCodeAt(0), l = op.length, prev = lookup[c], word = op.toUpperCase() !== op) => lookup[c] = (a, curPrec, from = idx) => curPrec < prec && (l < 2 || cur.substr(idx, l) == op) && (!word || !isId(cur.charCodeAt(idx + l))) && (idx += l, map(a, curPrec)) || (idx = from, prev?.(a, curPrec));
148
+ var token = (op, prec = SPACE, map, c = op.charCodeAt(0), l = op.length, prev = lookup[c], word = op.toUpperCase() !== op) => lookup[c] = (a, curPrec, from = idx) => curPrec < prec && (l < 2 || cur.substr(idx, l) == op) && (!word || !parse.id(cur.charCodeAt(idx + l))) && (idx += l, map(a, curPrec)) || (idx = from, prev?.(a, curPrec));
152
149
  var binary = (op, prec, right = false) => token(op, prec, (a, b) => a && (b = expr(prec - (right ? 0.5 : 0))) && [op, a, b]);
153
150
  var unary = (op, prec, post) => token(op, prec, (a) => post ? a && [op, a] : !a && (a = expr(prec - 0.5)) && [op, a]);
154
151
  var nary = (op, prec) => {
@@ -171,10 +168,10 @@ var prop = (a, fn, generic, obj, path) => a[0] === "()" ? prop(a[1], fn, generic
171
168
  var compile_default = compile;
172
169
 
173
170
  // node_modules/subscript/feature/number.js
174
- var num = (a) => a ? err2() : [, (a = +skip((c) => c === PERIOD || c >= _0 && c <= _9 || (c === _E || c === _e ? 2 : 0))) != a ? err2() : a];
171
+ var num = (a, _) => [, (a = +next((c) => c === PERIOD || c >= _0 && c <= _9 || (c === _E || c === _e ? 2 : 0))) != a ? err2() : a];
175
172
  lookup[PERIOD] = (a) => !a && num();
176
173
  for (let i = _0; i <= _9; i++)
177
- lookup[i] = num;
174
+ lookup[i] = (a) => a ? err2() : num();
178
175
 
179
176
  // node_modules/subscript/feature/string.js
180
177
  var escape = { n: "\n", r: "\r", t: " ", b: "\b", f: "\f", v: "\v" };
@@ -275,14 +272,14 @@ operator("=", (a, b) => (b = compile(b), prop(a, (container, path, ctx) => conta
275
272
  var subscript_default = (s) => compile_default(parse_default(s));
276
273
 
277
274
  // node_modules/subscript/feature/comment.js
278
- token("/*", PREC_TOKEN, (a, prec) => (skip((c) => c !== STAR && cur.charCodeAt(idx + 1) !== 47), skip(2), a || expr(prec) || []));
279
- token("//", PREC_TOKEN, (a, prec) => (skip((c) => c >= SPACE), a || expr(prec) || [""]));
275
+ token("/*", PREC_TOKEN, (a, prec) => (next((c) => c !== STAR && cur.charCodeAt(idx + 1) !== 47), skip(2), a || expr(prec) || []));
276
+ token("//", PREC_TOKEN, (a, prec) => (next((c) => c >= SPACE), a || expr(prec) || [""]));
280
277
 
281
278
  // node_modules/subscript/feature/pow.js
282
279
  binary("**", PREC_EXP, true), operator("**", (a, b) => b && (a = compile(a), b = compile(b), (ctx) => a(ctx) ** b(ctx)));
283
280
 
284
281
  // node_modules/subscript/feature/ternary.js
285
- token("?", PREC_ASSIGN, (a, b, c) => a && (b = expr(PREC_ASSIGN, COLON)) && (c = expr(PREC_ASSIGN + 1), ["?", a, b, c]));
282
+ token("?", PREC_ASSIGN, (a, b, c) => a && (b = expr(PREC_ASSIGN - 0.5, COLON)) && (c = expr(PREC_ASSIGN - 0.5), ["?", a, b, c]));
286
283
  operator("?", (a, b, c) => (a = compile(a), b = compile(b), c = compile(c), (ctx) => a(ctx) ? b(ctx) : c(ctx)));
287
284
 
288
285
  // node_modules/subscript/feature/bool.js
@@ -291,13 +288,16 @@ token("false", PREC_TOKEN, (a) => a ? err() : [, false]);
291
288
 
292
289
  // node_modules/subscript/feature/array.js
293
290
  group("[]", PREC_TOKEN);
294
- operator("[]", (a, b) => !a ? () => [] : a[0] === "," ? (a = a.slice(1).map(compile), (ctx) => a.map((a2) => a2(ctx))) : (a = compile(a), (ctx) => [a(ctx)]));
291
+ operator(
292
+ "[]",
293
+ (a, b) => (a = !a ? [] : a[0] === "," ? a.slice(1) : [a], a = a.map((a2) => a2[0] === "..." ? (a2 = compile(a2[1]), (ctx) => a2(ctx)) : (a2 = compile(a2), (ctx) => [a2(ctx)])), (ctx) => a.flatMap((a2) => a2(ctx)))
294
+ );
295
295
 
296
296
  // node_modules/subscript/feature/object.js
297
297
  group("{}", PREC_TOKEN);
298
- operator("{}", (a, b) => !a ? () => ({}) : a[0] === "," ? (a = a.slice(1).map(compile), (ctx) => Object.fromEntries(a.map((a2) => a2(ctx)))) : a[0] === ":" ? (a = compile(a), (ctx) => Object.fromEntries([a(ctx)])) : (b = compile(a), (ctx) => ({ [a]: b(ctx) })));
299
- binary(":", PREC_ASSIGN, true);
300
- operator(":", (a, b) => (b = compile(b), a = Array.isArray(a) ? compile(a) : ((a2) => a2).bind(0, a), (ctx) => [a(ctx), b(ctx)]));
298
+ operator("{}", (a, b) => (a = !a ? [] : a[0] !== "," ? [a] : a.slice(1), a = a.map((p) => compile(typeof p === "string" ? [":", p, p] : p)), (ctx) => Object.fromEntries(a.flatMap((frag) => frag(ctx)))));
299
+ binary(":", PREC_ASSIGN - 0.5, true);
300
+ operator(":", (a, b) => (b = compile(b), Array.isArray(a) ? (a = compile(a), (ctx) => [[a(ctx), b(ctx)]]) : (ctx) => [[a, b(ctx)]]));
301
301
 
302
302
  // node_modules/subscript/feature/arrow.js
303
303
  binary("=>", PREC_ASSIGN, true);
@@ -315,11 +315,15 @@ token("?.", PREC_ACCESS, (a, b) => a && (b = expr(PREC_ACCESS), !b?.map) && ["?.
315
315
  operator("?.", (a, b) => b && (a = compile(a), (ctx) => a(ctx)?.[b]));
316
316
  operator("(", (a, b, container, args, path, optional) => a[0] === "?." && (a[2] || Array.isArray(a[1])) && (args = !b ? () => [] : b[0] === "," ? (b = b.slice(1).map(compile), (ctx) => b.map((a2) => a2(ctx))) : (b = compile(b), (ctx) => [b(ctx)]), !a[2] && (optional = true, a = a[1]), a[0] === "[" ? path = compile(a[2]) : path = () => a[2], container = compile(a[1]), optional ? (ctx) => container(ctx)?.[path(ctx)]?.(...args(ctx)) : (ctx) => container(ctx)?.[path(ctx)](...args(ctx))));
317
317
 
318
+ // node_modules/subscript/feature/spread.js
319
+ unary("...", PREC_PREFIX);
320
+ operator("...", (a) => (a = compile(a), (ctx) => Object.entries(a(ctx))));
321
+
318
322
  // node_modules/subscript/justin.js
319
323
  binary("in", PREC_COMP), operator("in", (a, b) => b && (a = compile_default(a), b = compile_default(b), (ctx) => a(ctx) in b(ctx)));
320
324
  binary("===", PREC_EQ), binary("!==", 9);
321
325
  operator("===", (a, b) => (a = compile_default(a), b = compile_default(b), (ctx) => a(ctx) === b(ctx)));
322
- operator("===", (a, b) => (a = compile_default(a), b = compile_default(b), (ctx) => a(ctx) !== b(ctx)));
326
+ operator("!==", (a, b) => (a = compile_default(a), b = compile_default(b), (ctx) => a(ctx) !== b(ctx)));
323
327
  binary("??", PREC_LOR);
324
328
  operator("??", (a, b) => b && (a = compile_default(a), b = compile_default(b), (ctx) => a(ctx) ?? b(ctx)));
325
329
  binary("??=", PREC_ASSIGN, true);
@@ -344,10 +348,13 @@ function sprae(container, values) {
344
348
  return;
345
349
  if (memo.has(container)) {
346
350
  const [state2, effects2] = memo.get(container);
347
- for (let k in values)
351
+ for (let k in values) {
348
352
  state2[k] = values[k];
349
- for (let fx of effects2)
350
- fx();
353
+ }
354
+ untracked2(() => {
355
+ for (let fx of effects2)
356
+ fx();
357
+ });
351
358
  }
352
359
  const state = values || {};
353
360
  const effects = [];
@@ -439,9 +446,7 @@ directive.each = (tpl, expr2, state, name) => {
439
446
  let item = items[idx2];
440
447
  let substate = Object.create(state, { [idxVar]: { value: idx2 } });
441
448
  substate[itemVar] = item;
442
- item = item.peek?.() ?? item;
443
- let key = item.key ?? item.id ?? item;
444
- let el;
449
+ let el, key = item.key ?? item.id ?? item;
445
450
  if (key == null)
446
451
  el = tpl.cloneNode(true);
447
452
  else {
@@ -450,7 +455,6 @@ directive.each = (tpl, expr2, state, name) => {
450
455
  if (count.has(key)) {
451
456
  console.warn("Duplicate key", key), el = tpl.cloneNode(true);
452
457
  } else {
453
- console.log(key, count.has(key));
454
458
  count.add(key);
455
459
  el = memo2.get(key) || memo2.set(key, tpl.cloneNode(true)).get(key);
456
460
  }
@@ -470,24 +474,24 @@ directive.each = (tpl, expr2, state, name) => {
470
474
  // directive/if.js
471
475
  var _prevIf = Symbol("if");
472
476
  directive.if = (ifEl, expr2, state, name) => {
473
- let parent = ifEl.parentNode, next = ifEl.nextElementSibling, holder = document.createTextNode(""), evaluate = compile2(expr2, name), cur2, ifs, elses, none = [];
477
+ let parent = ifEl.parentNode, next2 = ifEl.nextElementSibling, holder = document.createTextNode(""), evaluate = compile2(expr2, name), cur2, ifs, elses, none = [];
474
478
  ifEl.after(holder);
475
479
  if (ifEl.content)
476
480
  cur2 = none, ifEl.remove(), ifs = [...ifEl.content.childNodes];
477
481
  else
478
482
  ifs = cur2 = [ifEl];
479
- if (next?.hasAttribute(":else")) {
480
- next.removeAttribute(":else");
481
- if (next.hasAttribute(":if"))
483
+ if (next2?.hasAttribute(":else")) {
484
+ next2.removeAttribute(":else");
485
+ if (next2.hasAttribute(":if"))
482
486
  elses = none;
483
487
  else
484
- next.remove(), elses = next.content ? [...next.content.childNodes] : [next];
488
+ next2.remove(), elses = next2.content ? [...next2.content.childNodes] : [next2];
485
489
  } else
486
490
  elses = none;
487
491
  return () => {
488
492
  const newEls = evaluate(state)?.valueOf() ? ifs : ifEl[_prevIf] ? none : elses;
489
- if (next)
490
- next[_prevIf] = newEls === ifs;
493
+ if (next2)
494
+ next2[_prevIf] = newEls === ifs;
491
495
  if (cur2 != newEls) {
492
496
  if (cur2[0]?.[_each])
493
497
  cur2 = [cur2[0][_each]];