sprae 12.2.4 → 12.3.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/readme.md CHANGED
@@ -1,510 +1,6 @@
1
- # ∴ spræ [![tests](https://github.com/dy/sprae/actions/workflows/node.js.yml/badge.svg)](https://github.com/dy/sprae/actions/workflows/node.js.yml) [![npm bundle size](https://img.shields.io/bundlephobia/minzip/sprae)](https://bundlephobia.com/package/sprae) [![npm](https://img.shields.io/npm/v/sprae?color=orange)](https://www.npmjs.com/package/sprae)
1
+ # [∴](https://dy.github.io/sprae) spræ
2
2
 
3
- <em>S</em>imple <em>pr</em>ogressive <em>æ</em>nhancement for DOM or JSX.<br/>
4
-
5
- <!-- [Usage](#usage) · [Directives](#directives) · [Modifiers](#modifiers) · [Store](#store) · [Signals](#signals) · [Evaluator](#evaluator) · [Start](#autoinit) · [JSX](#jsx) · [Build](#custom-build) · [Hints](#hints) · [Examples](#examples) -->
6
-
7
- ## Usage
8
-
9
- ```html
10
- <div id="counter" :scope="{ count: 0 }">
11
- <p :text="`Clicked ${count} times`"></p>
12
- <button :onclick="count++">Click me</button>
13
- </div>
14
-
15
- <script src="https://cdn.jsdelivr.net/npm/sprae@12.x.x/dist/sprae.umd.js" start></script>
16
- ```
17
-
18
- Sprae enables reactivity via `:`-directives.
19
-
20
- <!--
21
- ## Concepts
22
-
23
- **Directives** are `:` prefixed attributes that evaluate JavaScript expressions:
24
- `<div :text="message"></div>`
25
-
26
- **Reactivity** happens automatically through signals—just mutate values:
27
- `<button :onclick="count++">` updates `<span :text="count">`
28
-
29
- **Scope** creates a state container for a subtree:
30
- `<div :scope="{ user: 'Alice' }">` makes `user` available to children
31
-
32
- **Effects** run side effects:
33
- `:fx="console.log(count)"` logs when `count` changes
34
-
35
- **Modifiers** adjust directive behavior:
36
- `:oninput.debounce-200` delays handler by 200ms
37
- -->
38
-
39
- <!--
40
- ### Flavors
41
-
42
- * [sprae.js](dist/sprae.js) – ESM.
43
- * [sprae.umd.js](dist/sprae.umd.js) – CJS / UMD / standalone with autoinit.
44
- * [sprae.micro.js](dist/sprae.micro.js) – <2.5kb [micro version](#micro).
45
- -->
46
- <!-- * sprae.async.js - sprae with async events -->
47
- <!-- * sprae.alpine.js - alpine sprae, drop-in alpinejs replacement -->
48
- <!-- * sprae.vue.js - vue sprae, drop-in petite-vue replacement -->
49
- <!-- * sprae.preact.js - sprae with preact-signals -->
50
-
51
-
52
- ## Directives
53
-
54
- #### `:text`
55
-
56
- Set text content.
57
-
58
- ```html
59
- Welcome, <span :text="user.name">Guest</span>.
60
-
61
- <!-- fragment -->
62
- Welcome, <template :text="user.name"><template>.
63
-
64
- <!-- function -->
65
- <span :text="val => val + text"></span>
66
- ```
67
-
68
- #### `:class`
69
-
70
- Set className.
71
-
72
- ```html
73
- <div :class="foo"></div>
74
-
75
- <!-- appends to static class -->
76
- <div class="bar" :class="baz"></div>
77
-
78
- <!-- array/object, a-la clsx -->
79
- <div :class="['foo', bar && 'bar', { baz }]"></div>
80
-
81
- <!-- function -->
82
- <div :class="str => [str, 'active']"></div>
83
- ```
84
-
85
- #### `:style`
86
-
87
- Set style.
88
-
89
- ```html
90
- <span :style="'display: inline-block'"></span>
91
-
92
- <!-- extends static style -->
93
- <div style="foo: bar" :style="'bar-baz: qux'">
94
-
95
- <!-- object -->
96
- <div :style="{bar: 'baz', '--qux': 'quv'}"></div>
97
-
98
- <!-- function -->
99
- <div :style="obj => ({'--bar': baz})"></div>
100
- ```
101
-
102
- #### `:value`
103
-
104
- Bind input, textarea or select value.
105
-
106
- ```html
107
- <input :value="value" />
108
- <textarea :value="value" />
109
-
110
- <!-- handles option & selected attr -->
111
- <select :value="selected">
112
- <option :each="i in 5" :value="i" :text="i"></option>
113
- </select>
114
-
115
- <!-- checked attr -->
116
- <input type="checkbox" :value="item.done" />
117
-
118
- <!-- function -->
119
- <input :value="value => value + str" />
120
- ```
121
-
122
- #### `:<attr>`, `:`
123
-
124
- Set any attribute(s).
125
-
126
- ```html
127
- <label :for="name" :text="name" />
128
-
129
- <!-- multiple -->
130
- <input :id:name="name" />
131
-
132
- <!-- function -->
133
- <div :hidden="hidden => !hidden"></div>
134
-
135
- <!-- spread -->
136
- <input :="{ id: name, name, type: 'text', value, ...props }" />
137
- ```
138
-
139
- #### `:if`, `:else`
140
-
141
- Control flow.
142
-
143
- ```html
144
- <span :if="foo">foo</span>
145
- <span :else :if="bar">bar</span>
146
- <span :else>baz</span>
147
-
148
- <!-- fragment -->
149
- <template :if="foo">foo <span>bar</span> baz</template>
150
-
151
- <!-- function -->
152
- <span :if="active => test()"></span>
153
- ```
154
-
155
- #### `:each`
156
-
157
- Multiply content.
158
-
159
- ```html
160
- <ul><li :each="item in items" :text="item" /></ul>
161
-
162
- <!-- cases -->
163
- <li :each="item, idx? in array" />
164
- <li :each="value, key? in object" />
165
- <li :each="count, idx? in number" />
166
- <li :each="item, idx? in function" />
167
-
168
- <!-- fragment -->
169
- <template :each="item in items">
170
- <dt :text="item.term"/>
171
- <dd :text="item.definition"/>
172
- </template>
173
- ```
174
-
175
- #### `:scope`
176
-
177
- Define state container for a subtree.
178
-
179
- ```html
180
- <!-- transparent -->
181
- <x :scope="{foo: 'foo'}">
182
- <y :scope="{bar: 'bar'}" :text="foo + bar"></y>
183
- </x>
184
-
185
- <!-- define variables -->
186
- <x :scope="x=1, y=2" :text="x+y"></x>
187
-
188
- <!-- blank -->
189
- <x :scope :ref="id"></x>
190
-
191
- <!-- access to local scope instance -->
192
- <x :scope="scope => { scope.x = 'foo'; return scope }" :text="x"></x>
193
- ```
194
-
195
- #### `:fx`
196
-
197
- Run effect.
198
-
199
- ```html
200
- <!-- inline -->
201
- <div :fx="a.value ? foo() : bar()" />
202
-
203
- <!-- function / cleanup -->
204
- <div :fx="() => (id = setInterval(tick, 1000), () => clearInterval(id))" />
205
- ```
206
-
207
- #### `:ref`
208
-
209
- Expose an element in scope or get ref to the element.
210
-
211
- ```html
212
- <div :ref="card" :fx="handle(card)"></div>
213
-
214
- <!-- reference -->
215
- <div :ref="el => el.innerHTML = '...'"></div>
216
-
217
- <!-- local reference -->
218
- <li :each="item in items" :scope :ref="li">
219
- <input :onfocus="e => li.classList.add('editing')"/>
220
- </li>
221
-
222
- <!-- mount / unmount -->
223
- <textarea :ref="el => {/* onmount */ return () => {/* onunmount */}}" :if="show"></textarea>
224
- ```
225
-
226
- #### `:on<event>`
227
-
228
- Add event listener.
229
-
230
- ```html
231
- <!-- inline -->
232
- <button :onclick="count++">Up</button>
233
-
234
- <!-- function -->
235
- <input type="checkbox" :onchange="event => isChecked = event.target.value">
236
-
237
- <!-- multiple -->
238
- <input :onvalue="text" :oninput:onchange="event => text = event.target.value">
239
-
240
- <!-- sequence -->
241
- <button :onfocus..onblur="evt => { handleFocus(); return evt => handleBlur()}">
242
-
243
- <!-- modifiers -->
244
- <button :onclick.throttle-500="handle()">Not too often</button>
245
- ```
246
-
247
- <!--
248
- #### `:data="values"`
249
-
250
- Set `data-*` attributes. CamelCase is converted to dash-case.
251
-
252
- ```html
253
- <input :data="{foo: 1, barBaz: true}" />
254
- <!-- <input data-foo="1" data-bar-baz />
255
- ```
256
-
257
- #### `:aria="values"`
258
-
259
- Set `aria-*` attributes. Boolean values are stringified.
260
-
261
- ```html
262
- <input role="combobox" :aria="{
263
- controls: 'joketypes',
264
- autocomplete: 'list',
265
- expanded: false,
266
- activeOption: 'item1',
267
- activedescendant: ''
268
- }" />
269
- <!--
270
- <input role="combobox" aria-controls="joketypes" aria-autocomplete="list" aria-expanded="false" aria-active-option="item1" aria-activedescendant>
271
- ```
272
- -->
273
-
274
- <!--
275
- #### `:onvisible..oninvisible="e => e => {}"`
276
-
277
- Trigger when element is in/out of the screen.
278
-
279
- ```html
280
- <div :onvisible..oninvisible="e => (
281
- e.target.classList.add('visible'),
282
- e => e.target.classlist.remove('visible')
283
- )"/>
284
- ```
285
-
286
- #### `:onmount..onunmount="e => e => {}"`
287
-
288
- Trigger when element is connected / disconnected from DOM.
289
-
290
- ```html
291
- <div :onmount..onunmount="e => (dispose = init(), e => dispose())"/>
292
- ```
293
- -->
294
-
295
-
296
- ## Modifiers
297
-
298
-
299
- #### `.debounce-<ms|tick|frame|idle>?`
300
-
301
- Defer callback by ms, next tick/animation frame, or until idle. Defaults to 250ms.
302
-
303
- ```html
304
- <!-- debounce keyboard input by 200ms -->
305
- <input :oninput.debounce-200="event => update(event)" />
306
-
307
- <!-- set class in the next tick -->
308
- <div :class.debounce-tick="{ active }">...</div>
309
-
310
- <!-- debounce resize to animation framerate -->
311
- <div :onresize.window.debounce-frame="updateSize()">...</div>
312
-
313
- <!-- batch logging when idle -->
314
- <div :fx.debounce-idle="sendAnalytics(batch)"></div>
315
- ```
316
-
317
- #### `.throttle-<ms|tick|frame>?`
318
-
319
- Limit callback rate to interval in ms, tick or animation framerate. By default 250ms.
320
-
321
- ```html
322
- <!-- throttle text update -->
323
- <div :text.throttle-100="text.length"></div>
324
-
325
- <!-- lock style update to animation framerate -->
326
- <div :onscroll.throttle-frame="progress = (scrollTop / scrollHeight) * 100"/>
327
-
328
- <!-- ensure separate stack for events -->
329
- <div :onmessage.window.throttle-tick="event => log(event)">...</div>
330
- ```
331
-
332
- #### `.once`
333
-
334
- Call only once.
335
-
336
- ```html
337
- <!-- run event callback only once -->
338
- <button :onclick.once="loadMoreData()">Start</button>
339
-
340
- <!-- run once on sprae init -->
341
- <div :fx.once="console.log('sprae init')">
342
- ```
343
-
344
- #### `.window`, `.document`, `.body`, `.root`, `.parent`, `.outside`, `.self`
345
-
346
- Specify target.
347
-
348
- ```html
349
- <!-- close dropdown when click outside -->
350
- <div :onclick.outside="closeMenu()" :class="{ open: isOpen }">Dropdown</div>
351
-
352
- <!-- interframe communication -->
353
- <div :onmessage.window="e => e.data.type === 'success' && complete()">...</div>
354
-
355
- <!-- set css variable on document root element (<html>) -->
356
- <main :style.root="{'--x': x}">...</main>
357
- ```
358
-
359
- #### `.passive`, `.capture`  <kbd>events only</kbd>
360
-
361
- Event listener [options](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options).
362
-
363
- ```html
364
- <div :onscroll.passive="e => pos = e.scrollTop">Scroll me</div>
365
-
366
- <body :ontouchstart.capture="logTouch(e)"></body>
367
- ```
368
-
369
- #### `.prevent`, `.stop`, `.stop-immediate`  <kbd>events only</kbd>
370
-
371
- Prevent default or stop (immediate) propagation.
372
-
373
- ```html
374
- <!-- prevent default -->
375
- <a :onclick.prevent="navigate('/page')" href="/default">Go</a>
376
-
377
- <!-- stop immediate propagation -->
378
- <button :onclick.stop-immediate="criticalHandle()">Click</button>
379
- ```
380
-
381
- #### `.<key>-<*>` <kbd>events only</kbd>
382
-
383
- Filter event by [`event.key`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) or combination:
384
-
385
- * `.ctrl`, `.shift`, `.alt`, `.meta`, `.enter`, `.esc`, `.tab`, `.space` – direct key
386
- * `.delete` – delete or backspace
387
- * `.arrow` – up, right, down or left arrow
388
- * `.digit` – 0-9
389
- * `.letter` – A-Z, a-z or any [unicode letter](https://unicode.org/reports/tr18/#General_Category_Property)
390
- * `.char` – any non-space character
391
-
392
- ```html
393
- <!-- any arrow event -->
394
- <div :onkeydown.arrow="event => navigate(event.key)"></div>
395
-
396
- <!-- key combination -->
397
- <input :onkeydown.prevent.ctrl-c="copy(clean(value))">
398
- ```
399
-
400
- <!--
401
- #### `.persist-<kind?>`  <kbd>props</kbd>
402
-
403
- Persist value in local or session storage.
404
-
405
- ```html
406
- <textarea :value.persist="text" />
407
-
408
- <select :onchange="event => theme = event.target.value" :value.persist="theme">
409
- <option value="light">Light</option>
410
- <option value="dark">Dark</option>
411
- </select>
412
- ```
413
- -->
414
-
415
- #### `.<any>`
416
-
417
- Any other modifier has no effect, but allows binding multiple handlers.
418
-
419
- ```html
420
- <span :fx.once="init(x)" :fx.update="() => (update(), () => destroy())">
421
- ```
422
-
423
-
424
- ## Store
425
-
426
- Sprae uses signals store for reactivity.
427
-
428
- ```js
429
- import sprae, { store, signal, effect, computed } from 'sprae'
430
-
431
- const name = signal('foo');
432
- const capname = computed(() => name.value.toUpperCase());
433
-
434
- const state = store(
435
- {
436
- count: 0, // prop
437
- inc(){ this.count++ }, // method
438
- name, capname, // signal
439
- get twice(){ return this.count * 2 }, // computed
440
- _i: 0, // untracked
441
- },
442
-
443
- // globals / sandbox
444
- { Math }
445
- )
446
-
447
- sprae(element, state). // init
448
-
449
- state.inc(), state.count++ // update
450
- name.value = 'bar' // signal update
451
- state._i++ // no update
452
-
453
- state.Math // == globalThis.Math
454
- state.navigator // == undefined
455
- ```
456
-
457
-
458
- ## Signals
459
-
460
- Default signals can be replaced with _preact-signals_ alternative:
461
-
462
- ```js
463
- import sprae from 'sprae';
464
- import { signal, computed, effect, batch, untracked } from 'sprae/signal';
465
- import * as signals from '@preact/signals-core';
466
-
467
- sprae.use(signals);
468
- ```
469
-
470
- Provider | Size | Feature
471
- :---|:---|:---
472
- [`ulive`](https://ghub.io/ulive) | 350b | Minimal implementation, basic performance, good for small states.
473
- [`signal`](https://ghub.io/@webreflection/signal) | 633b | Class-based, better performance, good for small-medium states.
474
- [`usignal`](https://ghub.io/usignal) | 955b | Class-based with optimizations and optional async effects.
475
- [`@preact/signals-core`](https://ghub.io/@preact/signals-core) | 1.47kb | Best performance, good for any states, industry standard.
476
- [`signal-polyfill`](https://ghub.io/signal-polyfill) | 2.5kb | Proposal signals. Use via [adapter](https://gist.github.com/dy/bbac687464ccf5322ab0e2fd0680dc4d).
477
- [`alien-signals`](https://github.com/WebReflection/alien-signals) | 2.67kb | Preact-flavored [alien signals](https://github.com/stackblitz/alien-signals).
478
-
479
-
480
- ## Evaluator
481
-
482
- Default evaluator is fast and compact, but violates "unsafe-eval" CSP.<br/>
483
- To make eval stricter & safer, any alternative can be used, eg. [_justin_](https://github.com/dy/subscript#justin):
484
-
485
- ```js
486
- import sprae from 'sprae'
487
- import justin from 'subscript/justin'
488
-
489
- sprae.use({compile: justin})
490
- ```
491
-
492
- <!--
493
- a minimal JS subset:
494
-
495
- `++ -- ! - + * / % ** && || ??`<br/>
496
- `= < <= > >= == != === !==`<br/>
497
- `<< >> >>> & ^ | ~ ?: . ?. [] ()=>{} in`<br/>
498
- `= += -= *= /= %= **= &&= ||= ??= ... ,`<br/>
499
- `[] {} "" ''`<br/>
500
- `1 2.34 -5e6 0x7a`<br/>
501
- `true false null undefined NaN`
502
- -->
503
-
504
-
505
- ## Autoinit
506
-
507
- The `start` / `data-start` attribute automatically starts sprae on document. It can use a selector to adjust target container.
3
+ Microhydration for DOM tree with signals-based attributes.
508
4
 
509
5
  ```html
510
6
  <div id="counter" :scope="{count: 1}">
@@ -512,109 +8,19 @@ The `start` / `data-start` attribute automatically starts sprae on document. It
512
8
  <button :onclick="count++">Click me</button>
513
9
  </div>
514
10
 
515
- <script src="./sprae.js" data-start="#counter"></script>
516
- ```
517
-
518
- For manual start, remove `start` attribute:
519
-
520
- ```html
521
- <script src="./sprae.js"></script>
522
- <script>
523
- // watch & autoinit els
524
- sprae.start(document.body, { count: 1 });
525
-
526
- // OR init individual el (no watch)
527
- const state = sprae(document.getElementById('counter'), { count: 0 })
528
- </script>
529
- ```
530
-
531
- For more control use ESM:
532
-
533
- ```html
534
11
  <script type="module">
535
- import sprae from './sprae.js'
12
+ import sprae from '//unpkg.com/sprae?module'
536
13
 
537
- // init
538
14
  const state = sprae(document.getElementById('counter'), { count: 0 })
539
-
540
- // update state
541
15
  state.count++
542
16
  </script>
543
17
  ```
544
18
 
19
+ [`:text`](docs.md#text) [`:class`](docs.md#class) [`:style`](docs.md#style) [`:value`](docs.md#value) [`:<attr>`](docs.md#attr-) [`:if :else`](docs.md#if-else) [`:each`](docs.md#each) [`:scope`](docs.md#scope) [`:fx`](docs.md#fx) [`:ref`](docs.md#ref) [`:on<event>`](docs.md#onevent)
545
20
 
546
- ## JSX
547
-
548
- Sprae works with JSX via custom prefix (eg. `s-`).
549
- Useful to offload UI logic from server components in react / nextjs, instead of converting them to client components.
550
-
551
- ```jsx
552
- // app/page.jsx - server component
553
- export default function Page() {
554
- return <>
555
- <nav id="nav">
556
- <a href="/" s-class="location.pathname === '/' && 'active'">Home</a>
557
- <a href="/about" s-class="location.pathname === '/about' && 'active'">About</a>
558
- </nav>
559
- ...
560
- </>
561
- }
562
- ```
563
-
564
- ```jsx
565
- // layout.jsx
566
- import Script from 'next/script'
567
-
568
- export default function Layout({ children }) {
569
- return <>
570
- {children}
571
- <Script src="https://unpkg.com/sprae" data-prefix="s-" data-start />
572
- </>
573
- }
574
- ```
575
-
576
- ## Custom build
577
-
578
- Sprae build can be tweaked for project needs / size:
579
-
580
- ```js
581
- // sprae.custom.js
582
- import sprae, { directive, use } from 'sprae/core'
583
- import * as signals from '@preact/signals'
584
- import compile from 'subscript/justin'
585
-
586
- import _default from 'sprae/directive/default.js'
587
- import _if from 'sprae/directive/if.js'
588
- import _text from 'sprae/directive/text.js'
589
-
590
- use({
591
- // custom prefix, defaults to ':'
592
- prefix: 'data-sprae-',
593
-
594
- // use preact signals
595
- ...signals,
596
-
597
- // use safer compiler
598
- compile
599
- })
600
-
601
- // standard directives
602
- directive.if = _if;
603
- directive.text = _text;
604
- directive.default = _default;
605
-
606
- // custom directive :id="expression"
607
- directive.id = (el, state, expr) => {
608
- // ...init
609
- return newValue => {
610
- // ...update
611
- let nextValue = el.id = newValue
612
- return nextValue
613
- }
614
- }
615
-
616
- export default sprae;
617
- ```
21
+ [`.debounce`](docs.md#debounce-ms) [`.throttle`](docs.md#throttle-ms) [`.delay`](docs.md#tick) [`.once`](docs.md#once)<br>
22
+ [`.window`](docs.md#window-document-body-root-parent-outside-self) [`.document`](docs.md#window-document-body-root-parent-outside-self) [`.root`](docs.md#window-document-body-root-parent-outside-self) [`.body`](docs.md#window-document-body-root-parent-outside-self) [`.parent`](docs.md#window-document-body-root-parent-outside-self) [`.self`](docs.md#window-document-body-root-parent-outside-self) [`.outside`](docs.md#window-document-body-root-parent-outside-self)<br>
23
+ [`.passive`](docs.md#passive-captureevents-only) [`.capture`](docs.md#passive-captureevents-only) [`.prevent`](docs.md#prevent-stop-immediateevents-only) [`.stop`](docs.md#prevent-stop-immediateevents-only) [`.<key>`](docs.md#key-filters)
618
24
 
619
25
  <!--
620
26
  ## Micro
@@ -627,44 +33,15 @@ Micro sprae version is 2.5kb bundle with essentials:
627
33
  * no `:each`, `:if`, `:value`
628
34
  -->
629
35
 
630
- ## Hints
631
-
632
- * To prevent [FOUC](https://en.wikipedia.org/wiki/Flash_of_unstyled_content) add `<style>[\:each],[\:if],[\:else] {visibility: hidden}</style>`.
633
- * Attributes order matters, eg. `<li :each="el in els" :text="el.name"></li>` is not the same as `<li :text="el.name" :each="el in els"></li>`.
634
- * Invalid self-closing tags like `<a :text="item" />` cause error. Valid self-closing tags are: `li`, `p`, `dt`, `dd`, `option`, `tr`, `td`, `th`, `input`, `img`, `br`.
635
- * To destroy state and detach sprae handlers, call `element[Symbol.dispose]()`.
636
- * `this` is not used, to get element reference use `:ref="element => {...}"`.
637
- * `key` is not used, `:each` uses direct list mapping instead of DOM diffing.
638
- * Expressions can be async: `<div :text="await load()"></div>`
639
-
640
- <!--
641
- ## FAQ
642
-
643
- 1. Errors handling?
644
- 2. Typescript support?
645
- 3. Performance tips?
646
- -->
647
-
648
- ## Justification
649
-
650
- Modern frontend is like processed food.
651
- Frameworks come with endless tooling, tedious setups and configs, proprietary conventions, artificial abstractions and ecosystem lock-in. Progressive enhancement / graceful degradation is anachronism.
652
36
 
653
- Native [template-parts](https://github.com/github/template-parts) and [DCE](https://github.com/WICG/webcomponents/blob/gh-pages/proposals/Declarative-Custom-Elements-Strawman.md) give distant hope, but stuck with HTML quirks [1](https://github.com/github/template-parts/issues/24), [2](https://github.com/github/template-parts/issues/25), [3](https://shopify.github.io/liquid/tags/template/#raw).
37
+ ## why
654
38
 
655
- [Alpine](https://github.com/alpinejs/alpine) and [petite-vue](https://github.com/vuejs/petite-vue) offer PE / GD, but have excessive API (x-, @, $, etc.), tend to [self-encapsulate](https://github.com/alpinejs/alpine/discussions/3223), limit extensibility and disregard performance / size.
39
+ Simple practical reactivity without overhead.<br>
40
+ Perfect for SPA (static sites, prototypes, micro-frontends, lightweight UI).<br>
41
+ Inspired by [preact-signals](https://github.com/preactjs/signals), [alpine](https://github.com/alpinejs/alpine), [lodash](https://lodash.com) and others.<br> <!--[petite-vue](https://github.com/vuejs/petite-vue) and others. <!--[lucia](https://github.com/aidenybai/lucia), [nuejs](https://github.com/nuejs/nuejs), [hmpl](https://github.com/hmpl-language/hmpl), [unpoly](https://unpoly.com/up.link), [dagger](https://github.com/dagger8224/dagger.js)-->
42
+ Made with 🫰 for better DX.
43
+ <!-- – for those who tired of complexity. -->
656
44
 
657
- _Sprae_ holds open, safe, minimalistic philosophy:
658
-
659
- * One `:` prefix. Zero magic.
660
- * Valid HTML. Non-obtrusive.
661
- * Signals for reactivity. (preact-signals compatible)
662
- * Configurable signals, evaluator, directives, modifiers.
663
- * Build-free, ecosystem-agnostic.
664
- * Small, safe & fast.
665
- * 🫰 developers
666
-
667
- Perfect for small websites, static pages, prototypes, landings, SPA, PWA, JSX / SSR, micro-frontends, github pages, or anywhere where you need lightweight UI.
668
45
 
669
46
  <!--
670
47
  | | [AlpineJS](https://github.com/alpinejs/alpine) | [Petite-Vue](https://github.com/vuejs/petite-vue) | Sprae |
@@ -688,64 +65,13 @@ _Inline directives_ Yes
688
65
  -->
689
66
 
690
67
  <!--
691
- <details>
692
- <summary><strong>Benchmark</strong></summary>
693
-
694
- See [js-framework-benchmark](https://krausest.github.io/js-framework-benchmark/current.html#eyJmcmFtZXdvcmtzIjpbIm5vbi1rZXllZC9wZXRpdGUtdnVlIiwibm9uLWtleWVkL3NwcmFlIl0sImJlbmNobWFya3MiOlsiMDFfcnVuMWsiLCIwMl9yZXBsYWNlMWsiLCIwM191cGRhdGUxMHRoMWtfeDE2IiwiMDRfc2VsZWN0MWsiLCIwNV9zd2FwMWsiLCIwNl9yZW1vdmUtb25lLTFrIiwiMDdfY3JlYXRlMTBrIiwiMDhfY3JlYXRlMWstYWZ0ZXIxa194MiIsIjA5X2NsZWFyMWtfeDgiLCIyMV9yZWFkeS1tZW1vcnkiLCIyMl9ydW4tbWVtb3J5IiwiMjNfdXBkYXRlNS1tZW1vcnkiLCIyNV9ydW4tY2xlYXItbWVtb3J5IiwiMjZfcnVuLTEway1tZW1vcnkiLCIzMV9zdGFydHVwLWNpIiwiMzRfc3RhcnR1cC10b3RhbGJ5dGVzIiwiNDFfc2l6ZS11bmNvbXByZXNzZWQiLCI0Ml9zaXplLWNvbXByZXNzZWQiXSwiZGlzcGxheU1vZGUiOjF9).
695
-
696
- ![Benchmark](./bench.png)
697
- </details>
698
- -->
699
-
700
- <!--
701
- <details>
702
- <summary>How to run</summary>
703
-
704
- ```sh
705
- # prerequisite
706
- npm ci
707
- npm run install-server
708
- npm start
709
-
710
- # build
711
- cd frameworks/non-keyed/sprae
712
- npm ci
713
- npm run build-prod
714
-
715
- # bench
716
- [cd ../../../webdriver-ts
717
- npm ci
718
- npm run compile]
719
- npm run bench keyed/sprae
720
-
721
- # show results
722
- [cd ../webdriver-ts-results
723
- npm ci]
724
- cd ../webdriver-ts
725
- npm run results
726
- ```
727
- See results at localhost:8080/
728
- </details>
729
- -->
730
-
731
-
732
- ## Examples
68
+ ### Drops
733
69
 
734
70
  * ToDo MVC: [demo](https://dy.github.io/sprae/examples/todomvc), [code](https://github.com/dy/sprae/blob/main/examples/todomvc.html)
735
71
  * 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)
736
72
  * Wavearea: [demo](https://dy.github.io/wavearea?src=//cdn.freesound.org/previews/586/586281_2332564-lq.mp3), [code](https://github.com/dy/wavearea)
737
73
  * Carousel: [demo](https://rwdevelopment.github.io/sprae_js_carousel/), [code](https://github.com/RWDevelopment/sprae_js_carousel)
738
- * Tabs: [demo](https://rwdevelopment.github.io/sprae_js_tabs/), [code](https://github.com/RWDevelopment/sprae_js_tabs?tab=readme-ov-file)
74
+ * Tabs: [demo](https://rwdevelopment.github.io/sprae_js_tabs/), [code](https://github.com/RWDevelopment/sprae_js_tabs?tab=readme-ov-file)-->
739
75
  <!-- * Prostogreen [demo](https://web-being.org/prostogreen/), [code](https://github.com/web-being/prostogreen/) -->
740
76
 
741
- <!--
742
- ## See Also
743
-
744
- * [nadi](https://github.com/dy/nadi) - 101 signals. -->
745
-
746
- ## Refs
747
-
748
- [alpine](https://github.com/alpinejs/alpine), [lucia](https://github.com/aidenybai/lucia), [petite-vue](https://github.com/vuejs/petite-vue), [nuejs](https://github.com/nuejs/nuejs), [hmpl](https://github.com/hmpl-language/hmpl), [unpoly](https://unpoly.com/up.link), [dagger](https://github.com/dagger8224/dagger.js)
749
-
750
-
751
- <p align="center"><a href="https://github.com/krsnzd/license/">ॐ</a></p>
77
+ ### [![tests](https://github.com/dy/sprae/actions/workflows/node.js.yml/badge.svg)](https://github.com/dy/sprae/actions/workflows/node.js.yml) [![npm bundle size](https://img.shields.io/bundlephobia/minzip/sprae?color=white)](https://bundlephobia.com/package/sprae) [![npm](https://img.shields.io/npm/v/sprae?color=white)](https://www.npmjs.com/package/sprae) [![ॐ](https://img.shields.io/badge/MIT-%E0%A5%90-white)](https://krishnized.github.io/license)