sprae 2.0.0 → 2.1.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.
@@ -0,0 +1,100 @@
1
+ <!doctype html>
2
+ <html lang="en" data-framework="sprae">
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <title>Sprae • TodoMVC</title>
8
+ <link rel="stylesheet" href="https://unpkg.com/todomvc-common/base.css">
9
+ <link rel="stylesheet" href="https://unpkg.com/todomvc-app-css/index.css">
10
+ <style>[\:each]{display: none;}</style>
11
+ </head>
12
+
13
+ <body>
14
+ <section class="todoapp">
15
+ <header class="header">
16
+ <h1>todos</h1>
17
+ <input class="new-todo" placeholder="What needs to be done?" autofocus :onkeypress="e => {
18
+ if (e.key !== 'Enter') return
19
+ save(todos = [...todos, { text: e.target.value, done: false}])
20
+ e.target.value = ''
21
+ }">
22
+ </header>
23
+ <section class="main">
24
+ <input id="toggle-all" class="toggle-all" type="checkbox" :onclick="e => {
25
+ const all = todos.every(item => item.done)
26
+ save(todos = todos.map(item => (item.done = !all, item)))
27
+ }">
28
+ <label for="toggle-all">Mark all as complete</label>
29
+ <ul class="todo-list">
30
+ <li :ref="li" :each="item in todos"
31
+ :class="{completed: item.done}"
32
+ :hidden="hash === '#/active' ? item.done : hash === '#/completed' ? !item.done : false"
33
+ :ondblclick="e => {
34
+ li.querySelector('.edit').focus()
35
+ li.classList.add('editing')
36
+ }">
37
+ <div class="view">
38
+ <input class="toggle" type="checkbox"
39
+ :checked="item.done"
40
+ :onchange="e => (item.done = !item.done, save(todos))"
41
+ />
42
+ <label :text="item.text"></label>
43
+ <button class="destroy" :onclick="e => save(todos = todos.filter(i => i !== item))"></button>
44
+ </div>
45
+ <input class=edit
46
+ :value="item.text"
47
+ :on="{
48
+ input(e) { item.text = e.target.value; save(todos) },
49
+ blur(e) { li.classList.remove('editing'); },
50
+ keypress(e) { e.key === 'Enter' ? e.target.blur() : null },
51
+ }"
52
+ />
53
+ </li>
54
+ </ul>
55
+ </section>
56
+ <footer class="footer">
57
+ <span class="todo-count" :with="{count: todos.filter(item => !item.done).length}">
58
+ <strong :text="count">#</strong> <span :text="plur('item', count)">items</span> left
59
+ </span>
60
+ <ul class="filters">
61
+ <li :each="label, key in {'#/': 'All', '#/active': 'Active', '#/completed': 'Completed'}">
62
+ <a :class="{selected: hash===key}" :href="key" :text="label"></a>
63
+ </li>
64
+ </ul>
65
+ <button class="clear-completed"
66
+ :hidden="todos.every(item => !item.done)"
67
+ :onclick="e => save(todos = todos.filter(item => !item.done ? true : false))">
68
+ Clear completed
69
+ </button>
70
+ </footer>
71
+ </section>
72
+ <footer class="info">
73
+ <p>Double-click to edit a todo</p>
74
+ <p>Created by <a href="https://github.com/dy">dy</a></p>
75
+ <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
76
+ </footer>
77
+ <script src="https://unpkg.com/todomvc-common/base.js"></script>
78
+ <script type="importmap"> {
79
+ "imports": {
80
+ "sprae": "../../sprae.js",
81
+ "plur": "https://cdn.skypack.dev/plur"
82
+ }
83
+ }
84
+ </script>
85
+ <script type="module">
86
+ import sprae from 'sprae'
87
+ import plur from 'plur'
88
+
89
+ let state = sprae(document.body, {
90
+ plur,
91
+ todos: JSON.parse(localStorage.getItem('todomvc.items') || '[]'),
92
+ hash: window.location.hash || '#/',
93
+ save: localStorage.setItem('todomvc.items', JSON.stringify(items))
94
+ })
95
+
96
+ // hash source
97
+ window.addEventListener('hashchange', e => state.hash = window.location.hash)
98
+ </script>
99
+ </body>
100
+ </html>
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "sprae",
3
3
  "description": "Reactive directives with expressions for DOM microtemplating.",
4
- "version": "2.0.0",
4
+ "version": "2.1.0",
5
5
  "main": "sprae.js",
6
6
  "type": "module",
7
7
  "dependencies": {
8
8
  "@preact/signals-core": "^1.2.2",
9
9
  "element-props": "^2.3.0",
10
10
  "primitive-pool": "^2.0.0",
11
- "signal-struct": "^1.5.0",
11
+ "signal-struct": "^1.5.1",
12
12
  "sube": "^2.2.1",
13
13
  "swapdom": "^1.1.1"
14
14
  },
@@ -27,6 +27,7 @@
27
27
  "scripts": {
28
28
  "test": "node -r browser-env/register test/test.js",
29
29
  "build": "esbuild --bundle ./src/index.js --outfile=sprae.js --format=esm",
30
+ "watch": "esbuild --bundle ./src/index.js --outfile=sprae.js --format=esm --sourcemap --watch",
30
31
  "min": "terser sprae.js -o sprae.min.js --module -c passes=3 -m"
31
32
  },
32
33
  "repository": {
package/plan.md CHANGED
@@ -16,5 +16,13 @@
16
16
  * [x] optimization: arrays with multiple elements can be slow on creation. Maybe signal-struct must ignore arrays.
17
17
  -> yep: arrays are rarely changed as `a[i]=newItem` and regularly they're mapped.
18
18
  * [x] expand to any subscribables: both as state vars
19
+ * [x] :ref
20
+ * [x] :ref + :each
19
21
  * [ ] optimization: replace element-props with direct (better) setters
20
- * [ ] report usignal problem
22
+ * [ ] report usignal problem
23
+ * [ ] :onconnect, :ondisconnect
24
+ * [ ] frameworks benchmark
25
+ * [ ] examples
26
+ * [ ] todomvc
27
+ * [ ] Sandbox expressions: no global, no "scope" object name, no "arguments"
28
+ * [ ] `this` in expressions must refer to current element always
package/r&d.md CHANGED
@@ -125,17 +125,34 @@
125
125
  -> Second, there's easier way to just "evaporate" directive = not initialize twice;
126
126
  -> Third, there's too much pollution with class markers
127
127
 
128
+ ## [ ] :html?
129
+
130
+ - introduces malign hole of including sprae inside of html
131
+
132
+ ## [x] :fx? -> nah, already works. Just return `null` in any attr, that's it.
133
+
134
+ * let's wait for use-case
135
+ - doesn't necessarily useful, since any directive is already an effect
136
+ + works already out of box, just creates `fx` attribute if value is returned
137
+
138
+ ## [x] :init? -> same as fx.
139
+
140
+ * waiting for use-case
141
+
142
+ ## [ ] :key.enter?
143
+
144
+ - opens gateway to generic modifiers
145
+ - introduces a whole mental layer to learn, including combinations of modifiers all around.
146
+
147
+ ## [ ] :onconnected/disconnected?
148
+
128
149
  ## [ ] Plugins
129
150
 
130
- * init/connected/mount, unmount/disconnected?
131
- * init and connected are different apparently
132
- * :html?
133
- * :effect?
134
151
  * @sprae/tailwind: `<x :tw="mt-1 mx-2"></x>` - separate tailwind utility classes from main ones; allow conditional setters.
135
152
  * @sprae/item: `<x :item="{type:a, scope:b}"` – provide microdata
136
153
  * @sprae/hcodes: `<x :h=""` – provide microformats
137
154
 
138
- ## [x] Write any-attributes via :<prop>?
155
+ ## [x] Write any-attributes via :<prop>? -> yep
139
156
 
140
157
  + Since we support attr walking, maybe instead of :on and :prop just allow any attributes?
141
158
  + that would allow event and attr modifiers...
@@ -143,7 +160,7 @@
143
160
  + makes sense for `:="{}"` spread
144
161
  + makes place for other specific directives `:init=""` etc
145
162
 
146
- ## [x] :value is confusing: <option> also uses that.
163
+ ## [x] :value is confusing: <option> also uses that. -> let's skip for now: onchange is not a big deal
147
164
 
148
165
  ? :model="value"
149
166
  + v-model, x-model
@@ -154,9 +171,36 @@
154
171
  + more accurate logically
155
172
  - conflicts with existing naming (bind is used for attrs)
156
173
  - conflict if used along with `:value="x" :bind="y"`
157
- ? :value="value" :onchange="e=>value=e.target.value"
174
+ -> :value="value" :onchange="e=>value=e.target.value"
158
175
  + more apparent and explicit
159
176
  + less mental load, "model" is too heavy term
160
177
  + overhead is minimal
161
178
  + react-like
162
179
  + it has better control over serialization
180
+
181
+ ## [ ] Sandbox?
182
+
183
+ 1. Use subscript?
184
+ + solves access to any internal signals on syntactic level
185
+ + can tentatively be faster than signal-struct
186
+ + could tentatively get rid of struct and just use signals as input
187
+ ~ Yep, it's a bit weird template converts data into some reactive state. Just expose an update method instead and current state like useState hook. This way you can avoid exposing signal-specific functions.
188
+ + Provides precisely controlled sandbox
189
+ - Some limited lang opportunities
190
+ - need to match many syntax quirks, can be tedious
191
+ - Somewhat heavy to bundle
192
+ + Scope is easier to provide: no need for signal proxy essentially
193
+ + Can detect access errors in advance
194
+ + Syntax-level access to signals can be inavoidable: external signals still "leak in" (via arrays or etc.).
195
+ + Updating simple objects should also rerender the template parts, not just signals.
196
+ + Deps can be analyzed / implemented without signals
197
+ - Screwed up debugging / stacktrace (unless errored)
198
+ + that "unlimits" returned struct, so that any property can be added/deleted.
199
+
200
+ ## [ ] :onclick="direct code" ?
201
+
202
+ + compatible with direct `onclick=...`
203
+ + no need for arrow/regular functions syntax in templates
204
+ - still need that syntax for filters, maps etc
205
+ + can be made async by default
206
+ - illicit `event` object
package/readme.md CHANGED
@@ -1,13 +1,13 @@
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) [![size](https://img.shields.io/bundlephobia/minzip/sprae?label=size)](https://bundlephobia.com/result?p=sprae)
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) [![size](https://img.shields.io/bundlephobia/minzip/sprae?label=size)](https://bundlephobia.com/result?p=sprae) [![npm](https://img.shields.io/npm/v/sprae?color=orange)](https://npmjs.org/sprae)
2
2
 
3
- > Soft DOM hydration with reactive microdirectives
3
+ > DOM microhydration with `:` attributes
4
4
 
5
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
- Spraedrops (directives) are attributes starting with `:` that contain regular JS expressions:
10
+ Sprae defines attributes starting with `:` as directives:
11
11
 
12
12
  ```html
13
13
  <div id="container" :if="user">
@@ -22,8 +22,8 @@ Spraedrops (directives) are attributes starting with `:` that contain regular JS
22
22
  </script>
23
23
  ```
24
24
 
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.
25
+ * `sprae` initializes subtree with data and immediately evaporates `:` attrs.
26
+ * `state` is object reflecting current values, changing any of its props rerenders subtree.
27
27
 
28
28
  <!--
29
29
  <details>
@@ -44,7 +44,7 @@ Sprae can be used without build step or JS, autoinitializing document:
44
44
  </details>
45
45
  -->
46
46
 
47
- ## Directives
47
+ ## Attributes
48
48
 
49
49
  #### `:if="condition"`, `:else`
50
50
 
@@ -68,11 +68,14 @@ Multiply element. `index` value starts from 1.
68
68
  <!-- Cases -->
69
69
  <li :each="item, idx in list" />
70
70
  <li :each="val, key in obj" />
71
- <li :each="idx in 10" />
71
+ <li :each="idx, idx0 in number" />
72
72
 
73
73
  <!-- Loop by condition -->
74
74
  <li :if="items" :each="item in items" :text="item" />
75
75
  <li :else>Empty list</li>
76
+
77
+ <!-- To avoid FOUC -->
78
+ <style>[:each]{visibility: hidden}</style>
76
79
  ```
77
80
 
78
81
  #### `:text="value"`
@@ -166,9 +169,30 @@ Set data for a subtree fragment scope.
166
169
  </x>
167
170
  ```
168
171
 
169
- <!--
170
- ### Reactive values
172
+ #### `:ref="id"`
173
+
174
+ Expose element to a subtree fragment with the `id`.
175
+
176
+ ```html
177
+ <li :ref="item">
178
+ <input
179
+ :onfocus="e=> item.classList.add('editing')"
180
+ :onblur="e => item.classList.remove('editing')"
181
+ />
182
+ </li>
183
+ ```
184
+
171
185
 
186
+ ### Reactivity
187
+
188
+ _Sprae_ is built on top of [_@preact/signals_](https://ghub.io/@preact/signals). That gives:
189
+
190
+ * Expressions don't require explicit access to `.value` (see [signal-struct](https://github.com/dy/signal-struct))
191
+ * Expressions support any reactive values in data (see [sube](https://github.com/dy/sube))
192
+ * Updates happen minimally only when used values update
193
+ * Subscription is weak and get disposed when element is disposed.
194
+
195
+ <!--
172
196
  Directive expressions are natively reactive, ie. data may contain any async/reactive values, such as:
173
197
 
174
198
  * _Promise_ / _Thenable_
@@ -180,6 +204,7 @@ Directive expressions are natively reactive, ie. data may contain any async/reac
180
204
  This way, for example, _@preact/signals_ or _rxjs_ can be connected directly bypassing subscription or reading value.
181
205
 
182
206
  Update happens when any value changes:
207
+ -->
183
208
 
184
209
  ```html
185
210
  <div id="done" :text="loading ? 'loading' : result">...</div>
@@ -203,9 +228,6 @@ Update happens when any value changes:
203
228
  </script>
204
229
  ```
205
230
 
206
- Internally directives trigger updates only for used properties change. They subscribe in weak fashion and get disposed when element is disposed.
207
- -->
208
-
209
231
 
210
232
  ## Justification
211
233
 
@@ -216,11 +238,11 @@ Internally directives trigger updates only for used properties change. They subs
216
238
  _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.
217
239
 
218
240
  * It doesn't break static html markup.
219
- * It doesn't intrude native syntax space.
220
241
  * It falls back to element content if uninitialized.
221
- * It provides means for island hydration.
222
- * It doesn'y introduce syntax scatter.
223
- * It supports simple expressions with exposed reactive data types.
242
+ * It doesn't enforce SPA neither JSX.
243
+ * It enables island hydration.
244
+ * It introduces minimal syntax space as `:` convention.
245
+ * Expressions are built on preact/signal so naturally reactive and incur minimal updates.
224
246
 
225
247
 
226
248
  <p align="center"><a href="https://github.com/krsnzd/license/">🕉</a></p>