sprae 2.1.2 → 2.2.1
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 +6 -5
- package/plan.md +9 -3
- package/r&d.md +60 -9
- package/readme.md +89 -28
- package/sprae.js +73 -63
- package/sprae.min.js +1 -1
- package/src/core.js +8 -16
- package/src/directives.js +57 -38
- package/test/register.cjs +20 -0
- package/test/test.js +63 -15
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sprae",
|
|
3
3
|
"description": "DOM microhydration.",
|
|
4
|
-
"version": "2.1
|
|
5
|
-
"main": "
|
|
4
|
+
"version": "2.2.1",
|
|
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",
|
|
10
11
|
"primitive-pool": "^2.0.0",
|
|
11
12
|
"signal-struct": "^1.5.1",
|
|
12
13
|
"sube": "^2.2.1",
|
|
@@ -14,10 +15,10 @@
|
|
|
14
15
|
},
|
|
15
16
|
"devDependencies": {
|
|
16
17
|
"@preact/signals": "^1.1.2",
|
|
17
|
-
"browser-env": "^3.3.0",
|
|
18
18
|
"es-module-shims": "^1.6.2",
|
|
19
19
|
"esbuild": "^0.15.14",
|
|
20
20
|
"hyperf": "^1.4.0",
|
|
21
|
+
"jsdom": "^20.0.3",
|
|
21
22
|
"terser": "^5.15.1",
|
|
22
23
|
"tst": "^7.1.1",
|
|
23
24
|
"usignal": "^0.8.9",
|
|
@@ -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
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> DOM microhydration with `:` attributes
|
|
4
4
|
|
|
5
|
-
A lightweight essential alternative to [alpine](https://github.com/alpinejs/alpine), [petite-vue](https://github.com/vuejs/petite-vue), [templize](https://github.com/dy/templize) or JSX with better ergonomics[*](#justification)
|
|
5
|
+
A lightweight essential alternative to [alpine](https://github.com/alpinejs/alpine), [petite-vue](https://github.com/vuejs/petite-vue), [templize](https://github.com/dy/templize) or JSX with better ergonomics[*](#justification).<br>
|
|
6
|
+
It is reminiscent of [XSLT](https://www.w3schools.com/xml/xsl_intro.asp), considered a [buried treasure](https://github.com/bahrus/be-restated) by web-connoisseurs.
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
## Usage
|
|
@@ -108,7 +109,7 @@ Set style value from object or a string.
|
|
|
108
109
|
<!--
|
|
109
110
|
#### `:value="value"`
|
|
110
111
|
|
|
111
|
-
|
|
112
|
+
Set value of an input, textarea or select.
|
|
112
113
|
|
|
113
114
|
```html
|
|
114
115
|
<input :with="{text: ''}" :value="text" />
|
|
@@ -122,19 +123,62 @@ Bind (2-way) value to input, textarea or select.
|
|
|
122
123
|
|
|
123
124
|
#### `:<prop>="value"`, `:="props"`
|
|
124
125
|
|
|
125
|
-
Set any
|
|
126
|
+
Set any prop value.
|
|
126
127
|
|
|
127
128
|
```html
|
|
129
|
+
<!-- Single property -->
|
|
128
130
|
<label :for="name" :text="name" />
|
|
129
|
-
|
|
131
|
+
|
|
132
|
+
<!-- Multiple properties -->
|
|
133
|
+
<input :id:name="name" />
|
|
134
|
+
|
|
135
|
+
<!-- Bulk properties -->
|
|
136
|
+
<input :="{ id: name, name, type:'text', value }" />
|
|
130
137
|
```
|
|
131
138
|
|
|
132
|
-
#### `:on="events"`
|
|
139
|
+
#### `:on<event>="handler"`, `:on="events"`
|
|
133
140
|
|
|
134
141
|
Add event listeners.
|
|
135
142
|
|
|
136
143
|
```html
|
|
137
|
-
|
|
144
|
+
<!-- Single event -->
|
|
145
|
+
<input type="checkbox" :onchange="e => isChecked=e.target.value">
|
|
146
|
+
|
|
147
|
+
<!-- Multiple events -->
|
|
148
|
+
<input :value="text" :oninput:onchange="e => text=e.target.value">
|
|
149
|
+
|
|
150
|
+
<!-- Sequence of events -->
|
|
151
|
+
<button :onfocus-onblur="e => { // onfocus
|
|
152
|
+
let id = setInterval(track,200)
|
|
153
|
+
return e => { // onblur
|
|
154
|
+
clearInterval(id)
|
|
155
|
+
}
|
|
156
|
+
}">
|
|
157
|
+
|
|
158
|
+
<!-- Bulk/custom events -->
|
|
159
|
+
<button :on="{ click: handler, touch: handler, special: handler }">Submit</button>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### `:with="data"`
|
|
163
|
+
|
|
164
|
+
Set data for a subtree fragment scope.
|
|
165
|
+
|
|
166
|
+
```html
|
|
167
|
+
<!-- Inline data -->
|
|
168
|
+
<x :with="{ foo: 'bar' }" :text="foo"></x>
|
|
169
|
+
|
|
170
|
+
<!-- External data -->
|
|
171
|
+
<y :with="data"></y>
|
|
172
|
+
|
|
173
|
+
<!-- Inheritance -->
|
|
174
|
+
<x :with="{ foo: 'bar' }">
|
|
175
|
+
<y :with="{ baz: 'qux' }" :text="foo + baz"></y>
|
|
176
|
+
</x>
|
|
177
|
+
|
|
178
|
+
<!-- Single property -->
|
|
179
|
+
<li :with="this as li">
|
|
180
|
+
<input :onfocus-onblur="e => (li.classList.add('editing'), e => li.classList.remove('editing'))" />
|
|
181
|
+
</li>
|
|
138
182
|
```
|
|
139
183
|
|
|
140
184
|
#### `:data="values"`
|
|
@@ -159,28 +203,20 @@ Set [aria-role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA)
|
|
|
159
203
|
}" />
|
|
160
204
|
```
|
|
161
205
|
|
|
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
|
-
|
|
206
|
+
<!--
|
|
172
207
|
#### `:ref="id"`
|
|
173
208
|
|
|
174
209
|
Expose element to a subtree fragment with the `id`.
|
|
175
210
|
|
|
176
211
|
```html
|
|
177
|
-
<li :
|
|
212
|
+
<li :with="{ item: this }">
|
|
178
213
|
<input
|
|
179
|
-
:onfocus="e=> item.classList.add('editing')"
|
|
214
|
+
:onfocus="e => item.classList.add('editing')"
|
|
180
215
|
:onblur="e => item.classList.remove('editing')"
|
|
181
216
|
/>
|
|
182
217
|
</li>
|
|
183
218
|
```
|
|
219
|
+
-->
|
|
184
220
|
|
|
185
221
|
<!--
|
|
186
222
|
|
|
@@ -192,8 +228,7 @@ _sprae_ is built on top of [_@preact/signals_](https://ghub.io/@preact/signals).
|
|
|
192
228
|
* Expressions support any reactive values in data (see [sube](https://github.com/dy/sube))
|
|
193
229
|
* Updates happen minimally only when used values update
|
|
194
230
|
* Subscription is weak and get disposed when element is disposed.
|
|
195
|
-
|
|
196
|
-
<!--
|
|
231
|
+
|
|
197
232
|
Directive expressions are natively reactive, ie. data may contain any async/reactive values, such as:
|
|
198
233
|
|
|
199
234
|
* _Promise_ / _Thenable_
|
|
@@ -203,32 +238,58 @@ Directive expressions are natively reactive, ie. data may contain any async/reac
|
|
|
203
238
|
* etc., see [sube](https://github.com/dy/sube/blob/main/README.md) for the full list.
|
|
204
239
|
|
|
205
240
|
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
241
|
-->
|
|
209
|
-
|
|
242
|
+
|
|
243
|
+
## Hints
|
|
244
|
+
|
|
245
|
+
**1.** Attributes are initialized in order, so pay attention providing scope attributes:
|
|
246
|
+
|
|
247
|
+
```html
|
|
248
|
+
<li :each="item in items" :ref="li">
|
|
249
|
+
<button :onclick="e => li.classList.add('loading')"></button>
|
|
250
|
+
</li>
|
|
251
|
+
|
|
252
|
+
<li :ref="li" :each="item in items">
|
|
253
|
+
<!-- Invalid: li is undefined -->
|
|
254
|
+
<button :onclick="e => li.classList.add('loading')"></button>
|
|
255
|
+
</li>
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**2.** Data allows signals values, which can be an alternative way to control template state:
|
|
259
|
+
|
|
210
260
|
```html
|
|
211
261
|
<div id="done" :text="loading ? 'loading' : result">...</div>
|
|
212
262
|
|
|
213
|
-
<script>
|
|
263
|
+
<script type="module">
|
|
214
264
|
import sprae from 'sprae';
|
|
215
265
|
import { signals } from '@preact/signals';
|
|
216
266
|
|
|
217
267
|
// <div id="done">...</div>
|
|
218
268
|
|
|
219
269
|
const loading = signal(true), result = signal(false);
|
|
220
|
-
|
|
221
270
|
sprae(done, { loading, result })
|
|
271
|
+
setTimeout(() => (loading.value = true, result.value = 'done'), 1000)
|
|
222
272
|
|
|
223
273
|
// <div id="done">loading</div>
|
|
224
274
|
|
|
225
|
-
setTimeout(() => (loading.value = true, result.value = 'done'), 1000)
|
|
226
|
-
|
|
227
275
|
// ... 1s after
|
|
228
276
|
// <div id="done">done</div>
|
|
229
277
|
</script>
|
|
230
278
|
```
|
|
231
|
-
|
|
279
|
+
|
|
280
|
+
**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.
|
|
281
|
+
|
|
282
|
+
```html
|
|
283
|
+
<div :text="clicks">#</div> clicks
|
|
284
|
+
|
|
285
|
+
<script type="module">
|
|
286
|
+
import sprae from 'sprae';
|
|
287
|
+
import { fromEvent, scan } from 'rxjs';
|
|
288
|
+
sprae(document, {
|
|
289
|
+
clicks: fromEvent(document, 'click').pipe(scan((count) => count + 1, 0))
|
|
290
|
+
});
|
|
291
|
+
</script>
|
|
292
|
+
```
|
|
232
293
|
|
|
233
294
|
## Examples
|
|
234
295
|
|
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);
|
|
@@ -529,11 +529,11 @@ directives[":if"] = (el2, expr, state) => {
|
|
|
529
529
|
b((i2 = idx.value) => els[i2] != cur && ((cur[_each] || cur).replaceWith(cur = els[i2] || holder), sprae(cur, state)));
|
|
530
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();
|
|
@@ -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,25 @@ function sprae(container, values) {
|
|
|
701
714
|
values ||= {};
|
|
702
715
|
const state = SignalStruct(values);
|
|
703
716
|
const init = (el2) => {
|
|
704
|
-
let dir, attr, res;
|
|
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 ((res = dir(el2, attr.value, state)) <= 0)
|
|
711
|
-
return res;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
718
|
for (let i2 = 0; i2 < el2.attributes.length; ) {
|
|
715
|
-
let
|
|
716
|
-
if (
|
|
717
|
-
el2.removeAttribute(attr2.name);
|
|
718
|
-
if ((res = directives_default(el2, attr2.value, state, attr2.name.slice(1))) <= 0)
|
|
719
|
-
return res;
|
|
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
734
|
for (let i2 = 0, child; child = el2.children[i2]; i2++) {
|
|
725
|
-
res = init(child) || 0;
|
|
735
|
+
let res = init(child) || 0;
|
|
726
736
|
i2 += res;
|
|
727
737
|
}
|
|
728
738
|
};
|
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)))),-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 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,o;if(t.attributes){for(let r in L)if((n=t.attributes[r])&&(e=L[r],t.removeAttribute(r),(o=e(t,n.value,i))<=0))return o;for(let e=0;e<t.attributes.length;){let r=t.attributes[e];if(":"===r.name[0]){if(t.removeAttribute(r.name),(o=P(t,r.value,i,r.name.slice(1)))<=0)return o}else e++}}for(let e,i=0;e=t.children[i];i++)o=r(e)||0,i+=o};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,30 +13,22 @@ export default function sprae(container, values) {
|
|
|
13
13
|
|
|
14
14
|
// init directives on element
|
|
15
15
|
const init = (el) => {
|
|
16
|
-
let dir, attr, res
|
|
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 ((res = dir(el, attr.value, state)) <= 0) return res
|
|
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
30
|
for (let i = 0, child; child = el.children[i]; i++) {
|
|
39
|
-
res = init(child) || 0 // reduce number of removed elements
|
|
31
|
+
let res = init(child) || 0 // reduce number of removed elements
|
|
40
32
|
i += res
|
|
41
33
|
}
|
|
42
34
|
}
|
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,8 +24,8 @@ 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
|
|
@@ -30,7 +35,7 @@ directives[':with'] = (el, expr, rootState) => {
|
|
|
30
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
|
|
|
@@ -38,9 +43,9 @@ directives[':ref'] = (el, expr, state) => {
|
|
|
38
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);
|
|
@@ -66,11 +71,11 @@ directives[':if'] = (el, expr, state) => {
|
|
|
66
71
|
return -els.length
|
|
67
72
|
}
|
|
68
73
|
|
|
69
|
-
directives['
|
|
74
|
+
directives['each'] = (tpl, expr, state) => {
|
|
70
75
|
let each = parseForExpression(expr);
|
|
71
|
-
if (!each) return exprError(new Error, expr);
|
|
76
|
+
if (!each) return exprError(new Error, tpl, expr);
|
|
72
77
|
|
|
73
|
-
const getItems = parseExpr(each.items, ':each', state);
|
|
78
|
+
const getItems = parseExpr(tpl, each.items, ':each', state);
|
|
74
79
|
|
|
75
80
|
// FIXME: make sure no memory leak here
|
|
76
81
|
// we need :if to be able to replace holder instead of tpl for :if :each case
|
|
@@ -83,7 +88,7 @@ directives[':each'] = (tpl, expr, state) => {
|
|
|
83
88
|
if (typeof list === 'number') return Array.from({length: list}, (_, i)=>[i, i+1])
|
|
84
89
|
if (list.constructor === Object) return Object.entries(list)
|
|
85
90
|
if (Array.isArray(list)) return list.map((item,i) => [i+1, item])
|
|
86
|
-
exprError(Error('Bad list value'), each.items, ':each', list)
|
|
91
|
+
exprError(Error('Bad list value'), tpl, each.items, ':each', list)
|
|
87
92
|
})
|
|
88
93
|
|
|
89
94
|
// stores scope per data item
|
|
@@ -152,14 +157,14 @@ function parseForExpression(expression) {
|
|
|
152
157
|
return res
|
|
153
158
|
}
|
|
154
159
|
|
|
155
|
-
directives['
|
|
156
|
-
let evaluate = parseExpr(expr, ':id', state)
|
|
160
|
+
directives['id'] = (el, expr, state) => {
|
|
161
|
+
let evaluate = parseExpr(el, expr, ':id', state)
|
|
157
162
|
const update = v => el.id = v || v === 0 ? v : ''
|
|
158
163
|
effect(()=>update(evaluate(state)))
|
|
159
164
|
}
|
|
160
165
|
|
|
161
|
-
directives['
|
|
162
|
-
let evaluate = parseExpr(expr, ':', state)
|
|
166
|
+
directives[''] = (el, expr, state) => {
|
|
167
|
+
let evaluate = parseExpr(el, expr, ':', state)
|
|
163
168
|
const update = (value) => {
|
|
164
169
|
if (!value) return
|
|
165
170
|
for (let key in value) prop(el, key, value[key]);
|
|
@@ -167,8 +172,8 @@ directives[':'] = (el, expr, state) => {
|
|
|
167
172
|
effect(()=>update(evaluate(state)))
|
|
168
173
|
}
|
|
169
174
|
|
|
170
|
-
directives['
|
|
171
|
-
let evaluate = parseExpr(expr, ':text', state)
|
|
175
|
+
directives['text'] = (el, expr, state) => {
|
|
176
|
+
let evaluate = parseExpr(el, expr, ':text', state)
|
|
172
177
|
|
|
173
178
|
const update = (value) => {
|
|
174
179
|
el.textContent = value == null ? '' : value;
|
|
@@ -178,8 +183,8 @@ directives[':text'] = (el, expr, state) => {
|
|
|
178
183
|
}
|
|
179
184
|
|
|
180
185
|
// connect expr to element value
|
|
181
|
-
directives['
|
|
182
|
-
let evaluate = parseExpr(expr, ':in', state)
|
|
186
|
+
directives['value'] = (el, expr, state) => {
|
|
187
|
+
let evaluate = parseExpr(el, expr, ':in', state)
|
|
183
188
|
|
|
184
189
|
let [get, set] = input(el);
|
|
185
190
|
|
|
@@ -190,27 +195,41 @@ directives[':value'] = (el, expr, state) => {
|
|
|
190
195
|
effect(()=>update(evaluate(state)))
|
|
191
196
|
}
|
|
192
197
|
|
|
193
|
-
directives['
|
|
194
|
-
let evaluate = parseExpr(expr, ':on', state)
|
|
195
|
-
let listeners =
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
+
};
|
|
201
220
|
})
|
|
202
221
|
}
|
|
203
222
|
|
|
204
|
-
directives['
|
|
205
|
-
let evaluate = parseExpr(expr, ':data', state)
|
|
223
|
+
directives['data'] = (el, expr, state) => {
|
|
224
|
+
let evaluate = parseExpr(el, expr, ':data', state)
|
|
206
225
|
const value = computed(() => evaluate(state))
|
|
207
226
|
effect((v=value.value) => {
|
|
208
227
|
for (let key in v) el.dataset[key] = v[key];
|
|
209
228
|
})
|
|
210
229
|
}
|
|
211
230
|
|
|
212
|
-
directives['
|
|
213
|
-
let evaluate = parseExpr(expr, ':aria', state)
|
|
231
|
+
directives['aria'] = (el, expr, state) => {
|
|
232
|
+
let evaluate = parseExpr(el, expr, ':aria', state)
|
|
214
233
|
const update = (value) => {
|
|
215
234
|
for (let key in value) prop(el, 'aria'+key[0].toUpperCase()+key.slice(1), value[key] == null ? null : value[key] + '');
|
|
216
235
|
}
|
|
@@ -222,7 +241,7 @@ let evaluatorMemo = {}
|
|
|
222
241
|
|
|
223
242
|
// borrowed from alpine: https://github.com/alpinejs/alpine/blob/main/packages/alpinejs/src/evaluator.js#L61
|
|
224
243
|
// it seems to be more robust than subscript
|
|
225
|
-
function parseExpr(expression, dir, scope) {
|
|
244
|
+
function parseExpr(el, expression, dir, scope) {
|
|
226
245
|
if (evaluatorMemo[expression]) return evaluatorMemo[expression]
|
|
227
246
|
|
|
228
247
|
// Some expressions that are useful in Alpine are not valid as the right side of an expression.
|
|
@@ -239,22 +258,22 @@ function parseExpr(expression, dir, scope) {
|
|
|
239
258
|
// guard static-time eval errors
|
|
240
259
|
let evaluate
|
|
241
260
|
try {
|
|
242
|
-
evaluate = new Function(
|
|
261
|
+
evaluate = new Function(`let result; with (arguments[0]) { result = (${rightSideSafeExpression}) }; return result;`).bind(el)
|
|
243
262
|
} catch ( e ) {
|
|
244
|
-
return exprError(e, expression, dir, scope)
|
|
263
|
+
return exprError(e, el, expression, dir, scope)
|
|
245
264
|
}
|
|
246
265
|
|
|
247
266
|
// guard runtime eval errors
|
|
248
267
|
return evaluatorMemo[expression] = (state) => {
|
|
249
268
|
let result
|
|
250
269
|
try { result = evaluate(state) }
|
|
251
|
-
catch (e) { return exprError(e, expression, dir, scope) }
|
|
270
|
+
catch (e) { return exprError(e, el, expression, dir, scope) }
|
|
252
271
|
return result
|
|
253
272
|
}
|
|
254
273
|
}
|
|
255
274
|
|
|
256
|
-
export function exprError(error, expression, dir, scope) {
|
|
257
|
-
Object.assign( error, { expression } )
|
|
258
|
-
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)
|
|
259
278
|
setTimeout(() => { throw error }, 0)
|
|
260
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)
|
|
@@ -341,6 +347,41 @@ test('on: base', () => {
|
|
|
341
347
|
is(log.value, ['click','x','xx']);
|
|
342
348
|
})
|
|
343
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
|
+
|
|
344
385
|
test('with: inline', () => {
|
|
345
386
|
let el = h`<x :with="{foo:'bar', baz}"><y :text="foo + baz"></y></x>`
|
|
346
387
|
let state = sprae(el, {baz: 'qux'})
|
|
@@ -402,14 +443,14 @@ test('ref: base', () => {
|
|
|
402
443
|
is(a.outerHTML, `<a>2</a>`)
|
|
403
444
|
})
|
|
404
445
|
|
|
405
|
-
test('ref with each', () => {
|
|
446
|
+
test('ref: :ref with :each', () => {
|
|
406
447
|
let a = h`<y><x :ref="x" :each="item in items" :text="log.push(x), item"/></y>`
|
|
407
448
|
let state = sprae(a, {log: [], items: [1,2]})
|
|
408
449
|
is(a.innerHTML, `<x>1</x><x>2</x>`)
|
|
409
450
|
is(state.log, [...a.children])
|
|
410
451
|
})
|
|
411
452
|
|
|
412
|
-
test('reactive values', async () => {
|
|
453
|
+
test('any: reactive values', async () => {
|
|
413
454
|
let a = new Promise((ok) => setTimeout(() => ok(2), 10))
|
|
414
455
|
|
|
415
456
|
let el = h`<x :text="a">1</x>`
|
|
@@ -420,22 +461,29 @@ test('reactive values', async () => {
|
|
|
420
461
|
is(el.outerHTML, `<x>2</x>`)
|
|
421
462
|
})
|
|
422
463
|
|
|
423
|
-
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
|
|
424
472
|
let a = h`<x :text="y" :with="{y:1}" :ref="x"></x>`
|
|
425
473
|
sprae(a, {})
|
|
426
474
|
is(a.outerHTML, `<x>1</x>`)
|
|
427
475
|
})
|
|
428
476
|
|
|
429
477
|
test.todo('getters', async () => {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
+
})
|
|
441
489
|
})
|