@manyducks.co/dolla 2.0.0 → 3.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.
- package/README.md +133 -284
- package/dist/context-B5blupD2.js +363 -0
- package/dist/context-B5blupD2.js.map +1 -0
- package/dist/core/context.d.ts +29 -144
- package/dist/core/debug.d.ts +19 -0
- package/dist/core/index.d.ts +15 -16
- package/dist/core/markup/helpers.d.ts +34 -0
- package/dist/core/markup/html.d.ts +3 -0
- package/dist/core/{nodes → markup/nodes}/dom.d.ts +5 -4
- package/dist/core/markup/nodes/dynamic.d.ts +16 -0
- package/dist/core/markup/nodes/element.d.ts +14 -0
- package/dist/core/markup/nodes/portal.d.ts +15 -0
- package/dist/core/markup/nodes/repeat.d.ts +21 -0
- package/dist/core/markup/nodes/view.d.ts +17 -0
- package/dist/core/markup/scheduler.d.ts +1 -0
- package/dist/core/markup/types.d.ts +62 -0
- package/dist/core/markup/utils.d.ts +22 -0
- package/dist/core/ref.d.ts +6 -12
- package/dist/core/root.d.ts +36 -0
- package/dist/core/signals.d.ts +46 -76
- package/dist/core/symbols.d.ts +2 -0
- package/dist/core-JHktdqjt.js +242 -0
- package/dist/core-JHktdqjt.js.map +1 -0
- package/dist/http/index.d.ts +21 -33
- package/dist/http.js +89 -149
- package/dist/http.js.map +1 -1
- package/dist/index.js +4 -174
- package/dist/jsx-dev-runtime.d.ts +4 -3
- package/dist/jsx-dev-runtime.js +12 -9
- package/dist/jsx-dev-runtime.js.map +1 -1
- package/dist/jsx-runtime.d.ts +5 -4
- package/dist/jsx-runtime.js +17 -12
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/router/index.d.ts +4 -3
- package/dist/router/router.d.ts +19 -162
- package/dist/router/store.d.ts +12 -0
- package/dist/router/types.d.ts +152 -0
- package/dist/router/utils.d.ts +99 -0
- package/dist/router/utils.test.d.ts +1 -0
- package/dist/router.js +428 -5
- package/dist/router.js.map +1 -1
- package/dist/signals-CMJPGr_M.js +354 -0
- package/dist/signals-CMJPGr_M.js.map +1 -0
- package/dist/translate/index.d.ts +82 -0
- package/dist/translate.js +125 -0
- package/dist/translate.js.map +1 -0
- package/dist/types.d.ts +21 -39
- package/dist/utils.d.ts +41 -29
- package/dist/utils.test.d.ts +1 -0
- package/dist/virtual/index.d.ts +1 -0
- package/dist/virtual/list.d.ts +53 -0
- package/package.json +19 -16
- package/dist/core/app.d.ts +0 -24
- package/dist/core/env.d.ts +0 -3
- package/dist/core/hooks.d.ts +0 -70
- package/dist/core/logger.d.ts +0 -42
- package/dist/core/logger.test.d.ts +0 -0
- package/dist/core/markup.d.ts +0 -82
- package/dist/core/markup.test.d.ts +0 -0
- package/dist/core/nodes/_markup.d.ts +0 -36
- package/dist/core/nodes/dynamic.d.ts +0 -22
- package/dist/core/nodes/element.d.ts +0 -27
- package/dist/core/nodes/portal.d.ts +0 -18
- package/dist/core/nodes/repeat.d.ts +0 -27
- package/dist/core/nodes/view.d.ts +0 -25
- package/dist/core/views/default-crash-view.d.ts +0 -25
- package/dist/core/views/for.d.ts +0 -21
- package/dist/core/views/fragment.d.ts +0 -7
- package/dist/core/views/portal.d.ts +0 -16
- package/dist/core/views/show.d.ts +0 -25
- package/dist/fragment-BahD_BJA.js +0 -7
- package/dist/fragment-BahD_BJA.js.map +0 -1
- package/dist/i18n/index.d.ts +0 -134
- package/dist/i18n.js +0 -309
- package/dist/i18n.js.map +0 -1
- package/dist/index-DRJlxs-Q.js +0 -535
- package/dist/index-DRJlxs-Q.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logger-Aqi9m1CF.js +0 -565
- package/dist/logger-Aqi9m1CF.js.map +0 -1
- package/dist/markup-8jNhoqDe.js +0 -1089
- package/dist/markup-8jNhoqDe.js.map +0 -1
- package/dist/router/hooks.d.ts +0 -2
- package/dist/router/router.utils.d.ts +0 -93
- package/dist/typeChecking-5kmX0ulW.js +0 -65
- package/dist/typeChecking-5kmX0ulW.js.map +0 -1
- package/dist/typeChecking.d.ts +0 -95
- package/docs/buildless.md +0 -132
- package/docs/components.md +0 -238
- package/docs/hooks.md +0 -356
- package/docs/http.md +0 -178
- package/docs/i18n.md +0 -220
- package/docs/index.md +0 -10
- package/docs/markup.md +0 -136
- package/docs/mixins.md +0 -176
- package/docs/ref.md +0 -77
- package/docs/router.md +0 -281
- package/docs/setup.md +0 -137
- package/docs/signals.md +0 -262
- package/docs/stores.md +0 -113
- package/docs/views.md +0 -356
- package/notes/atomic.md +0 -452
- package/notes/elimination.md +0 -33
- package/notes/observable.md +0 -180
- package/notes/scratch.md +0 -565
- package/notes/splitting.md +0 -5
- package/notes/views.md +0 -195
- package/vite.config.js +0 -22
- /package/dist/core/{hooks.test.d.ts → markup/html.test.d.ts} +0 -0
- /package/dist/core/{ref.test.d.ts → markup/utils.test.d.ts} +0 -0
- /package/dist/router/{router.utils.test.d.ts → matcher.test.d.ts} +0 -0
- /package/dist/{typeChecking.test.d.ts → router/router.test.d.ts} +0 -0
package/notes/atomic.md
DELETED
|
@@ -1,452 +0,0 @@
|
|
|
1
|
-
# ATOMIC overhaul
|
|
2
|
-
|
|
3
|
-
Overhaul of Dolla as Atomic.
|
|
4
|
-
|
|
5
|
-
Diff:
|
|
6
|
-
|
|
7
|
-
- Change signals to plain functions.
|
|
8
|
-
- Access component context things with $functions in the component body.
|
|
9
|
-
- Split `router` and `localize` into companion packages.
|
|
10
|
-
- No top-level app; just `mount` a view.
|
|
11
|
-
|
|
12
|
-
Goals:
|
|
13
|
-
|
|
14
|
-
- Easy drop-in script tag to get started. Feasible to start with a CDN for prototyping and introduce build step later.
|
|
15
|
-
- Server side rendering support. `html` templates should create intermediate data structure that can be turned into DOM nodes or a string.
|
|
16
|
-
|
|
17
|
-
## Signals
|
|
18
|
-
|
|
19
|
-
```js
|
|
20
|
-
import { atom, memo } from "@manyducks.co/atomic";
|
|
21
|
-
|
|
22
|
-
// Atoms are hybrid getter/setter functions. Call without a value to get, call with a value to set.
|
|
23
|
-
const count = atom(0);
|
|
24
|
-
|
|
25
|
-
// Basic computed properties are just functions. Taking advantage of the fact that functions called in functions will still be tracked.
|
|
26
|
-
const doubled = () => count() * 2;
|
|
27
|
-
|
|
28
|
-
// Use memo to make a memoized value for more expensive calculations.
|
|
29
|
-
const quadrupled = memo(() => doubled() * 2);
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Scopes
|
|
33
|
-
|
|
34
|
-
Provided are certain functions that start with a `$` character. These are scope functions. They only work inside a component scope, which is to say, inside the body of a View or Store function.
|
|
35
|
-
|
|
36
|
-
ViewContext and StoreContext cease to be something the user worries about. Just import and call scope functions. This also provides TypeScript autocomplete for stores and things in plain JS.
|
|
37
|
-
|
|
38
|
-
```js
|
|
39
|
-
function Example() {
|
|
40
|
-
// Lifecycle
|
|
41
|
-
$mount(() => {
|
|
42
|
-
// ...
|
|
43
|
-
});
|
|
44
|
-
$unmount(() => {
|
|
45
|
-
// ...
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// Logger
|
|
49
|
-
const debug = $debug();
|
|
50
|
-
const debug = $debug("my-custom-prefix"); // Optionally pass a prefix (replaces ctx.name = "...")
|
|
51
|
-
|
|
52
|
-
// Signal effects
|
|
53
|
-
const count = atom(0);
|
|
54
|
-
$effect(() => {
|
|
55
|
-
debug.log("count has changed: %d", count());
|
|
56
|
-
|
|
57
|
-
return () => {
|
|
58
|
-
// Cleanup function. Runs between effect invocations or on unmount.
|
|
59
|
-
};
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// Context/Stores
|
|
63
|
-
$provide(SomeStore);
|
|
64
|
-
const some = $use(SomeStore);
|
|
65
|
-
|
|
66
|
-
// Router lib:
|
|
67
|
-
const router = $router();
|
|
68
|
-
router.pattern; // full pattern up to this point in the tree (merged from nested parent routers)
|
|
69
|
-
router.path; // full path up to this point
|
|
70
|
-
router.params; // merged params
|
|
71
|
-
router.query; // parsed query params
|
|
72
|
-
// Router navigation:
|
|
73
|
-
router.go("/somewhere/else");
|
|
74
|
-
router.back();
|
|
75
|
-
router.forward();
|
|
76
|
-
|
|
77
|
-
// .on() returns an outlet on which you can chain more .on() to define subroutes.
|
|
78
|
-
// This goes directly into your template to render those routes.
|
|
79
|
-
const outlet = router
|
|
80
|
-
.on("/example/route/one/*", SomeView)
|
|
81
|
-
.on("/example/route/two/*", OtherView)
|
|
82
|
-
.on("/example/route/three/*", AnotherView);
|
|
83
|
-
|
|
84
|
-
// Localize lib:
|
|
85
|
-
const { t } = $localize();
|
|
86
|
-
|
|
87
|
-
// Special internal function. Returns the actual internal context object.
|
|
88
|
-
// This could be exported for extension purposes.
|
|
89
|
-
const ctx = $$context();
|
|
90
|
-
|
|
91
|
-
// TODO: Need another way to render children. Children should be passed to the view (as Markup).
|
|
92
|
-
// So maybe just passing them into your template would work.
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### Scopes
|
|
97
|
-
|
|
98
|
-
> NOTE: Views and directives are called within a scope.
|
|
99
|
-
|
|
100
|
-
```js
|
|
101
|
-
// Functions starting with $ can be called within a scope.
|
|
102
|
-
// Scopes will clean up all effects created within them when they are disconnected.
|
|
103
|
-
const scope = createScope(() => {
|
|
104
|
-
$effect(() => {
|
|
105
|
-
// Atoms and memos called within an $effect are automatically tracked.
|
|
106
|
-
console.log(`count is ${count()} (doubled: ${doubled()})`);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// Changing tracked values will trigger effects to run again.
|
|
110
|
-
count(1);
|
|
111
|
-
|
|
112
|
-
// $effects are called immediately once, then again each time one or more dependencies change.
|
|
113
|
-
// Effects are settled in queueMicrotask(), effectively batching them.
|
|
114
|
-
|
|
115
|
-
count(2);
|
|
116
|
-
count(3);
|
|
117
|
-
count(4);
|
|
118
|
-
// Multiple synchronous calls in a row like this will only trigger the effect once.
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// ----- Scope Functions ----- //
|
|
122
|
-
|
|
123
|
-
$effect(() => {
|
|
124
|
-
// Tracks dependencies. This function will run again when any of them change.
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
$connected(() => {
|
|
128
|
-
// Runs when scope is connected.
|
|
129
|
-
// In views and directives this happens in the next microtask after DOM nodes are attached.
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
$disconnected(() => {
|
|
133
|
-
// Runs when scope is disconnected.
|
|
134
|
-
// In views and directives this happens in the next microtask after DOM nodes are disconnected.
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// ----- Scope API ----- //
|
|
138
|
-
|
|
139
|
-
const scope = createScope(() => {
|
|
140
|
-
/* ... */
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// Connect starts all $effects and runs $connected callbacks.
|
|
144
|
-
scope.connect();
|
|
145
|
-
|
|
146
|
-
// Disconnect disposes all $effects and runs $disconnected callbacks.
|
|
147
|
-
scope.disconnect();
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
## Templates
|
|
151
|
-
|
|
152
|
-
```js
|
|
153
|
-
// Provide directives and views in a config object.
|
|
154
|
-
const template = html({
|
|
155
|
-
directives: { custom: customDirective },
|
|
156
|
-
views: { SomeView },
|
|
157
|
-
})`
|
|
158
|
-
<div>
|
|
159
|
-
<SomeView *custom=${x} prop=${value} />
|
|
160
|
-
</div>
|
|
161
|
-
`;
|
|
162
|
-
|
|
163
|
-
// Or put the views and directives at the end?
|
|
164
|
-
const template = html`
|
|
165
|
-
<div>
|
|
166
|
-
<p>Counter: ${count}</p>
|
|
167
|
-
|
|
168
|
-
<!-- bind events with @name -->
|
|
169
|
-
<div>
|
|
170
|
-
<button @click=${increment}>+1</button>
|
|
171
|
-
<button @click=${decrement}>-1</button>
|
|
172
|
-
</div>
|
|
173
|
-
|
|
174
|
-
<!-- apply directives with *name -->
|
|
175
|
-
<div *ref=${refAtom} *if=${x} *unless=${x} *show=${x} *hide=${x} *classes=${x} *styles=${x} *custom=${whatever} />
|
|
176
|
-
|
|
177
|
-
<!-- bind properties with .name -->
|
|
178
|
-
<span .textContent=${x} />
|
|
179
|
-
|
|
180
|
-
<!-- two-way bind atoms with :value -->
|
|
181
|
-
<input :value=${valueAtom} />
|
|
182
|
-
|
|
183
|
-
<ul *if=${hasValues}>
|
|
184
|
-
<!-- render iterables from signals with list() -->
|
|
185
|
-
${list(values, (value, index) => {
|
|
186
|
-
// Render views into HTML templates with view()
|
|
187
|
-
return html`<li><SomeView item=${value} /></li>`.withViews({ SomeView });
|
|
188
|
-
})}
|
|
189
|
-
</ul>
|
|
190
|
-
</div>
|
|
191
|
-
`
|
|
192
|
-
.withDirectives({ custom: customDirective })
|
|
193
|
-
.withViews({ SomeView });
|
|
194
|
-
|
|
195
|
-
// Key
|
|
196
|
-
// @ for event listeners
|
|
197
|
-
// * for directives
|
|
198
|
-
// . for properties
|
|
199
|
-
// :value for two-way value binding
|
|
200
|
-
// no prefix for attributes
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
### Event modifiers
|
|
204
|
-
|
|
205
|
-
```js
|
|
206
|
-
html`<button
|
|
207
|
-
@click.stop.prevent.throttle[250]=${() => {
|
|
208
|
-
// stopPropagation & preventDefault already called
|
|
209
|
-
// Listener will be triggered a maximum of once every 250 milliseconds.
|
|
210
|
-
}}
|
|
211
|
-
>
|
|
212
|
-
Click Me
|
|
213
|
-
</button>`;
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
You can chain modifiers on event handlers. Inspired by [`Mizu.js`](https://mizu.sh/#event).
|
|
217
|
-
|
|
218
|
-
#### `.prevent`
|
|
219
|
-
|
|
220
|
-
Calls event.preventDefault() when triggered.
|
|
221
|
-
|
|
222
|
-
#### `.stop`
|
|
223
|
-
|
|
224
|
-
Calls event.stopPropagation() when triggered.
|
|
225
|
-
|
|
226
|
-
#### `.once`
|
|
227
|
-
|
|
228
|
-
Register listener with { once: true }. If present the listener is removed after being triggered once.
|
|
229
|
-
|
|
230
|
-
#### `.passive`
|
|
231
|
-
|
|
232
|
-
Register listener with { passive: true }.
|
|
233
|
-
|
|
234
|
-
#### `.capture`
|
|
235
|
-
|
|
236
|
-
Register listener with { capture: true }.
|
|
237
|
-
|
|
238
|
-
#### `.self`
|
|
239
|
-
|
|
240
|
-
Trigger listener only if event.target is the element itself.
|
|
241
|
-
|
|
242
|
-
#### `.attach[element | window | document]`
|
|
243
|
-
|
|
244
|
-
> `@click.attach[document]=${...}`
|
|
245
|
-
|
|
246
|
-
Attach listener to a different target.
|
|
247
|
-
|
|
248
|
-
#### `.throttle[duration≈250ms]`
|
|
249
|
-
|
|
250
|
-
Prevent listener from being called more than once during the specified time frame. Duration value is in milliseconds.
|
|
251
|
-
|
|
252
|
-
#### `.debounce[duration≈250ms]`
|
|
253
|
-
|
|
254
|
-
Delay listener execution until the specified time frame has passed without any activity. Duration value is in milliseconds.
|
|
255
|
-
|
|
256
|
-
### Lists
|
|
257
|
-
|
|
258
|
-
```js
|
|
259
|
-
list(values, (value, index) => {
|
|
260
|
-
return html`<li>${view(SomeComponent, value)}</li>`;
|
|
261
|
-
});
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
### Custom Directives
|
|
265
|
-
|
|
266
|
-
```js
|
|
267
|
-
function myDirective(element, value, modifiers) {
|
|
268
|
-
// Directives are called inside a scope.
|
|
269
|
-
$disconnected(() => {
|
|
270
|
-
// Cleanup
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
## Full Example
|
|
276
|
-
|
|
277
|
-
```js
|
|
278
|
-
import { atom, memo, html, connect, $effect } from "@manyducks.co/atomic";
|
|
279
|
-
|
|
280
|
-
// Functions starting with $ can only be called in the body of a component function.
|
|
281
|
-
|
|
282
|
-
// IDEA: CSS components. Ref counted and added to head while used at least once on the page.
|
|
283
|
-
const button = css`
|
|
284
|
-
color: "red";
|
|
285
|
-
|
|
286
|
-
&:hover {
|
|
287
|
-
color: "blue";
|
|
288
|
-
}
|
|
289
|
-
`;
|
|
290
|
-
|
|
291
|
-
function Counter() {
|
|
292
|
-
const debug = logger("Component");
|
|
293
|
-
|
|
294
|
-
const count = atom(0);
|
|
295
|
-
|
|
296
|
-
// Simple computed value; computation runs each time function is called
|
|
297
|
-
const doubled = () => count() * 2;
|
|
298
|
-
|
|
299
|
-
// Memoized; computation only runs when one of its dependencies changes
|
|
300
|
-
const quadrupled = memo((previousValue) => doubled() * 2, { equals: deepEqual });
|
|
301
|
-
// memos pass their previous to their callback
|
|
302
|
-
// memos can have an equality function specified (as can atoms)
|
|
303
|
-
|
|
304
|
-
$effect(() => {
|
|
305
|
-
// Dependencies are tracked when getters are called in a tracked scope.
|
|
306
|
-
// Tracked scopes are the body of a `memo` or `effect` callback.
|
|
307
|
-
debug.log(`Count is: ${count()}`);
|
|
308
|
-
|
|
309
|
-
// untrack
|
|
310
|
-
const value = peek(count);
|
|
311
|
-
const doubled = peek(() => {
|
|
312
|
-
return count() * 2;
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
$connected(() => {
|
|
317
|
-
// Runs when component is connected.
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
$disconnected(() => {
|
|
321
|
-
// Runs when component is disconnected.
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
function increment() {
|
|
325
|
-
// Set new value
|
|
326
|
-
count(count() + 1);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function decrement() {
|
|
330
|
-
count(count() - 1);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const hasValues = () => values().length > 0;
|
|
334
|
-
|
|
335
|
-
return html`
|
|
336
|
-
<div>
|
|
337
|
-
<p>Counter: ${count}</p>
|
|
338
|
-
<div>
|
|
339
|
-
<button @click=${increment}>+1</button>
|
|
340
|
-
<button @click=${decrement}>-1</button>
|
|
341
|
-
</div>
|
|
342
|
-
|
|
343
|
-
<div *ref=${refAtom} *if=${x} *unless=${x} *show=${x} *hide=${x} *classes=${x} *styles=${x} *custom=${whatever} />
|
|
344
|
-
|
|
345
|
-
<!-- Property binding -->
|
|
346
|
-
<span .textContent=${x} />
|
|
347
|
-
|
|
348
|
-
<!-- Two way binding of atoms -->
|
|
349
|
-
<input :value=${valueAtom} />
|
|
350
|
-
|
|
351
|
-
<ul *if=${hasValues}>
|
|
352
|
-
${list(values, (value, index) => {
|
|
353
|
-
return html`<li>${view(SomeComponent, value)}</li>`;
|
|
354
|
-
})}
|
|
355
|
-
</ul>
|
|
356
|
-
</div>
|
|
357
|
-
`.withDirectives({ custom: customDirective });
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// In another file...
|
|
361
|
-
|
|
362
|
-
function refDirective(element, fn) {
|
|
363
|
-
fn(element);
|
|
364
|
-
$disconnected(() => {
|
|
365
|
-
fn(undefined);
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function ifDirective(element, condition) {
|
|
370
|
-
// directives run in microtask immediately after element is attached to parent, before next paint
|
|
371
|
-
|
|
372
|
-
const placeholder = document.createComment("");
|
|
373
|
-
|
|
374
|
-
// $functions work in directives; they hook into the lifecycle of the element
|
|
375
|
-
$effect(() => {
|
|
376
|
-
if (condition()) {
|
|
377
|
-
// show element
|
|
378
|
-
if (!element.parentNode && placeholder.parentNode) {
|
|
379
|
-
element.insertBefore(placeholder.parentNode);
|
|
380
|
-
placeholder.parentNode.removeChild(placeholder);
|
|
381
|
-
}
|
|
382
|
-
} else {
|
|
383
|
-
// hide element
|
|
384
|
-
if (element.parentNode && !placeholder.parentNode) {
|
|
385
|
-
placeholder.insertBefore(element.parentNode);
|
|
386
|
-
element.parentNode.removeChild(element);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function unlessDirective(element, condition) {
|
|
393
|
-
return ifDirective(element, () => !condition());
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function showDirective(element, condition) {
|
|
397
|
-
// Store the element's current value.
|
|
398
|
-
let value = element.style.display;
|
|
399
|
-
|
|
400
|
-
$effect(() => {
|
|
401
|
-
if (condition()) {
|
|
402
|
-
// Apply the stored value when truthy.
|
|
403
|
-
element.style.display = value;
|
|
404
|
-
} else {
|
|
405
|
-
// Store value and hide when falsy.
|
|
406
|
-
value = element.style.display;
|
|
407
|
-
element.style.display = "none !important";
|
|
408
|
-
}
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
function hideDirective(element, condition) {
|
|
413
|
-
return showDirective(element, () => !condition());
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function classesDirective(element, classes) {
|
|
417
|
-
// TODO: Applies an object of class names and values, where the values may be signals or plain values.
|
|
418
|
-
// Truthy means "apply this class" while falsy means don't.
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function stylesDirective(element, styles) {
|
|
422
|
-
// TODO: Same idea as *classes but for styles.
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
connect(Component, document.body);
|
|
426
|
-
|
|
427
|
-
// Easy custom elements? Could be another library.
|
|
428
|
-
element("my-counter", function () {
|
|
429
|
-
// Runs just after connectedCallback. `this` is bound to the custom HTMLElement class.
|
|
430
|
-
const shadow = this.attachShadow({ mode: "closed" });
|
|
431
|
-
|
|
432
|
-
return html`<div></div>`;
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
// function css(strings, values) {
|
|
436
|
-
// return {
|
|
437
|
-
// type: "css",
|
|
438
|
-
|
|
439
|
-
// }
|
|
440
|
-
// }
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
```ts
|
|
444
|
-
interface TemplateNode {
|
|
445
|
-
mount(parent: Node, after?: Node): void;
|
|
446
|
-
unmount(skipDOM?: boolean): void;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
interface TemplateDirective {
|
|
450
|
-
(element: Element, value: unknown): Node | TemplateNode;
|
|
451
|
-
}
|
|
452
|
-
```
|
package/notes/elimination.md
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# What can we remove?
|
|
2
|
-
|
|
3
|
-
I still want to get this library with all its features down below 15kb. It's currently at 17.8kb. That said, I don't want to strip out things that are actually useful. The mission of this library is to be batteries-included, which implies some extra weight.
|
|
4
|
-
|
|
5
|
-
What can be removed without compromising the basics?
|
|
6
|
-
|
|
7
|
-
## Events?
|
|
8
|
-
|
|
9
|
-
> 1st ELIMINATED. We're at 16.77kb now.
|
|
10
|
-
|
|
11
|
-
If we have the ability to get contexts and call methods on them, isn't that just a better version of events?
|
|
12
|
-
|
|
13
|
-
## Logger / Built-in Crash View
|
|
14
|
-
|
|
15
|
-
> REDUCED! Removed simple-color-hash in favor of custom OKLCH hash and we're down to 15.9kb.
|
|
16
|
-
|
|
17
|
-
Logger could be a different package. Crashes could be handled by a crash handler you attach.
|
|
18
|
-
|
|
19
|
-
```js
|
|
20
|
-
Dolla.onCrash((error) => {
|
|
21
|
-
// Do what you will with this error.
|
|
22
|
-
});
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## HTTP Client?
|
|
26
|
-
|
|
27
|
-
Do we really need this? It's kind of a nice wrapper but the only thing I use the middleware for is to add auth headers for API calls, and it's trivial to write a function around `fetch` that does that. No need for the complexity of middleware.
|
|
28
|
-
|
|
29
|
-
Not really. Fetch is hella basic. I just tried to write my own trivial wrapper around fetch and it took a little too much thought. I don't want to do that for every project. Adding middleware to authenticate feels trivial with `http`.
|
|
30
|
-
|
|
31
|
-
## Markup?
|
|
32
|
-
|
|
33
|
-
Can JSX and `html` return DOM nodes directly? Not really, because views need to be mounted and unmounted like DOM nodes but they don't have the same API. Although I could define a minimal DOM-compatible API so that DOM nodes could directly work.
|
package/notes/observable.md
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
# Observable
|
|
2
|
-
|
|
3
|
-
Signals have some downsides, like if you call them inside a function, and you then call that function inside a tracking context, it can cause the tracking context to re-run unexpectedly. You then have to defensively call things inside `untracked` to avoid tracking deeply nested signal getters. It's not unmanageable but it's extremely surprising and unintuitive when you first run into it. It's a new and unnecessary consideration that makes code feel less safe and predictable.
|
|
4
|
-
|
|
5
|
-
I'm considering using Observable (or my own extended version of it) as a basis for a state management system. Still built on top of `alien-signals` but with explicit tracking of signal values. It could look something like the following.
|
|
6
|
-
|
|
7
|
-
This would probably be best as a separate library, maybe called `@manyducks.co/atomic`.
|
|
8
|
-
|
|
9
|
-
Going back to the atom/compose terminology.
|
|
10
|
-
|
|
11
|
-
```js
|
|
12
|
-
// Define an atom, the basic value holder object.
|
|
13
|
-
const count = atom(5);
|
|
14
|
-
|
|
15
|
-
// Atoms have a `value` field that is writable. This is not tracked by default.
|
|
16
|
-
count.value; // 5
|
|
17
|
-
count.value = 12;
|
|
18
|
-
count.value; // 12
|
|
19
|
-
|
|
20
|
-
// Implements the Observable interface.
|
|
21
|
-
const subscription = count.subscribe({
|
|
22
|
-
next: (value) => {
|
|
23
|
-
console.log("count is now", value);
|
|
24
|
-
},
|
|
25
|
-
error: (error) => {},
|
|
26
|
-
completed: () => {},
|
|
27
|
-
});
|
|
28
|
-
subscription.closed; // boolean
|
|
29
|
-
subscription.unsubscribe();
|
|
30
|
-
|
|
31
|
-
// Like `value` getter but tracks count in a signal tracking scope.
|
|
32
|
-
count.track(); // 12
|
|
33
|
-
|
|
34
|
-
// Can be closed which completes all subscribers and will throw an error if a new value is set.
|
|
35
|
-
count.close();
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Atoms can be composed.
|
|
39
|
-
|
|
40
|
-
```js
|
|
41
|
-
// You explicitly pass a dependencies array at the end, similar to React.
|
|
42
|
-
// Dependencies will be tracked and the compose function re-run any time they receive a new value.
|
|
43
|
-
const doubled = composed((prev) => count.value * 2, [count]);
|
|
44
|
-
|
|
45
|
-
// Read-only value.
|
|
46
|
-
doubled.value;
|
|
47
|
-
|
|
48
|
-
// Observable
|
|
49
|
-
const subscription = doubled.subscribe((value) => {
|
|
50
|
-
// ...
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// Trackable
|
|
54
|
-
doubled.track();
|
|
55
|
-
|
|
56
|
-
// Completes subscriptions, untracks deps and prevents receiving any new values.
|
|
57
|
-
doubled.close();
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Effects work basically the same as `composed` but they return a cancel function instead of a value.
|
|
61
|
-
|
|
62
|
-
```js
|
|
63
|
-
const cancel = effect(() => {
|
|
64
|
-
console.log(`count is now ${count.value}`);
|
|
65
|
-
}, [count]);
|
|
66
|
-
|
|
67
|
-
cancel();
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Other thoughts:
|
|
71
|
-
|
|
72
|
-
```js
|
|
73
|
-
// You can name observables for debugging purposes. If one of them throws an error it can include the name.
|
|
74
|
-
const count = atom(5).named("count");
|
|
75
|
-
|
|
76
|
-
// Maybe even named effects.
|
|
77
|
-
const cancel = effect(() => {
|
|
78
|
-
console.log(`count is now ${count.value}`);
|
|
79
|
-
}, [count]).named("countReader");
|
|
80
|
-
|
|
81
|
-
// Promise-based await next? This will resolve when count.value is set.
|
|
82
|
-
// If the subscription errors it rejects with that error. If the subscription completes it rejects with an error to indicate that.
|
|
83
|
-
const nextCount = await count.nextValue();
|
|
84
|
-
|
|
85
|
-
// Filter and signal. Wait up to 5 seconds for next even value.
|
|
86
|
-
const controller = new AbortController();
|
|
87
|
-
setTimeout(controller.abort, 5000);
|
|
88
|
-
const nextEven = await count.nextValue({
|
|
89
|
-
filter: (value) => value % 2 === 0,
|
|
90
|
-
signal: controller.signal,
|
|
91
|
-
// or timeout: 5000
|
|
92
|
-
});
|
|
93
|
-
// Resolves to null if aborted or timed out.
|
|
94
|
-
|
|
95
|
-
// Batching
|
|
96
|
-
|
|
97
|
-
const count1 = atom(5);
|
|
98
|
-
const count2 = atom(12);
|
|
99
|
-
|
|
100
|
-
effect(() => {
|
|
101
|
-
console.log(`total: ${count1.value + count2.value}`);
|
|
102
|
-
}, [count1, count2]);
|
|
103
|
-
|
|
104
|
-
// This causes the effect to run twice
|
|
105
|
-
count1.value = 50;
|
|
106
|
-
count2.value = 8;
|
|
107
|
-
|
|
108
|
-
// A batch suspends effects until it concludes; this runs the effect once
|
|
109
|
-
batch(() => {
|
|
110
|
-
count1.value = 50;
|
|
111
|
-
count2.value = 8;
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// Deep reactivity
|
|
115
|
-
const data = atom(
|
|
116
|
-
{
|
|
117
|
-
users: [
|
|
118
|
-
{ id: 1, name: "Tony" },
|
|
119
|
-
{ id: 2, name: "Morgan" },
|
|
120
|
-
],
|
|
121
|
-
},
|
|
122
|
-
{ deep: true },
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
// These updates trigger effects and subscriptions.
|
|
126
|
-
// By default only setting `.value` directly will trigger notifications.
|
|
127
|
-
data.value.users[0].name = "Bon";
|
|
128
|
-
data.value.users.find((user) => user.id === 1).name = "Tony";
|
|
129
|
-
|
|
130
|
-
// Then in theory, if you referenced one of the values
|
|
131
|
-
const morgan = data.value.users.find((user) => user.id === 2);
|
|
132
|
-
|
|
133
|
-
// And passed that around and modified it that would also still be reactive to the original atom.
|
|
134
|
-
// I don't know if this is a good idea.
|
|
135
|
-
morgan.name = "AKLSJDAKSD";
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## What would Dolla look like with this?
|
|
139
|
-
|
|
140
|
-
```jsx
|
|
141
|
-
function CounterView() {
|
|
142
|
-
const count = atom(0);
|
|
143
|
-
|
|
144
|
-
const increment = () => count.value++;
|
|
145
|
-
const decrement = () => count.value--;
|
|
146
|
-
|
|
147
|
-
return (
|
|
148
|
-
<div>
|
|
149
|
-
Counter: {count}
|
|
150
|
-
<button onClick={increment}>+1</button>
|
|
151
|
-
<button onClick={decrement}>-1</button>
|
|
152
|
-
</div>
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function ExampleView(props, ctx) {
|
|
157
|
-
const name = atom("");
|
|
158
|
-
|
|
159
|
-
// Update local name whenever props.name changes
|
|
160
|
-
ctx.effect(() => {
|
|
161
|
-
name.value = props.name.value;
|
|
162
|
-
}, [props.name]);
|
|
163
|
-
|
|
164
|
-
// Update greeting whenever local name changes
|
|
165
|
-
const greeting = composed(() => `Hello, ${name.value}`, [name]);
|
|
166
|
-
|
|
167
|
-
return (
|
|
168
|
-
<div>
|
|
169
|
-
<span>{greeting}</span>
|
|
170
|
-
<input value={name} onInput={(e) => (name.value = e.target.value)} />
|
|
171
|
-
</div>
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## TypeScript
|
|
177
|
-
|
|
178
|
-
- `Atom<T>` for the basic building block with a writable value.
|
|
179
|
-
- `Composed<T>` for a derived state based on other `Atom<T>` and `Composed<T>` values.
|
|
180
|
-
- `Atomic<T>` to encompass the basic API of both `Atom<T>` and `Composed<T>`.
|