sprae 0.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/.gitattributes +2 -0
- package/package.json +48 -0
- package/r&d.md +51 -0
- package/readme.md +163 -0
- package/src/core.js +50 -0
- package/src/dirs.js +43 -0
- package/src/eval.js +33 -0
- package/src/index.js +2 -0
- package/test.js +84 -0
package/.gitattributes
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sprae",
|
|
3
|
+
"description": "Reactive directives with expressions for DOM microtemplating.",
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"main": "sprae.js",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"sube": "^2.2.1",
|
|
8
|
+
"element-props": "^2.0.4",
|
|
9
|
+
"htm": "^3.1.1",
|
|
10
|
+
"swapdom": "^1.1.1",
|
|
11
|
+
"preact": "^10.11.2"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@preact/signals": "^1.1.2",
|
|
15
|
+
"hyperf": "^1.4.0",
|
|
16
|
+
"tst": "^7.1.1",
|
|
17
|
+
"value-ref": "^2.1.0",
|
|
18
|
+
"wait-please": "^3.1.0"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "node test"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/dy/sporae.git"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"preact",
|
|
29
|
+
"preact-signals",
|
|
30
|
+
"signals",
|
|
31
|
+
"observable",
|
|
32
|
+
"reactive",
|
|
33
|
+
"petite-vue",
|
|
34
|
+
"vue",
|
|
35
|
+
"template-parts",
|
|
36
|
+
"templize",
|
|
37
|
+
"alpine",
|
|
38
|
+
"alpinejs",
|
|
39
|
+
"directives",
|
|
40
|
+
"templating"
|
|
41
|
+
],
|
|
42
|
+
"author": "Dmitry Ivanov <df.creative@gmail.com>",
|
|
43
|
+
"license": "ISC",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/dy/sprae/issues"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/dy/sprae#readme"
|
|
48
|
+
}
|
package/r&d.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
## name
|
|
2
|
+
|
|
3
|
+
* rasa
|
|
4
|
+
* dew
|
|
5
|
+
* water
|
|
6
|
+
* humidify
|
|
7
|
+
* sprinkle
|
|
8
|
+
* va
|
|
9
|
+
* soma
|
|
10
|
+
* ley
|
|
11
|
+
* ros
|
|
12
|
+
* dewdrops
|
|
13
|
+
* mtn-dew
|
|
14
|
+
* pores
|
|
15
|
+
* drips
|
|
16
|
+
* straw
|
|
17
|
+
* spores
|
|
18
|
+
* spor
|
|
19
|
+
+ spores
|
|
20
|
+
+ russian
|
|
21
|
+
- spor
|
|
22
|
+
+ sport
|
|
23
|
+
* spore
|
|
24
|
+
* sporae
|
|
25
|
+
+ scottish twist
|
|
26
|
+
+ sounds russian-ish too
|
|
27
|
+
- a bit unusual from API perspective...
|
|
28
|
+
+ similar to algae
|
|
29
|
+
+ dots associate with colon prefix
|
|
30
|
+
* spour
|
|
31
|
+
* sprae?
|
|
32
|
+
+ reference to sporae and similar assoication
|
|
33
|
+
+ simpler word
|
|
34
|
+
+ better assoc with hydration
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## :attr, :data, :id, :class, :style, :on, :aria - do we enforce JS object or support unscoped expression? -> Use JS convention, too many use-cases.
|
|
38
|
+
|
|
39
|
+
1. JS object
|
|
40
|
+
+ JS object can directly set value as `:props="obj"`
|
|
41
|
+
- JS object is a bit verbose `:props="{a:1, b:2}"`, `:on="{click(e){}, touch(e){}}"`
|
|
42
|
+
+ It's very explicitly JS, no confusion must be introduced
|
|
43
|
+
- It makes HTML look a bit more noisy
|
|
44
|
+
+ It is more familiar
|
|
45
|
+
+ JS syntax saves redundant questions and an item from docs
|
|
46
|
+
|
|
47
|
+
2. Custom expressions
|
|
48
|
+
+ We anyways introduce custom-ish expression in `:each="item in items"`
|
|
49
|
+
- Vue introduces simple-ish parsing for that
|
|
50
|
+
+ Custom expressions are shorter: `:attr="a:1, b:2, c:3"`
|
|
51
|
+
- Custom expressions are confusing for style: `:style="a:1, b:2, c:3"` - very similar to direct style string
|
package/readme.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# ∴ spræ
|
|
2
|
+
|
|
3
|
+
Reactive directives for DOM microtemplating: moisturize tree without trasplanting to JSX.<br/>
|
|
4
|
+
A lightweight essential alternative to [alpine](https://github.com/alpinejs/alpine), [petite-vue](https://github.com/vuejs/petite-vue) and [templize](https://github.com/dy/templize) with better ergonomics[*](#justification).
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<script src="./sprae.js" defer init></script>
|
|
11
|
+
|
|
12
|
+
<div :scope="{ count: 0 }">
|
|
13
|
+
<span :text="count">
|
|
14
|
+
<button :on="{ click: e => count++ }">inc</button>
|
|
15
|
+
</div>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
* `:scope` marks regions on the tree that should be controlled by sprae.
|
|
19
|
+
* `init` attribute tells sprae to automatically initialize all elements that have `:scope`.
|
|
20
|
+
* any attribute starting with `:` is considered a sprae directive.
|
|
21
|
+
|
|
22
|
+
## Manual init
|
|
23
|
+
|
|
24
|
+
The more direct case is initializing sprae via JS.
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<div id="element" :if="user">
|
|
28
|
+
Logged in as <span :text="user.displayName">Guest.</span>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<script type="module">
|
|
32
|
+
import init from 'sprae';
|
|
33
|
+
|
|
34
|
+
const data = { user: { displayName: 'Dmitry Ivanov' } }
|
|
35
|
+
const [state, update] = init(user, data);
|
|
36
|
+
|
|
37
|
+
state.user.displayName = 'dy' // update value
|
|
38
|
+
update({user: {displayName: 'dy'}}) // alternatively
|
|
39
|
+
</script>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
* `init` initializes directives within an `element` subtree with passed `data`.
|
|
43
|
+
* `state` is proxy reflecting used values, changing any of its props updates directives.
|
|
44
|
+
* `update` can be used for bulk-updating multiple props.
|
|
45
|
+
* `data` is the initial state to render the template. It can include reactive values, see [reactivity](#reactivity).
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## Directives
|
|
49
|
+
|
|
50
|
+
* `:scope="data"` – autoinit subtree.
|
|
51
|
+
* `:if="condition"`, `:else-if="condition"`, `:else` - controls flow of elements.
|
|
52
|
+
* `:each="item, idx? in list"` - map list to instances of an element.
|
|
53
|
+
* `:text="value"` - set text content of an element.
|
|
54
|
+
* `:value="value"` – bind value to input or textarea.
|
|
55
|
+
* `:id`, `:name`, `:for`, `:type`, `:hidden`, `:disabled`, `:href`, `:src` – common attributes setters.
|
|
56
|
+
* `:class="[ foo, 'bar' ]"` – set element class from an array, object or a string.
|
|
57
|
+
* `:style="{ top:1, position:'absolute' }"` – set element style from a string or an object.
|
|
58
|
+
* `:prop="{ alt:'foo', title:'bar' }"` – set any attribute / property.
|
|
59
|
+
* `:on="{ click:e=>{}, touch:e=>{} }"` – add event listeners.
|
|
60
|
+
* `:data="{ foo:1, bar:2 }"` – set [data-*](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*) attributes.
|
|
61
|
+
* `:aria="{ role:'progressbar' }"` – set [aria-role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) attributes.
|
|
62
|
+
* `:item="{ id: 1 }"` – set [item*](https://developer.mozilla.org/en-US/docs/Web/HTML/Microdata) microdata attribute.
|
|
63
|
+
|
|
64
|
+
<!--
|
|
65
|
+
### Loops
|
|
66
|
+
|
|
67
|
+
Iterating over set of items can be done with `:each` directive:
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
<ul>
|
|
71
|
+
<li :each="item, index in items" :id="'item-' + item.id" :data="{value:item.value}" :text="item.label"></li>
|
|
72
|
+
</ul>
|
|
73
|
+
|
|
74
|
+
<li :each="{{ item, index in array }}">
|
|
75
|
+
<li :each="{{ key, value, index in object }}">
|
|
76
|
+
<li :each="{{ value in object }}">
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Conditions
|
|
80
|
+
|
|
81
|
+
To optionally display an element, there are `if`, `else-if`, `else` directives.
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<span :if="status == 0">Inactive</span>
|
|
85
|
+
<span :else-if="status == 1">Active</span>
|
|
86
|
+
<span :else>Finished</span>
|
|
87
|
+
```
|
|
88
|
+
-->
|
|
89
|
+
|
|
90
|
+
### Adding directives
|
|
91
|
+
|
|
92
|
+
Directives can be added by registering them via `directive(name, initializer)`:
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
import init, { directive } from 'sprae'
|
|
96
|
+
|
|
97
|
+
directive(':html', (el, expr) => {
|
|
98
|
+
// ...initialize here
|
|
99
|
+
const evaluate = parseExpression(expr)
|
|
100
|
+
return (state) => {
|
|
101
|
+
// ...update here
|
|
102
|
+
el.innerHTML = evaluate(state)
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
## Reactivity
|
|
109
|
+
|
|
110
|
+
Directive expressions are naturally reactive, ie. data may contain any async/reactive values, such as:
|
|
111
|
+
|
|
112
|
+
* _Promise_ / _Thenable_
|
|
113
|
+
* _Observable_ / _Subject_ / _Subscribable_
|
|
114
|
+
* _AsyncIterable_
|
|
115
|
+
* _observ-*_
|
|
116
|
+
* etc., see [sube](https://github.com/dy/sube/blob/main/README.md) for the full list.
|
|
117
|
+
|
|
118
|
+
This way, for example, _@preact/signals_ or _rxjs_ can be connected directly bypassing subscription or reading value.
|
|
119
|
+
|
|
120
|
+
Update happens when any value changes:
|
|
121
|
+
|
|
122
|
+
```html
|
|
123
|
+
<div id="done" :text="loading ? 'loading' : result">...</div>
|
|
124
|
+
|
|
125
|
+
<script>
|
|
126
|
+
import spores from 'sprae';
|
|
127
|
+
import { signals } from '@preact/signals';
|
|
128
|
+
|
|
129
|
+
// <div id="done">...</div>
|
|
130
|
+
|
|
131
|
+
const loading = signal(true),
|
|
132
|
+
result = signal(false);
|
|
133
|
+
spores(done, { loading, result })
|
|
134
|
+
|
|
135
|
+
// <div id="done">loading</div>
|
|
136
|
+
|
|
137
|
+
setTimeout(() => (loading.value = true, result.value = 'done'), 1000)
|
|
138
|
+
|
|
139
|
+
// ... 1s after
|
|
140
|
+
// <div id="done">done</div>
|
|
141
|
+
</script>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Note: observers don't require disposal, since they're connected in weak fashion. Once element is disposed, observables are disconnected.
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
## Justification
|
|
149
|
+
|
|
150
|
+
* [Template-parts](https://github.com/dy/template-parts) / [templize](https://github.com/dy/templize) is progressive, but is stuck with native HTML quirks (parsing table case, svg attributes, conflict with liquid syntax etc). Besides ergonomics of `attr="{{}}"` is inferior to `:attr=""` since it creates flash of uninitialized values.
|
|
151
|
+
* [Alpine](https://github.com/alpinejs/alpine) / [vue](https://github.com/vuejs/petite-vue) / [lit](https://github.com/lit/lit/tree/main/packages/lit-html) escapes native HTML quirks, but the syntax is a bit scattered: `:attr`, `v-*`,`x-*`, `@evt`, `{{}}` can be expressed with single convention. Besides, functionality is too broad and can be reduced to essence.
|
|
152
|
+
* [preact](https://ghub.io/preact) with HTML as JSX is a nice way to wire JS to templates, but it doesn't really support reactive fields (needs render call). Also migrating HTML to JS is an extreme with unwanted side-effects.
|
|
153
|
+
|
|
154
|
+
_Sprae_ takes elegant syntax convention of _alpine_ and method of _templize_ to connect any reactive values (like [@preact/signals](https://ghub.io/@preact/signals) or observables) to static HTML.
|
|
155
|
+
|
|
156
|
+
* It doesn't break static html markup.
|
|
157
|
+
* It doesn't intrude native syntax space.
|
|
158
|
+
* It falls back to element content if uninitialized.
|
|
159
|
+
* It provides means for island hydration.
|
|
160
|
+
* It doesn'y introduce syntax scatter.
|
|
161
|
+
* It supports simple expressions with exposed reactive data types.
|
|
162
|
+
|
|
163
|
+
<p align="center"><a href="https://github.com/krsnzd/license/">🕉</a></p>
|
package/src/core.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// autoinit
|
|
2
|
+
const s = document.currentScript
|
|
3
|
+
if (s && s.hasAttribute('init')) {
|
|
4
|
+
sprae(document.documentElement)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// sprae element: apply directives
|
|
8
|
+
export default function sprae(el, scope) {
|
|
9
|
+
scope ||= {}
|
|
10
|
+
|
|
11
|
+
let params, updates=[]
|
|
12
|
+
|
|
13
|
+
// prepare directives
|
|
14
|
+
for (let dir in directives) updates[dir] = directives[dir](el, scope)
|
|
15
|
+
|
|
16
|
+
const update = (diff=scope) => {
|
|
17
|
+
if (diff !== scope) Object.assign(scope, diff);
|
|
18
|
+
for (let dir in updates) updates[dir].forEach(update => update(scope))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// return update via destructuring of result to allow batch-update
|
|
22
|
+
scope[Symbol.iterator] = function*(){ yield params; yield update; }
|
|
23
|
+
|
|
24
|
+
return params = new Proxy(scope, {
|
|
25
|
+
set: (s, k, v) => (scope[k]=v, update(), 1),
|
|
26
|
+
deleteProperty: (s,k) => (delete scope[k], update(), 1)
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// dict of directives
|
|
31
|
+
const directives = {}, store = new WeakMap
|
|
32
|
+
|
|
33
|
+
// register a directive
|
|
34
|
+
export const directive = (name, initializer) => (
|
|
35
|
+
directives[name] = (container) => {
|
|
36
|
+
const els = container.querySelectorAll(`[${name}]`)
|
|
37
|
+
const updates = []
|
|
38
|
+
|
|
39
|
+
// replace all shortcuts with inner templates
|
|
40
|
+
for (el of els) {
|
|
41
|
+
// FIXME: make sure no leak is introduced here
|
|
42
|
+
if (!store.has(el)) {
|
|
43
|
+
store.add(el)
|
|
44
|
+
updates.push(initializer(el, el.getAttribute(name)))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return updates
|
|
49
|
+
}
|
|
50
|
+
)
|
package/src/dirs.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
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/src/index.js
ADDED
package/test.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
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')
|