sprae 5.1.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/todomvc.html +8 -10
- package/package.json +1 -1
- package/r&d.md +14 -3
- package/readme.md +32 -44
- package/sprae.auto.js +28 -9
- package/sprae.auto.min.js +1 -1
- package/sprae.js +28 -9
- package/sprae.min.js +1 -1
- package/src/directives.js +18 -11
- package/src/state.js +11 -0
- package/test/dom.js +32 -13
- package/todo.md +4 -1
package/examples/todomvc.html
CHANGED
|
@@ -40,22 +40,20 @@
|
|
|
40
40
|
:onchange="e => (item.done = !item.done, save(todos))"
|
|
41
41
|
/>
|
|
42
42
|
<label :text="item.text"></label>
|
|
43
|
-
<button class="destroy" :onclick="e => save(todos = todos.filter(i => i !== item))"></button>
|
|
43
|
+
<button class="destroy" :onclick="e => (save(todos = todos.filter(i => i.text !== item.text)))"></button>
|
|
44
44
|
</div>
|
|
45
45
|
<input class=edit
|
|
46
46
|
:value="item.text"
|
|
47
|
-
:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
keypress(e) { e.key === 'Enter' ? e.target.blur() : null },
|
|
51
|
-
}"
|
|
47
|
+
:oninput="e => {item.text = e.target.value; save(todos)}"
|
|
48
|
+
:onblur="e => li.classList.remove('editing');"
|
|
49
|
+
:onkeypress="(e) => { e.key === 'Enter' ? e.target.blur() : null }"
|
|
52
50
|
/>
|
|
53
51
|
</li>
|
|
54
52
|
</ul>
|
|
55
53
|
</section>
|
|
56
54
|
<footer class="footer">
|
|
57
55
|
<span class="todo-count">
|
|
58
|
-
<strong :text="count">#</strong> <span :text="plur('item', count)">items</span> left
|
|
56
|
+
<strong :text="count()">#</strong> <span :text="plur('item', count())">items</span> left
|
|
59
57
|
</span>
|
|
60
58
|
<ul class="filters">
|
|
61
59
|
<li :each="label, key in {'#/': 'All', '#/active': 'Active', '#/completed': 'Completed'}">
|
|
@@ -77,19 +75,19 @@
|
|
|
77
75
|
<script src="https://unpkg.com/todomvc-common/base.js"></script>
|
|
78
76
|
<script type="importmap"> {
|
|
79
77
|
"imports": {
|
|
80
|
-
"sprae": "https://cdn.
|
|
78
|
+
"sprae": "https://cdn.jsdelivr.net/npm/sprae/sprae.min.js",
|
|
81
79
|
"plur": "https://cdn.skypack.dev/plur"
|
|
82
80
|
}
|
|
83
81
|
}
|
|
84
82
|
</script>
|
|
85
83
|
<script type="module">
|
|
86
|
-
import sprae from 'sprae'
|
|
84
|
+
import sprae from '../sprae.js'
|
|
87
85
|
import plur from 'plur'
|
|
88
86
|
|
|
89
87
|
let state = sprae(document.body, {
|
|
90
88
|
plur,
|
|
91
89
|
todos: JSON.parse(localStorage.getItem('todomvc.items') || '[]'),
|
|
92
|
-
|
|
90
|
+
count() { return this.todos.filter(item => !item.done).length },
|
|
93
91
|
hash: window.location.hash || '#/',
|
|
94
92
|
save: items => localStorage.setItem('todomvc.items', JSON.stringify(items))
|
|
95
93
|
})
|
package/package.json
CHANGED
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,18 @@
|
|
|
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
|
+
- `:onbeforeinput="handleBeforeInput"`
|
|
385
|
+
- `:ondragenter..ondragleave:ondragenter..ondrop="e=>(this.classList.add('w-dragover'),e=>this.classList.remove('w-dragover'))"`
|
|
386
|
+
- `:ondrop="e=>console.log(e.dataTransfer.types)||e.preventDefault()"`
|
|
387
|
+
- `:onfocus="e => (e.relatedTarget ? e.relatedTarget.focus() : e.target.blur())"`
|
|
388
|
+
- `:onpopstate.window="e => goto(e.state)"`
|
|
389
|
+
- `:onclick.toggle="play"`
|
|
378
390
|
|
|
379
391
|
## [x] Should getters convert to computed? -> yes, that's relatively cheap and useful
|
|
380
392
|
|
|
@@ -448,8 +460,6 @@
|
|
|
448
460
|
* onkey.after, onkey.microtask, onkey.defer, onkey.immediate
|
|
449
461
|
* onkey.tick-1?
|
|
450
462
|
|
|
451
|
-
### [ ] onkey.unfocusable
|
|
452
|
-
|
|
453
463
|
## [ ] Prop modifiers
|
|
454
464
|
|
|
455
465
|
- overall seems code complication without much benefit
|
|
@@ -475,6 +485,7 @@
|
|
|
475
485
|
* [ ] x.watch-a-b-c - update by change of any of the deps
|
|
476
486
|
* [ ] :x.always - update by _any_ dep change
|
|
477
487
|
* [ ] :class.active="active"
|
|
488
|
+
* [ ] :x.persist="v"
|
|
478
489
|
|
|
479
490
|
## [x] Writing props on elements (like ones in :each) -> nah, just use `:x="this.x=abc"`
|
|
480
491
|
|
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). 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>
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
To use as module, import [`sprae.js`](./sprae.js):
|
|
19
|
-
|
|
20
|
-
```html
|
|
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>
|
|
17
|
+
<a :each="id in ['a','b','c']" :href="`#${id}`" :text="id"></a>
|
|
26
18
|
```
|
|
27
19
|
|
|
28
|
-
|
|
20
|
+
### Manual init
|
|
29
21
|
|
|
30
|
-
|
|
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
|
|
@@ -63,14 +55,12 @@ Control flow of elements.
|
|
|
63
55
|
Multiply element. `index` value starts from 1.
|
|
64
56
|
|
|
65
57
|
```html
|
|
66
|
-
<ul>
|
|
67
|
-
<li :each="item in items" :text="item">Untitled</li>
|
|
68
|
-
</ul>
|
|
58
|
+
<ul><li :each="item in items" :text="item"></ul>
|
|
69
59
|
|
|
70
60
|
<!-- Cases -->
|
|
71
61
|
<li :each="item, idx in list" />
|
|
72
62
|
<li :each="val, key in obj" />
|
|
73
|
-
<li :each="
|
|
63
|
+
<li :each="idx0, idx in number" />
|
|
74
64
|
|
|
75
65
|
<!-- Loop by condition -->
|
|
76
66
|
<li :if="items" :each="item in items" :text="item" />
|
|
@@ -78,9 +68,6 @@ Multiply element. `index` value starts from 1.
|
|
|
78
68
|
|
|
79
69
|
<!-- Key items to reuse elements -->
|
|
80
70
|
<li :each="item in items" :key="item.id" :text="item.value" />
|
|
81
|
-
|
|
82
|
-
<!-- To avoid FOUC -->
|
|
83
|
-
<style>[:each]{visibility: hidden}</style>
|
|
84
71
|
```
|
|
85
72
|
|
|
86
73
|
#### `:text="value"`
|
|
@@ -131,7 +118,7 @@ Set value of an input, textarea or select. Takes handle of `checked` and `select
|
|
|
131
118
|
|
|
132
119
|
#### `:<prop>="value?"`
|
|
133
120
|
|
|
134
|
-
Set any attribute value or run effect.
|
|
121
|
+
Set any attribute value or run an effect.
|
|
135
122
|
|
|
136
123
|
```html
|
|
137
124
|
<!-- Single property -->
|
|
@@ -166,10 +153,8 @@ Add event listener or events chain.
|
|
|
166
153
|
<!-- Sequence of events -->
|
|
167
154
|
<button :onfocus..onblur="e => {
|
|
168
155
|
// onfocus
|
|
169
|
-
let id = setInterval(track,200)
|
|
170
156
|
return e => {
|
|
171
157
|
// onblur
|
|
172
|
-
clearInterval(id)
|
|
173
158
|
}
|
|
174
159
|
}">
|
|
175
160
|
|
|
@@ -223,6 +208,7 @@ Expose element to current data scope with the `id`:
|
|
|
223
208
|
```html
|
|
224
209
|
<!-- single item -->
|
|
225
210
|
<textarea :ref="text" placeholder="Enter text..."></textarea>
|
|
211
|
+
<span :text="text.value"></span>
|
|
226
212
|
|
|
227
213
|
<!-- iterable items -->
|
|
228
214
|
<ul>
|
|
@@ -230,14 +216,6 @@ Expose element to current data scope with the `id`:
|
|
|
230
216
|
<input :onfocus..onblur="e => (item.classList.add('editing'), e => item.classList.remove('editing'))"/>
|
|
231
217
|
</li>
|
|
232
218
|
</ul>
|
|
233
|
-
|
|
234
|
-
<script type="module">
|
|
235
|
-
import sprae from 'sprae';
|
|
236
|
-
let state = sprae(document, {items: ['a','b','c']})
|
|
237
|
-
|
|
238
|
-
// element is in the state
|
|
239
|
-
state.text // <textarea></textarea>
|
|
240
|
-
</script>
|
|
241
219
|
```
|
|
242
220
|
|
|
243
221
|
## Sandbox
|
|
@@ -252,27 +230,37 @@ Expressions are sandboxed, ie. have no access to global or window (since sprae c
|
|
|
252
230
|
Default sandbox provides: _window_, _document_, _console_, _history_, _location_, _Array_, _Object_, _Number_, _String_, _Boolean_, _Date_, _Set_, _Map_.<br/>
|
|
253
231
|
Sandbox can be extended as `Object.assign(sprae.globals, { BigInt })`.
|
|
254
232
|
|
|
233
|
+
## FOUC
|
|
234
|
+
|
|
235
|
+
To avoid _flash of unstyled content_, you can hide sprae attribute or add a custom effect, eg. `:hidden` - that will be removed once sprae is initialized:
|
|
236
|
+
|
|
237
|
+
```html
|
|
238
|
+
<div :hidden></div>
|
|
239
|
+
<style>[:each],[:hidden] {visibility: hidden}</style>
|
|
240
|
+
```
|
|
241
|
+
|
|
255
242
|
## Examples
|
|
256
243
|
|
|
257
244
|
* TODO MVC: [demo](https://dy.github.io/sprae/examples/todomvc), [code](https://github.com/dy/sprae/blob/main/examples/todomvc.html)
|
|
258
245
|
* Wavearea: [demo](https://dy.github.io/wavearea?src=//cdn.freesound.org/previews/586/586281_2332564-lq.mp3), [code](https://github.com/dy/wavearea)
|
|
246
|
+
* Prostogreen [demo](http://web-being.org/prostogreen/), [code](https://github.com/web-being/prostogreen/)
|
|
259
247
|
|
|
260
|
-
## Justification
|
|
248
|
+
## Justification & alternatives
|
|
261
249
|
|
|
262
|
-
* [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), [
|
|
263
|
-
* [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.
|
|
250
|
+
* [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.
|
|
251
|
+
* [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) escapes native HTML quirks, but the syntax is a bit scattered: `:attr`, `v-*`,`x-*`, `l-*` `@evt`, `{{}}` can be expressed with single convention. `{{}}` also conflicts with template-parts and liquid/django. 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.
|
|
264
252
|
* 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).
|
|
265
253
|
|
|
266
|
-
_Sprae_ takes
|
|
254
|
+
_Sprae_ takes idea of _templize directives_/_alpine_/_vue_ attrs and builds upon <del>[_@preact/signals_](https://ghub.io/@preact/signals)</del> simple reactive state.
|
|
267
255
|
|
|
268
|
-
* It doesn't break or modify
|
|
256
|
+
* It doesn't break or modify static html markup.
|
|
269
257
|
* It falls back to element content if uninitialized.
|
|
270
258
|
* It doesn't enforce SPA nor JSX.
|
|
271
259
|
* It enables island hydration.
|
|
272
260
|
* It reserves minimal syntax space as `:` convention (keeping tree neatly decorated, not scattered).
|
|
273
261
|
* Expressions are naturally reactive and incur minimal updates.
|
|
274
|
-
* Input data may contain [signals](https://ghub.io/@preact/signals) or [reactive values](https://ghub.io/sube).
|
|
275
262
|
* Elements / data API is open and enable easy interop.
|
|
263
|
+
* It's just nice separation: attributes belong to client and template fields to server.
|
|
276
264
|
|
|
277
265
|
It is reminiscent of [XSLT](https://www.w3schools.com/xml/xsl_intro.asp), considered a [buried treasure](https://github.com/bahrus/be-restated) by web-connoisseurs.
|
|
278
266
|
|
package/sprae.auto.js
CHANGED
|
@@ -108,6 +108,14 @@
|
|
|
108
108
|
let proxy = new Proxy(obj, handler);
|
|
109
109
|
targetProxy.set(obj, proxy);
|
|
110
110
|
proxyTarget.set(proxy, obj);
|
|
111
|
+
let descriptors = Object.getOwnPropertyDescriptors(obj);
|
|
112
|
+
for (let name in descriptors) {
|
|
113
|
+
let desc = descriptors[name];
|
|
114
|
+
if (desc.get) {
|
|
115
|
+
if (desc.get)
|
|
116
|
+
desc.get = desc.get.bind(proxy), Object.defineProperty(obj, name, desc);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
111
119
|
obj[_parent] = parent ? state(parent) : sandbox;
|
|
112
120
|
return proxy;
|
|
113
121
|
};
|
|
@@ -215,12 +223,6 @@
|
|
|
215
223
|
}
|
|
216
224
|
};
|
|
217
225
|
};
|
|
218
|
-
primary["with"] = (el, expr, rootState) => {
|
|
219
|
-
let evaluate = parseExpr(el, expr, ":with");
|
|
220
|
-
const localState = evaluate(rootState);
|
|
221
|
-
let state2 = state(localState, rootState);
|
|
222
|
-
sprae(el, state2);
|
|
223
|
-
};
|
|
224
226
|
var _each = Symbol(":each");
|
|
225
227
|
primary["each"] = (tpl, expr) => {
|
|
226
228
|
let each = parseForExpression(expr);
|
|
@@ -241,7 +243,7 @@
|
|
|
241
243
|
if (!list)
|
|
242
244
|
list = [];
|
|
243
245
|
else if (typeof list === "number")
|
|
244
|
-
list = Array.from({ length: list }, (_, i) => [i
|
|
246
|
+
list = Array.from({ length: list }, (_, i) => [i + 1, i]);
|
|
245
247
|
else if (Array.isArray(list))
|
|
246
248
|
list = list.map((item, i) => [i + 1, item]);
|
|
247
249
|
else if (typeof list === "object")
|
|
@@ -271,6 +273,12 @@
|
|
|
271
273
|
}
|
|
272
274
|
};
|
|
273
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
|
+
};
|
|
274
282
|
primary["ref"] = (el, expr, state2) => {
|
|
275
283
|
state2[expr] = el;
|
|
276
284
|
};
|
|
@@ -310,8 +318,19 @@
|
|
|
310
318
|
let initClassName = el.getAttribute("class");
|
|
311
319
|
return (state2) => {
|
|
312
320
|
let v = evaluate(state2);
|
|
313
|
-
let className =
|
|
314
|
-
|
|
321
|
+
let className = [initClassName];
|
|
322
|
+
if (v) {
|
|
323
|
+
if (typeof v === "string")
|
|
324
|
+
className.push(v);
|
|
325
|
+
else if (Array.isArray(v))
|
|
326
|
+
className.push(...v);
|
|
327
|
+
else
|
|
328
|
+
className.push(...Object.entries(v).map(([k, v2]) => v2 ? k : ""));
|
|
329
|
+
}
|
|
330
|
+
if (className = className.filter(Boolean).join(" "))
|
|
331
|
+
el.setAttribute("class", className);
|
|
332
|
+
else
|
|
333
|
+
el.removeAttribute("class");
|
|
315
334
|
};
|
|
316
335
|
};
|
|
317
336
|
secondary["style"] = (el, expr) => {
|
package/sprae.auto.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{var e,t,r=Promise.prototype.then.bind(Promise.resolve()),n=new WeakMap,l=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)}},s=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)s.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);
|
|
1
|
+
(()=>{var e,t,r=Promise.prototype.then.bind(Promise.resolve()),n=new WeakMap,l=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)}},s=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)s.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 l=n[t];l.get&&l.get&&(l.get=l.get.bind(r),Object.defineProperty(e,t,l))}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 s)e.call();s.clear(),t=!1})))},g={},d={};g.if=(e,t)=>{let r=document.createTextNode(""),n=[j(e,t,":if")],l=[e],s=e;for(;(s=e.nextElementSibling)&&s.hasAttribute(":else");)s.removeAttribute(":else"),(t=s.getAttribute(":if"))?(s.removeAttribute(":if"),s.remove(),l.push(s),n.push(j(e,t,":else :if"))):(s.remove(),l.push(s),n.push((()=>1)));return e.replaceWith(s=r),e=>{let t=n.findIndex((t=>t(e)));l[t]!=s&&((s[m]||s).replaceWith(s=l[t]||r),O(s,e))}};var m=Symbol(":each");g.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(),l=r[1].replace(/^\s*\(|\)\s*$/g,"").trim(),s=l.match(t);return s?[l.replace(t,"").trim(),s[1].trim(),n]:[l,"",n]}(t);if(!r)return E(new Error,e,t,":each");const n=e[m]=document.createTextNode("");e.replaceWith(n);const s=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 l,c=new l;let f=[];return l=>{let i=s(l);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 s,i,f=o?.({[r[0]]:n,[r[1]]:t});null==f?s=e.cloneNode(!0):(s=c.get(f))||c.set(f,s=e.cloneNode(!0)),y.push(s),null!=f&&(i=u.get(f))?i[r[0]]=n:(i=p({[r[0]]:n,[a||""]:null,[r[1]]:t},l),null!=f&&u.set(f,i)),h.push(i)}!function(e,t,r,n){const l=new Map,s=new Map;let i,o;for(i=0;i<t.length;i++)l.set(t[i],i);for(i=0;i<r.length;i++)s.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=s.get(a),f=l.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])}},g.with=(e,t,r)=>{const n=j(e,t,":with")(r);O(e,p(n,r))},g.ref=(e,t,r)=>{r[t]=e},d.render=(e,t,r)=>{let n=j(e,t,":render")(r);n||E(new Error("Template not found"),e,t,":render");let l=n.content.cloneNode(!0);e.replaceChildren(l),O(e,r)},d.id=(e,t)=>{let r=j(e,t,":id");return t=>{return n=r(t),e.id=n||0===n?n:"";var n}},d.class=(e,t)=>{let r=j(e,t,":class"),n=e.getAttribute("class");return t=>{let l=r(t),s=[n];l&&("string"==typeof l?s.push(l):Array.isArray(l)?s.push(...l):s.push(...Object.entries(l).map((([e,t])=>t?e:"")))),(s=s.filter(Boolean).join(" "))?e.setAttribute("class",s):e.removeAttribute("class")}},d.style=(e,t)=>{let r=j(e,t,":style"),n=e.getAttribute("style")||"";return n.endsWith(";")||(n+="; "),t=>{let l=r(t);if("string"==typeof l)e.setAttribute("style",n+l);else{e.setAttribute("style",n);for(let t in l)e.style.setProperty(t,l[t])}}},d.text=(e,t)=>{let r=j(e,t,":text");return t=>{let n=r(t);e.textContent=null==n?"":n}},d[""]=(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])}},d.value=(e,t)=>{let r,n,l=j(e,t,":value"),s="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=>s(l(e))},d.on=(e,t)=>{let r=j(e,t,":on");return t=>{let n=r(t),l=[];for(let t in n)l.push(v(e,t,n[t]));return()=>{for(let e of l)e()}}};var b=(e,t,r,n)=>{let l=n.startsWith("on")&&n.slice(2),s=j(e,t,":"+n);if(s)return l?t=>{let r=s(t)||(()=>{});return v(e,l,r)}:t=>x(e,n,s(t))},v=(e,t,r)=>{if(!r)return;let n=t.split("..").map((t=>{let r={evt:"",target:e,test:()=>!0};return r.evt=(t.startsWith("on")?t.slice(2):t).replace(/\.(\w+)?-?([-\w]+)?/g,((e,t,n="")=>(r.test=A[t]?.(r,...n.split("-"))||r.test,""))),r}));if(1==n.length)return i(r,n[0]);const l=(t,r=0)=>{let s;return s=i((i=>{r&&s();let o=t.call(e,i);"function"!=typeof o&&(o=()=>{}),r+1<n.length&&l(o,r?r+1:1)}),n[r])};let s=l(r);return()=>s();function i(e,{evt:t,target:r,test:n,defer:l,stop:s,prevent:i,...o}){l&&(e=l(e));let a=t=>n(t)&&(s&&t.stopPropagation(),i&&t.preventDefault(),e.call(r,t));return r.addEventListener(t,a,o),()=>r.removeEventListener(t,a,o)}},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},toggle(e){e.defer=(t,r)=>n=>r?(r.call?.(e.target,n),r=null):r=t()},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,l=s=>{r=!0,setTimeout((()=>{if(r=!1,n)return n=!1,l(s),e(s)}),t)};return t=>r?n=!0:(l(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 l=/^[\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 ${l.trim()} };`)}catch(n){return E(n,e,t,r)}}return l=>{let s;try{s=n.call(e,l)}catch(n){return E(n,e,t,r)}return s}}function E(e,t,n,l){Object.assign(e,{element:t,expression:n}),console.warn(`∴ ${e.message}\n\n${l}=${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=[],l=(e,t=e.parentNode)=>{for(let l in g){let s=":"+l;if(e.hasAttribute?.(s)){let i=e.getAttribute(s);if(e.removeAttribute(s),n.push(g[l](e,i,r,l)),N.has(e))return;if(e.parentNode!==t)return!1}}if(e.attributes)for(let t=0;t<e.attributes.length;){let l=e.attributes[t];if(":"!==l.name[0]){t++;continue}e.removeAttribute(l.name);let s=l.value,i=l.name.slice(1).split(":");for(let t of i){let l=d[t]||b;n.push(l(e,s,r,t))}}for(let t,r=0;t=e.children[r];r++)!1===l(t,e)&&r--};l(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)})();
|
package/sprae.js
CHANGED
|
@@ -107,6 +107,14 @@ var state = (obj, parent) => {
|
|
|
107
107
|
let proxy = new Proxy(obj, handler);
|
|
108
108
|
targetProxy.set(obj, proxy);
|
|
109
109
|
proxyTarget.set(proxy, obj);
|
|
110
|
+
let descriptors = Object.getOwnPropertyDescriptors(obj);
|
|
111
|
+
for (let name in descriptors) {
|
|
112
|
+
let desc = descriptors[name];
|
|
113
|
+
if (desc.get) {
|
|
114
|
+
if (desc.get)
|
|
115
|
+
desc.get = desc.get.bind(proxy), Object.defineProperty(obj, name, desc);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
110
118
|
obj[_parent] = parent ? state(parent) : sandbox;
|
|
111
119
|
return proxy;
|
|
112
120
|
};
|
|
@@ -214,12 +222,6 @@ primary["if"] = (el, expr) => {
|
|
|
214
222
|
}
|
|
215
223
|
};
|
|
216
224
|
};
|
|
217
|
-
primary["with"] = (el, expr, rootState) => {
|
|
218
|
-
let evaluate = parseExpr(el, expr, ":with");
|
|
219
|
-
const localState = evaluate(rootState);
|
|
220
|
-
let state2 = state(localState, rootState);
|
|
221
|
-
sprae(el, state2);
|
|
222
|
-
};
|
|
223
225
|
var _each = Symbol(":each");
|
|
224
226
|
primary["each"] = (tpl, expr) => {
|
|
225
227
|
let each = parseForExpression(expr);
|
|
@@ -240,7 +242,7 @@ primary["each"] = (tpl, expr) => {
|
|
|
240
242
|
if (!list)
|
|
241
243
|
list = [];
|
|
242
244
|
else if (typeof list === "number")
|
|
243
|
-
list = Array.from({ length: list }, (_, i) => [i
|
|
245
|
+
list = Array.from({ length: list }, (_, i) => [i + 1, i]);
|
|
244
246
|
else if (Array.isArray(list))
|
|
245
247
|
list = list.map((item, i) => [i + 1, item]);
|
|
246
248
|
else if (typeof list === "object")
|
|
@@ -270,6 +272,12 @@ primary["each"] = (tpl, expr) => {
|
|
|
270
272
|
}
|
|
271
273
|
};
|
|
272
274
|
};
|
|
275
|
+
primary["with"] = (el, expr, rootState) => {
|
|
276
|
+
let evaluate = parseExpr(el, expr, ":with");
|
|
277
|
+
const localState = evaluate(rootState);
|
|
278
|
+
let state2 = state(localState, rootState);
|
|
279
|
+
sprae(el, state2);
|
|
280
|
+
};
|
|
273
281
|
primary["ref"] = (el, expr, state2) => {
|
|
274
282
|
state2[expr] = el;
|
|
275
283
|
};
|
|
@@ -309,8 +317,19 @@ secondary["class"] = (el, expr) => {
|
|
|
309
317
|
let initClassName = el.getAttribute("class");
|
|
310
318
|
return (state2) => {
|
|
311
319
|
let v = evaluate(state2);
|
|
312
|
-
let className =
|
|
313
|
-
|
|
320
|
+
let className = [initClassName];
|
|
321
|
+
if (v) {
|
|
322
|
+
if (typeof v === "string")
|
|
323
|
+
className.push(v);
|
|
324
|
+
else if (Array.isArray(v))
|
|
325
|
+
className.push(...v);
|
|
326
|
+
else
|
|
327
|
+
className.push(...Object.entries(v).map(([k, v2]) => v2 ? k : ""));
|
|
328
|
+
}
|
|
329
|
+
if (className = className.filter(Boolean).join(" "))
|
|
330
|
+
el.setAttribute("class", className);
|
|
331
|
+
else
|
|
332
|
+
el.removeAttribute("class");
|
|
314
333
|
};
|
|
315
334
|
};
|
|
316
335
|
secondary["style"] = (el, expr) => {
|
package/sprae.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e,t,r=Promise.prototype.then.bind(Promise.resolve()),n=new WeakMap,l=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)}},s=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)s.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);
|
|
1
|
+
var e,t,r=Promise.prototype.then.bind(Promise.resolve()),n=new WeakMap,l=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)}},s=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)s.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 l=n[t];l.get&&l.get&&(l.get=l.get.bind(r),Object.defineProperty(e,t,l))}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 s)e.call();s.clear(),t=!1})))},g={},d={};g.if=(e,t)=>{let r=document.createTextNode(""),n=[j(e,t,":if")],l=[e],s=e;for(;(s=e.nextElementSibling)&&s.hasAttribute(":else");)s.removeAttribute(":else"),(t=s.getAttribute(":if"))?(s.removeAttribute(":if"),s.remove(),l.push(s),n.push(j(e,t,":else :if"))):(s.remove(),l.push(s),n.push((()=>1)));return e.replaceWith(s=r),e=>{let t=n.findIndex((t=>t(e)));l[t]!=s&&((s[m]||s).replaceWith(s=l[t]||r),O(s,e))}};var m=Symbol(":each");g.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(),l=r[1].replace(/^\s*\(|\)\s*$/g,"").trim(),s=l.match(t);return s?[l.replace(t,"").trim(),s[1].trim(),n]:[l,"",n]}(t);if(!r)return E(new Error,e,t,":each");const n=e[m]=document.createTextNode("");e.replaceWith(n);const s=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 l,c=new l;let f=[];return l=>{let i=s(l);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 s,i,f=o?.({[r[0]]:n,[r[1]]:t});null==f?s=e.cloneNode(!0):(s=c.get(f))||c.set(f,s=e.cloneNode(!0)),y.push(s),null!=f&&(i=u.get(f))?i[r[0]]=n:(i=p({[r[0]]:n,[a||""]:null,[r[1]]:t},l),null!=f&&u.set(f,i)),h.push(i)}!function(e,t,r,n){const l=new Map,s=new Map;let i,o;for(i=0;i<t.length;i++)l.set(t[i],i);for(i=0;i<r.length;i++)s.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=s.get(a),f=l.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])}},g.with=(e,t,r)=>{const n=j(e,t,":with")(r);O(e,p(n,r))},g.ref=(e,t,r)=>{r[t]=e},d.render=(e,t,r)=>{let n=j(e,t,":render")(r);n||E(new Error("Template not found"),e,t,":render");let l=n.content.cloneNode(!0);e.replaceChildren(l),O(e,r)},d.id=(e,t)=>{let r=j(e,t,":id");return t=>{return n=r(t),e.id=n||0===n?n:"";var n}},d.class=(e,t)=>{let r=j(e,t,":class"),n=e.getAttribute("class");return t=>{let l=r(t),s=[n];l&&("string"==typeof l?s.push(l):Array.isArray(l)?s.push(...l):s.push(...Object.entries(l).map((([e,t])=>t?e:"")))),(s=s.filter(Boolean).join(" "))?e.setAttribute("class",s):e.removeAttribute("class")}},d.style=(e,t)=>{let r=j(e,t,":style"),n=e.getAttribute("style")||"";return n.endsWith(";")||(n+="; "),t=>{let l=r(t);if("string"==typeof l)e.setAttribute("style",n+l);else{e.setAttribute("style",n);for(let t in l)e.style.setProperty(t,l[t])}}},d.text=(e,t)=>{let r=j(e,t,":text");return t=>{let n=r(t);e.textContent=null==n?"":n}},d[""]=(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])}},d.value=(e,t)=>{let r,n,l=j(e,t,":value"),s="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=>s(l(e))},d.on=(e,t)=>{let r=j(e,t,":on");return t=>{let n=r(t),l=[];for(let t in n)l.push(v(e,t,n[t]));return()=>{for(let e of l)e()}}};var b=(e,t,r,n)=>{let l=n.startsWith("on")&&n.slice(2),s=j(e,t,":"+n);if(s)return l?t=>{let r=s(t)||(()=>{});return v(e,l,r)}:t=>x(e,n,s(t))},v=(e,t,r)=>{if(!r)return;let n=t.split("..").map((t=>{let r={evt:"",target:e,test:()=>!0};return r.evt=(t.startsWith("on")?t.slice(2):t).replace(/\.(\w+)?-?([-\w]+)?/g,((e,t,n="")=>(r.test=A[t]?.(r,...n.split("-"))||r.test,""))),r}));if(1==n.length)return i(r,n[0]);const l=(t,r=0)=>{let s;return s=i((i=>{r&&s();let o=t.call(e,i);"function"!=typeof o&&(o=()=>{}),r+1<n.length&&l(o,r?r+1:1)}),n[r])};let s=l(r);return()=>s();function i(e,{evt:t,target:r,test:n,defer:l,stop:s,prevent:i,...o}){l&&(e=l(e));let a=t=>n(t)&&(s&&t.stopPropagation(),i&&t.preventDefault(),e.call(r,t));return r.addEventListener(t,a,o),()=>r.removeEventListener(t,a,o)}},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},toggle(e){e.defer=(t,r)=>n=>r?(r.call?.(e.target,n),r=null):r=t()},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,l=s=>{r=!0,setTimeout((()=>{if(r=!1,n)return n=!1,l(s),e(s)}),t)};return t=>r?n=!0:(l(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 l=/^[\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 ${l.trim()} };`)}catch(n){return E(n,e,t,r)}}return l=>{let s;try{s=n.call(e,l)}catch(n){return E(n,e,t,r)}return s}}function E(e,t,n,l){Object.assign(e,{element:t,expression:n}),console.warn(`∴ ${e.message}\n\n${l}=${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=[],l=(e,t=e.parentNode)=>{for(let l in g){let s=":"+l;if(e.hasAttribute?.(s)){let i=e.getAttribute(s);if(e.removeAttribute(s),n.push(g[l](e,i,r,l)),N.has(e))return;if(e.parentNode!==t)return!1}}if(e.attributes)for(let t=0;t<e.attributes.length;){let l=e.attributes[t];if(":"!==l.name[0]){t++;continue}e.removeAttribute(l.name);let s=l.value,i=l.name.slice(1).split(":");for(let t of i){let l=d[t]||b;n.push(l(e,s,r,t))}}for(let t,r=0;t=e.children[r];r++)!1===l(t,e)&&r--};l(e);for(let e of n)if(e){let t;y((()=>{"function"==typeof t&&t(),t=e(r)}))}return N.set(e,r),r}var $=O;document.currentScript&&O(document.documentElement);export{$ as default};
|
package/src/directives.js
CHANGED
|
@@ -45,14 +45,6 @@ primary['if'] = (el, expr) => {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
// :with must come before :each, but :if has primary importance
|
|
49
|
-
primary['with'] = (el, expr, rootState) => {
|
|
50
|
-
let evaluate = parseExpr(el, expr, ':with')
|
|
51
|
-
const localState = evaluate(rootState)
|
|
52
|
-
let state = createState(localState, rootState)
|
|
53
|
-
sprae(el, state);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
48
|
const _each = Symbol(':each')
|
|
57
49
|
|
|
58
50
|
// :each must init before :ref, :id or any others, since it defines scope
|
|
@@ -82,7 +74,7 @@ primary['each'] = (tpl, expr) => {
|
|
|
82
74
|
let list = evaluate(state)
|
|
83
75
|
|
|
84
76
|
if (!list) list = []
|
|
85
|
-
else if (typeof list === 'number') list = Array.from({length: list}, (_, i)=>[i, i
|
|
77
|
+
else if (typeof list === 'number') list = Array.from({length: list}, (_, i)=>[i+1, i])
|
|
86
78
|
else if (Array.isArray(list)) list = list.map((item,i) => [i+1, item])
|
|
87
79
|
else if (typeof list === 'object') list = Object.entries(list)
|
|
88
80
|
else exprError(Error('Bad list value'), tpl, expr, ':each', list)
|
|
@@ -120,6 +112,15 @@ primary['each'] = (tpl, expr) => {
|
|
|
120
112
|
}
|
|
121
113
|
}
|
|
122
114
|
|
|
115
|
+
// `:each` can redefine scope as `:each="a in {myScope}"`,
|
|
116
|
+
// same time per-item scope as `:each="..." :with="{collapsed:true}"` is useful
|
|
117
|
+
primary['with'] = (el, expr, rootState) => {
|
|
118
|
+
let evaluate = parseExpr(el, expr, ':with')
|
|
119
|
+
const localState = evaluate(rootState)
|
|
120
|
+
let state = createState(localState, rootState)
|
|
121
|
+
sprae(el, state);
|
|
122
|
+
}
|
|
123
|
+
|
|
123
124
|
// ref must be last within primaries, since that must be skipped by :each, but before secondaries
|
|
124
125
|
primary['ref'] = (el, expr, state) => {
|
|
125
126
|
// FIXME: wait for complex ref use-case
|
|
@@ -172,8 +173,14 @@ secondary['class'] = (el, expr) => {
|
|
|
172
173
|
let initClassName = el.getAttribute('class')
|
|
173
174
|
return (state) => {
|
|
174
175
|
let v = evaluate(state)
|
|
175
|
-
let className =
|
|
176
|
-
|
|
176
|
+
let className = [initClassName]
|
|
177
|
+
if (v) {
|
|
178
|
+
if (typeof v === 'string') className.push(v)
|
|
179
|
+
else if (Array.isArray(v)) className.push(...v)
|
|
180
|
+
else className.push(...Object.entries(v).map(([k,v])=>v?k:''))
|
|
181
|
+
}
|
|
182
|
+
if (className = className.filter(Boolean).join(' ')) el.setAttribute('class', className);
|
|
183
|
+
else el.removeAttribute('class')
|
|
177
184
|
}
|
|
178
185
|
}
|
|
179
186
|
|
package/src/state.js
CHANGED
|
@@ -50,6 +50,7 @@ const handler = {
|
|
|
50
50
|
},
|
|
51
51
|
|
|
52
52
|
set(target, prop, value) {
|
|
53
|
+
// "fake" prototype chain, since regular one doesn't fit
|
|
53
54
|
if (!(prop in target) && (target[_parent] && prop in target[_parent])) return target[_parent][prop] = value
|
|
54
55
|
|
|
55
56
|
// avoid bumping unchanged values
|
|
@@ -84,6 +85,16 @@ export const state = (obj, parent) => {
|
|
|
84
85
|
targetProxy.set(obj, proxy)
|
|
85
86
|
proxyTarget.set(proxy, obj)
|
|
86
87
|
|
|
88
|
+
// bind all getters here to proxy
|
|
89
|
+
// FIXME: alternatively we can store getters somewhere
|
|
90
|
+
let descriptors = Object.getOwnPropertyDescriptors(obj)
|
|
91
|
+
for (let name in descriptors) {
|
|
92
|
+
let desc = descriptors[name]
|
|
93
|
+
if (desc.get) {
|
|
94
|
+
if (desc.get) desc.get = desc.get.bind(proxy), Object.defineProperty(obj, name, desc)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
87
98
|
// inherit from parent state
|
|
88
99
|
obj[_parent] = parent ? state(parent) : sandbox
|
|
89
100
|
|
package/test/dom.js
CHANGED
|
@@ -109,6 +109,12 @@ test('class', async () => {
|
|
|
109
109
|
// is(el.outerHTML, `<x class="base x"></x><y class="y w"></y><z class="b c"></z>`);
|
|
110
110
|
})
|
|
111
111
|
|
|
112
|
+
test('class: undefined value', async () => {
|
|
113
|
+
let el = h`<x :class="a"></x><y :class="[b]"></y><z :class="{c}"></z>`
|
|
114
|
+
sprae(el, {a:undefined, b:undefined, c:undefined})
|
|
115
|
+
is(el.outerHTML, `<x></x><y></y><z></z>`)
|
|
116
|
+
})
|
|
117
|
+
|
|
112
118
|
test('class: old svg fun', async () => {
|
|
113
119
|
// raw html creates svganimatedstring
|
|
114
120
|
let el = document.createElement('div')
|
|
@@ -441,10 +447,10 @@ test('each: loop within condition', async () => {
|
|
|
441
447
|
|
|
442
448
|
const params = sprae(el, { a: 1 })
|
|
443
449
|
|
|
444
|
-
is(el.innerHTML, '<x><y>
|
|
450
|
+
is(el.innerHTML, '<x><y>0</y></x>')
|
|
445
451
|
params.a = 2
|
|
446
452
|
await tick()
|
|
447
|
-
is(el.innerHTML, '<x><y
|
|
453
|
+
is(el.innerHTML, '<x><y>0</y><y>-1</y></x>')
|
|
448
454
|
params.a = 0
|
|
449
455
|
await tick()
|
|
450
456
|
is(el.innerHTML, '')
|
|
@@ -475,7 +481,7 @@ test('each: next items have own "this", not single one', async () => {
|
|
|
475
481
|
let el = h`<div><x :each="x in 3" :data-x="x" :x="log.push(x, this.dataset.x)"></x></div>`
|
|
476
482
|
let log = []
|
|
477
483
|
let state = sprae(el, {log})
|
|
478
|
-
is(state.log, [1,'1',2,'2'
|
|
484
|
+
is(state.log, [0,'0',1,'1',2,'2'])
|
|
479
485
|
})
|
|
480
486
|
|
|
481
487
|
test('each: unkeyed', async () => {
|
|
@@ -514,7 +520,7 @@ test('each: keyed', async () => {
|
|
|
514
520
|
test('each: wrapped source', async () => {
|
|
515
521
|
let el = h`<div><x :each="i in (x || 2)" :text="i"></x></div>`
|
|
516
522
|
sprae(el, {x:0})
|
|
517
|
-
is(el.innerHTML, `<x>
|
|
523
|
+
is(el.innerHTML, `<x>0</x><x>1</x>`)
|
|
518
524
|
})
|
|
519
525
|
|
|
520
526
|
test('each: unmounted elements remove listeners', async () => {
|
|
@@ -558,6 +564,12 @@ test('each: key-based caching is in-sync with direct elements', () => {
|
|
|
558
564
|
is(el.outerHTML, el2.outerHTML)
|
|
559
565
|
})
|
|
560
566
|
|
|
567
|
+
test('each: with :with', () => {
|
|
568
|
+
let el = h`<ul><li :each="i in 3" :with="{x:i}" :text="x"></li></ul>`
|
|
569
|
+
sprae(el)
|
|
570
|
+
is(el.outerHTML, `<ul><li>0</li><li>1</li><li>2</li></ul>`)
|
|
571
|
+
})
|
|
572
|
+
|
|
561
573
|
test('on: base', async () => {
|
|
562
574
|
let el = h`<div :on="{click(e){log.push('click')}, x}"></div>`
|
|
563
575
|
let log = []
|
|
@@ -1015,18 +1027,25 @@ test(':: scope directives must come first', async () => {
|
|
|
1015
1027
|
is(a.outerHTML, `<x>1</x>`)
|
|
1016
1028
|
})
|
|
1017
1029
|
|
|
1018
|
-
test.
|
|
1019
|
-
let
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1030
|
+
test.todo('immediate scope', async () => {
|
|
1031
|
+
let el = h`<x :with="{arr:[], inc(){ arr.push(1) }}" :onx="e=>inc()" :text="arr[0]"></x>`
|
|
1032
|
+
sprae(el)
|
|
1033
|
+
is(el.outerHTML, `<x></x>`)
|
|
1034
|
+
el.dispatchEvent(new CustomEvent('x'))
|
|
1035
|
+
await tick()
|
|
1036
|
+
is(el.outerHTML, `<x>1</x>`)
|
|
1037
|
+
})
|
|
1038
|
+
|
|
1039
|
+
test('getters', async () => {
|
|
1040
|
+
let x = h`<h2 :text="doubledCount >= 1 ? 1 : 0"></h2>`
|
|
1025
1041
|
let state = sprae(x, {
|
|
1026
1042
|
count:0,
|
|
1027
|
-
get doubledCount(){
|
|
1028
|
-
increment(){ this.count++ }
|
|
1043
|
+
get doubledCount(){ return this.count * 2 }
|
|
1029
1044
|
})
|
|
1045
|
+
is(x.outerHTML, `<h2>0</h2>`)
|
|
1046
|
+
state.count++
|
|
1047
|
+
await tick()
|
|
1048
|
+
is(x.outerHTML, `<h2>1</h2>`)
|
|
1030
1049
|
})
|
|
1031
1050
|
|
|
1032
1051
|
test('sandbox', async () => {
|
package/todo.md
CHANGED
|
@@ -48,4 +48,7 @@
|
|
|
48
48
|
* [x] parallel chains
|
|
49
49
|
* [x] Sandbox
|
|
50
50
|
* [x] Autorun
|
|
51
|
-
* [x] There's some bug with prostogreen not triggering effect. (caused by special array.length case)
|
|
51
|
+
* [x] There's some bug with prostogreen not triggering effect. (caused by special array.length case)
|
|
52
|
+
* [x] Getters must become evaluable
|
|
53
|
+
* [ ] `:with="{likes:[], like(){ /* likes should not be undefined here */ }}"`
|
|
54
|
+
* [ ] `<li :each="item in items" :with="{collapsed:true}"><button :onclick='e=>collapsed=false'></li>`
|