sprae 2.3.2 → 2.5.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,14 +1,14 @@
1
1
  {
2
2
  "name": "sprae",
3
3
  "description": "DOM microhydration.",
4
- "version": "2.3.2",
4
+ "version": "2.5.0",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
7
7
  "type": "module",
8
8
  "dependencies": {
9
9
  "@preact/signals-core": "^1.2.2",
10
10
  "primitive-pool": "^2.0.0",
11
- "signal-struct": "^1.6.0",
11
+ "signal-struct": "^1.8.0",
12
12
  "swapdom": "^1.1.1"
13
13
  },
14
14
  "devDependencies": {
package/plan.md CHANGED
@@ -29,8 +29,11 @@
29
29
  * [x] Make sure `false` gets serialized, not removes attr
30
30
  * [ ] Sandbox expressions: no global, no "scope" object name, no "arguments"
31
31
  * ~~[x] report usignal problem~~ author is not really interested
32
- * [ ] :oninit
32
+ * [x] `this` doesn't refer to element/scope in event handlers
33
+ * [x] :text="" empty values shouldn't throw
34
+ * [ ] implement :with
33
35
  * [ ] frameworks benchmark
34
36
  * [ ] examples
35
37
  * [x] todomvc
36
- * [ ] wave-edit
38
+ * [ ] wave-edit
39
+ * [ ] `sprae(el, newState)` can update element's state directly (as batch!?) -> must be tested against repeats in directives
package/r&d.md CHANGED
@@ -97,17 +97,41 @@
97
97
 
98
98
  -> possibly we have to just subscribe via mechanism of signals-like deps, and :with just initializes subtree with extended object
99
99
 
100
- ## [x] Get rid of :with? -> let's get rid of until really necessary
101
-
102
- + with is bad JS practice/association
103
- +? is there a bona-fide use case?
104
- + the implementation is heavy/unoptimal: two assign-updates happen: for root, for children
105
- + it is exception blocking streamline implementation of refs
106
- + it shadows data which creates all sorts of nasty debugging effects / states. Isn't it better to keep data/state transparent?
107
- + it even enables transparency of :each scopes, since they inherit root scope
108
- + it's easier to look out for data in one single place (state init), rather than in a bunch of markup locations
109
- +?! can be replaced with sort of `<x :xyz="xyz=...calc"></x>`, no?
110
- -> would need wrapping noname scope access
100
+ ## [ ] :with? -> let's use `:with="{x:1,y:2,z:3}"` for now
101
+
102
+ 1. Get rid of :with
103
+ + with is bad JS practice/association
104
+ +? is there a bona-fide use case?
105
+ + the implementation is heavy/unoptimal: two assign-updates happen: for root, for children
106
+ + it is exception blocking streamline implementation of refs
107
+ + it shadows data which creates all sorts of nasty debugging effects / states. Isn't it better to keep data/state transparent?
108
+ + it even enables transparency of :each scopes, since they inherit root scope
109
+ + it's easier to look out for data in one single place (state init), rather than in a bunch of markup locations
110
+ +?! can be replaced with sort of `<x :xyz="xyz=...calc"></x>`, no?
111
+ -> would need wrapping noname scope access
112
+ + `:with` defines too many concerns:
113
+ * binds root updates -> child updates;
114
+ * binds child updates to root updates (writes);
115
+ * defines local variables
116
+ * aliases root variables
117
+ ? is there value in all of these concerns? It seems we need only local variables, isn't it? Is there a chance partial extension can be required?
118
+ - `:with` can provide situational variables that are useful for props precalculation (since these variables can be reactive.)
119
+ * eg. `<span class="preloader" :with="{str: ''}" :init="setTimeout(() => str+='.', 500)" :text="str" />`
120
+ * that plays role of watch that doesn't require to be outside of local component state.
121
+ - `:with` allows local component state not cluttering global state. There's really no way to define local state that doesn't affect global state.
122
+ 1.1 Slim `:with="{a,b,c}"` - just initializes vars
123
+ - Doesn't give easy init syntax
124
+ + Convevtional and not hard to implement
125
+ 2. Use `:let="x, y=2"`?
126
+ + Doesn't pollute scope but instead cleanly declares local variables
127
+ + Indicates only local initializer, not subscription
128
+ + Liquid has `assign` tag `{% assign myVar = false %}` - it only initializes variable
129
+ + Django `with` performs only alias / complex calc access https://docs.djangoproject.com/en/4.1/ref/templates/builtins/#with - it doesn't sync up global state.
130
+ ? call it `:define="x, y, z"`?
131
+ -> it seems `:with="x=1, y=2"` works well. `:let` has dissonance with js'y let.
132
+ ? how to extend state
133
+ 3. `:with.x="1", :with.y="2"`
134
+ + easier to parse, since init code can be _messy_
111
135
 
112
136
  ## [x] Should we inherit values from `init` in `sprae(el, init)`, instead of creating a snapshot of reactive values in `init`? -> nah, nice idea but too little use. Better create signals struct.
113
137
 
@@ -191,11 +215,24 @@
191
215
  + methods provided in `init` may not have access to scope _yet_.
192
216
  ~- not reliable way to obtain scope via `this.x` - better be explicit as `state.x`
193
217
 
194
- ## [x] :onconnected/:ondisconnected? -> let's move to plugins for now
218
+ ## [x] :onconnected/:ondisconnected? -> nah, just use noname effect or external functionality eg fast-on-load
195
219
 
196
- -> waiting for use-case
220
+ + can be useful for :if, :each handlers, eg to start animation when element appears in DOM.
221
+ - it is not connected-disconnected: it has nothing to do with document: it attaches/detaches from parent.
222
+ - connected-disconnected is too long name
223
+ ? attach-detach?
224
+ ? onmount-onunmount?
225
+ - slows down domdiff
226
+ - can be solved as `<x :if="xxx" :="xxx ? (...) : '">` automatically
227
+
228
+ ## [x] :focus="direct code", :evt="direct code" -> nah, too messy.
229
+
230
+ + makes proper use-case for direct code events
231
+ - doesn't make sense for rective properties inside
232
+ + better fit for special props like `:mount`
233
+ - tons of new special-meaning namespace props
197
234
 
198
- ## [x] Chain of events: often useful to have shared context. -> Try `:onevent-onanotherevent`
235
+ ## [x] Chain of events: often useful to have shared context. -> Try `:onstart..onend`
199
236
 
200
237
  * focus/blur, connected/disconnected, mousedown/mouseup, keydown/keyup, touchstart/touchmove/touchend, dragstart/dragover/dragend, animationstart/animationover/animationend, transitionstart/transitionend
201
238
  ? is there a way to organize handlers for chains of events?
@@ -208,14 +245,35 @@
208
245
  - ? why not :on-focus-blur
209
246
  - ? why not :onfocus-onblur
210
247
  + can be converted from on="{ focusBlur: event }" via dash notation
248
+ - messy error messages
211
249
  + less :on prefixes
212
250
  + has better "flowy" meaning
213
251
  * 2.1 :onfocus-onblur="e => e => {}"
214
252
  + distinctive visually as 1
215
253
  + flowy nature of 2
254
+ - blocks `:onfile-attached` and other dashed events
255
+ - `ona-onb` vs `ona-b-onc` is hard to parse mentally
216
256
  3. :onfocus.onblur="e => e => {}"
217
257
  - looks like a modifier
218
258
  - . can be used as event class onclick.x
259
+ 4. `:onfocus--blur="e => e => {}"`
260
+ + reminds BEM
261
+ - reminds BEM
262
+ 5. `:onfocus..blur="e => e => {}"`
263
+ + reminds range
264
+ + literally means start..end
265
+ + all pros of 2.
266
+ + reminds spray via dots
267
+ - can be confusing if `blur` is event or just property (can that be a property?)
268
+ 6. `:onfocus..onblur="e => e => {}"`
269
+ + all props of 5.
270
+ + more obvious that blur is event.
271
+
272
+ 4. `:onfocus="e => e => {}"` Keep registered pairs of events: just expect focus return blur, etc.
273
+ + Shorter syntax
274
+ + Avoids :onfile-attachment-accepted problem
275
+ - Less verbose and explicit
276
+ - No way to customize sequences, eg. custom events
219
277
 
220
278
  ## [ ] Plugins
221
279
 
@@ -224,9 +282,10 @@
224
282
  - can be solved naturally, unless there's special meaning
225
283
  * @sprae/hcodes: `<x :hcode=""` – provide microformats
226
284
  * @sprae/with
227
- * @sprae/connected
285
+ * @sprae/onconnected
286
+ * @sprae/onvisible?
228
287
 
229
- ## [x] Write any-attributes via :<prop>? -> yep
288
+ ## [x] Write any-attributes via `:<prop>? -> yep`
230
289
 
231
290
  + Since we support attr walking, maybe instead of :on and :prop just allow any attributes?
232
291
  + that would allow event and attr modifiers...
@@ -263,8 +322,10 @@
263
322
  + Provides precisely controlled sandbox
264
323
  - Some limited lang opportunities
265
324
  - need to match many syntax quirks, can be tedious
325
+ ~ can be fine to limit expressions to meaningful default: why Proxy, generators, awaits, global access etc.
266
326
  - Somewhat heavy to bundle
267
- + Scope is easier to provide: no need for signal proxy essentially
327
+ ~ 1-2kb is not super-heavy, besides signal-struct kicks out
328
+ + Scope is easier to provide: no need for signal proxy
268
329
  + Can detect access errors in advance
269
330
  + Syntax-level access to signals can be inavoidable: external signals still "leak in" (via arrays or etc.).
270
331
  + Updating simple objects should also rerender the template parts, not just signals.
@@ -272,6 +333,9 @@
272
333
  - Screwed up debugging / stacktrace (unless errored)
273
334
  + that "unlimits" returned struct, so that any property can be added/deleted.
274
335
  - doesn't really save from `new (()=>{}).constructor` hack: we gotta substitute objects too.
336
+ + allows easier handle of `:with="a=1,b=2,c=3"` - we just naturally get local variables without messup with global
337
+ + we can even define locals without `let`...
338
+ - not having "comfy" compatible JS at hand: cognitive load of whole language "layer" in-between
275
339
 
276
340
  2. Use sandboxed proxy
277
341
  - tough evaluation
@@ -297,4 +361,33 @@
297
361
  ## [ ] Better :ref
298
362
  + :ref="`a-${1}`"
299
363
  + :id:ref="xyz"
300
- ? maybe id should have same signature
364
+ ? maybe id should have same signature
365
+ ? should it be very similar mechanism to `:with="a=1,b=2"`
366
+
367
+ ## [ ] Event modifiers :ona.once, `:ona`
368
+
369
+ - .prevent,.stop - not needed since expects a function
370
+ ? or should we just trigger it for user?
371
+ ? :onclick.outside
372
+ ? :onclick.window, :onclick.document
373
+ ? :onclick.once
374
+ ? :onclick.debounce
375
+ ? :onclick.throttle.750ms
376
+ ? :onclick.self
377
+ ? :onspecial-event.camel, :onx-y.dot
378
+ ? :onclick.passive
379
+ ? :onkeypress.shift.enter
380
+ .shift Shift
381
+ .enter Enter
382
+ .space Space
383
+ .ctrl Ctrl
384
+ .cmd Cmd
385
+ .meta Cmd on Mac, Windows key on Windows
386
+ .alt Alt
387
+ .up .down .left .right Up/Down/Left/Right arrows
388
+ .escape Escape
389
+ .tab Tab
390
+ .caps-lock Caps Lock
391
+ .equal Equal, =
392
+ .period Period, .
393
+ .slash Foward Slash, /
package/readme.md CHANGED
@@ -124,9 +124,9 @@ Set value of an input, textarea or select. Takes handle of `checked` and `select
124
124
  </select>
125
125
  ```
126
126
 
127
- #### `:<prop>="value"`, `:="props"`
127
+ #### `:<prop>="value?"`, `:="props?"`
128
128
 
129
- Set any prop value.
129
+ Set any prop value or run effect.
130
130
 
131
131
  ```html
132
132
  <!-- Single property -->
@@ -137,9 +137,12 @@ Set any prop value.
137
137
 
138
138
  <!-- Bulk properties -->
139
139
  <input :="{ id: name, name, type:'text', value }" />
140
+
141
+ <!-- Effect (triggers any time foo or bar changes) -->
142
+ <div :="if (foo) bar()" :fx="void bar()" ></div>
140
143
  ```
141
144
 
142
- #### `:on<event>="handler"`, `:on="events"`
145
+ #### `:on<event>="handler"`, `:on="events"`, `:<in>..<out>="handler"`
143
146
 
144
147
  Add event listeners.
145
148
 
@@ -151,7 +154,7 @@ Add event listeners.
151
154
  <input :value="text" :oninput:onchange="e => text=e.target.value">
152
155
 
153
156
  <!-- Sequence of events -->
154
- <button :onfocus-onblur="e => {
157
+ <button :onfocus..onblur="e => {
155
158
  // onfocus
156
159
  let id = setInterval(track,200)
157
160
  return e => {
@@ -164,23 +167,6 @@ Add event listeners.
164
167
  <button :on="{ click: handler, touch: handler, special: handler }">Submit</button>
165
168
  ```
166
169
 
167
- <!--
168
- #### `:oninit="fn"`
169
-
170
- Create initializer handler - called whenever element is initialized by sprae.
171
-
172
- ```html
173
- <div id="foo" :oninit="function(){ this === foo }"></div>
174
- ```
175
-
176
- #### `:onconnect="fn"`, `:ondisconnect="fn"`
177
-
178
- Invoked when element is connected or disconnected from DOM.
179
-
180
- ```html
181
- ```
182
- -->
183
-
184
170
  #### `:data="values"`
185
171
 
186
172
  Set [data-*](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*) attributes. CamelCase is converted to dash-case.
@@ -209,6 +195,23 @@ Set [aria-role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA)
209
195
  -->
210
196
  ```
211
197
 
198
+ #### `:with="data"`
199
+
200
+ Define variables for a subtree fragment scope.
201
+
202
+ ```html
203
+ <!-- Inline data -->
204
+ <x :with="{ foo: 'bar' }" :text="foo"></x>
205
+
206
+ <!-- External data -->
207
+ <y :with="data"></y>
208
+
209
+ <!-- Transparency -->
210
+ <x :with="{ foo: 'bar' }">
211
+ <y :with="{ baz: 'qux' }" :text="foo + baz"></y>
212
+ </x>
213
+ ```
214
+
212
215
  #### `:ref="id"`
213
216
 
214
217
  Expose element to data scope with the `id`:
@@ -220,9 +223,7 @@ Expose element to data scope with the `id`:
220
223
  <!-- iterable items -->
221
224
  <ul>
222
225
  <li :each="item in items" :ref="item">
223
- <input
224
- :onfocus-onblur="e => (item.classList.add('editing'), e => item.classList.remove('editing'))"
225
- />
226
+ <input :onfocus..onblur="e => (item.classList.add('editing'), e => item.classList.remove('editing'))"/>
226
227
  </li>
227
228
  </ul>
228
229
 
@@ -235,6 +236,7 @@ Expose element to data scope with the `id`:
235
236
  </script>
236
237
  ```
237
238
 
239
+
238
240
  <!--
239
241
 
240
242
  ### Reactivity
package/sprae.js CHANGED
@@ -363,33 +363,37 @@ var sube_default = (target, next, error, complete, stop, unsub) => target && (un
363
363
  var isSignal = (v2) => v2 && v2.peek;
364
364
  var isStruct = (v2) => v2 && v2[_struct];
365
365
  var _struct = Symbol("signal-struct");
366
- function SignalStruct(values) {
367
- if (isStruct(values))
366
+ signalStruct.isStruct = isStruct;
367
+ function signalStruct(values, proto) {
368
+ if (isStruct(values) && !proto)
368
369
  return values;
369
370
  let state, signals;
370
371
  if (isObject(values)) {
371
- state = {}, signals = {};
372
+ state = Object.create(proto || Object.getPrototypeOf(values)), signals = {};
372
373
  let desc = Object.getOwnPropertyDescriptors(values);
373
- for (let key in desc)
374
- signals[key] = defineSignal(state, key, desc[key].get ? w(desc[key].get.bind(state)) : desc[key].value);
374
+ if (isStruct(values))
375
+ for (let key in desc)
376
+ Object.defineProperty(state, key, desc[key]);
377
+ else
378
+ for (let key in desc)
379
+ signals[key] = defineSignal(state, key, desc[key].get ? w(desc[key].get.bind(state)) : desc[key].value);
375
380
  Object.defineProperty(state, _struct, { configurable: false, enumerable: false, value: true });
376
- Object.seal(state);
377
381
  return state;
378
382
  }
379
383
  if (Array.isArray(values)) {
380
- return values.map((v2) => SignalStruct(v2));
384
+ return values.map((v2) => signalStruct(v2));
381
385
  }
382
386
  return values;
383
387
  }
384
388
  function defineSignal(state, key, value) {
385
- let isObservable, s2 = isSignal(value) ? value : isObject(value) || Array.isArray(value) ? u(SignalStruct(value)) : u((isObservable = observable(value)) ? void 0 : value);
389
+ let isObservable, s2 = isSignal(value) ? value : isObject(value) || Array.isArray(value) ? u(signalStruct(value)) : u((isObservable = observable(value)) ? void 0 : value);
386
390
  if (isObservable)
387
391
  sube_default(value, (v2) => s2.value = v2);
388
392
  Object.defineProperty(state, key, {
389
393
  get() {
390
394
  return s2.value;
391
395
  },
392
- set: !isSignal(value) && isObject(value) ? (v2) => v2 ? Object.assign(s2.value, v2) : s2.value = SignalStruct(v2) : (v2) => s2.value = SignalStruct(v2),
396
+ set: !isSignal(value) && isObject(value) ? (v2) => v2 ? Object.assign(s2.value, v2) : s2.value = signalStruct(v2) : (v2) => s2.value = signalStruct(v2),
393
397
  enumerable: true,
394
398
  configurable: false
395
399
  });
@@ -452,11 +456,14 @@ var primitive_pool_default = (key) => {
452
456
  // src/directives.js
453
457
  var directives = {};
454
458
  var directives_default = (el, expr, values, name) => {
455
- if (name.startsWith("on")) {
456
- return directives.on(el, `{"${name.split("-").map((n2) => n2.startsWith("on") ? n2.slice(2) : n2).join("-")}": ${expr}}`, values);
457
- }
459
+ let evt = name.startsWith("on") && name.slice(2);
458
460
  let evaluate = parseExpr(el, expr, ":" + name);
459
- return (state) => attr(el, name, evaluate(state));
461
+ let value;
462
+ return evt ? (state) => {
463
+ value && removeListener(el, evt, value);
464
+ value = evaluate(state);
465
+ value && addListener(el, evt, value);
466
+ } : (state) => attr(el, name, evaluate(state));
460
467
  };
461
468
  var attr = (el, name, v2) => {
462
469
  if (v2 == null || v2 === false)
@@ -464,7 +471,7 @@ var attr = (el, name, v2) => {
464
471
  else
465
472
  el.setAttribute(name, v2 === true ? "" : typeof v2 === "number" || typeof v2 === "string" ? v2 : "");
466
473
  };
467
- directives[""] = (el, expr, values) => {
474
+ directives[""] = (el, expr) => {
468
475
  let evaluate = parseExpr(el, expr, ":");
469
476
  return (state) => {
470
477
  let value = evaluate(state);
@@ -474,15 +481,19 @@ directives[""] = (el, expr, values) => {
474
481
  };
475
482
  var _each = Symbol(":each");
476
483
  var _ref = Symbol(":ref");
477
- directives["ref"] = (el, expr, values) => {
484
+ directives["ref"] = (el, expr, state) => {
478
485
  if (el.hasAttribute(":each")) {
479
486
  el[_ref] = expr;
480
487
  return;
481
488
  }
482
489
  ;
483
- values[expr] = el;
490
+ state[expr] = el;
491
+ };
492
+ directives["with"] = (el, expr, rootState) => {
493
+ let evaluate = parseExpr(el, expr, "with");
494
+ sprae(el, signalStruct(evaluate(rootState), rootState));
484
495
  };
485
- directives["if"] = (el, expr, values) => {
496
+ directives["if"] = (el, expr) => {
486
497
  let holder = document.createTextNode(""), clauses = [parseExpr(el, expr, ":if")], els = [el], cur = el;
487
498
  while (cur = el.nextElementSibling) {
488
499
  if (cur.hasAttribute(":else")) {
@@ -508,7 +519,7 @@ directives["if"] = (el, expr, values) => {
508
519
  }
509
520
  };
510
521
  };
511
- directives["each"] = (tpl, expr, values) => {
522
+ directives["each"] = (tpl, expr) => {
512
523
  let each = parseForExpression(expr);
513
524
  if (!each)
514
525
  return exprError(new Error(), tpl, expr);
@@ -524,12 +535,12 @@ directives["each"] = (tpl, expr, values) => {
524
535
  list = [];
525
536
  else if (typeof list === "number")
526
537
  list = Array.from({ length: list }, (_2, i2) => [i2, i2 + 1]);
527
- else if (list.constructor === Object)
528
- list = Object.entries(list);
529
538
  else if (Array.isArray(list))
530
539
  list = list.map((item, i2) => [i2 + 1, item]);
540
+ else if (typeof list === "object")
541
+ list = Object.entries(list);
531
542
  else
532
- exprError(Error("Bad list value"), tpl, each.items, ":each", list);
543
+ exprError(Error("Bad list value"), tpl, expr, ":each", list);
533
544
  let newEls = [], elScopes = [];
534
545
  for (let [idx, item] of list) {
535
546
  let itemKey = primitive_pool_default(item);
@@ -576,12 +587,12 @@ function parseForExpression(expression) {
576
587
  }
577
588
  return res;
578
589
  }
579
- directives["id"] = (el, expr, values) => {
590
+ directives["id"] = (el, expr) => {
580
591
  let evaluate = parseExpr(el, expr, ":id");
581
592
  const update = (v2) => el.id = v2 || v2 === 0 ? v2 : "";
582
593
  return (state) => update(evaluate(state));
583
594
  };
584
- directives["class"] = (el, expr, values) => {
595
+ directives["class"] = (el, expr) => {
585
596
  let evaluate = parseExpr(el, expr, ":class");
586
597
  let initClassName = el.className;
587
598
  return (state) => {
@@ -589,7 +600,7 @@ directives["class"] = (el, expr, values) => {
589
600
  el.className = initClassName + typeof v2 === "string" ? v2 : (Array.isArray(v2) ? v2 : Object.entries(v2).map(([k, v3]) => v3 ? k : "")).filter(Boolean).join(" ");
590
601
  };
591
602
  };
592
- directives["style"] = (el, expr, values) => {
603
+ directives["style"] = (el, expr) => {
593
604
  let evaluate = parseExpr(el, expr, ":style");
594
605
  let initStyle = el.getAttribute("style") || "";
595
606
  if (!initStyle.endsWith(";"))
@@ -603,14 +614,14 @@ directives["style"] = (el, expr, values) => {
603
614
  el.style[k] = v2[k];
604
615
  };
605
616
  };
606
- directives["text"] = (el, expr, values) => {
617
+ directives["text"] = (el, expr) => {
607
618
  let evaluate = parseExpr(el, expr, ":text");
608
- const update = (value) => {
619
+ return (state) => {
620
+ let value = evaluate(state);
609
621
  el.textContent = value == null ? "" : value;
610
622
  };
611
- return (state) => update(evaluate(state));
612
623
  };
613
- directives["value"] = (el, expr, values) => {
624
+ directives["value"] = (el, expr) => {
614
625
  let evaluate = parseExpr(el, expr, ":value");
615
626
  let update = el.type === "text" || el.type === "" ? (value) => el.setAttribute("value", el.value = value == null ? "" : value) : el.type === "checkbox" ? (value) => (el.value = value ? "on" : "", attr(el, "checked", value)) : el.type === "select-one" ? (value) => {
616
627
  for (let option in el.options)
@@ -620,36 +631,45 @@ directives["value"] = (el, expr, values) => {
620
631
  } : (value) => el.value = value;
621
632
  return (state) => update(evaluate(state));
622
633
  };
623
- directives["on"] = (el, expr, values) => {
634
+ var _stop = Symbol("stop");
635
+ directives["on"] = (el, expr) => {
624
636
  let evaluate = parseExpr(el, expr, ":on");
625
637
  let listeners = {};
626
638
  return (state) => {
627
639
  for (let evt in listeners)
628
- el.removeEventListener(evt, listeners[evt]);
640
+ removeListener(el, evt, listeners[evt]);
629
641
  listeners = evaluate(state);
630
- for (let evt in listeners) {
631
- const evts = evt.split("-");
632
- if (evts.length === 1)
633
- el.addEventListener(evt, listeners[evt]);
634
- else {
635
- const startFn = listeners[evt];
636
- const nextEvt = (fn, cur = 0) => {
637
- el.addEventListener(evts[cur], listeners[evt] = (e2) => {
638
- fn = fn(e2);
639
- el.removeEventListener(evts[cur], listeners[evt]);
640
- if (++cur < evts.length && typeof fn === "function")
641
- nextEvt(fn, cur);
642
- else
643
- nextEvt(startFn);
644
- });
645
- };
646
- nextEvt(startFn);
647
- }
648
- }
649
- ;
642
+ for (let evt in listeners)
643
+ addListener(el, evt, listeners[evt]);
650
644
  };
651
645
  };
652
- directives["data"] = (el, expr, values) => {
646
+ var addListener = (el, evt, startFn) => {
647
+ if (evt.indexOf("..") < 0)
648
+ el.addEventListener(evt, startFn);
649
+ else {
650
+ const evts = evt.split("..").map((e2) => e2.startsWith("on") ? e2.slice(2) : e2);
651
+ const nextEvt = (fn, cur = 0) => {
652
+ let curListener = (e2) => {
653
+ el.removeEventListener(evts[cur], curListener);
654
+ if (typeof (fn = fn.call(el, e2)) !== "function")
655
+ fn = () => {
656
+ };
657
+ if (++cur < evts.length)
658
+ nextEvt(fn, cur);
659
+ else if (!startFn[_stop])
660
+ console.log("reset"), nextEvt(startFn);
661
+ };
662
+ el.addEventListener(evts[cur], curListener);
663
+ };
664
+ nextEvt(startFn);
665
+ }
666
+ };
667
+ var removeListener = (el, evt, fn) => {
668
+ if (evt.indexOf("..") >= 0)
669
+ console.log("rewire"), fn[_stop] = true;
670
+ el.removeEventListener(evt, fn);
671
+ };
672
+ directives["data"] = (el, expr) => {
653
673
  let evaluate = parseExpr(el, expr, ":data");
654
674
  return (state) => {
655
675
  let value = evaluate(state);
@@ -657,7 +677,7 @@ directives["data"] = (el, expr, values) => {
657
677
  el.dataset[key] = value[key];
658
678
  };
659
679
  };
660
- directives["aria"] = (el, expr, values) => {
680
+ directives["aria"] = (el, expr) => {
661
681
  let evaluate = parseExpr(el, expr, ":aria");
662
682
  const update = (value) => {
663
683
  for (let key in value)
@@ -708,7 +728,7 @@ function sprae(container, values) {
708
728
  return;
709
729
  if (memo.has(container))
710
730
  return memo.get(container);
711
- values ||= {};
731
+ const state = signalStruct(values || {});
712
732
  const updates = [];
713
733
  const init = (el, parent = el.parentNode) => {
714
734
  if (el.attributes) {
@@ -720,10 +740,12 @@ function sprae(container, values) {
720
740
  }
721
741
  el.removeAttribute(attr2.name);
722
742
  let expr = attr2.value;
743
+ if (!expr)
744
+ continue;
723
745
  let attrNames = attr2.name.slice(1).split(":");
724
746
  for (let attrName of attrNames) {
725
747
  let dir = directives[attrName] || directives_default;
726
- updates.push(dir(el, expr, values, attrName) || (() => {
748
+ updates.push(dir(el, expr, state, attrName) || (() => {
727
749
  }));
728
750
  if (memo.has(el) || el.parentNode !== parent)
729
751
  return false;
@@ -736,9 +758,9 @@ function sprae(container, values) {
736
758
  }
737
759
  };
738
760
  init(container);
739
- const state = SignalStruct(values);
740
761
  for (let update of updates)
741
762
  b(() => update(state));
763
+ Object.seal(state);
742
764
  memo.set(container, state);
743
765
  return state;
744
766
  }
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 f(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 a(t){this.v=t,this.i=0,this.n=void 0,this.t=void 0}function u(t){return new a(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){a.call(this,void 0),this.x=t,this.s=void 0,this.g=s-1,this.f=4}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,d(t),e}finally{i=o,e()}}}function d(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");c(this),i=t,this.f&=-2,8&this.f&&d(this),e()}function b(t){this.x=t,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32}function m(t){var e=new b(t);return e.c(),e.d.bind(e)}a.prototype.h=function(){return!0},a.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)},a.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)},a.prototype.subscribe=function(t){var e=this;return m((function(){var i=e.value,r=32&this.f;this.f&=-33;try{t(i)}finally{this.f|=r}}))},a.prototype.valueOf=function(){return this.value},a.prototype.toString=function(){return this.value+""},a.prototype.peek=function(){return this.v},Object.defineProperty(a.prototype,"value",{get:function(){var t=f(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 a).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)}a.prototype.S.call(this,t)},v.prototype.U=function(t){if(a.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=f(this);if(this.h(),void 0!==e&&(e.i=this.i),16&this.f)throw this.v;return this.v}}),b.prototype.c=function(){var t=this.S();try{8&this.f||void 0===this.x||(this.u=this.x())}finally{t()}},b.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)},b.prototype.N=function(){2&this.f||(this.f|=2,this.o=r,r=this)},b.prototype.d=function(){this.f|=8,1&this.f||d(this)},Symbol.observable||=Symbol("observable");var g=new FinalizationRegistry((t=>t.call?.())),S=t=>t&&t.peek,x=Symbol("signal-struct");function w(t){if((e=t)&&e[x])return t;var e;let i,r;if(O(t)){i={},r={};let e=Object.getOwnPropertyDescriptors(t);for(let t in e)r[t]=A(i,t,e[t].get?new v(e[t].get.bind(i)):e[t].value);return Object.defineProperty(i,x,{configurable:!1,enumerable:!1,value:!0}),Object.seal(i),i}return Array.isArray(t)?t.map((t=>w(t))):t}function A(t,e,i){let r,n=S(i)?i:O(i)||Array.isArray(i)?u(w(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,f,a,l,h,c;return r&&(f=t=>n.value=t,(s=i)&&(c=(s[Symbol.observable]?.()||s).subscribe?.(f,a,undefined),h=c&&(()=>c.unsubscribe?.())||s.set&&s.call?.(l,f)||(s.then?.((t=>{!l&&f(t)}),a)||(async t=>{try{for await(t of s){if(l)return;f(t)}}catch(t){}})())&&(t=>l=1),g.register(s,h))),Object.defineProperty(t,e,{get:()=>n.value,set:!S(i)&&O(i)?t=>t?Object.assign(n.value,t):n.value=w(t):t=>n.value=w(t),enumerable:!0,configurable:!1}),n}function O(t){return t&&t.constructor===Object}var j=(t,e,i,r=null)=>{let n,o,s,f=0,a=i.length,u=e.length,{remove:l,same:h,insert:c,replace:v}=j;for(;f<a&&f<u&&h(e[f],i[f]);)f++;for(;f<a&&f<u&&h(i[a-1],e[u-1]);)r=i[(--u,--a)];if(f==u)for(;f<a;)c(r,i[f++],t);else{for(n=e[f];f<a;)s=i[f++],o=n?n.nextSibling:r,h(n,s)?n=o:f<a&&h(i[f],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};j.same=(t,e)=>t==e,j.replace=(t,e,i)=>i.replaceChild(e,t),j.insert=(t,e,i)=>i.insertBefore(e,t),j.remove=(t,e)=>e.removeChild(t);var N=j,E={},$={},W={},k={},C=(t,e,i,r)=>{if(r.startsWith("on"))return k.on(t,`{"${r.split("-").map((t=>t.startsWith("on")?t.slice(2):t)).join("-")}": ${e}}`,i);let n=D(t,e,":"+r);return e=>U(t,r,n(e))},U=(t,e,i)=>{null==i||!1===i?t.removeAttribute(e):t.setAttribute(e,!0===i?"":"number"==typeof i||"string"==typeof i?i:"")};k[""]=(t,e,i)=>{let r=D(t,e,":");return e=>{let i=r(e);for(let e in i)U(t,M(e),i[e])}};var B=Symbol(":each"),L=Symbol(":ref");k.ref=(t,e,i)=>{t.hasAttribute(":each")?t[L]=e:i[e]=t},k.if=(t,e,i)=>{let r=document.createTextNode(""),n=[D(t,e,":if")],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(D(t,e,":else :if"))):(s.remove(),o.push(s),n.push((()=>1)));return t.replaceWith(s=r),t=>{let e=n.findIndex((e=>e(t)));o[e]!=s&&((s[B]||s).replaceWith(s=o[e]||r),F(s,t))}},k.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 _(new Error,t,e);const n=t[B]=document.createTextNode("");t.replaceWith(n);const o=D(t,r.items,":each"),s=new WeakMap,f=new WeakMap;let a=[];return e=>{let i=o(e);i?"number"==typeof i?i=Array.from({length:i},((t,e)=>[e,e+1])):i.constructor===Object?i=Object.entries(i):Array.isArray(i)?i=i.map(((t,e)=>[e+1,t])):_(Error("Bad list value"),t,r.items,":each"):i=[];let u=[],l=[];for(let[n,o]of i){let i=null===(h=o)?$:void 0===h?W:"number"==typeof h||h instanceof Number?E[h]||(E[h]=new Number(h)):"string"==typeof h||h instanceof String?E[h]||(E[h]=new String(h)):"boolean"==typeof h||h instanceof Boolean?E[h]||(E[h]=new Boolean(h)):h,a=f.get(i);if(a||(a=t.cloneNode(!0),f.set(i,a)),u.push(a),!s.has(i)){let f=Object.create(e);f[r.item]=o,r.index&&(f[r.index]=n),t[L]&&(f[t[L]]=a),s.set(i,f)}l.push(s.get(i))}var h;N(n.parentNode,a,u,n),a=u;for(let t=0;t<u.length;t++)F(u[t],l[t])}},k.id=(t,e,i)=>{let r=D(t,e,":id");return e=>{return i=r(e),t.id=i||0===i?i:"";var i}},k.class=(t,e,i)=>{let r=D(t,e,":class"),n=t.className;return e=>{let i=r(e);t.className=n+typeof i=="string"?i:(Array.isArray(i)?i:Object.entries(i).map((([t,e])=>e?t:""))).filter(Boolean).join(" ")}},k.style=(t,e,i)=>{let r=D(t,e,":style"),n=t.getAttribute("style")||"";return n.endsWith(";")||(n+="; "),e=>{let i=r(e);if("string"==typeof i)t.setAttribute("style",n+i);else for(let e in i)t.style[e]=i[e]}},k.text=(t,e,i)=>{let r=D(t,e,":text");return e=>{return i=r(e),void(t.textContent=null==i?"":i);var i}},k.value=(t,e,i)=>{let r=D(t,e,":value"),n="text"===t.type||""===t.type?e=>t.setAttribute("value",t.value=null==e?"":e):"checkbox"===t.type?e=>(t.value=e?"on":"",U(t,"checked",e)):"select-one"===t.type?e=>{for(let e in t.options)e.removeAttribute("selected");t.value=e,t.selectedOptions[0]?.setAttribute("selected","")}:e=>t.value=e;return t=>n(r(t))},k.on=(t,e,i)=>{let r=D(t,e,":on"),n={};return e=>{for(let e in n)t.removeEventListener(e,n[e]);n=r(e);for(let e in n){const i=e.split("-");if(1===i.length)t.addEventListener(e,n[e]);else{const r=n[e],o=(s,f=0)=>{t.addEventListener(i[f],n[e]=a=>{s=s(a),t.removeEventListener(i[f],n[e]),++f<i.length&&"function"==typeof s?o(s,f):o(r)})};o(r)}}}},k.data=(t,e,i)=>{let r=D(t,e,":data");return e=>{let i=r(e);for(let e in i)t.dataset[e]=i[e]}},k.aria=(t,e,i)=>{let r=D(t,e,":aria");return e=>(e=>{for(let i in e)U(t,"aria-"+M(i),null==e[i]?null:e[i]+"")})(r(e))};var P={};function D(t,e,i){if(P[e])return P[e];let r,n=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(() => { ${e} })()`:e;try{r=new Function("__scope",`with (__scope) { return (${n}) };`).bind(t)}catch(r){return _(r,t,e,i)}return P[e]=n=>{let o;try{o=r(n)}catch(r){return _(r,t,e,i)}return o}}function _(t,e,i,r){Object.assign(t,{element:e,expression:i}),console.warn(`∴ ${t.message}\n\n${r}=${i?`"${i}"\n\n`:""}`,e),setTimeout((()=>{throw t}),0)}function M(t){return t.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(t=>"-"+t.toLowerCase()))}var T=new WeakMap;function F(t,e){if(!t.children)return;if(T.has(t))return T.get(t);e||={};const i=[],r=(t,n=t.parentNode)=>{if(t.attributes)for(let r=0;r<t.attributes.length;){let o=t.attributes[r];if(":"!==o.name[0]){r++;continue}t.removeAttribute(o.name);let s=o.value,f=o.name.slice(1).split(":");for(let r of f){let o=k[r]||C;if(i.push(o(t,s,e,r)||(()=>{})),T.has(t)||t.parentNode!==n)return!1}}for(let e,i=0;e=t.children[i];i++)!1===r(e,t)&&i--};r(t);const n=w(e);for(let t of i)m((()=>t(n)));return T.set(t,n),n}var I=F;export{I 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)&&u(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 f(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 l(t){this.v=t,this.i=0,this.n=void 0,this.t=void 0}function a(t){return new l(t)}function u(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){l.call(this,void 0),this.x=t,this.s=void 0,this.g=s-1,this.f=4}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,d(t),e}finally{i=o,e()}}}function d(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");c(this),i=t,this.f&=-2,8&this.f&&d(this),e()}function b(t){this.x=t,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32}function m(t){var e=new b(t);return e.c(),e.d.bind(e)}l.prototype.h=function(){return!0},l.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)},l.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)},l.prototype.subscribe=function(t){var e=this;return m((function(){var i=e.value,r=32&this.f;this.f&=-33;try{t(i)}finally{this.f|=r}}))},l.prototype.valueOf=function(){return this.value},l.prototype.toString=function(){return this.value+""},l.prototype.peek=function(){return this.v},Object.defineProperty(l.prototype,"value",{get:function(){var t=f(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 l).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&&!u(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)}l.prototype.S.call(this,t)},v.prototype.U=function(t){if(l.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=f(this);if(this.h(),void 0!==e&&(e.i=this.i),16&this.f)throw this.v;return this.v}}),b.prototype.c=function(){var t=this.S();try{8&this.f||void 0===this.x||(this.u=this.x())}finally{t()}},b.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)},b.prototype.N=function(){2&this.f||(this.f|=2,this.o=r,r=this)},b.prototype.d=function(){this.f|=8,1&this.f||d(this)},Symbol.observable||=Symbol("observable");var g=new FinalizationRegistry((t=>t.call?.())),S=t=>t&&t.peek,x=t=>t&&t[w],w=Symbol("signal-struct");function A(t,e){if(x(t)&&!e)return t;let i,r;if(j(t)){i=Object.create(e||Object.getPrototypeOf(t)),r={};let n=Object.getOwnPropertyDescriptors(t);if(x(t))for(let t in n)Object.defineProperty(i,t,n[t]);else for(let t in n)r[t]=O(i,t,n[t].get?new v(n[t].get.bind(i)):n[t].value);return Object.defineProperty(i,w,{configurable:!1,enumerable:!1,value:!0}),i}return Array.isArray(t)?t.map((t=>A(t))):t}function O(t,e,i){let r,n=S(i)?i:j(i)||Array.isArray(i)?a(A(i)):a((r=(o=i)&&!!(o[Symbol.observable]||o[Symbol.asyncIterator]||o.call&&o.set||o.subscribe||o.then))?void 0:i);var o,s,f,l,u,h,c;return r&&(f=t=>n.value=t,(s=i)&&(c=(s[Symbol.observable]?.()||s).subscribe?.(f,l,undefined),h=c&&(()=>c.unsubscribe?.())||s.set&&s.call?.(u,f)||(s.then?.((t=>{!u&&f(t)}),l)||(async t=>{try{for await(t of s){if(u)return;f(t)}}catch(t){}})())&&(t=>u=1),g.register(s,h))),Object.defineProperty(t,e,{get:()=>n.value,set:!S(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}A.isStruct=x;var N=(t,e,i,r=null)=>{let n,o,s,f=0,l=i.length,a=e.length,{remove:u,same:h,insert:c,replace:v}=N;for(;f<l&&f<a&&h(e[f],i[f]);)f++;for(;f<l&&f<a&&h(i[l-1],e[a-1]);)r=i[(--a,--l)];if(f==a)for(;f<l;)c(r,i[f++],t);else{for(n=e[f];f<l;)s=i[f++],o=n?n.nextSibling:r,h(n,s)?n=o:f<l&&h(i[f],o)?(v(n,s,t),n=o):c(n,s,t);for(;!h(n,r);)o=n.nextSibling,u(n,t),n=o}return i};N.same=(t,e)=>t==e,N.replace=(t,e,i)=>i.replaceChild(e,t),N.insert=(t,e,i)=>i.insertBefore(e,t),N.remove=(t,e)=>e.removeChild(t);var E=N,W={},k={},$={},P={},C=(t,e,i,r)=>{let n,o=r.startsWith("on")&&r.slice(2),s=F(t,e,":"+r);return o?e=>{n&&M(t,o,n),n=s(e),n&&_(t,o,n)}:e=>U(t,r,s(e))},U=(t,e,i)=>{null==i||!1===i?t.removeAttribute(e):t.setAttribute(e,!0===i?"":"number"==typeof i||"string"==typeof i?i:"")};P[""]=(t,e)=>{let i=F(t,e,":");return e=>{let r=i(e);for(let e in r)U(t,z(e),r[e])}};var B=Symbol(":each"),L=Symbol(":ref");P.ref=(t,e,i)=>{t.hasAttribute(":each")?t[L]=e:i[e]=t},P.with=(t,e,i)=>{Z(t,A(F(t,e,"with")(i),i))},P.if=(t,e)=>{let i=document.createTextNode(""),r=[F(t,e,":if")],n=[t],o=t;for(;(o=t.nextElementSibling)&&o.hasAttribute(":else");)o.removeAttribute(":else"),(e=o.getAttribute(":if"))?(o.removeAttribute(":if"),o.remove(),n.push(o),r.push(F(t,e,":else :if"))):(o.remove(),n.push(o),r.push((()=>1)));return t.replaceWith(o=i),t=>{let e=r.findIndex((e=>e(t)));n[e]!=o&&((o[B]||o).replaceWith(o=n[e]||i),Z(o,t))}},P.each=(t,e)=>{let i=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(!i)return I(new Error,t,e);const r=t[B]=document.createTextNode("");t.replaceWith(r);const n=F(t,i.items,":each"),o=new WeakMap,s=new WeakMap;let f=[];return l=>{let a=n(l);a?"number"==typeof a?a=Array.from({length:a},((t,e)=>[e,e+1])):Array.isArray(a)?a=a.map(((t,e)=>[e+1,t])):"object"==typeof a?a=Object.entries(a):I(Error("Bad list value"),t,e,":each"):a=[];let u=[],h=[];for(let[e,r]of a){let n=null===(c=r)?k:void 0===c?$:"number"==typeof c||c instanceof Number?W[c]||(W[c]=new Number(c)):"string"==typeof c||c instanceof String?W[c]||(W[c]=new String(c)):"boolean"==typeof c||c instanceof Boolean?W[c]||(W[c]=new Boolean(c)):c,f=s.get(n);if(f||(f=t.cloneNode(!0),s.set(n,f)),u.push(f),!o.has(n)){let s=Object.create(l);s[i.item]=r,i.index&&(s[i.index]=e),t[L]&&(s[t[L]]=f),o.set(n,s)}h.push(o.get(n))}var c;E(r.parentNode,f,u,r),f=u;for(let t=0;t<u.length;t++)Z(u[t],h[t])}},P.id=(t,e)=>{let i=F(t,e,":id");return e=>{return r=i(e),t.id=r||0===r?r:"";var r}},P.class=(t,e)=>{let i=F(t,e,":class"),r=t.className;return e=>{let n=i(e);t.className=r+typeof n=="string"?n:(Array.isArray(n)?n:Object.entries(n).map((([t,e])=>e?t:""))).filter(Boolean).join(" ")}},P.style=(t,e)=>{let i=F(t,e,":style"),r=t.getAttribute("style")||"";return r.endsWith(";")||(r+="; "),e=>{let n=i(e);if("string"==typeof n)t.setAttribute("style",r+n);else for(let e in n)t.style[e]=n[e]}},P.text=(t,e)=>{let i=F(t,e,":text");return e=>{let r=i(e);t.textContent=null==r?"":r}},P.value=(t,e)=>{let i=F(t,e,":value"),r="text"===t.type||""===t.type?e=>t.setAttribute("value",t.value=null==e?"":e):"checkbox"===t.type?e=>(t.value=e?"on":"",U(t,"checked",e)):"select-one"===t.type?e=>{for(let e in t.options)e.removeAttribute("selected");t.value=e,t.selectedOptions[0]?.setAttribute("selected","")}:e=>t.value=e;return t=>r(i(t))};var D=Symbol("stop");P.on=(t,e)=>{let i=F(t,e,":on"),r={};return e=>{for(let e in r)M(t,e,r[e]);r=i(e);for(let e in r)_(t,e,r[e])}};var _=(t,e,i)=>{if(e.indexOf("..")<0)t.addEventListener(e,i);else{const r=e.split("..").map((t=>t.startsWith("on")?t.slice(2):t)),n=(e,o=0)=>{let s=f=>{t.removeEventListener(r[o],s),"function"!=typeof(e=e.call(t,f))&&(e=()=>{}),++o<r.length?n(e,o):i[D]||(console.log("reset"),n(i))};t.addEventListener(r[o],s)};n(i)}},M=(t,e,i)=>{e.indexOf("..")>=0&&(console.log("rewire"),i[D]=!0),t.removeEventListener(e,i)};P.data=(t,e)=>{let i=F(t,e,":data");return e=>{let r=i(e);for(let e in r)t.dataset[e]=r[e]}},P.aria=(t,e)=>{let i=F(t,e,":aria");return e=>(e=>{for(let i in e)U(t,"aria-"+z(i),null==e[i]?null:e[i]+"")})(i(e))};var T={};function F(t,e,i){if(T[e])return T[e];let r,n=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(() => { ${e} })()`:e;try{r=new Function("__scope",`with (__scope) { return (${n}) };`).bind(t)}catch(r){return I(r,t,e,i)}return T[e]=n=>{let o;try{o=r(n)}catch(r){return I(r,t,e,i)}return o}}function I(t,e,i,r){Object.assign(t,{element:e,expression:i}),console.warn(`∴ ${t.message}\n\n${r}=${i?`"${i}"\n\n`:""}`,e),setTimeout((()=>{throw t}),0)}function z(t){return t.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(t=>"-"+t.toLowerCase()))}var R=new WeakMap;function Z(t,e){if(!t.children)return;if(R.has(t))return R.get(t);const i=A(e||{}),r=[],n=(t,e=t.parentNode)=>{if(t.attributes)for(let n=0;n<t.attributes.length;){let o=t.attributes[n];if(":"!==o.name[0]){n++;continue}t.removeAttribute(o.name);let s=o.value;if(!s)continue;let f=o.name.slice(1).split(":");for(let n of f){let o=P[n]||C;if(r.push(o(t,s,i,n)||(()=>{})),R.has(t)||t.parentNode!==e)return!1}}for(let e,i=0;e=t.children[i];i++)!1===n(e,t)&&i--};n(t);for(let t of r)m((()=>t(i)));return Object.seal(i),R.set(t,i),i}var q=Z;export{q as default};
package/src/core.js CHANGED
@@ -8,8 +8,8 @@ export default function sprae(container, values) {
8
8
  if (!container.children) return
9
9
  if (memo.has(container)) return memo.get(container)
10
10
 
11
- values ||= {};
12
-
11
+ // signalStruct returns values if it's signalStruct already
12
+ const state = signalStruct(values || {});
13
13
  const updates = []
14
14
 
15
15
  // init directives on element
@@ -20,10 +20,11 @@ export default function sprae(container, values) {
20
20
  if (attr.name[0] !== ':') {i++; continue}
21
21
  el.removeAttribute(attr.name)
22
22
  let expr = attr.value
23
+ if (!expr) continue
23
24
  let attrNames = attr.name.slice(1).split(':')
24
25
  for (let attrName of attrNames) {
25
26
  let dir = directives[attrName] || defaultDirective;
26
- updates.push(dir(el, expr, values, attrName) || (()=>{}));
27
+ updates.push(dir(el, expr, state, attrName) || (()=>{}));
27
28
 
28
29
  // stop if element was spraed by directive or skipped
29
30
  if (memo.has(el) || el.parentNode !== parent) return false
@@ -41,9 +42,9 @@ export default function sprae(container, values) {
41
42
 
42
43
  // call updates: subscribes directives to state;
43
44
  // state is created after inits because directives can extend init values (expose refs etc)
44
- const state = signalStruct(values);
45
45
  for (let update of updates) effect(() => update(state));
46
46
 
47
+ Object.seal(state);
47
48
  memo.set(container, state);
48
49
 
49
50
  return state;
package/src/directives.js CHANGED
@@ -2,20 +2,23 @@
2
2
  import sprae from './core.js'
3
3
  import swap from 'swapdom'
4
4
  import p from 'primitive-pool'
5
+ import signalStruct from 'signal-struct'
5
6
 
6
7
  // reserved directives - order matters!
7
8
  export const directives = {}
8
9
 
9
10
  // any-prop directives
10
11
  export default (el, expr, values, name) => {
11
- if (name.startsWith('on')) {
12
- // :ona:onb=x -> :on={a:x,b:x}
13
- // :ona-onb=x -> :on={aB:x}
14
- return directives.on(el, `{"${name.split('-').map(n=>n.startsWith('on')?n.slice(2):n).join('-')}": ${expr}}`, values)
15
- }
12
+ let evt = name.startsWith('on') && name.slice(2)
16
13
  let evaluate = parseExpr(el, expr, ':'+name)
17
14
 
18
- return (state) => attr(el, name, evaluate(state))
15
+ let value
16
+ return evt ? state => {
17
+ value && removeListener(el, evt, value)
18
+ value = evaluate(state)
19
+ value && addListener(el, evt, value)
20
+ }
21
+ : state => attr(el, name, evaluate(state))
19
22
  }
20
23
 
21
24
  // set attr
@@ -24,7 +27,7 @@ const attr = (el, name, v) => {
24
27
  else el.setAttribute(name, v === true ? '' : (typeof v === 'number' || typeof v === 'string') ? v : '')
25
28
  }
26
29
 
27
- directives[''] = (el, expr, values) => {
30
+ directives[''] = (el, expr) => {
28
31
  let evaluate = parseExpr(el, expr, ':')
29
32
  return (state) => {
30
33
  let value = evaluate(state)
@@ -34,16 +37,21 @@ directives[''] = (el, expr, values) => {
34
37
 
35
38
  const _each = Symbol(':each'), _ref = Symbol(':ref')
36
39
 
37
- directives['ref'] = (el, expr, values) => {
40
+ directives['ref'] = (el, expr, state) => {
38
41
  // make sure :ref is initialized after :each (return to avoid initializing as signal)
39
42
  if (el.hasAttribute(':each')) {el[_ref] = expr; return};
40
43
 
41
44
  // FIXME: wait for complex ref use-case
42
45
  // parseExpr(el, `__scope[${expr}]=this`, ':ref')(values)
43
- values[expr] = el;
46
+ state[expr] = el;
47
+ }
48
+
49
+ directives['with'] = (el, expr, rootState) => {
50
+ let evaluate = parseExpr(el, expr, 'with')
51
+ sprae(el, signalStruct(evaluate(rootState), rootState));
44
52
  }
45
53
 
46
- directives['if'] = (el, expr, values) => {
54
+ directives['if'] = (el, expr) => {
47
55
  let holder = document.createTextNode(''),
48
56
  clauses = [parseExpr(el, expr, ':if')],
49
57
  els = [el], cur = el
@@ -74,7 +82,7 @@ directives['if'] = (el, expr, values) => {
74
82
  }
75
83
  }
76
84
 
77
- directives['each'] = (tpl, expr, values) => {
85
+ directives['each'] = (tpl, expr) => {
78
86
  let each = parseForExpression(expr);
79
87
  if (!each) return exprError(new Error, tpl, expr);
80
88
 
@@ -96,9 +104,9 @@ directives['each'] = (tpl, expr, values) => {
96
104
  let list = evaluate(state)
97
105
  if (!list) list = []
98
106
  else if (typeof list === 'number') list = Array.from({length: list}, (_, i)=>[i, i+1])
99
- else if (list.constructor === Object) list = Object.entries(list)
100
107
  else if (Array.isArray(list)) list = list.map((item,i) => [i+1, item])
101
- else exprError(Error('Bad list value'), tpl, each.items, ':each', list)
108
+ else if (typeof list === 'object') list = Object.entries(list)
109
+ else exprError(Error('Bad list value'), tpl, expr, ':each', list)
102
110
 
103
111
  // collect elements/scopes for items
104
112
  let newEls = [], elScopes = []
@@ -158,13 +166,13 @@ function parseForExpression(expression) {
158
166
  return res
159
167
  }
160
168
 
161
- directives['id'] = (el, expr, values) => {
169
+ directives['id'] = (el, expr) => {
162
170
  let evaluate = parseExpr(el, expr, ':id')
163
171
  const update = v => el.id = v || v === 0 ? v : ''
164
172
  return (state) => update(evaluate(state))
165
173
  }
166
174
 
167
- directives['class'] = (el, expr, values) => {
175
+ directives['class'] = (el, expr) => {
168
176
  let evaluate = parseExpr(el, expr, ':class')
169
177
  let initClassName = el.className
170
178
  return (state) => {
@@ -173,7 +181,7 @@ directives['class'] = (el, expr, values) => {
173
181
  }
174
182
  }
175
183
 
176
- directives['style'] = (el, expr, values) => {
184
+ directives['style'] = (el, expr) => {
177
185
  let evaluate = parseExpr(el, expr, ':style')
178
186
  let initStyle = el.getAttribute('style') || ''
179
187
  if (!initStyle.endsWith(';')) initStyle += '; '
@@ -184,18 +192,17 @@ directives['style'] = (el, expr, values) => {
184
192
  }
185
193
  }
186
194
 
187
- directives['text'] = (el, expr, values) => {
195
+ directives['text'] = (el, expr) => {
188
196
  let evaluate = parseExpr(el, expr, ':text')
189
197
 
190
- const update = (value) => {
198
+ return (state) => {
199
+ let value = evaluate(state)
191
200
  el.textContent = value == null ? '' : value;
192
201
  }
193
-
194
- return (state) => update(evaluate(state))
195
202
  }
196
203
 
197
204
  // connect expr to element value
198
- directives['value'] = (el, expr, values) => {
205
+ directives['value'] = (el, expr) => {
199
206
  let evaluate = parseExpr(el, expr, ':value')
200
207
 
201
208
  let update = (
@@ -212,35 +219,43 @@ directives['value'] = (el, expr, values) => {
212
219
  return (state) => update(evaluate(state))
213
220
  }
214
221
 
215
- directives['on'] = (el, expr, values) => {
222
+ const _stop = Symbol('stop')
223
+ directives['on'] = (el, expr) => {
216
224
  let evaluate = parseExpr(el, expr, ':on')
217
225
  let listeners = {}
218
226
 
219
227
  return (state) => {
220
- for (let evt in listeners) el.removeEventListener(evt, listeners[evt]);
221
-
228
+ for (let evt in listeners) removeListener(el, evt, listeners[evt])
222
229
  listeners = evaluate(state);
230
+ for (let evt in listeners) addListener(el, evt, listeners[evt])
231
+ }
232
+ }
223
233
 
224
- for (let evt in listeners) {
225
- const evts = evt.split('-')
226
- if (evts.length===1) el.addEventListener(evt, listeners[evt]);
227
- else {
228
- const startFn = listeners[evt]
229
- const nextEvt = (fn, cur=0) => {
230
- el.addEventListener(evts[cur], listeners[evt] = e => {
231
- fn = fn(e)
232
- el.removeEventListener(evts[cur], listeners[evt])
233
- if (++cur < evts.length && typeof fn === 'function') nextEvt(fn, cur)
234
- else nextEvt(startFn)
235
- })
236
- }
237
- nextEvt(startFn)
234
+ const addListener = (el, evt, startFn) => {
235
+ if (evt.indexOf('..')<0) el.addEventListener(evt, startFn);
236
+
237
+ // sequences like `a..b`: fn
238
+ else {
239
+ const evts = evt.split('..').map(e => e.startsWith('on') ? e.slice(2) : e)
240
+ const nextEvt = (fn, cur=0) => {
241
+ let curListener = e => {
242
+ el.removeEventListener(evts[cur], curListener)
243
+ if (typeof (fn = fn.call(el,e)) !== 'function') fn = ()=>{}
244
+ if (++cur < evts.length) nextEvt(fn, cur);
245
+ else if (!startFn[_stop]) console.log('reset'), nextEvt(startFn); // update only if chain isn't stopped
238
246
  }
239
- };
247
+ el.addEventListener(evts[cur],curListener)
248
+ }
249
+ nextEvt(startFn)
240
250
  }
241
251
  }
242
252
 
243
- directives['data'] = (el, expr, values) => {
253
+ const removeListener = (el, evt, fn) => {
254
+ if (evt.indexOf('..')>=0) console.log('rewire'), fn[_stop] = true
255
+ el.removeEventListener(evt, fn);
256
+ }
257
+
258
+ directives['data'] = (el, expr) => {
244
259
  let evaluate = parseExpr(el, expr, ':data')
245
260
 
246
261
  return ((state) => {
@@ -249,7 +264,7 @@ directives['data'] = (el, expr, values) => {
249
264
  })
250
265
  }
251
266
 
252
- directives['aria'] = (el, expr, values) => {
267
+ directives['aria'] = (el, expr) => {
253
268
  let evaluate = parseExpr(el, expr, ':aria')
254
269
  const update = (value) => {
255
270
  for (let key in value) attr(el, 'aria-' + dashcase(key), value[key] == null ? null : value[key] + '');
@@ -301,4 +316,4 @@ export function exprError(error, element, expression, dir) {
301
316
 
302
317
  function dashcase(str) {
303
318
  return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => '-' + match.toLowerCase());
304
- };
319
+ };
package/test/test.js CHANGED
@@ -44,6 +44,12 @@ test('common: reactive', async () => {
44
44
  is(el.outerHTML, `<label for="email">email</label><input id="email" name="email" type="email"><a href="//google.com"></a><img src="//google.com">`)
45
45
  })
46
46
 
47
+ test('common: empty strings', async () => {
48
+ let el = h`<x :="" :x=""></x>`
49
+ sprae(el)
50
+ is(el.outerHTML, `<x></x>`)
51
+ })
52
+
47
53
  test('style', async () => {
48
54
  let el = h`<x style="left: 1px" :style="style"></x>`
49
55
  let params = sprae(el, {style: "top: 1px"})
@@ -347,24 +353,40 @@ test('on: base', () => {
347
353
  is(log.value, ['click','x','xx']);
348
354
  })
349
355
 
356
+ test('onevt: this context', e => {
357
+ let el = h`<div :onx="function(){log.push(this)}"></div>`
358
+ let state = sprae(el, {log: []})
359
+ el.dispatchEvent(new window.Event('x'));
360
+ is(state.log, [el])
361
+ })
362
+
363
+ test('on: this in chains refers to el', () => {
364
+ let el = h`<div :ona..onb="function(e){x=this; log.push(1); return () => log.push(2)}"></div>`
365
+ let state = sprae(el, {log:[], x:null})
366
+ el.dispatchEvent(new window.Event('a'));
367
+ is(state.log, [1])
368
+ })
369
+
350
370
  test('on: multiple events', e => {
351
- let el = h`<div :onscroll:onclick="e=>log.push(e.type)"></div>`
371
+ let el = h`<div :onscroll:onclick:onx="e=>log.push(e.type)"></div>`
352
372
  let state = sprae(el, {log:[]})
353
373
 
354
374
  el.dispatchEvent(new window.Event('click'));
355
375
  is(state.log, ['click'])
356
376
  el.dispatchEvent(new window.Event('scroll'));
357
377
  is(state.log, ['click','scroll'])
378
+ el.dispatchEvent(new window.Event('x'));
379
+ is(state.log, ['click','scroll','x'])
358
380
  })
359
381
 
360
382
  test('on: in-out events', e => {
361
383
  // 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:[]})
384
+ // el.setAttribute(':onmousedown..onmouseup', 'e=>(log.push(e.type),e=>log.push(e.type))')
385
+ let el = h`<x :onmousedown..onmouseup="(e) => { x=this; log.push(e.type); return e=>log.push(e.type); }"></x>`
366
386
 
387
+ let state = sprae(el, {log:[],x:null})
367
388
  el.dispatchEvent(new window.Event('mousedown'));
389
+ is(state.x, el);
368
390
  is(state.log, ['mousedown'])
369
391
  el.dispatchEvent(new window.Event('mouseup'));
370
392
  is(state.log, ['mousedown','mouseup'])
@@ -374,7 +396,7 @@ test('on: in-out side-effects', e => {
374
396
  let log = []
375
397
 
376
398
  // 1. skip in event and do directly out
377
- let el = h`<x :onin-onout="io"></x>`
399
+ let el = h`<x :onin..onout="io"></x>`
378
400
  sprae(el, { io(e) {
379
401
  log.push(e.type)
380
402
  return (e) => (log.push(e.type), [1,2,3])
@@ -401,7 +423,7 @@ test('on: in-out side-effects', e => {
401
423
  })
402
424
 
403
425
  test('on: chain of events', e => {
404
- let el = h`<div :onmousedown-onmousemove-onmouseup="e=>(log.push(e.type),e=>(log.push(e.type),e=>log.push(e.type)))"></div>`
426
+ let el = h`<div :onmousedown..onmousemove..onmouseup="e=>(log.push(e.type),e=>(log.push(e.type),e=>log.push(e.type)))"></div>`
405
427
  let state = sprae(el, {log:[]})
406
428
 
407
429
  el.dispatchEvent(new window.Event('mousedown'));
@@ -412,32 +434,62 @@ test('on: chain of events', e => {
412
434
  is(state.log, ['mousedown','mousemove','mouseup'])
413
435
  })
414
436
 
415
- test.skip('with: inline', () => {
416
- let el = h`<x :with="{foo:'bar', baz}"><y :text="foo + baz"></y></x>`
437
+ test('on: state changes between chain of events', e => {
438
+ let el = h`<x :on="{'x..y':fn}"></x>`
439
+ let log = []
440
+ let state = sprae(el, {log, fn: () => console.log('log1')||log.push(1)})
441
+ console.log('xx')
442
+ el.dispatchEvent(new window.Event('x'));
443
+ el.dispatchEvent(new window.Event('x'));
444
+ is(log, [1])
445
+ state.fn = () => (console.log('log1.1')||log.push(1.1), () => log.push(2))
446
+ is(log, [1])
447
+ // console.log('xx')
448
+ // NOTE: state update registers new chain listener before finishing prev chain
449
+ // el.dispatchEvent(new window.Event('x'));
450
+ // el.dispatchEvent(new window.Event('x'));
451
+ // is(log, [1])
452
+ console.log('yy')
453
+ el.dispatchEvent(new window.Event('y'));
454
+ el.dispatchEvent(new window.Event('y'));
455
+ is(log, [1])
456
+ console.log('xx')
457
+ el.dispatchEvent(new window.Event('x'));
458
+ el.dispatchEvent(new window.Event('x'));
459
+ is(log, [1, 1.1])
460
+ console.log('yy')
461
+ el.dispatchEvent(new window.Event('y'));
462
+ el.dispatchEvent(new window.Event('y'));
463
+ is(log, [1, 1.1, 2])
464
+ })
465
+
466
+ test('with: inline', () => {
467
+ let el = h`<x :with="{foo:'bar'}"><y :text="foo + baz"></y></x>`
417
468
  let state = sprae(el, {baz: 'qux'})
418
469
  // FIXME: this doesn't inherit root scope baz property and instead uses hard-initialized one
419
470
  is(el.innerHTML, `<y>barqux</y>`)
420
471
  state.baz = 'quux'
421
472
  is(el.innerHTML, `<y>barquux</y>`)
422
473
  })
423
- test.skip('with: inline reactive', () => {
424
- let el = h`<x :with="{foo:'bar', baz}"><y :text="foo + baz"></y></x>`
474
+ test('with: inline reactive', () => {
475
+ let el = h`<x :with="{foo:'bar'}"><y :text="foo + baz"></y></x>`
425
476
  let baz = signal('qux')
426
- let state = sprae(el, {baz})
477
+ sprae(el, {baz})
427
478
  // FIXME: this doesn't inherit root scope baz property and instead uses hard-initialized one
428
479
  is(el.innerHTML, `<y>barqux</y>`)
429
480
  baz.value = 'quux'
430
481
  is(el.innerHTML, `<y>barquux</y>`)
431
482
  })
432
- test.skip('with: data', () => {
483
+ test('with: data', () => {
433
484
  let el = h`<x :with="x"><y :text="foo"></y></x>`
434
485
  let state = sprae(el, {x:{foo:'bar'}})
435
486
  is(el.innerHTML, `<y>bar</y>`)
436
487
  console.log('update')
437
- Object.assign(state, {x:{foo:'baz'}})
488
+ state.x.foo = 'baz'
489
+ // Object.assign(state, {x:{foo:'baz'}})
438
490
  is(el.innerHTML, `<y>baz</y>`)
439
491
  })
440
- test.skip('with: transparency', () => {
492
+ test('with: transparency', () => {
441
493
  // NOTE: y:text initializes through directive, not through parent
442
494
  // therefore by default :text uses parent's state, not defined by element itself
443
495
  let el = h`<x :with="{foo:'foo'}"><y :with="b" :text="foo+bar"></y></x>`
@@ -446,7 +498,7 @@ test.skip('with: transparency', () => {
446
498
  params.b.bar = 'baz'
447
499
  is(el.innerHTML, `<y>foobaz</y>`)
448
500
  })
449
- test.skip('with: reactive transparency', () => {
501
+ test('with: reactive transparency', () => {
450
502
  let el = h`<x :with="{foo:1}"><y :with="b.c" :text="foo+bar"></y></x>`
451
503
  const bar = signal('2')
452
504
  sprae(el, {b:{c:{bar}}})
@@ -454,7 +506,7 @@ test.skip('with: reactive transparency', () => {
454
506
  bar.value = '3'
455
507
  is(el.innerHTML, `<y>13</y>`)
456
508
  })
457
- test.skip('with: writes to state', () => {
509
+ test('with: writes to state', () => {
458
510
  let a = h`<x :with="{a:1}"><y :on="{x(){a++}}" :text="a"></y></x>`
459
511
  sprae(a)
460
512
  is(a.innerHTML, `<y>1</y>`)
@@ -492,13 +544,19 @@ test(':: reactive values', async () => {
492
544
  is(el.outerHTML, `<x>2</x>`)
493
545
  })
494
546
 
547
+ test(':: null result does nothing', async () => {
548
+ let a = h`<x :="undefined"></x>`
549
+ sprae(a)
550
+ is(a.outerHTML, `<x></x>`)
551
+ })
552
+
495
553
  test(':: scope refers to current element', async () => {
496
554
  let el = h`<x :text="log.push(this)"></x>`
497
555
  let state = sprae(el, {log:[]})
498
556
  is(state.log, [el])
499
557
  })
500
558
 
501
- test.skip(':: scope directives must come first', async () => {
559
+ test.todo(':: scope directives must come first', async () => {
502
560
  // NOTE: we init attributes in order of definition
503
561
  let a = h`<x :text="y" :with="{y:1}" :ref="x"></x>`
504
562
  sprae(a, {})