@manyducks.co/dolla 2.0.0-alpha.5 → 2.0.0-alpha.50

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 (83) hide show
  1. package/README.md +81 -933
  2. package/dist/core/context.d.ts +65 -0
  3. package/dist/{modules → core}/dolla.d.ts +43 -26
  4. package/dist/core/markup.d.ts +102 -0
  5. package/dist/core/nodes/dom.d.ts +13 -0
  6. package/dist/core/nodes/dynamic.d.ts +30 -0
  7. package/dist/core/nodes/fragment.d.ts +19 -0
  8. package/dist/core/nodes/html.d.ts +34 -0
  9. package/dist/core/nodes/outlet.d.ts +20 -0
  10. package/dist/core/nodes/portal.d.ts +22 -0
  11. package/dist/core/nodes/repeat.d.ts +28 -0
  12. package/dist/core/nodes/view.d.ts +97 -0
  13. package/dist/core/signals-api.d.ts +42 -0
  14. package/dist/core/signals.d.ts +22 -0
  15. package/dist/core/store.d.ts +52 -0
  16. package/dist/core/symbols.d.ts +4 -0
  17. package/dist/{views → core/views}/default-crash-view.d.ts +1 -1
  18. package/dist/core/views/fragment.d.ts +8 -0
  19. package/dist/{views → core/views}/passthrough.d.ts +2 -2
  20. package/dist/fragment-DjTOSAcw.js +8 -0
  21. package/dist/fragment-DjTOSAcw.js.map +1 -0
  22. package/dist/{modules/http.d.ts → http/index.d.ts} +3 -5
  23. package/dist/index.d.ts +15 -10
  24. package/dist/index.js +1056 -1216
  25. package/dist/index.js.map +1 -1
  26. package/dist/jsx-dev-runtime.d.ts +2 -2
  27. package/dist/jsx-dev-runtime.js +8 -8
  28. package/dist/jsx-dev-runtime.js.map +1 -1
  29. package/dist/jsx-runtime.d.ts +3 -3
  30. package/dist/jsx-runtime.js +10 -10
  31. package/dist/jsx-runtime.js.map +1 -1
  32. package/dist/markup-DZdmoqTk.js +1501 -0
  33. package/dist/markup-DZdmoqTk.js.map +1 -0
  34. package/dist/{modules/router.d.ts → router/index.d.ts} +53 -60
  35. package/dist/{routing.d.ts → router/router.utils.d.ts} +17 -3
  36. package/dist/router/router.utils.test.d.ts +1 -0
  37. package/dist/translate/index.d.ts +133 -0
  38. package/dist/typeChecking.d.ts +2 -98
  39. package/dist/typeChecking.test.d.ts +1 -0
  40. package/dist/types.d.ts +17 -7
  41. package/dist/utils.d.ts +18 -3
  42. package/docs/http.md +29 -0
  43. package/docs/i18n.md +38 -0
  44. package/docs/index.md +10 -0
  45. package/docs/router.md +80 -0
  46. package/docs/setup.md +31 -0
  47. package/docs/signals.md +166 -0
  48. package/docs/state.md +141 -0
  49. package/docs/stores.md +62 -0
  50. package/docs/views.md +208 -0
  51. package/examples/webcomponent/index.html +14 -0
  52. package/examples/webcomponent/main.js +165 -0
  53. package/index.d.ts +2 -2
  54. package/notes/TODO.md +6 -0
  55. package/notes/atomic.md +452 -0
  56. package/notes/context-routes.md +61 -0
  57. package/notes/custom-nodes.md +17 -0
  58. package/notes/elimination.md +33 -0
  59. package/notes/molecule.md +35 -0
  60. package/notes/readme-scratch.md +260 -0
  61. package/notes/route-middleware.md +42 -0
  62. package/notes/scratch.md +330 -7
  63. package/notes/splitting.md +5 -0
  64. package/notes/stores.md +53 -0
  65. package/package.json +13 -10
  66. package/vite.config.js +5 -10
  67. package/build.js +0 -34
  68. package/dist/markup.d.ts +0 -100
  69. package/dist/modules/language.d.ts +0 -41
  70. package/dist/modules/render.d.ts +0 -17
  71. package/dist/nodes/cond.d.ts +0 -26
  72. package/dist/nodes/html.d.ts +0 -31
  73. package/dist/nodes/observer.d.ts +0 -29
  74. package/dist/nodes/outlet.d.ts +0 -22
  75. package/dist/nodes/portal.d.ts +0 -19
  76. package/dist/nodes/repeat.d.ts +0 -34
  77. package/dist/nodes/text.d.ts +0 -19
  78. package/dist/passthrough-CtoBcpag.js +0 -1245
  79. package/dist/passthrough-CtoBcpag.js.map +0 -1
  80. package/dist/signals.d.ts +0 -101
  81. package/dist/view.d.ts +0 -50
  82. package/tests/signals.test.js +0 -135
  83. /package/dist/{routing.test.d.ts → core/signals-api.test.d.ts} +0 -0
@@ -0,0 +1,165 @@
1
+ import { $, effect, when } from "../../dist/index.js";
2
+
3
+ class EffectScope {
4
+ #observing = false;
5
+ #observers = [];
6
+ #cleanups = [];
7
+
8
+ get observing() {
9
+ return this.#observing;
10
+ }
11
+
12
+ observe(callback) {
13
+ this.#observers.push(callback);
14
+ if (this.#observing) {
15
+ this.#cleanups.push(effect(callback));
16
+ }
17
+ }
18
+
19
+ start() {
20
+ this.#observing = true;
21
+ for (const callback of this.#observers) {
22
+ this.#cleanups.push(effect(callback));
23
+ }
24
+ }
25
+
26
+ stop() {
27
+ this.#observing = false;
28
+ for (const cleanup of this.#cleanups) {
29
+ cleanup();
30
+ }
31
+ this.#cleanups.length = 0;
32
+ }
33
+ }
34
+
35
+ function setAttribute(el, attr, value) {
36
+ // handle special case attributes
37
+ el.setAttribute(attr, value);
38
+ }
39
+
40
+ function createElement(tag, attrs, children) {
41
+ // attrs can contain signals as values
42
+ // children can contain signals as values
43
+
44
+ // signals will be observed while the element is connected.
45
+
46
+ const el = document.createElement(tag);
47
+ const scope = new EffectScope();
48
+
49
+ for (const key in attrs) {
50
+ if (typeof attrs[key] === "function") {
51
+ // determine if it's an event handler or a signal
52
+ // this is determined by the name beginning with "on"
53
+ if (key.startsWith("on")) {
54
+ el.addEventListener(key.slice(2), attrs[key]);
55
+ } else {
56
+ // if signal then add an observer on the scope.
57
+ scope.observe(() => {
58
+ setAttribute(el, key, attrs[key]());
59
+ });
60
+ }
61
+ } else {
62
+ // attr is static
63
+ setAttribute(el, key, attrs[key]);
64
+ }
65
+ }
66
+
67
+ for (const child of children) {
68
+ if (typeof child === "function") {
69
+ // transform into a dynamic element
70
+ // for now just printing it as a string
71
+ const node = document.createTextNode(String(child()));
72
+ console.log("HELLO", node, child);
73
+ scope.observe(() => {
74
+ node.textContent = String(child());
75
+ });
76
+ el.appendChild(node);
77
+ } else if (child instanceof Node) {
78
+ el.appendChild(child);
79
+ } else {
80
+ el.appendChild(document.createTextNode(String(child)));
81
+ }
82
+ }
83
+
84
+ // run custom setup logic on connect and disconnect
85
+ el.$dolla = {
86
+ connected: false,
87
+ connect() {
88
+ if (this.connected) return;
89
+ console.log(el, "$dolla connected");
90
+ scope.start();
91
+
92
+ for (const child of el.childNodes) {
93
+ if ("$dolla" in child) {
94
+ child.$dolla.connect();
95
+ }
96
+ }
97
+ },
98
+ disconnect() {
99
+ if (!this.connected) return;
100
+ console.log(el, "$dolla disconnected");
101
+ scope.stop();
102
+
103
+ for (const child of el.childNodes) {
104
+ if ("$dolla" in child) {
105
+ child.$dolla.disconnect();
106
+ }
107
+ }
108
+ },
109
+ };
110
+
111
+ return el;
112
+ }
113
+
114
+ function mount(element, parent) {
115
+ // Observe childList changes on subtree and run lifecycle callbacks.
116
+ const observer = new MutationObserver((mutations) => {
117
+ for (const mutation of mutations) {
118
+ for (const node of mutation.addedNodes) {
119
+ if ("$dolla" in node) {
120
+ node.$dolla.connect();
121
+ }
122
+ }
123
+
124
+ for (const node of mutation.removedNodes) {
125
+ if ("$dolla" in node) {
126
+ node.$dolla.disconnect();
127
+ }
128
+ }
129
+ }
130
+ });
131
+ observer.observe(parent, { childList: true, subtree: true });
132
+
133
+ parent.appendChild(element);
134
+ }
135
+
136
+ // const html = htm.bind(createElement);
137
+
138
+ // need array of all event handler names
139
+
140
+ class DollaElement extends HTMLElement {
141
+ connectedCallback() {
142
+ if (this.template) {
143
+ mount(this.template, this);
144
+ }
145
+ }
146
+ }
147
+
148
+ class MyCounter extends DollaElement {
149
+ count = $(0);
150
+ increment = () => this.count((x) => x + 1);
151
+
152
+ template = createElement("div", {}, [
153
+ createElement("span", {}, ["Clicks: ", this.count]),
154
+ createElement("button", { onclick: this.increment }, ["Click Me!"]),
155
+ () => this.count() > 10 && createElement("span", {}, ["That's a lot of clicks."]),
156
+ ]);
157
+
158
+ // template = html`
159
+ // <span>Clicks: ${this.count}</span>
160
+ // <button onclick=${this.increment}>Click Me!</button>
161
+ // ${() => this.count() > 10 && html`<span>That's a lot!</span>`}
162
+ // `;
163
+ }
164
+
165
+ customElements.define("my-counter", MyCounter);
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,452 @@
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
+ ```
@@ -0,0 +1,61 @@
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
+ {this.router(function () {
15
+ this.route("something/*", SomethingView);
16
+ this.redirect("*", "./something/*");
17
+ })}
18
+
19
+ {ctx.router([
20
+ // Route path is relative to parent routes.
21
+ // Nested route definitions are a thing of the past.
22
+ // View could define its own routes.
23
+ { path: "something/*", view: SomethingView }
24
+ ])}
25
+ </main>
26
+ <div>
27
+ }
28
+
29
+ html`
30
+ <${Router}>
31
+ <${Route} path="something/*">
32
+ This child content isn't materialized until the route matches.
33
+ <${SomethingView} />
34
+ <//>
35
+ <//>
36
+ `
37
+ ```
38
+
39
+ 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.
40
+
41
+ Route matching would be done by forwarding the wildcard portion of a match to child routers. Routers would be stored on the element context.
42
+
43
+ Where would route info be stored then? It would have to be on the context as well.
44
+
45
+ ```js
46
+ // Merged data from all parent segments.
47
+ ctx.route.params;
48
+ ctx.route.query;
49
+ ctx.route.path;
50
+ ctx.route.pattern;
51
+
52
+ ctx.go("/some/path");
53
+ ctx.back();
54
+ ctx.forward();
55
+ ```
56
+
57
+ ## Thoughts
58
+
59
+ - Would eliminate the need for `ctx.outlet()`. Can pass children directly via `children` prop now.
60
+ - Would eliminate the need for `ViewElement` and `setChildView` method because a top level router no longer needs to call it.
61
+ - Couldn't do the current routing strategy of combining all routes into one flat list at the top level.
@@ -0,0 +1,17 @@
1
+ What if people could define custom Markup nodes and use them in JSX. Alternative to writing a view if you need lighter weight or more custom elements.
2
+
3
+ ```js
4
+ class CustomNode implements MarkupNode {
5
+ domNode = document.createElement('div');
6
+
7
+ isMounted = false;
8
+
9
+ mount(parent, before) {
10
+
11
+ }
12
+
13
+ unmount(parentIsUnmounting) {
14
+
15
+ }
16
+ }
17
+ ```