sprae 3.1.0 → 4.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/package.json +5 -3
- package/r&d.md +6 -0
- package/readme.md +54 -81
- package/sprae.auto.js +617 -0
- package/sprae.auto.min.js +1 -0
- package/sprae.js +4 -18
- package/sprae.min.js +1 -1
- package/src/directives.js +5 -22
- package/src/index.js +1 -5
- package/test/dom.js +13 -13
- package/test/index.html +6 -1
- package/test/state.js +1 -0
- package/todo.md +7 -5
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sprae",
|
|
3
3
|
"description": "DOM microhydration.",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.0",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
7
7
|
"type": "module",
|
|
@@ -23,9 +23,11 @@
|
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"test": "node -r ./test/register.cjs test/test.js",
|
|
26
|
-
"build": "
|
|
26
|
+
"build": "npm run build-esm && npm run build-iife && npm run min",
|
|
27
|
+
"build-esm": "esbuild --bundle ./src/index.js --outfile=sprae.js --format=esm",
|
|
28
|
+
"build-iife": "esbuild --bundle ./src/index.js --outfile=sprae.auto.js --format=iife",
|
|
27
29
|
"watch": "esbuild --bundle ./src/index.js --outfile=sprae.js --format=esm --sourcemap --watch",
|
|
28
|
-
"min": "terser sprae.js -o sprae.min.js --module -c passes=3 -m"
|
|
30
|
+
"min": "terser sprae.js -o sprae.min.js --module -c passes=3 -m && terser sprae.auto.js -o sprae.auto.min.js --module -c passes=3 -m"
|
|
29
31
|
},
|
|
30
32
|
"repository": {
|
|
31
33
|
"type": "git",
|
package/r&d.md
CHANGED
|
@@ -474,6 +474,7 @@
|
|
|
474
474
|
* [ ] x.raf="abc" - run regularly?
|
|
475
475
|
* [ ] x.watch-a-b-c - update by change of any of the deps
|
|
476
476
|
* [ ] :x.always - update by _any_ dep change
|
|
477
|
+
* [ ] :class.active="active"
|
|
477
478
|
|
|
478
479
|
## [x] Writing props on elements (like ones in :each) -> nah, just use `:x="this.x=abc"`
|
|
479
480
|
|
|
@@ -526,3 +527,8 @@
|
|
|
526
527
|
|
|
527
528
|
1. :use="ref-id"
|
|
528
529
|
2. :
|
|
530
|
+
|
|
531
|
+
## [x] Remove non-essential directives -> yep, less API friction
|
|
532
|
+
* :aria - can be defined via plain attributes
|
|
533
|
+
* :data - confusable with :scope, doesn't provide much value, can be used as `:data-x=""` etc
|
|
534
|
+
* :={} - what's the meaning? Can be replaced with multiple attributes, no? No: no easy way to spread attributes.
|
package/readme.md
CHANGED
|
@@ -1,11 +1,35 @@
|
|
|
1
1
|
# ∴ spræ [](https://github.com/dy/sprae/actions/workflows/node.js.yml) [](https://bundlephobia.com/result?p=sprae) [](https://npmjs.org/sprae)
|
|
2
2
|
|
|
3
|
-
> DOM microhydration with `:` attributes
|
|
3
|
+
> DOM microhydration with `:` attributes.
|
|
4
4
|
|
|
5
|
+
_Sprae_ is tiny progressive enhancement lib, a minimal essential alternative to [alpine](https://github.com/alpinejs/alpine), [petite-vue](https://github.com/vuejs/petite-vue) or [template-parts](https://github.com/github/template-parts) with improved ergonomics. It enables simple markup logic without external scripts. Perfect for small websites or prototypes.
|
|
5
6
|
|
|
6
|
-
##
|
|
7
|
+
## Install
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
To autoinit on document, include [`sprae.auto.js`](./sprae.auto.js):
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<!-- <script src="https://cdn.jsdelivr.net/npm/sprae/sprae.auto.js" defer></script> -->
|
|
13
|
+
<script src="./path/to/sprae.auto.js" defer></script>
|
|
14
|
+
|
|
15
|
+
<div :scope="{foo:'bar'}">
|
|
16
|
+
<span :text="foo"></span>
|
|
17
|
+
</div>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
To use as module, import [`sprae.js`](./sprae.js):
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<script type="module">
|
|
24
|
+
// import sprae from 'https://cdn.jsdelivr.net/npm/sprae/sprae.js';
|
|
25
|
+
import sprae from './path/to/sprae.js';
|
|
26
|
+
sprae(el, {foo: 'bar'});
|
|
27
|
+
</script>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Use
|
|
31
|
+
|
|
32
|
+
Sprae evaluates attributes starting with `:`:
|
|
9
33
|
|
|
10
34
|
```html
|
|
11
35
|
<div id="container" :if="user">
|
|
@@ -20,30 +44,28 @@ Sprae defines attributes starting with `:` as directives:
|
|
|
20
44
|
</script>
|
|
21
45
|
```
|
|
22
46
|
|
|
23
|
-
* `sprae` initializes subtree with data and immediately evaporates `:` attrs.
|
|
24
|
-
* `state`
|
|
25
|
-
* To batch-update multiple properties `sprae` can be run repeatedly as: `sprae(container, newValues)`
|
|
47
|
+
* `sprae` initializes container's subtree with data and immediately evaporates `:` attrs.
|
|
48
|
+
* `state` object reflects current values, changing any props rerenders subtree next tick.
|
|
26
49
|
|
|
27
|
-
<!--
|
|
28
|
-
<details>
|
|
29
|
-
<summary><strong>Autoinit</strong></summary>
|
|
30
50
|
|
|
31
|
-
|
|
51
|
+
## Attributes
|
|
32
52
|
|
|
33
|
-
|
|
34
|
-
<script src="./sprae.js" defer init="{ count: 0 }"></script>
|
|
53
|
+
#### `:scope="data"`
|
|
35
54
|
|
|
36
|
-
|
|
37
|
-
<button :on="{ click: e => count++ }">inc</button>
|
|
38
|
-
```
|
|
55
|
+
Define or extend data scope for a subtree.
|
|
39
56
|
|
|
40
|
-
|
|
41
|
-
|
|
57
|
+
```html
|
|
58
|
+
<!-- Inline data -->
|
|
59
|
+
<x :scope="{ foo: 'bar' }" :text="foo"></x>
|
|
42
60
|
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
<!-- External data -->
|
|
62
|
+
<y :scope="data"></y>
|
|
45
63
|
|
|
46
|
-
|
|
64
|
+
<!-- Extend scope -->
|
|
65
|
+
<x :scope="{ foo: 'bar' }">
|
|
66
|
+
<y :scope="{ baz: 'qux' }" :text="foo + baz"></y>
|
|
67
|
+
</x>
|
|
68
|
+
```
|
|
47
69
|
|
|
48
70
|
#### `:if="condition"`, `:else`
|
|
49
71
|
|
|
@@ -90,17 +112,17 @@ Welcome, <span :text="user.name">Guest</span>.
|
|
|
90
112
|
|
|
91
113
|
#### `:class="value"`
|
|
92
114
|
|
|
93
|
-
Set class value from either string, array or object.
|
|
115
|
+
Set class value from either string, array or object. Extends existing `class` attribute, if any.
|
|
94
116
|
|
|
95
117
|
```html
|
|
96
118
|
<div :class="`foo ${bar}`"></div>
|
|
97
|
-
<div :class="['foo', 'bar']"></div>
|
|
98
|
-
<div :class="{foo: true, bar: false}"></div>
|
|
99
119
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
<div class="
|
|
103
|
-
|
|
120
|
+
<!-- extend existing class -->
|
|
121
|
+
<div class="foo" :class="`bar`"></div>
|
|
122
|
+
<!-- <div class="foo bar"></div> -->
|
|
123
|
+
|
|
124
|
+
<!-- object with values -->
|
|
125
|
+
<div :class="{foo:true, bar: false}"></div>
|
|
104
126
|
```
|
|
105
127
|
|
|
106
128
|
#### `:style="value"`
|
|
@@ -183,54 +205,10 @@ Add event listeners.
|
|
|
183
205
|
* `.ctrl-<key>, .alt-<key>, .meta-<key>, .shift-<key>` – key combinations, eg. `.ctrl-alt-delete` or `.meta-x`.
|
|
184
206
|
* `.*` – any other modifier has no effect, but allows binding multiple handlers to same event (like jQuery event classes).
|
|
185
207
|
|
|
186
|
-
#### `:data="values"`
|
|
187
|
-
|
|
188
|
-
Set [data-*](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*) attributes. CamelCase is converted to dash-case.
|
|
189
|
-
|
|
190
|
-
```html
|
|
191
|
-
<input :data="{foo: 1, barBaz: true}" />
|
|
192
|
-
<!--
|
|
193
|
-
<input data-foo="1" data-bar-baz="true" />
|
|
194
|
-
-->
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
#### `:aria="values"`
|
|
198
|
-
|
|
199
|
-
Set [aria-role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) attributes. Boolean values are stringified.
|
|
200
|
-
|
|
201
|
-
```html
|
|
202
|
-
<input role="combobox" :aria="{
|
|
203
|
-
controls: 'joketypes',
|
|
204
|
-
autocomplete: 'list',
|
|
205
|
-
expanded: false,
|
|
206
|
-
activeOption: 'item1',
|
|
207
|
-
activedescendant: ''
|
|
208
|
-
}" />
|
|
209
|
-
<!--
|
|
210
|
-
<input role="combobox" aria-controls="joketypes" aria-autocomplete="list" aria-expanded="false" aria-active-option="item1" aria-activedescendant="">
|
|
211
|
-
-->
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
#### `:with="data"`
|
|
215
|
-
|
|
216
|
-
Define variables for a subtree fragment scope.
|
|
217
|
-
|
|
218
|
-
```html
|
|
219
|
-
<!-- Inline data -->
|
|
220
|
-
<x :with="{ foo: 'bar' }" :text="foo"></x>
|
|
221
|
-
|
|
222
|
-
<!-- External data -->
|
|
223
|
-
<y :with="data"></y>
|
|
224
|
-
|
|
225
|
-
<!-- Transparency -->
|
|
226
|
-
<x :with="{ foo: 'bar' }">
|
|
227
|
-
<y :with="{ baz: 'qux' }" :text="foo + baz"></y>
|
|
228
|
-
</x>
|
|
229
|
-
```
|
|
230
208
|
|
|
231
209
|
#### `:ref="id"`
|
|
232
210
|
|
|
233
|
-
Expose element to data scope with the `id`:
|
|
211
|
+
Expose element to current data scope with the `id`:
|
|
234
212
|
|
|
235
213
|
```html
|
|
236
214
|
<!-- single item -->
|
|
@@ -255,27 +233,23 @@ Expose element to data scope with the `id`:
|
|
|
255
233
|
## Sandbox
|
|
256
234
|
|
|
257
235
|
Expressions are sandboxed, ie. have no access to global or window (since sprae can be run in server environment).
|
|
258
|
-
|
|
259
236
|
Default sandbox provides: _Array_, _Object_, _Number_, _String_, _Boolean_, _Date_, _console_.
|
|
260
|
-
|
|
261
|
-
Sandbox can be extended via `Object.assign(sprae.globals, { BigInt, window, document })` etc.
|
|
237
|
+
Sandbox can be extended as `Object.assign(sprae.globals, { BigInt, window, document })` etc.
|
|
262
238
|
|
|
263
239
|
## Examples
|
|
264
240
|
|
|
265
241
|
* TODO MVC: [demo](https://dy.github.io/sprae/examples/todomvc), [code](https://github.com/dy/sprae/blob/main/examples/todomvc.html)
|
|
266
|
-
*
|
|
242
|
+
* Wavearea: [demo](https://dy.github.io/wavearea?src=//cdn.freesound.org/previews/586/586281_2332564-lq.mp3), [code](https://github.com/dy/wavearea)
|
|
267
243
|
|
|
268
244
|
## Justification
|
|
269
245
|
|
|
270
|
-
_Sprae_ is lightweight essential alternative to [alpine](https://github.com/alpinejs/alpine), [petite-vue](https://github.com/vuejs/petite-vue), [templize](https://github.com/dy/templize) or JSX with better ergonomics.
|
|
271
|
-
|
|
272
246
|
* [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.
|
|
273
247
|
* [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.
|
|
274
248
|
* 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).
|
|
275
249
|
|
|
276
|
-
_Sprae_ takes convention of _templize directives_ (_alpine_/_vue_ attrs) and builds upon [_@preact/signals_](https://ghub.io/@preact/signals).
|
|
250
|
+
_Sprae_ takes convention of _templize directives_ (_alpine_/_vue_ attrs) and builds upon <del>[_@preact/signals_](https://ghub.io/@preact/signals)</del> simple reacti.
|
|
277
251
|
|
|
278
|
-
* It doesn't break static html markup.
|
|
252
|
+
* It doesn't break or modify initial static html markup.
|
|
279
253
|
* It falls back to element content if uninitialized.
|
|
280
254
|
* It doesn't enforce SPA nor JSX.
|
|
281
255
|
* It enables island hydration.
|
|
@@ -284,7 +258,6 @@ _Sprae_ takes convention of _templize directives_ (_alpine_/_vue_ attrs) and bui
|
|
|
284
258
|
* Input data may contain [signals](https://ghub.io/@preact/signals) or [reactive values](https://ghub.io/sube).
|
|
285
259
|
* Elements / data API is open and enable easy interop.
|
|
286
260
|
|
|
287
|
-
|
|
288
261
|
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.
|
|
289
262
|
|
|
290
263
|
|
package/sprae.auto.js
ADDED
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
// src/state.js
|
|
3
|
+
var currentFx;
|
|
4
|
+
var batch = /* @__PURE__ */ new Set();
|
|
5
|
+
var pendingUpdate;
|
|
6
|
+
var targetFxs = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
var targetProxy = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
var proxyTarget = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
var _parent = Symbol("parent");
|
|
10
|
+
var sandbox = {
|
|
11
|
+
Array,
|
|
12
|
+
Object,
|
|
13
|
+
Number,
|
|
14
|
+
String,
|
|
15
|
+
Boolean,
|
|
16
|
+
Date,
|
|
17
|
+
console
|
|
18
|
+
};
|
|
19
|
+
var handler = {
|
|
20
|
+
has() {
|
|
21
|
+
return true;
|
|
22
|
+
},
|
|
23
|
+
get(target, prop) {
|
|
24
|
+
if (typeof prop === "symbol")
|
|
25
|
+
return target[prop];
|
|
26
|
+
if (!(prop in target))
|
|
27
|
+
return target[_parent]?.[prop];
|
|
28
|
+
if (Array.isArray(target) && prop in Array.prototype)
|
|
29
|
+
return target[prop];
|
|
30
|
+
let value = target[prop];
|
|
31
|
+
if (currentFx) {
|
|
32
|
+
let propFxs = targetFxs.get(target);
|
|
33
|
+
if (!propFxs)
|
|
34
|
+
targetFxs.set(target, propFxs = {});
|
|
35
|
+
if (!propFxs[prop])
|
|
36
|
+
propFxs[prop] = [currentFx];
|
|
37
|
+
else if (!propFxs[prop].includes(currentFx))
|
|
38
|
+
propFxs[prop].push(currentFx);
|
|
39
|
+
}
|
|
40
|
+
if (value && value.constructor === Object || Array.isArray(value)) {
|
|
41
|
+
let proxy = targetProxy.get(value);
|
|
42
|
+
if (!proxy)
|
|
43
|
+
targetProxy.set(value, proxy = new Proxy(value, handler));
|
|
44
|
+
return proxy;
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
},
|
|
48
|
+
set(target, prop, value) {
|
|
49
|
+
if (!(prop in target) && (target[_parent] && prop in target[_parent]))
|
|
50
|
+
return target[_parent][prop] = value;
|
|
51
|
+
if (Array.isArray(target) && prop in Array.prototype)
|
|
52
|
+
return target[prop] = value;
|
|
53
|
+
const prev = target[prop];
|
|
54
|
+
if (Object.is(prev, value))
|
|
55
|
+
return true;
|
|
56
|
+
target[prop] = value;
|
|
57
|
+
let propFxs = targetFxs.get(target)?.[prop];
|
|
58
|
+
if (propFxs)
|
|
59
|
+
for (let fx2 of propFxs)
|
|
60
|
+
batch.add(fx2);
|
|
61
|
+
planUpdate();
|
|
62
|
+
return true;
|
|
63
|
+
},
|
|
64
|
+
deleteProperty(target, prop) {
|
|
65
|
+
target[prop] = void 0;
|
|
66
|
+
delete target[prop];
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var state = (obj, parent) => {
|
|
71
|
+
if (targetProxy.has(obj))
|
|
72
|
+
return targetProxy.get(obj);
|
|
73
|
+
if (proxyTarget.has(obj))
|
|
74
|
+
return obj;
|
|
75
|
+
let proxy = new Proxy(obj, handler);
|
|
76
|
+
targetProxy.set(obj, proxy);
|
|
77
|
+
proxyTarget.set(proxy, obj);
|
|
78
|
+
obj[_parent] = parent ? state(parent) : sandbox;
|
|
79
|
+
return proxy;
|
|
80
|
+
};
|
|
81
|
+
var fx = (fn) => {
|
|
82
|
+
const call = () => {
|
|
83
|
+
let prev = currentFx;
|
|
84
|
+
currentFx = call;
|
|
85
|
+
fn();
|
|
86
|
+
currentFx = prev;
|
|
87
|
+
};
|
|
88
|
+
call();
|
|
89
|
+
return call;
|
|
90
|
+
};
|
|
91
|
+
var planUpdate = () => {
|
|
92
|
+
if (!pendingUpdate) {
|
|
93
|
+
pendingUpdate = true;
|
|
94
|
+
queueMicrotask(() => {
|
|
95
|
+
for (let fx2 of batch)
|
|
96
|
+
fx2.call();
|
|
97
|
+
batch.clear();
|
|
98
|
+
pendingUpdate = false;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/domdiff.js
|
|
104
|
+
function domdiff_default(parent, a, b, before) {
|
|
105
|
+
const aIdx = /* @__PURE__ */ new Map();
|
|
106
|
+
const bIdx = /* @__PURE__ */ new Map();
|
|
107
|
+
let i;
|
|
108
|
+
let j;
|
|
109
|
+
for (i = 0; i < a.length; i++) {
|
|
110
|
+
aIdx.set(a[i], i);
|
|
111
|
+
}
|
|
112
|
+
for (i = 0; i < b.length; i++) {
|
|
113
|
+
bIdx.set(b[i], i);
|
|
114
|
+
}
|
|
115
|
+
for (i = j = 0; i !== a.length || j !== b.length; ) {
|
|
116
|
+
var aElm = a[i], bElm = b[j];
|
|
117
|
+
if (aElm === null) {
|
|
118
|
+
i++;
|
|
119
|
+
} else if (b.length <= j) {
|
|
120
|
+
parent.removeChild(a[i]);
|
|
121
|
+
i++;
|
|
122
|
+
} else if (a.length <= i) {
|
|
123
|
+
parent.insertBefore(bElm, a[i] || before);
|
|
124
|
+
j++;
|
|
125
|
+
} else if (aElm === bElm) {
|
|
126
|
+
i++;
|
|
127
|
+
j++;
|
|
128
|
+
} else {
|
|
129
|
+
var curElmInNew = bIdx.get(aElm);
|
|
130
|
+
var wantedElmInOld = aIdx.get(bElm);
|
|
131
|
+
if (curElmInNew === void 0) {
|
|
132
|
+
parent.removeChild(a[i]);
|
|
133
|
+
i++;
|
|
134
|
+
} else if (wantedElmInOld === void 0) {
|
|
135
|
+
parent.insertBefore(
|
|
136
|
+
bElm,
|
|
137
|
+
a[i] || before
|
|
138
|
+
);
|
|
139
|
+
j++;
|
|
140
|
+
} else {
|
|
141
|
+
parent.insertBefore(
|
|
142
|
+
a[wantedElmInOld],
|
|
143
|
+
a[i] || before
|
|
144
|
+
);
|
|
145
|
+
a[wantedElmInOld] = null;
|
|
146
|
+
if (wantedElmInOld > i + 1)
|
|
147
|
+
i++;
|
|
148
|
+
j++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return b;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/weakish-map.js
|
|
156
|
+
var refs = /* @__PURE__ */ new WeakMap();
|
|
157
|
+
var set = (value) => {
|
|
158
|
+
const ref = new WeakRef(value);
|
|
159
|
+
refs.set(value, ref);
|
|
160
|
+
return ref;
|
|
161
|
+
};
|
|
162
|
+
var get = (value) => refs.get(value) || set(value);
|
|
163
|
+
var WeakishMap = class extends Map {
|
|
164
|
+
#registry = new FinalizationRegistry((key) => super.delete(key));
|
|
165
|
+
get size() {
|
|
166
|
+
return [...this].length;
|
|
167
|
+
}
|
|
168
|
+
constructor(entries = []) {
|
|
169
|
+
super();
|
|
170
|
+
for (const [key, value] of entries)
|
|
171
|
+
this.set(key, value);
|
|
172
|
+
}
|
|
173
|
+
get(key) {
|
|
174
|
+
return super.get(key)?.deref();
|
|
175
|
+
}
|
|
176
|
+
set(key, value) {
|
|
177
|
+
let ref = super.get(key);
|
|
178
|
+
if (ref)
|
|
179
|
+
this.#registry.unregister(ref);
|
|
180
|
+
ref = get(value);
|
|
181
|
+
this.#registry.register(value, key, ref);
|
|
182
|
+
return super.set(key, ref);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// src/directives.js
|
|
187
|
+
var primary = {};
|
|
188
|
+
var secondary = {};
|
|
189
|
+
primary["if"] = (el, expr) => {
|
|
190
|
+
let holder = document.createTextNode(""), clauses = [parseExpr(el, expr, ":if")], els = [el], cur = el;
|
|
191
|
+
while (cur = el.nextElementSibling) {
|
|
192
|
+
if (cur.hasAttribute(":else")) {
|
|
193
|
+
cur.removeAttribute(":else");
|
|
194
|
+
if (expr = cur.getAttribute(":if")) {
|
|
195
|
+
cur.removeAttribute(":if"), cur.remove();
|
|
196
|
+
els.push(cur);
|
|
197
|
+
clauses.push(parseExpr(el, expr, ":else :if"));
|
|
198
|
+
} else {
|
|
199
|
+
cur.remove();
|
|
200
|
+
els.push(cur);
|
|
201
|
+
clauses.push(() => 1);
|
|
202
|
+
}
|
|
203
|
+
} else
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
el.replaceWith(cur = holder);
|
|
207
|
+
return (state2) => {
|
|
208
|
+
let i = clauses.findIndex((f) => f(state2));
|
|
209
|
+
if (els[i] != cur) {
|
|
210
|
+
;
|
|
211
|
+
(cur[_each] || cur).replaceWith(cur = els[i] || holder);
|
|
212
|
+
sprae(cur, state2);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
primary["scope"] = (el, expr, rootState) => {
|
|
217
|
+
let evaluate = parseExpr(el, expr, "scope");
|
|
218
|
+
const localState = evaluate(rootState);
|
|
219
|
+
let state2 = state(localState, rootState);
|
|
220
|
+
sprae(el, state2);
|
|
221
|
+
};
|
|
222
|
+
var _each = Symbol(":each");
|
|
223
|
+
primary["each"] = (tpl, expr) => {
|
|
224
|
+
let each = parseForExpression(expr);
|
|
225
|
+
if (!each)
|
|
226
|
+
return exprError(new Error(), tpl, expr);
|
|
227
|
+
const holder = tpl[_each] = document.createTextNode("");
|
|
228
|
+
tpl.replaceWith(holder);
|
|
229
|
+
const evaluate = parseExpr(tpl, each[2], ":each");
|
|
230
|
+
const keyExpr = tpl.getAttribute(":key");
|
|
231
|
+
const itemKey = keyExpr ? parseExpr(null, keyExpr) : null;
|
|
232
|
+
tpl.removeAttribute(":key");
|
|
233
|
+
const refExpr = tpl.getAttribute(":ref");
|
|
234
|
+
const scopes = new WeakishMap();
|
|
235
|
+
const itemEls = new WeakishMap();
|
|
236
|
+
let curEls = [];
|
|
237
|
+
return (state2) => {
|
|
238
|
+
let list = evaluate(state2);
|
|
239
|
+
if (!list)
|
|
240
|
+
list = [];
|
|
241
|
+
else if (typeof list === "number")
|
|
242
|
+
list = Array.from({ length: list }, (_, i) => [i, i + 1]);
|
|
243
|
+
else if (Array.isArray(list))
|
|
244
|
+
list = list.map((item, i) => [i + 1, item]);
|
|
245
|
+
else if (typeof list === "object")
|
|
246
|
+
list = Object.entries(list);
|
|
247
|
+
else
|
|
248
|
+
exprError(Error("Bad list value"), tpl, expr, ":each", list);
|
|
249
|
+
let newEls = [], elScopes = [];
|
|
250
|
+
for (let [idx, item] of list) {
|
|
251
|
+
let el, scope, key = itemKey?.({ [each[0]]: item, [each[1]]: idx });
|
|
252
|
+
if (key == null)
|
|
253
|
+
el = tpl.cloneNode(true);
|
|
254
|
+
else
|
|
255
|
+
(el = itemEls.get(key)) || itemEls.set(key, el = tpl.cloneNode(true));
|
|
256
|
+
newEls.push(el);
|
|
257
|
+
if (key == null || !(scope = scopes.get(key))) {
|
|
258
|
+
scope = state({ [each[0]]: item, [refExpr || ""]: null, [each[1]]: idx }, state2);
|
|
259
|
+
if (key != null)
|
|
260
|
+
scopes.set(key, scope);
|
|
261
|
+
} else
|
|
262
|
+
scope[each[0]] = item;
|
|
263
|
+
elScopes.push(scope);
|
|
264
|
+
}
|
|
265
|
+
domdiff_default(holder.parentNode, curEls, newEls, holder);
|
|
266
|
+
curEls = newEls;
|
|
267
|
+
for (let i = 0; i < newEls.length; i++) {
|
|
268
|
+
sprae(newEls[i], elScopes[i]);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
function parseForExpression(expression) {
|
|
273
|
+
let forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/;
|
|
274
|
+
let stripParensRE = /^\s*\(|\)\s*$/g;
|
|
275
|
+
let forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/;
|
|
276
|
+
let inMatch = expression.match(forAliasRE);
|
|
277
|
+
if (!inMatch)
|
|
278
|
+
return;
|
|
279
|
+
let items = inMatch[2].trim();
|
|
280
|
+
let item = inMatch[1].replace(stripParensRE, "").trim();
|
|
281
|
+
let iteratorMatch = item.match(forIteratorRE);
|
|
282
|
+
if (iteratorMatch)
|
|
283
|
+
return [
|
|
284
|
+
item.replace(forIteratorRE, "").trim(),
|
|
285
|
+
iteratorMatch[1].trim(),
|
|
286
|
+
items
|
|
287
|
+
];
|
|
288
|
+
return [item, "", items];
|
|
289
|
+
}
|
|
290
|
+
secondary["ref"] = (el, expr, state2) => {
|
|
291
|
+
state2[expr] = el;
|
|
292
|
+
};
|
|
293
|
+
secondary["id"] = (el, expr) => {
|
|
294
|
+
let evaluate = parseExpr(el, expr, ":id");
|
|
295
|
+
const update = (v) => el.id = v || v === 0 ? v : "";
|
|
296
|
+
return (state2) => update(evaluate(state2));
|
|
297
|
+
};
|
|
298
|
+
secondary["class"] = (el, expr) => {
|
|
299
|
+
let evaluate = parseExpr(el, expr, ":class");
|
|
300
|
+
let initClassName = el.className;
|
|
301
|
+
return (state2) => {
|
|
302
|
+
let v = evaluate(state2);
|
|
303
|
+
let className = typeof v === "string" ? v : (Array.isArray(v) ? v : Object.entries(v).map(([k, v2]) => v2 ? k : "")).filter(Boolean).join(" ");
|
|
304
|
+
el.className = [initClassName, className].filter(Boolean).join(" ");
|
|
305
|
+
};
|
|
306
|
+
};
|
|
307
|
+
secondary["style"] = (el, expr) => {
|
|
308
|
+
let evaluate = parseExpr(el, expr, ":style");
|
|
309
|
+
let initStyle = el.getAttribute("style") || "";
|
|
310
|
+
if (!initStyle.endsWith(";"))
|
|
311
|
+
initStyle += "; ";
|
|
312
|
+
return (state2) => {
|
|
313
|
+
let v = evaluate(state2);
|
|
314
|
+
if (typeof v === "string")
|
|
315
|
+
el.setAttribute("style", initStyle + v);
|
|
316
|
+
else {
|
|
317
|
+
el.setAttribute("style", initStyle);
|
|
318
|
+
for (let k in v)
|
|
319
|
+
el.style.setProperty(k, v[k]);
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
};
|
|
323
|
+
secondary["text"] = (el, expr) => {
|
|
324
|
+
let evaluate = parseExpr(el, expr, ":text");
|
|
325
|
+
return (state2) => {
|
|
326
|
+
let value = evaluate(state2);
|
|
327
|
+
el.textContent = value == null ? "" : value;
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
secondary[""] = (el, expr) => {
|
|
331
|
+
let evaluate = parseExpr(el, expr, ":");
|
|
332
|
+
if (evaluate)
|
|
333
|
+
return (state2) => {
|
|
334
|
+
let value = evaluate(state2);
|
|
335
|
+
for (let key in value)
|
|
336
|
+
attr(el, dashcase(key), value[key]);
|
|
337
|
+
};
|
|
338
|
+
};
|
|
339
|
+
secondary["value"] = (el, expr) => {
|
|
340
|
+
let evaluate = parseExpr(el, expr, ":value");
|
|
341
|
+
let from, to;
|
|
342
|
+
let update = el.type === "text" || el.type === "" ? (value) => el.setAttribute("value", el.value = value == null ? "" : value) : el.tagName === "TEXTAREA" || el.type === "text" || el.type === "" ? (value) => (from = el.selectionStart, to = el.selectionEnd, el.setAttribute("value", el.value = value == null ? "" : value), from && el.setSelectionRange(from, to)) : el.type === "checkbox" ? (value) => (el.value = value ? "on" : "", attr(el, "checked", value)) : el.type === "select-one" ? (value) => {
|
|
343
|
+
for (let option in el.options)
|
|
344
|
+
option.removeAttribute("selected");
|
|
345
|
+
el.value = value;
|
|
346
|
+
el.selectedOptions[0]?.setAttribute("selected", "");
|
|
347
|
+
} : (value) => el.value = value;
|
|
348
|
+
return (state2) => update(evaluate(state2));
|
|
349
|
+
};
|
|
350
|
+
secondary["on"] = (el, expr) => {
|
|
351
|
+
let evaluate = parseExpr(el, expr, ":on");
|
|
352
|
+
return (state2) => {
|
|
353
|
+
let listeners = evaluate(state2);
|
|
354
|
+
let offs = [];
|
|
355
|
+
for (let evt in listeners)
|
|
356
|
+
offs.push(on(el, evt, listeners[evt]));
|
|
357
|
+
return () => {
|
|
358
|
+
for (let off of offs)
|
|
359
|
+
off();
|
|
360
|
+
};
|
|
361
|
+
};
|
|
362
|
+
};
|
|
363
|
+
var directives_default = (el, expr, state2, name) => {
|
|
364
|
+
let evt = name.startsWith("on") && name.slice(2);
|
|
365
|
+
let evaluate = parseExpr(el, expr, ":" + name);
|
|
366
|
+
if (!evaluate)
|
|
367
|
+
return;
|
|
368
|
+
if (evt)
|
|
369
|
+
return (state3) => {
|
|
370
|
+
let value = evaluate(state3) || (() => {
|
|
371
|
+
});
|
|
372
|
+
return on(el, evt, value);
|
|
373
|
+
};
|
|
374
|
+
return (state3) => attr(el, name, evaluate(state3));
|
|
375
|
+
};
|
|
376
|
+
var on = (target, evt, origFn) => {
|
|
377
|
+
if (!origFn)
|
|
378
|
+
return;
|
|
379
|
+
let ctxs = evt.split("..").map((e) => {
|
|
380
|
+
let ctx = { evt: "", target, test: () => true };
|
|
381
|
+
ctx.evt = (e.startsWith("on") ? e.slice(2) : e).replace(
|
|
382
|
+
/\.(\w+)?-?([-\w]+)?/g,
|
|
383
|
+
(match, mod, param = "") => (ctx.test = mods[mod]?.(ctx, ...param.split("-")) || ctx.test, "")
|
|
384
|
+
);
|
|
385
|
+
return ctx;
|
|
386
|
+
});
|
|
387
|
+
if (ctxs.length == 1)
|
|
388
|
+
return addListenerWithMods(origFn, ctxs[0]);
|
|
389
|
+
const onFn = (fn, cur = 0) => {
|
|
390
|
+
let off;
|
|
391
|
+
let curListener = (e) => {
|
|
392
|
+
if (cur)
|
|
393
|
+
off();
|
|
394
|
+
let nextFn = fn.call(target, e);
|
|
395
|
+
if (typeof nextFn !== "function")
|
|
396
|
+
nextFn = () => {
|
|
397
|
+
};
|
|
398
|
+
if (cur + 1 < ctxs.length)
|
|
399
|
+
onFn(nextFn, !cur ? 1 : cur + 1);
|
|
400
|
+
};
|
|
401
|
+
return off = addListenerWithMods(curListener, ctxs[cur]);
|
|
402
|
+
};
|
|
403
|
+
let rootOff = onFn(origFn);
|
|
404
|
+
return () => rootOff();
|
|
405
|
+
function addListenerWithMods(fn, { evt: evt2, target: target2, test, defer, stop, prevent, ...opts }) {
|
|
406
|
+
if (defer)
|
|
407
|
+
fn = defer(fn);
|
|
408
|
+
let cb = (e) => test(e) && (stop && e.stopPropagation(), prevent && e.preventDefault(), fn.call(target2, e));
|
|
409
|
+
target2.addEventListener(evt2, cb, opts);
|
|
410
|
+
return () => target2.removeEventListener(evt2, cb, opts);
|
|
411
|
+
}
|
|
412
|
+
;
|
|
413
|
+
};
|
|
414
|
+
var mods = {
|
|
415
|
+
prevent(ctx) {
|
|
416
|
+
ctx.prevent = true;
|
|
417
|
+
},
|
|
418
|
+
stop(ctx) {
|
|
419
|
+
ctx.stop = true;
|
|
420
|
+
},
|
|
421
|
+
once(ctx) {
|
|
422
|
+
ctx.once = true;
|
|
423
|
+
},
|
|
424
|
+
passive(ctx) {
|
|
425
|
+
ctx.passive = true;
|
|
426
|
+
},
|
|
427
|
+
capture(ctx) {
|
|
428
|
+
ctx.capture = true;
|
|
429
|
+
},
|
|
430
|
+
window(ctx) {
|
|
431
|
+
ctx.target = window;
|
|
432
|
+
},
|
|
433
|
+
document(ctx) {
|
|
434
|
+
ctx.target = document;
|
|
435
|
+
},
|
|
436
|
+
toggle(ctx) {
|
|
437
|
+
ctx.defer = (fn, out) => (e) => out ? (out.call?.(ctx.target, e), out = null) : out = fn();
|
|
438
|
+
},
|
|
439
|
+
throttle(ctx, limit) {
|
|
440
|
+
ctx.defer = (fn) => throttle(fn, limit ? Number(limit) || 0 : 108);
|
|
441
|
+
},
|
|
442
|
+
debounce(ctx, wait) {
|
|
443
|
+
ctx.defer = (fn) => debounce(fn, wait ? Number(wait) || 0 : 108);
|
|
444
|
+
},
|
|
445
|
+
outside: (ctx) => (e) => {
|
|
446
|
+
let target = ctx.target;
|
|
447
|
+
if (target.contains(e.target))
|
|
448
|
+
return false;
|
|
449
|
+
if (e.target.isConnected === false)
|
|
450
|
+
return false;
|
|
451
|
+
if (target.offsetWidth < 1 && target.offsetHeight < 1)
|
|
452
|
+
return false;
|
|
453
|
+
return true;
|
|
454
|
+
},
|
|
455
|
+
self: (ctx) => (e) => e.target === ctx.target,
|
|
456
|
+
ctrl: (ctx, ...param) => (e) => keys.ctrl(e) && param.every((p) => keys[p] ? keys[p](e) : e.key === p),
|
|
457
|
+
shift: (ctx, ...param) => (e) => keys.shift(e) && param.every((p) => keys[p] ? keys[p](e) : e.key === p),
|
|
458
|
+
alt: (ctx, ...param) => (e) => keys.alt(e) && param.every((p) => keys[p] ? keys[p](e) : e.key === p),
|
|
459
|
+
meta: (ctx, ...param) => (e) => keys.meta(e) && param.every((p) => keys[p] ? keys[p](e) : e.key === p),
|
|
460
|
+
arrow: (ctx) => keys.arrow,
|
|
461
|
+
enter: (ctx) => keys.enter,
|
|
462
|
+
escape: (ctx) => keys.escape,
|
|
463
|
+
tab: (ctx) => keys.tab,
|
|
464
|
+
space: (ctx) => keys.space,
|
|
465
|
+
backspace: (ctx) => keys.backspace,
|
|
466
|
+
delete: (ctx) => keys.delete,
|
|
467
|
+
digit: (ctx) => keys.digit,
|
|
468
|
+
letter: (ctx) => keys.letter,
|
|
469
|
+
character: (ctx) => keys.character
|
|
470
|
+
};
|
|
471
|
+
var keys = {
|
|
472
|
+
ctrl: (e) => e.ctrlKey || e.key === "Control" || e.key === "Ctrl",
|
|
473
|
+
shift: (e) => e.shiftKey || e.key === "Shift",
|
|
474
|
+
alt: (e) => e.altKey || e.key === "Alt",
|
|
475
|
+
meta: (e) => e.metaKey || e.key === "Meta" || e.key === "Command",
|
|
476
|
+
arrow: (e) => e.key.startsWith("Arrow"),
|
|
477
|
+
enter: (e) => e.key === "Enter",
|
|
478
|
+
escape: (e) => e.key.startsWith("Esc"),
|
|
479
|
+
tab: (e) => e.key === "Tab",
|
|
480
|
+
space: (e) => e.key === "\xA0" || e.key === "Space" || e.key === " ",
|
|
481
|
+
backspace: (e) => e.key === "Backspace",
|
|
482
|
+
delete: (e) => e.key === "Delete",
|
|
483
|
+
digit: (e) => /^\d$/.test(e.key),
|
|
484
|
+
letter: (e) => /^[a-zA-Z]$/.test(e.key),
|
|
485
|
+
character: (e) => /^\S$/.test(e.key)
|
|
486
|
+
};
|
|
487
|
+
var throttle = (fn, limit) => {
|
|
488
|
+
let pause, planned, block = (e) => {
|
|
489
|
+
pause = true;
|
|
490
|
+
setTimeout(() => {
|
|
491
|
+
pause = false;
|
|
492
|
+
if (planned)
|
|
493
|
+
return planned = false, block(e), fn(e);
|
|
494
|
+
}, limit);
|
|
495
|
+
};
|
|
496
|
+
return (e) => {
|
|
497
|
+
if (pause)
|
|
498
|
+
return planned = true;
|
|
499
|
+
block(e);
|
|
500
|
+
return fn(e);
|
|
501
|
+
};
|
|
502
|
+
};
|
|
503
|
+
var debounce = (fn, wait) => {
|
|
504
|
+
let timeout;
|
|
505
|
+
return (e) => {
|
|
506
|
+
clearTimeout(timeout);
|
|
507
|
+
timeout = setTimeout(() => {
|
|
508
|
+
timeout = null;
|
|
509
|
+
fn(e);
|
|
510
|
+
}, wait);
|
|
511
|
+
};
|
|
512
|
+
};
|
|
513
|
+
var attr = (el, name, v) => {
|
|
514
|
+
if (v == null || v === false)
|
|
515
|
+
el.removeAttribute(name);
|
|
516
|
+
else
|
|
517
|
+
el.setAttribute(name, v === true ? "" : typeof v === "number" || typeof v === "string" ? v : "");
|
|
518
|
+
};
|
|
519
|
+
var evaluatorMemo = {};
|
|
520
|
+
function parseExpr(el, expression, dir) {
|
|
521
|
+
let evaluate = evaluatorMemo[expression];
|
|
522
|
+
if (!evaluate) {
|
|
523
|
+
let rightSideSafeExpression = /^[\n\s]*if.*\(.*\)/.test(expression) || /\b(let|const)\s/.test(expression) && !dir.startsWith(":on") ? `(() => {${expression}})()` : expression;
|
|
524
|
+
try {
|
|
525
|
+
evaluate = evaluatorMemo[expression] = new Function(`__scope`, `with (__scope) { return ${rightSideSafeExpression.trim()} };`);
|
|
526
|
+
} catch (e) {
|
|
527
|
+
return exprError(e, el, expression, dir);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return (state2) => {
|
|
531
|
+
let result;
|
|
532
|
+
try {
|
|
533
|
+
result = evaluate.call(el, state2);
|
|
534
|
+
} catch (e) {
|
|
535
|
+
return exprError(e, el, expression, dir);
|
|
536
|
+
}
|
|
537
|
+
return result;
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function exprError(error, element, expression, dir) {
|
|
541
|
+
Object.assign(error, { element, expression });
|
|
542
|
+
console.warn(`\u2234 ${error.message}
|
|
543
|
+
|
|
544
|
+
${dir}=${expression ? `"${expression}"
|
|
545
|
+
|
|
546
|
+
` : ""}`, element);
|
|
547
|
+
queueMicrotask(() => {
|
|
548
|
+
throw error;
|
|
549
|
+
}, 0);
|
|
550
|
+
}
|
|
551
|
+
function dashcase(str) {
|
|
552
|
+
return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => "-" + match.toLowerCase());
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// src/core.js
|
|
556
|
+
sprae.globals = sandbox;
|
|
557
|
+
var memo = /* @__PURE__ */ new WeakMap();
|
|
558
|
+
function sprae(container, values) {
|
|
559
|
+
if (!container.children)
|
|
560
|
+
return;
|
|
561
|
+
if (memo.has(container))
|
|
562
|
+
return Object.assign(memo.get(container), values);
|
|
563
|
+
const state2 = state(values || {});
|
|
564
|
+
const updates = [];
|
|
565
|
+
const init = (el, parent = el.parentNode) => {
|
|
566
|
+
for (let name in primary) {
|
|
567
|
+
let attrName = ":" + name;
|
|
568
|
+
if (el.hasAttribute?.(attrName)) {
|
|
569
|
+
let expr = el.getAttribute(attrName);
|
|
570
|
+
el.removeAttribute(attrName);
|
|
571
|
+
updates.push(primary[name](el, expr, state2, name));
|
|
572
|
+
if (memo.has(el) || el.parentNode !== parent)
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (el.attributes) {
|
|
577
|
+
for (let i = 0; i < el.attributes.length; ) {
|
|
578
|
+
let attr2 = el.attributes[i];
|
|
579
|
+
if (attr2.name[0] !== ":") {
|
|
580
|
+
i++;
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
el.removeAttribute(attr2.name);
|
|
584
|
+
let expr = attr2.value;
|
|
585
|
+
let attrNames = attr2.name.slice(1).split(":");
|
|
586
|
+
for (let attrName of attrNames) {
|
|
587
|
+
let dir = secondary[attrName] || directives_default;
|
|
588
|
+
updates.push(dir(el, expr, state2, attrName));
|
|
589
|
+
if (memo.has(el) || el.parentNode !== parent)
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
for (let i = 0, child; child = el.children[i]; i++) {
|
|
595
|
+
if (init(child, el) === false)
|
|
596
|
+
i--;
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
init(container);
|
|
600
|
+
for (let update of updates)
|
|
601
|
+
if (update) {
|
|
602
|
+
let teardown;
|
|
603
|
+
fx(() => {
|
|
604
|
+
if (typeof teardown === "function")
|
|
605
|
+
teardown();
|
|
606
|
+
teardown = update(state2);
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
memo.set(container, state2);
|
|
610
|
+
return state2;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/index.js
|
|
614
|
+
var src_default = sprae;
|
|
615
|
+
if (document.currentScript)
|
|
616
|
+
sprae(document.documentElement);
|
|
617
|
+
})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(()=>{var e,t,r=new Set,n=new WeakMap,l=new WeakMap,s=new WeakMap,i=Symbol("parent"),a={Array:Array,Object:Object,Number:Number,String:String,Boolean:Boolean,Date:Date,console:console},o={has:()=>!0,get(t,r){if("symbol"==typeof r)return t[r];if(!(r in t))return t[i]?.[r];if(Array.isArray(t)&&r in Array.prototype)return t[r];let s=t[r];if(e){let l=n.get(t);l||n.set(t,l={}),l[r]?l[r].includes(e)||l[r].push(e):l[r]=[e]}if(s&&s.constructor===Object||Array.isArray(s)){let e=l.get(s);return e||l.set(s,e=new Proxy(s,o)),e}return s},set(e,t,l){if(!(t in e)&&e[i]&&t in e[i])return e[i][t]=l;if(Array.isArray(e)&&t in Array.prototype)return e[t]=l;const s=e[t];if(Object.is(s,l))return!0;e[t]=l;let a=n.get(e)?.[t];if(a)for(let e of a)r.add(e);return f(),!0},deleteProperty:(e,t)=>(e[t]=void 0,delete e[t],!0)},u=(e,t)=>{if(l.has(e))return l.get(e);if(s.has(e))return e;let r=new Proxy(e,o);return l.set(e,r),s.set(r,e),e[i]=t?u(t):a,r},c=t=>{const r=()=>{let n=e;e=r,t(),e=n};return r(),r},f=()=>{t||(t=!0,queueMicrotask((()=>{for(let e of r)e.call();r.clear(),t=!1})))},p=new WeakMap,y=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=>p.get(e)||(e=>{const t=new WeakRef(e);return p.set(e,t),t})(e))(t),this.#e.register(t,e,r),super.set(e,r)}},g={},h={};g.if=(e,t)=>{let r=document.createTextNode(""),n=[x(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(x(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[d]||s).replaceWith(s=l[t]||r),j(s,e))}},g.scope=(e,t,r)=>{const n=x(e,t,"scope")(r);j(e,u(n,r))};var d=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 S(new Error,e,t);const n=e[d]=document.createTextNode("");e.replaceWith(n);const l=x(e,r[2],":each"),s=e.getAttribute(":key"),i=s?x(null,s):null;e.removeAttribute(":key");const a=e.getAttribute(":ref"),o=new y,c=new y;let f=[];return s=>{let p=l(s);p?"number"==typeof p?p=Array.from({length:p},((e,t)=>[t,t+1])):Array.isArray(p)?p=p.map(((e,t)=>[t+1,e])):"object"==typeof p?p=Object.entries(p):S(Error("Bad list value"),e,t,":each"):p=[];let y=[],g=[];for(let[t,n]of p){let l,f,p=i?.({[r[0]]:n,[r[1]]:t});null==p?l=e.cloneNode(!0):(l=c.get(p))||c.set(p,l=e.cloneNode(!0)),y.push(l),null!=p&&(f=o.get(p))?f[r[0]]=n:(f=u({[r[0]]:n,[a||""]:null,[r[1]]:t},s),null!=p&&o.set(p,f)),g.push(f)}!function(e,t,r,n){const l=new Map,s=new Map;let i,a;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=a=0;i!==t.length||a!==r.length;){var o=t[i],u=r[a];if(null===o)i++;else if(r.length<=a)e.removeChild(t[i]),i++;else if(t.length<=i)e.insertBefore(u,t[i]||n),a++;else if(o===u)i++,a++;else{var c=s.get(o),f=l.get(u);void 0===c?(e.removeChild(t[i]),i++):void 0===f?(e.insertBefore(u,t[i]||n),a++):(e.insertBefore(t[f],t[i]||n),t[f]=null,f>i+1&&i++,a++)}}}(n.parentNode,f,y,n),f=y;for(let e=0;e<y.length;e++)j(y[e],g[e])}},h.ref=(e,t,r)=>{r[t]=e},h.id=(e,t)=>{let r=x(e,t,":id");return t=>{return n=r(t),e.id=n||0===n?n:"";var n}},h.class=(e,t)=>{let r=x(e,t,":class"),n=e.className;return t=>{let l=r(t),s="string"==typeof l?l:(Array.isArray(l)?l:Object.entries(l).map((([e,t])=>t?e:""))).filter(Boolean).join(" ");e.className=[n,s].filter(Boolean).join(" ")}},h.style=(e,t)=>{let r=x(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])}}},h.text=(e,t)=>{let r=x(e,t,":text");return t=>{let n=r(t);e.textContent=null==n?"":n}},h[""]=(e,t)=>{let r=x(e,t,":");if(r)return t=>{let n=r(t);for(let t in n)W(e,t.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(e=>"-"+e.toLowerCase())),n[t])}},h.value=(e,t)=>{let r,n,l=x(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":"",W(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))},h.on=(e,t)=>{let r=x(e,t,":on");return t=>{let n=r(t),l=[];for(let t in n)l.push(b(e,t,n[t]));return()=>{for(let e of l)e()}}};var m=(e,t,r,n)=>{let l=n.startsWith("on")&&n.slice(2),s=x(e,t,":"+n);if(s)return l?t=>{let r=s(t)||(()=>{});return b(e,l,r)}:t=>W(e,n,s(t))},b=(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=v[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 a=t.call(e,i);"function"!=typeof a&&(a=()=>{}),r+1<n.length&&l(a,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,...a}){l&&(e=l(e));let o=t=>n(t)&&(s&&t.stopPropagation(),i&&t.preventDefault(),e.call(r,t));return r.addEventListener(t,o,a),()=>r.removeEventListener(t,o,a)}},v={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=>A.ctrl(e)&&t.every((t=>A[t]?A[t](e):e.key===t)),shift:(e,...t)=>e=>A.shift(e)&&t.every((t=>A[t]?A[t](e):e.key===t)),alt:(e,...t)=>e=>A.alt(e)&&t.every((t=>A[t]?A[t](e):e.key===t)),meta:(e,...t)=>e=>A.meta(e)&&t.every((t=>A[t]?A[t](e):e.key===t)),arrow:e=>A.arrow,enter:e=>A.enter,escape:e=>A.escape,tab:e=>A.tab,space:e=>A.space,backspace:e=>A.backspace,delete:e=>A.delete,digit:e=>A.digit,letter:e=>A.letter,character:e=>A.character},A={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)}},W=(e,t,r)=>{null==r||!1===r?e.removeAttribute(t):e.setAttribute(t,!0===r?"":"number"==typeof r||"string"==typeof r?r:"")},N={};function x(e,t,r){let n=N[t];if(!n){let l=/^[\n\s]*if.*\(.*\)/.test(t)||/\b(let|const)\s/.test(t)&&!r.startsWith(":on")?`(() => {${t}})()`:t;try{n=N[t]=new Function("__scope",`with (__scope) { return ${l.trim()} };`)}catch(n){return S(n,e,t,r)}}return l=>{let s;try{s=n.call(e,l)}catch(n){return S(n,e,t,r)}return s}}function S(e,t,r,n){Object.assign(e,{element:t,expression:r}),console.warn(`∴ ${e.message}\n\n${n}=${r?`"${r}"\n\n`:""}`,t),queueMicrotask((()=>{throw e}),0)}j.globals=a;var E=new WeakMap;function j(e,t){if(!e.children)return;if(E.has(e))return Object.assign(E.get(e),t);const r=u(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)),E.has(e)||e.parentNode!==t)return!1}}if(e.attributes)for(let l=0;l<e.attributes.length;){let s=e.attributes[l];if(":"!==s.name[0]){l++;continue}e.removeAttribute(s.name);let i=s.value,a=s.name.slice(1).split(":");for(let l of a){let s=h[l]||m;if(n.push(s(e,i,r,l)),E.has(e)||e.parentNode!==t)return!1}}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;c((()=>{"function"==typeof t&&t(),t=e(r)}))}return E.set(e,r),r}document.currentScript&&j(document.documentElement)})();
|
package/sprae.js
CHANGED
|
@@ -212,8 +212,8 @@ primary["if"] = (el, expr) => {
|
|
|
212
212
|
}
|
|
213
213
|
};
|
|
214
214
|
};
|
|
215
|
-
primary["
|
|
216
|
-
let evaluate = parseExpr(el, expr, "
|
|
215
|
+
primary["scope"] = (el, expr, rootState) => {
|
|
216
|
+
let evaluate = parseExpr(el, expr, "scope");
|
|
217
217
|
const localState = evaluate(rootState);
|
|
218
218
|
let state2 = state(localState, rootState);
|
|
219
219
|
sprae(el, state2);
|
|
@@ -326,22 +326,6 @@ secondary["text"] = (el, expr) => {
|
|
|
326
326
|
el.textContent = value == null ? "" : value;
|
|
327
327
|
};
|
|
328
328
|
};
|
|
329
|
-
secondary["data"] = (el, expr) => {
|
|
330
|
-
let evaluate = parseExpr(el, expr, ":data");
|
|
331
|
-
return (state2) => {
|
|
332
|
-
let value = evaluate(state2);
|
|
333
|
-
for (let key in value)
|
|
334
|
-
el.dataset[key] = value[key];
|
|
335
|
-
};
|
|
336
|
-
};
|
|
337
|
-
secondary["aria"] = (el, expr) => {
|
|
338
|
-
let evaluate = parseExpr(el, expr, ":aria");
|
|
339
|
-
const update = (value) => {
|
|
340
|
-
for (let key in value)
|
|
341
|
-
attr(el, "aria-" + dashcase(key), value[key] == null ? null : value[key] + "");
|
|
342
|
-
};
|
|
343
|
-
return (state2) => update(evaluate(state2));
|
|
344
|
-
};
|
|
345
329
|
secondary[""] = (el, expr) => {
|
|
346
330
|
let evaluate = parseExpr(el, expr, ":");
|
|
347
331
|
if (evaluate)
|
|
@@ -627,6 +611,8 @@ function sprae(container, values) {
|
|
|
627
611
|
|
|
628
612
|
// src/index.js
|
|
629
613
|
var src_default = sprae;
|
|
614
|
+
if (document.currentScript)
|
|
615
|
+
sprae(document.documentElement);
|
|
630
616
|
export {
|
|
631
617
|
src_default as default
|
|
632
618
|
};
|
package/sprae.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e,t,r=new Set,n=new WeakMap,l=new WeakMap,s=new WeakMap,
|
|
1
|
+
var e,t,r=new Set,n=new WeakMap,l=new WeakMap,s=new WeakMap,i=Symbol("parent"),a={Array:Array,Object:Object,Number:Number,String:String,Boolean:Boolean,Date:Date,console:console},o={has:()=>!0,get(t,r){if("symbol"==typeof r)return t[r];if(!(r in t))return t[i]?.[r];if(Array.isArray(t)&&r in Array.prototype)return t[r];let s=t[r];if(e){let l=n.get(t);l||n.set(t,l={}),l[r]?l[r].includes(e)||l[r].push(e):l[r]=[e]}if(s&&s.constructor===Object||Array.isArray(s)){let e=l.get(s);return e||l.set(s,e=new Proxy(s,o)),e}return s},set(e,t,l){if(!(t in e)&&e[i]&&t in e[i])return e[i][t]=l;if(Array.isArray(e)&&t in Array.prototype)return e[t]=l;const s=e[t];if(Object.is(s,l))return!0;e[t]=l;let a=n.get(e)?.[t];if(a)for(let e of a)r.add(e);return f(),!0},deleteProperty:(e,t)=>(e[t]=void 0,delete e[t],!0)},u=(e,t)=>{if(l.has(e))return l.get(e);if(s.has(e))return e;let r=new Proxy(e,o);return l.set(e,r),s.set(r,e),e[i]=t?u(t):a,r},c=t=>{const r=()=>{let n=e;e=r,t(),e=n};return r(),r},f=()=>{t||(t=!0,queueMicrotask((()=>{for(let e of r)e.call();r.clear(),t=!1})))},p=new WeakMap,y=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=>p.get(e)||(e=>{const t=new WeakRef(e);return p.set(e,t),t})(e))(t),this.#e.register(t,e,r),super.set(e,r)}},g={},h={};g.if=(e,t)=>{let r=document.createTextNode(""),n=[N(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(N(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[d]||s).replaceWith(s=l[t]||r),j(s,e))}},g.scope=(e,t,r)=>{const n=N(e,t,"scope")(r);j(e,u(n,r))};var d=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 S(new Error,e,t);const n=e[d]=document.createTextNode("");e.replaceWith(n);const l=N(e,r[2],":each"),s=e.getAttribute(":key"),i=s?N(null,s):null;e.removeAttribute(":key");const a=e.getAttribute(":ref"),o=new y,c=new y;let f=[];return s=>{let p=l(s);p?"number"==typeof p?p=Array.from({length:p},((e,t)=>[t,t+1])):Array.isArray(p)?p=p.map(((e,t)=>[t+1,e])):"object"==typeof p?p=Object.entries(p):S(Error("Bad list value"),e,t,":each"):p=[];let y=[],g=[];for(let[t,n]of p){let l,f,p=i?.({[r[0]]:n,[r[1]]:t});null==p?l=e.cloneNode(!0):(l=c.get(p))||c.set(p,l=e.cloneNode(!0)),y.push(l),null!=p&&(f=o.get(p))?f[r[0]]=n:(f=u({[r[0]]:n,[a||""]:null,[r[1]]:t},s),null!=p&&o.set(p,f)),g.push(f)}!function(e,t,r,n){const l=new Map,s=new Map;let i,a;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=a=0;i!==t.length||a!==r.length;){var o=t[i],u=r[a];if(null===o)i++;else if(r.length<=a)e.removeChild(t[i]),i++;else if(t.length<=i)e.insertBefore(u,t[i]||n),a++;else if(o===u)i++,a++;else{var c=s.get(o),f=l.get(u);void 0===c?(e.removeChild(t[i]),i++):void 0===f?(e.insertBefore(u,t[i]||n),a++):(e.insertBefore(t[f],t[i]||n),t[f]=null,f>i+1&&i++,a++)}}}(n.parentNode,f,y,n),f=y;for(let e=0;e<y.length;e++)j(y[e],g[e])}},h.ref=(e,t,r)=>{r[t]=e},h.id=(e,t)=>{let r=N(e,t,":id");return t=>{return n=r(t),e.id=n||0===n?n:"";var n}},h.class=(e,t)=>{let r=N(e,t,":class"),n=e.className;return t=>{let l=r(t),s="string"==typeof l?l:(Array.isArray(l)?l:Object.entries(l).map((([e,t])=>t?e:""))).filter(Boolean).join(" ");e.className=[n,s].filter(Boolean).join(" ")}},h.style=(e,t)=>{let r=N(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])}}},h.text=(e,t)=>{let r=N(e,t,":text");return t=>{let n=r(t);e.textContent=null==n?"":n}},h[""]=(e,t)=>{let r=N(e,t,":");if(r)return t=>{let n=r(t);for(let t in n)W(e,t.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(e=>"-"+e.toLowerCase())),n[t])}},h.value=(e,t)=>{let r,n,l=N(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":"",W(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))},h.on=(e,t)=>{let r=N(e,t,":on");return t=>{let n=r(t),l=[];for(let t in n)l.push(b(e,t,n[t]));return()=>{for(let e of l)e()}}};var m=(e,t,r,n)=>{let l=n.startsWith("on")&&n.slice(2),s=N(e,t,":"+n);if(s)return l?t=>{let r=s(t)||(()=>{});return b(e,l,r)}:t=>W(e,n,s(t))},b=(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=v[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 a=t.call(e,i);"function"!=typeof a&&(a=()=>{}),r+1<n.length&&l(a,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,...a}){l&&(e=l(e));let o=t=>n(t)&&(s&&t.stopPropagation(),i&&t.preventDefault(),e.call(r,t));return r.addEventListener(t,o,a),()=>r.removeEventListener(t,o,a)}},v={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=>A.ctrl(e)&&t.every((t=>A[t]?A[t](e):e.key===t)),shift:(e,...t)=>e=>A.shift(e)&&t.every((t=>A[t]?A[t](e):e.key===t)),alt:(e,...t)=>e=>A.alt(e)&&t.every((t=>A[t]?A[t](e):e.key===t)),meta:(e,...t)=>e=>A.meta(e)&&t.every((t=>A[t]?A[t](e):e.key===t)),arrow:e=>A.arrow,enter:e=>A.enter,escape:e=>A.escape,tab:e=>A.tab,space:e=>A.space,backspace:e=>A.backspace,delete:e=>A.delete,digit:e=>A.digit,letter:e=>A.letter,character:e=>A.character},A={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)}},W=(e,t,r)=>{null==r||!1===r?e.removeAttribute(t):e.setAttribute(t,!0===r?"":"number"==typeof r||"string"==typeof r?r:"")},x={};function N(e,t,r){let n=x[t];if(!n){let l=/^[\n\s]*if.*\(.*\)/.test(t)||/\b(let|const)\s/.test(t)&&!r.startsWith(":on")?`(() => {${t}})()`:t;try{n=x[t]=new Function("__scope",`with (__scope) { return ${l.trim()} };`)}catch(n){return S(n,e,t,r)}}return l=>{let s;try{s=n.call(e,l)}catch(n){return S(n,e,t,r)}return s}}function S(e,t,r,n){Object.assign(e,{element:t,expression:r}),console.warn(`∴ ${e.message}\n\n${n}=${r?`"${r}"\n\n`:""}`,t),queueMicrotask((()=>{throw e}),0)}j.globals=a;var E=new WeakMap;function j(e,t){if(!e.children)return;if(E.has(e))return Object.assign(E.get(e),t);const r=u(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)),E.has(e)||e.parentNode!==t)return!1}}if(e.attributes)for(let l=0;l<e.attributes.length;){let s=e.attributes[l];if(":"!==s.name[0]){l++;continue}e.removeAttribute(s.name);let i=s.value,a=s.name.slice(1).split(":");for(let l of a){let s=h[l]||m;if(n.push(s(e,i,r,l)),E.has(e)||e.parentNode!==t)return!1}}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;c((()=>{"function"==typeof t&&t(),t=e(r)}))}return E.set(e,r),r}var M=j;document.currentScript&&j(document.documentElement);export{M as default};
|
package/src/directives.js
CHANGED
|
@@ -10,8 +10,8 @@ export const primary = {}, secondary = {}
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
// :if is interchangeable with :each depending on order, :if :each or :each :if have different meanings
|
|
13
|
-
// as for :if :
|
|
14
|
-
// we consider :
|
|
13
|
+
// as for :if :scope - :if must init first, since it is lazy, to avoid initializing component ahead of time by :scope
|
|
14
|
+
// we consider :scope={x} :if={x} case insignificant
|
|
15
15
|
primary['if'] = (el, expr) => {
|
|
16
16
|
let holder = document.createTextNode(''),
|
|
17
17
|
clauses = [parseExpr(el, expr, ':if')],
|
|
@@ -44,9 +44,9 @@ primary['if'] = (el, expr) => {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
// :
|
|
48
|
-
primary['
|
|
49
|
-
let evaluate = parseExpr(el, expr, '
|
|
47
|
+
// :scope must come before :each, but :if has primary importance
|
|
48
|
+
primary['scope'] = (el, expr, rootState) => {
|
|
49
|
+
let evaluate = parseExpr(el, expr, 'scope')
|
|
50
50
|
const localState = evaluate(rootState)
|
|
51
51
|
let state = createState(localState, rootState)
|
|
52
52
|
// console.log(123, state.foo, state.bar)
|
|
@@ -187,23 +187,6 @@ secondary['text'] = (el, expr) => {
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
secondary['data'] = (el, expr) => {
|
|
191
|
-
let evaluate = parseExpr(el, expr, ':data')
|
|
192
|
-
|
|
193
|
-
return ((state) => {
|
|
194
|
-
let value = evaluate(state)
|
|
195
|
-
for (let key in value) el.dataset[key] = value[key];
|
|
196
|
-
})
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
secondary['aria'] = (el, expr) => {
|
|
200
|
-
let evaluate = parseExpr(el, expr, ':aria')
|
|
201
|
-
const update = (value) => {
|
|
202
|
-
for (let key in value) attr(el, 'aria-' + dashcase(key), value[key] == null ? null : value[key] + '');
|
|
203
|
-
}
|
|
204
|
-
return ((state) => update(evaluate(state)))
|
|
205
|
-
}
|
|
206
|
-
|
|
207
190
|
// set props in-bulk or run effect
|
|
208
191
|
secondary[''] = (el, expr) => {
|
|
209
192
|
let evaluate = parseExpr(el, expr, ':')
|
package/src/index.js
CHANGED
|
@@ -3,8 +3,4 @@ import './directives.js';
|
|
|
3
3
|
export default sprae;
|
|
4
4
|
|
|
5
5
|
// autoinit
|
|
6
|
-
|
|
7
|
-
// const s = document.currentScript
|
|
8
|
-
// if (s && s.hasAttribute('init')) {
|
|
9
|
-
// sprae(document.documentElement)
|
|
10
|
-
// }
|
|
6
|
+
if (document.currentScript) sprae(document.documentElement)
|
package/test/dom.js
CHANGED
|
@@ -147,13 +147,13 @@ test.todo('props: semicols in expression', async () => {
|
|
|
147
147
|
})
|
|
148
148
|
|
|
149
149
|
|
|
150
|
-
test('data: base', async () => {
|
|
150
|
+
test.skip('data: base', async () => {
|
|
151
151
|
let el = h`<input :data="{a:1, fooBar:2}"/>`
|
|
152
152
|
let params = sprae(el)
|
|
153
153
|
is(el.outerHTML, `<input data-a="1" data-foo-bar="2">`)
|
|
154
154
|
})
|
|
155
155
|
|
|
156
|
-
test('aria: base', async () => {
|
|
156
|
+
test.skip('aria: base', async () => {
|
|
157
157
|
let el = h`<input type="text" id="jokes" role="combobox" :aria="{controls:'joketypes', autocomplete:'list', expanded:false, activeOption:'item1', activedescendant:'', xxx:null}"/>`
|
|
158
158
|
sprae(el)
|
|
159
159
|
is(el.outerHTML, `<input type="text" id="jokes" role="combobox" aria-controls="joketypes" aria-autocomplete="list" aria-expanded="false" aria-active-option="item1" aria-activedescendant="">`)
|
|
@@ -277,8 +277,8 @@ test('if: (#3) subsequent content is not abandoned', async () => {
|
|
|
277
277
|
is(x.outerHTML, `<x><z>123</z></x>`)
|
|
278
278
|
})
|
|
279
279
|
|
|
280
|
-
test('if: + :
|
|
281
|
-
let el = h`<div><x :if="x" :
|
|
280
|
+
test('if: + :scope doesnt prevent secondary effects from happening', async () => {
|
|
281
|
+
let el = h`<div><x :if="x" :scope="{}" :text="x"></x></div>`
|
|
282
282
|
let state = sprae(el, {x:''})
|
|
283
283
|
is(el.innerHTML, ``)
|
|
284
284
|
state.x = '123'
|
|
@@ -286,7 +286,7 @@ test('if: + :with doesnt prevent secondary effects from happening', async () =>
|
|
|
286
286
|
is(el.innerHTML, `<x>123</x>`)
|
|
287
287
|
|
|
288
288
|
// NOTE: we ignore this case
|
|
289
|
-
// let el2 = h`<div><x :if="x" :
|
|
289
|
+
// let el2 = h`<div><x :if="x" :scope="{x:cond}" :text="x"></x></div>`
|
|
290
290
|
// let state2 = sprae(el, {cond:''})
|
|
291
291
|
// is(el2.innerHTML, ``)
|
|
292
292
|
// state2.cond = '123'
|
|
@@ -460,7 +460,7 @@ test('each: condition within loop', async () => {
|
|
|
460
460
|
|
|
461
461
|
test('each: next items have own "this", not single one', async () => {
|
|
462
462
|
// FIXME: let el = h`<x :each="x in 3"></x>`
|
|
463
|
-
let el = h`<div><x :each="x in 3" :data="
|
|
463
|
+
let el = h`<div><x :each="x in 3" :data-x="x" :x="log.push(x, this.dataset.x)"></x></div>`
|
|
464
464
|
let log = []
|
|
465
465
|
let state = sprae(el, {log})
|
|
466
466
|
is(state.log, [1,'1',2,'2',3,'3'])
|
|
@@ -859,7 +859,7 @@ test('on: modifiers chain', async e => {
|
|
|
859
859
|
})
|
|
860
860
|
|
|
861
861
|
test('with: inline', async () => {
|
|
862
|
-
let el = h`<x :
|
|
862
|
+
let el = h`<x :scope="{foo:'bar'}"><y :text="foo + baz"></y></x>`
|
|
863
863
|
let state = sprae(el, {baz: 'qux'})
|
|
864
864
|
// FIXME: this doesn't inherit root scope baz property and instead uses hard-initialized one
|
|
865
865
|
is(el.innerHTML, `<y>barqux</y>`)
|
|
@@ -868,7 +868,7 @@ test('with: inline', async () => {
|
|
|
868
868
|
is(el.innerHTML, `<y>barquux</y>`)
|
|
869
869
|
})
|
|
870
870
|
test.skip('with: inline reactive', () => {
|
|
871
|
-
let el = h`<x :
|
|
871
|
+
let el = h`<x :scope="{foo:'bar'}"><y :text="foo + baz"></y></x>`
|
|
872
872
|
let baz = signal('qux')
|
|
873
873
|
sprae(el, {baz})
|
|
874
874
|
// FIXME: this doesn't inherit root scope baz property and instead uses hard-initialized one
|
|
@@ -877,7 +877,7 @@ test.skip('with: inline reactive', () => {
|
|
|
877
877
|
is(el.innerHTML, `<y>barquux</y>`)
|
|
878
878
|
})
|
|
879
879
|
test('with: data', async () => {
|
|
880
|
-
let el = h`<x :
|
|
880
|
+
let el = h`<x :scope="x"><y :text="foo"></y></x>`
|
|
881
881
|
let state = sprae(el, {x: {foo:'bar'}})
|
|
882
882
|
is(el.innerHTML, `<y>bar</y>`)
|
|
883
883
|
console.log('update', state.x)
|
|
@@ -889,7 +889,7 @@ test('with: data', async () => {
|
|
|
889
889
|
test('with: transparency', async () => {
|
|
890
890
|
// NOTE: y:text initializes through directive, not through parent
|
|
891
891
|
// therefore by default :text uses parent's state, not defined by element itself
|
|
892
|
-
let el = h`<x :
|
|
892
|
+
let el = h`<x :scope="{foo:'foo'}"><y :scope="b" :text="foo+bar"></y></x>`
|
|
893
893
|
let params = sprae(el, {b:{bar:'bar'}})
|
|
894
894
|
is(el.innerHTML, `<y>foobar</y>`)
|
|
895
895
|
params.b.bar = 'baz'
|
|
@@ -897,7 +897,7 @@ test('with: transparency', async () => {
|
|
|
897
897
|
is(el.innerHTML, `<y>foobaz</y>`)
|
|
898
898
|
})
|
|
899
899
|
test.skip('with: reactive transparency', () => {
|
|
900
|
-
let el = h`<x :
|
|
900
|
+
let el = h`<x :scope="{foo:1}"><y :scope="b.c" :text="foo+bar"></y></x>`
|
|
901
901
|
const bar = signal('2')
|
|
902
902
|
sprae(el, {b:{c:{bar}}})
|
|
903
903
|
is(el.innerHTML, `<y>12</y>`)
|
|
@@ -905,7 +905,7 @@ test.skip('with: reactive transparency', () => {
|
|
|
905
905
|
is(el.innerHTML, `<y>13</y>`)
|
|
906
906
|
})
|
|
907
907
|
test('with: writes to state', async () => {
|
|
908
|
-
let a = h`<x :
|
|
908
|
+
let a = h`<x :scope="{a:1}"><y :on="{x(){a++}}" :text="a"></y></x>`
|
|
909
909
|
sprae(a)
|
|
910
910
|
is(a.innerHTML, `<y>1</y>`)
|
|
911
911
|
a.firstChild.dispatchEvent(new window.Event('x'))
|
|
@@ -959,7 +959,7 @@ test(':: scope refers to current element', async () => {
|
|
|
959
959
|
|
|
960
960
|
test(':: scope directives must come first', async () => {
|
|
961
961
|
// NOTE: we init attributes in order of definition
|
|
962
|
-
let a = h`<x :text="y" :
|
|
962
|
+
let a = h`<x :text="y" :scope="{y:1}" :ref="x"></x>`
|
|
963
963
|
sprae(a, {})
|
|
964
964
|
is(a.outerHTML, `<x>1</x>`)
|
|
965
965
|
})
|
package/test/index.html
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
<meta charset=utf-8>
|
|
3
3
|
<title>Test</title>
|
|
4
4
|
|
|
5
|
+
<script defer src="../sprae.auto.js"></script>
|
|
6
|
+
<div :scope="{data:1}">
|
|
7
|
+
<span :text="data"></span>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
5
10
|
<script async src="../node_modules/es-module-shims/dist/es-module-shims.js"></script>
|
|
6
11
|
<script type="importmap">
|
|
7
12
|
{
|
|
@@ -27,4 +32,4 @@
|
|
|
27
32
|
}
|
|
28
33
|
</script>
|
|
29
34
|
|
|
30
|
-
<script src="./test.js" type="module"></script>
|
|
35
|
+
<script src="./test.js" type="module"></script>
|
package/test/state.js
CHANGED
package/todo.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* [x] combinations: :else :if
|
|
9
9
|
* [x] :each :if, :if :each
|
|
10
10
|
* [x] :each :each
|
|
11
|
-
* [x] :
|
|
11
|
+
* [x] :scope must be able to write state value as well
|
|
12
12
|
* [x] docs: give example to each directive
|
|
13
13
|
* [x] initialize per-element: <x :each><y :if></y><x> - tree-dependent (:each comes first).
|
|
14
14
|
* [x] generalize common attributes :prop="xyz"
|
|
@@ -22,7 +22,7 @@
|
|
|
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
|
-
* ~~[x] replace :ref with :
|
|
25
|
+
* ~~[x] replace :ref with :scope="this as x"~~
|
|
26
26
|
* [x] :ref creates instance in current state, not creates a new state
|
|
27
27
|
* [x] to avoid extending signal-struct, we must collect state data before, and call updates after for extended state
|
|
28
28
|
* [x] optimization: replace element-props with direct (better) setters
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
* ~~[x] report usignal problem~~ author is not really interested
|
|
32
32
|
* [x] `this` doesn't refer to element/scope in event handlers
|
|
33
33
|
* [x] :text="" empty values shouldn't throw
|
|
34
|
-
* [x] implement :
|
|
34
|
+
* [x] implement :scope
|
|
35
35
|
* [x] update :value without losing focus / position
|
|
36
36
|
* ~~[x] run tiredown if element got removed from condition or loop (free memory)~~ no need just make sure no refs to elements stored
|
|
37
37
|
* [x] `sprae(el, newState)` can update element's state directly (as batch!?) -> must be tested against repeats in directives
|
|
38
|
-
* [x] :if :ref, :if :
|
|
38
|
+
* [x] :if :ref, :if :scope -> context setters must come first always
|
|
39
39
|
* [x] :style="{'--x':value}"
|
|
40
40
|
* [x] :onkeydown.ctrl-alt-D
|
|
41
41
|
* [ ] frameworks benchmark
|
|
@@ -46,4 +46,6 @@
|
|
|
46
46
|
* [x] once, capture, passive
|
|
47
47
|
* [x] ...rest
|
|
48
48
|
* [x] parallel chains
|
|
49
|
-
* [x] Sandbox
|
|
49
|
+
* [x] Sandbox
|
|
50
|
+
* [x] Autorun
|
|
51
|
+
* [ ] There's some weird bug with attaching event listeners to document, hmmm. Need test case.
|