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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sprae",
3
3
  "description": "DOM microhydration.",
4
- "version": "3.1.0",
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": "esbuild --bundle ./src/index.js --outfile=sprae.js --format=esm && npm run min",
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
- ## [ ] Insert content by reusing the node/template,
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
- 2. :
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æ [![tests](https://github.com/dy/sprae/actions/workflows/node.js.yml/badge.svg)](https://github.com/dy/sprae/actions/workflows/node.js.yml) [![size](https://img.shields.io/bundlephobia/minzip/sprae?label=size)](https://bundlephobia.com/result?p=sprae) [![npm](https://img.shields.io/npm/v/sprae?color=orange)](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
- ## Usage
7
+ ## Install
7
8
 
8
- Sprae defines attributes starting with `:` as directives:
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` is object reflecting current values, changing any of its props rerenders subtree.
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. Appends existing `class` attribute, if any.
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
- <div class="a" :class="['b', 'c']"></div>
101
- <!--
102
- <div class="a b c"></div>
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?"`, `:="props?"`
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
- <!-- Bulk properties -->
141
- <input :="{ id: name, name, type:'text', value }" />
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
- <!-- Effects (trigger any time foo or bar changes) -->
144
- <div :="if (foo) bar()" :fx="void bar()" ></div>
151
+ ```html
152
+ <input :="{ id: name, name, type:'text', value }" />
145
153
  ```
146
154
 
147
- #### `:on<event>="handler"`, `:on="events"`, `:<in>..<out>="handler"`
155
+ #### `:on<event>="handler"`, `:on<in>..on<out>="handler"`
148
156
 
149
- Add event listeners.
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 variables for a subtree fragment scope.
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
- <!-- Transparency -->
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
- Default sandbox provides: _Array_, _Object_, _Number_, _String_, _Boolean_, _Date_, _console_.
247
+ ```html
248
+ <div :x="window.x"></div>
249
+ <!-- window is undefined -->
250
+ ```
260
251
 
261
- Sandbox can be extended via `Object.assign(sprae.globals, { BigInt, window, document })` etc.
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
- * Waveplay: [demo](https://dy.github.io/waveplay), [code](https://github.com/dy/waveedit)
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["with"] = (el, expr, rootState) => {
216
- let evaluate = parseExpr(el, expr, "with");
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,a=Symbol("parent"),i={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[a]?.[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[a]&&t in e[a])return e[a][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 i=n.get(e)?.[t];if(i)for(let e of i)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[a]=t?u(t):i,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)}},h={},g={};h.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),M(s,e))}},h.with=(e,t,r)=>{const n=N(e,t,"with")(r);M(e,u(n,r))};var d=Symbol(":each");h.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"),a=s?N(null,s):null;e.removeAttribute(":key");const i=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=[],h=[];for(let[t,n]of p){let l,f,p=a?.({[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,[i||""]:null,[r[1]]:t},s),null!=p&&o.set(p,f)),h.push(f)}!function(e,t,r,n){const l=new Map,s=new Map;let a,i;for(a=0;a<t.length;a++)l.set(t[a],a);for(a=0;a<r.length;a++)s.set(r[a],a);for(a=i=0;a!==t.length||i!==r.length;){var o=t[a],u=r[i];if(null===o)a++;else if(r.length<=i)e.removeChild(t[a]),a++;else if(t.length<=a)e.insertBefore(u,t[a]||n),i++;else if(o===u)a++,i++;else{var c=s.get(o),f=l.get(u);void 0===c?(e.removeChild(t[a]),a++):void 0===f?(e.insertBefore(u,t[a]||n),i++):(e.insertBefore(t[f],t[a]||n),t[f]=null,f>a+1&&a++,i++)}}}(n.parentNode,f,y,n),f=y;for(let e=0;e<y.length;e++)M(y[e],h[e])}},g.ref=(e,t,r)=>{r[t]=e},g.id=(e,t)=>{let r=N(e,t,":id");return t=>{return n=r(t),e.id=n||0===n?n:"";var n}},g.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(" ")}},g.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])}}},g.text=(e,t)=>{let r=N(e,t,":text");return t=>{let n=r(t);e.textContent=null==n?"":n}},g.data=(e,t)=>{let r=N(e,t,":data");return t=>{let n=r(t);for(let t in n)e.dataset[t]=n[t]}},g.aria=(e,t)=>{let r=N(e,t,":aria");return t=>(t=>{for(let r in t)W(e,"aria-"+j(r),null==t[r]?null:t[r]+"")})(r(t))},g[""]=(e,t)=>{let r=N(e,t,":");if(r)return t=>{let n=r(t);for(let t in n)W(e,j(t),n[t])}},g.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))},g.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 a(r,n[0]);const l=(t,r=0)=>{let s;return s=a((a=>{r&&s();let i=t.call(e,a);"function"!=typeof i&&(i=()=>{}),r+1<n.length&&l(i,r?r+1:1)}),n[r])};let s=l(r);return()=>s();function a(e,{evt:t,target:r,test:n,defer:l,stop:s,prevent:a,...i}){l&&(e=l(e));let o=t=>n(t)&&(s&&t.stopPropagation(),a&&t.preventDefault(),e.call(r,t));return r.addEventListener(t,o,i),()=>r.removeEventListener(t,o,i)}},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)}function j(e){return e.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(e=>"-"+e.toLowerCase()))}M.globals=i;var E=new WeakMap;function M(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 h){let s=":"+l;if(e.hasAttribute?.(s)){let a=e.getAttribute(s);if(e.removeAttribute(s),n.push(h[l](e,a,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 a=s.value,i=s.name.slice(1).split(":");for(let l of i){let s=g[l]||m;if(n.push(s(e,a,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;export{$ as default};
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 './weakish-map.js'
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, dir) {
439
+ function exprError(error, element, expression, directive) {
447
440
  Object.assign( error, { element, expression } )
448
- console.warn(`∴ ${error.message}\n\n${dir}=${ expression ? `"${expression}"\n\n` : '' }`, element)
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
- // NOTE: abandoning for now, since requires a separate non-module JS entry, until use-case appears
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,5 @@
1
+ import { queueMicrotask } from "./util.js"
2
+
1
3
  // currentFx stack of listeners
2
4
  let currentFx, batch = new Set, pendingUpdate
3
5
 
@@ -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="{x}" :x="log.push(x, this.dataset.x)"></x></div>`
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
@@ -239,6 +239,7 @@ t('state: direct list', async () => {
239
239
  let sum; fx(()=> sum = list.reduce((sum, item)=>item.x + sum, 0))
240
240
  is(sum, 3)
241
241
  list[0].x = 2
242
+ is(list[0].x, 2)
242
243
  await tick()
243
244
  is(sum, 4)
244
245
  list.splice(0, 2, {x:3}, {x:3})
package/todo.md CHANGED
@@ -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.