sprae 2.3.2 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/plan.md +5 -2
- package/r&d.md +111 -18
- package/readme.md +26 -24
- package/sprae.js +77 -55
- package/sprae.min.js +1 -1
- package/src/core.js +5 -4
- package/src/directives.js +57 -42
- package/test/test.js +76 -18
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sprae",
|
|
3
3
|
"description": "DOM microhydration.",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.5.0",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@preact/signals-core": "^1.2.2",
|
|
10
10
|
"primitive-pool": "^2.0.0",
|
|
11
|
-
"signal-struct": "^1.
|
|
11
|
+
"signal-struct": "^1.8.0",
|
|
12
12
|
"swapdom": "^1.1.1"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
package/plan.md
CHANGED
|
@@ -29,8 +29,11 @@
|
|
|
29
29
|
* [x] Make sure `false` gets serialized, not removes attr
|
|
30
30
|
* [ ] Sandbox expressions: no global, no "scope" object name, no "arguments"
|
|
31
31
|
* ~~[x] report usignal problem~~ author is not really interested
|
|
32
|
-
* [
|
|
32
|
+
* [x] `this` doesn't refer to element/scope in event handlers
|
|
33
|
+
* [x] :text="" empty values shouldn't throw
|
|
34
|
+
* [ ] implement :with
|
|
33
35
|
* [ ] frameworks benchmark
|
|
34
36
|
* [ ] examples
|
|
35
37
|
* [x] todomvc
|
|
36
|
-
* [ ] wave-edit
|
|
38
|
+
* [ ] wave-edit
|
|
39
|
+
* [ ] `sprae(el, newState)` can update element's state directly (as batch!?) -> must be tested against repeats in directives
|
package/r&d.md
CHANGED
|
@@ -97,17 +97,41 @@
|
|
|
97
97
|
|
|
98
98
|
-> possibly we have to just subscribe via mechanism of signals-like deps, and :with just initializes subtree with extended object
|
|
99
99
|
|
|
100
|
-
## [
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
+
|
|
106
|
-
+ it
|
|
107
|
-
|
|
108
|
-
+ it
|
|
109
|
-
|
|
110
|
-
|
|
100
|
+
## [ ] :with? -> let's use `:with="{x:1,y:2,z:3}"` for now
|
|
101
|
+
|
|
102
|
+
1. Get rid of :with
|
|
103
|
+
+ with is bad JS practice/association
|
|
104
|
+
+? is there a bona-fide use case?
|
|
105
|
+
+ the implementation is heavy/unoptimal: two assign-updates happen: for root, for children
|
|
106
|
+
+ it is exception blocking streamline implementation of refs
|
|
107
|
+
+ it shadows data which creates all sorts of nasty debugging effects / states. Isn't it better to keep data/state transparent?
|
|
108
|
+
+ it even enables transparency of :each scopes, since they inherit root scope
|
|
109
|
+
+ it's easier to look out for data in one single place (state init), rather than in a bunch of markup locations
|
|
110
|
+
+?! can be replaced with sort of `<x :xyz="xyz=...calc"></x>`, no?
|
|
111
|
+
-> would need wrapping noname scope access
|
|
112
|
+
+ `:with` defines too many concerns:
|
|
113
|
+
* binds root updates -> child updates;
|
|
114
|
+
* binds child updates to root updates (writes);
|
|
115
|
+
* defines local variables
|
|
116
|
+
* aliases root variables
|
|
117
|
+
? is there value in all of these concerns? It seems we need only local variables, isn't it? Is there a chance partial extension can be required?
|
|
118
|
+
- `:with` can provide situational variables that are useful for props precalculation (since these variables can be reactive.)
|
|
119
|
+
* eg. `<span class="preloader" :with="{str: ''}" :init="setTimeout(() => str+='.', 500)" :text="str" />`
|
|
120
|
+
* that plays role of watch that doesn't require to be outside of local component state.
|
|
121
|
+
- `:with` allows local component state not cluttering global state. There's really no way to define local state that doesn't affect global state.
|
|
122
|
+
1.1 Slim `:with="{a,b,c}"` - just initializes vars
|
|
123
|
+
- Doesn't give easy init syntax
|
|
124
|
+
+ Convevtional and not hard to implement
|
|
125
|
+
2. Use `:let="x, y=2"`?
|
|
126
|
+
+ Doesn't pollute scope but instead cleanly declares local variables
|
|
127
|
+
+ Indicates only local initializer, not subscription
|
|
128
|
+
+ Liquid has `assign` tag `{% assign myVar = false %}` - it only initializes variable
|
|
129
|
+
+ Django `with` performs only alias / complex calc access https://docs.djangoproject.com/en/4.1/ref/templates/builtins/#with - it doesn't sync up global state.
|
|
130
|
+
? call it `:define="x, y, z"`?
|
|
131
|
+
-> it seems `:with="x=1, y=2"` works well. `:let` has dissonance with js'y let.
|
|
132
|
+
? how to extend state
|
|
133
|
+
3. `:with.x="1", :with.y="2"`
|
|
134
|
+
+ easier to parse, since init code can be _messy_
|
|
111
135
|
|
|
112
136
|
## [x] Should we inherit values from `init` in `sprae(el, init)`, instead of creating a snapshot of reactive values in `init`? -> nah, nice idea but too little use. Better create signals struct.
|
|
113
137
|
|
|
@@ -191,11 +215,24 @@
|
|
|
191
215
|
+ methods provided in `init` may not have access to scope _yet_.
|
|
192
216
|
~- not reliable way to obtain scope via `this.x` - better be explicit as `state.x`
|
|
193
217
|
|
|
194
|
-
## [x] :onconnected/:ondisconnected? ->
|
|
218
|
+
## [x] :onconnected/:ondisconnected? -> nah, just use noname effect or external functionality eg fast-on-load
|
|
195
219
|
|
|
196
|
-
|
|
220
|
+
+ can be useful for :if, :each handlers, eg to start animation when element appears in DOM.
|
|
221
|
+
- it is not connected-disconnected: it has nothing to do with document: it attaches/detaches from parent.
|
|
222
|
+
- connected-disconnected is too long name
|
|
223
|
+
? attach-detach?
|
|
224
|
+
? onmount-onunmount?
|
|
225
|
+
- slows down domdiff
|
|
226
|
+
- can be solved as `<x :if="xxx" :="xxx ? (...) : '">` automatically
|
|
227
|
+
|
|
228
|
+
## [x] :focus="direct code", :evt="direct code" -> nah, too messy.
|
|
229
|
+
|
|
230
|
+
+ makes proper use-case for direct code events
|
|
231
|
+
- doesn't make sense for rective properties inside
|
|
232
|
+
+ better fit for special props like `:mount`
|
|
233
|
+
- tons of new special-meaning namespace props
|
|
197
234
|
|
|
198
|
-
## [x] Chain of events: often useful to have shared context. -> Try `:
|
|
235
|
+
## [x] Chain of events: often useful to have shared context. -> Try `:onstart..onend`
|
|
199
236
|
|
|
200
237
|
* focus/blur, connected/disconnected, mousedown/mouseup, keydown/keyup, touchstart/touchmove/touchend, dragstart/dragover/dragend, animationstart/animationover/animationend, transitionstart/transitionend
|
|
201
238
|
? is there a way to organize handlers for chains of events?
|
|
@@ -208,14 +245,35 @@
|
|
|
208
245
|
- ? why not :on-focus-blur
|
|
209
246
|
- ? why not :onfocus-onblur
|
|
210
247
|
+ can be converted from on="{ focusBlur: event }" via dash notation
|
|
248
|
+
- messy error messages
|
|
211
249
|
+ less :on prefixes
|
|
212
250
|
+ has better "flowy" meaning
|
|
213
251
|
* 2.1 :onfocus-onblur="e => e => {}"
|
|
214
252
|
+ distinctive visually as 1
|
|
215
253
|
+ flowy nature of 2
|
|
254
|
+
- blocks `:onfile-attached` and other dashed events
|
|
255
|
+
- `ona-onb` vs `ona-b-onc` is hard to parse mentally
|
|
216
256
|
3. :onfocus.onblur="e => e => {}"
|
|
217
257
|
- looks like a modifier
|
|
218
258
|
- . can be used as event class onclick.x
|
|
259
|
+
4. `:onfocus--blur="e => e => {}"`
|
|
260
|
+
+ reminds BEM
|
|
261
|
+
- reminds BEM
|
|
262
|
+
5. `:onfocus..blur="e => e => {}"`
|
|
263
|
+
+ reminds range
|
|
264
|
+
+ literally means start..end
|
|
265
|
+
+ all pros of 2.
|
|
266
|
+
+ reminds spray via dots
|
|
267
|
+
- can be confusing if `blur` is event or just property (can that be a property?)
|
|
268
|
+
6. `:onfocus..onblur="e => e => {}"`
|
|
269
|
+
+ all props of 5.
|
|
270
|
+
+ more obvious that blur is event.
|
|
271
|
+
|
|
272
|
+
4. `:onfocus="e => e => {}"` Keep registered pairs of events: just expect focus return blur, etc.
|
|
273
|
+
+ Shorter syntax
|
|
274
|
+
+ Avoids :onfile-attachment-accepted problem
|
|
275
|
+
- Less verbose and explicit
|
|
276
|
+
- No way to customize sequences, eg. custom events
|
|
219
277
|
|
|
220
278
|
## [ ] Plugins
|
|
221
279
|
|
|
@@ -224,9 +282,10 @@
|
|
|
224
282
|
- can be solved naturally, unless there's special meaning
|
|
225
283
|
* @sprae/hcodes: `<x :hcode=""` – provide microformats
|
|
226
284
|
* @sprae/with
|
|
227
|
-
* @sprae/
|
|
285
|
+
* @sprae/onconnected
|
|
286
|
+
* @sprae/onvisible?
|
|
228
287
|
|
|
229
|
-
## [x] Write any-attributes via
|
|
288
|
+
## [x] Write any-attributes via `:<prop>? -> yep`
|
|
230
289
|
|
|
231
290
|
+ Since we support attr walking, maybe instead of :on and :prop just allow any attributes?
|
|
232
291
|
+ that would allow event and attr modifiers...
|
|
@@ -263,8 +322,10 @@
|
|
|
263
322
|
+ Provides precisely controlled sandbox
|
|
264
323
|
- Some limited lang opportunities
|
|
265
324
|
- need to match many syntax quirks, can be tedious
|
|
325
|
+
~ can be fine to limit expressions to meaningful default: why Proxy, generators, awaits, global access etc.
|
|
266
326
|
- Somewhat heavy to bundle
|
|
267
|
-
|
|
327
|
+
~ 1-2kb is not super-heavy, besides signal-struct kicks out
|
|
328
|
+
+ Scope is easier to provide: no need for signal proxy
|
|
268
329
|
+ Can detect access errors in advance
|
|
269
330
|
+ Syntax-level access to signals can be inavoidable: external signals still "leak in" (via arrays or etc.).
|
|
270
331
|
+ Updating simple objects should also rerender the template parts, not just signals.
|
|
@@ -272,6 +333,9 @@
|
|
|
272
333
|
- Screwed up debugging / stacktrace (unless errored)
|
|
273
334
|
+ that "unlimits" returned struct, so that any property can be added/deleted.
|
|
274
335
|
- doesn't really save from `new (()=>{}).constructor` hack: we gotta substitute objects too.
|
|
336
|
+
+ allows easier handle of `:with="a=1,b=2,c=3"` - we just naturally get local variables without messup with global
|
|
337
|
+
+ we can even define locals without `let`...
|
|
338
|
+
- not having "comfy" compatible JS at hand: cognitive load of whole language "layer" in-between
|
|
275
339
|
|
|
276
340
|
2. Use sandboxed proxy
|
|
277
341
|
- tough evaluation
|
|
@@ -297,4 +361,33 @@
|
|
|
297
361
|
## [ ] Better :ref
|
|
298
362
|
+ :ref="`a-${1}`"
|
|
299
363
|
+ :id:ref="xyz"
|
|
300
|
-
? maybe id should have same signature
|
|
364
|
+
? maybe id should have same signature
|
|
365
|
+
? should it be very similar mechanism to `:with="a=1,b=2"`
|
|
366
|
+
|
|
367
|
+
## [ ] Event modifiers :ona.once, `:ona`
|
|
368
|
+
|
|
369
|
+
- .prevent,.stop - not needed since expects a function
|
|
370
|
+
? or should we just trigger it for user?
|
|
371
|
+
? :onclick.outside
|
|
372
|
+
? :onclick.window, :onclick.document
|
|
373
|
+
? :onclick.once
|
|
374
|
+
? :onclick.debounce
|
|
375
|
+
? :onclick.throttle.750ms
|
|
376
|
+
? :onclick.self
|
|
377
|
+
? :onspecial-event.camel, :onx-y.dot
|
|
378
|
+
? :onclick.passive
|
|
379
|
+
? :onkeypress.shift.enter
|
|
380
|
+
.shift Shift
|
|
381
|
+
.enter Enter
|
|
382
|
+
.space Space
|
|
383
|
+
.ctrl Ctrl
|
|
384
|
+
.cmd Cmd
|
|
385
|
+
.meta Cmd on Mac, Windows key on Windows
|
|
386
|
+
.alt Alt
|
|
387
|
+
.up .down .left .right Up/Down/Left/Right arrows
|
|
388
|
+
.escape Escape
|
|
389
|
+
.tab Tab
|
|
390
|
+
.caps-lock Caps Lock
|
|
391
|
+
.equal Equal, =
|
|
392
|
+
.period Period, .
|
|
393
|
+
.slash Foward Slash, /
|
package/readme.md
CHANGED
|
@@ -124,9 +124,9 @@ Set value of an input, textarea or select. Takes handle of `checked` and `select
|
|
|
124
124
|
</select>
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
#### `:<prop>="value"`, `:="props"`
|
|
127
|
+
#### `:<prop>="value?"`, `:="props?"`
|
|
128
128
|
|
|
129
|
-
Set any prop value.
|
|
129
|
+
Set any prop value or run effect.
|
|
130
130
|
|
|
131
131
|
```html
|
|
132
132
|
<!-- Single property -->
|
|
@@ -137,9 +137,12 @@ Set any prop value.
|
|
|
137
137
|
|
|
138
138
|
<!-- Bulk properties -->
|
|
139
139
|
<input :="{ id: name, name, type:'text', value }" />
|
|
140
|
+
|
|
141
|
+
<!-- Effect (triggers any time foo or bar changes) -->
|
|
142
|
+
<div :="if (foo) bar()" :fx="void bar()" ></div>
|
|
140
143
|
```
|
|
141
144
|
|
|
142
|
-
#### `:on<event>="handler"`, `:on="events"`
|
|
145
|
+
#### `:on<event>="handler"`, `:on="events"`, `:<in>..<out>="handler"`
|
|
143
146
|
|
|
144
147
|
Add event listeners.
|
|
145
148
|
|
|
@@ -151,7 +154,7 @@ Add event listeners.
|
|
|
151
154
|
<input :value="text" :oninput:onchange="e => text=e.target.value">
|
|
152
155
|
|
|
153
156
|
<!-- Sequence of events -->
|
|
154
|
-
<button :onfocus
|
|
157
|
+
<button :onfocus..onblur="e => {
|
|
155
158
|
// onfocus
|
|
156
159
|
let id = setInterval(track,200)
|
|
157
160
|
return e => {
|
|
@@ -164,23 +167,6 @@ Add event listeners.
|
|
|
164
167
|
<button :on="{ click: handler, touch: handler, special: handler }">Submit</button>
|
|
165
168
|
```
|
|
166
169
|
|
|
167
|
-
<!--
|
|
168
|
-
#### `:oninit="fn"`
|
|
169
|
-
|
|
170
|
-
Create initializer handler - called whenever element is initialized by sprae.
|
|
171
|
-
|
|
172
|
-
```html
|
|
173
|
-
<div id="foo" :oninit="function(){ this === foo }"></div>
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
#### `:onconnect="fn"`, `:ondisconnect="fn"`
|
|
177
|
-
|
|
178
|
-
Invoked when element is connected or disconnected from DOM.
|
|
179
|
-
|
|
180
|
-
```html
|
|
181
|
-
```
|
|
182
|
-
-->
|
|
183
|
-
|
|
184
170
|
#### `:data="values"`
|
|
185
171
|
|
|
186
172
|
Set [data-*](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*) attributes. CamelCase is converted to dash-case.
|
|
@@ -209,6 +195,23 @@ Set [aria-role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA)
|
|
|
209
195
|
-->
|
|
210
196
|
```
|
|
211
197
|
|
|
198
|
+
#### `:with="data"`
|
|
199
|
+
|
|
200
|
+
Define variables for a subtree fragment scope.
|
|
201
|
+
|
|
202
|
+
```html
|
|
203
|
+
<!-- Inline data -->
|
|
204
|
+
<x :with="{ foo: 'bar' }" :text="foo"></x>
|
|
205
|
+
|
|
206
|
+
<!-- External data -->
|
|
207
|
+
<y :with="data"></y>
|
|
208
|
+
|
|
209
|
+
<!-- Transparency -->
|
|
210
|
+
<x :with="{ foo: 'bar' }">
|
|
211
|
+
<y :with="{ baz: 'qux' }" :text="foo + baz"></y>
|
|
212
|
+
</x>
|
|
213
|
+
```
|
|
214
|
+
|
|
212
215
|
#### `:ref="id"`
|
|
213
216
|
|
|
214
217
|
Expose element to data scope with the `id`:
|
|
@@ -220,9 +223,7 @@ Expose element to data scope with the `id`:
|
|
|
220
223
|
<!-- iterable items -->
|
|
221
224
|
<ul>
|
|
222
225
|
<li :each="item in items" :ref="item">
|
|
223
|
-
<input
|
|
224
|
-
:onfocus-onblur="e => (item.classList.add('editing'), e => item.classList.remove('editing'))"
|
|
225
|
-
/>
|
|
226
|
+
<input :onfocus..onblur="e => (item.classList.add('editing'), e => item.classList.remove('editing'))"/>
|
|
226
227
|
</li>
|
|
227
228
|
</ul>
|
|
228
229
|
|
|
@@ -235,6 +236,7 @@ Expose element to data scope with the `id`:
|
|
|
235
236
|
</script>
|
|
236
237
|
```
|
|
237
238
|
|
|
239
|
+
|
|
238
240
|
<!--
|
|
239
241
|
|
|
240
242
|
### Reactivity
|
package/sprae.js
CHANGED
|
@@ -363,33 +363,37 @@ var sube_default = (target, next, error, complete, stop, unsub) => target && (un
|
|
|
363
363
|
var isSignal = (v2) => v2 && v2.peek;
|
|
364
364
|
var isStruct = (v2) => v2 && v2[_struct];
|
|
365
365
|
var _struct = Symbol("signal-struct");
|
|
366
|
-
|
|
367
|
-
|
|
366
|
+
signalStruct.isStruct = isStruct;
|
|
367
|
+
function signalStruct(values, proto) {
|
|
368
|
+
if (isStruct(values) && !proto)
|
|
368
369
|
return values;
|
|
369
370
|
let state, signals;
|
|
370
371
|
if (isObject(values)) {
|
|
371
|
-
state =
|
|
372
|
+
state = Object.create(proto || Object.getPrototypeOf(values)), signals = {};
|
|
372
373
|
let desc = Object.getOwnPropertyDescriptors(values);
|
|
373
|
-
|
|
374
|
-
|
|
374
|
+
if (isStruct(values))
|
|
375
|
+
for (let key in desc)
|
|
376
|
+
Object.defineProperty(state, key, desc[key]);
|
|
377
|
+
else
|
|
378
|
+
for (let key in desc)
|
|
379
|
+
signals[key] = defineSignal(state, key, desc[key].get ? w(desc[key].get.bind(state)) : desc[key].value);
|
|
375
380
|
Object.defineProperty(state, _struct, { configurable: false, enumerable: false, value: true });
|
|
376
|
-
Object.seal(state);
|
|
377
381
|
return state;
|
|
378
382
|
}
|
|
379
383
|
if (Array.isArray(values)) {
|
|
380
|
-
return values.map((v2) =>
|
|
384
|
+
return values.map((v2) => signalStruct(v2));
|
|
381
385
|
}
|
|
382
386
|
return values;
|
|
383
387
|
}
|
|
384
388
|
function defineSignal(state, key, value) {
|
|
385
|
-
let isObservable, s2 = isSignal(value) ? value : isObject(value) || Array.isArray(value) ? u(
|
|
389
|
+
let isObservable, s2 = isSignal(value) ? value : isObject(value) || Array.isArray(value) ? u(signalStruct(value)) : u((isObservable = observable(value)) ? void 0 : value);
|
|
386
390
|
if (isObservable)
|
|
387
391
|
sube_default(value, (v2) => s2.value = v2);
|
|
388
392
|
Object.defineProperty(state, key, {
|
|
389
393
|
get() {
|
|
390
394
|
return s2.value;
|
|
391
395
|
},
|
|
392
|
-
set: !isSignal(value) && isObject(value) ? (v2) => v2 ? Object.assign(s2.value, v2) : s2.value =
|
|
396
|
+
set: !isSignal(value) && isObject(value) ? (v2) => v2 ? Object.assign(s2.value, v2) : s2.value = signalStruct(v2) : (v2) => s2.value = signalStruct(v2),
|
|
393
397
|
enumerable: true,
|
|
394
398
|
configurable: false
|
|
395
399
|
});
|
|
@@ -452,11 +456,14 @@ var primitive_pool_default = (key) => {
|
|
|
452
456
|
// src/directives.js
|
|
453
457
|
var directives = {};
|
|
454
458
|
var directives_default = (el, expr, values, name) => {
|
|
455
|
-
|
|
456
|
-
return directives.on(el, `{"${name.split("-").map((n2) => n2.startsWith("on") ? n2.slice(2) : n2).join("-")}": ${expr}}`, values);
|
|
457
|
-
}
|
|
459
|
+
let evt = name.startsWith("on") && name.slice(2);
|
|
458
460
|
let evaluate = parseExpr(el, expr, ":" + name);
|
|
459
|
-
|
|
461
|
+
let value;
|
|
462
|
+
return evt ? (state) => {
|
|
463
|
+
value && removeListener(el, evt, value);
|
|
464
|
+
value = evaluate(state);
|
|
465
|
+
value && addListener(el, evt, value);
|
|
466
|
+
} : (state) => attr(el, name, evaluate(state));
|
|
460
467
|
};
|
|
461
468
|
var attr = (el, name, v2) => {
|
|
462
469
|
if (v2 == null || v2 === false)
|
|
@@ -464,7 +471,7 @@ var attr = (el, name, v2) => {
|
|
|
464
471
|
else
|
|
465
472
|
el.setAttribute(name, v2 === true ? "" : typeof v2 === "number" || typeof v2 === "string" ? v2 : "");
|
|
466
473
|
};
|
|
467
|
-
directives[""] = (el, expr
|
|
474
|
+
directives[""] = (el, expr) => {
|
|
468
475
|
let evaluate = parseExpr(el, expr, ":");
|
|
469
476
|
return (state) => {
|
|
470
477
|
let value = evaluate(state);
|
|
@@ -474,15 +481,19 @@ directives[""] = (el, expr, values) => {
|
|
|
474
481
|
};
|
|
475
482
|
var _each = Symbol(":each");
|
|
476
483
|
var _ref = Symbol(":ref");
|
|
477
|
-
directives["ref"] = (el, expr,
|
|
484
|
+
directives["ref"] = (el, expr, state) => {
|
|
478
485
|
if (el.hasAttribute(":each")) {
|
|
479
486
|
el[_ref] = expr;
|
|
480
487
|
return;
|
|
481
488
|
}
|
|
482
489
|
;
|
|
483
|
-
|
|
490
|
+
state[expr] = el;
|
|
491
|
+
};
|
|
492
|
+
directives["with"] = (el, expr, rootState) => {
|
|
493
|
+
let evaluate = parseExpr(el, expr, "with");
|
|
494
|
+
sprae(el, signalStruct(evaluate(rootState), rootState));
|
|
484
495
|
};
|
|
485
|
-
directives["if"] = (el, expr
|
|
496
|
+
directives["if"] = (el, expr) => {
|
|
486
497
|
let holder = document.createTextNode(""), clauses = [parseExpr(el, expr, ":if")], els = [el], cur = el;
|
|
487
498
|
while (cur = el.nextElementSibling) {
|
|
488
499
|
if (cur.hasAttribute(":else")) {
|
|
@@ -508,7 +519,7 @@ directives["if"] = (el, expr, values) => {
|
|
|
508
519
|
}
|
|
509
520
|
};
|
|
510
521
|
};
|
|
511
|
-
directives["each"] = (tpl, expr
|
|
522
|
+
directives["each"] = (tpl, expr) => {
|
|
512
523
|
let each = parseForExpression(expr);
|
|
513
524
|
if (!each)
|
|
514
525
|
return exprError(new Error(), tpl, expr);
|
|
@@ -524,12 +535,12 @@ directives["each"] = (tpl, expr, values) => {
|
|
|
524
535
|
list = [];
|
|
525
536
|
else if (typeof list === "number")
|
|
526
537
|
list = Array.from({ length: list }, (_2, i2) => [i2, i2 + 1]);
|
|
527
|
-
else if (list.constructor === Object)
|
|
528
|
-
list = Object.entries(list);
|
|
529
538
|
else if (Array.isArray(list))
|
|
530
539
|
list = list.map((item, i2) => [i2 + 1, item]);
|
|
540
|
+
else if (typeof list === "object")
|
|
541
|
+
list = Object.entries(list);
|
|
531
542
|
else
|
|
532
|
-
exprError(Error("Bad list value"), tpl,
|
|
543
|
+
exprError(Error("Bad list value"), tpl, expr, ":each", list);
|
|
533
544
|
let newEls = [], elScopes = [];
|
|
534
545
|
for (let [idx, item] of list) {
|
|
535
546
|
let itemKey = primitive_pool_default(item);
|
|
@@ -576,12 +587,12 @@ function parseForExpression(expression) {
|
|
|
576
587
|
}
|
|
577
588
|
return res;
|
|
578
589
|
}
|
|
579
|
-
directives["id"] = (el, expr
|
|
590
|
+
directives["id"] = (el, expr) => {
|
|
580
591
|
let evaluate = parseExpr(el, expr, ":id");
|
|
581
592
|
const update = (v2) => el.id = v2 || v2 === 0 ? v2 : "";
|
|
582
593
|
return (state) => update(evaluate(state));
|
|
583
594
|
};
|
|
584
|
-
directives["class"] = (el, expr
|
|
595
|
+
directives["class"] = (el, expr) => {
|
|
585
596
|
let evaluate = parseExpr(el, expr, ":class");
|
|
586
597
|
let initClassName = el.className;
|
|
587
598
|
return (state) => {
|
|
@@ -589,7 +600,7 @@ directives["class"] = (el, expr, values) => {
|
|
|
589
600
|
el.className = initClassName + typeof v2 === "string" ? v2 : (Array.isArray(v2) ? v2 : Object.entries(v2).map(([k, v3]) => v3 ? k : "")).filter(Boolean).join(" ");
|
|
590
601
|
};
|
|
591
602
|
};
|
|
592
|
-
directives["style"] = (el, expr
|
|
603
|
+
directives["style"] = (el, expr) => {
|
|
593
604
|
let evaluate = parseExpr(el, expr, ":style");
|
|
594
605
|
let initStyle = el.getAttribute("style") || "";
|
|
595
606
|
if (!initStyle.endsWith(";"))
|
|
@@ -603,14 +614,14 @@ directives["style"] = (el, expr, values) => {
|
|
|
603
614
|
el.style[k] = v2[k];
|
|
604
615
|
};
|
|
605
616
|
};
|
|
606
|
-
directives["text"] = (el, expr
|
|
617
|
+
directives["text"] = (el, expr) => {
|
|
607
618
|
let evaluate = parseExpr(el, expr, ":text");
|
|
608
|
-
|
|
619
|
+
return (state) => {
|
|
620
|
+
let value = evaluate(state);
|
|
609
621
|
el.textContent = value == null ? "" : value;
|
|
610
622
|
};
|
|
611
|
-
return (state) => update(evaluate(state));
|
|
612
623
|
};
|
|
613
|
-
directives["value"] = (el, expr
|
|
624
|
+
directives["value"] = (el, expr) => {
|
|
614
625
|
let evaluate = parseExpr(el, expr, ":value");
|
|
615
626
|
let update = el.type === "text" || el.type === "" ? (value) => el.setAttribute("value", el.value = value == null ? "" : value) : el.type === "checkbox" ? (value) => (el.value = value ? "on" : "", attr(el, "checked", value)) : el.type === "select-one" ? (value) => {
|
|
616
627
|
for (let option in el.options)
|
|
@@ -620,36 +631,45 @@ directives["value"] = (el, expr, values) => {
|
|
|
620
631
|
} : (value) => el.value = value;
|
|
621
632
|
return (state) => update(evaluate(state));
|
|
622
633
|
};
|
|
623
|
-
|
|
634
|
+
var _stop = Symbol("stop");
|
|
635
|
+
directives["on"] = (el, expr) => {
|
|
624
636
|
let evaluate = parseExpr(el, expr, ":on");
|
|
625
637
|
let listeners = {};
|
|
626
638
|
return (state) => {
|
|
627
639
|
for (let evt in listeners)
|
|
628
|
-
el
|
|
640
|
+
removeListener(el, evt, listeners[evt]);
|
|
629
641
|
listeners = evaluate(state);
|
|
630
|
-
for (let evt in listeners)
|
|
631
|
-
|
|
632
|
-
if (evts.length === 1)
|
|
633
|
-
el.addEventListener(evt, listeners[evt]);
|
|
634
|
-
else {
|
|
635
|
-
const startFn = listeners[evt];
|
|
636
|
-
const nextEvt = (fn, cur = 0) => {
|
|
637
|
-
el.addEventListener(evts[cur], listeners[evt] = (e2) => {
|
|
638
|
-
fn = fn(e2);
|
|
639
|
-
el.removeEventListener(evts[cur], listeners[evt]);
|
|
640
|
-
if (++cur < evts.length && typeof fn === "function")
|
|
641
|
-
nextEvt(fn, cur);
|
|
642
|
-
else
|
|
643
|
-
nextEvt(startFn);
|
|
644
|
-
});
|
|
645
|
-
};
|
|
646
|
-
nextEvt(startFn);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
;
|
|
642
|
+
for (let evt in listeners)
|
|
643
|
+
addListener(el, evt, listeners[evt]);
|
|
650
644
|
};
|
|
651
645
|
};
|
|
652
|
-
|
|
646
|
+
var addListener = (el, evt, startFn) => {
|
|
647
|
+
if (evt.indexOf("..") < 0)
|
|
648
|
+
el.addEventListener(evt, startFn);
|
|
649
|
+
else {
|
|
650
|
+
const evts = evt.split("..").map((e2) => e2.startsWith("on") ? e2.slice(2) : e2);
|
|
651
|
+
const nextEvt = (fn, cur = 0) => {
|
|
652
|
+
let curListener = (e2) => {
|
|
653
|
+
el.removeEventListener(evts[cur], curListener);
|
|
654
|
+
if (typeof (fn = fn.call(el, e2)) !== "function")
|
|
655
|
+
fn = () => {
|
|
656
|
+
};
|
|
657
|
+
if (++cur < evts.length)
|
|
658
|
+
nextEvt(fn, cur);
|
|
659
|
+
else if (!startFn[_stop])
|
|
660
|
+
console.log("reset"), nextEvt(startFn);
|
|
661
|
+
};
|
|
662
|
+
el.addEventListener(evts[cur], curListener);
|
|
663
|
+
};
|
|
664
|
+
nextEvt(startFn);
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
var removeListener = (el, evt, fn) => {
|
|
668
|
+
if (evt.indexOf("..") >= 0)
|
|
669
|
+
console.log("rewire"), fn[_stop] = true;
|
|
670
|
+
el.removeEventListener(evt, fn);
|
|
671
|
+
};
|
|
672
|
+
directives["data"] = (el, expr) => {
|
|
653
673
|
let evaluate = parseExpr(el, expr, ":data");
|
|
654
674
|
return (state) => {
|
|
655
675
|
let value = evaluate(state);
|
|
@@ -657,7 +677,7 @@ directives["data"] = (el, expr, values) => {
|
|
|
657
677
|
el.dataset[key] = value[key];
|
|
658
678
|
};
|
|
659
679
|
};
|
|
660
|
-
directives["aria"] = (el, expr
|
|
680
|
+
directives["aria"] = (el, expr) => {
|
|
661
681
|
let evaluate = parseExpr(el, expr, ":aria");
|
|
662
682
|
const update = (value) => {
|
|
663
683
|
for (let key in value)
|
|
@@ -708,7 +728,7 @@ function sprae(container, values) {
|
|
|
708
728
|
return;
|
|
709
729
|
if (memo.has(container))
|
|
710
730
|
return memo.get(container);
|
|
711
|
-
values
|
|
731
|
+
const state = signalStruct(values || {});
|
|
712
732
|
const updates = [];
|
|
713
733
|
const init = (el, parent = el.parentNode) => {
|
|
714
734
|
if (el.attributes) {
|
|
@@ -720,10 +740,12 @@ function sprae(container, values) {
|
|
|
720
740
|
}
|
|
721
741
|
el.removeAttribute(attr2.name);
|
|
722
742
|
let expr = attr2.value;
|
|
743
|
+
if (!expr)
|
|
744
|
+
continue;
|
|
723
745
|
let attrNames = attr2.name.slice(1).split(":");
|
|
724
746
|
for (let attrName of attrNames) {
|
|
725
747
|
let dir = directives[attrName] || directives_default;
|
|
726
|
-
updates.push(dir(el, expr,
|
|
748
|
+
updates.push(dir(el, expr, state, attrName) || (() => {
|
|
727
749
|
}));
|
|
728
750
|
if (memo.has(el) || el.parentNode !== parent)
|
|
729
751
|
return false;
|
|
@@ -736,9 +758,9 @@ function sprae(container, values) {
|
|
|
736
758
|
}
|
|
737
759
|
};
|
|
738
760
|
init(container);
|
|
739
|
-
const state = SignalStruct(values);
|
|
740
761
|
for (let update of updates)
|
|
741
762
|
b(() => update(state));
|
|
763
|
+
Object.seal(state);
|
|
742
764
|
memo.set(container, state);
|
|
743
765
|
return state;
|
|
744
766
|
}
|
package/sprae.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function t(){throw new Error("Cycle detected")}function e(){if(n>1)n--;else{for(var t,e=!1;void 0!==r;){var i=r;for(r=void 0,o++;void 0!==i;){var s=i.o;if(i.o=void 0,i.f&=-3,!(8&i.f)&&l(i))try{i.c()}catch(i){e||(t=i,e=!0)}i=s}}if(o=0,n--,e)throw t}}var i=void 0,r=void 0,n=0,o=0,s=0;function f(t){if(void 0!==i){var e=t.n;if(void 0===e||e.t!==i)return i.s=e={i:0,S:t,p:void 0,n:i.s,t:i,e:void 0,x:void 0,r:e},t.n=e,32&i.f&&t.S(e),e;if(-1===e.i)return e.i=0,void 0!==e.p&&(e.p.n=e.n,void 0!==e.n&&(e.n.p=e.p),e.p=void 0,e.n=i.s,i.s.p=e,i.s=e),e}}function a(t){this.v=t,this.i=0,this.n=void 0,this.t=void 0}function u(t){return new a(t)}function l(t){for(var e=t.s;void 0!==e;e=e.n)if(e.S.i!==e.i||!e.S.h()||e.S.i!==e.i)return!0;return!1}function h(t){for(var e=t.s;void 0!==e;e=e.n){var i=e.S.n;void 0!==i&&(e.r=i),e.S.n=e,e.i=-1}}function c(t){for(var e=t.s,i=void 0;void 0!==e;){var r=e.n;-1===e.i?(e.S.U(e),e.n=void 0):(void 0!==i&&(i.p=e),e.p=void 0,e.n=i,i=e),e.S.n=e.r,void 0!==e.r&&(e.r=void 0),e=r}t.s=i}function v(t){a.call(this,void 0),this.x=t,this.s=void 0,this.g=s-1,this.f=4}function p(t){var r=t.u;if(t.u=void 0,"function"==typeof r){n++;var o=i;i=void 0;try{r()}catch(e){throw t.f&=-2,t.f|=8,d(t),e}finally{i=o,e()}}}function d(t){for(var e=t.s;void 0!==e;e=e.n)e.S.U(e);t.x=void 0,t.s=void 0,p(t)}function y(t){if(i!==this)throw new Error("Out-of-order effect");c(this),i=t,this.f&=-2,8&this.f&&d(this),e()}function b(t){this.x=t,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32}function m(t){var e=new b(t);return e.c(),e.d.bind(e)}a.prototype.h=function(){return!0},a.prototype.S=function(t){this.t!==t&&void 0===t.e&&(t.x=this.t,void 0!==this.t&&(this.t.e=t),this.t=t)},a.prototype.U=function(t){var e=t.e,i=t.x;void 0!==e&&(e.x=i,t.e=void 0),void 0!==i&&(i.e=e,t.x=void 0),t===this.t&&(this.t=i)},a.prototype.subscribe=function(t){var e=this;return m((function(){var i=e.value,r=32&this.f;this.f&=-33;try{t(i)}finally{this.f|=r}}))},a.prototype.valueOf=function(){return this.value},a.prototype.toString=function(){return this.value+""},a.prototype.peek=function(){return this.v},Object.defineProperty(a.prototype,"value",{get:function(){var t=f(this);return void 0!==t&&(t.i=this.i),this.v},set:function(i){if(i!==this.v){o>100&&t(),this.v=i,this.i++,s++,n++;try{for(var r=this.t;void 0!==r;r=r.x)r.t.N()}finally{e()}}}}),(v.prototype=new a).h=function(){if(this.f&=-3,1&this.f)return!1;if(32==(36&this.f))return!0;if(this.f&=-5,this.g===s)return!0;if(this.g=s,this.f|=1,this.i>0&&!l(this))return this.f&=-2,!0;var t=i;try{h(this),i=this;var e=this.x();(16&this.f||this.v!==e||0===this.i)&&(this.v=e,this.f&=-17,this.i++)}catch(t){this.v=t,this.f|=16,this.i++}return i=t,c(this),this.f&=-2,!0},v.prototype.S=function(t){if(void 0===this.t){this.f|=36;for(var e=this.s;void 0!==e;e=e.n)e.S.S(e)}a.prototype.S.call(this,t)},v.prototype.U=function(t){if(a.prototype.U.call(this,t),void 0===this.t){this.f&=-33;for(var e=this.s;void 0!==e;e=e.n)e.S.U(e)}},v.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(var t=this.t;void 0!==t;t=t.x)t.t.N()}},v.prototype.peek=function(){if(this.h()||t(),16&this.f)throw this.v;return this.v},Object.defineProperty(v.prototype,"value",{get:function(){1&this.f&&t();var e=f(this);if(this.h(),void 0!==e&&(e.i=this.i),16&this.f)throw this.v;return this.v}}),b.prototype.c=function(){var t=this.S();try{8&this.f||void 0===this.x||(this.u=this.x())}finally{t()}},b.prototype.S=function(){1&this.f&&t(),this.f|=1,this.f&=-9,p(this),h(this),n++;var e=i;return i=this,y.bind(this,e)},b.prototype.N=function(){2&this.f||(this.f|=2,this.o=r,r=this)},b.prototype.d=function(){this.f|=8,1&this.f||d(this)},Symbol.observable||=Symbol("observable");var g=new FinalizationRegistry((t=>t.call?.())),S=t=>t&&t.peek,x=Symbol("signal-struct");function w(t){if((e=t)&&e[x])return t;var e;let i,r;if(O(t)){i={},r={};let e=Object.getOwnPropertyDescriptors(t);for(let t in e)r[t]=A(i,t,e[t].get?new v(e[t].get.bind(i)):e[t].value);return Object.defineProperty(i,x,{configurable:!1,enumerable:!1,value:!0}),Object.seal(i),i}return Array.isArray(t)?t.map((t=>w(t))):t}function A(t,e,i){let r,n=S(i)?i:O(i)||Array.isArray(i)?u(w(i)):u((r=(o=i)&&!!(o[Symbol.observable]||o[Symbol.asyncIterator]||o.call&&o.set||o.subscribe||o.then))?void 0:i);var o,s,f,a,l,h,c;return r&&(f=t=>n.value=t,(s=i)&&(c=(s[Symbol.observable]?.()||s).subscribe?.(f,a,undefined),h=c&&(()=>c.unsubscribe?.())||s.set&&s.call?.(l,f)||(s.then?.((t=>{!l&&f(t)}),a)||(async t=>{try{for await(t of s){if(l)return;f(t)}}catch(t){}})())&&(t=>l=1),g.register(s,h))),Object.defineProperty(t,e,{get:()=>n.value,set:!S(i)&&O(i)?t=>t?Object.assign(n.value,t):n.value=w(t):t=>n.value=w(t),enumerable:!0,configurable:!1}),n}function O(t){return t&&t.constructor===Object}var j=(t,e,i,r=null)=>{let n,o,s,f=0,a=i.length,u=e.length,{remove:l,same:h,insert:c,replace:v}=j;for(;f<a&&f<u&&h(e[f],i[f]);)f++;for(;f<a&&f<u&&h(i[a-1],e[u-1]);)r=i[(--u,--a)];if(f==u)for(;f<a;)c(r,i[f++],t);else{for(n=e[f];f<a;)s=i[f++],o=n?n.nextSibling:r,h(n,s)?n=o:f<a&&h(i[f],o)?(v(n,s,t),n=o):c(n,s,t);for(;!h(n,r);)o=n.nextSibling,l(n,t),n=o}return i};j.same=(t,e)=>t==e,j.replace=(t,e,i)=>i.replaceChild(e,t),j.insert=(t,e,i)=>i.insertBefore(e,t),j.remove=(t,e)=>e.removeChild(t);var N=j,E={},$={},W={},k={},C=(t,e,i,r)=>{if(r.startsWith("on"))return k.on(t,`{"${r.split("-").map((t=>t.startsWith("on")?t.slice(2):t)).join("-")}": ${e}}`,i);let n=D(t,e,":"+r);return e=>U(t,r,n(e))},U=(t,e,i)=>{null==i||!1===i?t.removeAttribute(e):t.setAttribute(e,!0===i?"":"number"==typeof i||"string"==typeof i?i:"")};k[""]=(t,e,i)=>{let r=D(t,e,":");return e=>{let i=r(e);for(let e in i)U(t,M(e),i[e])}};var B=Symbol(":each"),L=Symbol(":ref");k.ref=(t,e,i)=>{t.hasAttribute(":each")?t[L]=e:i[e]=t},k.if=(t,e,i)=>{let r=document.createTextNode(""),n=[D(t,e,":if")],o=[t],s=t;for(;(s=t.nextElementSibling)&&s.hasAttribute(":else");)s.removeAttribute(":else"),(e=s.getAttribute(":if"))?(s.removeAttribute(":if"),s.remove(),o.push(s),n.push(D(t,e,":else :if"))):(s.remove(),o.push(s),n.push((()=>1)));return t.replaceWith(s=r),t=>{let e=n.findIndex((e=>e(t)));o[e]!=s&&((s[B]||s).replaceWith(s=o[e]||r),F(s,t))}},k.each=(t,e,i)=>{let r=function(t){let e=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,i=t.match(/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/);if(!i)return;let r={};r.items=i[2].trim();let n=i[1].replace(/^\s*\(|\)\s*$/g,"").trim(),o=n.match(e);return o?(r.item=n.replace(e,"").trim(),r.index=o[1].trim()):r.item=n,r}(e);if(!r)return _(new Error,t,e);const n=t[B]=document.createTextNode("");t.replaceWith(n);const o=D(t,r.items,":each"),s=new WeakMap,f=new WeakMap;let a=[];return e=>{let i=o(e);i?"number"==typeof i?i=Array.from({length:i},((t,e)=>[e,e+1])):i.constructor===Object?i=Object.entries(i):Array.isArray(i)?i=i.map(((t,e)=>[e+1,t])):_(Error("Bad list value"),t,r.items,":each"):i=[];let u=[],l=[];for(let[n,o]of i){let i=null===(h=o)?$:void 0===h?W:"number"==typeof h||h instanceof Number?E[h]||(E[h]=new Number(h)):"string"==typeof h||h instanceof String?E[h]||(E[h]=new String(h)):"boolean"==typeof h||h instanceof Boolean?E[h]||(E[h]=new Boolean(h)):h,a=f.get(i);if(a||(a=t.cloneNode(!0),f.set(i,a)),u.push(a),!s.has(i)){let f=Object.create(e);f[r.item]=o,r.index&&(f[r.index]=n),t[L]&&(f[t[L]]=a),s.set(i,f)}l.push(s.get(i))}var h;N(n.parentNode,a,u,n),a=u;for(let t=0;t<u.length;t++)F(u[t],l[t])}},k.id=(t,e,i)=>{let r=D(t,e,":id");return e=>{return i=r(e),t.id=i||0===i?i:"";var i}},k.class=(t,e,i)=>{let r=D(t,e,":class"),n=t.className;return e=>{let i=r(e);t.className=n+typeof i=="string"?i:(Array.isArray(i)?i:Object.entries(i).map((([t,e])=>e?t:""))).filter(Boolean).join(" ")}},k.style=(t,e,i)=>{let r=D(t,e,":style"),n=t.getAttribute("style")||"";return n.endsWith(";")||(n+="; "),e=>{let i=r(e);if("string"==typeof i)t.setAttribute("style",n+i);else for(let e in i)t.style[e]=i[e]}},k.text=(t,e,i)=>{let r=D(t,e,":text");return e=>{return i=r(e),void(t.textContent=null==i?"":i);var i}},k.value=(t,e,i)=>{let r=D(t,e,":value"),n="text"===t.type||""===t.type?e=>t.setAttribute("value",t.value=null==e?"":e):"checkbox"===t.type?e=>(t.value=e?"on":"",U(t,"checked",e)):"select-one"===t.type?e=>{for(let e in t.options)e.removeAttribute("selected");t.value=e,t.selectedOptions[0]?.setAttribute("selected","")}:e=>t.value=e;return t=>n(r(t))},k.on=(t,e,i)=>{let r=D(t,e,":on"),n={};return e=>{for(let e in n)t.removeEventListener(e,n[e]);n=r(e);for(let e in n){const i=e.split("-");if(1===i.length)t.addEventListener(e,n[e]);else{const r=n[e],o=(s,f=0)=>{t.addEventListener(i[f],n[e]=a=>{s=s(a),t.removeEventListener(i[f],n[e]),++f<i.length&&"function"==typeof s?o(s,f):o(r)})};o(r)}}}},k.data=(t,e,i)=>{let r=D(t,e,":data");return e=>{let i=r(e);for(let e in i)t.dataset[e]=i[e]}},k.aria=(t,e,i)=>{let r=D(t,e,":aria");return e=>(e=>{for(let i in e)U(t,"aria-"+M(i),null==e[i]?null:e[i]+"")})(r(e))};var P={};function D(t,e,i){if(P[e])return P[e];let r,n=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(() => { ${e} })()`:e;try{r=new Function("__scope",`with (__scope) { return (${n}) };`).bind(t)}catch(r){return _(r,t,e,i)}return P[e]=n=>{let o;try{o=r(n)}catch(r){return _(r,t,e,i)}return o}}function _(t,e,i,r){Object.assign(t,{element:e,expression:i}),console.warn(`∴ ${t.message}\n\n${r}=${i?`"${i}"\n\n`:""}`,e),setTimeout((()=>{throw t}),0)}function M(t){return t.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(t=>"-"+t.toLowerCase()))}var T=new WeakMap;function F(t,e){if(!t.children)return;if(T.has(t))return T.get(t);e||={};const i=[],r=(t,n=t.parentNode)=>{if(t.attributes)for(let r=0;r<t.attributes.length;){let o=t.attributes[r];if(":"!==o.name[0]){r++;continue}t.removeAttribute(o.name);let s=o.value,f=o.name.slice(1).split(":");for(let r of f){let o=k[r]||C;if(i.push(o(t,s,e,r)||(()=>{})),T.has(t)||t.parentNode!==n)return!1}}for(let e,i=0;e=t.children[i];i++)!1===r(e,t)&&i--};r(t);const n=w(e);for(let t of i)m((()=>t(n)));return T.set(t,n),n}var I=F;export{I as default};
|
|
1
|
+
function t(){throw new Error("Cycle detected")}function e(){if(n>1)n--;else{for(var t,e=!1;void 0!==r;){var i=r;for(r=void 0,o++;void 0!==i;){var s=i.o;if(i.o=void 0,i.f&=-3,!(8&i.f)&&u(i))try{i.c()}catch(i){e||(t=i,e=!0)}i=s}}if(o=0,n--,e)throw t}}var i=void 0,r=void 0,n=0,o=0,s=0;function f(t){if(void 0!==i){var e=t.n;if(void 0===e||e.t!==i)return i.s=e={i:0,S:t,p:void 0,n:i.s,t:i,e:void 0,x:void 0,r:e},t.n=e,32&i.f&&t.S(e),e;if(-1===e.i)return e.i=0,void 0!==e.p&&(e.p.n=e.n,void 0!==e.n&&(e.n.p=e.p),e.p=void 0,e.n=i.s,i.s.p=e,i.s=e),e}}function l(t){this.v=t,this.i=0,this.n=void 0,this.t=void 0}function a(t){return new l(t)}function u(t){for(var e=t.s;void 0!==e;e=e.n)if(e.S.i!==e.i||!e.S.h()||e.S.i!==e.i)return!0;return!1}function h(t){for(var e=t.s;void 0!==e;e=e.n){var i=e.S.n;void 0!==i&&(e.r=i),e.S.n=e,e.i=-1}}function c(t){for(var e=t.s,i=void 0;void 0!==e;){var r=e.n;-1===e.i?(e.S.U(e),e.n=void 0):(void 0!==i&&(i.p=e),e.p=void 0,e.n=i,i=e),e.S.n=e.r,void 0!==e.r&&(e.r=void 0),e=r}t.s=i}function v(t){l.call(this,void 0),this.x=t,this.s=void 0,this.g=s-1,this.f=4}function p(t){var r=t.u;if(t.u=void 0,"function"==typeof r){n++;var o=i;i=void 0;try{r()}catch(e){throw t.f&=-2,t.f|=8,d(t),e}finally{i=o,e()}}}function d(t){for(var e=t.s;void 0!==e;e=e.n)e.S.U(e);t.x=void 0,t.s=void 0,p(t)}function y(t){if(i!==this)throw new Error("Out-of-order effect");c(this),i=t,this.f&=-2,8&this.f&&d(this),e()}function b(t){this.x=t,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32}function m(t){var e=new b(t);return e.c(),e.d.bind(e)}l.prototype.h=function(){return!0},l.prototype.S=function(t){this.t!==t&&void 0===t.e&&(t.x=this.t,void 0!==this.t&&(this.t.e=t),this.t=t)},l.prototype.U=function(t){var e=t.e,i=t.x;void 0!==e&&(e.x=i,t.e=void 0),void 0!==i&&(i.e=e,t.x=void 0),t===this.t&&(this.t=i)},l.prototype.subscribe=function(t){var e=this;return m((function(){var i=e.value,r=32&this.f;this.f&=-33;try{t(i)}finally{this.f|=r}}))},l.prototype.valueOf=function(){return this.value},l.prototype.toString=function(){return this.value+""},l.prototype.peek=function(){return this.v},Object.defineProperty(l.prototype,"value",{get:function(){var t=f(this);return void 0!==t&&(t.i=this.i),this.v},set:function(i){if(i!==this.v){o>100&&t(),this.v=i,this.i++,s++,n++;try{for(var r=this.t;void 0!==r;r=r.x)r.t.N()}finally{e()}}}}),(v.prototype=new l).h=function(){if(this.f&=-3,1&this.f)return!1;if(32==(36&this.f))return!0;if(this.f&=-5,this.g===s)return!0;if(this.g=s,this.f|=1,this.i>0&&!u(this))return this.f&=-2,!0;var t=i;try{h(this),i=this;var e=this.x();(16&this.f||this.v!==e||0===this.i)&&(this.v=e,this.f&=-17,this.i++)}catch(t){this.v=t,this.f|=16,this.i++}return i=t,c(this),this.f&=-2,!0},v.prototype.S=function(t){if(void 0===this.t){this.f|=36;for(var e=this.s;void 0!==e;e=e.n)e.S.S(e)}l.prototype.S.call(this,t)},v.prototype.U=function(t){if(l.prototype.U.call(this,t),void 0===this.t){this.f&=-33;for(var e=this.s;void 0!==e;e=e.n)e.S.U(e)}},v.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(var t=this.t;void 0!==t;t=t.x)t.t.N()}},v.prototype.peek=function(){if(this.h()||t(),16&this.f)throw this.v;return this.v},Object.defineProperty(v.prototype,"value",{get:function(){1&this.f&&t();var e=f(this);if(this.h(),void 0!==e&&(e.i=this.i),16&this.f)throw this.v;return this.v}}),b.prototype.c=function(){var t=this.S();try{8&this.f||void 0===this.x||(this.u=this.x())}finally{t()}},b.prototype.S=function(){1&this.f&&t(),this.f|=1,this.f&=-9,p(this),h(this),n++;var e=i;return i=this,y.bind(this,e)},b.prototype.N=function(){2&this.f||(this.f|=2,this.o=r,r=this)},b.prototype.d=function(){this.f|=8,1&this.f||d(this)},Symbol.observable||=Symbol("observable");var g=new FinalizationRegistry((t=>t.call?.())),S=t=>t&&t.peek,x=t=>t&&t[w],w=Symbol("signal-struct");function A(t,e){if(x(t)&&!e)return t;let i,r;if(j(t)){i=Object.create(e||Object.getPrototypeOf(t)),r={};let n=Object.getOwnPropertyDescriptors(t);if(x(t))for(let t in n)Object.defineProperty(i,t,n[t]);else for(let t in n)r[t]=O(i,t,n[t].get?new v(n[t].get.bind(i)):n[t].value);return Object.defineProperty(i,w,{configurable:!1,enumerable:!1,value:!0}),i}return Array.isArray(t)?t.map((t=>A(t))):t}function O(t,e,i){let r,n=S(i)?i:j(i)||Array.isArray(i)?a(A(i)):a((r=(o=i)&&!!(o[Symbol.observable]||o[Symbol.asyncIterator]||o.call&&o.set||o.subscribe||o.then))?void 0:i);var o,s,f,l,u,h,c;return r&&(f=t=>n.value=t,(s=i)&&(c=(s[Symbol.observable]?.()||s).subscribe?.(f,l,undefined),h=c&&(()=>c.unsubscribe?.())||s.set&&s.call?.(u,f)||(s.then?.((t=>{!u&&f(t)}),l)||(async t=>{try{for await(t of s){if(u)return;f(t)}}catch(t){}})())&&(t=>u=1),g.register(s,h))),Object.defineProperty(t,e,{get:()=>n.value,set:!S(i)&&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}A.isStruct=x;var N=(t,e,i,r=null)=>{let n,o,s,f=0,l=i.length,a=e.length,{remove:u,same:h,insert:c,replace:v}=N;for(;f<l&&f<a&&h(e[f],i[f]);)f++;for(;f<l&&f<a&&h(i[l-1],e[a-1]);)r=i[(--a,--l)];if(f==a)for(;f<l;)c(r,i[f++],t);else{for(n=e[f];f<l;)s=i[f++],o=n?n.nextSibling:r,h(n,s)?n=o:f<l&&h(i[f],o)?(v(n,s,t),n=o):c(n,s,t);for(;!h(n,r);)o=n.nextSibling,u(n,t),n=o}return i};N.same=(t,e)=>t==e,N.replace=(t,e,i)=>i.replaceChild(e,t),N.insert=(t,e,i)=>i.insertBefore(e,t),N.remove=(t,e)=>e.removeChild(t);var E=N,W={},k={},$={},P={},C=(t,e,i,r)=>{let n,o=r.startsWith("on")&&r.slice(2),s=F(t,e,":"+r);return o?e=>{n&&M(t,o,n),n=s(e),n&&_(t,o,n)}:e=>U(t,r,s(e))},U=(t,e,i)=>{null==i||!1===i?t.removeAttribute(e):t.setAttribute(e,!0===i?"":"number"==typeof i||"string"==typeof i?i:"")};P[""]=(t,e)=>{let i=F(t,e,":");return e=>{let r=i(e);for(let e in r)U(t,z(e),r[e])}};var B=Symbol(":each"),L=Symbol(":ref");P.ref=(t,e,i)=>{t.hasAttribute(":each")?t[L]=e:i[e]=t},P.with=(t,e,i)=>{Z(t,A(F(t,e,"with")(i),i))},P.if=(t,e)=>{let i=document.createTextNode(""),r=[F(t,e,":if")],n=[t],o=t;for(;(o=t.nextElementSibling)&&o.hasAttribute(":else");)o.removeAttribute(":else"),(e=o.getAttribute(":if"))?(o.removeAttribute(":if"),o.remove(),n.push(o),r.push(F(t,e,":else :if"))):(o.remove(),n.push(o),r.push((()=>1)));return t.replaceWith(o=i),t=>{let e=r.findIndex((e=>e(t)));n[e]!=o&&((o[B]||o).replaceWith(o=n[e]||i),Z(o,t))}},P.each=(t,e)=>{let i=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(!i)return I(new Error,t,e);const r=t[B]=document.createTextNode("");t.replaceWith(r);const n=F(t,i.items,":each"),o=new WeakMap,s=new WeakMap;let f=[];return l=>{let a=n(l);a?"number"==typeof a?a=Array.from({length:a},((t,e)=>[e,e+1])):Array.isArray(a)?a=a.map(((t,e)=>[e+1,t])):"object"==typeof a?a=Object.entries(a):I(Error("Bad list value"),t,e,":each"):a=[];let u=[],h=[];for(let[e,r]of a){let n=null===(c=r)?k:void 0===c?$:"number"==typeof c||c instanceof Number?W[c]||(W[c]=new Number(c)):"string"==typeof c||c instanceof String?W[c]||(W[c]=new String(c)):"boolean"==typeof c||c instanceof Boolean?W[c]||(W[c]=new Boolean(c)):c,f=s.get(n);if(f||(f=t.cloneNode(!0),s.set(n,f)),u.push(f),!o.has(n)){let s=Object.create(l);s[i.item]=r,i.index&&(s[i.index]=e),t[L]&&(s[t[L]]=f),o.set(n,s)}h.push(o.get(n))}var c;E(r.parentNode,f,u,r),f=u;for(let t=0;t<u.length;t++)Z(u[t],h[t])}},P.id=(t,e)=>{let i=F(t,e,":id");return e=>{return r=i(e),t.id=r||0===r?r:"";var r}},P.class=(t,e)=>{let i=F(t,e,":class"),r=t.className;return e=>{let n=i(e);t.className=r+typeof n=="string"?n:(Array.isArray(n)?n:Object.entries(n).map((([t,e])=>e?t:""))).filter(Boolean).join(" ")}},P.style=(t,e)=>{let i=F(t,e,":style"),r=t.getAttribute("style")||"";return r.endsWith(";")||(r+="; "),e=>{let n=i(e);if("string"==typeof n)t.setAttribute("style",r+n);else for(let e in n)t.style[e]=n[e]}},P.text=(t,e)=>{let i=F(t,e,":text");return e=>{let r=i(e);t.textContent=null==r?"":r}},P.value=(t,e)=>{let i=F(t,e,":value"),r="text"===t.type||""===t.type?e=>t.setAttribute("value",t.value=null==e?"":e):"checkbox"===t.type?e=>(t.value=e?"on":"",U(t,"checked",e)):"select-one"===t.type?e=>{for(let e in t.options)e.removeAttribute("selected");t.value=e,t.selectedOptions[0]?.setAttribute("selected","")}:e=>t.value=e;return t=>r(i(t))};var D=Symbol("stop");P.on=(t,e)=>{let i=F(t,e,":on"),r={};return e=>{for(let e in r)M(t,e,r[e]);r=i(e);for(let e in r)_(t,e,r[e])}};var _=(t,e,i)=>{if(e.indexOf("..")<0)t.addEventListener(e,i);else{const r=e.split("..").map((t=>t.startsWith("on")?t.slice(2):t)),n=(e,o=0)=>{let s=f=>{t.removeEventListener(r[o],s),"function"!=typeof(e=e.call(t,f))&&(e=()=>{}),++o<r.length?n(e,o):i[D]||(console.log("reset"),n(i))};t.addEventListener(r[o],s)};n(i)}},M=(t,e,i)=>{e.indexOf("..")>=0&&(console.log("rewire"),i[D]=!0),t.removeEventListener(e,i)};P.data=(t,e)=>{let i=F(t,e,":data");return e=>{let r=i(e);for(let e in r)t.dataset[e]=r[e]}},P.aria=(t,e)=>{let i=F(t,e,":aria");return e=>(e=>{for(let i in e)U(t,"aria-"+z(i),null==e[i]?null:e[i]+"")})(i(e))};var T={};function F(t,e,i){if(T[e])return T[e];let r,n=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(() => { ${e} })()`:e;try{r=new Function("__scope",`with (__scope) { return (${n}) };`).bind(t)}catch(r){return I(r,t,e,i)}return T[e]=n=>{let o;try{o=r(n)}catch(r){return I(r,t,e,i)}return o}}function I(t,e,i,r){Object.assign(t,{element:e,expression:i}),console.warn(`∴ ${t.message}\n\n${r}=${i?`"${i}"\n\n`:""}`,e),setTimeout((()=>{throw t}),0)}function z(t){return t.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(t=>"-"+t.toLowerCase()))}var R=new WeakMap;function Z(t,e){if(!t.children)return;if(R.has(t))return R.get(t);const i=A(e||{}),r=[],n=(t,e=t.parentNode)=>{if(t.attributes)for(let n=0;n<t.attributes.length;){let o=t.attributes[n];if(":"!==o.name[0]){n++;continue}t.removeAttribute(o.name);let s=o.value;if(!s)continue;let f=o.name.slice(1).split(":");for(let n of f){let o=P[n]||C;if(r.push(o(t,s,i,n)||(()=>{})),R.has(t)||t.parentNode!==e)return!1}}for(let e,i=0;e=t.children[i];i++)!1===n(e,t)&&i--};n(t);for(let t of r)m((()=>t(i)));return Object.seal(i),R.set(t,i),i}var q=Z;export{q as default};
|
package/src/core.js
CHANGED
|
@@ -8,8 +8,8 @@ export default function sprae(container, values) {
|
|
|
8
8
|
if (!container.children) return
|
|
9
9
|
if (memo.has(container)) return memo.get(container)
|
|
10
10
|
|
|
11
|
-
values
|
|
12
|
-
|
|
11
|
+
// signalStruct returns values if it's signalStruct already
|
|
12
|
+
const state = signalStruct(values || {});
|
|
13
13
|
const updates = []
|
|
14
14
|
|
|
15
15
|
// init directives on element
|
|
@@ -20,10 +20,11 @@ export default function sprae(container, values) {
|
|
|
20
20
|
if (attr.name[0] !== ':') {i++; continue}
|
|
21
21
|
el.removeAttribute(attr.name)
|
|
22
22
|
let expr = attr.value
|
|
23
|
+
if (!expr) continue
|
|
23
24
|
let attrNames = attr.name.slice(1).split(':')
|
|
24
25
|
for (let attrName of attrNames) {
|
|
25
26
|
let dir = directives[attrName] || defaultDirective;
|
|
26
|
-
updates.push(dir(el, expr,
|
|
27
|
+
updates.push(dir(el, expr, state, attrName) || (()=>{}));
|
|
27
28
|
|
|
28
29
|
// stop if element was spraed by directive or skipped
|
|
29
30
|
if (memo.has(el) || el.parentNode !== parent) return false
|
|
@@ -41,9 +42,9 @@ export default function sprae(container, values) {
|
|
|
41
42
|
|
|
42
43
|
// call updates: subscribes directives to state;
|
|
43
44
|
// state is created after inits because directives can extend init values (expose refs etc)
|
|
44
|
-
const state = signalStruct(values);
|
|
45
45
|
for (let update of updates) effect(() => update(state));
|
|
46
46
|
|
|
47
|
+
Object.seal(state);
|
|
47
48
|
memo.set(container, state);
|
|
48
49
|
|
|
49
50
|
return state;
|
package/src/directives.js
CHANGED
|
@@ -2,20 +2,23 @@
|
|
|
2
2
|
import sprae from './core.js'
|
|
3
3
|
import swap from 'swapdom'
|
|
4
4
|
import p from 'primitive-pool'
|
|
5
|
+
import signalStruct from 'signal-struct'
|
|
5
6
|
|
|
6
7
|
// reserved directives - order matters!
|
|
7
8
|
export const directives = {}
|
|
8
9
|
|
|
9
10
|
// any-prop directives
|
|
10
11
|
export default (el, expr, values, name) => {
|
|
11
|
-
|
|
12
|
-
// :ona:onb=x -> :on={a:x,b:x}
|
|
13
|
-
// :ona-onb=x -> :on={aB:x}
|
|
14
|
-
return directives.on(el, `{"${name.split('-').map(n=>n.startsWith('on')?n.slice(2):n).join('-')}": ${expr}}`, values)
|
|
15
|
-
}
|
|
12
|
+
let evt = name.startsWith('on') && name.slice(2)
|
|
16
13
|
let evaluate = parseExpr(el, expr, ':'+name)
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
let value
|
|
16
|
+
return evt ? state => {
|
|
17
|
+
value && removeListener(el, evt, value)
|
|
18
|
+
value = evaluate(state)
|
|
19
|
+
value && addListener(el, evt, value)
|
|
20
|
+
}
|
|
21
|
+
: state => attr(el, name, evaluate(state))
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
// set attr
|
|
@@ -24,7 +27,7 @@ const attr = (el, name, v) => {
|
|
|
24
27
|
else el.setAttribute(name, v === true ? '' : (typeof v === 'number' || typeof v === 'string') ? v : '')
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
directives[''] = (el, expr
|
|
30
|
+
directives[''] = (el, expr) => {
|
|
28
31
|
let evaluate = parseExpr(el, expr, ':')
|
|
29
32
|
return (state) => {
|
|
30
33
|
let value = evaluate(state)
|
|
@@ -34,16 +37,21 @@ directives[''] = (el, expr, values) => {
|
|
|
34
37
|
|
|
35
38
|
const _each = Symbol(':each'), _ref = Symbol(':ref')
|
|
36
39
|
|
|
37
|
-
directives['ref'] = (el, expr,
|
|
40
|
+
directives['ref'] = (el, expr, state) => {
|
|
38
41
|
// make sure :ref is initialized after :each (return to avoid initializing as signal)
|
|
39
42
|
if (el.hasAttribute(':each')) {el[_ref] = expr; return};
|
|
40
43
|
|
|
41
44
|
// FIXME: wait for complex ref use-case
|
|
42
45
|
// parseExpr(el, `__scope[${expr}]=this`, ':ref')(values)
|
|
43
|
-
|
|
46
|
+
state[expr] = el;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
directives['with'] = (el, expr, rootState) => {
|
|
50
|
+
let evaluate = parseExpr(el, expr, 'with')
|
|
51
|
+
sprae(el, signalStruct(evaluate(rootState), rootState));
|
|
44
52
|
}
|
|
45
53
|
|
|
46
|
-
directives['if'] = (el, expr
|
|
54
|
+
directives['if'] = (el, expr) => {
|
|
47
55
|
let holder = document.createTextNode(''),
|
|
48
56
|
clauses = [parseExpr(el, expr, ':if')],
|
|
49
57
|
els = [el], cur = el
|
|
@@ -74,7 +82,7 @@ directives['if'] = (el, expr, values) => {
|
|
|
74
82
|
}
|
|
75
83
|
}
|
|
76
84
|
|
|
77
|
-
directives['each'] = (tpl, expr
|
|
85
|
+
directives['each'] = (tpl, expr) => {
|
|
78
86
|
let each = parseForExpression(expr);
|
|
79
87
|
if (!each) return exprError(new Error, tpl, expr);
|
|
80
88
|
|
|
@@ -96,9 +104,9 @@ directives['each'] = (tpl, expr, values) => {
|
|
|
96
104
|
let list = evaluate(state)
|
|
97
105
|
if (!list) list = []
|
|
98
106
|
else if (typeof list === 'number') list = Array.from({length: list}, (_, i)=>[i, i+1])
|
|
99
|
-
else if (list.constructor === Object) list = Object.entries(list)
|
|
100
107
|
else if (Array.isArray(list)) list = list.map((item,i) => [i+1, item])
|
|
101
|
-
else
|
|
108
|
+
else if (typeof list === 'object') list = Object.entries(list)
|
|
109
|
+
else exprError(Error('Bad list value'), tpl, expr, ':each', list)
|
|
102
110
|
|
|
103
111
|
// collect elements/scopes for items
|
|
104
112
|
let newEls = [], elScopes = []
|
|
@@ -158,13 +166,13 @@ function parseForExpression(expression) {
|
|
|
158
166
|
return res
|
|
159
167
|
}
|
|
160
168
|
|
|
161
|
-
directives['id'] = (el, expr
|
|
169
|
+
directives['id'] = (el, expr) => {
|
|
162
170
|
let evaluate = parseExpr(el, expr, ':id')
|
|
163
171
|
const update = v => el.id = v || v === 0 ? v : ''
|
|
164
172
|
return (state) => update(evaluate(state))
|
|
165
173
|
}
|
|
166
174
|
|
|
167
|
-
directives['class'] = (el, expr
|
|
175
|
+
directives['class'] = (el, expr) => {
|
|
168
176
|
let evaluate = parseExpr(el, expr, ':class')
|
|
169
177
|
let initClassName = el.className
|
|
170
178
|
return (state) => {
|
|
@@ -173,7 +181,7 @@ directives['class'] = (el, expr, values) => {
|
|
|
173
181
|
}
|
|
174
182
|
}
|
|
175
183
|
|
|
176
|
-
directives['style'] = (el, expr
|
|
184
|
+
directives['style'] = (el, expr) => {
|
|
177
185
|
let evaluate = parseExpr(el, expr, ':style')
|
|
178
186
|
let initStyle = el.getAttribute('style') || ''
|
|
179
187
|
if (!initStyle.endsWith(';')) initStyle += '; '
|
|
@@ -184,18 +192,17 @@ directives['style'] = (el, expr, values) => {
|
|
|
184
192
|
}
|
|
185
193
|
}
|
|
186
194
|
|
|
187
|
-
directives['text'] = (el, expr
|
|
195
|
+
directives['text'] = (el, expr) => {
|
|
188
196
|
let evaluate = parseExpr(el, expr, ':text')
|
|
189
197
|
|
|
190
|
-
|
|
198
|
+
return (state) => {
|
|
199
|
+
let value = evaluate(state)
|
|
191
200
|
el.textContent = value == null ? '' : value;
|
|
192
201
|
}
|
|
193
|
-
|
|
194
|
-
return (state) => update(evaluate(state))
|
|
195
202
|
}
|
|
196
203
|
|
|
197
204
|
// connect expr to element value
|
|
198
|
-
directives['value'] = (el, expr
|
|
205
|
+
directives['value'] = (el, expr) => {
|
|
199
206
|
let evaluate = parseExpr(el, expr, ':value')
|
|
200
207
|
|
|
201
208
|
let update = (
|
|
@@ -212,35 +219,43 @@ directives['value'] = (el, expr, values) => {
|
|
|
212
219
|
return (state) => update(evaluate(state))
|
|
213
220
|
}
|
|
214
221
|
|
|
215
|
-
|
|
222
|
+
const _stop = Symbol('stop')
|
|
223
|
+
directives['on'] = (el, expr) => {
|
|
216
224
|
let evaluate = parseExpr(el, expr, ':on')
|
|
217
225
|
let listeners = {}
|
|
218
226
|
|
|
219
227
|
return (state) => {
|
|
220
|
-
for (let evt in listeners) el
|
|
221
|
-
|
|
228
|
+
for (let evt in listeners) removeListener(el, evt, listeners[evt])
|
|
222
229
|
listeners = evaluate(state);
|
|
230
|
+
for (let evt in listeners) addListener(el, evt, listeners[evt])
|
|
231
|
+
}
|
|
232
|
+
}
|
|
223
233
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
nextEvt(startFn)
|
|
234
|
+
const addListener = (el, evt, startFn) => {
|
|
235
|
+
if (evt.indexOf('..')<0) el.addEventListener(evt, startFn);
|
|
236
|
+
|
|
237
|
+
// sequences like `a..b`: fn
|
|
238
|
+
else {
|
|
239
|
+
const evts = evt.split('..').map(e => e.startsWith('on') ? e.slice(2) : e)
|
|
240
|
+
const nextEvt = (fn, cur=0) => {
|
|
241
|
+
let curListener = e => {
|
|
242
|
+
el.removeEventListener(evts[cur], curListener)
|
|
243
|
+
if (typeof (fn = fn.call(el,e)) !== 'function') fn = ()=>{}
|
|
244
|
+
if (++cur < evts.length) nextEvt(fn, cur);
|
|
245
|
+
else if (!startFn[_stop]) console.log('reset'), nextEvt(startFn); // update only if chain isn't stopped
|
|
238
246
|
}
|
|
239
|
-
|
|
247
|
+
el.addEventListener(evts[cur],curListener)
|
|
248
|
+
}
|
|
249
|
+
nextEvt(startFn)
|
|
240
250
|
}
|
|
241
251
|
}
|
|
242
252
|
|
|
243
|
-
|
|
253
|
+
const removeListener = (el, evt, fn) => {
|
|
254
|
+
if (evt.indexOf('..')>=0) console.log('rewire'), fn[_stop] = true
|
|
255
|
+
el.removeEventListener(evt, fn);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
directives['data'] = (el, expr) => {
|
|
244
259
|
let evaluate = parseExpr(el, expr, ':data')
|
|
245
260
|
|
|
246
261
|
return ((state) => {
|
|
@@ -249,7 +264,7 @@ directives['data'] = (el, expr, values) => {
|
|
|
249
264
|
})
|
|
250
265
|
}
|
|
251
266
|
|
|
252
|
-
directives['aria'] = (el, expr
|
|
267
|
+
directives['aria'] = (el, expr) => {
|
|
253
268
|
let evaluate = parseExpr(el, expr, ':aria')
|
|
254
269
|
const update = (value) => {
|
|
255
270
|
for (let key in value) attr(el, 'aria-' + dashcase(key), value[key] == null ? null : value[key] + '');
|
|
@@ -301,4 +316,4 @@ export function exprError(error, element, expression, dir) {
|
|
|
301
316
|
|
|
302
317
|
function dashcase(str) {
|
|
303
318
|
return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => '-' + match.toLowerCase());
|
|
304
|
-
};
|
|
319
|
+
};
|
package/test/test.js
CHANGED
|
@@ -44,6 +44,12 @@ test('common: reactive', async () => {
|
|
|
44
44
|
is(el.outerHTML, `<label for="email">email</label><input id="email" name="email" type="email"><a href="//google.com"></a><img src="//google.com">`)
|
|
45
45
|
})
|
|
46
46
|
|
|
47
|
+
test('common: empty strings', async () => {
|
|
48
|
+
let el = h`<x :="" :x=""></x>`
|
|
49
|
+
sprae(el)
|
|
50
|
+
is(el.outerHTML, `<x></x>`)
|
|
51
|
+
})
|
|
52
|
+
|
|
47
53
|
test('style', async () => {
|
|
48
54
|
let el = h`<x style="left: 1px" :style="style"></x>`
|
|
49
55
|
let params = sprae(el, {style: "top: 1px"})
|
|
@@ -347,24 +353,40 @@ test('on: base', () => {
|
|
|
347
353
|
is(log.value, ['click','x','xx']);
|
|
348
354
|
})
|
|
349
355
|
|
|
356
|
+
test('onevt: this context', e => {
|
|
357
|
+
let el = h`<div :onx="function(){log.push(this)}"></div>`
|
|
358
|
+
let state = sprae(el, {log: []})
|
|
359
|
+
el.dispatchEvent(new window.Event('x'));
|
|
360
|
+
is(state.log, [el])
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
test('on: this in chains refers to el', () => {
|
|
364
|
+
let el = h`<div :ona..onb="function(e){x=this; log.push(1); return () => log.push(2)}"></div>`
|
|
365
|
+
let state = sprae(el, {log:[], x:null})
|
|
366
|
+
el.dispatchEvent(new window.Event('a'));
|
|
367
|
+
is(state.log, [1])
|
|
368
|
+
})
|
|
369
|
+
|
|
350
370
|
test('on: multiple events', e => {
|
|
351
|
-
let el = h`<div :onscroll:onclick="e=>log.push(e.type)"></div>`
|
|
371
|
+
let el = h`<div :onscroll:onclick:onx="e=>log.push(e.type)"></div>`
|
|
352
372
|
let state = sprae(el, {log:[]})
|
|
353
373
|
|
|
354
374
|
el.dispatchEvent(new window.Event('click'));
|
|
355
375
|
is(state.log, ['click'])
|
|
356
376
|
el.dispatchEvent(new window.Event('scroll'));
|
|
357
377
|
is(state.log, ['click','scroll'])
|
|
378
|
+
el.dispatchEvent(new window.Event('x'));
|
|
379
|
+
is(state.log, ['click','scroll','x'])
|
|
358
380
|
})
|
|
359
381
|
|
|
360
382
|
test('on: in-out events', e => {
|
|
361
383
|
// let el = document.createElement('x');
|
|
362
|
-
// el.setAttribute(':onmousedown
|
|
363
|
-
let el = h`<x :onmousedown
|
|
364
|
-
|
|
365
|
-
let state = sprae(el, {log:[]})
|
|
384
|
+
// el.setAttribute(':onmousedown..onmouseup', 'e=>(log.push(e.type),e=>log.push(e.type))')
|
|
385
|
+
let el = h`<x :onmousedown..onmouseup="(e) => { x=this; log.push(e.type); return e=>log.push(e.type); }"></x>`
|
|
366
386
|
|
|
387
|
+
let state = sprae(el, {log:[],x:null})
|
|
367
388
|
el.dispatchEvent(new window.Event('mousedown'));
|
|
389
|
+
is(state.x, el);
|
|
368
390
|
is(state.log, ['mousedown'])
|
|
369
391
|
el.dispatchEvent(new window.Event('mouseup'));
|
|
370
392
|
is(state.log, ['mousedown','mouseup'])
|
|
@@ -374,7 +396,7 @@ test('on: in-out side-effects', e => {
|
|
|
374
396
|
let log = []
|
|
375
397
|
|
|
376
398
|
// 1. skip in event and do directly out
|
|
377
|
-
let el = h`<x :onin
|
|
399
|
+
let el = h`<x :onin..onout="io"></x>`
|
|
378
400
|
sprae(el, { io(e) {
|
|
379
401
|
log.push(e.type)
|
|
380
402
|
return (e) => (log.push(e.type), [1,2,3])
|
|
@@ -401,7 +423,7 @@ test('on: in-out side-effects', e => {
|
|
|
401
423
|
})
|
|
402
424
|
|
|
403
425
|
test('on: chain of events', e => {
|
|
404
|
-
let el = h`<div :onmousedown
|
|
426
|
+
let el = h`<div :onmousedown..onmousemove..onmouseup="e=>(log.push(e.type),e=>(log.push(e.type),e=>log.push(e.type)))"></div>`
|
|
405
427
|
let state = sprae(el, {log:[]})
|
|
406
428
|
|
|
407
429
|
el.dispatchEvent(new window.Event('mousedown'));
|
|
@@ -412,32 +434,62 @@ test('on: chain of events', e => {
|
|
|
412
434
|
is(state.log, ['mousedown','mousemove','mouseup'])
|
|
413
435
|
})
|
|
414
436
|
|
|
415
|
-
test
|
|
416
|
-
let el = h`<x :
|
|
437
|
+
test('on: state changes between chain of events', e => {
|
|
438
|
+
let el = h`<x :on="{'x..y':fn}"></x>`
|
|
439
|
+
let log = []
|
|
440
|
+
let state = sprae(el, {log, fn: () => console.log('log1')||log.push(1)})
|
|
441
|
+
console.log('xx')
|
|
442
|
+
el.dispatchEvent(new window.Event('x'));
|
|
443
|
+
el.dispatchEvent(new window.Event('x'));
|
|
444
|
+
is(log, [1])
|
|
445
|
+
state.fn = () => (console.log('log1.1')||log.push(1.1), () => log.push(2))
|
|
446
|
+
is(log, [1])
|
|
447
|
+
// console.log('xx')
|
|
448
|
+
// NOTE: state update registers new chain listener before finishing prev chain
|
|
449
|
+
// el.dispatchEvent(new window.Event('x'));
|
|
450
|
+
// el.dispatchEvent(new window.Event('x'));
|
|
451
|
+
// is(log, [1])
|
|
452
|
+
console.log('yy')
|
|
453
|
+
el.dispatchEvent(new window.Event('y'));
|
|
454
|
+
el.dispatchEvent(new window.Event('y'));
|
|
455
|
+
is(log, [1])
|
|
456
|
+
console.log('xx')
|
|
457
|
+
el.dispatchEvent(new window.Event('x'));
|
|
458
|
+
el.dispatchEvent(new window.Event('x'));
|
|
459
|
+
is(log, [1, 1.1])
|
|
460
|
+
console.log('yy')
|
|
461
|
+
el.dispatchEvent(new window.Event('y'));
|
|
462
|
+
el.dispatchEvent(new window.Event('y'));
|
|
463
|
+
is(log, [1, 1.1, 2])
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
test('with: inline', () => {
|
|
467
|
+
let el = h`<x :with="{foo:'bar'}"><y :text="foo + baz"></y></x>`
|
|
417
468
|
let state = sprae(el, {baz: 'qux'})
|
|
418
469
|
// FIXME: this doesn't inherit root scope baz property and instead uses hard-initialized one
|
|
419
470
|
is(el.innerHTML, `<y>barqux</y>`)
|
|
420
471
|
state.baz = 'quux'
|
|
421
472
|
is(el.innerHTML, `<y>barquux</y>`)
|
|
422
473
|
})
|
|
423
|
-
test
|
|
424
|
-
let el = h`<x :with="{foo:'bar'
|
|
474
|
+
test('with: inline reactive', () => {
|
|
475
|
+
let el = h`<x :with="{foo:'bar'}"><y :text="foo + baz"></y></x>`
|
|
425
476
|
let baz = signal('qux')
|
|
426
|
-
|
|
477
|
+
sprae(el, {baz})
|
|
427
478
|
// FIXME: this doesn't inherit root scope baz property and instead uses hard-initialized one
|
|
428
479
|
is(el.innerHTML, `<y>barqux</y>`)
|
|
429
480
|
baz.value = 'quux'
|
|
430
481
|
is(el.innerHTML, `<y>barquux</y>`)
|
|
431
482
|
})
|
|
432
|
-
test
|
|
483
|
+
test('with: data', () => {
|
|
433
484
|
let el = h`<x :with="x"><y :text="foo"></y></x>`
|
|
434
485
|
let state = sprae(el, {x:{foo:'bar'}})
|
|
435
486
|
is(el.innerHTML, `<y>bar</y>`)
|
|
436
487
|
console.log('update')
|
|
437
|
-
|
|
488
|
+
state.x.foo = 'baz'
|
|
489
|
+
// Object.assign(state, {x:{foo:'baz'}})
|
|
438
490
|
is(el.innerHTML, `<y>baz</y>`)
|
|
439
491
|
})
|
|
440
|
-
test
|
|
492
|
+
test('with: transparency', () => {
|
|
441
493
|
// NOTE: y:text initializes through directive, not through parent
|
|
442
494
|
// therefore by default :text uses parent's state, not defined by element itself
|
|
443
495
|
let el = h`<x :with="{foo:'foo'}"><y :with="b" :text="foo+bar"></y></x>`
|
|
@@ -446,7 +498,7 @@ test.skip('with: transparency', () => {
|
|
|
446
498
|
params.b.bar = 'baz'
|
|
447
499
|
is(el.innerHTML, `<y>foobaz</y>`)
|
|
448
500
|
})
|
|
449
|
-
test
|
|
501
|
+
test('with: reactive transparency', () => {
|
|
450
502
|
let el = h`<x :with="{foo:1}"><y :with="b.c" :text="foo+bar"></y></x>`
|
|
451
503
|
const bar = signal('2')
|
|
452
504
|
sprae(el, {b:{c:{bar}}})
|
|
@@ -454,7 +506,7 @@ test.skip('with: reactive transparency', () => {
|
|
|
454
506
|
bar.value = '3'
|
|
455
507
|
is(el.innerHTML, `<y>13</y>`)
|
|
456
508
|
})
|
|
457
|
-
test
|
|
509
|
+
test('with: writes to state', () => {
|
|
458
510
|
let a = h`<x :with="{a:1}"><y :on="{x(){a++}}" :text="a"></y></x>`
|
|
459
511
|
sprae(a)
|
|
460
512
|
is(a.innerHTML, `<y>1</y>`)
|
|
@@ -492,13 +544,19 @@ test(':: reactive values', async () => {
|
|
|
492
544
|
is(el.outerHTML, `<x>2</x>`)
|
|
493
545
|
})
|
|
494
546
|
|
|
547
|
+
test(':: null result does nothing', async () => {
|
|
548
|
+
let a = h`<x :="undefined"></x>`
|
|
549
|
+
sprae(a)
|
|
550
|
+
is(a.outerHTML, `<x></x>`)
|
|
551
|
+
})
|
|
552
|
+
|
|
495
553
|
test(':: scope refers to current element', async () => {
|
|
496
554
|
let el = h`<x :text="log.push(this)"></x>`
|
|
497
555
|
let state = sprae(el, {log:[]})
|
|
498
556
|
is(state.log, [el])
|
|
499
557
|
})
|
|
500
558
|
|
|
501
|
-
test.
|
|
559
|
+
test.todo(':: scope directives must come first', async () => {
|
|
502
560
|
// NOTE: we init attributes in order of definition
|
|
503
561
|
let a = h`<x :text="y" :with="{y:1}" :ref="x"></x>`
|
|
504
562
|
sprae(a, {})
|