sprae 2.3.1 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sprae",
3
3
  "description": "DOM microhydration.",
4
- "version": "2.3.1",
4
+ "version": "2.4.0",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
7
7
  "type": "module",
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,40 @@
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? ->
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 enable easy init syntax
124
+ 2. Use `:let="x, y=2"`?
125
+ + Doesn't pollute scope but instead cleanly declares local variables
126
+ + Indicates only local initializer, not subscription
127
+ + Liquid has `assign` tag `{% assign myVar = false %}` - it only initializes variable
128
+ + 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.
129
+ ? call it `:define="x, y, z"`?
130
+ -> it seems `:with="x=1, y=2"` works well. `:let` has dissonance with js'y let.
131
+ ? how to extend state
132
+ 3. `:with.x="1", :with.y="2"`
133
+ + easier to parse, since init code can be _messy_
111
134
 
112
135
  ## [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
136
 
@@ -191,11 +214,24 @@
191
214
  + methods provided in `init` may not have access to scope _yet_.
192
215
  ~- not reliable way to obtain scope via `this.x` - better be explicit as `state.x`
193
216
 
194
- ## [x] :onconnected/:ondisconnected? -> let's move to plugins for now
217
+ ## [x] :onconnected/:ondisconnected? -> nah, just use noname effect or external functionality eg fast-on-load
195
218
 
196
- -> waiting for use-case
219
+ + can be useful for :if, :each handlers, eg to start animation when element appears in DOM.
220
+ - it is not connected-disconnected: it has nothing to do with document: it attaches/detaches from parent.
221
+ - connected-disconnected is too long name
222
+ ? attach-detach?
223
+ ? onmount-onunmount?
224
+ - slows down domdiff
225
+ - can be solved as `<x :if="xxx" :="xxx ? (...) : '">` automatically
226
+
227
+ ## [x] :focus="direct code", :evt="direct code" -> nah, too messy.
228
+
229
+ + makes proper use-case for direct code events
230
+ - doesn't make sense for rective properties inside
231
+ + better fit for special props like `:mount`
232
+ - tons of new special-meaning namespace props
197
233
 
198
- ## [x] Chain of events: often useful to have shared context. -> Try `:onevent-onanotherevent`
234
+ ## [x] Chain of events: often useful to have shared context. -> Try `:onstart..onend`
199
235
 
200
236
  * focus/blur, connected/disconnected, mousedown/mouseup, keydown/keyup, touchstart/touchmove/touchend, dragstart/dragover/dragend, animationstart/animationover/animationend, transitionstart/transitionend
201
237
  ? is there a way to organize handlers for chains of events?
@@ -208,14 +244,35 @@
208
244
  - ? why not :on-focus-blur
209
245
  - ? why not :onfocus-onblur
210
246
  + can be converted from on="{ focusBlur: event }" via dash notation
247
+ - messy error messages
211
248
  + less :on prefixes
212
249
  + has better "flowy" meaning
213
250
  * 2.1 :onfocus-onblur="e => e => {}"
214
251
  + distinctive visually as 1
215
252
  + flowy nature of 2
253
+ - blocks `:onfile-attached` and other dashed events
254
+ - `ona-onb` vs `ona-b-onc` is hard to parse mentally
216
255
  3. :onfocus.onblur="e => e => {}"
217
256
  - looks like a modifier
218
257
  - . can be used as event class onclick.x
258
+ 4. `:onfocus--blur="e => e => {}"`
259
+ + reminds BEM
260
+ - reminds BEM
261
+ 5. `:onfocus..blur="e => e => {}"`
262
+ + reminds range
263
+ + literally means start..end
264
+ + all pros of 2.
265
+ + reminds spray via dots
266
+ - can be confusing if `blur` is event or just property (can that be a property?)
267
+ 6. `:onfocus..onblur="e => e => {}"`
268
+ + all props of 5.
269
+ + more obvious that blur is event.
270
+
271
+ 4. `:onfocus="e => e => {}"` Keep registered pairs of events: just expect focus return blur, etc.
272
+ + Shorter syntax
273
+ + Avoids :onfile-attachment-accepted problem
274
+ - Less verbose and explicit
275
+ - No way to customize sequences, eg. custom events
219
276
 
220
277
  ## [ ] Plugins
221
278
 
@@ -224,9 +281,10 @@
224
281
  - can be solved naturally, unless there's special meaning
225
282
  * @sprae/hcodes: `<x :hcode=""` – provide microformats
226
283
  * @sprae/with
227
- * @sprae/connected
284
+ * @sprae/onconnected
285
+ * @sprae/onvisible?
228
286
 
229
- ## [x] Write any-attributes via :<prop>? -> yep
287
+ ## [x] Write any-attributes via `:<prop>? -> yep`
230
288
 
231
289
  + Since we support attr walking, maybe instead of :on and :prop just allow any attributes?
232
290
  + that would allow event and attr modifiers...
@@ -263,8 +321,10 @@
263
321
  + Provides precisely controlled sandbox
264
322
  - Some limited lang opportunities
265
323
  - need to match many syntax quirks, can be tedious
324
+ ~ can be fine to limit expressions to meaningful default: why Proxy, generators, awaits, global access etc.
266
325
  - Somewhat heavy to bundle
267
- + Scope is easier to provide: no need for signal proxy essentially
326
+ ~ 1-2kb is not super-heavy, besides signal-struct kicks out
327
+ + Scope is easier to provide: no need for signal proxy
268
328
  + Can detect access errors in advance
269
329
  + Syntax-level access to signals can be inavoidable: external signals still "leak in" (via arrays or etc.).
270
330
  + Updating simple objects should also rerender the template parts, not just signals.
@@ -272,6 +332,9 @@
272
332
  - Screwed up debugging / stacktrace (unless errored)
273
333
  + that "unlimits" returned struct, so that any property can be added/deleted.
274
334
  - doesn't really save from `new (()=>{}).constructor` hack: we gotta substitute objects too.
335
+ + allows easier handle of `:with="a=1,b=2,c=3"` - we just naturally get local variables without messup with global
336
+ + we can even define locals without `let`...
337
+ - not having "comfy" compatible JS at hand: cognitive load of whole language "layer" in-between
275
338
 
276
339
  2. Use sandboxed proxy
277
340
  - tough evaluation
@@ -297,4 +360,33 @@
297
360
  ## [ ] Better :ref
298
361
  + :ref="`a-${1}`"
299
362
  + :id:ref="xyz"
300
- ? maybe id should have same signature
363
+ ? maybe id should have same signature
364
+ ? should it be very similar mechanism to `:with="a=1,b=2"`
365
+
366
+ ## [ ] Event modifiers :ona.once, `:ona`
367
+
368
+ - .prevent,.stop - not needed since expects a function
369
+ ? or should we just trigger it for user?
370
+ ? :onclick.outside
371
+ ? :onclick.window, :onclick.document
372
+ ? :onclick.once
373
+ ? :onclick.debounce
374
+ ? :onclick.throttle.750ms
375
+ ? :onclick.self
376
+ ? :onspecial-event.camel, :onx-y.dot
377
+ ? :onclick.passive
378
+ ? :onkeypress.shift.enter
379
+ .shift Shift
380
+ .enter Enter
381
+ .space Space
382
+ .ctrl Ctrl
383
+ .cmd Cmd
384
+ .meta Cmd on Mac, Windows key on Windows
385
+ .alt Alt
386
+ .up .down .left .right Up/Down/Left/Right arrows
387
+ .escape Escape
388
+ .tab Tab
389
+ .caps-lock Caps Lock
390
+ .equal Equal, =
391
+ .period Period, .
392
+ .slash Foward Slash, /
package/readme.md CHANGED
@@ -89,41 +89,44 @@ Welcome, <span :text="user.name">Guest</span>.
89
89
 
90
90
  #### `:class="value"`
91
91
 
92
- Set class value from either string, array or object.
92
+ Set class value from either string, array or object. Extends direct class, rather than replaces.
93
93
 
94
94
  ```html
95
95
  <div :class="`foo ${bar}`"></div>
96
96
  <div :class="['foo', 'bar']"></div>
97
97
  <div :class="{foo: true, bar: false}"></div>
98
+
99
+ <div class="a" :class="['b', 'c']"></div>
100
+ <!--
101
+ <div class="a b c"></div>
102
+ -->
98
103
  ```
99
104
 
100
105
  #### `:style="value"`
101
106
 
102
- Set style value from object or a string.
107
+ Set style value from object or a string. Extends style.
103
108
 
104
109
  ```html
105
110
  <div :style="foo: bar"></div>
106
111
  <div :style="{foo: 'bar'}"></div>
107
112
  ```
108
113
 
109
- <!--
110
114
  #### `:value="value"`
111
115
 
112
- Set value of an input, textarea or select.
116
+ Set value of an input, textarea or select. Takes handle of `checked` and `selected` attributes.
113
117
 
114
118
  ```html
115
- <input :with="{text: ''}" :value="text" />
116
- <textarea :with="{text: ''}" :value="text" />
119
+ <input :value="text" />
120
+ <textarea :value="text" />
117
121
 
118
- <select :with="{selected: 0}" :value="selected">
122
+ <select :value="selected">
119
123
  <option :each="i in 5" :value="i" :text="i"></option>
120
124
  </select>
121
125
  ```
122
- -->
123
126
 
124
- #### `:<prop>="value"`, `:="props"`
127
+ #### `:<prop>="value?"`, `:="props?"`
125
128
 
126
- Set any prop value.
129
+ Set any prop value or run effect.
127
130
 
128
131
  ```html
129
132
  <!-- Single property -->
@@ -134,9 +137,12 @@ Set any prop value.
134
137
 
135
138
  <!-- Bulk properties -->
136
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>
137
143
  ```
138
144
 
139
- #### `:on<event>="handler"`, `:on="events"`
145
+ #### `:on<event>="handler"`, `:on="events"`, `:<in>..<out>="handler"`
140
146
 
141
147
  Add event listeners.
142
148
 
@@ -148,7 +154,7 @@ Add event listeners.
148
154
  <input :value="text" :oninput:onchange="e => text=e.target.value">
149
155
 
150
156
  <!-- Sequence of events -->
151
- <button :onfocus-onblur="e => {
157
+ <button :onfocus..onblur="e => {
152
158
  // onfocus
153
159
  let id = setInterval(track,200)
154
160
  return e => {
@@ -161,23 +167,6 @@ Add event listeners.
161
167
  <button :on="{ click: handler, touch: handler, special: handler }">Submit</button>
162
168
  ```
163
169
 
164
- <!--
165
- #### `:oninit="fn"`
166
-
167
- Create initializer handler - called whenever element is initialized by sprae.
168
-
169
- ```html
170
- <div id="foo" :oninit="function(){ this === foo }"></div>
171
- ```
172
-
173
- #### `:onconnect="fn"`, `:ondisconnect="fn"`
174
-
175
- Invoked when element is connected or disconnected from DOM.
176
-
177
- ```html
178
- ```
179
- -->
180
-
181
170
  #### `:data="values"`
182
171
 
183
172
  Set [data-*](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*) attributes. CamelCase is converted to dash-case.
@@ -217,9 +206,7 @@ Expose element to data scope with the `id`:
217
206
  <!-- iterable items -->
218
207
  <ul>
219
208
  <li :each="item in items" :ref="item">
220
- <input
221
- :onfocus-onblur="e => (item.classList.add('editing'), e => item.classList.remove('editing'))"
222
- />
209
+ <input :onfocus..onblur="e => (item.classList.add('editing'), e => item.classList.remove('editing'))"/>
223
210
  </li>
224
211
  </ul>
225
212
 
@@ -325,8 +312,9 @@ _sprae_ takes convention of _templize directives_ (_alpine_/_vue_ attrs) and bui
325
312
  * It falls back to element content if uninitialized.
326
313
  * It doesn't enforce SPA nor JSX.
327
314
  * It enables island hydration.
328
- * It introduces minimal syntax space as `:` convention.
315
+ * It reserves minimal syntax space as `:` convention (keeping tree neatly decorated, not scattered).
329
316
  * Expressions are naturally reactive and incur minimal updates.
330
317
  * Input data may contain [signals](https://ghub.io/@preact/signals) or [reactive values](https://ghub.io/sube).
318
+ * Elements / data API is open and enable easy interop.
331
319
 
332
320
  <p align="center"><a href="https://github.com/krsnzd/license/">🕉</a></p>
package/sprae.js CHANGED
@@ -452,11 +452,14 @@ var primitive_pool_default = (key) => {
452
452
  // src/directives.js
453
453
  var directives = {};
454
454
  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
- }
455
+ let evt = name.startsWith("on") && name.slice(2);
458
456
  let evaluate = parseExpr(el, expr, ":" + name);
459
- return (state) => attr(el, name, evaluate(state));
457
+ let value;
458
+ return evt ? (state) => {
459
+ value && removeListener(el, evt, value);
460
+ value = evaluate(state);
461
+ value && addListener(el, evt, value);
462
+ } : (state) => attr(el, name, evaluate(state));
460
463
  };
461
464
  var attr = (el, name, v2) => {
462
465
  if (v2 == null || v2 === false)
@@ -482,6 +485,13 @@ directives["ref"] = (el, expr, values) => {
482
485
  ;
483
486
  values[expr] = el;
484
487
  };
488
+ directives["with"] = (el, expr, rootState) => {
489
+ let evaluate = parseExpr(el, expr, "with");
490
+ throw "Unimplemented";
491
+ let withValues = evaluate(rootState);
492
+ let newState = Object.assign(Object.create(rootState), withValues);
493
+ sprae(el, newState);
494
+ };
485
495
  directives["if"] = (el, expr, values) => {
486
496
  let holder = document.createTextNode(""), clauses = [parseExpr(el, expr, ":if")], els = [el], cur = el;
487
497
  while (cur = el.nextElementSibling) {
@@ -591,10 +601,13 @@ directives["class"] = (el, expr, values) => {
591
601
  };
592
602
  directives["style"] = (el, expr, values) => {
593
603
  let evaluate = parseExpr(el, expr, ":style");
604
+ let initStyle = el.getAttribute("style") || "";
605
+ if (!initStyle.endsWith(";"))
606
+ initStyle += "; ";
594
607
  return (state) => {
595
608
  let v2 = evaluate(state);
596
609
  if (typeof v2 === "string")
597
- el.setAttribute("style", v2);
610
+ el.setAttribute("style", initStyle + v2);
598
611
  else
599
612
  for (let k in v2)
600
613
  el.style[k] = v2[k];
@@ -608,39 +621,53 @@ directives["text"] = (el, expr, values) => {
608
621
  return (state) => update(evaluate(state));
609
622
  };
610
623
  directives["value"] = (el, expr, values) => {
611
- let evaluate = parseExpr(el, expr, ":in");
612
- 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) => ([...el.options].map((el2) => el2.removeAttribute("selected")), el.value = value, el.selectedOptions[0]?.setAttribute("selected", "")) : (value) => el.value = value;
624
+ let evaluate = parseExpr(el, expr, ":value");
625
+ 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) => {
626
+ for (let option in el.options)
627
+ option.removeAttribute("selected");
628
+ el.value = value;
629
+ el.selectedOptions[0]?.setAttribute("selected", "");
630
+ } : (value) => el.value = value;
613
631
  return (state) => update(evaluate(state));
614
632
  };
633
+ var _stop = Symbol("stop");
615
634
  directives["on"] = (el, expr, values) => {
616
635
  let evaluate = parseExpr(el, expr, ":on");
617
636
  let listeners = {};
618
637
  return (state) => {
619
638
  for (let evt in listeners)
620
- el.removeEventListener(evt, listeners[evt]);
639
+ removeListener(el, evt, listeners[evt]);
621
640
  listeners = evaluate(state);
622
- for (let evt in listeners) {
623
- const evts = evt.split("-");
624
- if (evts.length === 1)
625
- el.addEventListener(evt, listeners[evt]);
626
- else {
627
- const startFn = listeners[evt];
628
- const nextEvt = (fn, cur = 0) => {
629
- el.addEventListener(evts[cur], listeners[evt] = (e2) => {
630
- fn = fn(e2);
631
- el.removeEventListener(evts[cur], listeners[evt]);
632
- if (++cur < evts.length && typeof fn === "function")
633
- nextEvt(fn, cur);
634
- else
635
- nextEvt(startFn);
636
- });
637
- };
638
- nextEvt(startFn);
639
- }
640
- }
641
- ;
641
+ for (let evt in listeners)
642
+ addListener(el, evt, listeners[evt]);
642
643
  };
643
644
  };
645
+ var addListener = (el, evt, startFn) => {
646
+ if (evt.indexOf("..") < 0)
647
+ el.addEventListener(evt, startFn);
648
+ else {
649
+ const evts = evt.split("..").map((e2) => e2.startsWith("on") ? e2.slice(2) : e2);
650
+ const nextEvt = (fn, cur = 0) => {
651
+ let curListener = (e2) => {
652
+ el.removeEventListener(evts[cur], curListener);
653
+ if (typeof (fn = fn.call(el, e2)) !== "function")
654
+ fn = () => {
655
+ };
656
+ if (++cur < evts.length)
657
+ nextEvt(fn, cur);
658
+ else if (!startFn[_stop])
659
+ console.log("reset"), nextEvt(startFn);
660
+ };
661
+ el.addEventListener(evts[cur], curListener);
662
+ };
663
+ nextEvt(startFn);
664
+ }
665
+ };
666
+ var removeListener = (el, evt, fn) => {
667
+ if (evt.indexOf("..") >= 0)
668
+ console.log("rewire"), fn[_stop] = true;
669
+ el.removeEventListener(evt, fn);
670
+ };
644
671
  directives["data"] = (el, expr, values) => {
645
672
  let evaluate = parseExpr(el, expr, ":data");
646
673
  return (state) => {
@@ -712,6 +739,8 @@ function sprae(container, values) {
712
739
  }
713
740
  el.removeAttribute(attr2.name);
714
741
  let expr = attr2.value;
742
+ if (!expr)
743
+ continue;
715
744
  let attrNames = attr2.name.slice(1).split(":");
716
745
  for (let attrName of attrNames) {
717
746
  let dir = directives[attrName] || directives_default;
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={},$={},k={},W={},C=(t,e,i,r)=>{if(r.startsWith("on"))return W.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:"")};W[""]=(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");W.ref=(t,e,i)=>{t.hasAttribute(":each")?t[L]=e:i[e]=t},W.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))}},W.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?k:"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])}},W.id=(t,e,i)=>{let r=D(t,e,":id");return e=>{return i=r(e),t.id=i||0===i?i:"";var i}},W.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(" ")}},W.style=(t,e,i)=>{let r=D(t,e,":style");return e=>{let i=r(e);if("string"==typeof i)t.setAttribute("style",i);else for(let e in i)t.style[e]=i[e]}},W.text=(t,e,i)=>{let r=D(t,e,":text");return e=>{return i=r(e),void(t.textContent=null==i?"":i);var i}},W.value=(t,e,i)=>{let r=D(t,e,":in"),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=>([...t.options].map((t=>t.removeAttribute("selected"))),t.value=e,t.selectedOptions[0]?.setAttribute("selected","")):e=>t.value=e;return t=>n(r(t))},W.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)}}}},W.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]}},W.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=W[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,w=Symbol("signal-struct");function x(t){if((e=t)&&e[w])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,w,{configurable:!1,enumerable:!1,value:!0}),Object.seal(i),i}return Array.isArray(t)?t.map((t=>x(t))):t}function A(t,e,i){let r,n=S(i)?i:O(i)||Array.isArray(i)?a(x(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)&&O(i)?t=>t?Object.assign(n.value,t):n.value=x(t):t=>n.value=x(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,l=i.length,a=e.length,{remove:u,same:h,insert:c,replace:v}=j;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};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={},$={},U=(t,e,i,r)=>{let n,o=r.startsWith("on")&&r.slice(2),s=T(t,e,":"+r);return o?e=>{n&&_(t,o,n),n=s(e),n&&D(t,o,n)}:e=>C(t,r,s(e))},C=(t,e,i)=>{null==i||!1===i?t.removeAttribute(e):t.setAttribute(e,!0===i?"":"number"==typeof i||"string"==typeof i?i:"")};$[""]=(t,e,i)=>{let r=T(t,e,":");return e=>{let i=r(e);for(let e in i)C(t,I(e),i[e])}};var B=Symbol(":each"),L=Symbol(":ref");$.ref=(t,e,i)=>{t.hasAttribute(":each")?t[L]=e:i[e]=t},$.with=(t,e,i)=>{throw T(t,e,"with"),"Unimplemented"},$.if=(t,e,i)=>{let r=document.createTextNode(""),n=[T(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(T(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),R(s,t))}},$.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 F(new Error,t,e);const n=t[B]=document.createTextNode("");t.replaceWith(n);const o=T(t,r.items,":each"),s=new WeakMap,f=new WeakMap;let l=[];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])):F(Error("Bad list value"),t,r.items,":each"):i=[];let a=[],u=[];for(let[n,o]of i){let i=null===(h=o)?W:void 0===h?k:"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,l=f.get(i);if(l||(l=t.cloneNode(!0),f.set(i,l)),a.push(l),!s.has(i)){let f=Object.create(e);f[r.item]=o,r.index&&(f[r.index]=n),t[L]&&(f[t[L]]=l),s.set(i,f)}u.push(s.get(i))}var h;N(n.parentNode,l,a,n),l=a;for(let t=0;t<a.length;t++)R(a[t],u[t])}},$.id=(t,e,i)=>{let r=T(t,e,":id");return e=>{return i=r(e),t.id=i||0===i?i:"";var i}},$.class=(t,e,i)=>{let r=T(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(" ")}},$.style=(t,e,i)=>{let r=T(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]}},$.text=(t,e,i)=>{let r=T(t,e,":text");return e=>{return i=r(e),void(t.textContent=null==i?"":i);var i}},$.value=(t,e,i)=>{let r=T(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":"",C(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))};var P=Symbol("stop");$.on=(t,e,i)=>{let r=T(t,e,":on"),n={};return e=>{for(let e in n)_(t,e,n[e]);n=r(e);for(let e in n)D(t,e,n[e])}};var D=(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[P]||(console.log("reset"),n(i))};t.addEventListener(r[o],s)};n(i)}},_=(t,e,i)=>{e.indexOf("..")>=0&&(console.log("rewire"),i[P]=!0),t.removeEventListener(e,i)};$.data=(t,e,i)=>{let r=T(t,e,":data");return e=>{let i=r(e);for(let e in i)t.dataset[e]=i[e]}},$.aria=(t,e,i)=>{let r=T(t,e,":aria");return e=>(e=>{for(let i in e)C(t,"aria-"+I(i),null==e[i]?null:e[i]+"")})(r(e))};var M={};function T(t,e,i){if(M[e])return M[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 F(r,t,e,i)}return M[e]=n=>{let o;try{o=r(n)}catch(r){return F(r,t,e,i)}return o}}function F(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 I(t){return t.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(t=>"-"+t.toLowerCase()))}var z=new WeakMap;function R(t,e){if(!t.children)return;if(z.has(t))return z.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;if(!s)continue;let f=o.name.slice(1).split(":");for(let r of f){let o=$[r]||U;if(i.push(o(t,s,e,r)||(()=>{})),z.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=x(e);for(let t of i)m((()=>t(n)));return z.set(t,n),n}var Z=R;export{Z as default};
package/src/core.js CHANGED
@@ -9,6 +9,7 @@ export default function sprae(container, values) {
9
9
  if (memo.has(container)) return memo.get(container)
10
10
 
11
11
  values ||= {};
12
+ // FIXME: init values must read "snapshot" of signals to avoid leaking actual signals to directives
12
13
 
13
14
  const updates = []
14
15
 
@@ -20,6 +21,7 @@ export default function sprae(container, values) {
20
21
  if (attr.name[0] !== ':') {i++; continue}
21
22
  el.removeAttribute(attr.name)
22
23
  let expr = attr.value
24
+ if (!expr) continue
23
25
  let attrNames = attr.name.slice(1).split(':')
24
26
  for (let attrName of attrNames) {
25
27
  let dir = directives[attrName] || defaultDirective;
package/src/directives.js CHANGED
@@ -8,14 +8,16 @@ export const directives = {}
8
8
 
9
9
  // any-prop directives
10
10
  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
- }
11
+ let evt = name.startsWith('on') && name.slice(2)
16
12
  let evaluate = parseExpr(el, expr, ':'+name)
17
13
 
18
- return (state) => attr(el, name, evaluate(state))
14
+ let value
15
+ return evt ? state => {
16
+ value && removeListener(el, evt, value)
17
+ value = evaluate(state)
18
+ value && addListener(el, evt, value)
19
+ }
20
+ : state => attr(el, name, evaluate(state))
19
21
  }
20
22
 
21
23
  // set attr
@@ -43,6 +45,30 @@ directives['ref'] = (el, expr, values) => {
43
45
  values[expr] = el;
44
46
  }
45
47
 
48
+ directives['with'] = (el, expr, rootState) => {
49
+ let evaluate = parseExpr(el, expr, 'with')
50
+
51
+ throw 'Unimplemented';
52
+
53
+ // prev iteration
54
+ // // Instead of extending signals (which is a bit hard since signal-struct internals is not uniform)
55
+ // // we bind updating
56
+ // const params = computed(() => Object.assign({}, rootState, evaluate(rootState)))
57
+
58
+ // // NOTE: initialized element doesn't proceed with its children
59
+ // let state = sprae(el, params.value)
60
+
61
+ // // but update is still called with parent state
62
+ // return (rootState) => batch(() => Object.assign(state, params.value))
63
+
64
+
65
+ // must be as simple as
66
+ // don't care if root state updates - we're subscribed to it via inheritance _or_ shadowing it
67
+ let withValues = evaluate(rootState);
68
+ let newState = Object.assign(Object.create(rootState), withValues)
69
+ sprae(el, newState)
70
+ }
71
+
46
72
  directives['if'] = (el, expr, values) => {
47
73
  let holder = document.createTextNode(''),
48
74
  clauses = [parseExpr(el, expr, ':if')],
@@ -175,9 +201,11 @@ directives['class'] = (el, expr, values) => {
175
201
 
176
202
  directives['style'] = (el, expr, values) => {
177
203
  let evaluate = parseExpr(el, expr, ':style')
204
+ let initStyle = el.getAttribute('style') || ''
205
+ if (!initStyle.endsWith(';')) initStyle += '; '
178
206
  return (state) => {
179
207
  let v = evaluate(state)
180
- if (typeof v === 'string') el.setAttribute('style', v)
208
+ if (typeof v === 'string') el.setAttribute('style', initStyle + v)
181
209
  else for (let k in v) el.style[k] = v[k]
182
210
  }
183
211
  }
@@ -194,50 +222,58 @@ directives['text'] = (el, expr, values) => {
194
222
 
195
223
  // connect expr to element value
196
224
  directives['value'] = (el, expr, values) => {
197
- let evaluate = parseExpr(el, expr, ':in')
225
+ let evaluate = parseExpr(el, expr, ':value')
198
226
 
199
227
  let update = (
200
228
  el.type === 'text' || el.type === '' ? value => el.setAttribute('value', el.value = value == null ? '' : value) :
201
229
  el.type === 'checkbox' ? value => (el.value = value ? 'on' : '', attr(el, 'checked', value)) :
202
- el.type === 'select-one' ? value => (
203
- [...el.options].map(el => el.removeAttribute('selected')),
204
- el.value = value,
230
+ el.type === 'select-one' ? value => {
231
+ for (let option in el.options) option.removeAttribute('selected')
232
+ el.value = value;
205
233
  el.selectedOptions[0]?.setAttribute('selected', '')
206
- ) :
234
+ } :
207
235
  value => el.value = value
208
236
  )
209
237
 
210
238
  return (state) => update(evaluate(state))
211
239
  }
212
240
 
241
+ const _stop = Symbol('stop')
213
242
  directives['on'] = (el, expr, values) => {
214
243
  let evaluate = parseExpr(el, expr, ':on')
215
244
  let listeners = {}
216
245
 
217
246
  return (state) => {
218
- for (let evt in listeners) el.removeEventListener(evt, listeners[evt]);
219
-
247
+ for (let evt in listeners) removeListener(el, evt, listeners[evt])
220
248
  listeners = evaluate(state);
249
+ for (let evt in listeners) addListener(el, evt, listeners[evt])
250
+ }
251
+ }
221
252
 
222
- for (let evt in listeners) {
223
- const evts = evt.split('-')
224
- if (evts.length===1) el.addEventListener(evt, listeners[evt]);
225
- else {
226
- const startFn = listeners[evt]
227
- const nextEvt = (fn, cur=0) => {
228
- el.addEventListener(evts[cur], listeners[evt] = e => {
229
- fn = fn(e)
230
- el.removeEventListener(evts[cur], listeners[evt])
231
- if (++cur < evts.length && typeof fn === 'function') nextEvt(fn, cur)
232
- else nextEvt(startFn)
233
- })
234
- }
235
- nextEvt(startFn)
253
+ const addListener = (el, evt, startFn) => {
254
+ if (evt.indexOf('..')<0) el.addEventListener(evt, startFn);
255
+
256
+ // sequences like `a..b`: fn
257
+ else {
258
+ const evts = evt.split('..').map(e => e.startsWith('on') ? e.slice(2) : e)
259
+ const nextEvt = (fn, cur=0) => {
260
+ let curListener = e => {
261
+ el.removeEventListener(evts[cur], curListener)
262
+ if (typeof (fn = fn.call(el,e)) !== 'function') fn = ()=>{}
263
+ if (++cur < evts.length) nextEvt(fn, cur);
264
+ else if (!startFn[_stop]) console.log('reset'), nextEvt(startFn); // update only if chain isn't stopped
236
265
  }
237
- };
266
+ el.addEventListener(evts[cur],curListener)
267
+ }
268
+ nextEvt(startFn)
238
269
  }
239
270
  }
240
271
 
272
+ const removeListener = (el, evt, fn) => {
273
+ if (evt.indexOf('..')>=0) console.log('rewire'), fn[_stop] = true
274
+ el.removeEventListener(evt, fn);
275
+ }
276
+
241
277
  directives['data'] = (el, expr, values) => {
242
278
  let evaluate = parseExpr(el, expr, ':data')
243
279
 
@@ -299,4 +335,4 @@ export function exprError(error, element, expression, dir) {
299
335
 
300
336
  function dashcase(str) {
301
337
  return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => '-' + match.toLowerCase());
302
- };
338
+ };
package/test/test.js CHANGED
@@ -44,15 +44,21 @@ 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: style', async () => {
48
- let el = h`<x :style="style"></x>`
47
+ test('common: empty strings', async () => {
48
+ let el = h`<x :="" :x=""></x>`
49
+ sprae(el)
50
+ is(el.outerHTML, `<x></x>`)
51
+ })
52
+
53
+ test('style', async () => {
54
+ let el = h`<x style="left: 1px" :style="style"></x>`
49
55
  let params = sprae(el, {style: "top: 1px"})
50
- is(el.outerHTML, `<x style="top: 1px"></x>`)
56
+ is(el.outerHTML, `<x style="left: 1px; top: 1px"></x>`)
51
57
  params.style = {top: '2px'}
52
- is(el.outerHTML, `<x style="top: 2px;"></x>`)
58
+ is(el.outerHTML, `<x style="left: 1px; top: 2px;"></x>`)
53
59
  })
54
60
 
55
- test('common: class', async () => {
61
+ test('class', async () => {
56
62
  let el = h`<x :class="a"></x><y :class="[b, c]"></y><z :class="{b:true, c:d}"></z>`
57
63
  const c = signal('z')
58
64
  let params = sprae(el, {a:'x', b:'y', c, d:false});
@@ -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,7 +434,36 @@ test('on: chain of events', e => {
412
434
  is(state.log, ['mousedown','mousemove','mouseup'])
413
435
  })
414
436
 
415
- test.skip('with: inline', () => {
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.todo('with: inline', () => {
416
467
  let el = h`<x :with="{foo:'bar', baz}"><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
@@ -420,7 +471,7 @@ test.skip('with: inline', () => {
420
471
  state.baz = 'quux'
421
472
  is(el.innerHTML, `<y>barquux</y>`)
422
473
  })
423
- test.skip('with: inline reactive', () => {
474
+ test.todo('with: inline reactive', () => {
424
475
  let el = h`<x :with="{foo:'bar', baz}"><y :text="foo + baz"></y></x>`
425
476
  let baz = signal('qux')
426
477
  let state = sprae(el, {baz})
@@ -429,7 +480,7 @@ test.skip('with: inline reactive', () => {
429
480
  baz.value = 'quux'
430
481
  is(el.innerHTML, `<y>barquux</y>`)
431
482
  })
432
- test.skip('with: data', () => {
483
+ test.todo('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>`)
@@ -437,7 +488,7 @@ test.skip('with: data', () => {
437
488
  Object.assign(state, {x:{foo:'baz'}})
438
489
  is(el.innerHTML, `<y>baz</y>`)
439
490
  })
440
- test.skip('with: transparency', () => {
491
+ test.todo('with: transparency', () => {
441
492
  // NOTE: y:text initializes through directive, not through parent
442
493
  // therefore by default :text uses parent's state, not defined by element itself
443
494
  let el = h`<x :with="{foo:'foo'}"><y :with="b" :text="foo+bar"></y></x>`
@@ -446,7 +497,7 @@ test.skip('with: transparency', () => {
446
497
  params.b.bar = 'baz'
447
498
  is(el.innerHTML, `<y>foobaz</y>`)
448
499
  })
449
- test.skip('with: reactive transparency', () => {
500
+ test.todo('with: reactive transparency', () => {
450
501
  let el = h`<x :with="{foo:1}"><y :with="b.c" :text="foo+bar"></y></x>`
451
502
  const bar = signal('2')
452
503
  sprae(el, {b:{c:{bar}}})
@@ -454,7 +505,7 @@ test.skip('with: reactive transparency', () => {
454
505
  bar.value = '3'
455
506
  is(el.innerHTML, `<y>13</y>`)
456
507
  })
457
- test.skip('with: writes to state', () => {
508
+ test.todo('with: writes to state', () => {
458
509
  let a = h`<x :with="{a:1}"><y :on="{x(){a++}}" :text="a"></y></x>`
459
510
  sprae(a)
460
511
  is(a.innerHTML, `<y>1</y>`)
@@ -492,13 +543,19 @@ test(':: reactive values', async () => {
492
543
  is(el.outerHTML, `<x>2</x>`)
493
544
  })
494
545
 
546
+ test(':: null result does nothing', async () => {
547
+ let a = h`<x :="undefined"></x>`
548
+ sprae(a)
549
+ is(a.outerHTML, `<x></x>`)
550
+ })
551
+
495
552
  test(':: scope refers to current element', async () => {
496
553
  let el = h`<x :text="log.push(this)"></x>`
497
554
  let state = sprae(el, {log:[]})
498
555
  is(state.log, [el])
499
556
  })
500
557
 
501
- test.skip(':: scope directives must come first', async () => {
558
+ test.todo(':: scope directives must come first', async () => {
502
559
  // NOTE: we init attributes in order of definition
503
560
  let a = h`<x :text="y" :with="{y:1}" :ref="x"></x>`
504
561
  sprae(a, {})