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.
- package/examples/todomvc.html +2 -1
- package/package.json +1 -3
- package/plan.md +8 -6
- package/r&d.md +20 -1
- package/readme.md +25 -46
- package/sprae.js +143 -150
- package/sprae.min.js +1 -1
- package/src/core.js +16 -7
- package/src/directives.js +108 -89
- package/test/test.js +14 -13
package/examples/todomvc.html
CHANGED
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
</ul>
|
|
55
55
|
</section>
|
|
56
56
|
<footer class="footer">
|
|
57
|
-
<span class="todo-count"
|
|
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.
|
|
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
|
-
* [
|
|
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
|
-
## [
|
|
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
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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.**
|
|
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
|
-
**
|
|
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
|
-
**
|
|
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
|
|
453
|
+
var directives = {};
|
|
454
|
+
var directives_default = (el, expr, values, name) => {
|
|
488
455
|
if (name.startsWith("on")) {
|
|
489
|
-
return directives.on(
|
|
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(
|
|
492
|
-
|
|
493
|
-
|
|
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["
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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"] = (
|
|
512
|
-
let holder = document.createTextNode(""), clauses = [parseExpr(
|
|
513
|
-
while (cur =
|
|
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(
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
560
|
-
if (!
|
|
561
|
-
|
|
562
|
-
itemEls.set(itemKey,
|
|
536
|
+
let el = itemEls.get(itemKey);
|
|
537
|
+
if (!el) {
|
|
538
|
+
el = tpl.cloneNode(true);
|
|
539
|
+
itemEls.set(itemKey, el);
|
|
563
540
|
}
|
|
564
|
-
newEls.push(
|
|
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]] =
|
|
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"] = (
|
|
604
|
-
let evaluate = parseExpr(
|
|
605
|
-
const update = (v2) =>
|
|
606
|
-
|
|
607
|
-
};
|
|
608
|
-
directives[""] = (
|
|
609
|
-
let evaluate = parseExpr(
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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["
|
|
619
|
-
let evaluate = parseExpr(
|
|
620
|
-
|
|
621
|
-
|
|
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["
|
|
626
|
-
let evaluate = parseExpr(
|
|
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
|
-
|
|
630
|
-
set(value);
|
|
606
|
+
el.textContent = value == null ? "" : value;
|
|
631
607
|
};
|
|
632
|
-
|
|
608
|
+
return (state) => update(evaluate(state));
|
|
633
609
|
};
|
|
634
|
-
directives["
|
|
635
|
-
let evaluate = parseExpr(
|
|
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
|
-
|
|
618
|
+
return (state) => {
|
|
638
619
|
for (let evt in listeners)
|
|
639
|
-
|
|
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
|
-
|
|
625
|
+
el.addEventListener(evt, listeners[evt]);
|
|
645
626
|
else {
|
|
627
|
+
const startFn = listeners[evt];
|
|
646
628
|
const nextEvt = (fn, cur = 0) => {
|
|
647
|
-
|
|
629
|
+
el.addEventListener(evts[cur], listeners[evt] = (e2) => {
|
|
648
630
|
fn = fn(e2);
|
|
649
|
-
|
|
650
|
-
|
|
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(
|
|
638
|
+
nextEvt(startFn);
|
|
654
639
|
}
|
|
655
640
|
}
|
|
656
641
|
;
|
|
657
|
-
}
|
|
642
|
+
};
|
|
658
643
|
};
|
|
659
|
-
directives["data"] = (
|
|
660
|
-
let evaluate = parseExpr(
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
for (let key in
|
|
664
|
-
|
|
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"] = (
|
|
668
|
-
let evaluate = parseExpr(
|
|
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
|
-
|
|
656
|
+
attr(el, "aria-" + dashcase(key), value[key] == null ? null : value[key] + "");
|
|
672
657
|
};
|
|
673
|
-
|
|
658
|
+
return (state) => update(evaluate(state));
|
|
674
659
|
};
|
|
675
660
|
var evaluatorMemo = {};
|
|
676
|
-
function parseExpr(
|
|
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(
|
|
667
|
+
evaluate = new Function(`let result; with (arguments[0]) { result = (${rightSideSafeExpression}) }; return result;`).bind(el);
|
|
683
668
|
} catch (e2) {
|
|
684
|
-
return exprError(e2,
|
|
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,
|
|
676
|
+
return exprError(e2, el, expression, dir);
|
|
692
677
|
}
|
|
693
678
|
return result;
|
|
694
679
|
};
|
|
695
680
|
}
|
|
696
|
-
function exprError(error, element, expression, dir
|
|
681
|
+
function exprError(error, element, expression, dir) {
|
|
697
682
|
Object.assign(error, { element, expression });
|
|
698
|
-
console.warn(`\
|
|
683
|
+
console.warn(`\u2234 ${error.message}
|
|
699
684
|
|
|
700
685
|
${dir}=${expression ? `"${expression}"
|
|
701
686
|
|
|
702
|
-
` : ""}`, element
|
|
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
|
|
717
|
-
const init = (
|
|
718
|
-
if (
|
|
719
|
-
for (let i2 = 0; i2 <
|
|
720
|
-
let
|
|
721
|
-
if (
|
|
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
|
-
|
|
726
|
-
let expr =
|
|
727
|
-
let attrNames =
|
|
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
|
|
730
|
-
|
|
731
|
-
|
|
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 =
|
|
736
|
-
|
|
737
|
-
|
|
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
|
|
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
|
|
25
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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,
|
|
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}}`,
|
|
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
|
|
18
|
-
const update = value => prop(el, name, value)
|
|
19
|
-
effect(() => update(evaluate(state)))
|
|
20
|
-
}
|
|
16
|
+
let evaluate = parseExpr(el, expr, ':'+name)
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const _each = Symbol(':each'), _ref = Symbol(':ref')
|
|
18
|
+
return (state) => attr(el, name, evaluate(state))
|
|
19
|
+
}
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
43
|
-
return false // don't continue attrs init
|
|
41
|
+
values[expr] = el;
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
directives['if'] = (el, expr,
|
|
44
|
+
directives['if'] = (el, expr, values) => {
|
|
47
45
|
let holder = document.createTextNode(''),
|
|
48
|
-
clauses = [parseExpr(el, expr, ':if'
|
|
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'
|
|
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
|
-
|
|
71
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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,
|
|
161
|
-
let evaluate = parseExpr(el, expr, ':id'
|
|
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
|
-
|
|
162
|
+
return (state) => update(evaluate(state))
|
|
164
163
|
}
|
|
165
164
|
|
|
166
|
-
directives[''] = (el, expr,
|
|
167
|
-
let evaluate = parseExpr(el, expr, ':'
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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['
|
|
176
|
-
let evaluate = parseExpr(el, expr, ':
|
|
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
|
-
|
|
190
|
+
return (state) => update(evaluate(state))
|
|
183
191
|
}
|
|
184
192
|
|
|
185
193
|
// connect expr to element value
|
|
186
|
-
directives['value'] = (el, expr,
|
|
187
|
-
let evaluate = parseExpr(el, expr, ':in'
|
|
188
|
-
|
|
189
|
-
let
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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,
|
|
199
|
-
let evaluate = parseExpr(el, expr, ':on'
|
|
211
|
+
directives['on'] = (el, expr, values) => {
|
|
212
|
+
let evaluate = parseExpr(el, expr, ':on')
|
|
200
213
|
let listeners = {}
|
|
201
|
-
|
|
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,
|
|
226
|
-
let evaluate = parseExpr(el, expr, ':data'
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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,
|
|
234
|
-
let evaluate = parseExpr(el, expr, ':aria'
|
|
248
|
+
directives['aria'] = (el, expr, values) => {
|
|
249
|
+
let evaluate = parseExpr(el, expr, ':aria')
|
|
235
250
|
const update = (value) => {
|
|
236
|
-
for (let key in value)
|
|
251
|
+
for (let key in value) attr(el, 'aria-' + dashcase(key), value[key] == null ? null : value[key] + '');
|
|
237
252
|
}
|
|
238
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
292
|
+
export function exprError(error, element, expression, dir) {
|
|
278
293
|
Object.assign( error, { element, expression } )
|
|
279
|
-
console.warn(`∴
|
|
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:
|
|
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('
|
|
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('
|
|
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('
|
|
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, {})
|