sprae 2.1.2 → 2.2.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,12 +1,14 @@
1
1
  {
2
2
  "name": "sprae",
3
3
  "description": "DOM microhydration.",
4
- "version": "2.1.2",
5
- "main": "sprae.js",
4
+ "version": "2.2.0",
5
+ "main": "src/index.js",
6
+ "module": "src/index.js",
6
7
  "type": "module",
7
8
  "dependencies": {
8
9
  "@preact/signals-core": "^1.2.2",
9
- "element-props": "^2.3.0",
10
+ "element-props": "^2.4.2",
11
+ "jsdom": "^20.0.3",
10
12
  "primitive-pool": "^2.0.0",
11
13
  "signal-struct": "^1.5.1",
12
14
  "sube": "^2.2.1",
@@ -14,7 +16,6 @@
14
16
  },
15
17
  "devDependencies": {
16
18
  "@preact/signals": "^1.1.2",
17
- "browser-env": "^3.3.0",
18
19
  "es-module-shims": "^1.6.2",
19
20
  "esbuild": "^0.15.14",
20
21
  "hyperf": "^1.4.0",
@@ -25,7 +26,7 @@
25
26
  "wait-please": "^3.1.0"
26
27
  },
27
28
  "scripts": {
28
- "test": "node -r browser-env/register test/test.js",
29
+ "test": "node -r ./test/register.cjs test/test.js",
29
30
  "build": "esbuild --bundle ./src/index.js --outfile=sprae.js --format=esm",
30
31
  "watch": "esbuild --bundle ./src/index.js --outfile=sprae.js --format=esm --sourcemap --watch",
31
32
  "min": "terser sprae.js -o sprae.min.js --module -c passes=3 -m"
package/plan.md CHANGED
@@ -18,11 +18,17 @@
18
18
  * [x] expand to any subscribables: both as state vars
19
19
  * [x] :ref
20
20
  * [x] :ref + :each
21
+ * [x] event chains :ona-onb
22
+ * [x] bulk events :ona:onb
23
+ * [x] multiprop setter :a:b="c"
24
+ * [x] make `this` in expression an element
25
+ * [ ] replace :ref with :with="this as x"
21
26
  * [ ] optimization: replace element-props with direct (better) setters
27
+ * [ ] Make sure `false` gets serialized, not removes attr
22
28
  * [ ] report usignal problem
23
29
  * [ ] :onconnect, :ondisconnect
24
30
  * [ ] frameworks benchmark
25
31
  * [ ] examples
26
- * [ ] todomvc
27
- * [ ] Sandbox expressions: no global, no "scope" object name, no "arguments"
28
- * [ ] `this` in expressions must refer to current element always
32
+ * [x] todomvc
33
+ * [ ] wave-edit
34
+ * [ ] Sandbox expressions: no global, no "scope" object name, no "arguments"
package/r&d.md CHANGED
@@ -1,4 +1,4 @@
1
- ## [x] name -> sprea
1
+ ## [x] name -> sprae
2
2
 
3
3
  * rasa
4
4
  * dew
@@ -130,15 +130,16 @@
130
130
  + `:else :if` doesn't throw error in JSDOM tests
131
131
  - less resemblance with vue: who cares, we already remotely resemble it
132
132
 
133
- ## [x] Keep className marker of directive or not?
133
+ ## [x] Keep className marker of directive or not? -> no
134
134
 
135
135
  -> No: first, there's :class directive changing the class itself;
136
136
  -> Second, there's easier way to just "evaporate" directive = not initialize twice;
137
137
  -> Third, there's too much pollution with class markers
138
138
 
139
- ## [ ] :html?
139
+ ## [x] :html? -> Nope: can be implemented via any prop
140
140
 
141
141
  - introduces malign hole of including sprae inside of html
142
+ - nah: can easily be done manually as `:html="this.innerHTML = abc"`. Just need passing context
142
143
 
143
144
  ## [x] :fx? -> nah, already works. Just return `null` in any attr, that's it.
144
145
 
@@ -146,21 +147,68 @@
146
147
  - doesn't necessarily useful, since any directive is already an effect
147
148
  + works already out of box, just creates `fx` attribute if value is returned
148
149
 
149
- ## [x] :init? -> same as fx.
150
+ ## [x] :init? -> same as :fx="initCode".
150
151
 
151
152
  * waiting for use-case
152
153
 
153
- ## [ ] :key.enter?
154
+ ## [x] :key.enter? -> no, can be done manually
154
155
 
155
156
  - opens gateway to generic modifiers
156
157
  - introduces a whole mental layer to learn, including combinations of modifiers all around.
157
-
158
- ## [ ] :onconnected/disconnected?
158
+ - can be conflicting with event classes.
159
+ - too adhoc-y
160
+ - can be easily done as `:onkeypress="e => e.key === 'Enter'"`
161
+ -> waiting for use-case
162
+
163
+ ## [x] :key:boolean="abc" -> nope: can be done manually
164
+
165
+ ? do we really need typecast?
166
+ - it can be done manually as `:key="Boolean(abc)"`
167
+
168
+ ## [ ] `this` in expressions must refer to current element or scope? -> to current element
169
+
170
+ 1. `this === element`
171
+ + Allows this.innerHTML and other customs
172
+ - Can be done easily via `:ref="xxx"`
173
+ + External handlers don't have access to refs.
174
+ + Existing convention
175
+ 2. `this === scope`
176
+ - scope is not supposed to be extendible
177
+ - scope is already available
178
+ + methods provided in `init` may not have access to scope _yet_.
179
+ ~- not reliable way to obtain scope via `this.x` - better be explicit as `state.x`
180
+
181
+ ## [ ] :onconnected/:ondisconnected?
182
+
183
+ -> waiting for use-case
184
+
185
+ ## [x] Chain of events: often useful to have shared context. -> Try `:onevent-onanotherevent`
186
+
187
+ * focus/blur, connected/disconnected, mousedown/mouseup, keydown/keyup, touchstart/touchmove/touchend, dragstart/dragover/dragend, animationstart/animationover/animationend, transitionstart/transitionend
188
+ ? is there a way to organize handlers for chains of events?
189
+ 1. :onfocus:onblur="e => e => {}"
190
+ - :onblur looks more like a pseudo
191
+ + a bit better distinctive visually, less noisy
192
+ - combining root-level attrs them doesn't seem very intuitive for fn waterfall.
193
+ 2. :onfocus-blur="e => e => {}"
194
+ - ? is there dash-events? looks like a single event
195
+ - ? why not :on-focus-blur
196
+ - ? why not :onfocus-onblur
197
+ + can be converted from on="{ focusBlur: event }" via dash notation
198
+ + less :on prefixes
199
+ + has better "flowy" meaning
200
+ * 2.1 :onfocus-onblur="e => e => {}"
201
+ + distinctive visually as 1
202
+ + flowy nature of 2
203
+ 3. :onfocus.onblur="e => e => {}"
204
+ - looks like a modifier
205
+ - . can be used as event class onclick.x
159
206
 
160
207
  ## [ ] Plugins
161
208
 
162
209
  * @sprae/tailwind: `<x :tw="mt-1 mx-2"></x>` - separate tailwind utility classes from main ones; allow conditional setters.
163
210
  * @sprae/item: `<x :item="{type:a, scope:b}"` – provide microdata
211
+ - can be solved naturally, unless there's special meaning
164
212
  * @sprae/hcodes: `<x :h=""` – provide microformats
165
213
 
166
214
  ## [x] Write any-attributes via :<prop>? -> yep
@@ -188,6 +236,7 @@
188
236
  + overhead is minimal
189
237
  + react-like
190
238
  + it has better control over serialization
239
+ + `:onchange:oninput="e=>xyz"` is very good
191
240
 
192
241
  ## [ ] Sandbox?
193
242
 
@@ -207,11 +256,13 @@
207
256
  + Deps can be analyzed / implemented without signals
208
257
  - Screwed up debugging / stacktrace (unless errored)
209
258
  + that "unlimits" returned struct, so that any property can be added/deleted.
259
+ - doesn't really save from `new (()=>{}).constructor` hack: we gotta substitute objects too.
210
260
 
211
- ## [ ] :onclick="direct code" ?
261
+ ## [x] :onclick="direct code" ? -> no: immediately invoked.
212
262
 
213
263
  + compatible with direct `onclick=...`
214
264
  + no need for arrow/regular functions syntax in templates
215
265
  - still need that syntax for filters, maps etc
216
266
  + can be made async by default
217
- - illicit `event` object
267
+ - illicit `event` object
268
+ - conflicts with regular attrs logic: the code is immediately invoked and can assign a function.
package/readme.md CHANGED
@@ -108,7 +108,7 @@ Set style value from object or a string.
108
108
  <!--
109
109
  #### `:value="value"`
110
110
 
111
- Bind (2-way) value to input, textarea or select.
111
+ Set value of an input, textarea or select.
112
112
 
113
113
  ```html
114
114
  <input :with="{text: ''}" :value="text" />
@@ -122,19 +122,62 @@ Bind (2-way) value to input, textarea or select.
122
122
 
123
123
  #### `:<prop>="value"`, `:="props"`
124
124
 
125
- Set any other prop or props value.
125
+ Set any prop value.
126
126
 
127
127
  ```html
128
+ <!-- Single property -->
128
129
  <label :for="name" :text="name" />
129
- <input :="{ id: name, name, type, value }" :onchange="e => value=e.target.value" />
130
+
131
+ <!-- Multiple properties -->
132
+ <input :id:name="name" />
133
+
134
+ <!-- Bulk properties -->
135
+ <input :="{ id: name, name, type:'text', value }" />
130
136
  ```
131
137
 
132
- #### `:on="events"`
138
+ #### `:on<event>="handler"`, `:on="events"`
133
139
 
134
140
  Add event listeners.
135
141
 
136
142
  ```html
137
- <button :on="{ click: handler, touch: handler }">Submit</button>
143
+ <!-- Single event -->
144
+ <input type="checkbox" :onchange="e => isChecked=e.target.value">
145
+
146
+ <!-- Multiple events -->
147
+ <input :value="text" :oninput:onchange="e => text=e.target.value">
148
+
149
+ <!-- Sequence of events -->
150
+ <button :onfocus-onblur="e => { // onfocus
151
+ let id = setInterval(track,200)
152
+ return e => { // onblur
153
+ clearInterval(id)
154
+ }
155
+ }">
156
+
157
+ <!-- Bulk/custom events -->
158
+ <button :on="{ click: handler, touch: handler, special: handler }">Submit</button>
159
+ ```
160
+
161
+ #### `:with="data"`
162
+
163
+ Set data for a subtree fragment scope.
164
+
165
+ ```html
166
+ <!-- Inline data -->
167
+ <x :with="{ foo: 'bar' }" :text="foo"></x>
168
+
169
+ <!-- External data -->
170
+ <y :with="data"></y>
171
+
172
+ <!-- Inheritance -->
173
+ <x :with="{ foo: 'bar' }">
174
+ <y :with="{ baz: 'qux' }" :text="foo + baz"></y>
175
+ </x>
176
+
177
+ <!-- Single property -->
178
+ <li :with="this as li">
179
+ <input :onfocus-onblur="e => (li.classList.add('editing'), e => li.classList.remove('editing'))" />
180
+ </li>
138
181
  ```
139
182
 
140
183
  #### `:data="values"`
@@ -159,28 +202,20 @@ Set [aria-role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA)
159
202
  }" />
160
203
  ```
161
204
 
162
- #### `:with="data"`
163
-
164
- Set data for a subtree fragment scope.
165
-
166
- ```html
167
- <x :with="{ foo: 'bar' }">
168
- <y :with="{ baz: 'qux' }" :text="foo + baz"></y>
169
- </x>
170
- ```
171
-
205
+ <!--
172
206
  #### `:ref="id"`
173
207
 
174
208
  Expose element to a subtree fragment with the `id`.
175
209
 
176
210
  ```html
177
- <li :ref="item">
211
+ <li :with="{ item: this }">
178
212
  <input
179
- :onfocus="e=> item.classList.add('editing')"
213
+ :onfocus="e => item.classList.add('editing')"
180
214
  :onblur="e => item.classList.remove('editing')"
181
215
  />
182
216
  </li>
183
217
  ```
218
+ -->
184
219
 
185
220
  <!--
186
221
 
@@ -192,8 +227,7 @@ _sprae_ is built on top of [_@preact/signals_](https://ghub.io/@preact/signals).
192
227
  * Expressions support any reactive values in data (see [sube](https://github.com/dy/sube))
193
228
  * Updates happen minimally only when used values update
194
229
  * Subscription is weak and get disposed when element is disposed.
195
- -->
196
- <!--
230
+
197
231
  Directive expressions are natively reactive, ie. data may contain any async/reactive values, such as:
198
232
 
199
233
  * _Promise_ / _Thenable_
@@ -203,32 +237,58 @@ Directive expressions are natively reactive, ie. data may contain any async/reac
203
237
  * etc., see [sube](https://github.com/dy/sube/blob/main/README.md) for the full list.
204
238
 
205
239
  This way, for example, _@preact/signals_ or _rxjs_ can be connected directly bypassing subscription or reading value.
206
-
207
- Update happens when any value changes:
208
240
  -->
209
- <!--
241
+
242
+ ## Hints
243
+
244
+ **1.** Attributes are initialized in order, so pay attention providing scope attributes:
245
+
246
+ ```html
247
+ <li :each="item in items" :ref="li">
248
+ <button :onclick="e => li.classList.add('loading')"></button>
249
+ </li>
250
+
251
+ <li :ref="li" :each="item in items">
252
+ <!-- Invalid: li is undefined -->
253
+ <button :onclick="e => li.classList.add('loading')"></button>
254
+ </li>
255
+ ```
256
+
257
+ **2.** Data allows signals values, which can be an alternative way to control template state:
258
+
210
259
  ```html
211
260
  <div id="done" :text="loading ? 'loading' : result">...</div>
212
261
 
213
- <script>
262
+ <script type="module">
214
263
  import sprae from 'sprae';
215
264
  import { signals } from '@preact/signals';
216
265
 
217
266
  // <div id="done">...</div>
218
267
 
219
268
  const loading = signal(true), result = signal(false);
220
-
221
269
  sprae(done, { loading, result })
270
+ setTimeout(() => (loading.value = true, result.value = 'done'), 1000)
222
271
 
223
272
  // <div id="done">loading</div>
224
273
 
225
- setTimeout(() => (loading.value = true, result.value = 'done'), 1000)
226
-
227
274
  // ... 1s after
228
275
  // <div id="done">done</div>
229
276
  </script>
230
277
  ```
231
- -->
278
+
279
+ **3.** Data recognizes reactive values as inputs as well: _Promise_ / _Thenable_, _Observable_ / _Subscribable_, _AsyncIterable_ (etc., see [sube](https://github.com/dy/sube/blob/main/README.md)). This way, for example, _rxjs_ can be connected to template directly.
280
+
281
+ ```html
282
+ <div :text="clicks">#</div> clicks
283
+
284
+ <script type="module">
285
+ import sprae from 'sprae';
286
+ import { fromEvent, scan } from 'rxjs';
287
+ sprae(document, {
288
+ clicks: fromEvent(document, 'click').pipe(scan((count) => count + 1, 0))
289
+ });
290
+ </script>
291
+ ```
232
292
 
233
293
  ## Examples
234
294
 
package/sprae.js CHANGED
@@ -415,7 +415,7 @@ var prop = (el2, k, v2) => {
415
415
  if (el2[k] !== v2) {
416
416
  el2[k] = v2;
417
417
  }
418
- if (v2 === false || v2 == null)
418
+ if (v2 == null || v2 === false)
419
419
  el2.removeAttribute(k);
420
420
  else if (typeof v2 !== "function")
421
421
  el2.setAttribute(
@@ -428,12 +428,9 @@ var input = (el2) => [
428
428
  el2.type === "text" || el2.type === "" ? (value) => el2.value = value == null ? "" : value : el2.type === "checkbox" ? (value) => (el2.value = value ? "on" : "", prop(el2, "checked", value)) : el2.type === "select-one" ? (value) => ([...el2.options].map((el3) => el3.removeAttribute("selected")), el2.value = value, el2.selectedOptions[0]?.setAttribute("selected", "")) : (value) => el2.value = value
429
429
  ];
430
430
  var el = document.createElement("div");
431
- var dashcase = (str) => {
432
- el.dataset[str] = "";
433
- let dashStr = el.attributes[0].name.slice(5);
434
- delete el.dataset[str];
435
- return dashStr;
436
- };
431
+ function dashcase(str) {
432
+ return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => "-" + match.toLowerCase());
433
+ }
437
434
 
438
435
  // node_modules/swapdom/swap-inflate.js
439
436
  var swap = (parent, a2, b2, end = null) => {
@@ -487,35 +484,38 @@ var primitive_pool_default = (key) => {
487
484
 
488
485
  // src/directives.js
489
486
  var directives_default = (el2, expr, state, name) => {
490
- let evaluate = parseExpr(expr, ":" + name, state);
487
+ if (name.startsWith("on")) {
488
+ return directives.on(el2, `{"${name.split("-").map((n2) => n2.startsWith("on") ? n2.slice(2) : n2).join("-")}": ${expr}}`, state);
489
+ }
490
+ let evaluate = parseExpr(el2, expr, ":" + name, state);
491
491
  const update = (value) => prop(el2, name, value);
492
492
  b(() => update(evaluate(state)));
493
493
  };
494
494
  var directives = {};
495
495
  var _each = Symbol(":each");
496
496
  var _ref = Symbol(":ref");
497
- directives[":with"] = (el2, expr, rootState) => {
498
- let evaluate = parseExpr(expr, "with", rootState);
497
+ directives["with"] = (el2, expr, rootState) => {
498
+ let evaluate = parseExpr(el2, expr, "with", rootState);
499
499
  const params = w(() => Object.assign({}, rootState, evaluate(rootState)));
500
500
  let state = sprae(el2, params.value);
501
501
  b((values = params.value) => h(() => Object.assign(state, values)));
502
502
  return false;
503
503
  };
504
- directives[":ref"] = (el2, expr, state) => {
504
+ directives["ref"] = (el2, expr, state) => {
505
505
  if (el2.hasAttribute(":each"))
506
506
  return el2[_ref] = expr;
507
507
  sprae(el2, Object.assign(Object.create(state), { [expr]: el2 }));
508
508
  return false;
509
509
  };
510
- directives[":if"] = (el2, expr, state) => {
511
- let holder = document.createTextNode(""), clauses = [parseExpr(expr, ":if", state)], els = [el2], cur = el2;
510
+ directives["if"] = (el2, expr, state) => {
511
+ let holder = document.createTextNode(""), clauses = [parseExpr(el2, expr, ":if", state)], els = [el2], cur = el2;
512
512
  while (cur = el2.nextElementSibling) {
513
513
  if (cur.hasAttribute(":else")) {
514
514
  cur.removeAttribute(":else");
515
515
  if (expr = cur.getAttribute(":if")) {
516
516
  cur.removeAttribute(":if"), cur.remove();
517
517
  els.push(cur);
518
- clauses.push(parseExpr(expr, ":else :if", state));
518
+ clauses.push(parseExpr(el2, expr, ":else :if", state));
519
519
  } else {
520
520
  cur.remove();
521
521
  els.push(cur);
@@ -529,11 +529,11 @@ directives[":if"] = (el2, expr, state) => {
529
529
  b((i2 = idx.value) => els[i2] != cur && ((cur[_each] || cur).replaceWith(cur = els[i2] || holder), sprae(cur, state)));
530
530
  return -els.length;
531
531
  };
532
- directives[":each"] = (tpl, expr, state) => {
532
+ directives["each"] = (tpl, expr, state) => {
533
533
  let each = parseForExpression(expr);
534
534
  if (!each)
535
- return exprError(new Error(), expr);
536
- const getItems = parseExpr(each.items, ":each", state);
535
+ return exprError(new Error(), tpl, expr);
536
+ const getItems = parseExpr(tpl, each.items, ":each", state);
537
537
  const holder = tpl[_each] = document.createTextNode("");
538
538
  tpl.replaceWith(holder);
539
539
  const items = w(() => {
@@ -546,7 +546,7 @@ directives[":each"] = (tpl, expr, state) => {
546
546
  return Object.entries(list);
547
547
  if (Array.isArray(list))
548
548
  return list.map((item, i2) => [i2 + 1, item]);
549
- exprError(Error("Bad list value"), each.items, ":each", list);
549
+ exprError(Error("Bad list value"), tpl, each.items, ":each", list);
550
550
  });
551
551
  const scopes = /* @__PURE__ */ new WeakMap();
552
552
  const itemEls = /* @__PURE__ */ new WeakMap();
@@ -599,13 +599,13 @@ function parseForExpression(expression) {
599
599
  }
600
600
  return res;
601
601
  }
602
- directives[":id"] = (el2, expr, state) => {
603
- let evaluate = parseExpr(expr, ":id", state);
602
+ directives["id"] = (el2, expr, state) => {
603
+ let evaluate = parseExpr(el2, expr, ":id", state);
604
604
  const update = (v2) => el2.id = v2 || v2 === 0 ? v2 : "";
605
605
  b(() => update(evaluate(state)));
606
606
  };
607
- directives[":"] = (el2, expr, state) => {
608
- let evaluate = parseExpr(expr, ":", state);
607
+ directives[""] = (el2, expr, state) => {
608
+ let evaluate = parseExpr(el2, expr, ":", state);
609
609
  const update = (value) => {
610
610
  if (!value)
611
611
  return;
@@ -614,15 +614,15 @@ directives[":"] = (el2, expr, state) => {
614
614
  };
615
615
  b(() => update(evaluate(state)));
616
616
  };
617
- directives[":text"] = (el2, expr, state) => {
618
- let evaluate = parseExpr(expr, ":text", state);
617
+ directives["text"] = (el2, expr, state) => {
618
+ let evaluate = parseExpr(el2, expr, ":text", state);
619
619
  const update = (value) => {
620
620
  el2.textContent = value == null ? "" : value;
621
621
  };
622
622
  b(() => update(evaluate(state)));
623
623
  };
624
- directives[":value"] = (el2, expr, state) => {
625
- let evaluate = parseExpr(expr, ":in", state);
624
+ directives["value"] = (el2, expr, state) => {
625
+ let evaluate = parseExpr(el2, expr, ":in", state);
626
626
  let [get, set] = input(el2);
627
627
  const update = (value) => {
628
628
  prop(el2, "value", value);
@@ -630,28 +630,41 @@ directives[":value"] = (el2, expr, state) => {
630
630
  };
631
631
  b(() => update(evaluate(state)));
632
632
  };
633
- directives[":on"] = (el2, expr, state) => {
634
- let evaluate = parseExpr(expr, ":on", state);
635
- let listeners = w(() => evaluate(state));
636
- let prevListeners;
637
- b((values = listeners.value) => {
638
- for (let evt in prevListeners)
639
- el2.removeEventListener(evt, prevListeners[evt]);
640
- prevListeners = values;
641
- for (let evt in prevListeners)
642
- el2.addEventListener(evt, prevListeners[evt]);
633
+ directives["on"] = (el2, expr, state) => {
634
+ let evaluate = parseExpr(el2, expr, ":on", state);
635
+ let listeners = {};
636
+ b(() => {
637
+ for (let evt in listeners)
638
+ el2.removeEventListener(evt, listeners[evt]);
639
+ listeners = evaluate(state);
640
+ for (let evt in listeners) {
641
+ const evts = evt.split("-");
642
+ if (evts.length === 1)
643
+ el2.addEventListener(evt, listeners[evt]);
644
+ else {
645
+ const nextEvt = (fn, cur = 0) => {
646
+ el2.addEventListener(evts[cur], listeners[evt] = (e2) => {
647
+ fn = fn(e2);
648
+ el2.removeEventListener(evts[cur], listeners[evt]);
649
+ nextEvt(fn, (cur + 1) % evts.length);
650
+ });
651
+ };
652
+ nextEvt(listeners[evt]);
653
+ }
654
+ }
655
+ ;
643
656
  });
644
657
  };
645
- directives[":data"] = (el2, expr, state) => {
646
- let evaluate = parseExpr(expr, ":data", state);
658
+ directives["data"] = (el2, expr, state) => {
659
+ let evaluate = parseExpr(el2, expr, ":data", state);
647
660
  const value = w(() => evaluate(state));
648
661
  b((v2 = value.value) => {
649
662
  for (let key in v2)
650
663
  el2.dataset[key] = v2[key];
651
664
  });
652
665
  };
653
- directives[":aria"] = (el2, expr, state) => {
654
- let evaluate = parseExpr(expr, ":aria", state);
666
+ directives["aria"] = (el2, expr, state) => {
667
+ let evaluate = parseExpr(el2, expr, ":aria", state);
655
668
  const update = (value) => {
656
669
  for (let key in value)
657
670
  prop(el2, "aria" + key[0].toUpperCase() + key.slice(1), value[key] == null ? null : value[key] + "");
@@ -659,33 +672,33 @@ directives[":aria"] = (el2, expr, state) => {
659
672
  b(() => update(evaluate(state)));
660
673
  };
661
674
  var evaluatorMemo = {};
662
- function parseExpr(expression, dir, scope) {
675
+ function parseExpr(el2, expression, dir, scope) {
663
676
  if (evaluatorMemo[expression])
664
677
  return evaluatorMemo[expression];
665
678
  let rightSideSafeExpression = /^[\n\s]*if.*\(.*\)/.test(expression) || /^(let|const)\s/.test(expression) ? `(() => { ${expression} })()` : expression;
666
679
  let evaluate;
667
680
  try {
668
- evaluate = new Function(`let result; with (arguments[0]) { result = (${rightSideSafeExpression}) }; return result;`);
681
+ evaluate = new Function(`let result; with (arguments[0]) { result = (${rightSideSafeExpression}) }; return result;`).bind(el2);
669
682
  } catch (e2) {
670
- return exprError(e2, expression, dir, scope);
683
+ return exprError(e2, el2, expression, dir, scope);
671
684
  }
672
685
  return evaluatorMemo[expression] = (state) => {
673
686
  let result;
674
687
  try {
675
688
  result = evaluate(state);
676
689
  } catch (e2) {
677
- return exprError(e2, expression, dir, scope);
690
+ return exprError(e2, el2, expression, dir, scope);
678
691
  }
679
692
  return result;
680
693
  };
681
694
  }
682
- function exprError(error, expression, dir, scope) {
683
- Object.assign(error, { expression });
695
+ function exprError(error, element, expression, dir, scope) {
696
+ Object.assign(error, { element, expression });
684
697
  console.warn(`\u2234sprae: ${error.message}
685
698
 
686
699
  ${dir}=${expression ? `"${expression}"
687
700
 
688
- ` : ""}`, scope);
701
+ ` : ""}`, element, scope);
689
702
  setTimeout(() => {
690
703
  throw error;
691
704
  }, 0);
@@ -701,28 +714,25 @@ function sprae(container, values) {
701
714
  values ||= {};
702
715
  const state = SignalStruct(values);
703
716
  const init = (el2) => {
704
- let dir, attr, res;
705
717
  if (el2.attributes) {
706
- for (let name in directives) {
707
- if (attr = el2.attributes[name]) {
708
- dir = directives[name];
709
- el2.removeAttribute(name);
710
- if ((res = dir(el2, attr.value, state)) <= 0)
711
- return res;
712
- }
713
- }
714
718
  for (let i2 = 0; i2 < el2.attributes.length; ) {
715
- let attr2 = el2.attributes[i2];
716
- if (attr2.name[0] === ":") {
717
- el2.removeAttribute(attr2.name);
718
- if ((res = directives_default(el2, attr2.value, state, attr2.name.slice(1))) <= 0)
719
- return res;
720
- } else
719
+ let attr = el2.attributes[i2];
720
+ if (attr.name[0] !== ":") {
721
721
  i2++;
722
+ continue;
723
+ }
724
+ el2.removeAttribute(attr.name);
725
+ let expr = attr.value;
726
+ let attrNames = attr.name.slice(1).split(":");
727
+ for (let attrName of attrNames) {
728
+ let dir = directives[attrName] || directives_default, res;
729
+ if ((res = dir(el2, expr, state, attrName)) <= 0)
730
+ return res;
731
+ }
722
732
  }
723
733
  }
724
734
  for (let i2 = 0, child; child = el2.children[i2]; i2++) {
725
- res = init(child) || 0;
735
+ let res = init(child) || 0;
726
736
  i2 += res;
727
737
  }
728
738
  };
package/sprae.min.js CHANGED
@@ -1 +1 @@
1
- function t(){throw new Error("Cycle detected")}function e(){if(n>1)n--;else{for(var t,e=!1;void 0!==r;){var i=r;for(r=void 0,o++;void 0!==i;){var s=i.o;if(i.o=void 0,i.f&=-3,!(8&i.f)&&l(i))try{i.c()}catch(i){e||(t=i,e=!0)}i=s}}if(o=0,n--,e)throw t}}var i=void 0,r=void 0,n=0,o=0,s=0;function a(t){if(void 0!==i){var e=t.n;if(void 0===e||e.t!==i)return i.s=e={i:0,S:t,p:void 0,n:i.s,t:i,e:void 0,x:void 0,r:e},t.n=e,32&i.f&&t.S(e),e;if(-1===e.i)return e.i=0,void 0!==e.p&&(e.p.n=e.n,void 0!==e.n&&(e.n.p=e.p),e.p=void 0,e.n=i.s,i.s.p=e,i.s=e),e}}function f(t){this.v=t,this.i=0,this.n=void 0,this.t=void 0}function u(t){return new f(t)}function l(t){for(var e=t.s;void 0!==e;e=e.n)if(e.S.i!==e.i||!e.S.h()||e.S.i!==e.i)return!0;return!1}function h(t){for(var e=t.s;void 0!==e;e=e.n){var i=e.S.n;void 0!==i&&(e.r=i),e.S.n=e,e.i=-1}}function v(t){for(var e=t.s,i=void 0;void 0!==e;){var r=e.n;-1===e.i?(e.S.U(e),e.n=void 0):(void 0!==i&&(i.p=e),e.p=void 0,e.n=i,i=e),e.S.n=e.r,void 0!==e.r&&(e.r=void 0),e=r}t.s=i}function c(t){f.call(this,void 0),this.x=t,this.s=void 0,this.g=s-1,this.f=4}function d(t){return new c(t)}function p(t){var r=t.u;if(t.u=void 0,"function"==typeof r){n++;var o=i;i=void 0;try{r()}catch(e){throw t.f&=-2,t.f|=8,b(t),e}finally{i=o,e()}}}function b(t){for(var e=t.s;void 0!==e;e=e.n)e.S.U(e);t.x=void 0,t.s=void 0,p(t)}function y(t){if(i!==this)throw new Error("Out-of-order effect");v(this),i=t,this.f&=-2,8&this.f&&b(this),e()}function m(t){this.x=t,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32}function g(t){var e=new m(t);return e.c(),e.d.bind(e)}f.prototype.h=function(){return!0},f.prototype.S=function(t){this.t!==t&&void 0===t.e&&(t.x=this.t,void 0!==this.t&&(this.t.e=t),this.t=t)},f.prototype.U=function(t){var e=t.e,i=t.x;void 0!==e&&(e.x=i,t.e=void 0),void 0!==i&&(i.e=e,t.x=void 0),t===this.t&&(this.t=i)},f.prototype.subscribe=function(t){var e=this;return g((function(){var i=e.value,r=32&this.f;this.f&=-33;try{t(i)}finally{this.f|=r}}))},f.prototype.valueOf=function(){return this.value},f.prototype.toString=function(){return this.value+""},f.prototype.peek=function(){return this.v},Object.defineProperty(f.prototype,"value",{get:function(){var t=a(this);return void 0!==t&&(t.i=this.i),this.v},set:function(i){if(i!==this.v){o>100&&t(),this.v=i,this.i++,s++,n++;try{for(var r=this.t;void 0!==r;r=r.x)r.t.N()}finally{e()}}}}),(c.prototype=new f).h=function(){if(this.f&=-3,1&this.f)return!1;if(32==(36&this.f))return!0;if(this.f&=-5,this.g===s)return!0;if(this.g=s,this.f|=1,this.i>0&&!l(this))return this.f&=-2,!0;var t=i;try{h(this),i=this;var e=this.x();(16&this.f||this.v!==e||0===this.i)&&(this.v=e,this.f&=-17,this.i++)}catch(t){this.v=t,this.f|=16,this.i++}return i=t,v(this),this.f&=-2,!0},c.prototype.S=function(t){if(void 0===this.t){this.f|=36;for(var e=this.s;void 0!==e;e=e.n)e.S.S(e)}f.prototype.S.call(this,t)},c.prototype.U=function(t){if(f.prototype.U.call(this,t),void 0===this.t){this.f&=-33;for(var e=this.s;void 0!==e;e=e.n)e.S.U(e)}},c.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(var t=this.t;void 0!==t;t=t.x)t.t.N()}},c.prototype.peek=function(){if(this.h()||t(),16&this.f)throw this.v;return this.v},Object.defineProperty(c.prototype,"value",{get:function(){1&this.f&&t();var e=a(this);if(this.h(),void 0!==e&&(e.i=this.i),16&this.f)throw this.v;return this.v}}),m.prototype.c=function(){var t=this.S();try{8&this.f||void 0===this.x||(this.u=this.x())}finally{t()}},m.prototype.S=function(){1&this.f&&t(),this.f|=1,this.f&=-9,p(this),h(this),n++;var e=i;return i=this,y.bind(this,e)},m.prototype.N=function(){2&this.f||(this.f|=2,this.o=r,r=this)},m.prototype.d=function(){this.f|=8,1&this.f||b(this)},Symbol.observable||=Symbol("observable");var S=new FinalizationRegistry((t=>t.call?.())),x=t=>t&&t.peek,w=Symbol("signal-struct");function A(t){if((e=t)&&e[w])return t;var e;let i,r;if(j(t)){i={},r={};for(let e in t)r[e]=O(i,e,t[e]);return Object.defineProperty(i,w,{configurable:!1,enumerable:!1,value:!0}),Object.seal(i),i}return Array.isArray(t)?t.map((t=>A(t))):t}function O(t,e,i){let r,n=x(i)?i:j(i)||Array.isArray(i)?u(A(i)):u((r=(o=i)&&!!(o[Symbol.observable]||o[Symbol.asyncIterator]||o.call&&o.set||o.subscribe||o.then))?void 0:i);var o,s,a,f,l,h,v;return r&&(a=t=>n.value=t,(s=i)&&(v=(s[Symbol.observable]?.()||s).subscribe?.(a,f,undefined),h=v&&(()=>v.unsubscribe?.())||s.set&&s.call?.(l,a)||(s.then?.((t=>{!l&&a(t)}),f)||(async t=>{try{for await(t of s){if(l)return;a(t)}}catch(t){}})())&&(t=>l=1),S.register(s,h))),Object.defineProperty(t,e,{get:()=>n.value,set:!x(i)&&j(i)?t=>t?Object.assign(n.value,t):n.value=A(t):t=>n.value=A(t),enumerable:!0,configurable:!1}),n}function j(t){return t&&t.constructor===Object}var k=(t,e,i)=>{e.startsWith("on")&&(e=e.toLowerCase()),t[e]!==i&&(t[e]=i),!1===i||null==i?t.removeAttribute(e):"function"!=typeof i&&t.setAttribute($(e),!0===i?"":"number"==typeof i||"string"==typeof i?i:"class"===e?(Array.isArray(i)?i:Object.entries(i).map((([t,e])=>e?t:""))).filter(Boolean).join(" "):"style"===e?Object.entries(i).map((([t,e])=>`${t}: ${e}`)).join(";"):"")},N=document.createElement("div"),$=t=>{N.dataset[t]="";let e=N.attributes[0].name.slice(5);return delete N.dataset[t],e},E=(t,e,i,r=null)=>{let n,o,s,a=0,f=i.length,u=e.length,{remove:l,same:h,insert:v,replace:c}=E;for(;a<f&&a<u&&h(e[a],i[a]);)a++;for(;a<f&&a<u&&h(i[f-1],e[u-1]);)r=i[(--u,--f)];if(a==u)for(;a<f;)v(r,i[a++],t);else{for(n=e[a];a<f;)s=i[a++],o=n?n.nextSibling:r,h(n,s)?n=o:a<f&&h(i[a],o)?(c(n,s,t),n=o):v(n,s,t);for(;!h(n,r);)o=n.nextSibling,l(n,t),n=o}return i};E.same=(t,e)=>t==e,E.replace=(t,e,i)=>i.replaceChild(e,t),E.insert=(t,e,i)=>i.insertBefore(e,t),E.remove=(t,e)=>e.removeChild(t);var U=E,W={},C={},B={},P=(t,e,i,r)=>{let n=I(e,":"+r,i);g((()=>{return e=n(i),k(t,r,e);var e}))},L={},M=Symbol(":each"),T=Symbol(":ref");L[":with"]=(t,i,r)=>{let o=I(i,"with",r);const s=d((()=>Object.assign({},r,o(r))));let a=q(t,s.value);return g(((t=s.value)=>function(t){if(n>0)return t();n++;try{return t()}finally{e()}}((()=>Object.assign(a,t))))),!1},L[":ref"]=(t,e,i)=>t.hasAttribute(":each")?t[T]=e:(q(t,Object.assign(Object.create(i),{[e]:t})),!1),L[":if"]=(t,e,i)=>{let r=document.createTextNode(""),n=[I(e,":if",i)],o=[t],s=t;for(;(s=t.nextElementSibling)&&s.hasAttribute(":else");)s.removeAttribute(":else"),(e=s.getAttribute(":if"))?(s.removeAttribute(":if"),s.remove(),o.push(s),n.push(I(e,":else :if",i))):(s.remove(),o.push(s),n.push((()=>1)));t.replaceWith(s=r);let a=d((()=>n.findIndex((t=>t(i)))));return g(((t=a.value)=>o[t]!=s&&((s[M]||s).replaceWith(s=o[t]||r),q(s,i)))),-o.length},L[":each"]=(t,e,i)=>{let r=function(t){let e=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,i=t.match(/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/);if(!i)return;let r={};r.items=i[2].trim();let n=i[1].replace(/^\s*\(|\)\s*$/g,"").trim(),o=n.match(e);return o?(r.item=n.replace(e,"").trim(),r.index=o[1].trim()):r.item=n,r}(e);if(!r)return z(new Error,e);const n=I(r.items,":each",i),o=t[M]=document.createTextNode("");t.replaceWith(o);const s=d((()=>{let t=n(i);return t?"number"==typeof t?Array.from({length:t},((t,e)=>[e,e+1])):t.constructor===Object?Object.entries(t):Array.isArray(t)?t.map(((t,e)=>[e+1,t])):void z(Error("Bad list value"),r.items,":each",t):[]})),a=new WeakMap,f=new WeakMap;let u=[];return g(((e=s.value)=>{let n=[],l=[];for(let[o,s]of e){let e=null===(h=s)?C:void 0===h?B:"number"==typeof h||h instanceof Number?W[h]||(W[h]=new Number(h)):"string"==typeof h||h instanceof String?W[h]||(W[h]=new String(h)):"boolean"==typeof h||h instanceof Boolean?W[h]||(W[h]=new Boolean(h)):h,u=f.get(e);if(u||(u=t.cloneNode(!0),f.set(e,u)),n.push(u),!a.has(e)){let n=Object.create(i);n[r.item]=s,r.index&&(n[r.index]=o),t[T]&&(n[t[T]]=u),a.set(e,n)}l.push(a.get(e))}var h;U(o.parentNode,u,n,o),u=n;for(let t=0;t<n.length;t++)q(n[t],l[t])})),-1},L[":id"]=(t,e,i)=>{let r=I(e,":id",i);g((()=>{return e=r(i),t.id=e||0===e?e:"";var e}))},L[":"]=(t,e,i)=>{let r=I(e,":",i);g((()=>(e=>{if(e)for(let i in e)k(t,i,e[i])})(r(i))))},L[":text"]=(t,e,i)=>{let r=I(e,":text",i);g((()=>{return e=r(i),void(t.textContent=null==e?"":e);var e}))},L[":value"]=(t,e,i)=>{let r=I(e,":in",i),[n,o]=(t=>["checkbox"===t.type?()=>t.checked:()=>t.value,"text"===t.type||""===t.type?e=>t.value=null==e?"":e:"checkbox"===t.type?e=>(t.value=e?"on":"",k(t,"checked",e)):"select-one"===t.type?e=>([...t.options].map((t=>t.removeAttribute("selected"))),t.value=e,t.selectedOptions[0]?.setAttribute("selected","")):e=>t.value=e])(t);g((()=>{return e=r(i),k(t,"value",e),void o(e);var e}))},L[":on"]=(t,e,i)=>{let r,n=I(e,":on",i),o=d((()=>n(i)));g(((e=o.value)=>{for(let e in r)t.removeEventListener(e,r[e]);r=e;for(let e in r)t.addEventListener(e,r[e])}))},L[":data"]=(t,e,i)=>{let r=I(e,":data",i);const n=d((()=>r(i)));g(((e=n.value)=>{for(let i in e)t.dataset[i]=e[i]}))},L[":aria"]=(t,e,i)=>{let r=I(e,":aria",i);g((()=>(e=>{for(let i in e)k(t,"aria"+i[0].toUpperCase()+i.slice(1),null==e[i]?null:e[i]+"")})(r(i))))};var F={};function I(t,e,i){if(F[t])return F[t];let r,n=/^[\n\s]*if.*\(.*\)/.test(t)||/^(let|const)\s/.test(t)?`(() => { ${t} })()`:t;try{r=new Function(`let result; with (arguments[0]) { result = (${n}) }; return result;`)}catch(r){return z(r,t,e,i)}return F[t]=n=>{let o;try{o=r(n)}catch(r){return z(r,t,e,i)}return o}}function z(t,e,i,r){Object.assign(t,{expression:e}),console.warn(`∴sprae: ${t.message}\n\n${i}=${e?`"${e}"\n\n`:""}`,r),setTimeout((()=>{throw t}),0)}var R=new WeakMap;function q(t,e){if(!t.children)return;if(R.has(t))return R.get(t);e||={};const i=A(e),r=t=>{let e,n,o;if(t.attributes){for(let r in L)if((n=t.attributes[r])&&(e=L[r],t.removeAttribute(r),(o=e(t,n.value,i))<=0))return o;for(let e=0;e<t.attributes.length;){let r=t.attributes[e];if(":"===r.name[0]){if(t.removeAttribute(r.name),(o=P(t,r.value,i,r.name.slice(1)))<=0)return o}else e++}}for(let e,i=0;e=t.children[i];i++)o=r(e)||0,i+=o};return r(t),R.set(t,i),i}var D=q;export{D as default};
1
+ function t(){throw new Error("Cycle detected")}function e(){if(n>1)n--;else{for(var t,e=!1;void 0!==r;){var i=r;for(r=void 0,o++;void 0!==i;){var s=i.o;if(i.o=void 0,i.f&=-3,!(8&i.f)&&l(i))try{i.c()}catch(i){e||(t=i,e=!0)}i=s}}if(o=0,n--,e)throw t}}var i=void 0,r=void 0,n=0,o=0,s=0;function a(t){if(void 0!==i){var e=t.n;if(void 0===e||e.t!==i)return i.s=e={i:0,S:t,p:void 0,n:i.s,t:i,e:void 0,x:void 0,r:e},t.n=e,32&i.f&&t.S(e),e;if(-1===e.i)return e.i=0,void 0!==e.p&&(e.p.n=e.n,void 0!==e.n&&(e.n.p=e.p),e.p=void 0,e.n=i.s,i.s.p=e,i.s=e),e}}function f(t){this.v=t,this.i=0,this.n=void 0,this.t=void 0}function u(t){return new f(t)}function l(t){for(var e=t.s;void 0!==e;e=e.n)if(e.S.i!==e.i||!e.S.h()||e.S.i!==e.i)return!0;return!1}function h(t){for(var e=t.s;void 0!==e;e=e.n){var i=e.S.n;void 0!==i&&(e.r=i),e.S.n=e,e.i=-1}}function c(t){for(var e=t.s,i=void 0;void 0!==e;){var r=e.n;-1===e.i?(e.S.U(e),e.n=void 0):(void 0!==i&&(i.p=e),e.p=void 0,e.n=i,i=e),e.S.n=e.r,void 0!==e.r&&(e.r=void 0),e=r}t.s=i}function v(t){f.call(this,void 0),this.x=t,this.s=void 0,this.g=s-1,this.f=4}function d(t){return new v(t)}function p(t){var r=t.u;if(t.u=void 0,"function"==typeof r){n++;var o=i;i=void 0;try{r()}catch(e){throw t.f&=-2,t.f|=8,y(t),e}finally{i=o,e()}}}function y(t){for(var e=t.s;void 0!==e;e=e.n)e.S.U(e);t.x=void 0,t.s=void 0,p(t)}function b(t){if(i!==this)throw new Error("Out-of-order effect");c(this),i=t,this.f&=-2,8&this.f&&y(this),e()}function m(t){this.x=t,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32}function g(t){var e=new m(t);return e.c(),e.d.bind(e)}f.prototype.h=function(){return!0},f.prototype.S=function(t){this.t!==t&&void 0===t.e&&(t.x=this.t,void 0!==this.t&&(this.t.e=t),this.t=t)},f.prototype.U=function(t){var e=t.e,i=t.x;void 0!==e&&(e.x=i,t.e=void 0),void 0!==i&&(i.e=e,t.x=void 0),t===this.t&&(this.t=i)},f.prototype.subscribe=function(t){var e=this;return g((function(){var i=e.value,r=32&this.f;this.f&=-33;try{t(i)}finally{this.f|=r}}))},f.prototype.valueOf=function(){return this.value},f.prototype.toString=function(){return this.value+""},f.prototype.peek=function(){return this.v},Object.defineProperty(f.prototype,"value",{get:function(){var t=a(this);return void 0!==t&&(t.i=this.i),this.v},set:function(i){if(i!==this.v){o>100&&t(),this.v=i,this.i++,s++,n++;try{for(var r=this.t;void 0!==r;r=r.x)r.t.N()}finally{e()}}}}),(v.prototype=new f).h=function(){if(this.f&=-3,1&this.f)return!1;if(32==(36&this.f))return!0;if(this.f&=-5,this.g===s)return!0;if(this.g=s,this.f|=1,this.i>0&&!l(this))return this.f&=-2,!0;var t=i;try{h(this),i=this;var e=this.x();(16&this.f||this.v!==e||0===this.i)&&(this.v=e,this.f&=-17,this.i++)}catch(t){this.v=t,this.f|=16,this.i++}return i=t,c(this),this.f&=-2,!0},v.prototype.S=function(t){if(void 0===this.t){this.f|=36;for(var e=this.s;void 0!==e;e=e.n)e.S.S(e)}f.prototype.S.call(this,t)},v.prototype.U=function(t){if(f.prototype.U.call(this,t),void 0===this.t){this.f&=-33;for(var e=this.s;void 0!==e;e=e.n)e.S.U(e)}},v.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(var t=this.t;void 0!==t;t=t.x)t.t.N()}},v.prototype.peek=function(){if(this.h()||t(),16&this.f)throw this.v;return this.v},Object.defineProperty(v.prototype,"value",{get:function(){1&this.f&&t();var e=a(this);if(this.h(),void 0!==e&&(e.i=this.i),16&this.f)throw this.v;return this.v}}),m.prototype.c=function(){var t=this.S();try{8&this.f||void 0===this.x||(this.u=this.x())}finally{t()}},m.prototype.S=function(){1&this.f&&t(),this.f|=1,this.f&=-9,p(this),h(this),n++;var e=i;return i=this,b.bind(this,e)},m.prototype.N=function(){2&this.f||(this.f|=2,this.o=r,r=this)},m.prototype.d=function(){this.f|=8,1&this.f||y(this)},Symbol.observable||=Symbol("observable");var S=new FinalizationRegistry((t=>t.call?.())),x=t=>t&&t.peek,w=Symbol("signal-struct");function j(t){if((e=t)&&e[w])return t;var e;let i,r;if(O(t)){i={},r={};for(let e in t)r[e]=A(i,e,t[e]);return Object.defineProperty(i,w,{configurable:!1,enumerable:!1,value:!0}),Object.seal(i),i}return Array.isArray(t)?t.map((t=>j(t))):t}function A(t,e,i){let r,n=x(i)?i:O(i)||Array.isArray(i)?u(j(i)):u((r=(o=i)&&!!(o[Symbol.observable]||o[Symbol.asyncIterator]||o.call&&o.set||o.subscribe||o.then))?void 0:i);var o,s,a,f,l,h,c;return r&&(a=t=>n.value=t,(s=i)&&(c=(s[Symbol.observable]?.()||s).subscribe?.(a,f,undefined),h=c&&(()=>c.unsubscribe?.())||s.set&&s.call?.(l,a)||(s.then?.((t=>{!l&&a(t)}),f)||(async t=>{try{for await(t of s){if(l)return;a(t)}}catch(t){}})())&&(t=>l=1),S.register(s,h))),Object.defineProperty(t,e,{get:()=>n.value,set:!x(i)&&O(i)?t=>t?Object.assign(n.value,t):n.value=j(t):t=>n.value=j(t),enumerable:!0,configurable:!1}),n}function O(t){return t&&t.constructor===Object}var $=(t,e,i)=>{e.startsWith("on")&&(e=e.toLowerCase()),t[e]!==i&&(t[e]=i),null==i||!1===i?t.removeAttribute(e):"function"!=typeof i&&t.setAttribute(e.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(t=>"-"+t.toLowerCase())),!0===i?"":"number"==typeof i||"string"==typeof i?i:"class"===e?(Array.isArray(i)?i:Object.entries(i).map((([t,e])=>e?t:""))).filter(Boolean).join(" "):"style"===e?Object.entries(i).map((([t,e])=>`${t}: ${e}`)).join(";"):"")};document.createElement("div");var E=(t,e,i,r=null)=>{let n,o,s,a=0,f=i.length,u=e.length,{remove:l,same:h,insert:c,replace:v}=E;for(;a<f&&a<u&&h(e[a],i[a]);)a++;for(;a<f&&a<u&&h(i[f-1],e[u-1]);)r=i[(--u,--f)];if(a==u)for(;a<f;)c(r,i[a++],t);else{for(n=e[a];a<f;)s=i[a++],o=n?n.nextSibling:r,h(n,s)?n=o:a<f&&h(i[a],o)?(v(n,s,t),n=o):c(n,s,t);for(;!h(n,r);)o=n.nextSibling,l(n,t),n=o}return i};E.same=(t,e)=>t==e,E.replace=(t,e,i)=>i.replaceChild(e,t),E.insert=(t,e,i)=>i.insertBefore(e,t),E.remove=(t,e)=>e.removeChild(t);var k=E,N={},W={},C={},U=(t,e,i,r)=>{if(r.startsWith("on"))return L.on(t,`{"${r.split("-").map((t=>t.startsWith("on")?t.slice(2):t)).join("-")}": ${e}}`,i);let n=M(t,e,":"+r,i);g((()=>{return e=n(i),$(t,r,e);var e}))},L={},B=Symbol(":each"),P=Symbol(":ref");L.with=(t,i,r)=>{let o=M(t,i,"with",r);const s=d((()=>Object.assign({},r,o(r))));let a=I(t,s.value);return g(((t=s.value)=>function(t){if(n>0)return t();n++;try{return t()}finally{e()}}((()=>Object.assign(a,t))))),!1},L.ref=(t,e,i)=>t.hasAttribute(":each")?t[P]=e:(I(t,Object.assign(Object.create(i),{[e]:t})),!1),L.if=(t,e,i)=>{let r=document.createTextNode(""),n=[M(t,e,":if",i)],o=[t],s=t;for(;(s=t.nextElementSibling)&&s.hasAttribute(":else");)s.removeAttribute(":else"),(e=s.getAttribute(":if"))?(s.removeAttribute(":if"),s.remove(),o.push(s),n.push(M(t,e,":else :if",i))):(s.remove(),o.push(s),n.push((()=>1)));t.replaceWith(s=r);let a=d((()=>n.findIndex((t=>t(i)))));return g(((t=a.value)=>o[t]!=s&&((s[B]||s).replaceWith(s=o[t]||r),I(s,i)))),-o.length},L.each=(t,e,i)=>{let r=function(t){let e=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,i=t.match(/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/);if(!i)return;let r={};r.items=i[2].trim();let n=i[1].replace(/^\s*\(|\)\s*$/g,"").trim(),o=n.match(e);return o?(r.item=n.replace(e,"").trim(),r.index=o[1].trim()):r.item=n,r}(e);if(!r)return T(new Error,t,e);const n=M(t,r.items,":each",i),o=t[B]=document.createTextNode("");t.replaceWith(o);const s=d((()=>{let e=n(i);return e?"number"==typeof e?Array.from({length:e},((t,e)=>[e,e+1])):e.constructor===Object?Object.entries(e):Array.isArray(e)?e.map(((t,e)=>[e+1,t])):void T(Error("Bad list value"),t,r.items,":each",e):[]})),a=new WeakMap,f=new WeakMap;let u=[];return g(((e=s.value)=>{let n=[],l=[];for(let[o,s]of e){let e=null===(h=s)?W:void 0===h?C:"number"==typeof h||h instanceof Number?N[h]||(N[h]=new Number(h)):"string"==typeof h||h instanceof String?N[h]||(N[h]=new String(h)):"boolean"==typeof h||h instanceof Boolean?N[h]||(N[h]=new Boolean(h)):h,u=f.get(e);if(u||(u=t.cloneNode(!0),f.set(e,u)),n.push(u),!a.has(e)){let n=Object.create(i);n[r.item]=s,r.index&&(n[r.index]=o),t[P]&&(n[t[P]]=u),a.set(e,n)}l.push(a.get(e))}var h;k(o.parentNode,u,n,o),u=n;for(let t=0;t<n.length;t++)I(n[t],l[t])})),-1},L.id=(t,e,i)=>{let r=M(t,e,":id",i);g((()=>{return e=r(i),t.id=e||0===e?e:"";var e}))},L[""]=(t,e,i)=>{let r=M(t,e,":",i);g((()=>(e=>{if(e)for(let i in e)$(t,i,e[i])})(r(i))))},L.text=(t,e,i)=>{let r=M(t,e,":text",i);g((()=>{return e=r(i),void(t.textContent=null==e?"":e);var e}))},L.value=(t,e,i)=>{let r=M(t,e,":in",i),[n,o]=(t=>["checkbox"===t.type?()=>t.checked:()=>t.value,"text"===t.type||""===t.type?e=>t.value=null==e?"":e:"checkbox"===t.type?e=>(t.value=e?"on":"",$(t,"checked",e)):"select-one"===t.type?e=>([...t.options].map((t=>t.removeAttribute("selected"))),t.value=e,t.selectedOptions[0]?.setAttribute("selected","")):e=>t.value=e])(t);g((()=>{return e=r(i),$(t,"value",e),void o(e);var e}))},L.on=(t,e,i)=>{let r=M(t,e,":on",i),n={};g((()=>{for(let e in n)t.removeEventListener(e,n[e]);n=r(i);for(let e in n){const i=e.split("-");if(1===i.length)t.addEventListener(e,n[e]);else{const r=(o,s=0)=>{t.addEventListener(i[s],n[e]=a=>{o=o(a),t.removeEventListener(i[s],n[e]),r(o,(s+1)%i.length)})};r(n[e])}}}))},L.data=(t,e,i)=>{let r=M(t,e,":data",i);const n=d((()=>r(i)));g(((e=n.value)=>{for(let i in e)t.dataset[i]=e[i]}))},L.aria=(t,e,i)=>{let r=M(t,e,":aria",i);g((()=>(e=>{for(let i in e)$(t,"aria"+i[0].toUpperCase()+i.slice(1),null==e[i]?null:e[i]+"")})(r(i))))};var D={};function M(t,e,i,r){if(D[e])return D[e];let n,o=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(() => { ${e} })()`:e;try{n=new Function(`let result; with (arguments[0]) { result = (${o}) }; return result;`).bind(t)}catch(n){return T(n,t,e,i,r)}return D[e]=o=>{let s;try{s=n(o)}catch(n){return T(n,t,e,i,r)}return s}}function T(t,e,i,r,n){Object.assign(t,{element:e,expression:i}),console.warn(`∴sprae: ${t.message}\n\n${r}=${i?`"${i}"\n\n`:""}`,e,n),setTimeout((()=>{throw t}),0)}var F=new WeakMap;function I(t,e){if(!t.children)return;if(F.has(t))return F.get(t);e||={};const i=j(e),r=t=>{if(t.attributes)for(let e=0;e<t.attributes.length;){let r=t.attributes[e];if(":"!==r.name[0]){e++;continue}t.removeAttribute(r.name);let n=r.value,o=r.name.slice(1).split(":");for(let e of o){let r;if((r=(L[e]||U)(t,n,i,e))<=0)return r}}for(let e,i=0;e=t.children[i];i++)i+=r(e)||0};return r(t),F.set(t,i),i}var z=I;export{z as default};
package/src/core.js CHANGED
@@ -13,30 +13,22 @@ export default function sprae(container, values) {
13
13
 
14
14
  // init directives on element
15
15
  const init = (el) => {
16
- let dir, attr, res
17
-
18
16
  if (el.attributes) {
19
- // directives must be initialized in order
20
- for (let name in directives) {
21
- if (attr = el.attributes[name]) {
22
- dir = directives[name]
23
- el.removeAttribute(name)
24
- if ((res = dir(el, attr.value, state)) <= 0) return res
25
- }
26
- }
27
-
28
17
  for (let i = 0; i < el.attributes.length;) {
29
18
  let attr = el.attributes[i]
30
- if (attr.name[0]===':') {
31
- el.removeAttribute(attr.name)
32
- if ((res = defaultDirective(el, attr.value, state, attr.name.slice(1))) <= 0) return res
19
+ if (attr.name[0] !== ':') {i++; continue}
20
+ el.removeAttribute(attr.name)
21
+ let expr = attr.value
22
+ let attrNames = attr.name.slice(1).split(':')
23
+ for (let attrName of attrNames) {
24
+ let dir = directives[attrName] || defaultDirective, res
25
+ if ((res = dir(el, expr, state, attrName)) <= 0) return res
33
26
  }
34
- else i++
35
27
  }
36
28
  }
37
29
 
38
30
  for (let i = 0, child; child = el.children[i]; i++) {
39
- res = init(child) || 0 // reduce number of removed elements
31
+ let res = init(child) || 0 // reduce number of removed elements
40
32
  i += res
41
33
  }
42
34
  }
package/src/directives.js CHANGED
@@ -9,7 +9,12 @@ import p from 'primitive-pool'
9
9
 
10
10
  // any-prop directives
11
11
  export default (el, expr, state, name) => {
12
- let evaluate = parseExpr(expr, ':'+name, state)
12
+ if (name.startsWith('on')) {
13
+ // :ona:onb=x -> :on={a:x,b:x}
14
+ // :ona-onb=x -> :on={aB:x}
15
+ return directives.on(el, `{"${name.split('-').map(n=>n.startsWith('on')?n.slice(2):n).join('-')}": ${expr}}`, state)
16
+ }
17
+ let evaluate = parseExpr(el, expr, ':'+name, state)
13
18
  const update = value => prop(el, name, value)
14
19
  effect(() => update(evaluate(state)))
15
20
  }
@@ -19,8 +24,8 @@ export const directives = {}
19
24
 
20
25
  const _each = Symbol(':each'), _ref = Symbol(':ref')
21
26
 
22
- directives[':with'] = (el, expr, rootState) => {
23
- let evaluate = parseExpr(expr, 'with', rootState)
27
+ directives['with'] = (el, expr, rootState) => {
28
+ let evaluate = parseExpr(el, expr, 'with', rootState)
24
29
 
25
30
  // Instead of extending signals (which is a bit hard since signal-struct internals is not uniform)
26
31
  // we bind updating
@@ -30,7 +35,7 @@ directives[':with'] = (el, expr, rootState) => {
30
35
  return false // don't continue attrs init
31
36
  }
32
37
 
33
- directives[':ref'] = (el, expr, state) => {
38
+ directives['ref'] = (el, expr, state) => {
34
39
  // make sure :ref is initialized after :each
35
40
  if (el.hasAttribute(':each')) return el[_ref] = expr;
36
41
 
@@ -38,9 +43,9 @@ directives[':ref'] = (el, expr, state) => {
38
43
  return false // don't continue attrs init
39
44
  }
40
45
 
41
- directives[':if'] = (el, expr, state) => {
46
+ directives['if'] = (el, expr, state) => {
42
47
  let holder = document.createTextNode(''),
43
- clauses = [parseExpr(expr, ':if', state)],
48
+ clauses = [parseExpr(el, expr, ':if', state)],
44
49
  els = [el], cur = el
45
50
 
46
51
  while (cur = el.nextElementSibling) {
@@ -48,7 +53,7 @@ directives[':if'] = (el, expr, state) => {
48
53
  cur.removeAttribute(':else');
49
54
  if (expr = cur.getAttribute(':if')) {
50
55
  cur.removeAttribute(':if'), cur.remove();
51
- els.push(cur); clauses.push(parseExpr(expr, ':else :if', state));
56
+ els.push(cur); clauses.push(parseExpr(el, expr, ':else :if', state));
52
57
  }
53
58
  else {
54
59
  cur.remove(); els.push(cur); clauses.push(() => 1);
@@ -66,11 +71,11 @@ directives[':if'] = (el, expr, state) => {
66
71
  return -els.length
67
72
  }
68
73
 
69
- directives[':each'] = (tpl, expr, state) => {
74
+ directives['each'] = (tpl, expr, state) => {
70
75
  let each = parseForExpression(expr);
71
- if (!each) return exprError(new Error, expr);
76
+ if (!each) return exprError(new Error, tpl, expr);
72
77
 
73
- const getItems = parseExpr(each.items, ':each', state);
78
+ const getItems = parseExpr(tpl, each.items, ':each', state);
74
79
 
75
80
  // FIXME: make sure no memory leak here
76
81
  // we need :if to be able to replace holder instead of tpl for :if :each case
@@ -83,7 +88,7 @@ directives[':each'] = (tpl, expr, state) => {
83
88
  if (typeof list === 'number') return Array.from({length: list}, (_, i)=>[i, i+1])
84
89
  if (list.constructor === Object) return Object.entries(list)
85
90
  if (Array.isArray(list)) return list.map((item,i) => [i+1, item])
86
- exprError(Error('Bad list value'), each.items, ':each', list)
91
+ exprError(Error('Bad list value'), tpl, each.items, ':each', list)
87
92
  })
88
93
 
89
94
  // stores scope per data item
@@ -152,14 +157,14 @@ function parseForExpression(expression) {
152
157
  return res
153
158
  }
154
159
 
155
- directives[':id'] = (el, expr, state) => {
156
- let evaluate = parseExpr(expr, ':id', state)
160
+ directives['id'] = (el, expr, state) => {
161
+ let evaluate = parseExpr(el, expr, ':id', state)
157
162
  const update = v => el.id = v || v === 0 ? v : ''
158
163
  effect(()=>update(evaluate(state)))
159
164
  }
160
165
 
161
- directives[':'] = (el, expr, state) => {
162
- let evaluate = parseExpr(expr, ':', state)
166
+ directives[''] = (el, expr, state) => {
167
+ let evaluate = parseExpr(el, expr, ':', state)
163
168
  const update = (value) => {
164
169
  if (!value) return
165
170
  for (let key in value) prop(el, key, value[key]);
@@ -167,8 +172,8 @@ directives[':'] = (el, expr, state) => {
167
172
  effect(()=>update(evaluate(state)))
168
173
  }
169
174
 
170
- directives[':text'] = (el, expr, state) => {
171
- let evaluate = parseExpr(expr, ':text', state)
175
+ directives['text'] = (el, expr, state) => {
176
+ let evaluate = parseExpr(el, expr, ':text', state)
172
177
 
173
178
  const update = (value) => {
174
179
  el.textContent = value == null ? '' : value;
@@ -178,8 +183,8 @@ directives[':text'] = (el, expr, state) => {
178
183
  }
179
184
 
180
185
  // connect expr to element value
181
- directives[':value'] = (el, expr, state) => {
182
- let evaluate = parseExpr(expr, ':in', state)
186
+ directives['value'] = (el, expr, state) => {
187
+ let evaluate = parseExpr(el, expr, ':in', state)
183
188
 
184
189
  let [get, set] = input(el);
185
190
 
@@ -190,27 +195,41 @@ directives[':value'] = (el, expr, state) => {
190
195
  effect(()=>update(evaluate(state)))
191
196
  }
192
197
 
193
- directives[':on'] = (el, expr, state) => {
194
- let evaluate = parseExpr(expr, ':on', state)
195
- let listeners = computed(() => evaluate(state))
196
- let prevListeners
197
- effect((values=listeners.value) => {
198
- for (let evt in prevListeners) el.removeEventListener(evt, prevListeners[evt]);
199
- prevListeners = values;
200
- for (let evt in prevListeners) el.addEventListener(evt, prevListeners[evt]);
198
+ directives['on'] = (el, expr, state) => {
199
+ let evaluate = parseExpr(el, expr, ':on', state)
200
+ let listeners = {}
201
+ effect(() => {
202
+ for (let evt in listeners) el.removeEventListener(evt, listeners[evt]);
203
+
204
+ listeners = evaluate(state);
205
+
206
+ for (let evt in listeners) {
207
+ const evts = evt.split('-')
208
+ if (evts.length===1) el.addEventListener(evt, listeners[evt]);
209
+ else {
210
+ const nextEvt = (fn, cur=0) => {
211
+ el.addEventListener(evts[cur], listeners[evt] = e => {
212
+ fn = fn(e)
213
+ el.removeEventListener(evts[cur], listeners[evt])
214
+ nextEvt(fn, (cur+1)%evts.length)
215
+ })
216
+ }
217
+ nextEvt(listeners[evt])
218
+ }
219
+ };
201
220
  })
202
221
  }
203
222
 
204
- directives[':data'] = (el, expr, state) => {
205
- let evaluate = parseExpr(expr, ':data', state)
223
+ directives['data'] = (el, expr, state) => {
224
+ let evaluate = parseExpr(el, expr, ':data', state)
206
225
  const value = computed(() => evaluate(state))
207
226
  effect((v=value.value) => {
208
227
  for (let key in v) el.dataset[key] = v[key];
209
228
  })
210
229
  }
211
230
 
212
- directives[':aria'] = (el, expr, state) => {
213
- let evaluate = parseExpr(expr, ':aria', state)
231
+ directives['aria'] = (el, expr, state) => {
232
+ let evaluate = parseExpr(el, expr, ':aria', state)
214
233
  const update = (value) => {
215
234
  for (let key in value) prop(el, 'aria'+key[0].toUpperCase()+key.slice(1), value[key] == null ? null : value[key] + '');
216
235
  }
@@ -222,7 +241,7 @@ let evaluatorMemo = {}
222
241
 
223
242
  // borrowed from alpine: https://github.com/alpinejs/alpine/blob/main/packages/alpinejs/src/evaluator.js#L61
224
243
  // it seems to be more robust than subscript
225
- function parseExpr(expression, dir, scope) {
244
+ function parseExpr(el, expression, dir, scope) {
226
245
  if (evaluatorMemo[expression]) return evaluatorMemo[expression]
227
246
 
228
247
  // Some expressions that are useful in Alpine are not valid as the right side of an expression.
@@ -239,22 +258,22 @@ function parseExpr(expression, dir, scope) {
239
258
  // guard static-time eval errors
240
259
  let evaluate
241
260
  try {
242
- evaluate = new Function( `let result; with (arguments[0]) { result = (${rightSideSafeExpression}) }; return result;`)
261
+ evaluate = new Function(`let result; with (arguments[0]) { result = (${rightSideSafeExpression}) }; return result;`).bind(el)
243
262
  } catch ( e ) {
244
- return exprError(e, expression, dir, scope)
263
+ return exprError(e, el, expression, dir, scope)
245
264
  }
246
265
 
247
266
  // guard runtime eval errors
248
267
  return evaluatorMemo[expression] = (state) => {
249
268
  let result
250
269
  try { result = evaluate(state) }
251
- catch (e) { return exprError(e, expression, dir, scope) }
270
+ catch (e) { return exprError(e, el, expression, dir, scope) }
252
271
  return result
253
272
  }
254
273
  }
255
274
 
256
- export function exprError(error, expression, dir, scope) {
257
- Object.assign( error, { expression } )
258
- console.warn(`∴sprae: ${error.message}\n\n${dir}=${ expression ? `"${expression}"\n\n` : '' }`, scope)
275
+ export function exprError(error, element, expression, dir, scope) {
276
+ Object.assign( error, { element, expression } )
277
+ console.warn(`∴sprae: ${error.message}\n\n${dir}=${ expression ? `"${expression}"\n\n` : '' }`, element, scope)
259
278
  setTimeout(() => { throw error }, 0)
260
279
  }
@@ -0,0 +1,20 @@
1
+ // provide DOM env for node tests
2
+ let { JSDOM } = require('jsdom')
3
+
4
+ const { window } = new JSDOM(`<!DOCTYPE html>`, {
5
+ url: "http://localhost/",
6
+ storageQuota: 10000000,
7
+ pretendToBeVisual: true,
8
+ FetchExternalResources: false,
9
+ ProcessExternalResources: false
10
+ })
11
+
12
+ let props = Object.getOwnPropertyNames(window)
13
+
14
+ props.forEach(prop => {
15
+ if (prop in global) return
16
+ Object.defineProperty(global, prop, {
17
+ configurable: true,
18
+ get: () => window[prop]
19
+ })
20
+ })
package/test/test.js CHANGED
@@ -63,12 +63,18 @@ test('common: class', async () => {
63
63
  is(el.outerHTML, `<x class="x"></x><y class="y w"></y><z class="b c"></z>`);
64
64
  })
65
65
 
66
- test('spraed props: base', async () => {
66
+ test('props: base', async () => {
67
67
  let el = h`<input :id="0" :="{for:1, title:2, help:3, type:4, placeholder: 5, value: 6}" :value="7"/>`
68
68
  let params = sprae(el)
69
69
  is(el.outerHTML, `<input id="0" for="1" title="2" help="3" type="4" placeholder="5" value="7">`)
70
70
  })
71
71
 
72
+ test('props: multiprop', async () => {
73
+ let el = h`<input :id:name:for="0" />`
74
+ let params = sprae(el)
75
+ is(el.outerHTML, `<input id="0" name="0" for="0">`)
76
+ })
77
+
72
78
  test('data: base', async () => {
73
79
  let el = h`<input :data="{a:1, fooBar:2}"/>`
74
80
  let params = sprae(el)
@@ -341,6 +347,41 @@ test('on: base', () => {
341
347
  is(log.value, ['click','x','xx']);
342
348
  })
343
349
 
350
+ test('on: multiple events', e => {
351
+ let el = h`<div :onscroll:onclick="e=>log.push(e.type)"></div>`
352
+ let state = sprae(el, {log:[]})
353
+
354
+ el.dispatchEvent(new window.Event('click'));
355
+ is(state.log, ['click'])
356
+ el.dispatchEvent(new window.Event('scroll'));
357
+ is(state.log, ['click','scroll'])
358
+ })
359
+
360
+ test('on: in-out events', e => {
361
+ // let el = document.createElement('x');
362
+ // el.setAttribute(':onmousedown-onmouseup', 'e=>(log.push(e.type),e=>log.push(e.type))')
363
+ let el = h`<x :onmousedown-onmouseup="e=>(log.push(e.type),e=>log.push(e.type))"></x>`
364
+
365
+ let state = sprae(el, {log:[]})
366
+
367
+ el.dispatchEvent(new window.Event('mousedown'));
368
+ is(state.log, ['mousedown'])
369
+ el.dispatchEvent(new window.Event('mouseup'));
370
+ is(state.log, ['mousedown','mouseup'])
371
+ })
372
+
373
+ test('on: chain of events', e => {
374
+ let el = h`<div :onmousedown-onmousemove-onmouseup="e=>(log.push(e.type),e=>(log.push(e.type),e=>log.push(e.type)))"></div>`
375
+ let state = sprae(el, {log:[]})
376
+
377
+ el.dispatchEvent(new window.Event('mousedown'));
378
+ is(state.log, ['mousedown'])
379
+ el.dispatchEvent(new window.Event('mousemove'));
380
+ is(state.log, ['mousedown','mousemove'])
381
+ el.dispatchEvent(new window.Event('mouseup'));
382
+ is(state.log, ['mousedown','mousemove','mouseup'])
383
+ })
384
+
344
385
  test('with: inline', () => {
345
386
  let el = h`<x :with="{foo:'bar', baz}"><y :text="foo + baz"></y></x>`
346
387
  let state = sprae(el, {baz: 'qux'})
@@ -402,14 +443,14 @@ test('ref: base', () => {
402
443
  is(a.outerHTML, `<a>2</a>`)
403
444
  })
404
445
 
405
- test('ref with each', () => {
446
+ test('ref: :ref with :each', () => {
406
447
  let a = h`<y><x :ref="x" :each="item in items" :text="log.push(x), item"/></y>`
407
448
  let state = sprae(a, {log: [], items: [1,2]})
408
449
  is(a.innerHTML, `<x>1</x><x>2</x>`)
409
450
  is(state.log, [...a.children])
410
451
  })
411
452
 
412
- test('reactive values', async () => {
453
+ test('any: reactive values', async () => {
413
454
  let a = new Promise((ok) => setTimeout(() => ok(2), 10))
414
455
 
415
456
  let el = h`<x :text="a">1</x>`
@@ -420,22 +461,29 @@ test('reactive values', async () => {
420
461
  is(el.outerHTML, `<x>2</x>`)
421
462
  })
422
463
 
423
- test('scope directives must come first', async () => {
464
+ test('any: scope refers to current element', async () => {
465
+ let el = h`<x :text="log.push(this)"></x>`
466
+ let state = sprae(el, {log:[]})
467
+ is(state.log, [el])
468
+ })
469
+
470
+ test.skip('any: scope directives must come first', async () => {
471
+ // NOTE: we init attributes in order of definition
424
472
  let a = h`<x :text="y" :with="{y:1}" :ref="x"></x>`
425
473
  sprae(a, {})
426
474
  is(a.outerHTML, `<x>1</x>`)
427
475
  })
428
476
 
429
477
  test.todo('getters', async () => {
430
- // let x = h`<x>
431
- // <h2 :if="doubledCount > 10">YAY!</h2>
432
- // <button :text="count" :on="{click:increment}"/>
433
- // <button :text="doubledCount" :on="{click:increment}"/>
434
- // </x>`
435
- // document.body.appendChild(x)
436
- // sprae(x, {
437
- // count:0,
438
- // get doubledCount(){return this.count * 2},
439
- // increment(){ this.count++ }
440
- // })
478
+ let x = h`<x>
479
+ <h2 :if="doubledCount > 10">YAY!</h2>
480
+ <button :text="count" :on="{click:increment}"/>
481
+ <button :text="doubledCount" :on="{click:increment}"/>
482
+ </x>`
483
+ document.body.appendChild(x)
484
+ let state = sprae(x, {
485
+ count:0,
486
+ get doubledCount(){ console.log(this); return this.count * 2},
487
+ increment(){ this.count++ }
488
+ })
441
489
  })