sprae 5.2.0 → 6.0.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 +14 -15
- package/package.json +2 -2
- package/r&d.md +58 -17
- package/readme.md +77 -87
- package/sprae.auto.js +31 -70
- package/sprae.auto.min.js +1 -1
- package/sprae.js +31 -70
- package/sprae.min.js +1 -1
- package/src/core.js +16 -11
- package/src/directives.js +32 -65
- package/test/{dom.js → attrs.js} +9 -325
- package/test/events.js +138 -0
- package/test/test.js +12 -1
- package/todo.md +2 -1
package/examples/todomvc.html
CHANGED
|
@@ -14,39 +14,38 @@
|
|
|
14
14
|
<section class="todoapp">
|
|
15
15
|
<header class="header">
|
|
16
16
|
<h1>todos</h1>
|
|
17
|
-
<input class="new-todo" placeholder="What needs to be done?" autofocus
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}">
|
|
17
|
+
<input class="new-todo" placeholder="What needs to be done?" autofocus @keypress.enter="
|
|
18
|
+
save(todos = [...todos, { text: this.value, done: false}])
|
|
19
|
+
this.value = ''
|
|
20
|
+
">
|
|
22
21
|
</header>
|
|
23
22
|
<section class="main">
|
|
24
|
-
<input id="toggle-all" class="toggle-all" type="checkbox"
|
|
23
|
+
<input id="toggle-all" class="toggle-all" type="checkbox" @click="
|
|
25
24
|
const all = todos.every(item => item.done)
|
|
26
25
|
save(todos = todos.map(item => (item.done = !all, item)))
|
|
27
|
-
|
|
26
|
+
">
|
|
28
27
|
<label for="toggle-all">Mark all as complete</label>
|
|
29
28
|
<ul class="todo-list">
|
|
30
29
|
<li :ref="li" :each="item in todos"
|
|
31
30
|
:class="{completed: item.done}"
|
|
32
31
|
:hidden="hash === '#/active' ? item.done : hash === '#/completed' ? !item.done : false"
|
|
33
|
-
|
|
32
|
+
@dblclick="
|
|
34
33
|
li.querySelector('.edit').focus()
|
|
35
34
|
li.classList.add('editing')
|
|
36
|
-
|
|
35
|
+
">
|
|
37
36
|
<div class="view">
|
|
38
37
|
<input class="toggle" type="checkbox"
|
|
39
38
|
:checked="item.done"
|
|
40
|
-
|
|
39
|
+
@change="item.done = !item.done, save(todos)"
|
|
41
40
|
/>
|
|
42
41
|
<label :text="item.text"></label>
|
|
43
|
-
<button class="destroy"
|
|
42
|
+
<button class="destroy" @click="save(todos = todos.filter(i => i.text !== item.text))"></button>
|
|
44
43
|
</div>
|
|
45
44
|
<input class=edit
|
|
46
45
|
:value="item.text"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
@input="item.text = this.value; save(todos)"
|
|
47
|
+
@blur="li.classList.remove('editing');"
|
|
48
|
+
@keypress.enter="this.blur()"
|
|
50
49
|
/>
|
|
51
50
|
</li>
|
|
52
51
|
</ul>
|
|
@@ -62,7 +61,7 @@
|
|
|
62
61
|
</ul>
|
|
63
62
|
<button class="clear-completed"
|
|
64
63
|
:hidden="todos.every(item => !item.done)"
|
|
65
|
-
|
|
64
|
+
@click="save(todos = todos.filter(item => !item.done ? true : false))">
|
|
66
65
|
Clear completed
|
|
67
66
|
</button>
|
|
68
67
|
</footer>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sprae",
|
|
3
3
|
"description": "DOM microhydration.",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "6.0.0",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
7
7
|
"type": "module",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"@preact/signals": "^1.1.3",
|
|
13
13
|
"es-module-shims": "^1.6.2",
|
|
14
14
|
"esbuild": "^0.15.14",
|
|
15
|
-
"hyperf": "^1.
|
|
15
|
+
"hyperf": "^1.6.2",
|
|
16
16
|
"jsdom": "^21.1.0",
|
|
17
17
|
"signal-struct": "^1.10.0",
|
|
18
18
|
"terser": "^5.15.1",
|
package/r&d.md
CHANGED
|
@@ -225,13 +225,14 @@
|
|
|
225
225
|
- slows down domdiff
|
|
226
226
|
- can be solved as `<x :if="xxx" :="xxx && (...)'">` automatically
|
|
227
227
|
|
|
228
|
-
## [
|
|
228
|
+
## [ ] :onmount/onunmount? ->
|
|
229
229
|
|
|
230
230
|
+ useful for :if, :each
|
|
231
231
|
+ useful to dispose listeners via :onunmount (opposed to hidden symbols)
|
|
232
232
|
- doesn't really solve disposal: if element is attached again, it would need to reattach removed listeners
|
|
233
233
|
-> can be dolved via teardowns returned from updators
|
|
234
234
|
-> nah, event listeners don't need collection, just make sure no refs to element remain
|
|
235
|
+
+ can be useful for lazy-loadings
|
|
235
236
|
|
|
236
237
|
## [x] :focus="direct code", :evt="direct code" -> nah, too messy.
|
|
237
238
|
|
|
@@ -374,7 +375,63 @@
|
|
|
374
375
|
- still need that syntax for filters, maps etc
|
|
375
376
|
+ can be made async by default
|
|
376
377
|
- illicit `event` object
|
|
378
|
+
+ we don't seem to ever need that event argument, many cases are covered by `.prevent` or `.stop`
|
|
379
|
+
~+ generally `e=>` seem to conflict logically with modifiers sense
|
|
380
|
+
+ `e=>` brings syntax burden - we may not ever need functions
|
|
381
|
+
+ less problem detecting `const/let` in code
|
|
377
382
|
- conflicts with regular attrs logic: the code is immediately invoked and can assign a function.
|
|
383
|
+
- `:oninput="e => (handleCaret(e), updateTimecodes())"`
|
|
384
|
+
~ `:oninput="handleCaret(event), updateTimecodes()"`
|
|
385
|
+
- `:onbeforeinput="handleBeforeInput"`
|
|
386
|
+
~ `:onbeforeinput="handleBeforeInput(event)"`
|
|
387
|
+
- `:ondragenter..ondragleave:ondragenter..ondrop="e=>(this.classList.add('w-dragover'),e=>this.classList.remove('w-dragover'))"`
|
|
388
|
+
~ `:ondragenter..ondragleave:ondragenter..ondrop="this.classList.add('w-dragover'), e=>this.classList.remove('w-dragover')"`
|
|
389
|
+
- `:ondrop="e=>console.log(e.dataTransfer.types)||e.preventDefault()"`
|
|
390
|
+
~ `:ondrop.prevent="console.log(event.dataTransfer.types)"`
|
|
391
|
+
- `:onfocus="e => (e.relatedTarget ? e.relatedTarget.focus() : e.target.blur())"`
|
|
392
|
+
~ `:onfocus="event.relatedTarget ? event.relatedTarget.focus() : event.target.blur()"`
|
|
393
|
+
- `:onpopstate.window="e => goto(e.state)"`
|
|
394
|
+
~ `:onpopstate.window="goto(event.state)"`
|
|
395
|
+
- `:onclick.toggle="play"`
|
|
396
|
+
~ `:onclick.toggle="play()"`
|
|
397
|
+
|
|
398
|
+
## [ ] Should we introduce `@click` for short-notation events?
|
|
399
|
+
+ gives shorter code for majority of cases
|
|
400
|
+
+ can be non-conflicting
|
|
401
|
+
+ compatible with all frameworks (vue, alpine, lucia, lit)
|
|
402
|
+
+ gives better meaning to modifiers - moves them outside of `:` attribute
|
|
403
|
+
- multiple events `@input@change="code"` is not nice
|
|
404
|
+
~ that's fine and even meaningful
|
|
405
|
+
- chain of events `@focus..@blur="return (e)=>{}"` creates confusing `return` outside of body, as well as inconsistent chain pattern
|
|
406
|
+
? remove that pattern
|
|
407
|
+
+ it's still unsatisfactory: `@mousedown.document..@mouseup.document="e=> (isMouseDown = true, e=> isMouseDown = false)"` works, but what if we want to add `@touchstart..@touchend`, or
|
|
408
|
+
. `@click..@click="play" @keydown.alt-space..@keydown.alt-space="play"`, so in other words we need cross-reaction `@click_or_altspace..@click_or_altspace`, not just one single chain.
|
|
409
|
+
+ actually here `@click@keydown.space..@click@keydown.space` is possible, unlike `:ona..onb` case, the question is how to provide sequence in attribute
|
|
410
|
+
+ the thing is that local state, introduced by initiator events, is not useful by itself, detached from scope.
|
|
411
|
+
. the state is better reflected in data scope, rather than by initiator event.
|
|
412
|
+
? what about temporaries like `@a..@b="id=setTimeout(),()=>clearTimeout(id)"` or `@a.toggle="stop=play(),stop"`
|
|
413
|
+
. use `:with={id:null} @a="id=setTimeout()" @b="clearTimeout(id)"`
|
|
414
|
+
- introduces illicit `event` variable ~ although compatible with standard, still obscure
|
|
415
|
+
- `@` prefix is unchangeable ~ can be removed, not set, but still on the verge.
|
|
416
|
+
- `@click.toggle="code"` has same problem as `@a..@b` - how can we make code separation in attribute?
|
|
417
|
+
+ remove toggle
|
|
418
|
+
+ overall less code
|
|
419
|
+
|
|
420
|
+
## [x] Multiple chain events resolution -> redirect to main event for now
|
|
421
|
+
* Consider
|
|
422
|
+
```
|
|
423
|
+
:onclick..onclick="play"
|
|
424
|
+
:onkeydown.document.alt-space..onkeydown.document.alt-space="play"
|
|
425
|
+
```
|
|
426
|
+
* When started by click and ended by alt-space, it doesn't clear the onclick
|
|
427
|
+
* We actually want here `:onclick:onkeydown.document.alt-space..onclick:onkeydown.document.alt-space`.
|
|
428
|
+
- this makes inconsistency of `..onclick` - colon is missing
|
|
429
|
+
- also it makes precedence of `:` and `..` unclear - what comes before what after.
|
|
430
|
+
|
|
431
|
+
? Can we use `:onclick.toggle="play"`?
|
|
432
|
+
- it doesn't help with switch-over
|
|
433
|
+
? Some 'or' character `:onclick--onkeydown`
|
|
434
|
+
? We can redirect to main event, that's it for now
|
|
378
435
|
|
|
379
436
|
## [x] Should getters convert to computed? -> yes, that's relatively cheap and useful
|
|
380
437
|
|
|
@@ -501,22 +558,6 @@
|
|
|
501
558
|
5. `:x="this.x=value"`
|
|
502
559
|
+ yepyepyep
|
|
503
560
|
|
|
504
|
-
## [x] Multiple chain events resolution -> redirect to main event for now
|
|
505
|
-
* Consider
|
|
506
|
-
```
|
|
507
|
-
:onclick..onclick="play"
|
|
508
|
-
:onkeydown.document.alt-space..onkeydown.document.alt-space="play"
|
|
509
|
-
```
|
|
510
|
-
* When started by click and ended by alt-space, it doesn't clear the onclick
|
|
511
|
-
* We actually want here `:onclick:onkeydown.document.alt-space..onclick:onkeydown.document.alt-space`.
|
|
512
|
-
- this makes inconsistency of `..onclick` - colon is missing
|
|
513
|
-
- also it makes precedence of `:` and `..` unclear - what comes before what after.
|
|
514
|
-
|
|
515
|
-
? Can we use `:onclick.toggle="play"`?
|
|
516
|
-
- it doesn't help with switch-over
|
|
517
|
-
? Some 'or' character `:onclick--onkeydown`
|
|
518
|
-
? We can redirect to main event, that's it for now
|
|
519
|
-
|
|
520
561
|
## [x] Insert content by reusing the node/template -> use `:render="ref" :with="data"`
|
|
521
562
|
|
|
522
563
|
* Makes easy use of repeatable fragments, instead of web-components
|
package/readme.md
CHANGED
|
@@ -2,32 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
> DOM microhydration with `:` attributes.
|
|
4
4
|
|
|
5
|
-
_Sprae_ is tiny progressive enhancement
|
|
5
|
+
_Sprae_ is tiny progressive enhancement framework, a minimal essential alternative to [alpine](https://github.com/alpinejs/alpine)/[lucia](https://github.com/aidenybai/lucia), [petite-vue](https://github.com/vuejs/petite-vue) or [template-parts](https://github.com/github/template-parts) with improved ergonomics[*](#justification--alternatives). It enables simple markup logic without external scripts. Perfect for small websites, prototypes or UI logic.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Use
|
|
8
|
+
|
|
9
|
+
### Autoinit
|
|
8
10
|
|
|
9
|
-
To autoinit
|
|
11
|
+
To autoinit document, include [`sprae.auto.js`](./sprae.auto.js):
|
|
10
12
|
|
|
11
13
|
```html
|
|
12
14
|
<!-- <script src="https://cdn.jsdelivr.net/npm/sprae/sprae.auto.js" defer></script> -->
|
|
13
15
|
<script src="./path/to/sprae.auto.js" defer></script>
|
|
14
16
|
|
|
15
|
-
<a :each="id in ['a','b','c']" :href="`#${id}`"></a>
|
|
17
|
+
<a :each="id in ['a','b','c']" :href="`#${id}`" :text="id"></a>
|
|
16
18
|
```
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
### Manual init
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
<script type="module">
|
|
22
|
-
// import sprae from 'https://cdn.jsdelivr.net/npm/sprae/sprae.js';
|
|
23
|
-
import sprae from './path/to/sprae.js';
|
|
24
|
-
sprae(el, {foo: 'bar'});
|
|
25
|
-
</script>
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Use
|
|
29
|
-
|
|
30
|
-
Sprae evaluates attributes starting with `:`:
|
|
22
|
+
To use as module, import [`sprae.js`](./sprae.js):
|
|
31
23
|
|
|
32
24
|
```html
|
|
33
25
|
<div id="container" :if="user">
|
|
@@ -35,15 +27,15 @@ Sprae evaluates attributes starting with `:`:
|
|
|
35
27
|
</div>
|
|
36
28
|
|
|
37
29
|
<script type="module">
|
|
38
|
-
import sprae from 'sprae';
|
|
30
|
+
// import sprae from 'https://cdn.jsdelivr.net/npm/sprae/sprae.js';
|
|
31
|
+
import sprae from './path/to/sprae.js';
|
|
39
32
|
|
|
40
33
|
const state = sprae(container, { user: { displayName: 'Dmitry Ivanov' } });
|
|
41
|
-
state.user.displayName = 'dy'; //
|
|
34
|
+
state.user.displayName = 'dy'; // updates DOM
|
|
42
35
|
</script>
|
|
43
36
|
```
|
|
44
37
|
|
|
45
|
-
|
|
46
|
-
* `state` object reflects current values, changing any props rerenders subtree next tick.
|
|
38
|
+
Sprae evaluates `:`-attributes and evaporates them. Reactive `state` reflects current values, can be updated directly.
|
|
47
39
|
|
|
48
40
|
|
|
49
41
|
## Attributes
|
|
@@ -68,7 +60,7 @@ Multiply element. `index` value starts from 1.
|
|
|
68
60
|
<!-- Cases -->
|
|
69
61
|
<li :each="item, idx in list" />
|
|
70
62
|
<li :each="val, key in obj" />
|
|
71
|
-
<li :each="
|
|
63
|
+
<li :each="idx0, idx in number" />
|
|
72
64
|
|
|
73
65
|
<!-- Loop by condition -->
|
|
74
66
|
<li :if="items" :each="item in items" :text="item" />
|
|
@@ -124,63 +116,6 @@ Set value of an input, textarea or select. Takes handle of `checked` and `select
|
|
|
124
116
|
</select>
|
|
125
117
|
```
|
|
126
118
|
|
|
127
|
-
#### `:<prop>="value?"`
|
|
128
|
-
|
|
129
|
-
Set any attribute value or run an effect.
|
|
130
|
-
|
|
131
|
-
```html
|
|
132
|
-
<!-- Single property -->
|
|
133
|
-
<label :for="name" :text="name" />
|
|
134
|
-
|
|
135
|
-
<!-- Multiple properties -->
|
|
136
|
-
<input :id:name="name" />
|
|
137
|
-
|
|
138
|
-
<!-- Effect (triggers any time bar changes) -->
|
|
139
|
-
<div :fx="void bar()" ></div>
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
#### `:="props?"`
|
|
143
|
-
|
|
144
|
-
Spread multiple attibures.
|
|
145
|
-
|
|
146
|
-
```html
|
|
147
|
-
<input :="{ id: name, name, type:'text', value }" />
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
#### `:on<event>="handler"`, `:on<in>..on<out>="handler"`
|
|
151
|
-
|
|
152
|
-
Add event listener or events chain.
|
|
153
|
-
|
|
154
|
-
```html
|
|
155
|
-
<!-- Single event -->
|
|
156
|
-
<input type="checkbox" :onchange="e => isChecked=e.target.value">
|
|
157
|
-
|
|
158
|
-
<!-- Multiple events -->
|
|
159
|
-
<input :value="text" :oninput:onchange="e => text=e.target.value">
|
|
160
|
-
|
|
161
|
-
<!-- Sequence of events -->
|
|
162
|
-
<button :onfocus..onblur="e => {
|
|
163
|
-
// onfocus
|
|
164
|
-
return e => {
|
|
165
|
-
// onblur
|
|
166
|
-
}
|
|
167
|
-
}">
|
|
168
|
-
|
|
169
|
-
<!-- Event modifiers -->
|
|
170
|
-
<button :onclick.throttle-500="handler">Not too often</button>
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
##### Event modifiers
|
|
174
|
-
|
|
175
|
-
* `.once`, `.passive`, `.capture` – listener [options](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options).
|
|
176
|
-
* `.prevent`, `.stop` – prevent default or stop propagation.
|
|
177
|
-
* `.window`, `.document`, `.outside`, `.self` – specify event target.
|
|
178
|
-
* `.throttle-<ms>`, `.debounce-<ms>` – defer function call with one of the methods.
|
|
179
|
-
* `.toggle` – run function and its result in turn.
|
|
180
|
-
* `.ctrl`, `.shift`, `.alt`, `.meta`, `.arrow`, `.enter`, `.escape`, `.tab`, `.space`, `.backspace`, `.delete`, `.digit`, `.letter`, `.character` – filter by [`event.key`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values).
|
|
181
|
-
* `.ctrl-<key>, .alt-<key>, .meta-<key>, .shift-<key>` – key combinations, eg. `.ctrl-alt-delete` or `.meta-x`.
|
|
182
|
-
* `.*` – any other modifier has no effect, but allows binding multiple handlers to same event (like jQuery event classes).
|
|
183
|
-
|
|
184
119
|
#### `:with="data"`
|
|
185
120
|
|
|
186
121
|
Define or extend data scope for a subtree.
|
|
@@ -205,8 +140,8 @@ Include template as element content.
|
|
|
205
140
|
```html
|
|
206
141
|
<template :ref="foo"><span :text="foo"></span></template>
|
|
207
142
|
|
|
208
|
-
<div :render="foo" :with="{foo:'bar'}"
|
|
209
|
-
<div :render="foo" :with="{foo:'baz'}"
|
|
143
|
+
<div :render="foo" :with="{foo:'bar'}">...inserted here...</div>
|
|
144
|
+
<div :render="foo" :with="{foo:'baz'}">...inserted here...</div>
|
|
210
145
|
```
|
|
211
146
|
|
|
212
147
|
#### `:ref="id"`
|
|
@@ -221,18 +156,73 @@ Expose element to current data scope with the `id`:
|
|
|
221
156
|
<!-- iterable items -->
|
|
222
157
|
<ul>
|
|
223
158
|
<li :each="item in items" :ref="item">
|
|
224
|
-
<input
|
|
159
|
+
<input @focus="item.classList.add('editing')" @blur="item.classList.remove('editing')"/>
|
|
225
160
|
</li>
|
|
226
161
|
</ul>
|
|
227
162
|
```
|
|
228
163
|
|
|
164
|
+
#### `:="props?"`
|
|
165
|
+
|
|
166
|
+
Spread multiple attibures.
|
|
167
|
+
|
|
168
|
+
```html
|
|
169
|
+
<input :="{ id: name, name, type:'text', value }" />
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
#### `:<prop>="value?"`
|
|
173
|
+
|
|
174
|
+
Set any attribute value or run an effect.
|
|
175
|
+
|
|
176
|
+
```html
|
|
177
|
+
<!-- Single property -->
|
|
178
|
+
<label :for="name" :text="name" />
|
|
179
|
+
|
|
180
|
+
<!-- Multiple properties -->
|
|
181
|
+
<input :id:name="name" />
|
|
182
|
+
|
|
183
|
+
<!-- Effect - returns undefined, triggers any time bar changes -->
|
|
184
|
+
<div :fx="void bar()" ></div>
|
|
185
|
+
|
|
186
|
+
<!-- Raw event listener (see events) -->
|
|
187
|
+
<div :onclick="e=>e.preventDefault()"></div>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
## Events
|
|
192
|
+
|
|
193
|
+
#### `@<event>="handler"`, `@<foo>@<bar>.<baz>="handler"`
|
|
194
|
+
|
|
195
|
+
Attach event listener or multiple listeners with possible modifiers, `event` variable holds current event.
|
|
196
|
+
|
|
197
|
+
```html
|
|
198
|
+
<!-- Single event -->
|
|
199
|
+
<input type="checkbox" @change="isChecked = event.target.value">
|
|
200
|
+
|
|
201
|
+
<!-- Multiple events -->
|
|
202
|
+
<input :value="text" @input@change="text = event.target.value">
|
|
203
|
+
|
|
204
|
+
<!-- Event modifiers -->
|
|
205
|
+
<button @click.throttle-500="handler(event)">Not too often</button>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
##### Event modifiers
|
|
209
|
+
|
|
210
|
+
* `.once`, `.passive`, `.capture` – listener [options](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options).
|
|
211
|
+
* `.prevent`, `.stop` – prevent default or stop propagation.
|
|
212
|
+
* `.window`, `.document`, `.outside`, `.self` – specify event target.
|
|
213
|
+
* `.throttle-<ms>`, `.debounce-<ms>` – defer function call with one of the methods.
|
|
214
|
+
* `.ctrl`, `.shift`, `.alt`, `.meta`, `.arrow`, `.enter`, `.escape`, `.tab`, `.space`, `.backspace`, `.delete`, `.digit`, `.letter`, `.character` – filter by [`event.key`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values).
|
|
215
|
+
* `.ctrl-<key>, .alt-<key>, .meta-<key>, .shift-<key>` – key combinations, eg. `.ctrl-alt-delete` or `.meta-x`.
|
|
216
|
+
* `.*` – any other modifier has no effect, but allows binding multiple handlers to same event (like jQuery event classes).
|
|
217
|
+
|
|
218
|
+
|
|
229
219
|
## Sandbox
|
|
230
220
|
|
|
231
221
|
Expressions are sandboxed, ie. have no access to global or window (since sprae can be run in server environment).
|
|
232
222
|
|
|
233
223
|
```html
|
|
234
|
-
<div :x="
|
|
235
|
-
<!--
|
|
224
|
+
<div :x="global.x"></div>
|
|
225
|
+
<!-- global is undefined -->
|
|
236
226
|
```
|
|
237
227
|
|
|
238
228
|
Default sandbox provides: _window_, _document_, _console_, _history_, _location_, _Array_, _Object_, _Number_, _String_, _Boolean_, _Date_, _Set_, _Map_.<br/>
|
|
@@ -253,13 +243,13 @@ To avoid _flash of unstyled content_, you can hide sprae attribute or add a cust
|
|
|
253
243
|
* Wavearea: [demo](https://dy.github.io/wavearea?src=//cdn.freesound.org/previews/586/586281_2332564-lq.mp3), [code](https://github.com/dy/wavearea)
|
|
254
244
|
* Prostogreen [demo](http://web-being.org/prostogreen/), [code](https://github.com/web-being/prostogreen/)
|
|
255
245
|
|
|
256
|
-
## Justification
|
|
246
|
+
## Justification & alternatives
|
|
257
247
|
|
|
258
|
-
* [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), [
|
|
259
|
-
* [Alpine](https://github.com/alpinejs/alpine) / [vue](https://github.com/vuejs/petite-vue) / [lit](https://github.com/lit/lit/tree/main/packages/lit-html)
|
|
248
|
+
* [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. Also it's just nice to keep `{{}}` generic, regardless of markup, and attributes as part of markup.
|
|
249
|
+
* [Alpine](https://github.com/alpinejs/alpine) / [vue](https://github.com/vuejs/petite-vue) / [lit](https://github.com/lit/lit/tree/main/packages/lit-html) / [lucia](https://github.com/aidenybai/lucia) escape native HTML quirks, but the syntax (`:attr`, `v-*`,`x-*`, `l-*` `@evt`, `{{}}`) and 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, invent own tooling or complex reactivity.
|
|
260
250
|
* 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).
|
|
261
251
|
|
|
262
|
-
_Sprae_ takes
|
|
252
|
+
_Sprae_ takes idea of _templize directives_/_alpine_/_vue_ attrs and builds upon <del>[_@preact/signals_](https://ghub.io/@preact/signals)</del> simple reactive state.
|
|
263
253
|
|
|
264
254
|
* It doesn't break or modify static html markup.
|
|
265
255
|
* It falls back to element content if uninitialized.
|
package/sprae.auto.js
CHANGED
|
@@ -223,12 +223,6 @@
|
|
|
223
223
|
}
|
|
224
224
|
};
|
|
225
225
|
};
|
|
226
|
-
primary["with"] = (el, expr, rootState) => {
|
|
227
|
-
let evaluate = parseExpr(el, expr, ":with");
|
|
228
|
-
const localState = evaluate(rootState);
|
|
229
|
-
let state2 = state(localState, rootState);
|
|
230
|
-
sprae(el, state2);
|
|
231
|
-
};
|
|
232
226
|
var _each = Symbol(":each");
|
|
233
227
|
primary["each"] = (tpl, expr) => {
|
|
234
228
|
let each = parseForExpression(expr);
|
|
@@ -249,7 +243,7 @@
|
|
|
249
243
|
if (!list)
|
|
250
244
|
list = [];
|
|
251
245
|
else if (typeof list === "number")
|
|
252
|
-
list = Array.from({ length: list }, (_, i) => [i
|
|
246
|
+
list = Array.from({ length: list }, (_, i) => [i + 1, i]);
|
|
253
247
|
else if (Array.isArray(list))
|
|
254
248
|
list = list.map((item, i) => [i + 1, item]);
|
|
255
249
|
else if (typeof list === "object")
|
|
@@ -279,6 +273,12 @@
|
|
|
279
273
|
}
|
|
280
274
|
};
|
|
281
275
|
};
|
|
276
|
+
primary["with"] = (el, expr, rootState) => {
|
|
277
|
+
let evaluate = parseExpr(el, expr, ":with");
|
|
278
|
+
const localState = evaluate(rootState);
|
|
279
|
+
let state2 = state(localState, rootState);
|
|
280
|
+
sprae(el, state2);
|
|
281
|
+
};
|
|
282
282
|
primary["ref"] = (el, expr, state2) => {
|
|
283
283
|
state2[expr] = el;
|
|
284
284
|
};
|
|
@@ -376,19 +376,6 @@
|
|
|
376
376
|
} : (value) => el.value = value;
|
|
377
377
|
return (state2) => update(evaluate(state2));
|
|
378
378
|
};
|
|
379
|
-
secondary["on"] = (el, expr) => {
|
|
380
|
-
let evaluate = parseExpr(el, expr, ":on");
|
|
381
|
-
return (state2) => {
|
|
382
|
-
let listeners = evaluate(state2);
|
|
383
|
-
let offs = [];
|
|
384
|
-
for (let evt in listeners)
|
|
385
|
-
offs.push(on(el, evt, listeners[evt]));
|
|
386
|
-
return () => {
|
|
387
|
-
for (let off of offs)
|
|
388
|
-
off();
|
|
389
|
-
};
|
|
390
|
-
};
|
|
391
|
-
};
|
|
392
379
|
var directives_default = (el, expr, state2, name) => {
|
|
393
380
|
let evt = name.startsWith("on") && name.slice(2);
|
|
394
381
|
let evaluate = parseExpr(el, expr, ":" + name);
|
|
@@ -402,43 +389,20 @@
|
|
|
402
389
|
};
|
|
403
390
|
return (state3) => attr(el, name, evaluate(state3));
|
|
404
391
|
};
|
|
405
|
-
var on = (
|
|
406
|
-
if (!
|
|
392
|
+
var on = (el, e, fn) => {
|
|
393
|
+
if (!fn)
|
|
407
394
|
return;
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
let off;
|
|
420
|
-
let curListener = (e) => {
|
|
421
|
-
if (cur)
|
|
422
|
-
off();
|
|
423
|
-
let nextFn = fn.call(target, e);
|
|
424
|
-
if (typeof nextFn !== "function")
|
|
425
|
-
nextFn = () => {
|
|
426
|
-
};
|
|
427
|
-
if (cur + 1 < ctxs.length)
|
|
428
|
-
onFn(nextFn, !cur ? 1 : cur + 1);
|
|
429
|
-
};
|
|
430
|
-
return off = addListenerWithMods(curListener, ctxs[cur]);
|
|
431
|
-
};
|
|
432
|
-
let rootOff = onFn(origFn);
|
|
433
|
-
return () => rootOff();
|
|
434
|
-
function addListenerWithMods(fn, { evt: evt2, target: target2, test, defer, stop, prevent, ...opts }) {
|
|
435
|
-
if (defer)
|
|
436
|
-
fn = defer(fn);
|
|
437
|
-
let cb = (e) => test(e) && (stop && e.stopPropagation(), prevent && e.preventDefault(), fn.call(target2, e));
|
|
438
|
-
target2.addEventListener(evt2, cb, opts);
|
|
439
|
-
return () => target2.removeEventListener(evt2, cb, opts);
|
|
440
|
-
}
|
|
441
|
-
;
|
|
395
|
+
const ctx = { evt: "", target: el, test: () => true };
|
|
396
|
+
ctx.evt = (e.startsWith("on") ? e.slice(2) : e).replace(
|
|
397
|
+
/\.(\w+)?-?([-\w]+)?/g,
|
|
398
|
+
(match, mod, param = "") => (ctx.test = mods[mod]?.(ctx, ...param.split("-")) || ctx.test, "")
|
|
399
|
+
);
|
|
400
|
+
const { evt, target, test, defer, stop, prevent, ...opts } = ctx;
|
|
401
|
+
if (defer)
|
|
402
|
+
fn = defer(fn);
|
|
403
|
+
const cb = (e2) => test(e2) && (stop && e2.stopPropagation(), prevent && e2.preventDefault(), fn.call(target, e2));
|
|
404
|
+
target.addEventListener(evt, cb, opts);
|
|
405
|
+
return () => target.removeEventListener(evt, cb, opts);
|
|
442
406
|
};
|
|
443
407
|
var mods = {
|
|
444
408
|
prevent(ctx) {
|
|
@@ -462,9 +426,6 @@
|
|
|
462
426
|
document(ctx) {
|
|
463
427
|
ctx.target = document;
|
|
464
428
|
},
|
|
465
|
-
toggle(ctx) {
|
|
466
|
-
ctx.defer = (fn, out) => (e) => out ? (out.call?.(ctx.target, e), out = null) : out = fn();
|
|
467
|
-
},
|
|
468
429
|
throttle(ctx, limit) {
|
|
469
430
|
ctx.defer = (fn) => throttle(fn, limit ? Number(limit) || 0 : 108);
|
|
470
431
|
},
|
|
@@ -606,18 +567,18 @@ ${directive}=${expression ? `"${expression}"
|
|
|
606
567
|
}
|
|
607
568
|
if (el.attributes) {
|
|
608
569
|
for (let i = 0; i < el.attributes.length; ) {
|
|
609
|
-
let attr2 = el.attributes[i];
|
|
610
|
-
if (
|
|
570
|
+
let attr2 = el.attributes[i], prefix = attr2.name[0];
|
|
571
|
+
if (prefix === ":" || prefix === "@") {
|
|
572
|
+
el.removeAttribute(attr2.name);
|
|
573
|
+
let expr = prefix === "@" ? `event=>{${attr2.value}}` : attr2.value, names = attr2.name.slice(1).split(prefix);
|
|
574
|
+
for (let name of names) {
|
|
575
|
+
if (prefix === "@")
|
|
576
|
+
name = `on` + name;
|
|
577
|
+
let dir = secondary[name] || directives_default;
|
|
578
|
+
updates.push(dir(el, expr, state2, name));
|
|
579
|
+
}
|
|
580
|
+
} else
|
|
611
581
|
i++;
|
|
612
|
-
continue;
|
|
613
|
-
}
|
|
614
|
-
el.removeAttribute(attr2.name);
|
|
615
|
-
let expr = attr2.value;
|
|
616
|
-
let attrNames = attr2.name.slice(1).split(":");
|
|
617
|
-
for (let attrName of attrNames) {
|
|
618
|
-
let dir = secondary[attrName] || directives_default;
|
|
619
|
-
updates.push(dir(el, expr, state2, attrName));
|
|
620
|
-
}
|
|
621
582
|
}
|
|
622
583
|
}
|
|
623
584
|
for (let i = 0, child; child = el.children[i]; i++) {
|
package/sprae.auto.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{var e,t,r=Promise.prototype.then.bind(Promise.resolve()),n=new WeakMap,
|
|
1
|
+
(()=>{var e,t,r=Promise.prototype.then.bind(Promise.resolve()),n=new WeakMap,s=class extends Map{#e=new FinalizationRegistry((e=>super.delete(e)));get size(){return[...this].length}constructor(e=[]){super();for(const[t,r]of e)this.set(t,r)}get(e){return super.get(e)?.deref()}set(e,t){let r=super.get(e);return r&&this.#e.unregister(r),r=(e=>n.get(e)||(e=>{const t=new WeakRef(e);return n.set(e,t),t})(e))(t),this.#e.register(t,e,r),super.set(e,r)}},l=new Set,i=new WeakMap,o=new WeakMap,a=new WeakMap,u=Symbol("parent"),c={Array:Array,Object:Object,Number:Number,String:String,Boolean:Boolean,Date:Date,console:console,window:window,document:document,history:history,location:location},f={has:()=>!0,get(t,r){if("symbol"==typeof r)return t[r];if(!(r in t))return t[u]?.[r];if(r in Object.prototype||Array.isArray(t)&&r in Array.prototype&&"length"!==r)return t[r];let n=t[r];if(e){let n=i.get(t);n||i.set(t,n={}),n[r]?n[r].includes(e)||n[r].push(e):n[r]=[e]}if(n&&n.constructor===Object||Array.isArray(n)){let e=o.get(n);return e||o.set(n,e=new Proxy(n,f)),e}return n},set(e,t,r){if(!(t in e)&&e[u]&&t in e[u])return e[u][t]=r;if(!Array.isArray(e)&&Object.is(e[t],r))return!0;e[t]=r;let n=i.get(e)?.[t];if(n)for(let e of n)l.add(e);return h(),!0},deleteProperty:(e,t)=>(e[t]=void 0,delete e[t],!0)},p=(e,t)=>{if(o.has(e))return o.get(e);if(a.has(e))return e;let r=new Proxy(e,f);o.set(e,r),a.set(r,e);let n=Object.getOwnPropertyDescriptors(e);for(let t in n){let s=n[t];s.get&&s.get&&(s.get=s.get.bind(r),Object.defineProperty(e,t,s))}return e[u]=t?p(t):c,r},y=t=>{const r=()=>{let n=e;e=r,t(),e=n};return r(),r},h=()=>{t||(t=!0,r((()=>{for(let e of l)e.call();l.clear(),t=!1})))},d={},g={};d.if=(e,t)=>{let r=document.createTextNode(""),n=[j(e,t,":if")],s=[e],l=e;for(;(l=e.nextElementSibling)&&l.hasAttribute(":else");)l.removeAttribute(":else"),(t=l.getAttribute(":if"))?(l.removeAttribute(":if"),l.remove(),s.push(l),n.push(j(e,t,":else :if"))):(l.remove(),s.push(l),n.push((()=>1)));return e.replaceWith(l=r),e=>{let t=n.findIndex((t=>t(e)));s[t]!=l&&((l[b]||l).replaceWith(l=s[t]||r),O(l,e))}};var b=Symbol(":each");d.each=(e,t)=>{let r=function(e){let t=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,r=e.match(/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/);if(!r)return;let n=r[2].trim(),s=r[1].replace(/^\s*\(|\)\s*$/g,"").trim(),l=s.match(t);return l?[s.replace(t,"").trim(),l[1].trim(),n]:[s,"",n]}(t);if(!r)return E(new Error,e,t,":each");const n=e[b]=document.createTextNode("");e.replaceWith(n);const l=j(e,r[2],":each"),i=e.getAttribute(":key"),o=i?j(null,i,":each"):null;e.removeAttribute(":key");const a=e.getAttribute(":ref"),u=new s,c=new s;let f=[];return s=>{let i=l(s);i?"number"==typeof i?i=Array.from({length:i},((e,t)=>[t+1,t])):Array.isArray(i)?i=i.map(((e,t)=>[t+1,e])):"object"==typeof i?i=Object.entries(i):E(Error("Bad list value"),e,t,":each"):i=[];let y=[],h=[];for(let[t,n]of i){let l,i,f=o?.({[r[0]]:n,[r[1]]:t});null==f?l=e.cloneNode(!0):(l=c.get(f))||c.set(f,l=e.cloneNode(!0)),y.push(l),null!=f&&(i=u.get(f))?i[r[0]]=n:(i=p({[r[0]]:n,[a||""]:null,[r[1]]:t},s),null!=f&&u.set(f,i)),h.push(i)}!function(e,t,r,n){const s=new Map,l=new Map;let i,o;for(i=0;i<t.length;i++)s.set(t[i],i);for(i=0;i<r.length;i++)l.set(r[i],i);for(i=o=0;i!==t.length||o!==r.length;){var a=t[i],u=r[o];if(null===a)i++;else if(r.length<=o)e.removeChild(t[i]),i++;else if(t.length<=i)e.insertBefore(u,t[i]||n),o++;else if(a===u)i++,o++;else{var c=l.get(a),f=s.get(u);void 0===c?(e.removeChild(t[i]),i++):void 0===f?(e.insertBefore(u,t[i]||n),o++):(e.insertBefore(t[f],t[i]||n),t[f]=null,f>i+1&&i++,o++)}}}(n.parentNode,f,y,n),f=y;for(let e=0;e<y.length;e++)O(y[e],h[e])}},d.with=(e,t,r)=>{const n=j(e,t,":with")(r);O(e,p(n,r))},d.ref=(e,t,r)=>{r[t]=e},g.render=(e,t,r)=>{let n=j(e,t,":render")(r);n||E(new Error("Template not found"),e,t,":render");let s=n.content.cloneNode(!0);e.replaceChildren(s),O(e,r)},g.id=(e,t)=>{let r=j(e,t,":id");return t=>{return n=r(t),e.id=n||0===n?n:"";var n}},g.class=(e,t)=>{let r=j(e,t,":class"),n=e.getAttribute("class");return t=>{let s=r(t),l=[n];s&&("string"==typeof s?l.push(s):Array.isArray(s)?l.push(...s):l.push(...Object.entries(s).map((([e,t])=>t?e:"")))),(l=l.filter(Boolean).join(" "))?e.setAttribute("class",l):e.removeAttribute("class")}},g.style=(e,t)=>{let r=j(e,t,":style"),n=e.getAttribute("style")||"";return n.endsWith(";")||(n+="; "),t=>{let s=r(t);if("string"==typeof s)e.setAttribute("style",n+s);else{e.setAttribute("style",n);for(let t in s)e.style.setProperty(t,s[t])}}},g.text=(e,t)=>{let r=j(e,t,":text");return t=>{let n=r(t);e.textContent=null==n?"":n}},g[""]=(e,t)=>{let r=j(e,t,":");if(r)return t=>{let n=r(t);for(let t in n)x(e,t.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(e=>"-"+e.toLowerCase())),n[t])}},g.value=(e,t)=>{let r,n,s=j(e,t,":value"),l="text"===e.type||""===e.type?t=>e.setAttribute("value",e.value=null==t?"":t):"TEXTAREA"===e.tagName||"text"===e.type||""===e.type?t=>(r=e.selectionStart,n=e.selectionEnd,e.setAttribute("value",e.value=null==t?"":t),r&&e.setSelectionRange(r,n)):"checkbox"===e.type?t=>(e.value=t?"on":"",x(e,"checked",t)):"select-one"===e.type?t=>{for(let t in e.options)t.removeAttribute("selected");e.value=t,e.selectedOptions[0]?.setAttribute("selected","")}:t=>e.value=t;return e=>l(s(e))};var m=(e,t,r,n)=>{let s=n.startsWith("on")&&n.slice(2),l=j(e,t,":"+n);if(l)return s?t=>{let r=l(t)||(()=>{});return v(e,s,r)}:t=>x(e,n,l(t))},v=(e,t,r)=>{if(!r)return;const n={evt:"",target:e,test:()=>!0};n.evt=(t.startsWith("on")?t.slice(2):t).replace(/\.(\w+)?-?([-\w]+)?/g,((e,t,r="")=>(n.test=A[t]?.(n,...r.split("-"))||n.test,"")));const{evt:s,target:l,test:i,defer:o,stop:a,prevent:u,...c}=n;o&&(r=o(r));const f=e=>i(e)&&(a&&e.stopPropagation(),u&&e.preventDefault(),r.call(l,e));return l.addEventListener(s,f,c),()=>l.removeEventListener(s,f,c)},A={prevent(e){e.prevent=!0},stop(e){e.stop=!0},once(e){e.once=!0},passive(e){e.passive=!0},capture(e){e.capture=!0},window(e){e.target=window},document(e){e.target=document},throttle(e,t){e.defer=e=>k(e,t?Number(t)||0:108)},debounce(e,t){e.defer=e=>W(e,t?Number(t)||0:108)},outside:e=>t=>{let r=e.target;return!(r.contains(t.target)||!1===t.target.isConnected||r.offsetWidth<1&&r.offsetHeight<1)},self:e=>t=>t.target===e.target,ctrl:(e,...t)=>e=>w.ctrl(e)&&t.every((t=>w[t]?w[t](e):e.key===t)),shift:(e,...t)=>e=>w.shift(e)&&t.every((t=>w[t]?w[t](e):e.key===t)),alt:(e,...t)=>e=>w.alt(e)&&t.every((t=>w[t]?w[t](e):e.key===t)),meta:(e,...t)=>e=>w.meta(e)&&t.every((t=>w[t]?w[t](e):e.key===t)),arrow:e=>w.arrow,enter:e=>w.enter,escape:e=>w.escape,tab:e=>w.tab,space:e=>w.space,backspace:e=>w.backspace,delete:e=>w.delete,digit:e=>w.digit,letter:e=>w.letter,character:e=>w.character},w={ctrl:e=>e.ctrlKey||"Control"===e.key||"Ctrl"===e.key,shift:e=>e.shiftKey||"Shift"===e.key,alt:e=>e.altKey||"Alt"===e.key,meta:e=>e.metaKey||"Meta"===e.key||"Command"===e.key,arrow:e=>e.key.startsWith("Arrow"),enter:e=>"Enter"===e.key,escape:e=>e.key.startsWith("Esc"),tab:e=>"Tab"===e.key,space:e=>" "===e.key||"Space"===e.key||" "===e.key,backspace:e=>"Backspace"===e.key,delete:e=>"Delete"===e.key,digit:e=>/^\d$/.test(e.key),letter:e=>/^[a-zA-Z]$/.test(e.key),character:e=>/^\S$/.test(e.key)},k=(e,t)=>{let r,n,s=l=>{r=!0,setTimeout((()=>{if(r=!1,n)return n=!1,s(l),e(l)}),t)};return t=>r?n=!0:(s(t),e(t))},W=(e,t)=>{let r;return n=>{clearTimeout(r),r=setTimeout((()=>{r=null,e(n)}),t)}},x=(e,t,r)=>{null==r||!1===r?e.removeAttribute(t):e.setAttribute(t,!0===r?"":"number"==typeof r||"string"==typeof r?r:"")},S={};function j(e,t,r){let n=S[t];if(!n){let s=/^[\n\s]*if.*\(.*\)/.test(t)||/\b(let|const)\s/.test(t)&&!r.startsWith(":on")?`(() => {${t}})()`:t;try{n=S[t]=new Function("__scope",`with (__scope) { return ${s.trim()} };`)}catch(n){return E(n,e,t,r)}}return s=>{let l;try{l=n.call(e,s)}catch(n){return E(n,e,t,r)}return l}}function E(e,t,n,s){Object.assign(e,{element:t,expression:n}),console.warn(`∴ ${e.message}\n\n${s}=${n?`"${n}"\n\n`:""}`,t),r((()=>{throw e}),0)}O.globals=c;var N=new WeakMap;function O(e,t){if(!e.children)return;if(N.has(e))return Object.assign(N.get(e),t);const r=p(t||{}),n=[],s=(e,t=e.parentNode)=>{for(let s in d){let l=":"+s;if(e.hasAttribute?.(l)){let i=e.getAttribute(l);if(e.removeAttribute(l),n.push(d[s](e,i,r,s)),N.has(e))return;if(e.parentNode!==t)return!1}}if(e.attributes)for(let t=0;t<e.attributes.length;){let s=e.attributes[t],l=s.name[0];if(":"===l||"@"===l){e.removeAttribute(s.name);let t="@"===l?`event=>{${s.value}}`:s.value,i=s.name.slice(1).split(l);for(let s of i){"@"===l&&(s="on"+s);let i=g[s]||m;n.push(i(e,t,r,s))}}else t++}for(let t,r=0;t=e.children[r];r++)!1===s(t,e)&&r--};s(e);for(let e of n)if(e){let t;y((()=>{"function"==typeof t&&t(),t=e(r)}))}return N.set(e,r),r}document.currentScript&&O(document.documentElement)})();
|