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 +1 -1
- package/plan.md +0 -1
- package/r&d.md +10 -3
- package/readme.md +34 -21
- package/sprae.js +12 -4
- package/sprae.min.js +1 -1
- package/src/directives.js +11 -7
- package/test/test.js +5 -5
package/package.json
CHANGED
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
|
-
## [
|
|
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
|
|
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 :
|
|
116
|
-
<textarea :
|
|
119
|
+
<input :value="text" />
|
|
120
|
+
<textarea :value="text" />
|
|
117
121
|
|
|
118
|
-
<select :
|
|
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 => {
|
|
154
|
+
<button :onfocus-onblur="e => {
|
|
155
|
+
// onfocus
|
|
152
156
|
let id = setInterval(track,200)
|
|
153
|
-
return e => {
|
|
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
|
|
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
|
|
214
|
+
Expose element to data scope with the `id`:
|
|
204
215
|
|
|
205
216
|
```html
|
|
206
217
|
<!-- single item -->
|
|
207
|
-
<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
|
-
//
|
|
224
|
-
state.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
|
|
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="
|
|
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
|
|
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, ":
|
|
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) =>
|
|
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(`
|
|
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
|
|
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, ':
|
|
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
|
-
|
|
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(`
|
|
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('
|
|
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('
|
|
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});
|