sprae 3.1.0 → 4.0.0

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