sprae 0.1.0 → 2.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/.github/workflows/node.js.yml +30 -0
- package/package.json +13 -5
- package/plan.md +20 -0
- package/r&d.md +115 -4
- package/readme.md +140 -58
- package/sprae.js +709 -0
- package/sprae.min.js +1 -0
- package/src/core.js +30 -102
- package/src/directives/each.js +2 -5
- package/src/directives.js +235 -0
- package/src/index.js +12 -2
- package/test/index.html +6 -3
- package/test/test.js +242 -45
- package/src/directives/hidden.js +0 -14
- package/src/directives/if.js +0 -34
- package/src/directives/index.js +0 -5
- package/src/directives/text.js +0 -13
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
|
3
|
+
|
|
4
|
+
name: Tests
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [ "main" ]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [ "main" ]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
|
|
17
|
+
strategy:
|
|
18
|
+
matrix:
|
|
19
|
+
node-version: [18.x]
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v3
|
|
23
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
24
|
+
uses: actions/setup-node@v3
|
|
25
|
+
with:
|
|
26
|
+
node-version: ${{ matrix.node-version }}
|
|
27
|
+
cache: 'npm'
|
|
28
|
+
- run: npm ci
|
|
29
|
+
- run: npm run build --if-present
|
|
30
|
+
- run: npm test
|
package/package.json
CHANGED
|
@@ -1,25 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sprae",
|
|
3
3
|
"description": "Reactive directives with expressions for DOM microtemplating.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "2.0.0",
|
|
5
5
|
"main": "sprae.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"dependencies": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
8
|
+
"@preact/signals-core": "^1.2.2",
|
|
9
|
+
"element-props": "^2.3.0",
|
|
10
|
+
"primitive-pool": "^2.0.0",
|
|
11
|
+
"signal-struct": "^1.5.0",
|
|
10
12
|
"sube": "^2.2.1",
|
|
11
13
|
"swapdom": "^1.1.1"
|
|
12
14
|
},
|
|
13
15
|
"devDependencies": {
|
|
14
16
|
"@preact/signals": "^1.1.2",
|
|
17
|
+
"browser-env": "^3.3.0",
|
|
15
18
|
"es-module-shims": "^1.6.2",
|
|
19
|
+
"esbuild": "^0.15.14",
|
|
16
20
|
"hyperf": "^1.4.0",
|
|
21
|
+
"terser": "^5.15.1",
|
|
17
22
|
"tst": "^7.1.1",
|
|
23
|
+
"usignal": "^0.8.9",
|
|
18
24
|
"value-ref": "^2.1.0",
|
|
19
25
|
"wait-please": "^3.1.0"
|
|
20
26
|
},
|
|
21
27
|
"scripts": {
|
|
22
|
-
"test": "node test"
|
|
28
|
+
"test": "node -r browser-env/register test/test.js",
|
|
29
|
+
"build": "esbuild --bundle ./src/index.js --outfile=sprae.js --format=esm",
|
|
30
|
+
"min": "terser sprae.js -o sprae.min.js --module -c passes=3 -m"
|
|
23
31
|
},
|
|
24
32
|
"repository": {
|
|
25
33
|
"type": "git",
|
package/plan.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
* [x] finish directives
|
|
2
|
+
* [x] better list diffing
|
|
3
|
+
* [x] ordered directives init (:each + :if vs :if + :each) -> find out if really needed and which is faster
|
|
4
|
+
-> yes, needed and solve many init issues.
|
|
5
|
+
* [?] autoinit -> too much maintenance burden
|
|
6
|
+
* [x] node tests
|
|
7
|
+
* [x] better deps updating -> cumulative signal
|
|
8
|
+
* [x] combinations: :else :if
|
|
9
|
+
* [x] :each :if, :if :each
|
|
10
|
+
* [x] :each :each
|
|
11
|
+
* [x] :with must be able to write state value as well
|
|
12
|
+
* [x] docs: give example to each directive
|
|
13
|
+
* [x] initialize per-element: <x :each><y :if></y><x> - tree-dependent (:each comes first).
|
|
14
|
+
* [x] generalize common attributes :prop="xyz"
|
|
15
|
+
* [x] spread props
|
|
16
|
+
* [x] optimization: arrays with multiple elements can be slow on creation. Maybe signal-struct must ignore arrays.
|
|
17
|
+
-> yep: arrays are rarely changed as `a[i]=newItem` and regularly they're mapped.
|
|
18
|
+
* [x] expand to any subscribables: both as state vars
|
|
19
|
+
* [ ] optimization: replace element-props with direct (better) setters
|
|
20
|
+
* [ ] report usignal problem
|
package/r&d.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
## name
|
|
1
|
+
## [x] name -> sprae
|
|
2
2
|
|
|
3
3
|
* rasa
|
|
4
4
|
* dew
|
|
@@ -32,9 +32,14 @@
|
|
|
32
32
|
+ reference to sporae and similar assoication
|
|
33
33
|
+ simpler word
|
|
34
34
|
+ better assoc with hydration
|
|
35
|
+
+ spree
|
|
36
|
+
+ spread
|
|
37
|
+
* sprinkle
|
|
38
|
+
+ better meaning
|
|
39
|
+
- stands out less than sprae
|
|
40
|
+
* aerosol
|
|
35
41
|
|
|
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.
|
|
42
|
+
## [x] :attr, :data, :id, :class, :style, :on, :aria - do we enforce JS syntax or support unscoped expression? -> Use JS convention, too many use-cases.
|
|
38
43
|
|
|
39
44
|
1. JS object
|
|
40
45
|
+ JS object can directly set value as `:props="obj"`
|
|
@@ -48,4 +53,110 @@
|
|
|
48
53
|
+ We anyways introduce custom-ish expression in `:each="item in items"`
|
|
49
54
|
- Vue introduces simple-ish parsing for that
|
|
50
55
|
+ 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
|
|
56
|
+
- Custom expressions are confusing for style: `:style="a:1, b:2, c:3"` - very similar to direct style string
|
|
57
|
+
|
|
58
|
+
## [x] Attribute directive: `:={a:1}` vs `:attr={a:1}` vs `:prop={a:1}` -> hold on for now
|
|
59
|
+
|
|
60
|
+
+ `:=obj` reminds pascal assignment operator, which is cool
|
|
61
|
+
+ `:={a:1,b:2}` is natural convention from vue/alpine as - all props in object are assigned as `:{attr}`
|
|
62
|
+
- We can use `:="{data}"` fro sprae autoinit, since scope has confusing name: `:scope={}`, `:sprae={}`, `:with={}`
|
|
63
|
+
-> let's use :prop= for now, since `:={}` can have multiple interpretations
|
|
64
|
+
|
|
65
|
+
## [x] Scopes mechanism: prototype inheritance chain vs multiple `with` wrappers -> init subtrees, no need for explicit mechanism
|
|
66
|
+
|
|
67
|
+
- prototype inheritance chain causes deps update difficulties
|
|
68
|
+
- prototype chain is messy-ish
|
|
69
|
+
- prototype chain is a bit more difficult to provide multiple parent scopes
|
|
70
|
+
- prototype state object is inheritance mess - can be super-hard to analyze
|
|
71
|
+
~ `with(a) with(b) with(c)` is the same as `with(a)` with prototype inheritance in terms of access.
|
|
72
|
+
- `with` chain allows runtime update of scopes, eg. child scope was updated to something new.
|
|
73
|
+
- `prototype` chain is fixed from the time of init.
|
|
74
|
+
- `prototype` chain hails to unidentified root scope and inherits from that. Maybe we should clarify scopes inhertiance and first implement reactive store (see next item).
|
|
75
|
+
|
|
76
|
+
? what if we avoid scope inheritance mechanism (what's the real use for it?) and instead just make reactive store object, so that :with directive subscribes to any input props, but "shadows" subtree?
|
|
77
|
+
? are there uses for inheritance
|
|
78
|
+
|
|
79
|
+
? Do we need scopes at all? Only for the purpose of autoinit?
|
|
80
|
+
- it seems scopes can introduce more confusion and mess in templates: indicating full paths is more beneficial
|
|
81
|
+
+ unless we introduce proper ":with="item.x as x""
|
|
82
|
+
+ prototype chain is a single object:
|
|
83
|
+
+ meaning updators receive one actual-for-element scope instance
|
|
84
|
+
+ that makes external API easier
|
|
85
|
+
+ that allows handling store via single reactive object
|
|
86
|
+
|
|
87
|
+
-> possibly we have to just subscribe via mechanism of signals-like deps, and :with just initializes subtree with extended object
|
|
88
|
+
|
|
89
|
+
## [x] Should we inherit values from `init` in `sprae(el, init)`, instead of creating a snapshot of reactive values in `init`? -> nah, nice idea but too little use. Better create signals struct.
|
|
90
|
+
|
|
91
|
+
+ it allows passing any arbitrary scope to initialize from.
|
|
92
|
+
- it can make hard finding reactive sources...
|
|
93
|
+
+ it is sort-of neat pattern: object parent updates its particular state: it can also have observable method making object a store
|
|
94
|
+
-> can be delegated to a separate functionality - init just gets converted to reactive store
|
|
95
|
+
+ it sort-of makes `init` directly a scope (a parent of scope), which is more natural-ish rather than 2 independent entities
|
|
96
|
+
+ can pass both observables and direct state anywhere, eg. init child components from it
|
|
97
|
+
-> worthy of a separate library, signal-struct?
|
|
98
|
+
|
|
99
|
+
## [x] Per-directive initialize vs per-element initialize -> directives can immediately initialize rest on elements
|
|
100
|
+
|
|
101
|
+
+ Per-directive is very simple and trivial approach
|
|
102
|
+
- Per-directive doesn't read attributes order and init directives independently
|
|
103
|
+
~ Practically linear in-order init doesn't make much service either here
|
|
104
|
+
- Per-directive is a bit hard to deal with scopes
|
|
105
|
+
-> gotta benchmark, vs just walker.
|
|
106
|
+
-> seems unavoidable to combine :if within :each, since :each should remove elements and init on find only
|
|
107
|
+
|
|
108
|
+
## [x] avoid updating unchanged directives if values don't affect them -> signal struct
|
|
109
|
+
|
|
110
|
+
? what if we use preact/signals to subscribe only to required props?
|
|
111
|
+
-> parseExpr is going to need to be handled by core.js (not directives), and detect & subscribe to dependencies itself
|
|
112
|
+
-> so that directive updator gets invoked only when any of expr dependencies change
|
|
113
|
+
-> gotta solve via signal-struct
|
|
114
|
+
|
|
115
|
+
## [x] Replace :else-if with :else :if -> not ideal technically, but done
|
|
116
|
+
|
|
117
|
+
+ `:else :if=""` is meaningful expansion of both directives
|
|
118
|
+
+ `:else :if` is coming from JS
|
|
119
|
+
+ `:else :if` doesn't throw error in JSDOM tests
|
|
120
|
+
- less resemblance with vue: who cares, we already remotely resemble it
|
|
121
|
+
|
|
122
|
+
## [x] Keep className marker of directive or not?
|
|
123
|
+
|
|
124
|
+
-> No: first, there's :class directive changing the class itself;
|
|
125
|
+
-> Second, there's easier way to just "evaporate" directive = not initialize twice;
|
|
126
|
+
-> Third, there's too much pollution with class markers
|
|
127
|
+
|
|
128
|
+
## [ ] Plugins
|
|
129
|
+
|
|
130
|
+
* init/connected/mount, unmount/disconnected?
|
|
131
|
+
* init and connected are different apparently
|
|
132
|
+
* :html?
|
|
133
|
+
* :effect?
|
|
134
|
+
* @sprae/tailwind: `<x :tw="mt-1 mx-2"></x>` - separate tailwind utility classes from main ones; allow conditional setters.
|
|
135
|
+
* @sprae/item: `<x :item="{type:a, scope:b}"` – provide microdata
|
|
136
|
+
* @sprae/hcodes: `<x :h=""` – provide microformats
|
|
137
|
+
|
|
138
|
+
## [x] Write any-attributes via :<prop>?
|
|
139
|
+
|
|
140
|
+
+ Since we support attr walking, maybe instead of :on and :prop just allow any attributes?
|
|
141
|
+
+ that would allow event and attr modifiers...
|
|
142
|
+
+ that would allow somewhat alpine/vue-compatible code
|
|
143
|
+
+ makes sense for `:="{}"` spread
|
|
144
|
+
+ makes place for other specific directives `:init=""` etc
|
|
145
|
+
|
|
146
|
+
## [x] :value is confusing: <option> also uses that.
|
|
147
|
+
|
|
148
|
+
? :model="value"
|
|
149
|
+
+ v-model, x-model
|
|
150
|
+
- confusing
|
|
151
|
+
? :in="text"
|
|
152
|
+
? :input="text"
|
|
153
|
+
? :bind="value"
|
|
154
|
+
+ more accurate logically
|
|
155
|
+
- conflicts with existing naming (bind is used for attrs)
|
|
156
|
+
- conflict if used along with `:value="x" :bind="y"`
|
|
157
|
+
? :value="value" :onchange="e=>value=e.target.value"
|
|
158
|
+
+ more apparent and explicit
|
|
159
|
+
+ less mental load, "model" is too heavy term
|
|
160
|
+
+ overhead is minimal
|
|
161
|
+
+ react-like
|
|
162
|
+
+ it has better control over serialization
|
package/readme.md
CHANGED
|
@@ -1,95 +1,175 @@
|
|
|
1
|
-
# ∴ spræ
|
|
1
|
+
# ∴ spræ [](https://github.com/dy/sprae/actions/workflows/node.js.yml) [](https://bundlephobia.com/result?p=sprae)
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Soft DOM hydration with reactive microdirectives
|
|
4
4
|
|
|
5
|
-
A lightweight essential alternative to [alpine](https://github.com/alpinejs/alpine), [petite-vue](https://github.com/vuejs/petite-vue)
|
|
5
|
+
A lightweight essential alternative to [alpine](https://github.com/alpinejs/alpine), [petite-vue](https://github.com/vuejs/petite-vue), [templize](https://github.com/dy/templize) or JSX with better ergonomics[*](#justification).
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
## Usage
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Spraedrops (directives) are attributes starting with `:` that contain regular JS expressions:
|
|
11
11
|
|
|
12
12
|
```html
|
|
13
|
-
<div id="
|
|
13
|
+
<div id="container" :if="user">
|
|
14
14
|
Logged in as <span :text="user.displayName">Guest.</span>
|
|
15
15
|
</div>
|
|
16
16
|
|
|
17
17
|
<script type="module">
|
|
18
|
-
import
|
|
18
|
+
import sprae from 'sprae';
|
|
19
19
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
state.user.displayName = 'dy' // update value
|
|
24
|
-
update({user: {displayName: 'dy'}}) // alternatively
|
|
20
|
+
const state = sprae(container, { user: { displayName: 'Dmitry Ivanov' } });
|
|
21
|
+
state.user.displayName = 'dy'; // automatically updates DOM
|
|
25
22
|
</script>
|
|
26
23
|
```
|
|
27
24
|
|
|
28
|
-
* `
|
|
29
|
-
* `state` is
|
|
30
|
-
* `update` can be used for bulk-updating multiple props.
|
|
31
|
-
* `data` is the initial state to render the template. It can include reactive values, see [reactivity](#reactivity).
|
|
25
|
+
* `sprae` initializes directives with data (can include [signals](https://github.com/preactjs/signals) or [reactive values](https://github.com/dy/sube)). Once initialized, they immediately evaporate.
|
|
26
|
+
* `state` is object reflecting values, changing any of its props rerenders directives.
|
|
32
27
|
|
|
28
|
+
<!--
|
|
33
29
|
<details>
|
|
34
|
-
<summary><
|
|
30
|
+
<summary><strong>Autoinit</strong></summary>
|
|
35
31
|
|
|
36
|
-
Sprae can be used without build step or JS, autoinitializing
|
|
32
|
+
Sprae can be used without build step or JS, autoinitializing document:
|
|
37
33
|
|
|
38
34
|
```html
|
|
39
|
-
<script src="./sprae.js" defer init></script>
|
|
35
|
+
<script src="./sprae.js" defer init="{ count: 0 }"></script>
|
|
40
36
|
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
<button :on="{ click: e => count++ }">inc</button>
|
|
44
|
-
</div>
|
|
37
|
+
<span :text="count">
|
|
38
|
+
<button :on="{ click: e => count++ }">inc</button>
|
|
45
39
|
```
|
|
46
40
|
|
|
47
|
-
* `:
|
|
48
|
-
* `init` attribute tells sprae to automatically initialize
|
|
41
|
+
* `:with` defines data for regions of the tree to autoinit sprae on.
|
|
42
|
+
* `init` attribute tells sprae to automatically initialize document.
|
|
49
43
|
|
|
50
44
|
</details>
|
|
51
|
-
|
|
45
|
+
-->
|
|
52
46
|
|
|
53
47
|
## Directives
|
|
54
48
|
|
|
55
|
-
|
|
56
|
-
* [x] `:each="item, i? in list"`,`:each="i in number"` - create multiple instances of element by mapping list or number.
|
|
57
|
-
* [x] `:text="value"` - set text content of an element.
|
|
58
|
-
* [ ] `:value="value"` – bind value to input or textarea.
|
|
59
|
-
* [ ]`:id`, `:name`, `:for`, `:type`, `:hidden`, `:disabled`, `:href`, `:src` – common attributes setters.
|
|
60
|
-
* [ ] `:class="[ foo, 'bar' ]"` – set element class from an array, object or a string.
|
|
61
|
-
* [ ] `:style="{ top:1, position:'absolute' }"` – set element style from a string or an object.
|
|
62
|
-
* [ ] `:prop="{ alt:'foo', title:'bar' }"` – set any attribute / property.
|
|
63
|
-
* [ ] `:on="{ click:e=>{}, touch:e=>{} }"` – add event listeners.
|
|
64
|
-
* [ ] `:data="{ foo:1, bar:2 }"` – set [data-*](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*) attributes.
|
|
65
|
-
* [ ] `:aria="{ role:'progressbar' }"` – set [aria-role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) attributes.
|
|
66
|
-
* [ ] `:item="{ id: 1 }"` – set [item*](https://developer.mozilla.org/en-US/docs/Web/HTML/Microdata) microdata attribute.
|
|
67
|
-
* [ ] `:scope="data"` – autoinit subtree.
|
|
49
|
+
#### `:if="condition"`, `:else`
|
|
68
50
|
|
|
69
|
-
|
|
70
|
-
|
|
51
|
+
Control flow of elements.
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
<span :if="foo">foo</span>
|
|
55
|
+
<span :else :if="bar">bar</span>
|
|
56
|
+
<span :else>baz</span>
|
|
57
|
+
```
|
|
71
58
|
|
|
72
|
-
|
|
59
|
+
#### `:each="item, index in items"`
|
|
73
60
|
|
|
74
|
-
|
|
75
|
-
import init, { directive } from 'sprae';
|
|
61
|
+
Multiply element. `index` value starts from 1.
|
|
76
62
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
63
|
+
```html
|
|
64
|
+
<ul :with="{items: ['a','b','c']}">
|
|
65
|
+
<li :each="item in items" :text="item">Untitled</li>
|
|
66
|
+
</ul>
|
|
67
|
+
|
|
68
|
+
<!-- Cases -->
|
|
69
|
+
<li :each="item, idx in list" />
|
|
70
|
+
<li :each="val, key in obj" />
|
|
71
|
+
<li :each="idx in 10" />
|
|
72
|
+
|
|
73
|
+
<!-- Loop by condition -->
|
|
74
|
+
<li :if="items" :each="item in items" :text="item" />
|
|
75
|
+
<li :else>Empty list</li>
|
|
85
76
|
```
|
|
86
77
|
|
|
87
|
-
|
|
78
|
+
#### `:text="value"`
|
|
79
|
+
|
|
80
|
+
Set text content of an element. Default text can be used as fallback:
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
Welcome, <span :text="user.name">Guest</span>.
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### `:class="value"`
|
|
87
|
+
|
|
88
|
+
Set class value from either string, array or object.
|
|
89
|
+
|
|
90
|
+
```html
|
|
91
|
+
<div :class="`foo ${bar}`"></div>
|
|
92
|
+
<div :class="['foo', 'bar']"></div>
|
|
93
|
+
<div :class="{foo: true, bar: false}"></div>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### `:style="value"`
|
|
97
|
+
|
|
98
|
+
Set style value from object or a string.
|
|
99
|
+
|
|
100
|
+
```html
|
|
101
|
+
<div :style="foo: bar"></div>
|
|
102
|
+
<div :style="{foo: 'bar'}"></div>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
<!--
|
|
106
|
+
#### `:value="value"`
|
|
107
|
+
|
|
108
|
+
Bind (2-way) value to input, textarea or select.
|
|
109
|
+
|
|
110
|
+
```html
|
|
111
|
+
<input :with="{text: ''}" :value="text" />
|
|
112
|
+
<textarea :with="{text: ''}" :value="text" />
|
|
88
113
|
|
|
114
|
+
<select :with="{selected: 0}" :value="selected">
|
|
115
|
+
<option :each="i in 5" :value="i" :text="i"></option>
|
|
116
|
+
</select>
|
|
117
|
+
```
|
|
118
|
+
-->
|
|
119
|
+
|
|
120
|
+
#### `:<prop>="value"`, `:="props"`
|
|
121
|
+
|
|
122
|
+
Set any other prop or props value.
|
|
123
|
+
|
|
124
|
+
```html
|
|
125
|
+
<label :for="name" :text="name" />
|
|
126
|
+
<input :="{ id: name, name, type, value }" :onchange="e => value=e.target.value" />
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### `:on="events"`
|
|
89
130
|
|
|
90
|
-
|
|
131
|
+
Add event listeners.
|
|
91
132
|
|
|
92
|
-
|
|
133
|
+
```html
|
|
134
|
+
<button :on="{ click: handler, touch: handler }">Submit</button>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### `:data="values"`
|
|
138
|
+
|
|
139
|
+
Set [data-*](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*) attributes. CamelCase is converted to dash-case.
|
|
140
|
+
|
|
141
|
+
```html
|
|
142
|
+
<input :data="{foo: 1, barBaz: true}" />
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### `:aria="values"`
|
|
146
|
+
|
|
147
|
+
Set [aria-role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) attributes. Boolean values are stringified.
|
|
148
|
+
|
|
149
|
+
```html
|
|
150
|
+
<input type="text" id="jokes" role="combobox" :aria="{
|
|
151
|
+
controls: 'joketypes',
|
|
152
|
+
autocomplete: 'list',
|
|
153
|
+
expanded: false,
|
|
154
|
+
activeOption: 'item1',
|
|
155
|
+
activedescendant: ''
|
|
156
|
+
}" />
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### `:with="data"`
|
|
160
|
+
|
|
161
|
+
Set data for a subtree fragment scope.
|
|
162
|
+
|
|
163
|
+
```html
|
|
164
|
+
<x :with="{ foo: 'bar' }">
|
|
165
|
+
<y :with="{ baz: 'qux' }" :text="foo + baz"></y>
|
|
166
|
+
</x>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
<!--
|
|
170
|
+
### Reactive values
|
|
171
|
+
|
|
172
|
+
Directive expressions are natively reactive, ie. data may contain any async/reactive values, such as:
|
|
93
173
|
|
|
94
174
|
* _Promise_ / _Thenable_
|
|
95
175
|
* _Observable_ / _Subject_ / _Subscribable_
|
|
@@ -105,14 +185,14 @@ Update happens when any value changes:
|
|
|
105
185
|
<div id="done" :text="loading ? 'loading' : result">...</div>
|
|
106
186
|
|
|
107
187
|
<script>
|
|
108
|
-
import
|
|
188
|
+
import sprae from 'sprae';
|
|
109
189
|
import { signals } from '@preact/signals';
|
|
110
190
|
|
|
111
191
|
// <div id="done">...</div>
|
|
112
192
|
|
|
113
193
|
const loading = signal(true), result = signal(false);
|
|
114
194
|
|
|
115
|
-
|
|
195
|
+
sprae(done, { loading, result })
|
|
116
196
|
|
|
117
197
|
// <div id="done">loading</div>
|
|
118
198
|
|
|
@@ -123,14 +203,15 @@ Update happens when any value changes:
|
|
|
123
203
|
</script>
|
|
124
204
|
```
|
|
125
205
|
|
|
126
|
-
|
|
206
|
+
Internally directives trigger updates only for used properties change. They subscribe in weak fashion and get disposed when element is disposed.
|
|
207
|
+
-->
|
|
127
208
|
|
|
128
209
|
|
|
129
210
|
## Justification
|
|
130
211
|
|
|
131
212
|
* [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.
|
|
132
213
|
* [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. Also they tend to [self-encapsulate](https://github.com/alpinejs/alpine/discussions/3223), making interop hard.
|
|
133
|
-
* [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
|
|
214
|
+
* [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 all HTML to JS is an extreme: SPAs are not organic for HTML.
|
|
134
215
|
|
|
135
216
|
_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.
|
|
136
217
|
|
|
@@ -141,4 +222,5 @@ _Sprae_ takes elegant syntax convention of _alpine_ and method of _templize_ to
|
|
|
141
222
|
* It doesn'y introduce syntax scatter.
|
|
142
223
|
* It supports simple expressions with exposed reactive data types.
|
|
143
224
|
|
|
225
|
+
|
|
144
226
|
<p align="center"><a href="https://github.com/krsnzd/license/">🕉</a></p>
|