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