sprae 2.2.4 → 2.3.1
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 -7
- package/r&d.md +30 -4
- package/readme.md +39 -51
- package/sprae.js +137 -148
- package/sprae.min.js +1 -1
- package/src/core.js +16 -7
- package/src/directives.js +111 -90
- 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.1",
|
|
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,15 @@
|
|
|
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
|
-
* [ ] :onconnect, :ondisconnect
|
|
31
33
|
* [ ] frameworks benchmark
|
|
32
34
|
* [ ] examples
|
|
33
35
|
* [x] todomvc
|
|
34
|
-
* [ ] wave-edit
|
|
35
|
-
* [ ] Sandbox expressions: no global, no "scope" object name, no "arguments"
|
|
36
|
+
* [ ] 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.
|
|
@@ -179,7 +191,7 @@
|
|
|
179
191
|
+ methods provided in `init` may not have access to scope _yet_.
|
|
180
192
|
~- not reliable way to obtain scope via `this.x` - better be explicit as `state.x`
|
|
181
193
|
|
|
182
|
-
## [
|
|
194
|
+
## [x] :onconnected/:ondisconnected? -> let's move to plugins for now
|
|
183
195
|
|
|
184
196
|
-> waiting for use-case
|
|
185
197
|
|
|
@@ -211,6 +223,8 @@
|
|
|
211
223
|
* @sprae/item: `<x :item="{type:a, scope:b}"` – provide microdata
|
|
212
224
|
- can be solved naturally, unless there's special meaning
|
|
213
225
|
* @sprae/hcodes: `<x :hcode=""` – provide microformats
|
|
226
|
+
* @sprae/with
|
|
227
|
+
* @sprae/connected
|
|
214
228
|
|
|
215
229
|
## [x] Write any-attributes via :<prop>? -> yep
|
|
216
230
|
|
|
@@ -239,7 +253,7 @@
|
|
|
239
253
|
+ it has better control over serialization
|
|
240
254
|
+ `:onchange:oninput="e=>xyz"` is very good
|
|
241
255
|
|
|
242
|
-
## [ ] Sandbox?
|
|
256
|
+
## [ ] Sandbox? -> too complex for now. Waiting for use-cases
|
|
243
257
|
|
|
244
258
|
1. Use subscript?
|
|
245
259
|
+ solves access to any internal signals on syntactic level
|
|
@@ -259,6 +273,13 @@
|
|
|
259
273
|
+ that "unlimits" returned struct, so that any property can be added/deleted.
|
|
260
274
|
- doesn't really save from `new (()=>{}).constructor` hack: we gotta substitute objects too.
|
|
261
275
|
|
|
276
|
+
2. Use sandboxed proxy
|
|
277
|
+
- tough evaluation
|
|
278
|
+
- tough implementation
|
|
279
|
+
- screwed up data
|
|
280
|
+
- no full protection
|
|
281
|
+
+ does minimal catch
|
|
282
|
+
|
|
262
283
|
## [x] :onclick="direct code" ? -> no: immediately invoked.
|
|
263
284
|
|
|
264
285
|
+ compatible with direct `onclick=...`
|
|
@@ -268,7 +289,12 @@
|
|
|
268
289
|
- illicit `event` object
|
|
269
290
|
- conflicts with regular attrs logic: the code is immediately invoked and can assign a function.
|
|
270
291
|
|
|
271
|
-
## [
|
|
292
|
+
## [x] Should getters convert to computed? -> yes, that's relatively cheap and useful
|
|
272
293
|
|
|
273
294
|
+ shorter and nicer syntax
|
|
274
|
-
- possibly longer
|
|
295
|
+
- possibly longer init
|
|
296
|
+
|
|
297
|
+
## [ ] Better :ref
|
|
298
|
+
+ :ref="`a-${1}`"
|
|
299
|
+
+ :id:ref="xyz"
|
|
300
|
+
? maybe id should have same signature
|
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
|
|
|
@@ -148,9 +148,11 @@ Add event listeners.
|
|
|
148
148
|
<input :value="text" :oninput:onchange="e => text=e.target.value">
|
|
149
149
|
|
|
150
150
|
<!-- Sequence of events -->
|
|
151
|
-
<button :onfocus-onblur="e => {
|
|
151
|
+
<button :onfocus-onblur="e => {
|
|
152
|
+
// onfocus
|
|
152
153
|
let id = setInterval(track,200)
|
|
153
|
-
return e => {
|
|
154
|
+
return e => {
|
|
155
|
+
// onblur
|
|
154
156
|
clearInterval(id)
|
|
155
157
|
}
|
|
156
158
|
}">
|
|
@@ -176,34 +178,15 @@ Invoked when element is connected or disconnected from DOM.
|
|
|
176
178
|
```
|
|
177
179
|
-->
|
|
178
180
|
|
|
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
181
|
#### `:data="values"`
|
|
202
182
|
|
|
203
183
|
Set [data-*](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*) attributes. CamelCase is converted to dash-case.
|
|
204
184
|
|
|
205
185
|
```html
|
|
206
186
|
<input :data="{foo: 1, barBaz: true}" />
|
|
187
|
+
<!--
|
|
188
|
+
<input data-foo="1" data-bar-baz="true" />
|
|
189
|
+
-->
|
|
207
190
|
```
|
|
208
191
|
|
|
209
192
|
#### `:aria="values"`
|
|
@@ -211,26 +194,42 @@ Set [data-*](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes
|
|
|
211
194
|
Set [aria-role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) attributes. Boolean values are stringified.
|
|
212
195
|
|
|
213
196
|
```html
|
|
214
|
-
<input
|
|
197
|
+
<input role="combobox" :aria="{
|
|
215
198
|
controls: 'joketypes',
|
|
216
199
|
autocomplete: 'list',
|
|
217
200
|
expanded: false,
|
|
218
201
|
activeOption: 'item1',
|
|
219
202
|
activedescendant: ''
|
|
220
203
|
}" />
|
|
204
|
+
<!--
|
|
205
|
+
<input role="combobox" aria-controls="joketypes" aria-autocomplete="list" aria-expanded="false" aria-active-option="item1" aria-activedescendant="">
|
|
206
|
+
-->
|
|
221
207
|
```
|
|
222
208
|
|
|
223
209
|
#### `:ref="id"`
|
|
224
210
|
|
|
225
|
-
Expose element to
|
|
211
|
+
Expose element to data scope with the `id`:
|
|
226
212
|
|
|
227
213
|
```html
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
214
|
+
<!-- single item -->
|
|
215
|
+
<textarea :ref="text" placeholder="Enter text..."></textarea>
|
|
216
|
+
|
|
217
|
+
<!-- iterable items -->
|
|
218
|
+
<ul>
|
|
219
|
+
<li :each="item in items" :ref="item">
|
|
220
|
+
<input
|
|
221
|
+
:onfocus-onblur="e => (item.classList.add('editing'), e => item.classList.remove('editing'))"
|
|
222
|
+
/>
|
|
223
|
+
</li>
|
|
224
|
+
</ul>
|
|
225
|
+
|
|
226
|
+
<script type="module">
|
|
227
|
+
import sprae from 'sprae';
|
|
228
|
+
let state = sprae(document, {items: ['a','b','c']})
|
|
229
|
+
|
|
230
|
+
// element is in the state
|
|
231
|
+
state.text // <textarea></textarea>
|
|
232
|
+
</script>
|
|
234
233
|
```
|
|
235
234
|
|
|
236
235
|
<!--
|
|
@@ -257,20 +256,7 @@ This way, for example, _@preact/signals_ or _rxjs_ can be connected directly byp
|
|
|
257
256
|
|
|
258
257
|
## Hints
|
|
259
258
|
|
|
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:
|
|
259
|
+
**1.** Data supports signal values, which can be an alternative way to control template state:
|
|
274
260
|
|
|
275
261
|
```html
|
|
276
262
|
<div id="done" :text="loading ? 'loading' : result">...</div>
|
|
@@ -292,7 +278,7 @@ This way, for example, _@preact/signals_ or _rxjs_ can be connected directly byp
|
|
|
292
278
|
</script>
|
|
293
279
|
```
|
|
294
280
|
|
|
295
|
-
**
|
|
281
|
+
**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
282
|
|
|
297
283
|
```html
|
|
298
284
|
<div :text="clicks">#</div> clicks
|
|
@@ -306,11 +292,11 @@ This way, for example, _@preact/signals_ or _rxjs_ can be connected directly byp
|
|
|
306
292
|
</script>
|
|
307
293
|
```
|
|
308
294
|
|
|
309
|
-
**
|
|
295
|
+
**3.** Getters turn into computed values automatically:
|
|
310
296
|
|
|
311
297
|
```html
|
|
312
298
|
<div id="x-plus-y">
|
|
313
|
-
<span :text="x">x</span> + <span :text="y">y</span> = <span :text="
|
|
299
|
+
<span :text="x">x</span> + <span :text="y">y</span> = <span :text="z">z</span>
|
|
314
300
|
</div>
|
|
315
301
|
|
|
316
302
|
<script type="module">
|
|
@@ -318,17 +304,19 @@ This way, for example, _@preact/signals_ or _rxjs_ can be connected directly byp
|
|
|
318
304
|
let state = sprae(document, { x:1, y:1, get z() { return this.x + this.y } })
|
|
319
305
|
|
|
320
306
|
state.x = 2, state.y = 2
|
|
307
|
+
state.z // 4
|
|
321
308
|
</script>
|
|
322
309
|
```
|
|
323
310
|
|
|
324
311
|
## Examples
|
|
325
312
|
|
|
326
313
|
* TODO MVC: [demo](https://dy.github.io/sprae/examples/todomvc), [code](https://github.com/dy/sprae/blob/main/examples/todomvc.html)
|
|
314
|
+
* Waveedit: [demo](), [code](https://github.com/dy/waveedit)
|
|
327
315
|
|
|
328
316
|
## Justification
|
|
329
317
|
|
|
330
318
|
* [Template-parts](https://github.com/dy/template-parts) / [templize](https://github.com/dy/templize) is progressive, but is stuck with native HTML quirks ([parsing table](https://github.com/github/template-parts/issues/24), [svg attributes](https://github.com/github/template-parts/issues/25), [liquid syntax](https://shopify.github.io/liquid/tags/template/#raw) conflict etc). Also ergonomics of `attr="{{}}"` is inferior to `:attr=""` since it creates flash of uninitialized values.
|
|
331
|
-
* [Alpine](https://github.com/alpinejs/alpine) / [vue](https://github.com/vuejs/petite-vue) / [lit](https://github.com/lit/lit/tree/main/packages/lit-html) escapes native HTML quirks, but the syntax is a bit scattered: `:attr`, `v-*`,`x-*`, `@evt`, `{{}}` can be expressed with single convention. Besides, functionality is too broad and can be reduced to essence. Also they tend to [self-encapsulate](https://github.com/alpinejs/alpine/discussions/3223), making interop hard.
|
|
319
|
+
* [Alpine](https://github.com/alpinejs/alpine) / [vue](https://github.com/vuejs/petite-vue) / [lit](https://github.com/lit/lit/tree/main/packages/lit-html) escapes native HTML quirks, but the syntax is a bit scattered: `:attr`, `v-*`,`x-*`, `@evt`, `{{}}` can be expressed with single convention. Besides, functionality is too broad and can be reduced to essence: perfection is when there's nothing to take away, not add. Also they tend to [self-encapsulate](https://github.com/alpinejs/alpine/discussions/3223), making interop hard.
|
|
332
320
|
* React/[preact](https://ghub.io/preact) does the job wiring up JS to HTML, but with an extreme of migrating HTML to JSX and enforcing SPA, which is not organic for HTML. Also it doesn't support reactive fields (needs render call).
|
|
333
321
|
|
|
334
322
|
_sprae_ takes convention of _templize directives_ (_alpine_/_vue_ attrs) and builds upon [_@preact/signals_](https://ghub.io/@preact/signals).
|
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,54 +576,59 @@ 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 {
|
|
646
627
|
const startFn = listeners[evt];
|
|
647
628
|
const nextEvt = (fn, cur = 0) => {
|
|
648
|
-
|
|
629
|
+
el.addEventListener(evts[cur], listeners[evt] = (e2) => {
|
|
649
630
|
fn = fn(e2);
|
|
650
|
-
|
|
631
|
+
el.removeEventListener(evts[cur], listeners[evt]);
|
|
651
632
|
if (++cur < evts.length && typeof fn === "function")
|
|
652
633
|
nextEvt(fn, cur);
|
|
653
634
|
else
|
|
@@ -658,56 +639,59 @@ directives["on"] = (el2, expr, state) => {
|
|
|
658
639
|
}
|
|
659
640
|
}
|
|
660
641
|
;
|
|
661
|
-
}
|
|
642
|
+
};
|
|
662
643
|
};
|
|
663
|
-
directives["data"] = (
|
|
664
|
-
let evaluate = parseExpr(
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
for (let key in
|
|
668
|
-
|
|
669
|
-
}
|
|
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
|
+
};
|
|
670
651
|
};
|
|
671
|
-
directives["aria"] = (
|
|
672
|
-
let evaluate = parseExpr(
|
|
652
|
+
directives["aria"] = (el, expr, values) => {
|
|
653
|
+
let evaluate = parseExpr(el, expr, ":aria");
|
|
673
654
|
const update = (value) => {
|
|
674
655
|
for (let key in value)
|
|
675
|
-
|
|
656
|
+
attr(el, "aria-" + dashcase(key), value[key] == null ? null : value[key] + "");
|
|
676
657
|
};
|
|
677
|
-
|
|
658
|
+
return (state) => update(evaluate(state));
|
|
678
659
|
};
|
|
679
660
|
var evaluatorMemo = {};
|
|
680
|
-
function parseExpr(
|
|
661
|
+
function parseExpr(el, expression, dir) {
|
|
681
662
|
if (evaluatorMemo[expression])
|
|
682
663
|
return evaluatorMemo[expression];
|
|
683
664
|
let rightSideSafeExpression = /^[\n\s]*if.*\(.*\)/.test(expression) || /^(let|const)\s/.test(expression) ? `(() => { ${expression} })()` : expression;
|
|
684
665
|
let evaluate;
|
|
685
666
|
try {
|
|
686
|
-
evaluate = new Function(`
|
|
667
|
+
evaluate = new Function(`__scope`, `with (__scope) { return (${rightSideSafeExpression}) };`).bind(el);
|
|
687
668
|
} catch (e2) {
|
|
688
|
-
return exprError(e2,
|
|
669
|
+
return exprError(e2, el, expression, dir);
|
|
689
670
|
}
|
|
690
671
|
return evaluatorMemo[expression] = (state) => {
|
|
691
672
|
let result;
|
|
692
673
|
try {
|
|
693
674
|
result = evaluate(state);
|
|
694
675
|
} catch (e2) {
|
|
695
|
-
return exprError(e2,
|
|
676
|
+
return exprError(e2, el, expression, dir);
|
|
696
677
|
}
|
|
697
678
|
return result;
|
|
698
679
|
};
|
|
699
680
|
}
|
|
700
|
-
function exprError(error, element, expression, dir
|
|
681
|
+
function exprError(error, element, expression, dir) {
|
|
701
682
|
Object.assign(error, { element, expression });
|
|
702
|
-
console.warn(`\
|
|
683
|
+
console.warn(`\u2234 ${error.message}
|
|
703
684
|
|
|
704
685
|
${dir}=${expression ? `"${expression}"
|
|
705
686
|
|
|
706
|
-
` : ""}`, element
|
|
687
|
+
` : ""}`, element);
|
|
707
688
|
setTimeout(() => {
|
|
708
689
|
throw error;
|
|
709
690
|
}, 0);
|
|
710
691
|
}
|
|
692
|
+
function dashcase(str) {
|
|
693
|
+
return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => "-" + match.toLowerCase());
|
|
694
|
+
}
|
|
711
695
|
|
|
712
696
|
// src/core.js
|
|
713
697
|
var memo = /* @__PURE__ */ new WeakMap();
|
|
@@ -717,31 +701,36 @@ function sprae(container, values) {
|
|
|
717
701
|
if (memo.has(container))
|
|
718
702
|
return memo.get(container);
|
|
719
703
|
values ||= {};
|
|
720
|
-
const
|
|
721
|
-
const init = (
|
|
722
|
-
if (
|
|
723
|
-
for (let i2 = 0; i2 <
|
|
724
|
-
let
|
|
725
|
-
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] !== ":") {
|
|
726
710
|
i2++;
|
|
727
711
|
continue;
|
|
728
712
|
}
|
|
729
|
-
|
|
730
|
-
let expr =
|
|
731
|
-
let attrNames =
|
|
713
|
+
el.removeAttribute(attr2.name);
|
|
714
|
+
let expr = attr2.value;
|
|
715
|
+
let attrNames = attr2.name.slice(1).split(":");
|
|
732
716
|
for (let attrName of attrNames) {
|
|
733
|
-
let dir = directives[attrName] || directives_default
|
|
734
|
-
|
|
735
|
-
|
|
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;
|
|
736
722
|
}
|
|
737
723
|
}
|
|
738
724
|
}
|
|
739
|
-
for (let i2 = 0, child; child =
|
|
740
|
-
|
|
741
|
-
|
|
725
|
+
for (let i2 = 0, child; child = el.children[i2]; i2++) {
|
|
726
|
+
if (init(child, el) === false)
|
|
727
|
+
i2--;
|
|
742
728
|
}
|
|
743
729
|
};
|
|
744
730
|
init(container);
|
|
731
|
+
const state = SignalStruct(values);
|
|
732
|
+
for (let update of updates)
|
|
733
|
+
b(() => update(state));
|
|
745
734
|
memo.set(container, state);
|
|
746
735
|
return state;
|
|
747
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=n[e],o=(s,a=0)=>{t.addEventListener(i[a],n[e]=f=>{s=s(f),t.removeEventListener(i[a],n[e]),++a<i.length&&"function"==typeof s?o(s,a):o(r)})};o(r)}}}))},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 a(t){this.v=t,this.i=0,this.n=void 0,this.t=void 0}function u(t){return new a(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){a.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)}a.prototype.h=function(){return!0},a.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)},a.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)},a.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}}))},a.prototype.valueOf=function(){return this.value},a.prototype.toString=function(){return this.value+""},a.prototype.peek=function(){return this.v},Object.defineProperty(a.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 a).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)}a.prototype.S.call(this,t)},v.prototype.U=function(t){if(a.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)?u(w(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,f,a,l,h,c;return r&&(f=t=>n.value=t,(s=i)&&(c=(s[Symbol.observable]?.()||s).subscribe?.(f,a,undefined),h=c&&(()=>c.unsubscribe?.())||s.set&&s.call?.(l,f)||(s.then?.((t=>{!l&&f(t)}),a)||(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,a=i.length,u=e.length,{remove:l,same:h,insert:c,replace:v}=j;for(;f<a&&f<u&&h(e[f],i[f]);)f++;for(;f<a&&f<u&&h(i[a-1],e[u-1]);)r=i[(--u,--a)];if(f==u)for(;f<a;)c(r,i[f++],t);else{for(n=e[f];f<a;)s=i[f++],o=n?n.nextSibling:r,h(n,s)?n=o:f<a&&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,M(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),F(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 _(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 a=[];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])):_(Error("Bad list value"),t,r.items,":each"):i=[];let u=[],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,a=f.get(i);if(a||(a=t.cloneNode(!0),f.set(i,a)),u.push(a),!s.has(i)){let f=Object.create(e);f[r.item]=o,r.index&&(f[r.index]=n),t[L]&&(f[t[L]]=a),s.set(i,f)}l.push(s.get(i))}var h;N(n.parentNode,a,u,n),a=u;for(let t=0;t<u.length;t++)F(u[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]=a=>{s=s(a),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-"+M(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("__scope",`with (__scope) { return (${n}) };`).bind(t)}catch(r){return _(r,t,e,i)}return P[e]=n=>{let o;try{o=r(n)}catch(r){return _(r,t,e,i)}return o}}function _(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 M(t){return t.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(t=>"-"+t.toLowerCase()))}var T=new WeakMap;function F(t,e){if(!t.children)return;if(T.has(t))return T.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)||(()=>{})),T.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 T.set(t,n),n}var I=F;export{I 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,51 @@
|
|
|
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
|
-
|
|
41
|
+
// FIXME: wait for complex ref use-case
|
|
42
|
+
// parseExpr(el, `__scope[${expr}]=this`, ':ref')(values)
|
|
43
|
+
values[expr] = el;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
directives['if'] = (el, expr,
|
|
46
|
+
directives['if'] = (el, expr, values) => {
|
|
47
47
|
let holder = document.createTextNode(''),
|
|
48
|
-
clauses = [parseExpr(el, expr, ':if'
|
|
48
|
+
clauses = [parseExpr(el, expr, ':if')],
|
|
49
49
|
els = [el], cur = el
|
|
50
50
|
|
|
51
51
|
while (cur = el.nextElementSibling) {
|
|
@@ -53,7 +53,7 @@ directives['if'] = (el, expr, state) => {
|
|
|
53
53
|
cur.removeAttribute(':else');
|
|
54
54
|
if (expr = cur.getAttribute(':if')) {
|
|
55
55
|
cur.removeAttribute(':if'), cur.remove();
|
|
56
|
-
els.push(cur); clauses.push(parseExpr(el, expr, ':else :if'
|
|
56
|
+
els.push(cur); clauses.push(parseExpr(el, expr, ':else :if'));
|
|
57
57
|
}
|
|
58
58
|
else {
|
|
59
59
|
cur.remove(); els.push(cur); clauses.push(() => 1);
|
|
@@ -63,40 +63,43 @@ directives['if'] = (el, expr, state) => {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
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
66
|
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
return (state) => {
|
|
68
|
+
let i = clauses.findIndex(f => f(state))
|
|
69
|
+
if (els[i] != cur) {
|
|
70
|
+
(cur[_each] || cur).replaceWith(cur = els[i] || holder);
|
|
71
|
+
// NOTE: it lazily initializes elements on insertion, it's safe to sprae multiple times
|
|
72
|
+
sprae(cur, state);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
72
75
|
}
|
|
73
76
|
|
|
74
|
-
directives['each'] = (tpl, expr,
|
|
77
|
+
directives['each'] = (tpl, expr, values) => {
|
|
75
78
|
let each = parseForExpression(expr);
|
|
76
79
|
if (!each) return exprError(new Error, tpl, expr);
|
|
77
80
|
|
|
78
|
-
const getItems = parseExpr(tpl, each.items, ':each', state);
|
|
79
|
-
|
|
80
81
|
// FIXME: make sure no memory leak here
|
|
81
82
|
// we need :if to be able to replace holder instead of tpl for :if :each case
|
|
82
83
|
const holder = tpl[_each] = document.createTextNode('')
|
|
83
84
|
tpl.replaceWith(holder)
|
|
84
85
|
|
|
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
|
-
})
|
|
86
|
+
const evaluate = parseExpr(tpl, each.items, ':each');
|
|
93
87
|
|
|
94
88
|
// stores scope per data item
|
|
95
89
|
const scopes = new WeakMap()
|
|
96
90
|
// element per data item
|
|
97
91
|
const itemEls = new WeakMap()
|
|
98
92
|
let curEls = []
|
|
99
|
-
|
|
93
|
+
|
|
94
|
+
return (state) => {
|
|
95
|
+
// get items
|
|
96
|
+
let list = evaluate(state)
|
|
97
|
+
if (!list) list = []
|
|
98
|
+
else if (typeof list === 'number') list = Array.from({length: list}, (_, i)=>[i, i+1])
|
|
99
|
+
else if (list.constructor === Object) list = Object.entries(list)
|
|
100
|
+
else if (Array.isArray(list)) list = list.map((item,i) => [i+1, item])
|
|
101
|
+
else exprError(Error('Bad list value'), tpl, each.items, ':each', list)
|
|
102
|
+
|
|
100
103
|
// collect elements/scopes for items
|
|
101
104
|
let newEls = [], elScopes = []
|
|
102
105
|
|
|
@@ -128,9 +131,7 @@ directives['each'] = (tpl, expr, state) => {
|
|
|
128
131
|
for (let i = 0; i < newEls.length; i++) {
|
|
129
132
|
sprae(newEls[i], elScopes[i])
|
|
130
133
|
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return -1 // count back for 1
|
|
134
|
+
}
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
// This was taken AlpineJS, former VueJS 2.* core. Thanks Alpine & Vue!
|
|
@@ -157,48 +158,63 @@ function parseForExpression(expression) {
|
|
|
157
158
|
return res
|
|
158
159
|
}
|
|
159
160
|
|
|
160
|
-
directives['id'] = (el, expr,
|
|
161
|
-
let evaluate = parseExpr(el, expr, ':id'
|
|
161
|
+
directives['id'] = (el, expr, values) => {
|
|
162
|
+
let evaluate = parseExpr(el, expr, ':id')
|
|
162
163
|
const update = v => el.id = v || v === 0 ? v : ''
|
|
163
|
-
|
|
164
|
+
return (state) => update(evaluate(state))
|
|
164
165
|
}
|
|
165
166
|
|
|
166
|
-
directives[''] = (el, expr,
|
|
167
|
-
let evaluate = parseExpr(el, expr, ':'
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
directives['class'] = (el, expr, values) => {
|
|
168
|
+
let evaluate = parseExpr(el, expr, ':class')
|
|
169
|
+
let initClassName = el.className
|
|
170
|
+
return (state) => {
|
|
171
|
+
let v = evaluate(state)
|
|
172
|
+
el.className = initClassName + typeof v === 'string' ? v : (Array.isArray(v) ? v : Object.entries(v).map(([k,v])=>v?k:'')).filter(Boolean).join(' ')
|
|
171
173
|
}
|
|
172
|
-
effect(()=>update(evaluate(state)))
|
|
173
174
|
}
|
|
174
175
|
|
|
175
|
-
directives['
|
|
176
|
-
let evaluate = parseExpr(el, expr, ':
|
|
176
|
+
directives['style'] = (el, expr, values) => {
|
|
177
|
+
let evaluate = parseExpr(el, expr, ':style')
|
|
178
|
+
return (state) => {
|
|
179
|
+
let v = evaluate(state)
|
|
180
|
+
if (typeof v === 'string') el.setAttribute('style', v)
|
|
181
|
+
else for (let k in v) el.style[k] = v[k]
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
directives['text'] = (el, expr, values) => {
|
|
186
|
+
let evaluate = parseExpr(el, expr, ':text')
|
|
177
187
|
|
|
178
188
|
const update = (value) => {
|
|
179
189
|
el.textContent = value == null ? '' : value;
|
|
180
190
|
}
|
|
181
191
|
|
|
182
|
-
|
|
192
|
+
return (state) => update(evaluate(state))
|
|
183
193
|
}
|
|
184
194
|
|
|
185
195
|
// 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
|
-
|
|
196
|
+
directives['value'] = (el, expr, values) => {
|
|
197
|
+
let evaluate = parseExpr(el, expr, ':in')
|
|
198
|
+
|
|
199
|
+
let update = (
|
|
200
|
+
el.type === 'text' || el.type === '' ? value => el.setAttribute('value', el.value = value == null ? '' : value) :
|
|
201
|
+
el.type === 'checkbox' ? value => (el.value = value ? 'on' : '', attr(el, 'checked', value)) :
|
|
202
|
+
el.type === 'select-one' ? value => (
|
|
203
|
+
[...el.options].map(el => el.removeAttribute('selected')),
|
|
204
|
+
el.value = value,
|
|
205
|
+
el.selectedOptions[0]?.setAttribute('selected', '')
|
|
206
|
+
) :
|
|
207
|
+
value => el.value = value
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return (state) => update(evaluate(state))
|
|
196
211
|
}
|
|
197
212
|
|
|
198
|
-
directives['on'] = (el, expr,
|
|
199
|
-
let evaluate = parseExpr(el, expr, ':on'
|
|
213
|
+
directives['on'] = (el, expr, values) => {
|
|
214
|
+
let evaluate = parseExpr(el, expr, ':on')
|
|
200
215
|
let listeners = {}
|
|
201
|
-
|
|
216
|
+
|
|
217
|
+
return (state) => {
|
|
202
218
|
for (let evt in listeners) el.removeEventListener(evt, listeners[evt]);
|
|
203
219
|
|
|
204
220
|
listeners = evaluate(state);
|
|
@@ -219,23 +235,24 @@ directives['on'] = (el, expr, state) => {
|
|
|
219
235
|
nextEvt(startFn)
|
|
220
236
|
}
|
|
221
237
|
};
|
|
222
|
-
}
|
|
238
|
+
}
|
|
223
239
|
}
|
|
224
240
|
|
|
225
|
-
directives['data'] = (el, expr,
|
|
226
|
-
let evaluate = parseExpr(el, expr, ':data'
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
241
|
+
directives['data'] = (el, expr, values) => {
|
|
242
|
+
let evaluate = parseExpr(el, expr, ':data')
|
|
243
|
+
|
|
244
|
+
return ((state) => {
|
|
245
|
+
let value = evaluate(state)
|
|
246
|
+
for (let key in value) el.dataset[key] = value[key];
|
|
230
247
|
})
|
|
231
248
|
}
|
|
232
249
|
|
|
233
|
-
directives['aria'] = (el, expr,
|
|
234
|
-
let evaluate = parseExpr(el, expr, ':aria'
|
|
250
|
+
directives['aria'] = (el, expr, values) => {
|
|
251
|
+
let evaluate = parseExpr(el, expr, ':aria')
|
|
235
252
|
const update = (value) => {
|
|
236
|
-
for (let key in value)
|
|
253
|
+
for (let key in value) attr(el, 'aria-' + dashcase(key), value[key] == null ? null : value[key] + '');
|
|
237
254
|
}
|
|
238
|
-
|
|
255
|
+
return ((state) => update(evaluate(state)))
|
|
239
256
|
}
|
|
240
257
|
|
|
241
258
|
|
|
@@ -243,7 +260,7 @@ let evaluatorMemo = {}
|
|
|
243
260
|
|
|
244
261
|
// borrowed from alpine: https://github.com/alpinejs/alpine/blob/main/packages/alpinejs/src/evaluator.js#L61
|
|
245
262
|
// it seems to be more robust than subscript
|
|
246
|
-
function parseExpr(el, expression, dir
|
|
263
|
+
function parseExpr(el, expression, dir) {
|
|
247
264
|
if (evaluatorMemo[expression]) return evaluatorMemo[expression]
|
|
248
265
|
|
|
249
266
|
// Some expressions that are useful in Alpine are not valid as the right side of an expression.
|
|
@@ -260,22 +277,26 @@ function parseExpr(el, expression, dir, scope) {
|
|
|
260
277
|
// guard static-time eval errors
|
|
261
278
|
let evaluate
|
|
262
279
|
try {
|
|
263
|
-
evaluate = new Function(`
|
|
280
|
+
evaluate = new Function(`__scope`,`with (__scope) { return (${rightSideSafeExpression}) };`).bind(el)
|
|
264
281
|
} catch ( e ) {
|
|
265
|
-
return exprError(e, el, expression, dir
|
|
282
|
+
return exprError(e, el, expression, dir)
|
|
266
283
|
}
|
|
267
284
|
|
|
268
285
|
// guard runtime eval errors
|
|
269
286
|
return evaluatorMemo[expression] = (state) => {
|
|
270
287
|
let result
|
|
271
288
|
try { result = evaluate(state) }
|
|
272
|
-
catch (e) { return exprError(e, el, expression, dir
|
|
289
|
+
catch (e) { return exprError(e, el, expression, dir) }
|
|
273
290
|
return result
|
|
274
291
|
}
|
|
275
292
|
}
|
|
276
293
|
|
|
277
|
-
export function exprError(error, element, expression, dir
|
|
294
|
+
export function exprError(error, element, expression, dir) {
|
|
278
295
|
Object.assign( error, { element, expression } )
|
|
279
|
-
console.warn(`∴
|
|
296
|
+
console.warn(`∴ ${error.message}\n\n${dir}=${ expression ? `"${expression}"\n\n` : '' }`, element)
|
|
280
297
|
setTimeout(() => { throw error }, 0)
|
|
281
298
|
}
|
|
299
|
+
|
|
300
|
+
function dashcase(str) {
|
|
301
|
+
return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => '-' + match.toLowerCase());
|
|
302
|
+
};
|
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, {})
|