sprae 0.0.0 → 1.0.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/test/test.js ADDED
@@ -0,0 +1,303 @@
1
+ import { signal } from '@preact/signals'
2
+ import test, {is, any, throws} from 'tst'
3
+ import {tick, time} from 'wait-please'
4
+ import sprae from '../src/index.js'
5
+ import h from 'hyperf'
6
+
7
+ Object.defineProperty(DocumentFragment.prototype, 'outerHTML', {
8
+ get() {
9
+ let s = ''
10
+ this.childNodes.forEach(n => {
11
+ s += n.nodeType === 3 ? n.textContent : n.outerHTML != null ? n.outerHTML : ''
12
+ })
13
+ return s
14
+ }
15
+ })
16
+
17
+ test.skip('autoinit', async () => {
18
+ is(window.x.innerHTML, '1')
19
+ })
20
+
21
+ test('hidden: core', async () => {
22
+ let el = h`<div :hidden="hidden"></div>`
23
+ let params = sprae(el, {hidden:true})
24
+ is(el.outerHTML, `<div class="∴hidden" hidden=""></div>`)
25
+ params.hidden = false
26
+ is(el.outerHTML, `<div class="∴hidden"></div>`)
27
+ })
28
+
29
+ test('hidden: reactive', async () => {
30
+ const hidden = signal(true)
31
+ let el = h`<div :hidden="hidden"></div>`
32
+ sprae(el, {hidden})
33
+ is(el.outerHTML, `<div class="∴hidden" hidden=""></div>`)
34
+ hidden.value = false
35
+ is(el.outerHTML, `<div class="∴hidden"></div>`)
36
+ })
37
+
38
+ test('common: reactive', async () => {
39
+ 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
+ let params = sprae(el, {name:'text', url:'//google.com'})
41
+ is(el.outerHTML, `<label class="∴text ∴for" for="text">text</label><input class="∴id ∴name ∴type ∴disabled" id="text" name="text" type="text"><a class="∴href" href="//google.com"></a><img class="∴src" src="//google.com">`)
42
+ params.name = 'email'
43
+ is(el.outerHTML, `<label class="∴text ∴for" for="email">email</label><input class="∴id ∴name ∴type ∴disabled" id="email" name="email" type="email"><a class="∴href" href="//google.com"></a><img class="∴src" src="//google.com">`)
44
+ })
45
+
46
+ test('common: style', async () => {
47
+ let el = h`<x :style="style"></x>`
48
+ let params = sprae(el, {style: "top: 1px"})
49
+ is(el.outerHTML, `<x class="∴style" style="top: 1px"></x>`)
50
+ params.style = {top: '2px'}
51
+ is(el.outerHTML, `<x class="∴style" style="top: 2px"></x>`)
52
+ })
53
+
54
+ test('common: class', async () => {
55
+ let el = h`<x :class="a"></x><y :class="[b, c]"></y><z :class="{b:true, c:d}"></z>`
56
+ const c = signal('z')
57
+ let params = sprae(el, {a:'x', b:'y', c, d:false});
58
+ is(el.outerHTML, `<x class="x"></x><y class="y z"></y><z class="b"></z>`);
59
+ params.d = true;
60
+ is(el.outerHTML, `<x class="x"></x><y class="y z"></y><z class="b c"></z>`);
61
+ c.value = 'w'
62
+ is(el.outerHTML, `<x class="x"></x><y class="y w"></y><z class="b c"></z>`);
63
+ })
64
+
65
+ test('props: base', async () => {
66
+ let el = h`<input :prop="{for:1, title:2, help:3, type:4, placeholder: 5}"/>`
67
+ let params = sprae(el)
68
+ is(el.outerHTML, `<input class="∴prop" for="1" title="2" help="3" type="4" placeholder="5">`)
69
+ })
70
+
71
+ test('data: base', async () => {
72
+ let el = h`<input :data="{a:1, fooBar:2}"/>`
73
+ let params = sprae(el)
74
+ is(el.outerHTML, `<input class="∴data" data-a="1" data-foo-bar="2">`)
75
+ })
76
+
77
+ 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
+ sprae(el)
80
+ is(el.outerHTML, `<input type="text" id="jokes" role="combobox" class="∴aria" aria-controls="joketypes" aria-autocomplete="list" aria-expanded="false" aria-active-option="item1" aria-activedescendant="">`)
81
+ })
82
+
83
+ test('input: direct', async () => {
84
+ let el = h`<input :value="a" />`
85
+ let state = sprae(el, {a:1})
86
+ is(el.value, '1')
87
+ is(el.outerHTML, `<input class="∴value" value="1">`)
88
+ state.a = 2
89
+ is(el.value, '2')
90
+ is(el.outerHTML, `<input class="∴value" value="2">`)
91
+
92
+ el.value = 3
93
+ el.dispatchEvent(new window.Event('change'))
94
+ is(state.a, '3')
95
+ })
96
+
97
+ test('text: core', async () => {
98
+ let el = h`<div :text="text"></div>`
99
+ let params = sprae(el, {text:'abc'})
100
+ is(el.outerHTML, `<div class="∴text">abc</div>`)
101
+ params.text = null
102
+ is(el.outerHTML, `<div class="∴text"></div>`)
103
+ })
104
+
105
+ test('conditions: base', async () => {
106
+ let el = h`<p>
107
+ <span :if="a==1">a</span>
108
+ <span :else-if="a==2">b</span>
109
+ <span :else >c</span>
110
+ </p>`
111
+
112
+ const params = sprae(el, { a: 1 })
113
+
114
+ is(el.innerHTML, '<span class="∴if">a</span>')
115
+ params.a = 2
116
+ is(el.innerHTML, '<span class="∴else-if">b</span>')
117
+ params.a = 3
118
+ is(el.innerHTML, '<span class="∴else">c</span>')
119
+
120
+ delete params.a
121
+ })
122
+
123
+ test('conditions: short with insertions', async () => {
124
+ let el = h`<p>
125
+ <span :if="a==1" :text="'1:'+a"></span>
126
+ <span :else-if="a==2" :text="'2:'+a"></span>
127
+ <span :else :text="a"></span>
128
+ </p>`
129
+
130
+ const params = sprae(el, { a: 1 })
131
+
132
+ is(el.innerHTML, '<span class="∴text ∴if">1:1</span>')
133
+ params.a = 2
134
+ is(el.innerHTML, '<span class="∴text ∴else-if">2:2</span>')
135
+ params.a = 3
136
+ is(el.innerHTML, '<span class="∴text ∴else">3</span>')
137
+ params.a = 4
138
+ is(el.innerHTML, '<span class="∴text ∴else">4</span>')
139
+
140
+ params.a = 1
141
+ is(el.innerHTML, '<span class="∴text ∴if">1:1</span>')
142
+ params.a = 4
143
+ is(el.innerHTML, '<span class="∴text ∴else">4</span>')
144
+
145
+ delete params.a
146
+ })
147
+
148
+ test('conditions: reactive values', async () => {
149
+ let el = h`<p>
150
+ <span :if="a==1" :text="'1:'+a"></span>
151
+ <span :else-if="a==2" :text="'2:'+a"></span>
152
+ <span :else :text="a"></span>
153
+ </p>`
154
+
155
+ const a = signal(1)
156
+ sprae(el, { a })
157
+
158
+ is(el.innerHTML, '<span class="∴text ∴if">1:1</span>')
159
+ a.value = 2
160
+ is(el.innerHTML, '<span class="∴text ∴else-if">2:2</span>')
161
+ a.value = 3
162
+ is(el.innerHTML, '<span class="∴text ∴else">3</span>')
163
+ a.value = 4
164
+ is(el.innerHTML, '<span class="∴text ∴else">4</span>')
165
+
166
+ a.value = 1
167
+ is(el.innerHTML, '<span class="∴text ∴if">1:1</span>')
168
+ a.value = 4
169
+ is(el.innerHTML, '<span class="∴text ∴else">4</span>')
170
+ })
171
+
172
+
173
+ test('each: base', async () => {
174
+ // FIXME: in some conspicuous reason jsdom fails to update text nodes somehow
175
+ let el = h`<p>
176
+ <span :each="a in b" :text="a"></span>
177
+ </p>`
178
+
179
+ const params = sprae(el, { b: [] })
180
+
181
+ is(el.innerHTML, '')
182
+ params.b = [1,2]
183
+ is(el.innerHTML, '<span class="∴each ∴text">1</span><span class="∴each ∴text">2</span>')
184
+ params.b = []
185
+ is(el.innerHTML, '')
186
+ delete params.b
187
+ is(el.innerHTML, '')
188
+ })
189
+
190
+ test('each: reactive values', async () => {
191
+ let el = h`<p>
192
+ <span :each="a in b" :text="a"></span>
193
+ </p>`
194
+
195
+ const b = signal([])
196
+ const params = sprae(el, { b })
197
+
198
+ is(el.innerHTML, '')
199
+ b.value = [1,2]
200
+ is(el.innerHTML, '<span class="∴each ∴text">1</span><span class="∴each ∴text">2</span>')
201
+ b.value = []
202
+ is(el.innerHTML, '')
203
+ delete params.b
204
+ is(el.innerHTML, '')
205
+ })
206
+
207
+ test('each: loop with condition', async () => {
208
+ let el = h`<p>
209
+ <span :each="a in b" :text="a" :if="c"></span>
210
+ </p>`
211
+
212
+ const params = sprae(el, { b: [1,2], c:false })
213
+
214
+ is(el.innerHTML, '')
215
+ params.c = true
216
+ is(el.innerHTML, '<span class="∴each ∴text ∴if">1</span><span class="∴each ∴text ∴if">2</span>')
217
+ params.b = [1]
218
+ is(el.innerHTML, '<span class="∴each ∴text ∴if">1</span>')
219
+ delete params.b
220
+ is(el.innerHTML, '')
221
+ })
222
+
223
+ test('each: loop within condition', async () => {
224
+ let el = h`<p>
225
+ <x :if="a==1"><y :each="i in a" :text="i"></y></x>
226
+ <x :else-if="a==2"><y :each="i in a" :text="-i"></y></x>
227
+ </p>`
228
+
229
+ const params = sprae(el, { a: 1 })
230
+
231
+ is(el.innerHTML, '<x class="∴if"><y class="∴each ∴text">1</y></x>')
232
+ params.a = 2
233
+ is(el.innerHTML, '<x class="∴else-if"><y class="∴each ∴text">-1</y><y class="∴each ∴text">-2</y></x>')
234
+ params.a = 0
235
+ is(el.innerHTML, '')
236
+ })
237
+
238
+ test('each: condition within loop', async () => {
239
+ let el = h`<p>
240
+ <x :each="a in b">
241
+ <y :if="a==1" :text="'1:'+a"></y>
242
+ <y :else-if="a==2" :text="'2:'+a"></y>
243
+ <y :else :text="a"></y>
244
+ </x>
245
+ </p>`
246
+
247
+ const params = sprae(el, { b: [1,2,3] })
248
+
249
+ is(el.innerHTML, '<x class="∴each"><y class="∴text ∴if">1:1</y></x><x class="∴each"><y class="∴text ∴else-if">2:2</y></x><x class="∴each"><y class="∴text ∴else">3</y></x>')
250
+ params.b = [2]
251
+ is(el.innerHTML, '<x class="∴each"><y class="∴text ∴else-if">2:2</y></x>')
252
+ delete params.b
253
+ is(el.innerHTML, '')
254
+ })
255
+
256
+ test('on: base', () => {
257
+ let el = h`<div :on="{click(e){log.push('click')},x}"></div>`
258
+ let log = signal([])
259
+ let params = sprae(el, {x(){log.value.push('x')}, log})
260
+
261
+ is(el.outerHTML, `<div class="∴on"></div>`);
262
+ el.dispatchEvent(new window.Event('click'));
263
+ is(log.value, ['click'])
264
+ el.dispatchEvent(new window.Event('x'));
265
+ is(log.value, ['click','x'])
266
+
267
+ params.x = function(){log.value.push('xx')}
268
+ el.dispatchEvent(new window.Event('x'));
269
+ is(log.value, ['click','x','xx']);
270
+
271
+ delete params.x;
272
+ el.dispatchEvent(new window.Event('x'));
273
+ is(log.value, ['click','x','xx']);
274
+ })
275
+
276
+ test('with: inline', () => {
277
+ let el = h`<x :with="{foo:'bar', baz}"><y :text="foo + baz"></y></x>`
278
+ let state = sprae(el, {baz: 'qux'})
279
+ // FIXME: this doesn't inherit root scope baz property and instead uses hard-initialized one
280
+ is(el.innerHTML, `<y class="∴text">barqux</y>`)
281
+ state.baz = 'quux'
282
+ is(el.innerHTML, `<y class="∴text">barquux</y>`)
283
+ })
284
+ test('with: data', () => {
285
+ let el = h`<x :with="x"><y :text="foo"></y></x>`
286
+ let [state, update] = sprae(el, {x:{foo:'bar'}})
287
+ is(el.innerHTML, `<y class="∴text">bar</y>`)
288
+ update({x:{foo:'baz'}})
289
+ is(el.innerHTML, `<y class="∴text">baz</y>`)
290
+ })
291
+ test('with: inheritance', () => {
292
+ // NOTE: y:text initializes through directive, not through parent
293
+ // therefore by default :text uses parent's state, not defined by element itself
294
+ 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 class="∴with ∴text">foobar</y>`)
297
+ })
298
+ test('with: reactive inheritance', () => {
299
+ let el = h`<x :with="{foo:1}"><y :with="b.c" :text="foo+bar"></y></x>`
300
+ const bar = signal('2')
301
+ sprae(el, {b:{c:{bar}}})
302
+ is(el.innerHTML, `<y class="∴with ∴text">12</y>`)
303
+ })
package/src/dirs.js DELETED
@@ -1,43 +0,0 @@
1
- import { directive } from './core'
2
- import { parseExpr } from './eval'
3
-
4
- // hidden attribute directive example
5
- directive(':hidden', (el, expr) => {
6
- let evaluate = parseExpr(expr)
7
- return (state) => {
8
- let value = evaluate(state)
9
- prop(el, 'hidden', value)
10
- }
11
- })
12
-
13
-
14
- // directive(':if', (el, expr, stash) => {
15
- // stasj
16
- // let stash = {els: [el], clauses:[parse(expr)], holder:new Text}, current = el
17
- // ifStash.set(el, stash)
18
- // return state => {
19
- // let idx = stash.clauses.findIndex(match => match(state))
20
- // if (idx >= 0) current.replaceWith(current = els[idx])
21
- // else current.replaceWith(current = holder)
22
- // }
23
- // })
24
- // directive(':else-if', (el, expr) => {
25
- // const ifNode = prev('text-with-if')
26
- // const stash = ifStash.get(ifNode)
27
- // stash.els.push(el)
28
- // stash.els.
29
- // return
30
- // })
31
- // directive(':else', (el, expr) => {
32
-
33
- // })
34
-
35
- // directive(':each', (instance, part) => {
36
- // let evalLoop = part.eval
37
- // part.eval = state => {
38
- // let [itemId, items] = evalLoop(state), list=[]
39
- // // FIXME: cache els instead of recreating. Causes difficulties tracking el children
40
- // for (let item of items) list.push(new TemplateInstance(part.template, {...state,[itemId]:item}, processor))
41
- // return list
42
- // }
43
- // })
package/src/eval.js DELETED
@@ -1,33 +0,0 @@
1
- let evaluatorMemo = {}
2
-
3
- // borrowed from alpine: https://github.com/alpinejs/alpine/blob/main/packages/alpinejs/src/evaluator.js#L61
4
- // it seems to be more robust than subscript
5
- export function parseExpr(expression, el) {
6
- if (evaluatorMemo[expression]) return evaluatorMemo[expression]
7
-
8
- let AsyncFunction = Object.getPrototypeOf(async function(){}).constructor
9
-
10
- // Some expressions that are useful in Alpine are not valid as the right side of an expression.
11
- // Here we'll detect if the expression isn't valid for an assignement and wrap it in a self-
12
- // calling function so that we don't throw an error AND a "return" statement can b e used.
13
- let rightSideSafeExpression = 0
14
- // Support expressions starting with "if" statements like: "if (...) doSomething()"
15
- || /^[\n\s]*if.*\(.*\)/.test(expression)
16
- // Support expressions starting with "let/const" like: "let foo = 'bar'"
17
- || /^(let|const)\s/.test(expression)
18
- ? `(() => { ${expression} })()`
19
- : expression
20
-
21
- const safeAsyncFunction = () => {
22
- try {
23
- return new AsyncFunction(['scope'], `let result; with (scope) { result = ${rightSideSafeExpression} }; return result;`)
24
- } catch ( error ) {
25
- Object.assign( error, { el, expression } )
26
- console.warn(`∴ Expression Error: ${error.message}\n\n${ expression ? 'Expression: \"' + expression + '\"\n\n' : '' }`, el)
27
- setTimeout(() => { throw error }, 0)
28
- return Promise.resolve()
29
- }
30
- }
31
-
32
- return evaluatorMemo[expression] = safeAsyncFunction()
33
- }
package/test.js DELETED
@@ -1,84 +0,0 @@
1
- import v from 'value-ref'
2
- import { signal } from '@preact/signals'
3
- import test, {is, throws} from 'tst'
4
- import {tick, time} from 'wait-please'
5
- import sprae from '../sprae.js'
6
- import h from 'hyperf'
7
-
8
-
9
- test.only('hidden', async () => {
10
- let el = `<div :hidden="hidden"></div>`
11
- let params = sprae
12
- })
13
-
14
- test('conditions: short', async () => {
15
- let el = h`<p>
16
- <span :if="a==1">a</span>
17
- <span :else-if="a==2">b</span>
18
- <span :else >c</span>
19
- </p>`
20
-
21
- const params = sprae(el, { a: 1 })
22
-
23
- is(el.innerHTML, '<span>a</span>')
24
- params.a = 2
25
- is(el.innerHTML, '<span>b</span>')
26
- params.a = 3
27
- is(el.innerHTML, '<span>c</span>')
28
-
29
- delete params.a
30
- })
31
-
32
- test('conditions: short with insertions', async () => {
33
- let el = h`<p>
34
- <span :if="a==1" :text="['1:',a]"></span>
35
- <span :else-if="a==2" :text="['2:',a]"></span>
36
- <span :else :text="a"></span>
37
- </p>`
38
-
39
- const params = templize(el, { a: 1 }, exprProcessor)
40
-
41
- is(el.innerHTML, '<span>1:1</span>')
42
- params.a = 2
43
- is(el.innerHTML, '<span>2:2</span>')
44
- params.a = 3
45
- is(el.innerHTML, '<span>3</span>')
46
- params.a = 4
47
- is(el.innerHTML, '<span>4</span>')
48
-
49
- params.a = 1
50
- is(el.innerHTML, '<span>1:1</span>')
51
- params.a = 4
52
- is(el.innerHTML, '<span>4</span>')
53
-
54
- delete params.a
55
- })
56
-
57
- test.todo('conditions: reactive values', async () => {
58
-
59
- })
60
-
61
-
62
- test('loops: short', async () => {
63
- // FIXME: in some conspicuous reason jsdom fails to update text nodes somehow
64
- let el = h`<p>
65
- <span :each="item in items" :text="item"></span>
66
- </p>`
67
-
68
- const params = sprae(el, { items: [] })
69
-
70
- is(el.innerHTML, '')
71
- params.items = [1,2]
72
- is(el.innerHTML, '<span>1</span><span>2</span>')
73
- params.items = []
74
- is(el.innerHTML, '')
75
-
76
- delete params.items
77
- })
78
-
79
- test.todo('loops: reactive values', async () => {
80
- })
81
-
82
- test.todo('loops: condition within loop')
83
-
84
- test.todo('loops: loop within condition')