sprae 8.1.2 → 9.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/core.js +127 -0
- package/directive/aria.js +10 -0
- package/directive/class.js +17 -0
- package/directive/data.js +10 -0
- package/directive/default.js +148 -0
- package/directive/each.js +64 -0
- package/directive/fx.js +6 -0
- package/directive/html.js +11 -0
- package/directive/if.js +40 -0
- package/directive/ref.js +10 -0
- package/directive/scope.js +11 -0
- package/directive/style.js +16 -0
- package/directive/text.js +12 -0
- package/directive/value.js +31 -0
- package/dist/sprae.js +749 -0
- package/dist/sprae.min.js +1 -0
- package/package.json +17 -15
- package/readme.md +379 -208
- package/sprae.js +14 -977
- package/sprae.auto.js +0 -976
- package/sprae.auto.min.js +0 -1
- package/sprae.min.js +0 -1
- package/src/core.js +0 -82
- package/src/directives.js +0 -447
- package/src/domdiff.js +0 -71
- package/src/index.js +0 -6
- package/src/state.proxy.js +0 -150
- package/src/state.signals-proxy.js +0 -151
- package/src/state.signals.js +0 -82
- package/src/util.js +0 -13
package/readme.md
CHANGED
|
@@ -1,287 +1,464 @@
|
|
|
1
1
|
# ∴ spræ [](https://github.com/dy/sprae/actions/workflows/node.js.yml) [](https://bundlephobia.com/result?p=sprae) [](https://npmjs.org/sprae)
|
|
2
2
|
|
|
3
|
-
> DOM tree
|
|
3
|
+
> DOM tree microhydration
|
|
4
4
|
|
|
5
|
-
_Sprae_ is compact ergonomic
|
|
6
|
-
It provides
|
|
7
|
-
Perfect for small-scale websites, prototypes or UI
|
|
8
|
-
It is tiny and performant alternative to [alpine](https://github.com/alpinejs/alpine), [petite-vue](https://github.com/vuejs/petite-vue) or [template-parts](https://github.com/github/template-parts).
|
|
5
|
+
_Sprae_ is a compact & ergonomic progressive enhancement framework.<br/>
|
|
6
|
+
It provides `:`-attributes for inline markup logic with _signals_-based reactivity.<br/>
|
|
7
|
+
Perfect for small-scale websites, prototypes, or lightweight UI.<br/>
|
|
9
8
|
|
|
10
|
-
## Usage
|
|
11
|
-
|
|
12
|
-
### Autoinit
|
|
13
|
-
|
|
14
|
-
To autoinit document, include [`sprae.auto.js`](./sprae.auto.js):
|
|
15
|
-
|
|
16
|
-
```html
|
|
17
|
-
<!-- <script src="https://cdn.jsdelivr.net/npm/sprae/sprae.auto.js" defer></script> -->
|
|
18
|
-
<script defer src="./path/to/sprae.auto.js"></script>
|
|
19
|
-
|
|
20
|
-
<ul>
|
|
21
|
-
<li :each="item in ['apple', 'bananas', 'citrus']"">
|
|
22
|
-
<a :href="`#${item}`" :text="item" />
|
|
23
|
-
</li>
|
|
24
|
-
</ul>
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### Manual init
|
|
28
9
|
|
|
29
|
-
|
|
10
|
+
## Usage
|
|
30
11
|
|
|
31
12
|
```html
|
|
32
13
|
<div id="container" :if="user">
|
|
33
|
-
|
|
14
|
+
Hello <span :text="user.name">World</span>.
|
|
34
15
|
</div>
|
|
35
16
|
|
|
36
17
|
<script type="module">
|
|
37
|
-
|
|
38
|
-
|
|
18
|
+
import sprae, { signal } from 'sprae'
|
|
19
|
+
|
|
20
|
+
const name = signal('Kitty')
|
|
21
|
+
sprae(container, { user: { name } }) // init
|
|
39
22
|
|
|
40
|
-
|
|
41
|
-
state.user.name = 'dy'; // updates DOM
|
|
23
|
+
name.value = 'Dolly' // update
|
|
42
24
|
</script>
|
|
43
25
|
```
|
|
44
26
|
|
|
45
|
-
Sprae evaluates `:`-
|
|
27
|
+
Sprae evaluates `:`-directives and evaporates them, attaching state to html.
|
|
46
28
|
|
|
47
|
-
## State
|
|
48
29
|
|
|
49
|
-
|
|
50
|
-
It is based on [signals](https://github.com/preactjs/signals) and can take them as inputs.
|
|
30
|
+
## Directives
|
|
51
31
|
|
|
52
|
-
|
|
53
|
-
|
|
32
|
+
<details>
|
|
33
|
+
<summary><strong>:if, :else</strong></summary>
|
|
54
34
|
|
|
55
|
-
|
|
56
|
-
const state = sprae(container, { foo: 'bar', version })
|
|
35
|
+
#### `:if="condition"`, `:else`
|
|
57
36
|
|
|
58
|
-
|
|
59
|
-
state.foo = 'baz'
|
|
37
|
+
Control flow of elements.
|
|
60
38
|
|
|
61
|
-
|
|
62
|
-
|
|
39
|
+
```html
|
|
40
|
+
<span :if="foo">foo</span>
|
|
41
|
+
<span :else :if="bar">bar</span>
|
|
42
|
+
<span :else>baz</span>
|
|
63
43
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
44
|
+
<!-- fragment -->
|
|
45
|
+
<template :if="foo">
|
|
46
|
+
foo <span>bar</span> baz
|
|
47
|
+
</template>
|
|
48
|
+
```
|
|
49
|
+
</details>
|
|
67
50
|
|
|
68
|
-
|
|
51
|
+
<details>
|
|
52
|
+
<summary><strong>:each</strong></summary>
|
|
69
53
|
|
|
70
|
-
#### `:
|
|
54
|
+
#### `:each="item, index in items"`
|
|
71
55
|
|
|
72
|
-
|
|
56
|
+
Multiply element. Item is identified either by `item.id` or `item.key`.
|
|
73
57
|
|
|
74
|
-
```html
|
|
75
|
-
<
|
|
76
|
-
<span :else :if="bar">bar</span>
|
|
77
|
-
<span :else>baz</span>
|
|
78
|
-
```
|
|
58
|
+
```html
|
|
59
|
+
<ul><li :each="item in items" :text="item"/></ul>
|
|
79
60
|
|
|
80
|
-
|
|
61
|
+
<!-- cases -->
|
|
62
|
+
<li :each="item, idx in list" />
|
|
63
|
+
<li :each="val, key in obj" />
|
|
64
|
+
<li :each="idx in number" />
|
|
81
65
|
|
|
82
|
-
|
|
66
|
+
<!-- by condition -->
|
|
67
|
+
<li :if="items" :each="item in items" :text="item" />
|
|
68
|
+
<li :else>Empty list</li>
|
|
83
69
|
|
|
84
|
-
|
|
85
|
-
<
|
|
70
|
+
<!-- fragment -->
|
|
71
|
+
<template :each="item in items">
|
|
72
|
+
<dt :text="item.term"/>
|
|
73
|
+
<dd :text="item.definition"/>
|
|
74
|
+
</template>
|
|
86
75
|
|
|
87
|
-
<!--
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
76
|
+
<!-- prevent FOUC -->
|
|
77
|
+
<style>[:each] {visibility: hidden}</style>
|
|
78
|
+
```
|
|
79
|
+
</details>
|
|
91
80
|
|
|
92
|
-
|
|
93
|
-
<
|
|
94
|
-
<li :else>Empty list</li>
|
|
95
|
-
```
|
|
81
|
+
<details>
|
|
82
|
+
<summary><strong>:text</strong></summary>
|
|
96
83
|
|
|
97
|
-
#### `:text="value"`
|
|
84
|
+
#### `:text="value"`
|
|
98
85
|
|
|
99
|
-
Set text content of an element.
|
|
86
|
+
Set text content of an element.
|
|
100
87
|
|
|
101
|
-
```html
|
|
102
|
-
Welcome, <span :text="user.name">Guest</span>.
|
|
103
|
-
```
|
|
88
|
+
```html
|
|
89
|
+
Welcome, <span :text="user.name">Guest</span>.
|
|
104
90
|
|
|
105
|
-
|
|
91
|
+
<!-- fragment -->
|
|
92
|
+
Welcome, <template :text="user.name" />.
|
|
93
|
+
```
|
|
94
|
+
</details>
|
|
106
95
|
|
|
107
|
-
|
|
96
|
+
<details>
|
|
97
|
+
<summary><strong>:class</strong></summary>
|
|
108
98
|
|
|
109
|
-
|
|
110
|
-
<!-- set from string -->
|
|
111
|
-
<div :class="`foo ${bar}`"></div>
|
|
99
|
+
#### `:class="value"`
|
|
112
100
|
|
|
113
|
-
|
|
114
|
-
<div class="foo" :class="`bar`"></div>
|
|
101
|
+
Set class value, extends existing `class`.
|
|
115
102
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
103
|
+
```html
|
|
104
|
+
<!-- string with interpolation -->
|
|
105
|
+
<div :class="'foo $<bar>'"></div>
|
|
119
106
|
|
|
120
|
-
|
|
107
|
+
<!-- array/object a-la clsx -->
|
|
108
|
+
<div :class="[foo && 'foo', {bar: bar}]"></div>
|
|
109
|
+
```
|
|
110
|
+
</details>
|
|
121
111
|
|
|
122
|
-
|
|
112
|
+
<details>
|
|
113
|
+
<summary><strong>:style</strong></summary>
|
|
123
114
|
|
|
124
|
-
|
|
125
|
-
<!-- from string -->
|
|
126
|
-
<div :style="`foo: ${bar}`"></div>
|
|
115
|
+
#### `:style="value"`
|
|
127
116
|
|
|
128
|
-
|
|
129
|
-
<div :style="{foo: 'bar'}"></div>
|
|
117
|
+
Set style value, extends existing `style`.
|
|
130
118
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
119
|
+
```html
|
|
120
|
+
<!-- string with interpolation -->
|
|
121
|
+
<div :style="'foo: $<bar>'"></div>
|
|
134
122
|
|
|
135
|
-
|
|
123
|
+
<!-- object -->
|
|
124
|
+
<div :style="{foo: 'bar'}"></div>
|
|
136
125
|
|
|
137
|
-
|
|
126
|
+
<!-- CSS variable -->
|
|
127
|
+
<div :style="{'--baz': qux}"></div>
|
|
128
|
+
```
|
|
129
|
+
</details>
|
|
138
130
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
<input :value="value" />
|
|
142
|
-
<textarea :value="value" />
|
|
143
|
-
|
|
144
|
-
<!-- selects right option -->
|
|
145
|
-
<select :value="selected">
|
|
146
|
-
<option :each="i in 5" :value="i" :text="i"></option>
|
|
147
|
-
</select>
|
|
148
|
-
```
|
|
131
|
+
<details>
|
|
132
|
+
<summary><strong>:value</strong></summary>
|
|
149
133
|
|
|
150
|
-
#### `:
|
|
134
|
+
#### `:value="value"`
|
|
151
135
|
|
|
152
|
-
|
|
136
|
+
Set value of an input, textarea or select. Takes handle of `checked` and `selected` attributes.
|
|
153
137
|
|
|
154
|
-
```html
|
|
155
|
-
|
|
156
|
-
<
|
|
138
|
+
```html
|
|
139
|
+
<input :value="value" />
|
|
140
|
+
<textarea :value="value" />
|
|
157
141
|
|
|
158
|
-
<!--
|
|
159
|
-
<
|
|
142
|
+
<!-- selects right option -->
|
|
143
|
+
<select :value="selected">
|
|
144
|
+
<option :each="i in 5" :value="i" :text="i"></option>
|
|
145
|
+
</select>
|
|
146
|
+
```
|
|
147
|
+
</details>
|
|
160
148
|
|
|
161
|
-
|
|
162
|
-
<
|
|
163
|
-
<y :with="{ baz: 'qux' }" :text="foo + baz"></y>
|
|
164
|
-
</x>
|
|
165
|
-
```
|
|
149
|
+
<details>
|
|
150
|
+
<summary><strong>:*</strong></summary>
|
|
166
151
|
|
|
167
|
-
####
|
|
152
|
+
#### `:*="value"`, `:="values"`
|
|
168
153
|
|
|
169
|
-
Set any attribute
|
|
154
|
+
Set any attribute(s).
|
|
170
155
|
|
|
171
|
-
```html
|
|
172
|
-
|
|
173
|
-
<label :for="name" :text="name" />
|
|
156
|
+
```html
|
|
157
|
+
<label :for="name" :text="name" />
|
|
174
158
|
|
|
175
|
-
<!--
|
|
176
|
-
<input :id:name="name" />
|
|
159
|
+
<!-- multiple attributes -->
|
|
160
|
+
<input :id:name="name" />
|
|
177
161
|
|
|
178
|
-
<!--
|
|
179
|
-
<
|
|
162
|
+
<!-- spread attributes -->
|
|
163
|
+
<input :="{ id: name, name, type: 'text', value }" />
|
|
164
|
+
```
|
|
165
|
+
</details>
|
|
180
166
|
|
|
181
|
-
|
|
182
|
-
<
|
|
183
|
-
```
|
|
167
|
+
<details>
|
|
168
|
+
<summary><strong>:scope</strong></summary>
|
|
184
169
|
|
|
185
|
-
####
|
|
170
|
+
#### `:scope="data"`
|
|
186
171
|
|
|
187
|
-
|
|
172
|
+
Define or extend data scope for a subtree.
|
|
188
173
|
|
|
189
|
-
```html
|
|
190
|
-
<
|
|
191
|
-
|
|
174
|
+
```html
|
|
175
|
+
<x :scope="{ foo: signal('bar') }">
|
|
176
|
+
<!-- extends parent scope -->
|
|
177
|
+
<y :scope="{ baz: 'qux' }" :text="foo + baz"></y>
|
|
178
|
+
</x>
|
|
179
|
+
```
|
|
180
|
+
</details>
|
|
192
181
|
|
|
193
|
-
|
|
182
|
+
<details>
|
|
183
|
+
<summary><strong>:ref</strong></summary>
|
|
194
184
|
|
|
195
|
-
|
|
185
|
+
#### `:ref="name"`
|
|
196
186
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
<
|
|
187
|
+
Expose element to current scope with `name`.
|
|
188
|
+
|
|
189
|
+
```html
|
|
190
|
+
<textarea :ref="text" placeholder="Enter text..."></textarea>
|
|
201
191
|
|
|
202
|
-
<!-- iterable items -->
|
|
203
|
-
<ul>
|
|
192
|
+
<!-- iterable items -->
|
|
204
193
|
<li :each="item in items" :ref="item">
|
|
205
|
-
<input
|
|
194
|
+
<input :onfocus..onblur=="e => (item.classList.add('editing'), e => item.classList.remove('editing'))"/>
|
|
206
195
|
</li>
|
|
207
|
-
|
|
208
|
-
|
|
196
|
+
```
|
|
197
|
+
</details>
|
|
198
|
+
|
|
199
|
+
<details>
|
|
200
|
+
<summary><strong>:fx</strong></summary>
|
|
209
201
|
|
|
210
|
-
#### `:
|
|
202
|
+
#### `:fx="values"`
|
|
211
203
|
|
|
212
|
-
|
|
204
|
+
Run effect, not changing any attribute.<br/>Optional cleanup is called in-between effect calls or on disposal.
|
|
205
|
+
|
|
206
|
+
```html
|
|
207
|
+
<div :fx="a.value ? foo() : bar()" />
|
|
208
|
+
|
|
209
|
+
<!-- cleanup function -->
|
|
210
|
+
<div :fx="id = setInterval(tick, interval), () => clearInterval(tick)" />
|
|
211
|
+
```
|
|
212
|
+
</details>
|
|
213
|
+
|
|
214
|
+
<details>
|
|
215
|
+
<summary><strong>:on*</strong></summary>
|
|
216
|
+
|
|
217
|
+
#### `:on<event>.<mod>="handler"`, `:on<in>..on<out>="handler"`
|
|
218
|
+
|
|
219
|
+
Attach event(s) listener with possible modifiers.
|
|
220
|
+
|
|
221
|
+
```html
|
|
222
|
+
<input type="checkbox" :onchange="e => isChecked = e.target.value">
|
|
223
|
+
|
|
224
|
+
<!-- multiple events -->
|
|
225
|
+
<input :value="text" :oninput:onchange="e => text = e.target.value">
|
|
226
|
+
|
|
227
|
+
<!-- events sequence -->
|
|
228
|
+
<button :onfocus..onblur="e => ( handleFocus(), e => handleBlur())">
|
|
229
|
+
|
|
230
|
+
<!-- event modifiers -->
|
|
231
|
+
<button :onclick.throttle-500="handler">Not too often</button>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
##### Modifiers:
|
|
235
|
+
|
|
236
|
+
* `.once`, `.passive`, `.capture` – listener [options](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options).
|
|
237
|
+
* `.prevent`, `.stop` – prevent default or stop propagation.
|
|
238
|
+
* `.window`, `.document`, `.outside`, `.self` – specify event target.
|
|
239
|
+
* `.throttle-<ms>`, `.debounce-<ms>` – defer function call with one of the methods.
|
|
240
|
+
* `.ctrl`, `.shift`, `.alt`, `.meta`, `.arrow`, `.enter`, `.escape`, `.tab`, `.space`, `.backspace`, `.delete`, `.digit`, `.letter`, `.character` – filter by [`event.key`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values).
|
|
241
|
+
* `.ctrl-<key>, .alt-<key>, .meta-<key>, .shift-<key>` – key combinations, eg. `.ctrl-alt-delete` or `.meta-x`.
|
|
242
|
+
* `.*` – any other modifier has no effect, but allows binding multiple handlers to same event (like jQuery event classes).
|
|
243
|
+
|
|
244
|
+
</details>
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
<details>
|
|
248
|
+
<summary><strong>:html</strong> 🔌</summary>
|
|
249
|
+
|
|
250
|
+
#### `:html="element"`
|
|
251
|
+
|
|
252
|
+
> Include as `import 'sprae/directive/html'`.
|
|
253
|
+
|
|
254
|
+
Set html content of an element or instantiate a template.
|
|
255
|
+
|
|
256
|
+
```html
|
|
257
|
+
Hello, <span :html="userElement">Guest</span>.
|
|
258
|
+
|
|
259
|
+
<!-- fragment -->
|
|
260
|
+
Hello, <template :html="user.name">Guest</template>.
|
|
261
|
+
|
|
262
|
+
<!-- instantiate template -->
|
|
263
|
+
<template :ref="tpl"><span :text="foo"></span></template>
|
|
264
|
+
<div :html="tpl" :scope="{foo:'bar'}">...inserted here...</div>
|
|
265
|
+
```
|
|
266
|
+
</details>
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
<details>
|
|
270
|
+
<summary><strong>:data</strong> 🔌</summary>
|
|
271
|
+
|
|
272
|
+
#### `:data="values"`
|
|
273
|
+
|
|
274
|
+
> Include as `import 'sprae/directive/data'`.
|
|
275
|
+
|
|
276
|
+
Set `data-*` attributes. CamelCase is converted to dash-case.
|
|
277
|
+
|
|
278
|
+
```html
|
|
279
|
+
<input :data="{foo: 1, barBaz: true}" />
|
|
280
|
+
<!-- <input data-foo="1" data-bar-baz /> -->
|
|
281
|
+
```
|
|
282
|
+
</details>
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
<details>
|
|
286
|
+
<summary><strong>:aria</strong> 🔌</summary>
|
|
287
|
+
|
|
288
|
+
#### `:aria="values"`
|
|
289
|
+
|
|
290
|
+
> Include as `import 'sprae/directive/aria'`.
|
|
291
|
+
|
|
292
|
+
Set `aria-*` attributes. Boolean values are stringified.
|
|
293
|
+
|
|
294
|
+
```html
|
|
295
|
+
<input role="combobox" :aria="{
|
|
296
|
+
controls: 'joketypes',
|
|
297
|
+
autocomplete: 'list',
|
|
298
|
+
expanded: false,
|
|
299
|
+
activeOption: 'item1',
|
|
300
|
+
activedescendant: ''
|
|
301
|
+
}" />
|
|
302
|
+
<!--
|
|
303
|
+
<input role="combobox" aria-controls="joketypes" aria-autocomplete="list" aria-expanded="false" aria-active-option="item1" aria-activedescendant>
|
|
304
|
+
-->
|
|
305
|
+
```
|
|
306
|
+
</details>
|
|
307
|
+
|
|
308
|
+
<!--
|
|
309
|
+
#### `:onvisible..oninvisible="e => e => {}"`
|
|
310
|
+
|
|
311
|
+
Trigger when element is in/out of the screen.
|
|
213
312
|
|
|
214
313
|
```html
|
|
215
|
-
|
|
216
|
-
|
|
314
|
+
<div :onvisible..oninvisible="e => (
|
|
315
|
+
e.target.classList.add('visible'),
|
|
316
|
+
e => e.target.classlist.remove('visible')
|
|
317
|
+
)"/>
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
#### `:onmount..onunmount="e => e => {}"`
|
|
217
321
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
322
|
+
Trigger when element is connected / disconnected from DOM.
|
|
323
|
+
|
|
324
|
+
```html
|
|
325
|
+
<div :onmount..onunmount="e => (dispose = init(), e => dispose())"/>
|
|
221
326
|
```
|
|
327
|
+
-->
|
|
222
328
|
|
|
223
329
|
|
|
224
|
-
##
|
|
330
|
+
## Expressions
|
|
225
331
|
|
|
226
|
-
|
|
332
|
+
Expressions use [_justin_](https://github.com/dy/subscript?tab=readme-ov-file#justin), a minimal JS subset. It avoids "unsafe-eval" CSP and provides sandboxing. Also it's _fast_.
|
|
227
333
|
|
|
228
|
-
|
|
334
|
+
###### Operators:
|
|
229
335
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
336
|
+
`++ -- ! - + ** * / % && || ??`<br/>
|
|
337
|
+
`= < <= > >= == != === !==`<br/>
|
|
338
|
+
`<< >> & ^ | ~ ?: . ?. [] ()=>{} in`
|
|
339
|
+
|
|
340
|
+
###### Primitives:
|
|
233
341
|
|
|
234
|
-
|
|
235
|
-
|
|
342
|
+
`[] {} "" ''`<br/>
|
|
343
|
+
`1 2.34 -5e6 0x7a`<br/>
|
|
344
|
+
`true false null undefined NaN`
|
|
236
345
|
|
|
237
|
-
|
|
238
|
-
|
|
346
|
+
|
|
347
|
+
## Signals
|
|
348
|
+
|
|
349
|
+
Sprae uses minimal signals based on [`ulive`](https://ghub.io/ulive). It can be switched to [`@preact/signals-core`](https://ghub.io/@preact/signals-core), [`@webreflection/signal`](https://ghib.io/@webreflection/signal), [`usignal`](https://ghib.io/usignal), which are better for complex states:
|
|
350
|
+
|
|
351
|
+
```js
|
|
352
|
+
import sprae, { signal, computed, effect, batch, untracked } from 'sprae';
|
|
353
|
+
import * as signals from '@preact/signals-core';
|
|
354
|
+
|
|
355
|
+
sprae.use(signals);
|
|
356
|
+
|
|
357
|
+
sprae(el, { name: signal('Kitty') });
|
|
239
358
|
```
|
|
240
359
|
|
|
241
|
-
##### Event modifiers
|
|
242
360
|
|
|
243
|
-
|
|
244
|
-
* `.prevent`, `.stop` – prevent default or stop propagation.
|
|
245
|
-
* `.window`, `.document`, `.outside`, `.self` – specify event target.
|
|
246
|
-
* `.throttle-<ms>`, `.debounce-<ms>` – defer function call with one of the methods.
|
|
247
|
-
* `.ctrl`, `.shift`, `.alt`, `.meta`, `.arrow`, `.enter`, `.escape`, `.tab`, `.space`, `.backspace`, `.delete`, `.digit`, `.letter`, `.character` – filter by [`event.key`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values).
|
|
248
|
-
* `.ctrl-<key>, .alt-<key>, .meta-<key>, .shift-<key>` – key combinations, eg. `.ctrl-alt-delete` or `.meta-x`.
|
|
249
|
-
* `.*` – any other modifier has no effect, but allows binding multiple handlers to same event (like jQuery event classes).
|
|
361
|
+
## Customization
|
|
250
362
|
|
|
363
|
+
Sprae build can be tailored to project needs via `sprae/core` and `sprae/directive/*`:
|
|
251
364
|
|
|
252
|
-
|
|
365
|
+
```js
|
|
366
|
+
import sprae, { directive, compile } from 'sprae/core.js'
|
|
253
367
|
|
|
254
|
-
|
|
368
|
+
// include directives
|
|
369
|
+
import 'sprae/directive/if.js';
|
|
370
|
+
import 'sprae/directive/text.js';
|
|
255
371
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
372
|
+
// define custom directive
|
|
373
|
+
directive.id = (el, expr, state) => {
|
|
374
|
+
const evaluate = compile(state, 'id') // expression string -> evaluator
|
|
375
|
+
return () => el.id = evaluate(state) // return update function
|
|
376
|
+
}
|
|
259
377
|
```
|
|
260
378
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
_alert_, _prompt_, _confirm_, _fetch_, _performance_,
|
|
264
|
-
_setTimeout_, _setInterval_, _requestAnimationFrame_.
|
|
379
|
+
<!--
|
|
380
|
+
### DOM diffing
|
|
265
381
|
|
|
266
|
-
|
|
382
|
+
DOM differ uses [swapdom](https://github.com/dy/swapdom), can be reconfigured to [list-difference](https://github.com/paldepind/list-difference/), [udomdiff](https://github.com/WebReflection/udomdiff), [domdiff](https://github.com/WebReflection/domdiff), [etc](https://github.com/luwes/js-diff-benchmark):
|
|
267
383
|
|
|
268
|
-
|
|
384
|
+
```js
|
|
385
|
+
import sprae from 'sprae';
|
|
386
|
+
import domdiff from 'list-difference';
|
|
269
387
|
|
|
270
|
-
|
|
388
|
+
// swap(parentNode, prevEls, newEls, endNode?)
|
|
389
|
+
sprae.use({ swap: domdiff });
|
|
390
|
+
```
|
|
391
|
+
-->
|
|
271
392
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
393
|
+
<!--
|
|
394
|
+
### Custom Build
|
|
395
|
+
|
|
396
|
+
`sprae/core` exports bare-bones engine without directives, which allows tailoring build to project needs:
|
|
397
|
+
|
|
398
|
+
```js
|
|
399
|
+
import sprae, { directive, effect } from 'sprae/core'
|
|
400
|
+
|
|
401
|
+
// include required directives
|
|
402
|
+
import 'sprae/directive/if'
|
|
403
|
+
import 'sprae/directive/text'
|
|
275
404
|
```
|
|
405
|
+
-->
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
<!-- ## Dispose
|
|
409
|
+
|
|
410
|
+
To destroy state and detach sprae handlers, call `element[Symbol.dispose]()`. -->
|
|
276
411
|
|
|
277
|
-
## Dispose
|
|
278
412
|
|
|
279
|
-
|
|
413
|
+
## v9 changes
|
|
280
414
|
|
|
281
|
-
|
|
415
|
+
* No autoinit → use manual init via `import sprae from 'sprae'; sprae(document.body, state)`.
|
|
416
|
+
* No default globals (`console`, `setTimeout` etc) - pass to state if required.
|
|
417
|
+
* ``:class="`abc ${def}`"`` → `:class="'abc $<def>'"` (_justin_)
|
|
418
|
+
* `:with={x:'x'}` -> `:scope={x:'x'}`
|
|
419
|
+
* No reactive store → use signals for reactive values.
|
|
420
|
+
* `:render="tpl"` → `:html="tpl"`
|
|
421
|
+
* `@click="event.target"` → `:onclick="event => event.target"`
|
|
422
|
+
* Async props / events are not supported, pass async functions via state.
|
|
423
|
+
* Directives order matters, eg. `<a :if :each :scope />` !== `<a :scope :each :if />`
|
|
424
|
+
* Only one directive per `<template>`, eg. `<template :each />`, not `<template :if :each/>`
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
## Justification
|
|
428
|
+
|
|
429
|
+
[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](https://github.com/github/template-parts/issues/24), [SVG attributes](https://github.com/github/template-parts/issues/25), [liquid syntax](https://shopify.github.io/liquid/tags/template/#raw) conflict etc). [Alpine](https://github.com/alpinejs/alpine) / [petite-vue](https://github.com/vuejs/petite-vue) / [lucia](https://github.com/aidenyabi/lucia) escape native HTML quirks, but have excessive API (`:`, `x-`, `{}`, `@`, `$`) and tend to [self-encapsulate](https://github.com/alpinejs/alpine/discussions/3223) (no access to data, own reactivity, own expressions, own domdiffer).
|
|
430
|
+
|
|
431
|
+
_Sprae_ holds to open & minimalistic philosophy, combining _`:`-directives_ with _signals_.
|
|
432
|
+
|
|
433
|
+
<!--
|
|
434
|
+
| | [AlpineJS](https://github.com/alpinejs/alpine) | [Petite-Vue](https://github.com/vuejs/petite-vue) | Sprae |
|
|
435
|
+
|-----------------------|-------------------|-------------------|------------------|
|
|
436
|
+
| _Size_ | ~10KB | ~6KB | ~5KB |
|
|
437
|
+
| _Memory_ | 5.05 | 3.16 | 2.78 |
|
|
438
|
+
| _Performance_ | 2.64 | 2.43 | 1.76 |
|
|
439
|
+
| _CSP_ | Limited | No | Yes |
|
|
440
|
+
| _SSR_ | No | No | No |
|
|
441
|
+
| _Evaluation_ | [`new AsyncFunction`](https://github.com/alpinejs/alpine/blob/main/packages/alpinejs/src/evaluator.js#L81) | [`new Function`](https://github.com/vuejs/petite-vue/blob/main/src/eval.ts#L20) | [`new Function`]() / [justin](https://github.com/dy/subscript) |
|
|
442
|
+
| _Reactivity_ | `Alpine.store` | _@vue/reactivity_ | _signals_ |
|
|
443
|
+
| _Sandboxing_ | No | No | Yes |
|
|
444
|
+
| _Directives_ | `:`, `x-`, `{}` | `:`, `v-`, `@`, `{}` | `:` |
|
|
445
|
+
| _Magic_ | `$data` | `$app` | - |
|
|
446
|
+
| _Fragments_ | Yes | No | Yes |
|
|
447
|
+
| _Plugins_ | Yes | No | Yes |
|
|
448
|
+
| _Modifiers_ | Yes | No | Yes |
|
|
449
|
+
-->
|
|
450
|
+
|
|
451
|
+
<!--
|
|
452
|
+
<details>
|
|
453
|
+
<summary><strong>Benchmark</strong></summary>
|
|
282
454
|
|
|
283
455
|
See [js-framework-benchmark](https://krausest.github.io/js-framework-benchmark/current.html#eyJmcmFtZXdvcmtzIjpbIm5vbi1rZXllZC9wZXRpdGUtdnVlIiwibm9uLWtleWVkL3NwcmFlIl0sImJlbmNobWFya3MiOlsiMDFfcnVuMWsiLCIwMl9yZXBsYWNlMWsiLCIwM191cGRhdGUxMHRoMWtfeDE2IiwiMDRfc2VsZWN0MWsiLCIwNV9zd2FwMWsiLCIwNl9yZW1vdmUtb25lLTFrIiwiMDdfY3JlYXRlMTBrIiwiMDhfY3JlYXRlMWstYWZ0ZXIxa194MiIsIjA5X2NsZWFyMWtfeDgiLCIyMV9yZWFkeS1tZW1vcnkiLCIyMl9ydW4tbWVtb3J5IiwiMjNfdXBkYXRlNS1tZW1vcnkiLCIyNV9ydW4tY2xlYXItbWVtb3J5IiwiMjZfcnVuLTEway1tZW1vcnkiLCIzMV9zdGFydHVwLWNpIiwiMzRfc3RhcnR1cC10b3RhbGJ5dGVzIiwiNDFfc2l6ZS11bmNvbXByZXNzZWQiLCI0Ml9zaXplLWNvbXByZXNzZWQiXSwiZGlzcGxheU1vZGUiOjF9).
|
|
284
456
|
|
|
457
|
+

|
|
458
|
+
</details>
|
|
459
|
+
-->
|
|
460
|
+
|
|
461
|
+
<!--
|
|
285
462
|
<details>
|
|
286
463
|
<summary>How to run</summary>
|
|
287
464
|
|
|
@@ -292,7 +469,7 @@ npm run install-server
|
|
|
292
469
|
npm start
|
|
293
470
|
|
|
294
471
|
# build
|
|
295
|
-
cd frameworks/keyed/sprae
|
|
472
|
+
cd frameworks/non-keyed/sprae
|
|
296
473
|
npm ci
|
|
297
474
|
npm run build-prod
|
|
298
475
|
|
|
@@ -312,37 +489,31 @@ cd webdriver-ts
|
|
|
312
489
|
npm run results
|
|
313
490
|
```
|
|
314
491
|
</details>
|
|
492
|
+
-->
|
|
315
493
|
|
|
316
|
-
##
|
|
494
|
+
<!-- ## See also -->
|
|
317
495
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
* Wavearea: [demo](https://dy.github.io/wavearea?src=//cdn.freesound.org/previews/586/586281_2332564-lq.mp3), [code](https://github.com/dy/wavearea)
|
|
321
|
-
* Prostogreen [demo](http://web-being.org/prostogreen/), [code](https://github.com/web-being/prostogreen/)
|
|
496
|
+
<!--
|
|
497
|
+
## Alternatives
|
|
322
498
|
|
|
323
|
-
|
|
499
|
+
* [Alpine](https://github.com/alpinejs/alpine)
|
|
500
|
+
* ~~[Lucia](https://github.com/aidenybai/lucia)~~ deprecated
|
|
501
|
+
* [Petite-vue](https://github.com/vuejs/petite-vue)
|
|
502
|
+
* [nuejs](https://github.com/nuejs/nuejs)
|
|
503
|
+
-->
|
|
324
504
|
|
|
325
|
-
* [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](https://github.com/github/template-parts/issues/24), [SVG attributes](https://github.com/github/template-parts/issues/25), [liquid syntax](https://shopify.github.io/liquid/tags/template/#raw) conflict etc). Also ergonomics of `attr="{{}}"` is inferior to `:attr=""` since it creates flash of uninitialized values. Also it's just nice to keep `{{}}` generic, regardless of markup, and attributes as part of markup.
|
|
326
|
-
* [Alpine](https://github.com/alpinejs/alpine) / [vue](https://github.com/vuejs/petite-vue) / [lit](https://github.com/lit/lit/tree/main/packages/lit-html) escape native HTML quirks, but the syntax space (`:attr`, `v-*`,`x-*`, `l-*` `@evt`, `{{}}`) is too broad, as well as functionality. Perfection is when there's nothing to take away, not add (c). Also they tend to [self-encapsulate](https://github.com/alpinejs/alpine/discussions/3223) making interop hard, invent own tooling or complex reactivity.
|
|
327
|
-
* React / [preact](https://ghub.io/preact) does the job wiring up JS to HTML, but with an extreme of migrating HTML to JSX and enforcing SPA, which is not organic for HTML. Also it doesn't support reactive fields (needs render call).
|
|
328
505
|
|
|
329
|
-
|
|
506
|
+
## Examples
|
|
330
507
|
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
* It reserves minimal syntax space as `:` convention (keeping tree neatly decorated, not scattered).
|
|
336
|
-
* Expressions are naturally reactive and incur minimal updates.
|
|
337
|
-
* Elements / data API is open and enable easy interop.
|
|
508
|
+
* ToDo MVC: [demo](https://dy.github.io/sprae/examples/todomvc), [code](https://github.com/dy/sprae/blob/main/examples/todomvc.html)
|
|
509
|
+
* JS Framework Benchmark: [demo](https://dy.github.io/sprae/examples/js-framework-benchmark), [code](https://github.com/dy/sprae/blob/main/examples/js-framework-benchmark.html)
|
|
510
|
+
* Wavearea: [demo](https://dy.github.io/wavearea?src=//cdn.freesound.org/previews/586/586281_2332564-lq.mp3), [code](https://github.com/dy/wavearea)
|
|
511
|
+
* Prostogreen [demo](http://web-being.org/prostogreen/), [code](https://github.com/web-being/prostogreen/)
|
|
338
512
|
|
|
339
|
-
|
|
513
|
+
<!--
|
|
514
|
+
## See Also
|
|
340
515
|
|
|
341
|
-
|
|
516
|
+
* [nadi](https://github.com/dy/nadi) - 101 signals. -->
|
|
342
517
|
|
|
343
|
-
* [Alpine](https://github.com/alpinejs/alpine)
|
|
344
|
-
* ~~[Lucia](https://github.com/aidenybai/lucia)~~ deprecated
|
|
345
|
-
* [Petite-vue](https://github.com/vuejs/petite-vue)
|
|
346
|
-
* [nuejs](https://github.com/nuejs/nuejs)
|
|
347
518
|
|
|
348
519
|
<p align="center"><a href="https://github.com/krsnzd/license/">🕉</a></p>
|