@manyducks.co/dolla 2.0.0-alpha.4 → 2.0.0-alpha.41

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.
Files changed (74) hide show
  1. package/README.md +31 -964
  2. package/dist/core/context.d.ts +53 -0
  3. package/dist/{modules → core}/dolla.d.ts +43 -26
  4. package/dist/core/markup.d.ts +90 -0
  5. package/dist/core/nodes/dom.d.ts +13 -0
  6. package/dist/core/nodes/dynamic.d.ts +28 -0
  7. package/dist/core/nodes/html.d.ts +33 -0
  8. package/dist/core/nodes/list.d.ts +28 -0
  9. package/dist/core/nodes/outlet.d.ts +19 -0
  10. package/dist/core/nodes/portal.d.ts +22 -0
  11. package/dist/core/nodes/view.d.ts +78 -0
  12. package/dist/core/ref.d.ts +28 -0
  13. package/dist/core/signals.d.ts +127 -0
  14. package/dist/core/store.d.ts +52 -0
  15. package/dist/core/symbols.d.ts +4 -0
  16. package/dist/{views → core/views}/passthrough.d.ts +1 -1
  17. package/dist/{modules/http.d.ts → http/index.d.ts} +3 -5
  18. package/dist/index.d.ts +14 -11
  19. package/dist/index.js +986 -1216
  20. package/dist/index.js.map +1 -1
  21. package/dist/jsx-dev-runtime.d.ts +2 -2
  22. package/dist/jsx-dev-runtime.js +2 -2
  23. package/dist/jsx-dev-runtime.js.map +1 -1
  24. package/dist/jsx-runtime.d.ts +3 -3
  25. package/dist/jsx-runtime.js +2 -2
  26. package/dist/jsx-runtime.js.map +1 -1
  27. package/dist/markup-DkQI155j.js +1447 -0
  28. package/dist/markup-DkQI155j.js.map +1 -0
  29. package/dist/{modules/router.d.ts → router/index.d.ts} +37 -48
  30. package/dist/router/router.utils.test.d.ts +1 -0
  31. package/dist/translate/index.d.ts +133 -0
  32. package/dist/typeChecking.d.ts +2 -98
  33. package/dist/typeChecking.test.d.ts +1 -0
  34. package/dist/types.d.ts +12 -14
  35. package/dist/utils.d.ts +18 -3
  36. package/docs/http.md +29 -0
  37. package/docs/i18n.md +38 -0
  38. package/docs/index.md +10 -0
  39. package/docs/router.md +80 -0
  40. package/docs/setup.md +31 -0
  41. package/docs/signals.md +149 -0
  42. package/docs/state.md +141 -0
  43. package/docs/stores.md +62 -0
  44. package/docs/views.md +208 -0
  45. package/index.d.ts +2 -2
  46. package/notes/TODO.md +6 -0
  47. package/notes/atomic.md +209 -0
  48. package/notes/context-routes.md +56 -0
  49. package/notes/elimination.md +33 -0
  50. package/notes/readme-scratch.md +260 -0
  51. package/notes/route-middleware.md +42 -0
  52. package/notes/scratch.md +330 -7
  53. package/notes/stores.md +53 -0
  54. package/package.json +14 -10
  55. package/vite.config.js +5 -10
  56. package/build.js +0 -34
  57. package/dist/markup.d.ts +0 -100
  58. package/dist/modules/language.d.ts +0 -41
  59. package/dist/modules/render.d.ts +0 -17
  60. package/dist/nodes/cond.d.ts +0 -26
  61. package/dist/nodes/html.d.ts +0 -31
  62. package/dist/nodes/observer.d.ts +0 -29
  63. package/dist/nodes/outlet.d.ts +0 -22
  64. package/dist/nodes/portal.d.ts +0 -19
  65. package/dist/nodes/repeat.d.ts +0 -34
  66. package/dist/nodes/text.d.ts +0 -19
  67. package/dist/passthrough-BSLd3foL.js +0 -1245
  68. package/dist/passthrough-BSLd3foL.js.map +0 -1
  69. package/dist/signals.d.ts +0 -101
  70. package/dist/view.d.ts +0 -50
  71. package/tests/signals.test.js +0 -135
  72. /package/dist/{routing.test.d.ts → core/signals.test.d.ts} +0 -0
  73. /package/dist/{views → core/views}/default-crash-view.d.ts +0 -0
  74. /package/dist/{routing.d.ts → router/router.utils.d.ts} +0 -0
package/docs/state.md ADDED
@@ -0,0 +1,141 @@
1
+ ## ⚡ Reactive Updates with `State`
2
+
3
+ Dolla sets out to solve the challenge of keeping your UI in sync with your data. All apps have state that changes at runtime, and as those values change your UI must update itself to stay in sync with that state. JavaScript frameworks all have their own ways of meeting this challenge, but there are two main ones; virtual DOM and signals.
4
+
5
+ [React](https://react.dev) and similar frameworks make use of a [virtual DOM](https://svelte.dev/blog/virtual-dom-is-pure-overhead), in which every state change causes a "diff" of the real DOM nodes on the page against a lightweight representation of what those nodes _should_ look like, followed by a "patch" where the minimal updates are performed to bring the DOM in line with the ideal virtual DOM.
6
+
7
+ [Solid](https://www.solidjs.com) and similar frameworks make use of [signals](https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob), which are containers for data that will change over time. Signal values are accessed through special getter functions that can be called inside of a "scope" to track their values. When the value of a tracked signal changes, any computations that happened in scopes that depend on those signals are re-run. In an app like this, all of your DOM updates are performed with pinpoint accuracy without diffing as signal values change.
8
+
9
+ Dolla uses a concept of a `State`, which is a signal-like container for values that change over time. Where `State` differs from signals, however, is that there is no magical scope tracking going on behind the scenes. All States that depend on others do so explicity, so your code is easier to read and understand.
10
+
11
+ The `State` API has just four functions:
12
+
13
+ - `createState` to create a new state and a linked setter function.
14
+ - `derive` to create a new state whose value depends on one or more other states.
15
+ - `toState` to ensure that a value is a state object.
16
+ - `toValue` to ensure that a value is a plain value.
17
+
18
+ ### Basic State API
19
+
20
+ ```js
21
+ import { createState } from "@manyducks.co/dolla";
22
+
23
+ // Equivalent to React's `useState` or Solid's `createSignal`.
24
+ // A new read-only State and linked Setter are created.
25
+ const [$count, setCount] = createState(72);
26
+
27
+ // Get the current value.
28
+ $count.get(): // 72
29
+
30
+ // Set a new value.
31
+ setCount(300);
32
+
33
+ // The State now reflects the latest value.
34
+ $count.get(); // 300
35
+
36
+ // Data can also be updated by passing a function.
37
+ // This function takes the current state and returns a new one.
38
+ setCount((current) => current + 1);
39
+ $count.get(); // 301
40
+ ```
41
+
42
+ ### Deriving States from other States
43
+
44
+ #### Example 1: Doubled
45
+
46
+ ```js
47
+ import { createState, derive } from "@manyducks.co/dolla";
48
+
49
+ const [$count, setCount] = createState(1);
50
+
51
+ const $doubled = derive([$count], (count) => count * 2);
52
+
53
+ setCount(10);
54
+ $doubled.get(); // 20
55
+ ```
56
+
57
+ That was a typical toy example where we create a `$doubled` state that always contains the value of `$count`... doubled! This is the essential basic example of computed properties, as written in Dolla.
58
+
59
+ #### Example 2: Selecting a User
60
+
61
+ ```js
62
+ import { createState, derive } from "@manyducks.co/dolla";
63
+
64
+ const [$users, setUsers] = createState([
65
+ { id: 1, name: "Audie" },
66
+ { id: 2, name: "Bob" },
67
+ { id: 3, name: "Cabel" },
68
+ ]);
69
+ const [$selectedUserId, setSelectedUserId] = createState(1);
70
+
71
+ const $selectedUser = derive([$users, $selectedUserId], (users, id) => {
72
+ return users.find((user) => user.id === id);
73
+ });
74
+
75
+ $selectedUser.get(); // { id: 1, name: "Audie" }
76
+
77
+ setSelectedId(3);
78
+
79
+ $selectedUser.get(); // { id: 3, name: "Cabel" }
80
+ ```
81
+
82
+ That was a more realistic example you might actually use in real life. Here we are selecting a user from a list based on its `id` field. This is kind of similar to a `JOIN` operation in a SQL database. I use this kind of pattern constantly in my apps.
83
+
84
+ The strength of setting up a join like this is that the `$users` array can be updated (by API call, websockets, etc.) and your `$selectedUser` will always be pointing to the latest version of the user data.
85
+
86
+ #### Example 3: Narrowing Complex Data
87
+
88
+ ```jsx
89
+ import { createState, derive } from "@manyducks.co/dolla";
90
+
91
+ const [$user, setUser] = createState({ id: 1, name: "Audie" });
92
+
93
+ const $name = derive([$user], (user) => user.name);
94
+
95
+ $name.get(); // "Audie"
96
+
97
+ // In a view:
98
+ <span class="user-name">{$name}</span>;
99
+ ```
100
+
101
+ Another common pattern. In a real app, most data is stored as arrays of objects. But what you need in order to slot it into a view is just a string. In the example above we've selected the user's name and slotted it into a `span`. If the `$user` value ever changes, the name will stay in sync.
102
+
103
+ ### Converting to and from States
104
+
105
+ ```js
106
+ import { createState, toState, toValue } from "@manyducks.co/dolla";
107
+
108
+ const [$count, setCount] = createState(512);
109
+
110
+ // Unwrap the value of $count. Returns 512.
111
+ const count = toValue($count);
112
+ // Passing a non-state value will simply return it.
113
+ const name = toValue("World");
114
+
115
+ // Wrap "Hello" into a State containing "Hello"
116
+ const $value = toState("Hello");
117
+ // Passing a state will simply return that same state.
118
+ const $number = toState($count);
119
+ ```
120
+
121
+ ### In Views
122
+
123
+ ```jsx
124
+ import { derive } from "@manyducks.co/dolla";
125
+
126
+ function UserNameView(props, ctx) {
127
+ const $name = derive([props.$user], (user) => user.name);
128
+
129
+ return <span class={{ "user-name": true, "is-selected": props.$selected }}>{$name}</span>;
130
+ });
131
+ ```
132
+
133
+ In the example above we've displayed the `name` field from a `$user` object inside of a span. We are also assigning an `is-selected` class dynamically based on whether the `$selected` prop contains a truthy or falsy value.
134
+
135
+ ---
136
+
137
+ End.
138
+
139
+ - [🗂️ Docs](./index.md)
140
+ - [🏠 README](../README.md)
141
+ - [🦆 That's a lot of ducks.](https://www.manyducks.co)
package/docs/stores.md ADDED
@@ -0,0 +1,62 @@
1
+ # Stores
2
+
3
+ > TODO: Write about stores
4
+
5
+ ```tsx
6
+ import Dolla, { createState } from "@manyducks.co/dolla";
7
+
8
+ function CounterStore (initialValue, ctx) {
9
+ const [$count, setCount] = createState(initialValue);
10
+
11
+ // Respond to context events which bubble up from views.
12
+ ctx.on("counter:increment", (e) => {
13
+ e.stop(); // call to stop events bubbling to parent contexts.
14
+ setCount((count) => count + 1);
15
+ });
16
+
17
+ ctx.on("counter:decrement", () => {
18
+ setCount((count) => count - 1);
19
+ });
20
+
21
+ ctx.on("counter:reset", () => {
22
+ setCount(0);
23
+ });
24
+
25
+ return $count;
26
+ });
27
+
28
+ // Stores can be provided by the app itself.
29
+ Dolla.provide(CounterStore, 0);
30
+
31
+ function CounterView(props, ctx) {
32
+ // Store instances can also be provided at the view level to provide them to the current scope and those of child views.
33
+ // Views that are not children of this CounterView will not be able to access this particular instance of CounterStore.
34
+ ctx.provide(CounterStore, 0);
35
+
36
+ // Store return values can be accessed with `use`.
37
+ // This method will check the current context for an instance, then recursively check up the view tree until it finds one.
38
+ // An error will be thrown if no instances of the store are provided.
39
+ const $count = ctx.use(CounterStore);
40
+
41
+ // The buttons increment the value inside the store by emitting events.
42
+ // Child views at any depth could also emit these events to update the store.
43
+ return (
44
+ <div>
45
+ <p>Clicks: {$count}</p>
46
+ <div>
47
+ <button onClick={() => ctx.emit("counter:decrement")}>-1</button>
48
+ <button onClick={() => ctx.emit("counter:reset")}>Reset</button>
49
+ <button onClick={() => ctx.emit("counter:increment")}>+1</button>
50
+ </div>
51
+ </div>
52
+ );
53
+ });
54
+ ```
55
+
56
+ ---
57
+
58
+ End.
59
+
60
+ - [🗂️ Docs](./index.md)
61
+ - [🏠 README](../README.md)
62
+ - [🦆 That's a lot of ducks.](https://www.manyducks.co)
package/docs/views.md ADDED
@@ -0,0 +1,208 @@
1
+ # Views
2
+
3
+ Views are one of two component types in Dolla. We call them views because they deal specifically with presenting visible things to the user. The other type of component, [Stores](./stores.md), deal with data and events.
4
+
5
+ At its most basic, a view is a function that returns markup.
6
+
7
+ ```jsx
8
+ function ExampleView() {
9
+ return <h1>Hello World!</h1>;
10
+ }
11
+ ```
12
+
13
+ ## View Props
14
+
15
+ A view function takes a `props` object as its first argument. This object contains all properties passed to the view when it's invoked.
16
+
17
+ ```jsx
18
+ function ListItemView(props) {
19
+ return <li>{props.label}</li>;
20
+ }
21
+
22
+ function ListView() {
23
+ return (
24
+ <ul>
25
+ <ListItemView label="Squirrel" />
26
+ <ListItemView label="Chipmunk" />
27
+ <ListItemView label="Groundhog" />
28
+ </ul>
29
+ );
30
+ }
31
+ ```
32
+
33
+ As you may have guessed, you can pass States as props and slot them in in exactly the same way. This is important because Views do not re-render the way you might expect from other frameworks. Whatever you pass as props is what the View gets for its entire lifecycle.
34
+
35
+ ## View Helpers
36
+
37
+ ### `cond($condition, whenTruthy, whenFalsy)`
38
+
39
+ The `cond` helper does conditional rendering. When `$condition` is truthy, the second argument is rendered. When `$condition` is falsy the third argument is rendered. Either case can be left null or undefined if you don't want to render something for that condition.
40
+
41
+ ```jsx
42
+ function ConditionalListView(props) {
43
+ return (
44
+ <div>
45
+ {cond(
46
+ props.$show,
47
+
48
+ // Visible when truthy
49
+ <ul>
50
+ <ListItemView label="Squirrel" />
51
+ <ListItemView label="Chipmunk" />
52
+ <ListItemView label="Groundhog" />
53
+ </ul>,
54
+
55
+ // Visible when falsy
56
+ <span>List is hidden</span>,
57
+ )}
58
+ </div>
59
+ );
60
+ }
61
+ ```
62
+
63
+ ### `repeat($items, keyFn, renderFn)`
64
+
65
+ The `repeat` helper repeats a render function for each item in a list. The `keyFn` takes an item's value and returns a number, string or Symbol that uniquely identifies that list item. If `$items` changes or gets reordered, all rendered items with matching keys will be reused, those no longer in the list will be removed and those that didn't previously have a matching key are created.
66
+
67
+ ```jsx
68
+ function RepeatedListView() {
69
+ const [$items, setItems] = createState(["Squirrel", "Chipmunk", "Groundhog"]);
70
+
71
+ return (
72
+ <ul>
73
+ {repeat(
74
+ $items,
75
+ (item, index) => item, // Using the string itself as the key
76
+ ($item, $index, context) => {
77
+ return <ListItemView label={$item} />;
78
+ },
79
+ )}
80
+ </ul>
81
+ );
82
+ }
83
+ ```
84
+
85
+ ### `portal(content, parentNode)`
86
+
87
+ The `portal` helper displays DOM elements from a view as children of a parent element elsewhere in the document. Portals are typically used to display modals and other content that needs to appear at the top level of a document.
88
+
89
+ ```jsx
90
+ function PortalView() {
91
+ const content = (
92
+ <div class="modal">
93
+ <p>This is a modal.</p>
94
+ </div>
95
+ );
96
+
97
+ // Content will be appended to `document.body` while this view is connected.
98
+ return portal(document.body, content);
99
+ }
100
+ ```
101
+
102
+ ## View Context
103
+
104
+ A view function takes a context object as its second argument. The context provides a set of functions you can use to respond to lifecycle events, observe dynamic data, print debug messages and display child elements among other things.
105
+
106
+ ```jsx
107
+ function ExampleView(props, ctx) {
108
+ ctx.onMount(() => {
109
+ ctx.log("HELLO!");
110
+ });
111
+
112
+ return <h1>Hello World!</h1>;
113
+ }
114
+ ```
115
+
116
+ ### Printing Debug Messages
117
+
118
+ ```jsx
119
+ function ExampleView(props, ctx) {
120
+ // Set the name of this view's context. Console messages are prefixed with name.
121
+ ctx.name = "CustomName";
122
+
123
+ // Print messages to the console. These are suppressed by default in the app's "production" mode.
124
+ // You can also change which of these are printed and filter messages from certain contexts in the `createApp` options object.
125
+ ctx.info("Verbose debugging info that might be useful to know");
126
+ ctx.log("Standard messages");
127
+ ctx.warn("Something bad might be happening");
128
+ ctx.error("Uh oh!");
129
+
130
+ // If you encounter a bad enough situation, you can halt and disconnect the entire app.
131
+ ctx.crash(new Error("BOOM"));
132
+
133
+ return <h1>Hello World!</h1>;
134
+ }
135
+ ```
136
+
137
+ ### Lifecycle Events
138
+
139
+ ```jsx
140
+ function ExampleView(props, ctx) {
141
+ ctx.beforeMount(() => {
142
+ // Do something before this view's DOM nodes are created.
143
+ });
144
+
145
+ ctx.onMount(() => {
146
+ // Do something immediately after this view is connected to the DOM.
147
+ });
148
+
149
+ ctx.beforeUnmount(() => {
150
+ // Do something before removing this view from the DOM.
151
+ });
152
+
153
+ ctx.onUnmount(() => {
154
+ // Do some cleanup after this view is disconnected from the DOM.
155
+ });
156
+
157
+ return <h1>Hello World!</h1>;
158
+ }
159
+ ```
160
+
161
+ ### Displaying Children
162
+
163
+ The context has an `outlet` function that can be used to display children at a location of your choosing.
164
+
165
+ ```js
166
+ function LayoutView(props, ctx) {
167
+ return (
168
+ <div className="layout">
169
+ <div className="content">{ctx.outlet()}</div>
170
+ </div>
171
+ );
172
+ }
173
+
174
+ function ExampleView() {
175
+ // <h1> and <p> are displayed inside LayoutView's outlet.
176
+ return (
177
+ <LayoutView>
178
+ <h1>Hello</h1>
179
+ <p>This is inside the box.</p>
180
+ </LayoutView>
181
+ );
182
+ }
183
+ ```
184
+
185
+ ### Watching States
186
+
187
+ The `watch` function starts observing when the view is connected and stops when disconnected. This takes care of cleaning up watchers so you don't have to worry about memory leaks.
188
+
189
+ ```jsx
190
+ function ExampleView(props, ctx) {
191
+ const [$count, setCount] = createState(0);
192
+
193
+ // This callback will run when any states in the dependency array receive new values.
194
+ ctx.watch([$count], (count) => {
195
+ ctx.log("count is now", count);
196
+ });
197
+
198
+ // ...
199
+ }
200
+ ```
201
+
202
+ ---
203
+
204
+ End.
205
+
206
+ - [🗂️ Docs](./index.md)
207
+ - [🏠 README](../README.md)
208
+ - [🦆 That's a lot of ducks.](https://www.manyducks.co)
package/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export * from "./lib/index";
1
+ export * from "./src/index";
2
2
 
3
- import type { IntrinsicElements as Elements } from "./lib/core/types";
3
+ import type { IntrinsicElements as Elements } from "./src/types";
4
4
 
5
5
  declare global {
6
6
  namespace JSX {
package/notes/TODO.md ADDED
@@ -0,0 +1,6 @@
1
+ # TO DO LIST
2
+
3
+ - Combine/refactor very similar Group, Outlet and Observer nodes.
4
+ - Group is simplest and exists to mount an array of MarkupElements as one.
5
+ - Outlet is basically the same as Group but it expects a $children state with an array of MarkupElements.
6
+ - Observer is a generic catch-all that works with a set of states and a render function. The render function can return any kind of Renderable which is then converted into a MarkupElement, but very similar update logic outside of that. Observer uses Group internally.
@@ -0,0 +1,209 @@
1
+ # Atomic API
2
+
3
+ ```js
4
+ function SomeView(props, ctx) {
5
+ // Atoms are the basic building block of state.
6
+ const count = new Atom(5);
7
+
8
+ count.value; // returns the value
9
+ count.value = 12; // replaces the value
10
+ count.update((value) => value + 1); // updates the value. You can use Immer here for complex objects.
11
+
12
+ // or you could just implement an update function yourself with Immer. Probably gonna cut it.
13
+ function update(atom, callback) {
14
+ atom.value = produce(atom.value, callback);
15
+ }
16
+ update(count, (value) => value + 1);
17
+
18
+ // Listen for changes. Callback will be run the next time the value changes and each time again afterwards.
19
+ const unsubscribe = count.subscribe((value) => {
20
+ console.log(value);
21
+ });
22
+
23
+ // Composed is a state that depends on one or more other states.
24
+ // The callback takes a getter function that will track that state as a dependency and return its current value.
25
+ // We recompute if any tracked dependency receives a new value.
26
+ const doubled = new Composed((get) => get(count) * 2);
27
+
28
+ // Effects follow the same pattern as a Composed callback.
29
+ ctx.effect(() => {
30
+ console.log(doubled.value);
31
+ });
32
+
33
+ const print = new Atom(false);
34
+
35
+ // Dependency lists are rebuilt every time the callback is run.
36
+ // Below, `value` will not be tracked as a dependency until `print` has changed to true.
37
+ ctx.effect(() => {
38
+ if (get(print)) {
39
+ console.log(get(value));
40
+ }
41
+ });
42
+
43
+ // get() is also the ONLY way to track dependencies.
44
+ // You're free to use the state's own getter if you want the value without actually tracking it.
45
+ ctx.effect((get) => {
46
+ if (get(value) > 5) {
47
+ console.log(doubled.get()); // will not be tracked
48
+ }
49
+ });
50
+
51
+ // ALSO: Need to track sets and updates so we can throw an error if a set was committed in the same scope that value is tracked. Otherwise this will cause an infinite loop.
52
+ }
53
+ ```
54
+
55
+ Refined API:
56
+
57
+ ```js
58
+ const $count = atom(5);
59
+ $count.value++;
60
+ $count.value; // 6
61
+
62
+ const $doubled = compose(() => get($count) * 2);
63
+ const $quadrupled = compose(() => get($doubled) * 2);
64
+
65
+ ctx.effect(() => {
66
+ if (get($count) > 25) {
67
+ console.log($doubled.value);
68
+ get($quadrupled);
69
+ }
70
+ });
71
+ ```
72
+
73
+ vs old API:
74
+
75
+ ```js
76
+ // ----- Basic State ----- //
77
+
78
+ // Old
79
+ const [$count, setCount] = createState(5);
80
+ setCount((count) => count + 1);
81
+ $count.get(); // 6
82
+
83
+ // New
84
+ const count = atom(5);
85
+ count.value++;
86
+ count.value; // 6
87
+
88
+ // ----- Derived State ----- //
89
+
90
+ // Old
91
+ const $doubled = derive([$count], (count) => count * 2);
92
+ const $quadrupled = derive([$doubled], (doubled) => doubled * 2);
93
+
94
+ // New
95
+ const doubled = compose((get) => get(count) * 2);
96
+ const quadrupled = compose((get) => get(doubled) * 2);
97
+
98
+ // ----- Side Effects ----- //
99
+
100
+ // Old
101
+ ctx.watch([$count, $quadrupled], (count, quadrupled) => {
102
+ if (count > 25) {
103
+ console.log($doubled.get()); // not tracked
104
+
105
+ console.log(quadrupled);
106
+ // changes to $quadrupled will trigger this callback to re-run, even if count is still <= 25
107
+ }
108
+ });
109
+
110
+ // New
111
+ ctx.effect((get) => {
112
+ // count is tracked by reading it with 'get'
113
+ if (get(count) > 25) {
114
+ console.log(doubled.value); // not tracked
115
+
116
+ get(quadrupled); // only tracked while count > 25 (this 'get' doesn't run otherwise)
117
+ // changes to 'quadrupled' will NOT trigger this callback to re-run unless 'count' is already >= 25
118
+ }
119
+ });
120
+ ```
121
+
122
+ Atoms and composed values implement the `Reactive<T>` interface for TypeScript purposes. There is also `Atom<T>` and `Composed<T>` if you want to be specific.
123
+
124
+ The API above is a remix of Preact signals, Jotai and the TC39 Signals proposal. I strongly dislike automatic dependency tracking. I think the developer should explicitly describe what they want instead of having the language assume what they want and making them opt out with `untrack` and such. Madness.
125
+
126
+ It's also unintuitive what's a tracked scope and what isn't. There's nothing to tell you that at a glance. It's left up to the framework conventions. With this API you know; if you're in a function scope and you have a getter, you're in a tracking-capable scope. Doing that tracking is then left up to you. You can explicitly see that things are being tracked by reading the code. There is no background knowledge needed and no side effects required.
127
+
128
+ Further API:
129
+
130
+ ```js
131
+ // If count is reactive we get its current value. Otherwise we get it as is.
132
+ const value = unwrap(count);
133
+
134
+ // If count is reactive we get it as is (typed as Reactive<T>). Otherwise we get it wrapped as a Reactive<T>.
135
+ const value = reactive(count);
136
+ ```
137
+
138
+ ```js
139
+ const me = compose((get) => {
140
+ const id = get(userId);
141
+ return get(users)_?.find((u) => u.id === id);
142
+ });
143
+
144
+ const $me = derive([$userId, $users], (id, users) => users?.find((u) => u.id === id));
145
+ ```
146
+
147
+ ```js
148
+ const Counter = view("Counter", function () {
149
+ const count = atom(0);
150
+
151
+ return html`
152
+ <div>
153
+ <span>${count}</span>
154
+
155
+ <button onclick=${() => count.value++}>Increment</button>
156
+ <button onclick=${() => count.value--}>Decrement</button>
157
+ </div>
158
+ `;
159
+ });
160
+
161
+ const Routes = view("Routes", function () {
162
+ this.onMount(function () {
163
+ // this still refers to view context
164
+ this.log("hello!");
165
+ });
166
+
167
+ return html`
168
+ <div>
169
+ ${this.router(function () {
170
+ this.route("/path", View);
171
+
172
+ this.route("/nested", Layout, function () {
173
+ this.route("/test", Nested); // RouterOutlet passed as children
174
+ });
175
+ })}
176
+ </div>
177
+ `;
178
+ });
179
+
180
+ const CounterStore = store("Counter", function () {
181
+ const count = atom(0);
182
+
183
+ return {
184
+ count: compose(() => get(count)),
185
+
186
+ increment() {
187
+ count.value++;
188
+ },
189
+ decrement() {
190
+ count.value--;
191
+ },
192
+ };
193
+ });
194
+
195
+ const Counter = view("Counter", function () {
196
+ const counter = this.attach(CounterStore);
197
+
198
+ // const { count, increment, decrement } = this.get(CounterStore);
199
+
200
+ return html`
201
+ <div>
202
+ <span>${counter.count}</span>
203
+
204
+ <button onclick=${counter.increment}>Increment</button>
205
+ <button onclick=${counter.decrement}>Decrement</button>
206
+ </div>
207
+ `;
208
+ });
209
+ ```
@@ -0,0 +1,56 @@
1
+ # Context routing
2
+
3
+ I had an idea to integrate routing back into views instead of having a separate router. I originally had it work this way but I wasn't good enough to pull it off then.
4
+
5
+ Here's how it might look:
6
+
7
+ ```jsx
8
+ function Example(props, ctx) {
9
+ return <div>
10
+ <header>
11
+ <h1>Some kind of layout.</h1>
12
+ </header>
13
+ <main>
14
+ {ctx.router([
15
+ // Route path is relative to parent routes.
16
+ // Nested route definitions are a thing of the past.
17
+ // View could define its own routes.
18
+ { path: "something/*", view: SomethingView }
19
+ ])}
20
+ </main>
21
+ <div>
22
+ }
23
+
24
+ html`
25
+ <${Router}>
26
+ <${Route} path="something/*">
27
+ This child content isn't materialized until the route matches.
28
+ <${SomethingView} />
29
+ <//>
30
+ <//>
31
+ `
32
+ ```
33
+
34
+ This removes the need to import and define the whole app at the top level. You can also add routes as you need them when prototyping.
35
+
36
+ Route matching would be done by forwarding the wildcard portion of a match to child routers. Routers would be stored on the element context.
37
+
38
+ Where would route info be stored then? It would have to be on the context as well.
39
+
40
+ ```js
41
+ // Merged data from all parent segments.
42
+ ctx.route.params;
43
+ ctx.route.query;
44
+ ctx.route.path;
45
+ ctx.route.pattern;
46
+
47
+ ctx.go("/some/path");
48
+ ctx.back();
49
+ ctx.forward();
50
+ ```
51
+
52
+ ## Thoughts
53
+
54
+ - Would eliminate the need for `ctx.outlet()`. Can pass children directly via `children` prop now.
55
+ - Would eliminate the need for `ViewElement` and `setChildView` method because a top level router no longer needs to call it.
56
+ - Couldn't do the current routing strategy to combining all routes into one flat list at the top level.