sprae 2.2.3 → 2.3.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.
@@ -54,7 +54,7 @@
54
54
  </ul>
55
55
  </section>
56
56
  <footer class="footer">
57
- <span class="todo-count" :with="{count: todos.filter(item => !item.done).length}">
57
+ <span class="todo-count">
58
58
  <strong :text="count">#</strong> <span :text="plur('item', count)">items</span> left
59
59
  </span>
60
60
  <ul class="filters">
@@ -89,6 +89,7 @@
89
89
  let state = sprae(document.body, {
90
90
  plur,
91
91
  todos: JSON.parse(localStorage.getItem('todomvc.items') || '[]'),
92
+ get count() { return this.todos.filter(item => !item.done).length },
92
93
  hash: window.location.hash || '#/',
93
94
  save: items => localStorage.setItem('todomvc.items', JSON.stringify(items))
94
95
  })
package/package.json CHANGED
@@ -1,16 +1,14 @@
1
1
  {
2
2
  "name": "sprae",
3
3
  "description": "DOM microhydration.",
4
- "version": "2.2.3",
4
+ "version": "2.3.0",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
7
7
  "type": "module",
8
8
  "dependencies": {
9
9
  "@preact/signals-core": "^1.2.2",
10
- "element-props": "^2.4.2",
11
10
  "primitive-pool": "^2.0.0",
12
11
  "signal-struct": "^1.6.0",
13
- "sube": "^2.2.1",
14
12
  "swapdom": "^1.1.1"
15
13
  },
16
14
  "devDependencies": {
package/plan.md CHANGED
@@ -22,14 +22,16 @@
22
22
  * [x] bulk events :ona:onb
23
23
  * [x] multiprop setter :a:b="c"
24
24
  * [x] make `this` in expression an element
25
- * [ ] replace :ref with :with="this as x"
25
+ * ~~[x] replace :ref with :with="this as x"~~
26
+ * [x] :ref creates instance in current state, not creates a new state
27
+ * [x] to avoid extending signal-struct, we must collect state data before, and call updates after for extended state
28
+ * [x] optimization: replace element-props with direct (better) setters
29
+ * [x] Make sure `false` gets serialized, not removes attr
30
+ * [ ] Sandbox expressions: no global, no "scope" object name, no "arguments"
31
+ * ~~[x] report usignal problem~~ author is not really interested
26
32
  * [ ] :oninit
27
- * [ ] optimization: replace element-props with direct (better) setters
28
- * [ ] Make sure `false` gets serialized, not removes attr
29
- * [ ] report usignal problem
30
33
  * [ ] :onconnect, :ondisconnect
31
34
  * [ ] frameworks benchmark
32
35
  * [ ] examples
33
36
  * [x] todomvc
34
- * [ ] wave-edit
35
- * [ ] Sandbox expressions: no global, no "scope" object name, no "arguments"
37
+ * [ ] wave-edit
package/r&d.md CHANGED
@@ -97,6 +97,18 @@
97
97
 
98
98
  -> possibly we have to just subscribe via mechanism of signals-like deps, and :with just initializes subtree with extended object
99
99
 
100
+ ## [x] Get rid of :with? -> let's get rid of until really necessary
101
+
102
+ + with is bad JS practice/association
103
+ +? is there a bona-fide use case?
104
+ + the implementation is heavy/unoptimal: two assign-updates happen: for root, for children
105
+ + it is exception blocking streamline implementation of refs
106
+ + it shadows data which creates all sorts of nasty debugging effects / states. Isn't it better to keep data/state transparent?
107
+ + it even enables transparency of :each scopes, since they inherit root scope
108
+ + it's easier to look out for data in one single place (state init), rather than in a bunch of markup locations
109
+ +?! can be replaced with sort of `<x :xyz="xyz=...calc"></x>`, no?
110
+ -> would need wrapping noname scope access
111
+
100
112
  ## [x] Should we inherit values from `init` in `sprae(el, init)`, instead of creating a snapshot of reactive values in `init`? -> nah, nice idea but too little use. Better create signals struct.
101
113
 
102
114
  + it allows passing any arbitrary scope to initialize from.
@@ -259,6 +271,13 @@
259
271
  + that "unlimits" returned struct, so that any property can be added/deleted.
260
272
  - doesn't really save from `new (()=>{}).constructor` hack: we gotta substitute objects too.
261
273
 
274
+ 2. Use sandboxed proxy
275
+ - tough evaluation
276
+ - tough implementation
277
+ - screwed up data
278
+ - no full protection
279
+ + does minimal catch
280
+
262
281
  ## [x] :onclick="direct code" ? -> no: immediately invoked.
263
282
 
264
283
  + compatible with direct `onclick=...`
@@ -268,7 +287,7 @@
268
287
  - illicit `event` object
269
288
  - conflicts with regular attrs logic: the code is immediately invoked and can assign a function.
270
289
 
271
- ## [ ] Should getters convert to computed?
290
+ ## [x] Should getters convert to computed? -> yes, that's relatively cheap and useful
272
291
 
273
292
  + shorter and nicer syntax
274
293
  - possibly longer inti
package/readme.md CHANGED
@@ -62,7 +62,7 @@ Control flow of elements.
62
62
  Multiply element. `index` value starts from 1.
63
63
 
64
64
  ```html
65
- <ul :with="{items: ['a','b','c']}">
65
+ <ul>
66
66
  <li :each="item in items" :text="item">Untitled</li>
67
67
  </ul>
68
68
 
@@ -176,28 +176,6 @@ Invoked when element is connected or disconnected from DOM.
176
176
  ```
177
177
  -->
178
178
 
179
- #### `:with="data"`
180
-
181
- Set data for a subtree fragment scope.
182
-
183
- ```html
184
- <!-- Inline data -->
185
- <x :with="{ foo: 'bar' }" :text="foo"></x>
186
-
187
- <!-- External data -->
188
- <y :with="data"></y>
189
-
190
- <!-- Inheritance -->
191
- <x :with="{ foo: 'bar' }">
192
- <y :with="{ baz: 'qux' }" :text="foo + baz"></y>
193
- </x>
194
-
195
- <!-- Single property -->
196
- <li :with="this as li">
197
- <input :onfocus-onblur="e => (li.classList.add('editing'), e => li.classList.remove('editing'))" />
198
- </li>
199
- ```
200
-
201
179
  #### `:data="values"`
202
180
 
203
181
  Set [data-*](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*) attributes. CamelCase is converted to dash-case.
@@ -222,15 +200,29 @@ Set [aria-role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA)
222
200
 
223
201
  #### `:ref="id"`
224
202
 
225
- Expose element to a subtree fragment with the `id`.
203
+ Expose element to a subtree fragment with the `id`, as well as to scope:
226
204
 
227
205
  ```html
228
- <li :ref="item">
229
- <input
230
- :onfocus="e => item.classList.add('editing')"
231
- :onblur="e => item.classList.remove('editing')"
232
- />
233
- </li>
206
+ <!-- single item -->
207
+ <textarea :id:ref="text" placeholder="Enter text..."></textarea>
208
+
209
+ <!-- iterable items -->
210
+ <ul>
211
+ <li :each="item in items" :ref="item">
212
+ <input
213
+ :onfocus="e => item.classList.add('editing')"
214
+ :onblur="e => item.classList.remove('editing')"
215
+ />
216
+ </li>
217
+ </ul>
218
+
219
+ <script type="module">
220
+ import sprae from 'sprae';
221
+ let state = sprae(document, {items: ['a','b','c']})
222
+
223
+ // exposes element in state
224
+ state.text // <textarea id="text"></textarea>
225
+ </script>
234
226
  ```
235
227
 
236
228
  <!--
@@ -257,20 +249,7 @@ This way, for example, _@preact/signals_ or _rxjs_ can be connected directly byp
257
249
 
258
250
  ## Hints
259
251
 
260
- **1.** Attributes are initialized in order, so pay attention providing `:scope`/`:ref` attributes:
261
-
262
- ```html
263
- <li :each="item in items" :ref="li">
264
- <button :onclick="e => li.classList.add('loading')"></button>
265
- </li>
266
-
267
- <li :ref="li" :each="item in items">
268
- <!-- Invalid: li is undefined -->
269
- <button :onclick="e => li.classList.add('loading')"></button>
270
- </li>
271
- ```
272
-
273
- **2.** Data allows signals values, which can be an alternative way to control template state:
252
+ **1.** Data allows signals values, which can be an alternative way to control template state:
274
253
 
275
254
  ```html
276
255
  <div id="done" :text="loading ? 'loading' : result">...</div>
@@ -292,7 +271,7 @@ This way, for example, _@preact/signals_ or _rxjs_ can be connected directly byp
292
271
  </script>
293
272
  ```
294
273
 
295
- **3.** Data recognizes reactive values as inputs as well: _Promise_ / _Thenable_, _Observable_ / _Subscribable_, _AsyncIterable_ (etc., see [sube](https://github.com/dy/sube/blob/main/README.md)). This way, for example, _rxjs_ can be connected to template directly.
274
+ **2.** Data recognizes reactive values as inputs as well: _Promise_ / _Thenable_, _Observable_ / _Subscribable_, _AsyncIterable_ (etc., see [sube](https://github.com/dy/sube/blob/main/README.md)). This way, for example, _rxjs_ can be connected to template directly.
296
275
 
297
276
  ```html
298
277
  <div :text="clicks">#</div> clicks
@@ -306,7 +285,7 @@ This way, for example, _@preact/signals_ or _rxjs_ can be connected directly byp
306
285
  </script>
307
286
  ```
308
287
 
309
- **4.** Getters turn into computed values automatically:
288
+ **3.** Getters turn into computed values automatically:
310
289
 
311
290
  ```html
312
291
  <div id="x-plus-y">
package/sprae.js CHANGED
@@ -32,16 +32,6 @@ function t() {
32
32
  } else
33
33
  n--;
34
34
  }
35
- function h(i2) {
36
- if (n > 0)
37
- return i2();
38
- n++;
39
- try {
40
- return i2();
41
- } finally {
42
- t();
43
- }
44
- }
45
35
  var o = void 0;
46
36
  var r = void 0;
47
37
  var n = 0;
@@ -409,30 +399,6 @@ function isObject(v2) {
409
399
  return v2 && v2.constructor === Object;
410
400
  }
411
401
 
412
- // node_modules/element-props/element-props.js
413
- var prop = (el2, k, v2) => {
414
- if (k.startsWith("on"))
415
- k = k.toLowerCase();
416
- if (el2[k] !== v2) {
417
- el2[k] = v2;
418
- }
419
- if (v2 == null || v2 === false)
420
- el2.removeAttribute(k);
421
- else if (typeof v2 !== "function")
422
- el2.setAttribute(
423
- dashcase(k),
424
- v2 === true ? "" : typeof v2 === "number" || typeof v2 === "string" ? v2 : k === "class" ? (Array.isArray(v2) ? v2 : Object.entries(v2).map(([k2, v3]) => v3 ? k2 : "")).filter(Boolean).join(" ") : k === "style" ? Object.entries(v2).map(([k2, v3]) => `${k2}: ${v3}`).join(";") : ""
425
- );
426
- };
427
- var input = (el2) => [
428
- el2.type === "checkbox" ? () => el2.checked : () => el2.value,
429
- el2.type === "text" || el2.type === "" ? (value) => el2.value = value == null ? "" : value : el2.type === "checkbox" ? (value) => (el2.value = value ? "on" : "", prop(el2, "checked", value)) : el2.type === "select-one" ? (value) => ([...el2.options].map((el3) => el3.removeAttribute("selected")), el2.value = value, el2.selectedOptions[0]?.setAttribute("selected", "")) : (value) => el2.value = value
430
- ];
431
- var el = document.createElement("div");
432
- function dashcase(str) {
433
- return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => "-" + match.toLowerCase());
434
- }
435
-
436
402
  // node_modules/swapdom/swap-inflate.js
437
403
  var swap = (parent, a2, b2, end = null) => {
438
404
  let i2 = 0, cur, next, bi, n2 = b2.length, m = a2.length, { remove, same, insert, replace } = swap;
@@ -484,39 +450,47 @@ var primitive_pool_default = (key) => {
484
450
  };
485
451
 
486
452
  // src/directives.js
487
- var directives_default = (el2, expr, state, name) => {
453
+ var directives = {};
454
+ var directives_default = (el, expr, values, name) => {
488
455
  if (name.startsWith("on")) {
489
- return directives.on(el2, `{"${name.split("-").map((n2) => n2.startsWith("on") ? n2.slice(2) : n2).join("-")}": ${expr}}`, state);
456
+ return directives.on(el, `{"${name.split("-").map((n2) => n2.startsWith("on") ? n2.slice(2) : n2).join("-")}": ${expr}}`, values);
490
457
  }
491
- let evaluate = parseExpr(el2, expr, ":" + name, state);
492
- const update = (value) => prop(el2, name, value);
493
- b(() => update(evaluate(state)));
458
+ let evaluate = parseExpr(el, expr, ":" + name);
459
+ return (state) => attr(el, name, evaluate(state));
460
+ };
461
+ var attr = (el, name, v2) => {
462
+ if (v2 == null || v2 === false)
463
+ el.removeAttribute(name);
464
+ else
465
+ el.setAttribute(name, v2 === true ? "" : typeof v2 === "number" || typeof v2 === "string" ? v2 : "");
466
+ };
467
+ directives[""] = (el, expr, values) => {
468
+ let evaluate = parseExpr(el, expr, ":");
469
+ return (state) => {
470
+ let value = evaluate(state);
471
+ for (let key in value)
472
+ attr(el, dashcase(key), value[key]);
473
+ };
494
474
  };
495
- var directives = {};
496
475
  var _each = Symbol(":each");
497
476
  var _ref = Symbol(":ref");
498
- directives["with"] = (el2, expr, rootState) => {
499
- let evaluate = parseExpr(el2, expr, "with", rootState);
500
- const params = w(() => Object.assign({}, rootState, evaluate(rootState)));
501
- let state = sprae(el2, params.value);
502
- b((values = params.value) => h(() => Object.assign(state, values)));
503
- return false;
504
- };
505
- directives["ref"] = (el2, expr, state) => {
506
- if (el2.hasAttribute(":each"))
507
- return el2[_ref] = expr;
508
- sprae(el2, Object.assign(Object.create(state), { [expr]: el2 }));
509
- return false;
477
+ directives["ref"] = (el, expr, values) => {
478
+ if (el.hasAttribute(":each")) {
479
+ el[_ref] = expr;
480
+ return;
481
+ }
482
+ ;
483
+ values[expr] = el;
510
484
  };
511
- directives["if"] = (el2, expr, state) => {
512
- let holder = document.createTextNode(""), clauses = [parseExpr(el2, expr, ":if", state)], els = [el2], cur = el2;
513
- while (cur = el2.nextElementSibling) {
485
+ directives["if"] = (el, expr, values) => {
486
+ let holder = document.createTextNode(""), clauses = [parseExpr(el, expr, ":if")], els = [el], cur = el;
487
+ while (cur = el.nextElementSibling) {
514
488
  if (cur.hasAttribute(":else")) {
515
489
  cur.removeAttribute(":else");
516
490
  if (expr = cur.getAttribute(":if")) {
517
491
  cur.removeAttribute(":if"), cur.remove();
518
492
  els.push(cur);
519
- clauses.push(parseExpr(el2, expr, ":else :if", state));
493
+ clauses.push(parseExpr(el, expr, ":else :if"));
520
494
  } else {
521
495
  cur.remove();
522
496
  els.push(cur);
@@ -525,50 +499,53 @@ directives["if"] = (el2, expr, state) => {
525
499
  } else
526
500
  break;
527
501
  }
528
- el2.replaceWith(cur = holder);
529
- let idx = w(() => clauses.findIndex((f2) => f2(state)));
530
- b((i2 = idx.value) => els[i2] != cur && ((cur[_each] || cur).replaceWith(cur = els[i2] || holder), sprae(cur, state)));
531
- return -els.length;
502
+ el.replaceWith(cur = holder);
503
+ return (state) => {
504
+ let i2 = clauses.findIndex((f2) => f2(state));
505
+ if (els[i2] != cur) {
506
+ (cur[_each] || cur).replaceWith(cur = els[i2] || holder);
507
+ sprae(cur, state);
508
+ }
509
+ };
532
510
  };
533
- directives["each"] = (tpl, expr, state) => {
511
+ directives["each"] = (tpl, expr, values) => {
534
512
  let each = parseForExpression(expr);
535
513
  if (!each)
536
514
  return exprError(new Error(), tpl, expr);
537
- const getItems = parseExpr(tpl, each.items, ":each", state);
538
515
  const holder = tpl[_each] = document.createTextNode("");
539
516
  tpl.replaceWith(holder);
540
- const items = w(() => {
541
- let list = getItems(state);
542
- if (!list)
543
- return [];
544
- if (typeof list === "number")
545
- return Array.from({ length: list }, (_2, i2) => [i2, i2 + 1]);
546
- if (list.constructor === Object)
547
- return Object.entries(list);
548
- if (Array.isArray(list))
549
- return list.map((item, i2) => [i2 + 1, item]);
550
- exprError(Error("Bad list value"), tpl, each.items, ":each", list);
551
- });
517
+ const evaluate = parseExpr(tpl, each.items, ":each");
552
518
  const scopes = /* @__PURE__ */ new WeakMap();
553
519
  const itemEls = /* @__PURE__ */ new WeakMap();
554
520
  let curEls = [];
555
- b((list = items.value) => {
521
+ return (state) => {
522
+ let list = evaluate(state);
523
+ if (!list)
524
+ list = [];
525
+ else if (typeof list === "number")
526
+ list = Array.from({ length: list }, (_2, i2) => [i2, i2 + 1]);
527
+ else if (list.constructor === Object)
528
+ list = Object.entries(list);
529
+ else if (Array.isArray(list))
530
+ list = list.map((item, i2) => [i2 + 1, item]);
531
+ else
532
+ exprError(Error("Bad list value"), tpl, each.items, ":each", list);
556
533
  let newEls = [], elScopes = [];
557
534
  for (let [idx, item] of list) {
558
535
  let itemKey = primitive_pool_default(item);
559
- let el2 = itemEls.get(itemKey);
560
- if (!el2) {
561
- el2 = tpl.cloneNode(true);
562
- itemEls.set(itemKey, el2);
536
+ let el = itemEls.get(itemKey);
537
+ if (!el) {
538
+ el = tpl.cloneNode(true);
539
+ itemEls.set(itemKey, el);
563
540
  }
564
- newEls.push(el2);
541
+ newEls.push(el);
565
542
  if (!scopes.has(itemKey)) {
566
543
  let scope = Object.create(state);
567
544
  scope[each.item] = item;
568
545
  if (each.index)
569
546
  scope[each.index] = idx;
570
547
  if (tpl[_ref])
571
- scope[tpl[_ref]] = el2;
548
+ scope[tpl[_ref]] = el;
572
549
  scopes.set(itemKey, scope);
573
550
  }
574
551
  elScopes.push(scopes.get(itemKey));
@@ -578,8 +555,7 @@ directives["each"] = (tpl, expr, state) => {
578
555
  for (let i2 = 0; i2 < newEls.length; i2++) {
579
556
  sprae(newEls[i2], elScopes[i2]);
580
557
  }
581
- });
582
- return -1;
558
+ };
583
559
  };
584
560
  function parseForExpression(expression) {
585
561
  let forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/;
@@ -600,110 +576,122 @@ function parseForExpression(expression) {
600
576
  }
601
577
  return res;
602
578
  }
603
- directives["id"] = (el2, expr, state) => {
604
- let evaluate = parseExpr(el2, expr, ":id", state);
605
- const update = (v2) => el2.id = v2 || v2 === 0 ? v2 : "";
606
- b(() => update(evaluate(state)));
607
- };
608
- directives[""] = (el2, expr, state) => {
609
- let evaluate = parseExpr(el2, expr, ":", state);
610
- const update = (value) => {
611
- if (!value)
612
- return;
613
- for (let key in value)
614
- prop(el2, key, value[key]);
579
+ directives["id"] = (el, expr, values) => {
580
+ let evaluate = parseExpr(el, expr, ":id");
581
+ const update = (v2) => el.id = v2 || v2 === 0 ? v2 : "";
582
+ return (state) => update(evaluate(state));
583
+ };
584
+ directives["class"] = (el, expr, values) => {
585
+ let evaluate = parseExpr(el, expr, ":class");
586
+ let initClassName = el.className;
587
+ return (state) => {
588
+ let v2 = evaluate(state);
589
+ el.className = initClassName + typeof v2 === "string" ? v2 : (Array.isArray(v2) ? v2 : Object.entries(v2).map(([k, v3]) => v3 ? k : "")).filter(Boolean).join(" ");
615
590
  };
616
- b(() => update(evaluate(state)));
617
591
  };
618
- directives["text"] = (el2, expr, state) => {
619
- let evaluate = parseExpr(el2, expr, ":text", state);
620
- const update = (value) => {
621
- el2.textContent = value == null ? "" : value;
592
+ directives["style"] = (el, expr, values) => {
593
+ let evaluate = parseExpr(el, expr, ":style");
594
+ return (state) => {
595
+ let v2 = evaluate(state);
596
+ if (typeof v2 === "string")
597
+ el.setAttribute("style", v2);
598
+ else
599
+ for (let k in v2)
600
+ el.style[k] = v2[k];
622
601
  };
623
- b(() => update(evaluate(state)));
624
602
  };
625
- directives["value"] = (el2, expr, state) => {
626
- let evaluate = parseExpr(el2, expr, ":in", state);
627
- let [get, set] = input(el2);
603
+ directives["text"] = (el, expr, values) => {
604
+ let evaluate = parseExpr(el, expr, ":text");
628
605
  const update = (value) => {
629
- prop(el2, "value", value);
630
- set(value);
606
+ el.textContent = value == null ? "" : value;
631
607
  };
632
- b(() => update(evaluate(state)));
608
+ return (state) => update(evaluate(state));
633
609
  };
634
- directives["on"] = (el2, expr, state) => {
635
- let evaluate = parseExpr(el2, expr, ":on", state);
610
+ directives["value"] = (el, expr, values) => {
611
+ let evaluate = parseExpr(el, expr, ":in");
612
+ let update = el.type === "text" || el.type === "" ? (value) => el.setAttribute("value", el.value = value == null ? "" : value) : el.type === "checkbox" ? (value) => (el.value = value ? "on" : "", attr(el, "checked", value)) : el.type === "select-one" ? (value) => ([...el.options].map((el2) => el2.removeAttribute("selected")), el.value = value, el.selectedOptions[0]?.setAttribute("selected", "")) : (value) => el.value = value;
613
+ return (state) => update(evaluate(state));
614
+ };
615
+ directives["on"] = (el, expr, values) => {
616
+ let evaluate = parseExpr(el, expr, ":on");
636
617
  let listeners = {};
637
- b(() => {
618
+ return (state) => {
638
619
  for (let evt in listeners)
639
- el2.removeEventListener(evt, listeners[evt]);
620
+ el.removeEventListener(evt, listeners[evt]);
640
621
  listeners = evaluate(state);
641
622
  for (let evt in listeners) {
642
623
  const evts = evt.split("-");
643
624
  if (evts.length === 1)
644
- el2.addEventListener(evt, listeners[evt]);
625
+ el.addEventListener(evt, listeners[evt]);
645
626
  else {
627
+ const startFn = listeners[evt];
646
628
  const nextEvt = (fn, cur = 0) => {
647
- el2.addEventListener(evts[cur], listeners[evt] = (e2) => {
629
+ el.addEventListener(evts[cur], listeners[evt] = (e2) => {
648
630
  fn = fn(e2);
649
- el2.removeEventListener(evts[cur], listeners[evt]);
650
- nextEvt(fn, (cur + 1) % evts.length);
631
+ el.removeEventListener(evts[cur], listeners[evt]);
632
+ if (++cur < evts.length && typeof fn === "function")
633
+ nextEvt(fn, cur);
634
+ else
635
+ nextEvt(startFn);
651
636
  });
652
637
  };
653
- nextEvt(listeners[evt]);
638
+ nextEvt(startFn);
654
639
  }
655
640
  }
656
641
  ;
657
- });
642
+ };
658
643
  };
659
- directives["data"] = (el2, expr, state) => {
660
- let evaluate = parseExpr(el2, expr, ":data", state);
661
- const value = w(() => evaluate(state));
662
- b((v2 = value.value) => {
663
- for (let key in v2)
664
- el2.dataset[key] = v2[key];
665
- });
644
+ directives["data"] = (el, expr, values) => {
645
+ let evaluate = parseExpr(el, expr, ":data");
646
+ return (state) => {
647
+ let value = evaluate(state);
648
+ for (let key in value)
649
+ el.dataset[key] = value[key];
650
+ };
666
651
  };
667
- directives["aria"] = (el2, expr, state) => {
668
- let evaluate = parseExpr(el2, expr, ":aria", state);
652
+ directives["aria"] = (el, expr, values) => {
653
+ let evaluate = parseExpr(el, expr, ":aria");
669
654
  const update = (value) => {
670
655
  for (let key in value)
671
- prop(el2, "aria" + key[0].toUpperCase() + key.slice(1), value[key] == null ? null : value[key] + "");
656
+ attr(el, "aria-" + dashcase(key), value[key] == null ? null : value[key] + "");
672
657
  };
673
- b(() => update(evaluate(state)));
658
+ return (state) => update(evaluate(state));
674
659
  };
675
660
  var evaluatorMemo = {};
676
- function parseExpr(el2, expression, dir, scope) {
661
+ function parseExpr(el, expression, dir) {
677
662
  if (evaluatorMemo[expression])
678
663
  return evaluatorMemo[expression];
679
664
  let rightSideSafeExpression = /^[\n\s]*if.*\(.*\)/.test(expression) || /^(let|const)\s/.test(expression) ? `(() => { ${expression} })()` : expression;
680
665
  let evaluate;
681
666
  try {
682
- evaluate = new Function(`let result; with (arguments[0]) { result = (${rightSideSafeExpression}) }; return result;`).bind(el2);
667
+ evaluate = new Function(`let result; with (arguments[0]) { result = (${rightSideSafeExpression}) }; return result;`).bind(el);
683
668
  } catch (e2) {
684
- return exprError(e2, el2, expression, dir, scope);
669
+ return exprError(e2, el, expression, dir);
685
670
  }
686
671
  return evaluatorMemo[expression] = (state) => {
687
672
  let result;
688
673
  try {
689
674
  result = evaluate(state);
690
675
  } catch (e2) {
691
- return exprError(e2, el2, expression, dir, scope);
676
+ return exprError(e2, el, expression, dir);
692
677
  }
693
678
  return result;
694
679
  };
695
680
  }
696
- function exprError(error, element, expression, dir, scope) {
681
+ function exprError(error, element, expression, dir) {
697
682
  Object.assign(error, { element, expression });
698
- console.warn(`\u2234sprae: ${error.message}
683
+ console.warn(`\u2234 ${error.message}
699
684
 
700
685
  ${dir}=${expression ? `"${expression}"
701
686
 
702
- ` : ""}`, element, scope);
687
+ ` : ""}`, element);
703
688
  setTimeout(() => {
704
689
  throw error;
705
690
  }, 0);
706
691
  }
692
+ function dashcase(str) {
693
+ return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => "-" + match.toLowerCase());
694
+ }
707
695
 
708
696
  // src/core.js
709
697
  var memo = /* @__PURE__ */ new WeakMap();
@@ -713,31 +701,36 @@ function sprae(container, values) {
713
701
  if (memo.has(container))
714
702
  return memo.get(container);
715
703
  values ||= {};
716
- const state = SignalStruct(values);
717
- const init = (el2) => {
718
- if (el2.attributes) {
719
- for (let i2 = 0; i2 < el2.attributes.length; ) {
720
- let attr = el2.attributes[i2];
721
- if (attr.name[0] !== ":") {
704
+ const updates = [];
705
+ const init = (el, parent = el.parentNode) => {
706
+ if (el.attributes) {
707
+ for (let i2 = 0; i2 < el.attributes.length; ) {
708
+ let attr2 = el.attributes[i2];
709
+ if (attr2.name[0] !== ":") {
722
710
  i2++;
723
711
  continue;
724
712
  }
725
- el2.removeAttribute(attr.name);
726
- let expr = attr.value;
727
- let attrNames = attr.name.slice(1).split(":");
713
+ el.removeAttribute(attr2.name);
714
+ let expr = attr2.value;
715
+ let attrNames = attr2.name.slice(1).split(":");
728
716
  for (let attrName of attrNames) {
729
- let dir = directives[attrName] || directives_default, res;
730
- if ((res = dir(el2, expr, state, attrName)) <= 0)
731
- return res;
717
+ let dir = directives[attrName] || directives_default;
718
+ updates.push(dir(el, expr, values, attrName) || (() => {
719
+ }));
720
+ if (memo.has(el) || el.parentNode !== parent)
721
+ return false;
732
722
  }
733
723
  }
734
724
  }
735
- for (let i2 = 0, child; child = el2.children[i2]; i2++) {
736
- let res = init(child) || 0;
737
- i2 += res;
725
+ for (let i2 = 0, child; child = el.children[i2]; i2++) {
726
+ if (init(child, el) === false)
727
+ i2--;
738
728
  }
739
729
  };
740
730
  init(container);
731
+ const state = SignalStruct(values);
732
+ for (let update of updates)
733
+ b(() => update(state));
741
734
  memo.set(container, state);
742
735
  return state;
743
736
  }
package/sprae.min.js CHANGED
@@ -1 +1 @@
1
- function t(){throw new Error("Cycle detected")}function e(){if(n>1)n--;else{for(var t,e=!1;void 0!==r;){var i=r;for(r=void 0,o++;void 0!==i;){var s=i.o;if(i.o=void 0,i.f&=-3,!(8&i.f)&&l(i))try{i.c()}catch(i){e||(t=i,e=!0)}i=s}}if(o=0,n--,e)throw t}}var i=void 0,r=void 0,n=0,o=0,s=0;function a(t){if(void 0!==i){var e=t.n;if(void 0===e||e.t!==i)return i.s=e={i:0,S:t,p:void 0,n:i.s,t:i,e:void 0,x:void 0,r:e},t.n=e,32&i.f&&t.S(e),e;if(-1===e.i)return e.i=0,void 0!==e.p&&(e.p.n=e.n,void 0!==e.n&&(e.n.p=e.p),e.p=void 0,e.n=i.s,i.s.p=e,i.s=e),e}}function f(t){this.v=t,this.i=0,this.n=void 0,this.t=void 0}function u(t){return new f(t)}function l(t){for(var e=t.s;void 0!==e;e=e.n)if(e.S.i!==e.i||!e.S.h()||e.S.i!==e.i)return!0;return!1}function h(t){for(var e=t.s;void 0!==e;e=e.n){var i=e.S.n;void 0!==i&&(e.r=i),e.S.n=e,e.i=-1}}function c(t){for(var e=t.s,i=void 0;void 0!==e;){var r=e.n;-1===e.i?(e.S.U(e),e.n=void 0):(void 0!==i&&(i.p=e),e.p=void 0,e.n=i,i=e),e.S.n=e.r,void 0!==e.r&&(e.r=void 0),e=r}t.s=i}function v(t){f.call(this,void 0),this.x=t,this.s=void 0,this.g=s-1,this.f=4}function p(t){return new v(t)}function d(t){var r=t.u;if(t.u=void 0,"function"==typeof r){n++;var o=i;i=void 0;try{r()}catch(e){throw t.f&=-2,t.f|=8,y(t),e}finally{i=o,e()}}}function y(t){for(var e=t.s;void 0!==e;e=e.n)e.S.U(e);t.x=void 0,t.s=void 0,d(t)}function b(t){if(i!==this)throw new Error("Out-of-order effect");c(this),i=t,this.f&=-2,8&this.f&&y(this),e()}function m(t){this.x=t,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32}function g(t){var e=new m(t);return e.c(),e.d.bind(e)}f.prototype.h=function(){return!0},f.prototype.S=function(t){this.t!==t&&void 0===t.e&&(t.x=this.t,void 0!==this.t&&(this.t.e=t),this.t=t)},f.prototype.U=function(t){var e=t.e,i=t.x;void 0!==e&&(e.x=i,t.e=void 0),void 0!==i&&(i.e=e,t.x=void 0),t===this.t&&(this.t=i)},f.prototype.subscribe=function(t){var e=this;return g((function(){var i=e.value,r=32&this.f;this.f&=-33;try{t(i)}finally{this.f|=r}}))},f.prototype.valueOf=function(){return this.value},f.prototype.toString=function(){return this.value+""},f.prototype.peek=function(){return this.v},Object.defineProperty(f.prototype,"value",{get:function(){var t=a(this);return void 0!==t&&(t.i=this.i),this.v},set:function(i){if(i!==this.v){o>100&&t(),this.v=i,this.i++,s++,n++;try{for(var r=this.t;void 0!==r;r=r.x)r.t.N()}finally{e()}}}}),(v.prototype=new f).h=function(){if(this.f&=-3,1&this.f)return!1;if(32==(36&this.f))return!0;if(this.f&=-5,this.g===s)return!0;if(this.g=s,this.f|=1,this.i>0&&!l(this))return this.f&=-2,!0;var t=i;try{h(this),i=this;var e=this.x();(16&this.f||this.v!==e||0===this.i)&&(this.v=e,this.f&=-17,this.i++)}catch(t){this.v=t,this.f|=16,this.i++}return i=t,c(this),this.f&=-2,!0},v.prototype.S=function(t){if(void 0===this.t){this.f|=36;for(var e=this.s;void 0!==e;e=e.n)e.S.S(e)}f.prototype.S.call(this,t)},v.prototype.U=function(t){if(f.prototype.U.call(this,t),void 0===this.t){this.f&=-33;for(var e=this.s;void 0!==e;e=e.n)e.S.U(e)}},v.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(var t=this.t;void 0!==t;t=t.x)t.t.N()}},v.prototype.peek=function(){if(this.h()||t(),16&this.f)throw this.v;return this.v},Object.defineProperty(v.prototype,"value",{get:function(){1&this.f&&t();var e=a(this);if(this.h(),void 0!==e&&(e.i=this.i),16&this.f)throw this.v;return this.v}}),m.prototype.c=function(){var t=this.S();try{8&this.f||void 0===this.x||(this.u=this.x())}finally{t()}},m.prototype.S=function(){1&this.f&&t(),this.f|=1,this.f&=-9,d(this),h(this),n++;var e=i;return i=this,b.bind(this,e)},m.prototype.N=function(){2&this.f||(this.f|=2,this.o=r,r=this)},m.prototype.d=function(){this.f|=8,1&this.f||y(this)},Symbol.observable||=Symbol("observable");var S=new FinalizationRegistry((t=>t.call?.())),w=t=>t&&t.peek,x=Symbol("signal-struct");function O(t){if((e=t)&&e[x])return t;var e;let i,r;if(A(t)){i={},r={};let e=Object.getOwnPropertyDescriptors(t);for(let t in e)r[t]=j(i,t,e[t].get?p(e[t].get.bind(i)):e[t].value);return Object.defineProperty(i,x,{configurable:!1,enumerable:!1,value:!0}),Object.seal(i),i}return Array.isArray(t)?t.map((t=>O(t))):t}function j(t,e,i){let r,n=w(i)?i:A(i)||Array.isArray(i)?u(O(i)):u((r=(o=i)&&!!(o[Symbol.observable]||o[Symbol.asyncIterator]||o.call&&o.set||o.subscribe||o.then))?void 0:i);var o,s,a,f,l,h,c;return r&&(a=t=>n.value=t,(s=i)&&(c=(s[Symbol.observable]?.()||s).subscribe?.(a,f,undefined),h=c&&(()=>c.unsubscribe?.())||s.set&&s.call?.(l,a)||(s.then?.((t=>{!l&&a(t)}),f)||(async t=>{try{for await(t of s){if(l)return;a(t)}}catch(t){}})())&&(t=>l=1),S.register(s,h))),Object.defineProperty(t,e,{get:()=>n.value,set:!w(i)&&A(i)?t=>t?Object.assign(n.value,t):n.value=O(t):t=>n.value=O(t),enumerable:!0,configurable:!1}),n}function A(t){return t&&t.constructor===Object}var $=(t,e,i)=>{e.startsWith("on")&&(e=e.toLowerCase()),t[e]!==i&&(t[e]=i),null==i||!1===i?t.removeAttribute(e):"function"!=typeof i&&t.setAttribute(e.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(t=>"-"+t.toLowerCase())),!0===i?"":"number"==typeof i||"string"==typeof i?i:"class"===e?(Array.isArray(i)?i:Object.entries(i).map((([t,e])=>e?t:""))).filter(Boolean).join(" "):"style"===e?Object.entries(i).map((([t,e])=>`${t}: ${e}`)).join(";"):"")};document.createElement("div");var E=(t,e,i,r=null)=>{let n,o,s,a=0,f=i.length,u=e.length,{remove:l,same:h,insert:c,replace:v}=E;for(;a<f&&a<u&&h(e[a],i[a]);)a++;for(;a<f&&a<u&&h(i[f-1],e[u-1]);)r=i[(--u,--f)];if(a==u)for(;a<f;)c(r,i[a++],t);else{for(n=e[a];a<f;)s=i[a++],o=n?n.nextSibling:r,h(n,s)?n=o:a<f&&h(i[a],o)?(v(n,s,t),n=o):c(n,s,t);for(;!h(n,r);)o=n.nextSibling,l(n,t),n=o}return i};E.same=(t,e)=>t==e,E.replace=(t,e,i)=>i.replaceChild(e,t),E.insert=(t,e,i)=>i.insertBefore(e,t),E.remove=(t,e)=>e.removeChild(t);var k=E,N={},W={},C={},U=(t,e,i,r)=>{if(r.startsWith("on"))return L.on(t,`{"${r.split("-").map((t=>t.startsWith("on")?t.slice(2):t)).join("-")}": ${e}}`,i);let n=M(t,e,":"+r,i);g((()=>{return e=n(i),$(t,r,e);var e}))},L={},B=Symbol(":each"),P=Symbol(":ref");L.with=(t,i,r)=>{let o=M(t,i,"with",r);const s=p((()=>Object.assign({},r,o(r))));let a=I(t,s.value);return g(((t=s.value)=>function(t){if(n>0)return t();n++;try{return t()}finally{e()}}((()=>Object.assign(a,t))))),!1},L.ref=(t,e,i)=>t.hasAttribute(":each")?t[P]=e:(I(t,Object.assign(Object.create(i),{[e]:t})),!1),L.if=(t,e,i)=>{let r=document.createTextNode(""),n=[M(t,e,":if",i)],o=[t],s=t;for(;(s=t.nextElementSibling)&&s.hasAttribute(":else");)s.removeAttribute(":else"),(e=s.getAttribute(":if"))?(s.removeAttribute(":if"),s.remove(),o.push(s),n.push(M(t,e,":else :if",i))):(s.remove(),o.push(s),n.push((()=>1)));t.replaceWith(s=r);let a=p((()=>n.findIndex((t=>t(i)))));return g(((t=a.value)=>o[t]!=s&&((s[B]||s).replaceWith(s=o[t]||r),I(s,i)))),-o.length},L.each=(t,e,i)=>{let r=function(t){let e=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,i=t.match(/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/);if(!i)return;let r={};r.items=i[2].trim();let n=i[1].replace(/^\s*\(|\)\s*$/g,"").trim(),o=n.match(e);return o?(r.item=n.replace(e,"").trim(),r.index=o[1].trim()):r.item=n,r}(e);if(!r)return T(new Error,t,e);const n=M(t,r.items,":each",i),o=t[B]=document.createTextNode("");t.replaceWith(o);const s=p((()=>{let e=n(i);return e?"number"==typeof e?Array.from({length:e},((t,e)=>[e,e+1])):e.constructor===Object?Object.entries(e):Array.isArray(e)?e.map(((t,e)=>[e+1,t])):void T(Error("Bad list value"),t,r.items,":each",e):[]})),a=new WeakMap,f=new WeakMap;let u=[];return g(((e=s.value)=>{let n=[],l=[];for(let[o,s]of e){let e=null===(h=s)?W:void 0===h?C:"number"==typeof h||h instanceof Number?N[h]||(N[h]=new Number(h)):"string"==typeof h||h instanceof String?N[h]||(N[h]=new String(h)):"boolean"==typeof h||h instanceof Boolean?N[h]||(N[h]=new Boolean(h)):h,u=f.get(e);if(u||(u=t.cloneNode(!0),f.set(e,u)),n.push(u),!a.has(e)){let n=Object.create(i);n[r.item]=s,r.index&&(n[r.index]=o),t[P]&&(n[t[P]]=u),a.set(e,n)}l.push(a.get(e))}var h;k(o.parentNode,u,n,o),u=n;for(let t=0;t<n.length;t++)I(n[t],l[t])})),-1},L.id=(t,e,i)=>{let r=M(t,e,":id",i);g((()=>{return e=r(i),t.id=e||0===e?e:"";var e}))},L[""]=(t,e,i)=>{let r=M(t,e,":",i);g((()=>(e=>{if(e)for(let i in e)$(t,i,e[i])})(r(i))))},L.text=(t,e,i)=>{let r=M(t,e,":text",i);g((()=>{return e=r(i),void(t.textContent=null==e?"":e);var e}))},L.value=(t,e,i)=>{let r=M(t,e,":in",i),[n,o]=(t=>["checkbox"===t.type?()=>t.checked:()=>t.value,"text"===t.type||""===t.type?e=>t.value=null==e?"":e:"checkbox"===t.type?e=>(t.value=e?"on":"",$(t,"checked",e)):"select-one"===t.type?e=>([...t.options].map((t=>t.removeAttribute("selected"))),t.value=e,t.selectedOptions[0]?.setAttribute("selected","")):e=>t.value=e])(t);g((()=>{return e=r(i),$(t,"value",e),void o(e);var e}))},L.on=(t,e,i)=>{let r=M(t,e,":on",i),n={};g((()=>{for(let e in n)t.removeEventListener(e,n[e]);n=r(i);for(let e in n){const i=e.split("-");if(1===i.length)t.addEventListener(e,n[e]);else{const r=(o,s=0)=>{t.addEventListener(i[s],n[e]=a=>{o=o(a),t.removeEventListener(i[s],n[e]),r(o,(s+1)%i.length)})};r(n[e])}}}))},L.data=(t,e,i)=>{let r=M(t,e,":data",i);const n=p((()=>r(i)));g(((e=n.value)=>{for(let i in e)t.dataset[i]=e[i]}))},L.aria=(t,e,i)=>{let r=M(t,e,":aria",i);g((()=>(e=>{for(let i in e)$(t,"aria"+i[0].toUpperCase()+i.slice(1),null==e[i]?null:e[i]+"")})(r(i))))};var D={};function M(t,e,i,r){if(D[e])return D[e];let n,o=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(() => { ${e} })()`:e;try{n=new Function(`let result; with (arguments[0]) { result = (${o}) }; return result;`).bind(t)}catch(n){return T(n,t,e,i,r)}return D[e]=o=>{let s;try{s=n(o)}catch(n){return T(n,t,e,i,r)}return s}}function T(t,e,i,r,n){Object.assign(t,{element:e,expression:i}),console.warn(`∴sprae: ${t.message}\n\n${r}=${i?`"${i}"\n\n`:""}`,e,n),setTimeout((()=>{throw t}),0)}var F=new WeakMap;function I(t,e){if(!t.children)return;if(F.has(t))return F.get(t);e||={};const i=O(e),r=t=>{if(t.attributes)for(let e=0;e<t.attributes.length;){let r=t.attributes[e];if(":"!==r.name[0]){e++;continue}t.removeAttribute(r.name);let n=r.value,o=r.name.slice(1).split(":");for(let e of o){let r;if((r=(L[e]||U)(t,n,i,e))<=0)return r}}for(let e,i=0;e=t.children[i];i++)i+=r(e)||0};return r(t),F.set(t,i),i}var z=I;export{z as default};
1
+ function t(){throw new Error("Cycle detected")}function e(){if(n>1)n--;else{for(var t,e=!1;void 0!==r;){var i=r;for(r=void 0,o++;void 0!==i;){var s=i.o;if(i.o=void 0,i.f&=-3,!(8&i.f)&&l(i))try{i.c()}catch(i){e||(t=i,e=!0)}i=s}}if(o=0,n--,e)throw t}}var i=void 0,r=void 0,n=0,o=0,s=0;function f(t){if(void 0!==i){var e=t.n;if(void 0===e||e.t!==i)return i.s=e={i:0,S:t,p:void 0,n:i.s,t:i,e:void 0,x:void 0,r:e},t.n=e,32&i.f&&t.S(e),e;if(-1===e.i)return e.i=0,void 0!==e.p&&(e.p.n=e.n,void 0!==e.n&&(e.n.p=e.p),e.p=void 0,e.n=i.s,i.s.p=e,i.s=e),e}}function u(t){this.v=t,this.i=0,this.n=void 0,this.t=void 0}function a(t){return new u(t)}function l(t){for(var e=t.s;void 0!==e;e=e.n)if(e.S.i!==e.i||!e.S.h()||e.S.i!==e.i)return!0;return!1}function h(t){for(var e=t.s;void 0!==e;e=e.n){var i=e.S.n;void 0!==i&&(e.r=i),e.S.n=e,e.i=-1}}function c(t){for(var e=t.s,i=void 0;void 0!==e;){var r=e.n;-1===e.i?(e.S.U(e),e.n=void 0):(void 0!==i&&(i.p=e),e.p=void 0,e.n=i,i=e),e.S.n=e.r,void 0!==e.r&&(e.r=void 0),e=r}t.s=i}function v(t){u.call(this,void 0),this.x=t,this.s=void 0,this.g=s-1,this.f=4}function p(t){var r=t.u;if(t.u=void 0,"function"==typeof r){n++;var o=i;i=void 0;try{r()}catch(e){throw t.f&=-2,t.f|=8,d(t),e}finally{i=o,e()}}}function d(t){for(var e=t.s;void 0!==e;e=e.n)e.S.U(e);t.x=void 0,t.s=void 0,p(t)}function y(t){if(i!==this)throw new Error("Out-of-order effect");c(this),i=t,this.f&=-2,8&this.f&&d(this),e()}function b(t){this.x=t,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32}function m(t){var e=new b(t);return e.c(),e.d.bind(e)}u.prototype.h=function(){return!0},u.prototype.S=function(t){this.t!==t&&void 0===t.e&&(t.x=this.t,void 0!==this.t&&(this.t.e=t),this.t=t)},u.prototype.U=function(t){var e=t.e,i=t.x;void 0!==e&&(e.x=i,t.e=void 0),void 0!==i&&(i.e=e,t.x=void 0),t===this.t&&(this.t=i)},u.prototype.subscribe=function(t){var e=this;return m((function(){var i=e.value,r=32&this.f;this.f&=-33;try{t(i)}finally{this.f|=r}}))},u.prototype.valueOf=function(){return this.value},u.prototype.toString=function(){return this.value+""},u.prototype.peek=function(){return this.v},Object.defineProperty(u.prototype,"value",{get:function(){var t=f(this);return void 0!==t&&(t.i=this.i),this.v},set:function(i){if(i!==this.v){o>100&&t(),this.v=i,this.i++,s++,n++;try{for(var r=this.t;void 0!==r;r=r.x)r.t.N()}finally{e()}}}}),(v.prototype=new u).h=function(){if(this.f&=-3,1&this.f)return!1;if(32==(36&this.f))return!0;if(this.f&=-5,this.g===s)return!0;if(this.g=s,this.f|=1,this.i>0&&!l(this))return this.f&=-2,!0;var t=i;try{h(this),i=this;var e=this.x();(16&this.f||this.v!==e||0===this.i)&&(this.v=e,this.f&=-17,this.i++)}catch(t){this.v=t,this.f|=16,this.i++}return i=t,c(this),this.f&=-2,!0},v.prototype.S=function(t){if(void 0===this.t){this.f|=36;for(var e=this.s;void 0!==e;e=e.n)e.S.S(e)}u.prototype.S.call(this,t)},v.prototype.U=function(t){if(u.prototype.U.call(this,t),void 0===this.t){this.f&=-33;for(var e=this.s;void 0!==e;e=e.n)e.S.U(e)}},v.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(var t=this.t;void 0!==t;t=t.x)t.t.N()}},v.prototype.peek=function(){if(this.h()||t(),16&this.f)throw this.v;return this.v},Object.defineProperty(v.prototype,"value",{get:function(){1&this.f&&t();var e=f(this);if(this.h(),void 0!==e&&(e.i=this.i),16&this.f)throw this.v;return this.v}}),b.prototype.c=function(){var t=this.S();try{8&this.f||void 0===this.x||(this.u=this.x())}finally{t()}},b.prototype.S=function(){1&this.f&&t(),this.f|=1,this.f&=-9,p(this),h(this),n++;var e=i;return i=this,y.bind(this,e)},b.prototype.N=function(){2&this.f||(this.f|=2,this.o=r,r=this)},b.prototype.d=function(){this.f|=8,1&this.f||d(this)},Symbol.observable||=Symbol("observable");var g=new FinalizationRegistry((t=>t.call?.())),S=t=>t&&t.peek,x=Symbol("signal-struct");function w(t){if((e=t)&&e[x])return t;var e;let i,r;if(O(t)){i={},r={};let e=Object.getOwnPropertyDescriptors(t);for(let t in e)r[t]=A(i,t,e[t].get?new v(e[t].get.bind(i)):e[t].value);return Object.defineProperty(i,x,{configurable:!1,enumerable:!1,value:!0}),Object.seal(i),i}return Array.isArray(t)?t.map((t=>w(t))):t}function A(t,e,i){let r,n=S(i)?i:O(i)||Array.isArray(i)?a(w(i)):a((r=(o=i)&&!!(o[Symbol.observable]||o[Symbol.asyncIterator]||o.call&&o.set||o.subscribe||o.then))?void 0:i);var o,s,f,u,l,h,c;return r&&(f=t=>n.value=t,(s=i)&&(c=(s[Symbol.observable]?.()||s).subscribe?.(f,u,undefined),h=c&&(()=>c.unsubscribe?.())||s.set&&s.call?.(l,f)||(s.then?.((t=>{!l&&f(t)}),u)||(async t=>{try{for await(t of s){if(l)return;f(t)}}catch(t){}})())&&(t=>l=1),g.register(s,h))),Object.defineProperty(t,e,{get:()=>n.value,set:!S(i)&&O(i)?t=>t?Object.assign(n.value,t):n.value=w(t):t=>n.value=w(t),enumerable:!0,configurable:!1}),n}function O(t){return t&&t.constructor===Object}var j=(t,e,i,r=null)=>{let n,o,s,f=0,u=i.length,a=e.length,{remove:l,same:h,insert:c,replace:v}=j;for(;f<u&&f<a&&h(e[f],i[f]);)f++;for(;f<u&&f<a&&h(i[u-1],e[a-1]);)r=i[(--a,--u)];if(f==a)for(;f<u;)c(r,i[f++],t);else{for(n=e[f];f<u;)s=i[f++],o=n?n.nextSibling:r,h(n,s)?n=o:f<u&&h(i[f],o)?(v(n,s,t),n=o):c(n,s,t);for(;!h(n,r);)o=n.nextSibling,l(n,t),n=o}return i};j.same=(t,e)=>t==e,j.replace=(t,e,i)=>i.replaceChild(e,t),j.insert=(t,e,i)=>i.insertBefore(e,t),j.remove=(t,e)=>e.removeChild(t);var N=j,E={},$={},k={},W={},C=(t,e,i,r)=>{if(r.startsWith("on"))return W.on(t,`{"${r.split("-").map((t=>t.startsWith("on")?t.slice(2):t)).join("-")}": ${e}}`,i);let n=D(t,e,":"+r);return e=>U(t,r,n(e))},U=(t,e,i)=>{null==i||!1===i?t.removeAttribute(e):t.setAttribute(e,!0===i?"":"number"==typeof i||"string"==typeof i?i:"")};W[""]=(t,e,i)=>{let r=D(t,e,":");return e=>{let i=r(e);for(let e in i)U(t,T(e),i[e])}};var B=Symbol(":each"),L=Symbol(":ref");W.ref=(t,e,i)=>{t.hasAttribute(":each")?t[L]=e:i[e]=t},W.if=(t,e,i)=>{let r=document.createTextNode(""),n=[D(t,e,":if")],o=[t],s=t;for(;(s=t.nextElementSibling)&&s.hasAttribute(":else");)s.removeAttribute(":else"),(e=s.getAttribute(":if"))?(s.removeAttribute(":if"),s.remove(),o.push(s),n.push(D(t,e,":else :if"))):(s.remove(),o.push(s),n.push((()=>1)));return t.replaceWith(s=r),t=>{let e=n.findIndex((e=>e(t)));o[e]!=s&&((s[B]||s).replaceWith(s=o[e]||r),I(s,t))}},W.each=(t,e,i)=>{let r=function(t){let e=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,i=t.match(/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/);if(!i)return;let r={};r.items=i[2].trim();let n=i[1].replace(/^\s*\(|\)\s*$/g,"").trim(),o=n.match(e);return o?(r.item=n.replace(e,"").trim(),r.index=o[1].trim()):r.item=n,r}(e);if(!r)return M(new Error,t,e);const n=t[B]=document.createTextNode("");t.replaceWith(n);const o=D(t,r.items,":each"),s=new WeakMap,f=new WeakMap;let u=[];return e=>{let i=o(e);i?"number"==typeof i?i=Array.from({length:i},((t,e)=>[e,e+1])):i.constructor===Object?i=Object.entries(i):Array.isArray(i)?i=i.map(((t,e)=>[e+1,t])):M(Error("Bad list value"),t,r.items,":each"):i=[];let a=[],l=[];for(let[n,o]of i){let i=null===(h=o)?$:void 0===h?k:"number"==typeof h||h instanceof Number?E[h]||(E[h]=new Number(h)):"string"==typeof h||h instanceof String?E[h]||(E[h]=new String(h)):"boolean"==typeof h||h instanceof Boolean?E[h]||(E[h]=new Boolean(h)):h,u=f.get(i);if(u||(u=t.cloneNode(!0),f.set(i,u)),a.push(u),!s.has(i)){let f=Object.create(e);f[r.item]=o,r.index&&(f[r.index]=n),t[L]&&(f[t[L]]=u),s.set(i,f)}l.push(s.get(i))}var h;N(n.parentNode,u,a,n),u=a;for(let t=0;t<a.length;t++)I(a[t],l[t])}},W.id=(t,e,i)=>{let r=D(t,e,":id");return e=>{return i=r(e),t.id=i||0===i?i:"";var i}},W.class=(t,e,i)=>{let r=D(t,e,":class"),n=t.className;return e=>{let i=r(e);t.className=n+typeof i=="string"?i:(Array.isArray(i)?i:Object.entries(i).map((([t,e])=>e?t:""))).filter(Boolean).join(" ")}},W.style=(t,e,i)=>{let r=D(t,e,":style");return e=>{let i=r(e);if("string"==typeof i)t.setAttribute("style",i);else for(let e in i)t.style[e]=i[e]}},W.text=(t,e,i)=>{let r=D(t,e,":text");return e=>{return i=r(e),void(t.textContent=null==i?"":i);var i}},W.value=(t,e,i)=>{let r=D(t,e,":in"),n="text"===t.type||""===t.type?e=>t.setAttribute("value",t.value=null==e?"":e):"checkbox"===t.type?e=>(t.value=e?"on":"",U(t,"checked",e)):"select-one"===t.type?e=>([...t.options].map((t=>t.removeAttribute("selected"))),t.value=e,t.selectedOptions[0]?.setAttribute("selected","")):e=>t.value=e;return t=>n(r(t))},W.on=(t,e,i)=>{let r=D(t,e,":on"),n={};return e=>{for(let e in n)t.removeEventListener(e,n[e]);n=r(e);for(let e in n){const i=e.split("-");if(1===i.length)t.addEventListener(e,n[e]);else{const r=n[e],o=(s,f=0)=>{t.addEventListener(i[f],n[e]=u=>{s=s(u),t.removeEventListener(i[f],n[e]),++f<i.length&&"function"==typeof s?o(s,f):o(r)})};o(r)}}}},W.data=(t,e,i)=>{let r=D(t,e,":data");return e=>{let i=r(e);for(let e in i)t.dataset[e]=i[e]}},W.aria=(t,e,i)=>{let r=D(t,e,":aria");return e=>(e=>{for(let i in e)U(t,"aria-"+T(i),null==e[i]?null:e[i]+"")})(r(e))};var P={};function D(t,e,i){if(P[e])return P[e];let r,n=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(() => { ${e} })()`:e;try{r=new Function(`let result; with (arguments[0]) { result = (${n}) }; return result;`).bind(t)}catch(r){return M(r,t,e,i)}return P[e]=n=>{let o;try{o=r(n)}catch(r){return M(r,t,e,i)}return o}}function M(t,e,i,r){Object.assign(t,{element:e,expression:i}),console.warn(`∴ ${t.message}\n\n${r}=${i?`"${i}"\n\n`:""}`,e),setTimeout((()=>{throw t}),0)}function T(t){return t.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(t=>"-"+t.toLowerCase()))}var F=new WeakMap;function I(t,e){if(!t.children)return;if(F.has(t))return F.get(t);e||={};const i=[],r=(t,n=t.parentNode)=>{if(t.attributes)for(let r=0;r<t.attributes.length;){let o=t.attributes[r];if(":"!==o.name[0]){r++;continue}t.removeAttribute(o.name);let s=o.value,f=o.name.slice(1).split(":");for(let r of f){let o=W[r]||C;if(i.push(o(t,s,e,r)||(()=>{})),F.has(t)||t.parentNode!==n)return!1}}for(let e,i=0;e=t.children[i];i++)!1===r(e,t)&&i--};r(t);const n=w(e);for(let t of i)m((()=>t(n)));return F.set(t,n),n}var z=I;export{z as default};
package/src/core.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import signalStruct from 'signal-struct';
2
2
  import defaultDirective, { directives } from './directives.js';
3
+ import { effect, computed, batch } from '@preact/signals-core'
3
4
 
4
5
  // sprae element: apply directives
5
6
  const memo = new WeakMap
@@ -9,10 +10,10 @@ export default function sprae(container, values) {
9
10
 
10
11
  values ||= {};
11
12
 
12
- const state = signalStruct(values);
13
+ const updates = []
13
14
 
14
15
  // init directives on element
15
- const init = (el) => {
16
+ const init = (el, parent=el.parentNode) => {
16
17
  if (el.attributes) {
17
18
  for (let i = 0; i < el.attributes.length;) {
18
19
  let attr = el.attributes[i]
@@ -21,19 +22,27 @@ export default function sprae(container, values) {
21
22
  let expr = attr.value
22
23
  let attrNames = attr.name.slice(1).split(':')
23
24
  for (let attrName of attrNames) {
24
- let dir = directives[attrName] || defaultDirective, res
25
- if ((res = dir(el, expr, state, attrName)) <= 0) return res
25
+ let dir = directives[attrName] || defaultDirective;
26
+ updates.push(dir(el, expr, values, attrName) || (()=>{}));
27
+
28
+ // stop if element was spraed by directive or skipped
29
+ if (memo.has(el) || el.parentNode !== parent) return false
26
30
  }
27
31
  }
28
32
  }
29
33
 
30
34
  for (let i = 0, child; child = el.children[i]; i++) {
31
- let res = init(child) || 0 // reduce number of removed elements
32
- i += res
35
+ // if element was removed from parent (skipped) - reduce index
36
+ if (init(child, el) === false) i--
33
37
  }
34
38
  }
35
39
 
36
- init(container)
40
+ init(container);
41
+
42
+ // call updates: subscribes directives to state;
43
+ // state is created after inits because directives can extend init values (expose refs etc)
44
+ const state = signalStruct(values);
45
+ for (let update of updates) effect(() => update(state));
37
46
 
38
47
  memo.set(container, state);
39
48
 
package/src/directives.js CHANGED
@@ -1,51 +1,49 @@
1
1
  // directives & parsing
2
2
  import sprae from './core.js'
3
- import { prop, input } from 'element-props'
4
- // import { effect, computed, batch } from 'usignal/sync'
5
- import { effect, computed, batch } from '@preact/signals-core'
6
3
  import swap from 'swapdom'
7
4
  import p from 'primitive-pool'
8
5
 
6
+ // reserved directives - order matters!
7
+ export const directives = {}
9
8
 
10
9
  // any-prop directives
11
- export default (el, expr, state, name) => {
10
+ export default (el, expr, values, name) => {
12
11
  if (name.startsWith('on')) {
13
12
  // :ona:onb=x -> :on={a:x,b:x}
14
13
  // :ona-onb=x -> :on={aB:x}
15
- return directives.on(el, `{"${name.split('-').map(n=>n.startsWith('on')?n.slice(2):n).join('-')}": ${expr}}`, state)
14
+ return directives.on(el, `{"${name.split('-').map(n=>n.startsWith('on')?n.slice(2):n).join('-')}": ${expr}}`, values)
16
15
  }
17
- let evaluate = parseExpr(el, expr, ':'+name, state)
18
- const update = value => prop(el, name, value)
19
- effect(() => update(evaluate(state)))
20
- }
16
+ let evaluate = parseExpr(el, expr, ':'+name)
21
17
 
22
- // reserved directives - order matters!
23
- export const directives = {}
24
-
25
- const _each = Symbol(':each'), _ref = Symbol(':ref')
18
+ return (state) => attr(el, name, evaluate(state))
19
+ }
26
20
 
27
- directives['with'] = (el, expr, rootState) => {
28
- let evaluate = parseExpr(el, expr, 'with', rootState)
21
+ // set attr
22
+ const attr = (el, name, v) => {
23
+ if (v == null || v === false) el.removeAttribute(name)
24
+ else el.setAttribute(name, v === true ? '' : (typeof v === 'number' || typeof v === 'string') ? v : '')
25
+ }
29
26
 
30
- // Instead of extending signals (which is a bit hard since signal-struct internals is not uniform)
31
- // we bind updating
32
- const params = computed(() => Object.assign({}, rootState, evaluate(rootState)))
33
- let state = sprae(el, params.value)
34
- effect((values=params.value) => batch(() => Object.assign(state, values)))
35
- return false // don't continue attrs init
27
+ directives[''] = (el, expr, values) => {
28
+ let evaluate = parseExpr(el, expr, ':')
29
+ return (state) => {
30
+ let value = evaluate(state)
31
+ for (let key in value) attr(el, dashcase(key), value[key]);
32
+ }
36
33
  }
37
34
 
38
- directives['ref'] = (el, expr, state) => {
39
- // make sure :ref is initialized after :each
40
- if (el.hasAttribute(':each')) return el[_ref] = expr;
35
+ const _each = Symbol(':each'), _ref = Symbol(':ref')
36
+
37
+ directives['ref'] = (el, expr, values) => {
38
+ // make sure :ref is initialized after :each (return to avoid initializing as signal)
39
+ if (el.hasAttribute(':each')) {el[_ref] = expr; return};
41
40
 
42
- sprae(el, Object.assign(Object.create(state), {[expr]: el}))
43
- return false // don't continue attrs init
41
+ values[expr] = el;
44
42
  }
45
43
 
46
- directives['if'] = (el, expr, state) => {
44
+ directives['if'] = (el, expr, values) => {
47
45
  let holder = document.createTextNode(''),
48
- clauses = [parseExpr(el, expr, ':if', state)],
46
+ clauses = [parseExpr(el, expr, ':if')],
49
47
  els = [el], cur = el
50
48
 
51
49
  while (cur = el.nextElementSibling) {
@@ -53,7 +51,7 @@ directives['if'] = (el, expr, state) => {
53
51
  cur.removeAttribute(':else');
54
52
  if (expr = cur.getAttribute(':if')) {
55
53
  cur.removeAttribute(':if'), cur.remove();
56
- els.push(cur); clauses.push(parseExpr(el, expr, ':else :if', state));
54
+ els.push(cur); clauses.push(parseExpr(el, expr, ':else :if'));
57
55
  }
58
56
  else {
59
57
  cur.remove(); els.push(cur); clauses.push(() => 1);
@@ -63,40 +61,43 @@ directives['if'] = (el, expr, state) => {
63
61
  }
64
62
 
65
63
  el.replaceWith(cur = holder)
66
- let idx = computed(() => clauses.findIndex(f => f(state)))
67
- // NOTE: it lazily initializes elements on insertion, it's safe to sprae multiple times
68
- effect((i=idx.value) => (els[i] != cur && ((cur[_each]||cur).replaceWith(cur = els[i] || holder), sprae(cur, state))))
69
64
 
70
- // indicate number of removed elements
71
- return -els.length
65
+ return (state) => {
66
+ let i = clauses.findIndex(f => f(state))
67
+ if (els[i] != cur) {
68
+ (cur[_each] || cur).replaceWith(cur = els[i] || holder);
69
+ // NOTE: it lazily initializes elements on insertion, it's safe to sprae multiple times
70
+ sprae(cur, state);
71
+ }
72
+ }
72
73
  }
73
74
 
74
- directives['each'] = (tpl, expr, state) => {
75
+ directives['each'] = (tpl, expr, values) => {
75
76
  let each = parseForExpression(expr);
76
77
  if (!each) return exprError(new Error, tpl, expr);
77
78
 
78
- const getItems = parseExpr(tpl, each.items, ':each', state);
79
-
80
79
  // FIXME: make sure no memory leak here
81
80
  // we need :if to be able to replace holder instead of tpl for :if :each case
82
81
  const holder = tpl[_each] = document.createTextNode('')
83
82
  tpl.replaceWith(holder)
84
83
 
85
- const items = computed(()=>{
86
- let list = getItems(state)
87
- if (!list) return []
88
- if (typeof list === 'number') return Array.from({length: list}, (_, i)=>[i, i+1])
89
- if (list.constructor === Object) return Object.entries(list)
90
- if (Array.isArray(list)) return list.map((item,i) => [i+1, item])
91
- exprError(Error('Bad list value'), tpl, each.items, ':each', list)
92
- })
84
+ const evaluate = parseExpr(tpl, each.items, ':each');
93
85
 
94
86
  // stores scope per data item
95
87
  const scopes = new WeakMap()
96
88
  // element per data item
97
89
  const itemEls = new WeakMap()
98
90
  let curEls = []
99
- effect((list=items.value) => {
91
+
92
+ return (state) => {
93
+ // get items
94
+ let list = evaluate(state)
95
+ if (!list) list = []
96
+ else if (typeof list === 'number') list = Array.from({length: list}, (_, i)=>[i, i+1])
97
+ else if (list.constructor === Object) list = Object.entries(list)
98
+ else if (Array.isArray(list)) list = list.map((item,i) => [i+1, item])
99
+ else exprError(Error('Bad list value'), tpl, each.items, ':each', list)
100
+
100
101
  // collect elements/scopes for items
101
102
  let newEls = [], elScopes = []
102
103
 
@@ -128,9 +129,7 @@ directives['each'] = (tpl, expr, state) => {
128
129
  for (let i = 0; i < newEls.length; i++) {
129
130
  sprae(newEls[i], elScopes[i])
130
131
  }
131
- })
132
-
133
- return -1 // count back for 1
132
+ }
134
133
  }
135
134
 
136
135
  // This was taken AlpineJS, former VueJS 2.* core. Thanks Alpine & Vue!
@@ -157,48 +156,63 @@ function parseForExpression(expression) {
157
156
  return res
158
157
  }
159
158
 
160
- directives['id'] = (el, expr, state) => {
161
- let evaluate = parseExpr(el, expr, ':id', state)
159
+ directives['id'] = (el, expr, values) => {
160
+ let evaluate = parseExpr(el, expr, ':id')
162
161
  const update = v => el.id = v || v === 0 ? v : ''
163
- effect(()=>update(evaluate(state)))
162
+ return (state) => update(evaluate(state))
164
163
  }
165
164
 
166
- directives[''] = (el, expr, state) => {
167
- let evaluate = parseExpr(el, expr, ':', state)
168
- const update = (value) => {
169
- if (!value) return
170
- for (let key in value) prop(el, key, value[key]);
165
+ directives['class'] = (el, expr, values) => {
166
+ let evaluate = parseExpr(el, expr, ':class')
167
+ let initClassName = el.className
168
+ return (state) => {
169
+ let v = evaluate(state)
170
+ el.className = initClassName + typeof v === 'string' ? v : (Array.isArray(v) ? v : Object.entries(v).map(([k,v])=>v?k:'')).filter(Boolean).join(' ')
171
171
  }
172
- effect(()=>update(evaluate(state)))
173
172
  }
174
173
 
175
- directives['text'] = (el, expr, state) => {
176
- let evaluate = parseExpr(el, expr, ':text', state)
174
+ directives['style'] = (el, expr, values) => {
175
+ let evaluate = parseExpr(el, expr, ':style')
176
+ return (state) => {
177
+ let v = evaluate(state)
178
+ if (typeof v === 'string') el.setAttribute('style', v)
179
+ else for (let k in v) el.style[k] = v[k]
180
+ }
181
+ }
182
+
183
+ directives['text'] = (el, expr, values) => {
184
+ let evaluate = parseExpr(el, expr, ':text')
177
185
 
178
186
  const update = (value) => {
179
187
  el.textContent = value == null ? '' : value;
180
188
  }
181
189
 
182
- effect(()=>update(evaluate(state)))
190
+ return (state) => update(evaluate(state))
183
191
  }
184
192
 
185
193
  // connect expr to element value
186
- directives['value'] = (el, expr, state) => {
187
- let evaluate = parseExpr(el, expr, ':in', state)
188
-
189
- let [get, set] = input(el);
190
-
191
- const update = (value) => {
192
- prop(el, 'value', value)
193
- set(value);
194
- }
195
- effect(()=>update(evaluate(state)))
194
+ directives['value'] = (el, expr, values) => {
195
+ let evaluate = parseExpr(el, expr, ':in')
196
+
197
+ let update = (
198
+ el.type === 'text' || el.type === '' ? value => el.setAttribute('value', el.value = value == null ? '' : value) :
199
+ el.type === 'checkbox' ? value => (el.value = value ? 'on' : '', attr(el, 'checked', value)) :
200
+ el.type === 'select-one' ? value => (
201
+ [...el.options].map(el => el.removeAttribute('selected')),
202
+ el.value = value,
203
+ el.selectedOptions[0]?.setAttribute('selected', '')
204
+ ) :
205
+ value => el.value = value
206
+ )
207
+
208
+ return (state) => update(evaluate(state))
196
209
  }
197
210
 
198
- directives['on'] = (el, expr, state) => {
199
- let evaluate = parseExpr(el, expr, ':on', state)
211
+ directives['on'] = (el, expr, values) => {
212
+ let evaluate = parseExpr(el, expr, ':on')
200
213
  let listeners = {}
201
- effect(() => {
214
+
215
+ return (state) => {
202
216
  for (let evt in listeners) el.removeEventListener(evt, listeners[evt]);
203
217
 
204
218
  listeners = evaluate(state);
@@ -219,23 +233,24 @@ directives['on'] = (el, expr, state) => {
219
233
  nextEvt(startFn)
220
234
  }
221
235
  };
222
- })
236
+ }
223
237
  }
224
238
 
225
- directives['data'] = (el, expr, state) => {
226
- let evaluate = parseExpr(el, expr, ':data', state)
227
- const value = computed(() => evaluate(state))
228
- effect((v=value.value) => {
229
- for (let key in v) el.dataset[key] = v[key];
239
+ directives['data'] = (el, expr, values) => {
240
+ let evaluate = parseExpr(el, expr, ':data')
241
+
242
+ return ((state) => {
243
+ let value = evaluate(state)
244
+ for (let key in value) el.dataset[key] = value[key];
230
245
  })
231
246
  }
232
247
 
233
- directives['aria'] = (el, expr, state) => {
234
- let evaluate = parseExpr(el, expr, ':aria', state)
248
+ directives['aria'] = (el, expr, values) => {
249
+ let evaluate = parseExpr(el, expr, ':aria')
235
250
  const update = (value) => {
236
- for (let key in value) prop(el, 'aria'+key[0].toUpperCase()+key.slice(1), value[key] == null ? null : value[key] + '');
251
+ for (let key in value) attr(el, 'aria-' + dashcase(key), value[key] == null ? null : value[key] + '');
237
252
  }
238
- effect(() => update(evaluate(state)))
253
+ return ((state) => update(evaluate(state)))
239
254
  }
240
255
 
241
256
 
@@ -243,7 +258,7 @@ let evaluatorMemo = {}
243
258
 
244
259
  // borrowed from alpine: https://github.com/alpinejs/alpine/blob/main/packages/alpinejs/src/evaluator.js#L61
245
260
  // it seems to be more robust than subscript
246
- function parseExpr(el, expression, dir, scope) {
261
+ function parseExpr(el, expression, dir) {
247
262
  if (evaluatorMemo[expression]) return evaluatorMemo[expression]
248
263
 
249
264
  // Some expressions that are useful in Alpine are not valid as the right side of an expression.
@@ -262,20 +277,24 @@ function parseExpr(el, expression, dir, scope) {
262
277
  try {
263
278
  evaluate = new Function(`let result; with (arguments[0]) { result = (${rightSideSafeExpression}) }; return result;`).bind(el)
264
279
  } catch ( e ) {
265
- return exprError(e, el, expression, dir, scope)
280
+ return exprError(e, el, expression, dir)
266
281
  }
267
282
 
268
283
  // guard runtime eval errors
269
284
  return evaluatorMemo[expression] = (state) => {
270
285
  let result
271
286
  try { result = evaluate(state) }
272
- catch (e) { return exprError(e, el, expression, dir, scope) }
287
+ catch (e) { return exprError(e, el, expression, dir) }
273
288
  return result
274
289
  }
275
290
  }
276
291
 
277
- export function exprError(error, element, expression, dir, scope) {
292
+ export function exprError(error, element, expression, dir) {
278
293
  Object.assign( error, { element, expression } )
279
- console.warn(`∴sprae: ${error.message}\n\n${dir}=${ expression ? `"${expression}"\n\n` : '' }`, element, scope)
294
+ console.warn(`∴ ${error.message}\n\n${dir}=${ expression ? `"${expression}"\n\n` : '' }`, element)
280
295
  setTimeout(() => { throw error }, 0)
281
296
  }
297
+
298
+ function dashcase(str) {
299
+ return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => '-' + match.toLowerCase());
300
+ };
package/test/test.js CHANGED
@@ -49,7 +49,7 @@ test('common: style', async () => {
49
49
  let params = sprae(el, {style: "top: 1px"})
50
50
  is(el.outerHTML, `<x style="top: 1px"></x>`)
51
51
  params.style = {top: '2px'}
52
- is(el.outerHTML, `<x style="top: 2px"></x>`)
52
+ is(el.outerHTML, `<x style="top: 2px;"></x>`)
53
53
  })
54
54
 
55
55
  test('common: class', async () => {
@@ -64,9 +64,9 @@ test('common: class', async () => {
64
64
  })
65
65
 
66
66
  test('props: base', async () => {
67
- let el = h`<input :id="0" :="{for:1, title:2, help:3, type:4, placeholder: 5, value: 6}" :value="7"/>`
67
+ let el = h`<input :id="0" :="{for:1, title:2, help:3, type:4, placeholder: 5, value: 6, aB: 8}" :value="7"/>`
68
68
  let params = sprae(el)
69
- is(el.outerHTML, `<input id="0" for="1" title="2" help="3" type="4" placeholder="5" value="7">`)
69
+ is(el.outerHTML, `<input id="0" for="1" title="2" help="3" type="4" placeholder="5" value="7" a-b="8">`)
70
70
  })
71
71
 
72
72
  test('props: multiprop', async () => {
@@ -412,7 +412,7 @@ test('on: chain of events', e => {
412
412
  is(state.log, ['mousedown','mousemove','mouseup'])
413
413
  })
414
414
 
415
- test('with: inline', () => {
415
+ test.skip('with: inline', () => {
416
416
  let el = h`<x :with="{foo:'bar', baz}"><y :text="foo + baz"></y></x>`
417
417
  let state = sprae(el, {baz: 'qux'})
418
418
  // FIXME: this doesn't inherit root scope baz property and instead uses hard-initialized one
@@ -420,7 +420,7 @@ test('with: inline', () => {
420
420
  state.baz = 'quux'
421
421
  is(el.innerHTML, `<y>barquux</y>`)
422
422
  })
423
- test('with: inline reactive', () => {
423
+ test.skip('with: inline reactive', () => {
424
424
  let el = h`<x :with="{foo:'bar', baz}"><y :text="foo + baz"></y></x>`
425
425
  let baz = signal('qux')
426
426
  let state = sprae(el, {baz})
@@ -429,7 +429,7 @@ test('with: inline reactive', () => {
429
429
  baz.value = 'quux'
430
430
  is(el.innerHTML, `<y>barquux</y>`)
431
431
  })
432
- test('with: data', () => {
432
+ test.skip('with: data', () => {
433
433
  let el = h`<x :with="x"><y :text="foo"></y></x>`
434
434
  let state = sprae(el, {x:{foo:'bar'}})
435
435
  is(el.innerHTML, `<y>bar</y>`)
@@ -437,7 +437,7 @@ test('with: data', () => {
437
437
  Object.assign(state, {x:{foo:'baz'}})
438
438
  is(el.innerHTML, `<y>baz</y>`)
439
439
  })
440
- test('with: transparency', () => {
440
+ test.skip('with: transparency', () => {
441
441
  // NOTE: y:text initializes through directive, not through parent
442
442
  // therefore by default :text uses parent's state, not defined by element itself
443
443
  let el = h`<x :with="{foo:'foo'}"><y :with="b" :text="foo+bar"></y></x>`
@@ -446,7 +446,7 @@ test('with: transparency', () => {
446
446
  params.b.bar = 'baz'
447
447
  is(el.innerHTML, `<y>foobaz</y>`)
448
448
  })
449
- test('with: reactive transparency', () => {
449
+ test.skip('with: reactive transparency', () => {
450
450
  let el = h`<x :with="{foo:1}"><y :with="b.c" :text="foo+bar"></y></x>`
451
451
  const bar = signal('2')
452
452
  sprae(el, {b:{c:{bar}}})
@@ -454,7 +454,7 @@ test('with: reactive transparency', () => {
454
454
  bar.value = '3'
455
455
  is(el.innerHTML, `<y>13</y>`)
456
456
  })
457
- test('with: writes to state', () => {
457
+ test.skip('with: writes to state', () => {
458
458
  let a = h`<x :with="{a:1}"><y :on="{x(){a++}}" :text="a"></y></x>`
459
459
  sprae(a)
460
460
  is(a.innerHTML, `<y>1</y>`)
@@ -471,16 +471,17 @@ test('ref: base', () => {
471
471
  is(a.outerHTML, `<a>1</a>`)
472
472
  state.b = 2
473
473
  is(a.outerHTML, `<a>2</a>`)
474
+ is(state.a, a, 'Exposes to the state');
474
475
  })
475
476
 
476
- test('ref: :ref with :each', () => {
477
+ test('ref: with :each', () => {
477
478
  let a = h`<y><x :ref="x" :each="item in items" :text="log.push(x), item"/></y>`
478
479
  let state = sprae(a, {log: [], items: [1,2]})
479
480
  is(a.innerHTML, `<x>1</x><x>2</x>`)
480
481
  is(state.log, [...a.children])
481
482
  })
482
483
 
483
- test('any: reactive values', async () => {
484
+ test(':: reactive values', async () => {
484
485
  let a = new Promise((ok) => setTimeout(() => ok(2), 10))
485
486
 
486
487
  let el = h`<x :text="a">1</x>`
@@ -491,13 +492,13 @@ test('any: reactive values', async () => {
491
492
  is(el.outerHTML, `<x>2</x>`)
492
493
  })
493
494
 
494
- test('any: scope refers to current element', async () => {
495
+ test(':: scope refers to current element', async () => {
495
496
  let el = h`<x :text="log.push(this)"></x>`
496
497
  let state = sprae(el, {log:[]})
497
498
  is(state.log, [el])
498
499
  })
499
500
 
500
- test.skip('any: scope directives must come first', async () => {
501
+ test.skip(':: scope directives must come first', async () => {
501
502
  // NOTE: we init attributes in order of definition
502
503
  let a = h`<x :text="y" :with="{y:1}" :ref="x"></x>`
503
504
  sprae(a, {})