sprae 3.1.0 → 5.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 +50 -2
- package/readme.md +69 -80
- 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 +16 -23
- package/src/index.js +1 -5
- package/src/state.js +2 -0
- package/src/{weakish-map.js → util.js} +5 -0
- package/test/dom.js +18 -3
- package/test/index.html +6 -1
- package/test/state.js +1 -0
- package/todo.md +3 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sprae",
|
|
3
3
|
"description": "DOM microhydration.",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "5.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
|
|
|
@@ -517,12 +518,59 @@
|
|
|
517
518
|
? Some 'or' character `:onclick--onkeydown`
|
|
518
519
|
? We can redirect to main event, that's it for now
|
|
519
520
|
|
|
520
|
-
## [
|
|
521
|
+
## [x] Insert content by reusing the node/template -> use `:render="ref" :with="data"`
|
|
521
522
|
|
|
522
523
|
* Makes easy use of repeatable fragments, instead of web-components
|
|
523
524
|
+ sort-of "detached" for-each
|
|
524
525
|
+ reinforces :ref
|
|
525
526
|
? does it replace element or puts a content?
|
|
527
|
+
* Refs:
|
|
528
|
+
* https://github.com/justinfagnani/html-include-element - src=
|
|
529
|
+
* https://www.npmjs.com/package/imported-template - content=path-to-file
|
|
530
|
+
* https://github.com/SirPepe/html-import src="content.selector"
|
|
531
|
+
* https://github.com/Juicy/juicy-html - html="rawhtml"
|
|
532
|
+
* https://www.npmjs.com/package/html-import-wc - src=path
|
|
533
|
+
* https://github.com/sashafirsov/slotted-element - src=path-to-htmlor-json
|
|
534
|
+
* https://github.com/webcomponents/html-imports - href=filepath
|
|
535
|
+
* https://github.com/giuseppeg/xm - import src=filepath
|
|
536
|
+
* https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-include-html#link-tag-html-only
|
|
537
|
+
* https://github.com/maherbo/include-HTML-document-fragment - link href=path
|
|
538
|
+
* https://github.com/github/include-fragment-element - src=path-to-html
|
|
526
539
|
|
|
527
540
|
1. :use="ref-id"
|
|
528
|
-
|
|
541
|
+
```
|
|
542
|
+
<template :ref="abc" id="abc"><span :text="abc"></span></template>
|
|
543
|
+
|
|
544
|
+
<div :use="abc" :with="{abc:'def'}"></div>
|
|
545
|
+
<div :use="'#abc'" :with="{abc:'def'}"></div>
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
- `<use>` from SVG replaces element, but we need inserting content
|
|
549
|
+
|
|
550
|
+
2. :content="#template-id"
|
|
551
|
+
|
|
552
|
+
- conflicts with direct inline content
|
|
553
|
+
|
|
554
|
+
3. :include="#template-id"
|
|
555
|
+
|
|
556
|
+
- conflicts with path to file
|
|
557
|
+
|
|
558
|
+
4. :tpl="#template-id"
|
|
559
|
+
|
|
560
|
+
- some confusion of meaning
|
|
561
|
+
|
|
562
|
+
5. :render="#template-id"
|
|
563
|
+
|
|
564
|
+
+ compatible with liquid 5.0
|
|
565
|
+
+ makes sense as :render=a :with=b
|
|
566
|
+
|
|
567
|
+
* Let's think consequently.
|
|
568
|
+
1. `<template>` element has direct purpose for that
|
|
569
|
+
2. We provide content fallback for unloaded elements in case of `:text` as `<x :text="abc">fallback</x>`
|
|
570
|
+
3. There's too many ways to implement fetching - ideally we leave that concern out and focus only on including content
|
|
571
|
+
4. The approach is almost ready declarative custom element. `<template>` is standard part of it - adds to 1.
|
|
572
|
+
|
|
573
|
+
## [x] Remove non-essential directives -> yep, less API friction
|
|
574
|
+
* :aria - can be defined via plain attributes
|
|
575
|
+
* :data - confusable with :scope, doesn't provide much value, can be used as `:data-x=""` etc
|
|
576
|
+
* :={} - 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,33 @@
|
|
|
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
|
+
<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>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Use
|
|
29
|
+
|
|
30
|
+
Sprae evaluates attributes starting with `:`:
|
|
9
31
|
|
|
10
32
|
```html
|
|
11
33
|
<div id="container" :if="user">
|
|
@@ -20,28 +42,9 @@ Sprae defines attributes starting with `:` as directives:
|
|
|
20
42
|
</script>
|
|
21
43
|
```
|
|
22
44
|
|
|
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)`
|
|
45
|
+
* `sprae` initializes container's subtree with data and immediately evaporates `:` attrs.
|
|
46
|
+
* `state` object reflects current values, changing any props rerenders subtree next tick.
|
|
26
47
|
|
|
27
|
-
<!--
|
|
28
|
-
<details>
|
|
29
|
-
<summary><strong>Autoinit</strong></summary>
|
|
30
|
-
|
|
31
|
-
sprae can be used without build step or JS, autoinitializing document:
|
|
32
|
-
|
|
33
|
-
```html
|
|
34
|
-
<script src="./sprae.js" defer init="{ count: 0 }"></script>
|
|
35
|
-
|
|
36
|
-
<span :text="count">
|
|
37
|
-
<button :on="{ click: e => count++ }">inc</button>
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
* `:with` defines data for regions of the tree to autoinit sprae on.
|
|
41
|
-
* `init` attribute tells sprae to automatically initialize document.
|
|
42
|
-
|
|
43
|
-
</details>
|
|
44
|
-
-->
|
|
45
48
|
|
|
46
49
|
## Attributes
|
|
47
50
|
|
|
@@ -90,17 +93,17 @@ Welcome, <span :text="user.name">Guest</span>.
|
|
|
90
93
|
|
|
91
94
|
#### `:class="value"`
|
|
92
95
|
|
|
93
|
-
Set class value from either string, array or object.
|
|
96
|
+
Set class value from either string, array or object. Extends existing `class` attribute, if any.
|
|
94
97
|
|
|
95
98
|
```html
|
|
96
99
|
<div :class="`foo ${bar}`"></div>
|
|
97
|
-
<div :class="['foo', 'bar']"></div>
|
|
98
|
-
<div :class="{foo: true, bar: false}"></div>
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
<div class="
|
|
103
|
-
|
|
101
|
+
<!-- extend existing class -->
|
|
102
|
+
<div class="foo" :class="`bar`"></div>
|
|
103
|
+
<!-- <div class="foo bar"></div> -->
|
|
104
|
+
|
|
105
|
+
<!-- object with values -->
|
|
106
|
+
<div :class="{foo:true, bar: false}"></div>
|
|
104
107
|
```
|
|
105
108
|
|
|
106
109
|
#### `:style="value"`
|
|
@@ -126,7 +129,7 @@ Set value of an input, textarea or select. Takes handle of `checked` and `select
|
|
|
126
129
|
</select>
|
|
127
130
|
```
|
|
128
131
|
|
|
129
|
-
#### `:<prop>="value?"
|
|
132
|
+
#### `:<prop>="value?"`
|
|
130
133
|
|
|
131
134
|
Set any attribute value or run effect.
|
|
132
135
|
|
|
@@ -137,16 +140,21 @@ Set any attribute value or run effect.
|
|
|
137
140
|
<!-- Multiple properties -->
|
|
138
141
|
<input :id:name="name" />
|
|
139
142
|
|
|
140
|
-
<!--
|
|
141
|
-
<
|
|
143
|
+
<!-- Effect (triggers any time bar changes) -->
|
|
144
|
+
<div :fx="void bar()" ></div>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### `:="props?"`
|
|
148
|
+
|
|
149
|
+
Spread multiple attibures.
|
|
142
150
|
|
|
143
|
-
|
|
144
|
-
<
|
|
151
|
+
```html
|
|
152
|
+
<input :="{ id: name, name, type:'text', value }" />
|
|
145
153
|
```
|
|
146
154
|
|
|
147
|
-
#### `:on<event>="handler"`, `:on
|
|
155
|
+
#### `:on<event>="handler"`, `:on<in>..on<out>="handler"`
|
|
148
156
|
|
|
149
|
-
Add event
|
|
157
|
+
Add event listener or events chain.
|
|
150
158
|
|
|
151
159
|
```html
|
|
152
160
|
<!-- Single event -->
|
|
@@ -167,9 +175,6 @@ Add event listeners.
|
|
|
167
175
|
|
|
168
176
|
<!-- Event modifiers -->
|
|
169
177
|
<button :onclick.throttle-500="handler">Not too often</button>
|
|
170
|
-
|
|
171
|
-
<!-- Bulk/custom events -->
|
|
172
|
-
<button :on="{ click: handler, touch: handler, special: handler }">Submit</button>
|
|
173
178
|
```
|
|
174
179
|
|
|
175
180
|
##### Event modifiers
|
|
@@ -183,37 +188,9 @@ Add event listeners.
|
|
|
183
188
|
* `.ctrl-<key>, .alt-<key>, .meta-<key>, .shift-<key>` – key combinations, eg. `.ctrl-alt-delete` or `.meta-x`.
|
|
184
189
|
* `.*` – any other modifier has no effect, but allows binding multiple handlers to same event (like jQuery event classes).
|
|
185
190
|
|
|
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
191
|
#### `:with="data"`
|
|
215
192
|
|
|
216
|
-
Define
|
|
193
|
+
Define or extend data scope for a subtree.
|
|
217
194
|
|
|
218
195
|
```html
|
|
219
196
|
<!-- Inline data -->
|
|
@@ -222,15 +199,26 @@ Define variables for a subtree fragment scope.
|
|
|
222
199
|
<!-- External data -->
|
|
223
200
|
<y :with="data"></y>
|
|
224
201
|
|
|
225
|
-
<!--
|
|
202
|
+
<!-- Extend scope -->
|
|
226
203
|
<x :with="{ foo: 'bar' }">
|
|
227
204
|
<y :with="{ baz: 'qux' }" :text="foo + baz"></y>
|
|
228
205
|
</x>
|
|
229
206
|
```
|
|
230
207
|
|
|
208
|
+
#### `:render="ref"`
|
|
209
|
+
|
|
210
|
+
Include template as element content.
|
|
211
|
+
|
|
212
|
+
```html
|
|
213
|
+
<template :ref="foo"><span :text="foo"></span></template>
|
|
214
|
+
|
|
215
|
+
<div :render="foo" :with="{foo:'bar'}">unknown</div>
|
|
216
|
+
<div :render="foo" :with="{foo:'baz'}">unknown</div>
|
|
217
|
+
```
|
|
218
|
+
|
|
231
219
|
#### `:ref="id"`
|
|
232
220
|
|
|
233
|
-
Expose element to data scope with the `id`:
|
|
221
|
+
Expose element to current data scope with the `id`:
|
|
234
222
|
|
|
235
223
|
```html
|
|
236
224
|
<!-- single item -->
|
|
@@ -256,26 +244,28 @@ Expose element to data scope with the `id`:
|
|
|
256
244
|
|
|
257
245
|
Expressions are sandboxed, ie. have no access to global or window (since sprae can be run in server environment).
|
|
258
246
|
|
|
259
|
-
|
|
247
|
+
```html
|
|
248
|
+
<div :x="window.x"></div>
|
|
249
|
+
<!-- window is undefined -->
|
|
250
|
+
```
|
|
260
251
|
|
|
261
|
-
|
|
252
|
+
Default sandbox provides: _Array_, _Object_, _Number_, _String_, _Boolean_, _Date_, _console_.<br/>
|
|
253
|
+
Sandbox can be extended as `Object.assign(sprae.globals, { BigInt, window, document })`.
|
|
262
254
|
|
|
263
255
|
## Examples
|
|
264
256
|
|
|
265
257
|
* TODO MVC: [demo](https://dy.github.io/sprae/examples/todomvc), [code](https://github.com/dy/sprae/blob/main/examples/todomvc.html)
|
|
266
|
-
*
|
|
258
|
+
* Wavearea: [demo](https://dy.github.io/wavearea?src=//cdn.freesound.org/previews/586/586281_2332564-lq.mp3), [code](https://github.com/dy/wavearea)
|
|
267
259
|
|
|
268
260
|
## Justification
|
|
269
261
|
|
|
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
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), [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
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.
|
|
274
264
|
* 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
265
|
|
|
276
|
-
_Sprae_ takes convention of _templize directives_ (_alpine_/_vue_ attrs) and builds upon [_@preact/signals_](https://ghub.io/@preact/signals).
|
|
266
|
+
_Sprae_ takes convention of _templize directives_ (_alpine_/_vue_ attrs) and builds upon <del>[_@preact/signals_](https://ghub.io/@preact/signals)</del> simple reacti.
|
|
277
267
|
|
|
278
|
-
* It doesn't break static html markup.
|
|
268
|
+
* It doesn't break or modify initial static html markup.
|
|
279
269
|
* It falls back to element content if uninitialized.
|
|
280
270
|
* It doesn't enforce SPA nor JSX.
|
|
281
271
|
* It enables island hydration.
|
|
@@ -284,7 +274,6 @@ _Sprae_ takes convention of _templize directives_ (_alpine_/_vue_ attrs) and bui
|
|
|
284
274
|
* Input data may contain [signals](https://ghub.io/@preact/signals) or [reactive values](https://ghub.io/sube).
|
|
285
275
|
* Elements / data API is open and enable easy interop.
|
|
286
276
|
|
|
287
|
-
|
|
288
277
|
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
278
|
|
|
290
279
|
|
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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import sprae from './core.js'
|
|
3
3
|
import swap from './domdiff.js'
|
|
4
4
|
import { state as createState} from './state.js'
|
|
5
|
-
import { WeakishMap } from './
|
|
5
|
+
import { queueMicrotask, WeakishMap } from './util.js'
|
|
6
6
|
|
|
7
7
|
// reserved directives - order matters!
|
|
8
8
|
// primary initialized first by selector, secondary initialized by iterating attributes
|
|
@@ -46,7 +46,7 @@ primary['if'] = (el, expr) => {
|
|
|
46
46
|
|
|
47
47
|
// :with must come before :each, but :if has primary importance
|
|
48
48
|
primary['with'] = (el, expr, rootState) => {
|
|
49
|
-
let evaluate = parseExpr(el, expr, 'with')
|
|
49
|
+
let evaluate = parseExpr(el, expr, ':with')
|
|
50
50
|
const localState = evaluate(rootState)
|
|
51
51
|
let state = createState(localState, rootState)
|
|
52
52
|
// console.log(123, state.foo, state.bar)
|
|
@@ -58,7 +58,7 @@ const _each = Symbol(':each')
|
|
|
58
58
|
// :each must init before :ref, :id or any others, since it defines scope
|
|
59
59
|
primary['each'] = (tpl, expr) => {
|
|
60
60
|
let each = parseForExpression(expr);
|
|
61
|
-
if (!each) return exprError(new Error, tpl, expr);
|
|
61
|
+
if (!each) return exprError(new Error, tpl, expr, ':each');
|
|
62
62
|
|
|
63
63
|
// FIXME: make sure no memory leak here
|
|
64
64
|
// we need :if to be able to replace holder instead of tpl for :if :each case
|
|
@@ -68,7 +68,7 @@ primary['each'] = (tpl, expr) => {
|
|
|
68
68
|
const evaluate = parseExpr(tpl, each[2], ':each');
|
|
69
69
|
|
|
70
70
|
const keyExpr = tpl.getAttribute(':key');
|
|
71
|
-
const itemKey = keyExpr ? parseExpr(null, keyExpr) : null;
|
|
71
|
+
const itemKey = keyExpr ? parseExpr(null, keyExpr, ':each') : null;
|
|
72
72
|
tpl.removeAttribute(':key')
|
|
73
73
|
|
|
74
74
|
const refExpr = tpl.getAttribute(':ref');
|
|
@@ -142,6 +142,16 @@ function parseForExpression(expression) {
|
|
|
142
142
|
return [item, '', items]
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
secondary['render'] = (el, expr, state) => {
|
|
146
|
+
let evaluate = parseExpr(el, expr, ':render'),
|
|
147
|
+
tpl = evaluate(state)
|
|
148
|
+
|
|
149
|
+
if (!tpl) exprError(new Error('Template not found'), el, expr, ':render');
|
|
150
|
+
|
|
151
|
+
let content = tpl.content.cloneNode(true);
|
|
152
|
+
el.replaceChildren(content)
|
|
153
|
+
sprae(el, state)
|
|
154
|
+
}
|
|
145
155
|
|
|
146
156
|
secondary['ref'] = (el, expr, state) => {
|
|
147
157
|
// FIXME: wait for complex ref use-case
|
|
@@ -187,23 +197,6 @@ secondary['text'] = (el, expr) => {
|
|
|
187
197
|
}
|
|
188
198
|
}
|
|
189
199
|
|
|
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
200
|
// set props in-bulk or run effect
|
|
208
201
|
secondary[''] = (el, expr) => {
|
|
209
202
|
let evaluate = parseExpr(el, expr, ':')
|
|
@@ -443,9 +436,9 @@ function parseExpr(el, expression, dir) {
|
|
|
443
436
|
}
|
|
444
437
|
}
|
|
445
438
|
|
|
446
|
-
function exprError(error, element, expression,
|
|
439
|
+
function exprError(error, element, expression, directive) {
|
|
447
440
|
Object.assign( error, { element, expression } )
|
|
448
|
-
console.warn(`∴ ${error.message}\n\n${
|
|
441
|
+
console.warn(`∴ ${error.message}\n\n${directive}=${ expression ? `"${expression}"\n\n` : '' }`, element)
|
|
449
442
|
queueMicrotask(() => { throw error }, 0)
|
|
450
443
|
}
|
|
451
444
|
|
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/src/state.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
// reset stacktrace or plan for fastest next call
|
|
3
|
+
// https://twitter.com/_developit/status/1634033380940455936
|
|
4
|
+
export const queueMicrotask = Promise.prototype.then.bind(Promise.resolve());
|
|
5
|
+
|
|
1
6
|
// based on https://github.com/WebReflection/not-so-weak/
|
|
2
7
|
const refs = new WeakMap;
|
|
3
8
|
|
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="">`)
|
|
@@ -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'])
|
|
@@ -916,6 +916,21 @@ test('with: writes to state', async () => {
|
|
|
916
916
|
is(a.innerHTML, `<y>3</y>`)
|
|
917
917
|
})
|
|
918
918
|
|
|
919
|
+
test(':render by ref', async () => {
|
|
920
|
+
let a = h`<template :ref="abc"><div :text="123"></div></template><x :render="abc">456</x>`
|
|
921
|
+
sprae(a)
|
|
922
|
+
is(a.outerHTML, `<template><div :text="123"></div></template><x><div>123</div></x>`)
|
|
923
|
+
})
|
|
924
|
+
|
|
925
|
+
test(':render :with', async () => {
|
|
926
|
+
let a = h`<template :ref="abc"><div :text="text"></div></template><x :render="abc" />`
|
|
927
|
+
let state = sprae(a, {text:'abc'})
|
|
928
|
+
is(a.outerHTML, `<template><div :text="text"></div></template><x><div>abc</div></x>`)
|
|
929
|
+
state.text = 'def'
|
|
930
|
+
await tick()
|
|
931
|
+
is(a.outerHTML, `<template><div :text="text"></div></template><x><div>def</div></x>`)
|
|
932
|
+
})
|
|
933
|
+
|
|
919
934
|
test('ref: base', async () => {
|
|
920
935
|
let a = h`<a :ref="a" :init="log.push(a), null" :text="b"></a>`
|
|
921
936
|
let state = sprae(a, {log:[], b:1})
|
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 :with="{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