sprae 2.3.0 → 2.3.2

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.0",
4
+ "version": "2.3.2",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
7
7
  "type": "module",
package/plan.md CHANGED
@@ -30,7 +30,6 @@
30
30
  * [ ] Sandbox expressions: no global, no "scope" object name, no "arguments"
31
31
  * ~~[x] report usignal problem~~ author is not really interested
32
32
  * [ ] :oninit
33
- * [ ] :onconnect, :ondisconnect
34
33
  * [ ] frameworks benchmark
35
34
  * [ ] examples
36
35
  * [x] todomvc
package/r&d.md CHANGED
@@ -191,7 +191,7 @@
191
191
  + methods provided in `init` may not have access to scope _yet_.
192
192
  ~- not reliable way to obtain scope via `this.x` - better be explicit as `state.x`
193
193
 
194
- ## [ ] :onconnected/:ondisconnected? -> let's add
194
+ ## [x] :onconnected/:ondisconnected? -> let's move to plugins for now
195
195
 
196
196
  -> waiting for use-case
197
197
 
@@ -223,6 +223,8 @@
223
223
  * @sprae/item: `<x :item="{type:a, scope:b}"` – provide microdata
224
224
  - can be solved naturally, unless there's special meaning
225
225
  * @sprae/hcodes: `<x :hcode=""` – provide microformats
226
+ * @sprae/with
227
+ * @sprae/connected
226
228
 
227
229
  ## [x] Write any-attributes via :<prop>? -> yep
228
230
 
@@ -251,7 +253,7 @@
251
253
  + it has better control over serialization
252
254
  + `:onchange:oninput="e=>xyz"` is very good
253
255
 
254
- ## [ ] Sandbox?
256
+ ## [ ] Sandbox? -> too complex for now. Waiting for use-cases
255
257
 
256
258
  1. Use subscript?
257
259
  + solves access to any internal signals on syntactic level
@@ -290,4 +292,9 @@
290
292
  ## [x] Should getters convert to computed? -> yes, that's relatively cheap and useful
291
293
 
292
294
  + shorter and nicer syntax
293
- - possibly longer inti
295
+ - possibly longer init
296
+
297
+ ## [ ] Better :ref
298
+ + :ref="`a-${1}`"
299
+ + :id:ref="xyz"
300
+ ? maybe id should have same signature
package/readme.md CHANGED
@@ -89,37 +89,40 @@ 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
127
  #### `:<prop>="value"`, `:="props"`
125
128
 
@@ -148,9 +151,11 @@ Add event listeners.
148
151
  <input :value="text" :oninput:onchange="e => text=e.target.value">
149
152
 
150
153
  <!-- Sequence of events -->
151
- <button :onfocus-onblur="e => { // onfocus
154
+ <button :onfocus-onblur="e => {
155
+ // onfocus
152
156
  let id = setInterval(track,200)
153
- return e => { // onblur
157
+ return e => {
158
+ // onblur
154
159
  clearInterval(id)
155
160
  }
156
161
  }">
@@ -182,6 +187,9 @@ Set [data-*](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes
182
187
 
183
188
  ```html
184
189
  <input :data="{foo: 1, barBaz: true}" />
190
+ <!--
191
+ <input data-foo="1" data-bar-baz="true" />
192
+ -->
185
193
  ```
186
194
 
187
195
  #### `:aria="values"`
@@ -189,29 +197,31 @@ Set [data-*](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes
189
197
  Set [aria-role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) attributes. Boolean values are stringified.
190
198
 
191
199
  ```html
192
- <input type="text" id="jokes" role="combobox" :aria="{
200
+ <input role="combobox" :aria="{
193
201
  controls: 'joketypes',
194
202
  autocomplete: 'list',
195
203
  expanded: false,
196
204
  activeOption: 'item1',
197
205
  activedescendant: ''
198
206
  }" />
207
+ <!--
208
+ <input role="combobox" aria-controls="joketypes" aria-autocomplete="list" aria-expanded="false" aria-active-option="item1" aria-activedescendant="">
209
+ -->
199
210
  ```
200
211
 
201
212
  #### `:ref="id"`
202
213
 
203
- Expose element to a subtree fragment with the `id`, as well as to scope:
214
+ Expose element to data scope with the `id`:
204
215
 
205
216
  ```html
206
217
  <!-- single item -->
207
- <textarea :id:ref="text" placeholder="Enter text..."></textarea>
218
+ <textarea :ref="text" placeholder="Enter text..."></textarea>
208
219
 
209
220
  <!-- iterable items -->
210
221
  <ul>
211
222
  <li :each="item in items" :ref="item">
212
223
  <input
213
- :onfocus="e => item.classList.add('editing')"
214
- :onblur="e => item.classList.remove('editing')"
224
+ :onfocus-onblur="e => (item.classList.add('editing'), e => item.classList.remove('editing'))"
215
225
  />
216
226
  </li>
217
227
  </ul>
@@ -220,8 +230,8 @@ Expose element to a subtree fragment with the `id`, as well as to scope:
220
230
  import sprae from 'sprae';
221
231
  let state = sprae(document, {items: ['a','b','c']})
222
232
 
223
- // exposes element in state
224
- state.text // <textarea id="text"></textarea>
233
+ // element is in the state
234
+ state.text // <textarea></textarea>
225
235
  </script>
226
236
  ```
227
237
 
@@ -249,7 +259,7 @@ This way, for example, _@preact/signals_ or _rxjs_ can be connected directly byp
249
259
 
250
260
  ## Hints
251
261
 
252
- **1.** Data allows signals values, which can be an alternative way to control template state:
262
+ **1.** Data supports signal values, which can be an alternative way to control template state:
253
263
 
254
264
  ```html
255
265
  <div id="done" :text="loading ? 'loading' : result">...</div>
@@ -289,7 +299,7 @@ This way, for example, _@preact/signals_ or _rxjs_ can be connected directly byp
289
299
 
290
300
  ```html
291
301
  <div id="x-plus-y">
292
- <span :text="x">x</span> + <span :text="y">y</span> = <span :text="sum">z</span>
302
+ <span :text="x">x</span> + <span :text="y">y</span> = <span :text="z">z</span>
293
303
  </div>
294
304
 
295
305
  <script type="module">
@@ -297,17 +307,19 @@ This way, for example, _@preact/signals_ or _rxjs_ can be connected directly byp
297
307
  let state = sprae(document, { x:1, y:1, get z() { return this.x + this.y } })
298
308
 
299
309
  state.x = 2, state.y = 2
310
+ state.z // 4
300
311
  </script>
301
312
  ```
302
313
 
303
314
  ## Examples
304
315
 
305
316
  * TODO MVC: [demo](https://dy.github.io/sprae/examples/todomvc), [code](https://github.com/dy/sprae/blob/main/examples/todomvc.html)
317
+ * Waveedit: [demo](), [code](https://github.com/dy/waveedit)
306
318
 
307
319
  ## Justification
308
320
 
309
321
  * [Template-parts](https://github.com/dy/template-parts) / [templize](https://github.com/dy/templize) is progressive, but is stuck with native HTML quirks ([parsing table](https://github.com/github/template-parts/issues/24), [svg attributes](https://github.com/github/template-parts/issues/25), [liquid syntax](https://shopify.github.io/liquid/tags/template/#raw) conflict etc). Also ergonomics of `attr="{{}}"` is inferior to `:attr=""` since it creates flash of uninitialized values.
310
- * [Alpine](https://github.com/alpinejs/alpine) / [vue](https://github.com/vuejs/petite-vue) / [lit](https://github.com/lit/lit/tree/main/packages/lit-html) escapes native HTML quirks, but the syntax is a bit scattered: `:attr`, `v-*`,`x-*`, `@evt`, `{{}}` can be expressed with single convention. Besides, functionality is too broad and can be reduced to essence. Also they tend to [self-encapsulate](https://github.com/alpinejs/alpine/discussions/3223), making interop hard.
322
+ * [Alpine](https://github.com/alpinejs/alpine) / [vue](https://github.com/vuejs/petite-vue) / [lit](https://github.com/lit/lit/tree/main/packages/lit-html) escapes native HTML quirks, but the syntax is a bit scattered: `:attr`, `v-*`,`x-*`, `@evt`, `{{}}` can be expressed with single convention. Besides, functionality is too broad and can be reduced to essence: perfection is when there's nothing to take away, not add. Also they tend to [self-encapsulate](https://github.com/alpinejs/alpine/discussions/3223), making interop hard.
311
323
  * React/[preact](https://ghub.io/preact) does the job wiring up JS to HTML, but with an extreme of migrating HTML to JSX and enforcing SPA, which is not organic for HTML. Also it doesn't support reactive fields (needs render call).
312
324
 
313
325
  _sprae_ takes convention of _templize directives_ (_alpine_/_vue_ attrs) and builds upon [_@preact/signals_](https://ghub.io/@preact/signals).
@@ -316,8 +328,9 @@ _sprae_ takes convention of _templize directives_ (_alpine_/_vue_ attrs) and bui
316
328
  * It falls back to element content if uninitialized.
317
329
  * It doesn't enforce SPA nor JSX.
318
330
  * It enables island hydration.
319
- * It introduces minimal syntax space as `:` convention.
331
+ * It reserves minimal syntax space as `:` convention (keeping tree neatly decorated, not scattered).
320
332
  * Expressions are naturally reactive and incur minimal updates.
321
333
  * Input data may contain [signals](https://ghub.io/@preact/signals) or [reactive values](https://ghub.io/sube).
334
+ * Elements / data API is open and enable easy interop.
322
335
 
323
336
  <p align="center"><a href="https://github.com/krsnzd/license/">🕉</a></p>
package/sprae.js CHANGED
@@ -591,10 +591,13 @@ directives["class"] = (el, expr, values) => {
591
591
  };
592
592
  directives["style"] = (el, expr, values) => {
593
593
  let evaluate = parseExpr(el, expr, ":style");
594
+ let initStyle = el.getAttribute("style") || "";
595
+ if (!initStyle.endsWith(";"))
596
+ initStyle += "; ";
594
597
  return (state) => {
595
598
  let v2 = evaluate(state);
596
599
  if (typeof v2 === "string")
597
- el.setAttribute("style", v2);
600
+ el.setAttribute("style", initStyle + v2);
598
601
  else
599
602
  for (let k in v2)
600
603
  el.style[k] = v2[k];
@@ -608,8 +611,13 @@ directives["text"] = (el, expr, values) => {
608
611
  return (state) => update(evaluate(state));
609
612
  };
610
613
  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;
614
+ let evaluate = parseExpr(el, expr, ":value");
615
+ 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
+ for (let option in el.options)
617
+ option.removeAttribute("selected");
618
+ el.value = value;
619
+ el.selectedOptions[0]?.setAttribute("selected", "");
620
+ } : (value) => el.value = value;
613
621
  return (state) => update(evaluate(state));
614
622
  };
615
623
  directives["on"] = (el, expr, values) => {
@@ -664,7 +672,7 @@ function parseExpr(el, expression, dir) {
664
672
  let rightSideSafeExpression = /^[\n\s]*if.*\(.*\)/.test(expression) || /^(let|const)\s/.test(expression) ? `(() => { ${expression} })()` : expression;
665
673
  let evaluate;
666
674
  try {
667
- evaluate = new Function(`let result; with (arguments[0]) { result = (${rightSideSafeExpression}) }; return result;`).bind(el);
675
+ evaluate = new Function(`__scope`, `with (__scope) { return (${rightSideSafeExpression}) };`).bind(el);
668
676
  } catch (e2) {
669
677
  return exprError(e2, el, expression, dir);
670
678
  }
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 u(t){this.v=t,this.i=0,this.n=void 0,this.t=void 0}function a(t){return new u(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){u.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)}u.prototype.h=function(){return!0},u.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)},u.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)},u.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}}))},u.prototype.valueOf=function(){return this.value},u.prototype.toString=function(){return this.value+""},u.prototype.peek=function(){return this.v},Object.defineProperty(u.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 u).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)}u.prototype.S.call(this,t)},v.prototype.U=function(t){if(u.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)?a(w(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,u,l,h,c;return r&&(f=t=>n.value=t,(s=i)&&(c=(s[Symbol.observable]?.()||s).subscribe?.(f,u,undefined),h=c&&(()=>c.unsubscribe?.())||s.set&&s.call?.(l,f)||(s.then?.((t=>{!l&&f(t)}),u)||(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,u=i.length,a=e.length,{remove:l,same:h,insert:c,replace:v}=j;for(;f<u&&f<a&&h(e[f],i[f]);)f++;for(;f<u&&f<a&&h(i[u-1],e[a-1]);)r=i[(--a,--u)];if(f==a)for(;f<u;)c(r,i[f++],t);else{for(n=e[f];f<u;)s=i[f++],o=n?n.nextSibling:r,h(n,s)?n=o:f<u&&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,T(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),I(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 M(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 u=[];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])):M(Error("Bad list value"),t,r.items,":each"):i=[];let a=[],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,u=f.get(i);if(u||(u=t.cloneNode(!0),f.set(i,u)),a.push(u),!s.has(i)){let f=Object.create(e);f[r.item]=o,r.index&&(f[r.index]=n),t[L]&&(f[t[L]]=u),s.set(i,f)}l.push(s.get(i))}var h;N(n.parentNode,u,a,n),u=a;for(let t=0;t<a.length;t++)I(a[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]=u=>{s=s(u),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-"+T(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(`let result; with (arguments[0]) { result = (${n}) }; return result;`).bind(t)}catch(r){return M(r,t,e,i)}return P[e]=n=>{let o;try{o=r(n)}catch(r){return M(r,t,e,i)}return o}}function M(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 T(t){return t.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(t=>"-"+t.toLowerCase()))}var F=new WeakMap;function I(t,e){if(!t.children)return;if(F.has(t))return F.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)||(()=>{})),F.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 F.set(t,n),n}var z=I;export{z as default};
1
+ function t(){throw new Error("Cycle detected")}function e(){if(n>1)n--;else{for(var t,e=!1;void 0!==r;){var i=r;for(r=void 0,o++;void 0!==i;){var s=i.o;if(i.o=void 0,i.f&=-3,!(8&i.f)&&l(i))try{i.c()}catch(i){e||(t=i,e=!0)}i=s}}if(o=0,n--,e)throw t}}var i=void 0,r=void 0,n=0,o=0,s=0;function 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};
package/src/directives.js CHANGED
@@ -38,6 +38,8 @@ directives['ref'] = (el, expr, values) => {
38
38
  // make sure :ref is initialized after :each (return to avoid initializing as signal)
39
39
  if (el.hasAttribute(':each')) {el[_ref] = expr; return};
40
40
 
41
+ // FIXME: wait for complex ref use-case
42
+ // parseExpr(el, `__scope[${expr}]=this`, ':ref')(values)
41
43
  values[expr] = el;
42
44
  }
43
45
 
@@ -173,9 +175,11 @@ directives['class'] = (el, expr, values) => {
173
175
 
174
176
  directives['style'] = (el, expr, values) => {
175
177
  let evaluate = parseExpr(el, expr, ':style')
178
+ let initStyle = el.getAttribute('style') || ''
179
+ if (!initStyle.endsWith(';')) initStyle += '; '
176
180
  return (state) => {
177
181
  let v = evaluate(state)
178
- if (typeof v === 'string') el.setAttribute('style', v)
182
+ if (typeof v === 'string') el.setAttribute('style', initStyle + v)
179
183
  else for (let k in v) el.style[k] = v[k]
180
184
  }
181
185
  }
@@ -192,16 +196,16 @@ directives['text'] = (el, expr, values) => {
192
196
 
193
197
  // connect expr to element value
194
198
  directives['value'] = (el, expr, values) => {
195
- let evaluate = parseExpr(el, expr, ':in')
199
+ let evaluate = parseExpr(el, expr, ':value')
196
200
 
197
201
  let update = (
198
202
  el.type === 'text' || el.type === '' ? value => el.setAttribute('value', el.value = value == null ? '' : value) :
199
203
  el.type === 'checkbox' ? value => (el.value = value ? 'on' : '', attr(el, 'checked', value)) :
200
- el.type === 'select-one' ? value => (
201
- [...el.options].map(el => el.removeAttribute('selected')),
202
- el.value = value,
204
+ el.type === 'select-one' ? value => {
205
+ for (let option in el.options) option.removeAttribute('selected')
206
+ el.value = value;
203
207
  el.selectedOptions[0]?.setAttribute('selected', '')
204
- ) :
208
+ } :
205
209
  value => el.value = value
206
210
  )
207
211
 
@@ -275,7 +279,7 @@ function parseExpr(el, expression, dir) {
275
279
  // guard static-time eval errors
276
280
  let evaluate
277
281
  try {
278
- evaluate = new Function(`let result; with (arguments[0]) { result = (${rightSideSafeExpression}) }; return result;`).bind(el)
282
+ evaluate = new Function(`__scope`,`with (__scope) { return (${rightSideSafeExpression}) };`).bind(el)
279
283
  } catch ( e ) {
280
284
  return exprError(e, el, expression, dir)
281
285
  }
package/test/test.js CHANGED
@@ -44,15 +44,15 @@ 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('style', async () => {
48
+ let el = h`<x style="left: 1px" :style="style"></x>`
49
49
  let params = sprae(el, {style: "top: 1px"})
50
- is(el.outerHTML, `<x style="top: 1px"></x>`)
50
+ is(el.outerHTML, `<x style="left: 1px; top: 1px"></x>`)
51
51
  params.style = {top: '2px'}
52
- is(el.outerHTML, `<x style="top: 2px;"></x>`)
52
+ is(el.outerHTML, `<x style="left: 1px; top: 2px;"></x>`)
53
53
  })
54
54
 
55
- test('common: class', async () => {
55
+ test('class', async () => {
56
56
  let el = h`<x :class="a"></x><y :class="[b, c]"></y><z :class="{b:true, c:d}"></z>`
57
57
  const c = signal('z')
58
58
  let params = sprae(el, {a:'x', b:'y', c, d:false});