sprae 1.0.0 → 2.1.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/.github/workflows/node.js.yml +30 -0
- package/examples/todomvc.html +100 -0
- package/package.json +8 -2
- package/plan.md +28 -6
- package/r&d.md +104 -9
- package/readme.md +148 -77
- package/sprae.js +676 -253
- package/sprae.js.map +7 -0
- package/sprae.min.js +1 -1
- package/src/core.js +37 -101
- package/src/directives.js +259 -0
- package/src/index.js +2 -2
- package/test/index.html +5 -2
- package/test/test.js +191 -72
- package/src/directives/aria.js +0 -10
- package/src/directives/common.js +0 -15
- package/src/directives/data.js +0 -10
- package/src/directives/each.js +0 -53
- package/src/directives/if.js +0 -31
- package/src/directives/index.js +0 -11
- package/src/directives/on.js +0 -12
- package/src/directives/prop.js +0 -10
- package/src/directives/text.js +0 -10
- package/src/directives/value.js +0 -26
- package/src/directives/with.js +0 -21
package/test/test.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { signal } from '
|
|
1
|
+
// import { signal } from 'usignal/sync'
|
|
2
|
+
import { signal } from '@preact/signals-core'
|
|
2
3
|
import test, {is, any, throws} from 'tst'
|
|
3
4
|
import {tick, time} from 'wait-please'
|
|
4
5
|
import sprae from '../src/index.js'
|
|
@@ -21,34 +22,34 @@ test.skip('autoinit', async () => {
|
|
|
21
22
|
test('hidden: core', async () => {
|
|
22
23
|
let el = h`<div :hidden="hidden"></div>`
|
|
23
24
|
let params = sprae(el, {hidden:true})
|
|
24
|
-
is(el.outerHTML, `<div
|
|
25
|
+
is(el.outerHTML, `<div hidden=""></div>`)
|
|
25
26
|
params.hidden = false
|
|
26
|
-
is(el.outerHTML, `<div
|
|
27
|
+
is(el.outerHTML, `<div></div>`)
|
|
27
28
|
})
|
|
28
29
|
|
|
29
30
|
test('hidden: reactive', async () => {
|
|
30
31
|
const hidden = signal(true)
|
|
31
32
|
let el = h`<div :hidden="hidden"></div>`
|
|
32
33
|
sprae(el, {hidden})
|
|
33
|
-
is(el.outerHTML, `<div
|
|
34
|
+
is(el.outerHTML, `<div hidden=""></div>`)
|
|
34
35
|
hidden.value = false
|
|
35
|
-
is(el.outerHTML, `<div
|
|
36
|
+
is(el.outerHTML, `<div></div>`)
|
|
36
37
|
})
|
|
37
38
|
|
|
38
39
|
test('common: reactive', async () => {
|
|
39
40
|
let el = h`<label :for="name" :text="name" ></label><input :id="name" :name="name" :type="name" :disabled="!name"/><a :href="url"></a><img :src="url"/>`
|
|
40
41
|
let params = sprae(el, {name:'text', url:'//google.com'})
|
|
41
|
-
is(el.outerHTML, `<label
|
|
42
|
+
is(el.outerHTML, `<label for="text">text</label><input id="text" name="text" type="text"><a href="//google.com"></a><img src="//google.com">`)
|
|
42
43
|
params.name = 'email'
|
|
43
|
-
is(el.outerHTML, `<label
|
|
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">`)
|
|
44
45
|
})
|
|
45
46
|
|
|
46
47
|
test('common: style', async () => {
|
|
47
48
|
let el = h`<x :style="style"></x>`
|
|
48
49
|
let params = sprae(el, {style: "top: 1px"})
|
|
49
|
-
is(el.outerHTML, `<x
|
|
50
|
+
is(el.outerHTML, `<x style="top: 1px"></x>`)
|
|
50
51
|
params.style = {top: '2px'}
|
|
51
|
-
is(el.outerHTML, `<x
|
|
52
|
+
is(el.outerHTML, `<x style="top: 2px"></x>`)
|
|
52
53
|
})
|
|
53
54
|
|
|
54
55
|
test('common: class', async () => {
|
|
@@ -62,115 +63,115 @@ test('common: class', async () => {
|
|
|
62
63
|
is(el.outerHTML, `<x class="x"></x><y class="y w"></y><z class="b c"></z>`);
|
|
63
64
|
})
|
|
64
65
|
|
|
65
|
-
test('props: base', async () => {
|
|
66
|
-
let el = h`<input :
|
|
66
|
+
test('spread props: base', async () => {
|
|
67
|
+
let el = h`<input :id="0" :="{for:1, title:2, help:3, type:4, placeholder: 5, value: 6}" :value="7"/>`
|
|
67
68
|
let params = sprae(el)
|
|
68
|
-
is(el.outerHTML, `<input
|
|
69
|
+
is(el.outerHTML, `<input id="0" for="1" title="2" help="3" type="4" placeholder="5" value="7">`)
|
|
69
70
|
})
|
|
70
71
|
|
|
71
72
|
test('data: base', async () => {
|
|
72
73
|
let el = h`<input :data="{a:1, fooBar:2}"/>`
|
|
73
74
|
let params = sprae(el)
|
|
74
|
-
is(el.outerHTML, `<input
|
|
75
|
+
is(el.outerHTML, `<input data-a="1" data-foo-bar="2">`)
|
|
75
76
|
})
|
|
76
77
|
|
|
77
78
|
test('aria: base', async () => {
|
|
78
|
-
let el = h`<input type="text" id="jokes" role="combobox" :aria="{controls:'joketypes', autocomplete:'list', expanded:false, activeOption:'item1', activedescendant:''}"/>`
|
|
79
|
+
let el = h`<input type="text" id="jokes" role="combobox" :aria="{controls:'joketypes', autocomplete:'list', expanded:false, activeOption:'item1', activedescendant:'', xxx:null}"/>`
|
|
79
80
|
sprae(el)
|
|
80
|
-
is(el.outerHTML, `<input type="text" id="jokes" role="combobox"
|
|
81
|
+
is(el.outerHTML, `<input type="text" id="jokes" role="combobox" aria-controls="joketypes" aria-autocomplete="list" aria-expanded="false" aria-active-option="item1" aria-activedescendant="">`)
|
|
81
82
|
})
|
|
82
83
|
|
|
83
|
-
test('
|
|
84
|
+
test('value: direct', async () => {
|
|
84
85
|
let el = h`<input :value="a" />`
|
|
85
86
|
let state = sprae(el, {a:1})
|
|
86
87
|
is(el.value, '1')
|
|
87
|
-
is(el.outerHTML, `<input
|
|
88
|
+
is(el.outerHTML, `<input value="1">`)
|
|
88
89
|
state.a = 2
|
|
89
90
|
is(el.value, '2')
|
|
90
|
-
is(el.outerHTML, `<input
|
|
91
|
+
is(el.outerHTML, `<input value="2">`)
|
|
91
92
|
|
|
92
93
|
el.value = 3
|
|
93
|
-
el.dispatchEvent(new window.Event('change'))
|
|
94
|
-
is(state.a, '3')
|
|
94
|
+
// el.dispatchEvent(new window.Event('change'))
|
|
95
|
+
// is(state.a, '3')
|
|
95
96
|
})
|
|
96
97
|
|
|
97
98
|
test('text: core', async () => {
|
|
98
99
|
let el = h`<div :text="text"></div>`
|
|
99
100
|
let params = sprae(el, {text:'abc'})
|
|
100
|
-
is(el.outerHTML, `<div
|
|
101
|
+
is(el.outerHTML, `<div>abc</div>`)
|
|
101
102
|
params.text = null
|
|
102
|
-
is(el.outerHTML, `<div
|
|
103
|
+
is(el.outerHTML, `<div></div>`)
|
|
103
104
|
})
|
|
104
105
|
|
|
105
106
|
test('conditions: base', async () => {
|
|
106
107
|
let el = h`<p>
|
|
107
108
|
<span :if="a==1">a</span>
|
|
108
|
-
<span :else
|
|
109
|
+
<span :else :if="a==2">b</span>
|
|
109
110
|
<span :else >c</span>
|
|
110
111
|
</p>`
|
|
111
112
|
|
|
112
113
|
const params = sprae(el, { a: 1 })
|
|
113
114
|
|
|
114
|
-
is(el.innerHTML, '<span
|
|
115
|
+
is(el.innerHTML, '<span>a</span>')
|
|
115
116
|
params.a = 2
|
|
116
|
-
is(el.innerHTML, '<span
|
|
117
|
+
is(el.innerHTML, '<span>b</span>')
|
|
117
118
|
params.a = 3
|
|
118
|
-
is(el.innerHTML, '<span
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
is(el.innerHTML, '<span>c</span>')
|
|
120
|
+
params.a = null
|
|
121
|
+
is(el.innerHTML, '<span>c</span>')
|
|
121
122
|
})
|
|
122
123
|
|
|
123
124
|
test('conditions: short with insertions', async () => {
|
|
124
125
|
let el = h`<p>
|
|
125
126
|
<span :if="a==1" :text="'1:'+a"></span>
|
|
126
|
-
<span :else
|
|
127
|
+
<span :else :if="a==2" :text="'2:'+a"></span>
|
|
127
128
|
<span :else :text="a"></span>
|
|
128
129
|
</p>`
|
|
129
130
|
|
|
130
131
|
const params = sprae(el, { a: 1 })
|
|
131
132
|
|
|
132
|
-
is(el.innerHTML, '<span
|
|
133
|
+
is(el.innerHTML, '<span>1:1</span>')
|
|
133
134
|
params.a = 2
|
|
134
|
-
is(el.innerHTML, '<span
|
|
135
|
+
is(el.innerHTML, '<span>2:2</span>')
|
|
135
136
|
params.a = 3
|
|
136
|
-
is(el.innerHTML, '<span
|
|
137
|
+
is(el.innerHTML, '<span>3</span>')
|
|
137
138
|
params.a = 4
|
|
138
|
-
is(el.innerHTML, '<span
|
|
139
|
+
is(el.innerHTML, '<span>4</span>')
|
|
139
140
|
|
|
140
141
|
params.a = 1
|
|
141
|
-
is(el.innerHTML, '<span
|
|
142
|
+
is(el.innerHTML, '<span>1:1</span>')
|
|
142
143
|
params.a = 4
|
|
143
|
-
is(el.innerHTML, '<span
|
|
144
|
+
is(el.innerHTML, '<span>4</span>')
|
|
144
145
|
|
|
145
|
-
|
|
146
|
+
params.a = null
|
|
146
147
|
})
|
|
147
148
|
|
|
148
149
|
test('conditions: reactive values', async () => {
|
|
149
150
|
let el = h`<p>
|
|
150
151
|
<span :if="a==1" :text="'1:'+a"></span>
|
|
151
|
-
<span :else
|
|
152
|
+
<span :else :if="a==2" :text="'2:'+a"></span>
|
|
152
153
|
<span :else :text="a"></span>
|
|
153
154
|
</p>`
|
|
154
155
|
|
|
155
156
|
const a = signal(1)
|
|
156
157
|
sprae(el, { a })
|
|
157
158
|
|
|
158
|
-
is(el.innerHTML, '<span
|
|
159
|
+
is(el.innerHTML, '<span>1:1</span>')
|
|
159
160
|
a.value = 2
|
|
160
|
-
is(el.innerHTML, '<span
|
|
161
|
+
is(el.innerHTML, '<span>2:2</span>')
|
|
161
162
|
a.value = 3
|
|
162
|
-
is(el.innerHTML, '<span
|
|
163
|
+
is(el.innerHTML, '<span>3</span>')
|
|
163
164
|
a.value = 4
|
|
164
|
-
is(el.innerHTML, '<span
|
|
165
|
+
is(el.innerHTML, '<span>4</span>')
|
|
165
166
|
|
|
166
167
|
a.value = 1
|
|
167
|
-
is(el.innerHTML, '<span
|
|
168
|
+
is(el.innerHTML, '<span>1:1</span>')
|
|
168
169
|
a.value = 4
|
|
169
|
-
is(el.innerHTML, '<span
|
|
170
|
+
is(el.innerHTML, '<span>4</span>')
|
|
170
171
|
})
|
|
171
172
|
|
|
172
173
|
|
|
173
|
-
test('each:
|
|
174
|
+
test('each: array', async () => {
|
|
174
175
|
// FIXME: in some conspicuous reason jsdom fails to update text nodes somehow
|
|
175
176
|
let el = h`<p>
|
|
176
177
|
<span :each="a in b" :text="a"></span>
|
|
@@ -179,14 +180,53 @@ test('each: base', async () => {
|
|
|
179
180
|
const params = sprae(el, { b: [] })
|
|
180
181
|
|
|
181
182
|
is(el.innerHTML, '')
|
|
183
|
+
console.log('set 1,2')
|
|
182
184
|
params.b = [1,2]
|
|
183
|
-
is(el.innerHTML, '<span
|
|
185
|
+
is(el.innerHTML, '<span>1</span><span>2</span>')
|
|
184
186
|
params.b = []
|
|
185
187
|
is(el.innerHTML, '')
|
|
186
|
-
|
|
188
|
+
params.b = null
|
|
187
189
|
is(el.innerHTML, '')
|
|
188
190
|
})
|
|
189
191
|
|
|
192
|
+
test('each: object', async () => {
|
|
193
|
+
// FIXME: in some conspicuous reason jsdom fails to update text nodes somehow
|
|
194
|
+
let el = h`<p>
|
|
195
|
+
<span :each="x,key in b" :text="[key,x]"></span>
|
|
196
|
+
</p>`
|
|
197
|
+
|
|
198
|
+
const params = sprae(el, { b: null })
|
|
199
|
+
|
|
200
|
+
is(el.innerHTML, '')
|
|
201
|
+
console.log('set 1,2')
|
|
202
|
+
params.b = { x:1, y:2 }
|
|
203
|
+
is(el.innerHTML, '<span>x,1</span><span>y,2</span>')
|
|
204
|
+
params.b = []
|
|
205
|
+
is(el.innerHTML, '')
|
|
206
|
+
params.b = null
|
|
207
|
+
is(el.innerHTML, '')
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test('each: loop within loop', async () => {
|
|
211
|
+
let el = h`<p>
|
|
212
|
+
<x :each="b in c"><y :each="a in b" :text="a"></y></x>
|
|
213
|
+
</p>`
|
|
214
|
+
|
|
215
|
+
const params = sprae(el, { c: [[1,2], [3,4]] })
|
|
216
|
+
|
|
217
|
+
is(el.innerHTML, '<x><y>1</y><y>2</y></x><x><y>3</y><y>4</y></x>')
|
|
218
|
+
params.c = [[5,6], [3,4]]
|
|
219
|
+
is(el.innerHTML, '<x><y>5</y><y>6</y></x><x><y>3</y><y>4</y></x>')
|
|
220
|
+
// params.c[1] = [7,8]
|
|
221
|
+
params.c = [params.c[0], [7,8]]
|
|
222
|
+
is(el.innerHTML, '<x><y>5</y><y>6</y></x><x><y>7</y><y>8</y></x>')
|
|
223
|
+
// is(el.innerHTML, '<span>1</span><span>2</span>')
|
|
224
|
+
params.c = []
|
|
225
|
+
is(el.innerHTML, '')
|
|
226
|
+
// params.b = null
|
|
227
|
+
// is(el.innerHTML, '')
|
|
228
|
+
})
|
|
229
|
+
|
|
190
230
|
test('each: reactive values', async () => {
|
|
191
231
|
let el = h`<p>
|
|
192
232
|
<span :each="a in b" :text="a"></span>
|
|
@@ -197,40 +237,63 @@ test('each: reactive values', async () => {
|
|
|
197
237
|
|
|
198
238
|
is(el.innerHTML, '')
|
|
199
239
|
b.value = [1,2]
|
|
200
|
-
is(el.innerHTML, '<span
|
|
240
|
+
is(el.innerHTML, '<span>1</span><span>2</span>')
|
|
201
241
|
b.value = []
|
|
202
242
|
is(el.innerHTML, '')
|
|
203
|
-
|
|
243
|
+
params.b = null
|
|
204
244
|
is(el.innerHTML, '')
|
|
205
245
|
})
|
|
206
246
|
|
|
207
247
|
test('each: loop with condition', async () => {
|
|
248
|
+
// NOTE: there doesn't seem to be much value in exactly that
|
|
249
|
+
// also it creates confusion with :else directive
|
|
250
|
+
// prohibitin that allows in-order directives init
|
|
208
251
|
let el = h`<p>
|
|
209
252
|
<span :each="a in b" :text="a" :if="c"></span>
|
|
210
253
|
</p>`
|
|
211
254
|
|
|
212
|
-
const params = sprae(el, { b: [1,2], c:false })
|
|
255
|
+
const params = sprae(el, { b: [1,2], c: false })
|
|
213
256
|
|
|
214
257
|
is(el.innerHTML, '')
|
|
215
258
|
params.c = true
|
|
216
|
-
is(el.innerHTML, '<span
|
|
259
|
+
is(el.innerHTML, '<span>1</span><span>2</span>')
|
|
260
|
+
params.b = [1]
|
|
261
|
+
is(el.innerHTML, '<span>1</span>')
|
|
262
|
+
params.b = null
|
|
263
|
+
is(el.innerHTML, '')
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
test('each: condition with loop', async () => {
|
|
267
|
+
let el = h`<p>
|
|
268
|
+
<span :if="c" :each="a in b" :text="a"></span>
|
|
269
|
+
<span :else :text="c"></span>
|
|
270
|
+
</p>`
|
|
271
|
+
|
|
272
|
+
const params = sprae(el, { b: [1,2], c: false })
|
|
273
|
+
|
|
274
|
+
is(el.innerHTML, '<span>false</span>')
|
|
275
|
+
params.c = true
|
|
276
|
+
is(el.innerHTML, '<span>1</span><span>2</span>')
|
|
217
277
|
params.b = [1]
|
|
218
|
-
is(el.innerHTML, '<span
|
|
219
|
-
|
|
278
|
+
is(el.innerHTML, '<span>1</span>')
|
|
279
|
+
params.b = null
|
|
220
280
|
is(el.innerHTML, '')
|
|
281
|
+
console.log('c=false')
|
|
282
|
+
params.c = false
|
|
283
|
+
is(el.innerHTML, '<span>false</span>')
|
|
221
284
|
})
|
|
222
285
|
|
|
223
286
|
test('each: loop within condition', async () => {
|
|
224
287
|
let el = h`<p>
|
|
225
288
|
<x :if="a==1"><y :each="i in a" :text="i"></y></x>
|
|
226
|
-
<x :else
|
|
289
|
+
<x :else :if="a==2"><y :each="i in a" :text="-i"></y></x>
|
|
227
290
|
</p>`
|
|
228
291
|
|
|
229
292
|
const params = sprae(el, { a: 1 })
|
|
230
293
|
|
|
231
|
-
is(el.innerHTML, '<x
|
|
294
|
+
is(el.innerHTML, '<x><y>1</y></x>')
|
|
232
295
|
params.a = 2
|
|
233
|
-
is(el.innerHTML, '<x
|
|
296
|
+
is(el.innerHTML, '<x><y>-1</y><y>-2</y></x>')
|
|
234
297
|
params.a = 0
|
|
235
298
|
is(el.innerHTML, '')
|
|
236
299
|
})
|
|
@@ -239,17 +302,17 @@ test('each: condition within loop', async () => {
|
|
|
239
302
|
let el = h`<p>
|
|
240
303
|
<x :each="a in b">
|
|
241
304
|
<y :if="a==1" :text="'1:'+a"></y>
|
|
242
|
-
<y :else
|
|
305
|
+
<y :else :if="a==2" :text="'2:'+a"></y>
|
|
243
306
|
<y :else :text="a"></y>
|
|
244
307
|
</x>
|
|
245
308
|
</p>`
|
|
246
309
|
|
|
247
310
|
const params = sprae(el, { b: [1,2,3] })
|
|
248
311
|
|
|
249
|
-
is(el.innerHTML, '<x
|
|
312
|
+
is(el.innerHTML, '<x><y>1:1</y></x><x><y>2:2</y></x><x><y>3</y></x>')
|
|
250
313
|
params.b = [2]
|
|
251
|
-
is(el.innerHTML, '<x
|
|
252
|
-
|
|
314
|
+
is(el.innerHTML, '<x><y>2:2</y></x>')
|
|
315
|
+
params.b = null
|
|
253
316
|
is(el.innerHTML, '')
|
|
254
317
|
})
|
|
255
318
|
|
|
@@ -258,7 +321,7 @@ test('on: base', () => {
|
|
|
258
321
|
let log = signal([])
|
|
259
322
|
let params = sprae(el, {x(){log.value.push('x')}, log})
|
|
260
323
|
|
|
261
|
-
is(el.outerHTML, `<div
|
|
324
|
+
is(el.outerHTML, `<div></div>`);
|
|
262
325
|
el.dispatchEvent(new window.Event('click'));
|
|
263
326
|
is(log.value, ['click'])
|
|
264
327
|
el.dispatchEvent(new window.Event('x'));
|
|
@@ -268,7 +331,7 @@ test('on: base', () => {
|
|
|
268
331
|
el.dispatchEvent(new window.Event('x'));
|
|
269
332
|
is(log.value, ['click','x','xx']);
|
|
270
333
|
|
|
271
|
-
|
|
334
|
+
params.x = null;
|
|
272
335
|
el.dispatchEvent(new window.Event('x'));
|
|
273
336
|
is(log.value, ['click','x','xx']);
|
|
274
337
|
})
|
|
@@ -277,27 +340,83 @@ test('with: inline', () => {
|
|
|
277
340
|
let el = h`<x :with="{foo:'bar', baz}"><y :text="foo + baz"></y></x>`
|
|
278
341
|
let state = sprae(el, {baz: 'qux'})
|
|
279
342
|
// FIXME: this doesn't inherit root scope baz property and instead uses hard-initialized one
|
|
280
|
-
is(el.innerHTML, `<y
|
|
343
|
+
is(el.innerHTML, `<y>barqux</y>`)
|
|
281
344
|
state.baz = 'quux'
|
|
282
|
-
is(el.innerHTML, `<y
|
|
345
|
+
is(el.innerHTML, `<y>barquux</y>`)
|
|
346
|
+
})
|
|
347
|
+
test('with: inline reactive', () => {
|
|
348
|
+
let el = h`<x :with="{foo:'bar', baz}"><y :text="foo + baz"></y></x>`
|
|
349
|
+
let baz = signal('qux')
|
|
350
|
+
let state = sprae(el, {baz})
|
|
351
|
+
// FIXME: this doesn't inherit root scope baz property and instead uses hard-initialized one
|
|
352
|
+
is(el.innerHTML, `<y>barqux</y>`)
|
|
353
|
+
baz.value = 'quux'
|
|
354
|
+
is(el.innerHTML, `<y>barquux</y>`)
|
|
283
355
|
})
|
|
284
356
|
test('with: data', () => {
|
|
285
357
|
let el = h`<x :with="x"><y :text="foo"></y></x>`
|
|
286
|
-
let
|
|
287
|
-
is(el.innerHTML, `<y
|
|
288
|
-
|
|
289
|
-
|
|
358
|
+
let state = sprae(el, {x:{foo:'bar'}})
|
|
359
|
+
is(el.innerHTML, `<y>bar</y>`)
|
|
360
|
+
console.log('update')
|
|
361
|
+
Object.assign(state, {x:{foo:'baz'}})
|
|
362
|
+
is(el.innerHTML, `<y>baz</y>`)
|
|
290
363
|
})
|
|
291
|
-
test('with:
|
|
364
|
+
test('with: transparency', () => {
|
|
292
365
|
// NOTE: y:text initializes through directive, not through parent
|
|
293
366
|
// therefore by default :text uses parent's state, not defined by element itself
|
|
294
367
|
let el = h`<x :with="{foo:'foo'}"><y :with="b" :text="foo+bar"></y></x>`
|
|
295
|
-
sprae(el, {b:{bar:'bar'}})
|
|
296
|
-
is(el.innerHTML, `<y
|
|
368
|
+
let params = sprae(el, {b:{bar:'bar'}})
|
|
369
|
+
is(el.innerHTML, `<y>foobar</y>`)
|
|
370
|
+
params.b.bar = 'baz'
|
|
371
|
+
is(el.innerHTML, `<y>foobaz</y>`)
|
|
297
372
|
})
|
|
298
|
-
test('with: reactive
|
|
373
|
+
test('with: reactive transparency', () => {
|
|
299
374
|
let el = h`<x :with="{foo:1}"><y :with="b.c" :text="foo+bar"></y></x>`
|
|
300
375
|
const bar = signal('2')
|
|
301
376
|
sprae(el, {b:{c:{bar}}})
|
|
302
|
-
is(el.innerHTML, `<y
|
|
377
|
+
is(el.innerHTML, `<y>12</y>`)
|
|
378
|
+
bar.value = '3'
|
|
379
|
+
is(el.innerHTML, `<y>13</y>`)
|
|
380
|
+
})
|
|
381
|
+
test('with: writes to state', () => {
|
|
382
|
+
let a = h`<x :with="{a:1}"><y :on="{x(){a++}}" :text="a"></y></x>`
|
|
383
|
+
sprae(a)
|
|
384
|
+
is(a.innerHTML, `<y>1</y>`)
|
|
385
|
+
a.firstChild.dispatchEvent(new window.Event('x'))
|
|
386
|
+
is(a.innerHTML, `<y>2</y>`)
|
|
387
|
+
a.firstChild.dispatchEvent(new window.Event('x'))
|
|
388
|
+
is(a.innerHTML, `<y>3</y>`)
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
test('ref: base', () => {
|
|
392
|
+
let a = h`<a :ref="a" :init="log.push(a), null" :text="b"></a>`
|
|
393
|
+
let state = sprae(a, {log:[], b:1})
|
|
394
|
+
is(state.log[0], a)
|
|
395
|
+
is(a.outerHTML, `<a>1</a>`)
|
|
396
|
+
state.b = 2
|
|
397
|
+
is(a.outerHTML, `<a>2</a>`)
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
test('ref with each', () => {
|
|
401
|
+
let a = h`<y><x :ref="x" :each="item in items" :text="log.push(x), item"/></y>`
|
|
402
|
+
let state = sprae(a, {log: [], items: [1,2]})
|
|
403
|
+
is(a.innerHTML, `<x>1</x><x>2</x>`)
|
|
404
|
+
is(state.log, [...a.children])
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
test('reactive values', async () => {
|
|
408
|
+
let a = new Promise((ok) => setTimeout(() => ok(2), 10))
|
|
409
|
+
|
|
410
|
+
let el = h`<x :text="a">1</x>`
|
|
411
|
+
sprae(el, {a})
|
|
412
|
+
is(el.outerHTML, `<x></x>`)
|
|
413
|
+
|
|
414
|
+
await time(20)
|
|
415
|
+
is(el.outerHTML, `<x>2</x>`)
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
test('scope directives must come first', async () => {
|
|
419
|
+
let a = h`<x :text="y" :with="{y:1}" :ref="x"></x>`
|
|
420
|
+
sprae(a, {})
|
|
421
|
+
is(a.outerHTML, `<x>1</x>`)
|
|
303
422
|
})
|
package/src/directives/aria.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { directive, parseExpr } from '../core.js'
|
|
2
|
-
import { prop } from 'element-props'
|
|
3
|
-
|
|
4
|
-
directive(':aria', (el, expr) => {
|
|
5
|
-
let evaluate = parseExpr(expr);
|
|
6
|
-
return (state) => {
|
|
7
|
-
let value = evaluate(state);
|
|
8
|
-
for (let key in value) prop(el, 'aria'+key[0].toUpperCase()+key.slice(1), value[key]);
|
|
9
|
-
}
|
|
10
|
-
})
|
package/src/directives/common.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// common directives just set/map value as is
|
|
2
|
-
import { directive, parseExpr } from '../core.js'
|
|
3
|
-
import {prop} from 'element-props'
|
|
4
|
-
|
|
5
|
-
common(`id`), common(`name`), common(`for`), common(`type`), common(`hidden`), common(`disabled`), common(`href`), common(`src`), common(`style`), common(`class`)
|
|
6
|
-
|
|
7
|
-
function common(name) {
|
|
8
|
-
directive(':'+name, (el,expr) => {
|
|
9
|
-
let evaluate = parseExpr(expr)
|
|
10
|
-
return state => {
|
|
11
|
-
let value = evaluate(state);
|
|
12
|
-
prop(el, name, value)
|
|
13
|
-
}
|
|
14
|
-
})
|
|
15
|
-
}
|
package/src/directives/data.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { directive, parseExpr } from '../core.js'
|
|
2
|
-
import { prop } from 'element-props'
|
|
3
|
-
|
|
4
|
-
directive(':data', (el, expr) => {
|
|
5
|
-
let evaluate = parseExpr(expr);
|
|
6
|
-
return (state) => {
|
|
7
|
-
let value = evaluate(state);
|
|
8
|
-
for (let key in value) el.dataset[key] = value[key];
|
|
9
|
-
}
|
|
10
|
-
})
|
package/src/directives/each.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import sprae, { directive, exprError, parseExpr } from '../core.js'
|
|
2
|
-
|
|
3
|
-
directive(':each', (el, expr) => {
|
|
4
|
-
let each = parseForExpression(expr);
|
|
5
|
-
if (!each) return exprError(new Error, expr);
|
|
6
|
-
|
|
7
|
-
const getItems = parseExpr(each.items);
|
|
8
|
-
|
|
9
|
-
const holder = new Text
|
|
10
|
-
el.replaceWith(holder)
|
|
11
|
-
|
|
12
|
-
// FIXME: make sure no memory leak here
|
|
13
|
-
// FIXME: there can DOM swapper be used instead
|
|
14
|
-
let els = [];
|
|
15
|
-
return state => {
|
|
16
|
-
els.forEach(el => el.remove()); els = [];
|
|
17
|
-
let items = getItems(state);
|
|
18
|
-
if (typeof items === 'number') items = Array.from({length: items}, (item, i)=>i+1)
|
|
19
|
-
items?.forEach((item,i) => {
|
|
20
|
-
const scope = {...state};
|
|
21
|
-
scope[each.item] = item;
|
|
22
|
-
if (each.index) scope[each.index] = i;
|
|
23
|
-
let itemEl = el.cloneNode(true);
|
|
24
|
-
els.push(itemEl);
|
|
25
|
-
holder.before(itemEl);
|
|
26
|
-
sprae(itemEl, scope);
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
// This was taken AlpineJS, former VueJS 2.* core. Thanks Alpine & Vue!
|
|
32
|
-
function parseForExpression(expression) {
|
|
33
|
-
let forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
|
|
34
|
-
let stripParensRE = /^\s*\(|\)\s*$/g
|
|
35
|
-
let forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
|
|
36
|
-
let inMatch = expression.match(forAliasRE)
|
|
37
|
-
|
|
38
|
-
if (!inMatch) return
|
|
39
|
-
|
|
40
|
-
let res = {}
|
|
41
|
-
res.items = inMatch[2].trim()
|
|
42
|
-
let item = inMatch[1].replace(stripParensRE, '').trim()
|
|
43
|
-
let iteratorMatch = item.match(forIteratorRE)
|
|
44
|
-
|
|
45
|
-
if (iteratorMatch) {
|
|
46
|
-
res.item = item.replace(forIteratorRE, '').trim()
|
|
47
|
-
res.index = iteratorMatch[1].trim()
|
|
48
|
-
} else {
|
|
49
|
-
res.item = item
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return res
|
|
53
|
-
}
|
package/src/directives/if.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { directive, parseExpr } from '../core.js'
|
|
2
|
-
|
|
3
|
-
directive(':if', (el, expr) => {
|
|
4
|
-
let cur = el, els = [el], clauses = [parseExpr(expr)], holder = new Text
|
|
5
|
-
|
|
6
|
-
// collect clauses
|
|
7
|
-
while (cur = el.nextElementSibling) {
|
|
8
|
-
if (expr = cur.getAttribute(':else-if')) {
|
|
9
|
-
cur.removeAttribute(':else-if');
|
|
10
|
-
cur.classList.add('∴else-if')
|
|
11
|
-
cur.remove();
|
|
12
|
-
els.push(cur); clauses.push(parseExpr(expr));
|
|
13
|
-
continue
|
|
14
|
-
}
|
|
15
|
-
if (cur.hasAttribute(':else')) {
|
|
16
|
-
cur.removeAttribute(':else');
|
|
17
|
-
cur.classList.add('∴else')
|
|
18
|
-
cur.remove();
|
|
19
|
-
els.push(cur); clauses.push(() => 1);
|
|
20
|
-
}
|
|
21
|
-
break;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
cur = els[0]
|
|
25
|
-
|
|
26
|
-
return state => {
|
|
27
|
-
let idx = clauses.findIndex(match => match(state));
|
|
28
|
-
if (idx >= 0) cur.replaceWith(cur = els[idx]);
|
|
29
|
-
else cur.replaceWith(cur = holder);
|
|
30
|
-
}
|
|
31
|
-
})
|
package/src/directives/index.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// order defines precedence
|
|
2
|
-
import './with.js'
|
|
3
|
-
import './each.js'
|
|
4
|
-
import './text.js'
|
|
5
|
-
import './if.js' // if must go last, after other directives are initialized, since it removes :else, :else-if from tree
|
|
6
|
-
import './common.js'
|
|
7
|
-
import './prop.js'
|
|
8
|
-
import './data.js'
|
|
9
|
-
import './aria.js'
|
|
10
|
-
import './value.js'
|
|
11
|
-
import './on.js'
|
package/src/directives/on.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { directive, parseExpr } from '../core.js'
|
|
2
|
-
|
|
3
|
-
directive(':on', (el, expr) => {
|
|
4
|
-
let evaluate = parseExpr(expr);
|
|
5
|
-
let listeners = {}
|
|
6
|
-
return (state) => {
|
|
7
|
-
for (let evt in listeners) el.removeEventListener(evt, listeners[evt]);
|
|
8
|
-
listeners = evaluate(state);
|
|
9
|
-
for (let evt in listeners) el.addEventListener(evt, listeners[evt]);
|
|
10
|
-
}
|
|
11
|
-
})
|
|
12
|
-
|
package/src/directives/prop.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { directive, parseExpr } from '../core.js'
|
|
2
|
-
import { prop } from 'element-props'
|
|
3
|
-
|
|
4
|
-
directive(':prop', (el, expr) => {
|
|
5
|
-
let evaluate = parseExpr(expr);
|
|
6
|
-
return (state) => {
|
|
7
|
-
let value = evaluate(state);
|
|
8
|
-
for (let key in value) prop(el, key, value[key]);
|
|
9
|
-
}
|
|
10
|
-
})
|
package/src/directives/text.js
DELETED
package/src/directives/value.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { input, prop } from "element-props";
|
|
2
|
-
import { directive, parseExpr } from '../core.js';
|
|
3
|
-
|
|
4
|
-
// connect expr to element value
|
|
5
|
-
directive(':value', (el, expr) => {
|
|
6
|
-
let evaluateGet = parseExpr(expr);
|
|
7
|
-
let [get, set] = input(el);
|
|
8
|
-
let evaluateSet = parseSetter(expr);
|
|
9
|
-
let curState, onchange = e => evaluateSet(curState, get(el));
|
|
10
|
-
el.addEventListener('input', onchange);
|
|
11
|
-
el.addEventListener('change', onchange);
|
|
12
|
-
return (state) => {
|
|
13
|
-
let value = evaluateGet(curState = state);
|
|
14
|
-
prop(el, 'value', value)
|
|
15
|
-
set(value);
|
|
16
|
-
}
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
const memo = {}
|
|
20
|
-
function parseSetter(expr) {
|
|
21
|
-
if (memo[expr]) return memo[expr]
|
|
22
|
-
return memo[expr] = new Function(
|
|
23
|
-
['scope', 'value'],
|
|
24
|
-
`with (scope) { ${expr} = value };`
|
|
25
|
-
)
|
|
26
|
-
}
|