@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.
- package/README.md +31 -964
- package/dist/core/context.d.ts +53 -0
- package/dist/{modules → core}/dolla.d.ts +43 -26
- package/dist/core/markup.d.ts +90 -0
- package/dist/core/nodes/dom.d.ts +13 -0
- package/dist/core/nodes/dynamic.d.ts +28 -0
- package/dist/core/nodes/html.d.ts +33 -0
- package/dist/core/nodes/list.d.ts +28 -0
- package/dist/core/nodes/outlet.d.ts +19 -0
- package/dist/core/nodes/portal.d.ts +22 -0
- package/dist/core/nodes/view.d.ts +78 -0
- package/dist/core/ref.d.ts +28 -0
- package/dist/core/signals.d.ts +127 -0
- package/dist/core/store.d.ts +52 -0
- package/dist/core/symbols.d.ts +4 -0
- package/dist/{views → core/views}/passthrough.d.ts +1 -1
- package/dist/{modules/http.d.ts → http/index.d.ts} +3 -5
- package/dist/index.d.ts +14 -11
- package/dist/index.js +986 -1216
- package/dist/index.js.map +1 -1
- package/dist/jsx-dev-runtime.d.ts +2 -2
- package/dist/jsx-dev-runtime.js +2 -2
- package/dist/jsx-dev-runtime.js.map +1 -1
- package/dist/jsx-runtime.d.ts +3 -3
- package/dist/jsx-runtime.js +2 -2
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/markup-DkQI155j.js +1447 -0
- package/dist/markup-DkQI155j.js.map +1 -0
- package/dist/{modules/router.d.ts → router/index.d.ts} +37 -48
- package/dist/router/router.utils.test.d.ts +1 -0
- package/dist/translate/index.d.ts +133 -0
- package/dist/typeChecking.d.ts +2 -98
- package/dist/typeChecking.test.d.ts +1 -0
- package/dist/types.d.ts +12 -14
- package/dist/utils.d.ts +18 -3
- package/docs/http.md +29 -0
- package/docs/i18n.md +38 -0
- package/docs/index.md +10 -0
- package/docs/router.md +80 -0
- package/docs/setup.md +31 -0
- package/docs/signals.md +149 -0
- package/docs/state.md +141 -0
- package/docs/stores.md +62 -0
- package/docs/views.md +208 -0
- package/index.d.ts +2 -2
- package/notes/TODO.md +6 -0
- package/notes/atomic.md +209 -0
- package/notes/context-routes.md +56 -0
- package/notes/elimination.md +33 -0
- package/notes/readme-scratch.md +260 -0
- package/notes/route-middleware.md +42 -0
- package/notes/scratch.md +330 -7
- package/notes/stores.md +53 -0
- package/package.json +14 -10
- package/vite.config.js +5 -10
- package/build.js +0 -34
- package/dist/markup.d.ts +0 -100
- package/dist/modules/language.d.ts +0 -41
- package/dist/modules/render.d.ts +0 -17
- package/dist/nodes/cond.d.ts +0 -26
- package/dist/nodes/html.d.ts +0 -31
- package/dist/nodes/observer.d.ts +0 -29
- package/dist/nodes/outlet.d.ts +0 -22
- package/dist/nodes/portal.d.ts +0 -19
- package/dist/nodes/repeat.d.ts +0 -34
- package/dist/nodes/text.d.ts +0 -19
- package/dist/passthrough-BSLd3foL.js +0 -1245
- package/dist/passthrough-BSLd3foL.js.map +0 -1
- package/dist/signals.d.ts +0 -101
- package/dist/view.d.ts +0 -50
- package/tests/signals.test.js +0 -135
- /package/dist/{routing.test.d.ts → core/signals.test.d.ts} +0 -0
- /package/dist/{views → core/views}/default-crash-view.d.ts +0 -0
- /package/dist/{routing.d.ts → router/router.utils.d.ts} +0 -0
package/dist/typeChecking.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
type TypeNames = "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | "null" | "array" | "class" | "promise" | "NaN";
|
|
1
|
+
type TypeNames = "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | "null" | "array" | "class" | "promise" | "map" | "set" | "NaN";
|
|
2
2
|
/**
|
|
3
3
|
* Extends `typeof` operator with more specific and useful type distinctions.
|
|
4
4
|
*/
|
|
5
|
-
export declare function typeOf(value:
|
|
5
|
+
export declare function typeOf(value: any): TypeNames;
|
|
6
6
|
/**
|
|
7
7
|
* Throws a TypeError unless `condition` is truthy.
|
|
8
8
|
*
|
|
@@ -18,12 +18,6 @@ export declare function isArray(value: unknown): value is Array<unknown>;
|
|
|
18
18
|
* Throws an error if `value` is not an array.
|
|
19
19
|
*/
|
|
20
20
|
export declare function assertArray(value: unknown, errorMessage?: string): value is Array<unknown>;
|
|
21
|
-
/**
|
|
22
|
-
* Returns a function that takes a `value` and ensures that it is an array for which `check` returns true for every item.
|
|
23
|
-
*
|
|
24
|
-
* @param check - Function to check items against.
|
|
25
|
-
*/
|
|
26
|
-
export declare function isArrayOf<T>(check: (item: unknown) => boolean): (value: unknown) => value is T[];
|
|
27
21
|
/**
|
|
28
22
|
* Returns true when `value` is an array and `check` returns true for every item.
|
|
29
23
|
*
|
|
@@ -31,12 +25,6 @@ export declare function isArrayOf<T>(check: (item: unknown) => boolean): (value:
|
|
|
31
25
|
* @param value - A possible array.
|
|
32
26
|
*/
|
|
33
27
|
export declare function isArrayOf<T>(check: (item: unknown) => boolean, value: unknown): value is T[];
|
|
34
|
-
/**
|
|
35
|
-
* Returns a function that takes a `value` and throws a TypeError unless it is an array for which `check` returns true for every item.
|
|
36
|
-
*
|
|
37
|
-
* @param check - Function to check items against.
|
|
38
|
-
*/
|
|
39
|
-
export declare function assertArrayOf<T>(check: (item: unknown) => boolean): (value: unknown) => value is T[];
|
|
40
28
|
/**
|
|
41
29
|
* Throws a TypeError unless `value` is an array and `check` returns true for every item.
|
|
42
30
|
*
|
|
@@ -45,14 +33,6 @@ export declare function assertArrayOf<T>(check: (item: unknown) => boolean): (va
|
|
|
45
33
|
* @param errorMessage - A custom error message.
|
|
46
34
|
*/
|
|
47
35
|
export declare function assertArrayOf<T>(check: (item: unknown) => boolean, value: unknown, errorMessage?: string): value is T[];
|
|
48
|
-
/**
|
|
49
|
-
* Returns true if `value` is equal to `true` or `false`.
|
|
50
|
-
*/
|
|
51
|
-
export declare function isBoolean(value: unknown): value is boolean;
|
|
52
|
-
/**
|
|
53
|
-
* Throws a TypeError unless `value` is equal to `true` or `false`.
|
|
54
|
-
*/
|
|
55
|
-
export declare function assertBoolean(value: unknown, errorMessage?: string): value is boolean;
|
|
56
36
|
/**
|
|
57
37
|
* Returns true if `value` is a string.
|
|
58
38
|
*/
|
|
@@ -77,34 +57,6 @@ export declare function isNumber(value: unknown): value is number;
|
|
|
77
57
|
* Throws a TypeError unless `value` is a number.
|
|
78
58
|
*/
|
|
79
59
|
export declare function assertNumber(value: unknown, errorMessage?: string): value is number;
|
|
80
|
-
/**
|
|
81
|
-
* Returns true if `value` implements the Promise protocol.
|
|
82
|
-
* This matches true instances of Promise as well as any object that
|
|
83
|
-
* implements `next`, `catch` and `finally` methods.
|
|
84
|
-
*
|
|
85
|
-
* To strictly match instances of Promise, use `isInstanceOf(Promise)`.
|
|
86
|
-
*/
|
|
87
|
-
export declare function isPromise<T = unknown>(value: unknown): value is Promise<T>;
|
|
88
|
-
/**
|
|
89
|
-
* Throws a TypeError unless `value` implements the Promise protocol.
|
|
90
|
-
* This matches true instances of Promise as well as any object that
|
|
91
|
-
* implements `next`, `catch` and `finally` methods.
|
|
92
|
-
*
|
|
93
|
-
* To strictly allow only instances of Promise, use `Type.assertInstanceOf(Promise)`.
|
|
94
|
-
*/
|
|
95
|
-
export declare function assertPromise<T = unknown>(value: unknown, errorMessage?: string): value is Promise<T>;
|
|
96
|
-
/**
|
|
97
|
-
* Returns true if `value` is a class.
|
|
98
|
-
*/
|
|
99
|
-
export declare function isClass(value: unknown): value is {
|
|
100
|
-
new (): unknown;
|
|
101
|
-
};
|
|
102
|
-
/**
|
|
103
|
-
* Throws a TypeError unless `value` is a class.
|
|
104
|
-
*/
|
|
105
|
-
export declare function assertClass(value: unknown, errorMessage?: string): value is {
|
|
106
|
-
new (): unknown;
|
|
107
|
-
};
|
|
108
60
|
/**
|
|
109
61
|
* Returns a function that takes a `value` and returns true if `value` is an instance of `constructor`.
|
|
110
62
|
*
|
|
@@ -132,30 +84,6 @@ export declare function assertInstanceOf<T extends Function>(constructor: T): (v
|
|
|
132
84
|
* @param errorMessage - A custom error message for when the assertion fails.
|
|
133
85
|
*/
|
|
134
86
|
export declare function assertInstanceOf<T extends Function>(constructor: T, value: unknown, errorMessage?: string): value is T;
|
|
135
|
-
/**
|
|
136
|
-
* Returns true if `value` is a Map.
|
|
137
|
-
*/
|
|
138
|
-
export declare function isMap<K = unknown, V = unknown>(value: any): value is Map<K, V>;
|
|
139
|
-
/**
|
|
140
|
-
* Throws a TypeError unless `value` is a Map.
|
|
141
|
-
*/
|
|
142
|
-
export declare function assertMap<K = unknown, V = unknown>(value: any, errorMessage?: string): value is Map<K, V>;
|
|
143
|
-
/**
|
|
144
|
-
* Returns true if `value` is a Set.
|
|
145
|
-
*/
|
|
146
|
-
export declare function isSet<T = unknown>(value: any): value is Set<T>;
|
|
147
|
-
/**
|
|
148
|
-
* Throws a TypeError if `value` is not a Set.
|
|
149
|
-
*/
|
|
150
|
-
export declare function assertSet<T = unknown>(value: any, errorMessage?: string): value is Set<T>;
|
|
151
|
-
/**
|
|
152
|
-
* Returns true if `value` implements the Iterable protocol.
|
|
153
|
-
*/
|
|
154
|
-
export declare function isIterable<T>(value: any): value is Iterable<T>;
|
|
155
|
-
/**
|
|
156
|
-
* Throws a TypeError unless `value` implements the Iterable protocol.
|
|
157
|
-
*/
|
|
158
|
-
export declare function assertIterable<T>(value: any, errorMessage?: string): value is Iterable<T>;
|
|
159
87
|
/**
|
|
160
88
|
* Returns true if `value` is a plain JavaScript object.
|
|
161
89
|
*/
|
|
@@ -164,28 +92,4 @@ export declare function isObject(value: unknown): value is Record<string | numbe
|
|
|
164
92
|
* Throws a TypeError unless `value` is a plain JavaScript object.
|
|
165
93
|
*/
|
|
166
94
|
export declare function assertObject(value: unknown, errorMessage?: string): value is object;
|
|
167
|
-
/**
|
|
168
|
-
* Returns true if `value` is equal to `null`.
|
|
169
|
-
*/
|
|
170
|
-
export declare function isNull(value: unknown): value is null;
|
|
171
|
-
/**
|
|
172
|
-
* Throws a TypeError unless `value` is equal to `null`.
|
|
173
|
-
*/
|
|
174
|
-
export declare function assertNull(value: unknown, errorMessage?: string): value is null;
|
|
175
|
-
/**
|
|
176
|
-
* Returns true if `value` is equal to `undefined`.
|
|
177
|
-
*/
|
|
178
|
-
export declare function isUndefined(value: unknown): value is undefined;
|
|
179
|
-
/**
|
|
180
|
-
* Throws a TypeError unless `value` is equal to `undefined`.
|
|
181
|
-
*/
|
|
182
|
-
export declare function assertUndefined(value: unknown, errorMessage?: string): value is undefined;
|
|
183
|
-
/**
|
|
184
|
-
* Returns true if `value` is equal to `null` or `undefined`.
|
|
185
|
-
*/
|
|
186
|
-
export declare function isEmpty(value: unknown): value is void;
|
|
187
|
-
/**
|
|
188
|
-
* Throws a TypeError unless `value` is equal to `null` or `undefined`.
|
|
189
|
-
*/
|
|
190
|
-
export declare function assertEmpty(value: unknown, errorMessage?: string): value is void;
|
|
191
95
|
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import type * as CSS from "csstype";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import type { Markup } from "./core/markup.js";
|
|
3
|
+
import { Reactive } from "./core/signals.js";
|
|
4
4
|
/**
|
|
5
5
|
* Represents everything that can be handled as a DOM node.
|
|
6
6
|
* These are all the items considered valid to pass as children to any element.
|
|
7
7
|
*/
|
|
8
|
-
export type Renderable = string | number | Markup | false | null | undefined |
|
|
8
|
+
export type Renderable = string | number | Markup | false | null | undefined | Reactive<any> | (string | number | Markup | false | null | undefined | Reactive<any>)[];
|
|
9
9
|
export type Stringable = {
|
|
10
10
|
toString(): string;
|
|
11
11
|
};
|
|
12
|
-
type
|
|
13
|
-
type OptionalProperty<T> =
|
|
14
|
-
type RequiredProperty<T> = T |
|
|
12
|
+
type MaybeReactive<T> = T | Reactive<T> | Reactive<T | undefined>;
|
|
13
|
+
type OptionalProperty<T> = MaybeReactive<T>;
|
|
14
|
+
type RequiredProperty<T> = T | Reactive<T>;
|
|
15
15
|
type AutocapitalizeValues = "off" | "on" | "none" | "sentences" | "words" | "characters";
|
|
16
16
|
type ContentEditableValues = true | false | "true" | "false" | "plaintext-only" | "inherit";
|
|
17
17
|
type ClassListValues = string | ClassMap | Array<string | ClassMap | (string | ClassMap)[]>;
|
|
@@ -30,7 +30,6 @@ export interface ElementProps {
|
|
|
30
30
|
/**
|
|
31
31
|
* Object of event listeners.
|
|
32
32
|
*/
|
|
33
|
-
eventListeners?: OptionalProperty<Record<string, EventHandler<Event>>>;
|
|
34
33
|
/**
|
|
35
34
|
* CSS classes to be applied to this element. In addition to the standard space-separated list of class names,
|
|
36
35
|
* this property also supports a class map object with class names as keys and booleans as values.
|
|
@@ -110,7 +109,7 @@ export interface ElementProps {
|
|
|
110
109
|
*
|
|
111
110
|
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/style
|
|
112
111
|
*/
|
|
113
|
-
style?: string | CSSProperties |
|
|
112
|
+
style?: string | CSSProperties | Reactive<string> | Reactive<CSSProperties> | Reactive<string | CSSProperties> | Reactive<string | undefined> | Reactive<CSSProperties | undefined> | Reactive<string | CSSProperties | undefined>;
|
|
114
113
|
/**
|
|
115
114
|
* Fired when a CSS animation unexpectedly aborts.
|
|
116
115
|
*
|
|
@@ -1210,7 +1209,7 @@ export type CSSProperties = {
|
|
|
1210
1209
|
[K in keyof Styles]: OptionalProperty<Styles[K]>;
|
|
1211
1210
|
};
|
|
1212
1211
|
export interface ClassMap {
|
|
1213
|
-
[className: string]:
|
|
1212
|
+
[className: string]: MaybeReactive<any>;
|
|
1214
1213
|
}
|
|
1215
1214
|
export type EventHandler<E> = (event: E) => void;
|
|
1216
1215
|
export interface PropertiesOf<E extends HTMLElement> extends HTMLElementProps {
|
|
@@ -1219,9 +1218,9 @@ export interface PropertiesOf<E extends HTMLElement> extends HTMLElementProps {
|
|
|
1219
1218
|
*/
|
|
1220
1219
|
children?: any;
|
|
1221
1220
|
/**
|
|
1222
|
-
*
|
|
1221
|
+
* Receives a reference to the DOM node when rendered.
|
|
1223
1222
|
*/
|
|
1224
|
-
ref?:
|
|
1223
|
+
ref?: ((value: E | undefined) => void) | ((value: HTMLElement | undefined) => void) | ((value: Element | undefined) => void) | ((value: Node | undefined) => void);
|
|
1225
1224
|
}
|
|
1226
1225
|
/**
|
|
1227
1226
|
* The following elements are defined based on the WHATWG HTML spec:
|
|
@@ -2098,7 +2097,7 @@ interface HTMLMediaElementProps<T extends HTMLMediaElement> extends HTMLElementP
|
|
|
2098
2097
|
*
|
|
2099
2098
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject
|
|
2100
2099
|
*/
|
|
2101
|
-
srcObject?: MediaStream | MediaSource | Blob | File |
|
|
2100
|
+
srcObject?: MediaStream | MediaSource | Blob | File | Reactive<MediaStream> | Reactive<MediaStream | undefined> | Reactive<MediaSource> | Reactive<MediaSource | undefined> | Reactive<Blob> | Reactive<Blob | undefined> | Reactive<File> | Reactive<File | undefined>;
|
|
2102
2101
|
/**
|
|
2103
2102
|
* The current audio volume of the media element. Must be a number between 0 and 1.
|
|
2104
2103
|
*
|
|
@@ -2597,7 +2596,7 @@ interface HTMLImageElementProps extends PropertiesOf<HTMLImageElement> {
|
|
|
2597
2596
|
*
|
|
2598
2597
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/sizes
|
|
2599
2598
|
*/
|
|
2600
|
-
sizes?:
|
|
2599
|
+
sizes?: MaybeReactive<string>;
|
|
2601
2600
|
/**
|
|
2602
2601
|
* The image URL.
|
|
2603
2602
|
*
|
|
@@ -3143,7 +3142,6 @@ interface HTMLInputElementProps extends PropertiesOf<HTMLInputElement> {
|
|
|
3143
3142
|
step?: OptionalProperty<number>;
|
|
3144
3143
|
type?: OptionalProperty<InputType>;
|
|
3145
3144
|
value?: OptionalProperty<string>;
|
|
3146
|
-
$$value?: SettableSignal<any>;
|
|
3147
3145
|
width?: OptionalProperty<string | number> | OptionalProperty<string> | OptionalProperty<number>;
|
|
3148
3146
|
title?: OptionalProperty<string>;
|
|
3149
3147
|
/**
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
export declare const noOp: () => void;
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function getUniqueId(): string;
|
|
3
|
+
/**
|
|
4
|
+
* Equality check that passes if both values are the same object.
|
|
5
|
+
* This is the default equality check for states.
|
|
6
|
+
*/
|
|
7
|
+
export declare function strictEqual(a: any, b: any): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Equality check that passes if both values are the same object, or if both are objects or arrays with equal keys and values.
|
|
10
|
+
*/
|
|
11
|
+
export declare function shallowEqual(a: any, b: any): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Equality check that passes if two objects have equal values, even if they are not the same object.
|
|
14
|
+
*/
|
|
15
|
+
export declare function deepEqual(a: any, b: any): boolean;
|
|
3
16
|
/**
|
|
4
17
|
* Takes an old value and a new value. Returns a merged copy if both are objects, otherwise returns the new value.
|
|
5
18
|
*/
|
|
@@ -13,8 +26,10 @@ export declare function merge(one: unknown, two: unknown): any;
|
|
|
13
26
|
* @param object - An object to clone without the omitted keys.
|
|
14
27
|
*/
|
|
15
28
|
export declare function omit<O extends Record<any, any>>(keys: (keyof O)[], object: O): Record<any, any>;
|
|
16
|
-
|
|
17
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Takes any string and returns an OKLCH color.
|
|
31
|
+
*/
|
|
32
|
+
export declare function okhash(value: string): string;
|
|
18
33
|
export type MatcherFunction = (value: string) => boolean;
|
|
19
34
|
/**
|
|
20
35
|
* Parses a filter string into a matcher function.
|
package/docs/http.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# HTTP Client
|
|
2
|
+
|
|
3
|
+
> TODO: Write me.
|
|
4
|
+
|
|
5
|
+
This page goes into detail on how to use the built in HTTP client.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import Dolla from "@manyducks.co/dolla";
|
|
9
|
+
|
|
10
|
+
Dolla.http.use(async (req, next) => {
|
|
11
|
+
// Apply auth header to all API routes.
|
|
12
|
+
if (req.url.pathname.startsWith("/api/")) {
|
|
13
|
+
req.headers.set("authorization", `Bearer ${localStorage.getItem("api-key")}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
await next();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const res = await Dolla.http.get("/api/some-api-route");
|
|
20
|
+
res.body; // body is already parsed as JSON if server responded with JSON
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
End.
|
|
26
|
+
|
|
27
|
+
- [🗂️ Docs](./index.md)
|
|
28
|
+
- [🏠 README](../README.md)
|
|
29
|
+
- [🦆 That's a lot of ducks.](https://www.manyducks.co)
|
package/docs/i18n.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Internationalization (i18n) Support
|
|
2
|
+
|
|
3
|
+
```jsx
|
|
4
|
+
import Dolla, { createState, t } from "@manyducks.co/dolla";
|
|
5
|
+
|
|
6
|
+
function CounterView(props) {
|
|
7
|
+
const [$count, setCount] = createState(0);
|
|
8
|
+
|
|
9
|
+
const increment = () => {
|
|
10
|
+
setCount((count) => count + 1);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div>
|
|
15
|
+
<p>Clicks: {$count}</p>
|
|
16
|
+
<button onClick={increment}>{t("buttonLabel")}</button>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
Dolla.i18n.setup({
|
|
22
|
+
locale: "en",
|
|
23
|
+
translations: [
|
|
24
|
+
{ locale: "en", strings: { buttonLabel: "Click here to increment" } },
|
|
25
|
+
{ locale: "ja", strings: { buttonLabel: "ここに押して増加する" } },
|
|
26
|
+
],
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
Dolla.mount(document.body, CounterView);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
End.
|
|
35
|
+
|
|
36
|
+
- [🗂️ Docs](./index.md)
|
|
37
|
+
- [🏠 README](../README.md)
|
|
38
|
+
- [🦆 That's a lot of ducks.](https://www.manyducks.co)
|
package/docs/index.md
ADDED
package/docs/router.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Router
|
|
2
|
+
|
|
3
|
+
Dolla makes heavy use of client-side routing. You can define as many routes as you have views, and the URL
|
|
4
|
+
will determine which one the app shows at any given time. By building an app around routes, lots of things one expects
|
|
5
|
+
from a web app will just work; back and forward buttons, sharable URLs, bookmarks, etc.
|
|
6
|
+
|
|
7
|
+
Routes are matched by highest specificity regardless of the order they were registered.
|
|
8
|
+
This avoids some confusing situations that come up with order-based routers like that of `express`.
|
|
9
|
+
On the other hand, order-based routers can support regular expressions as patterns which Dolla's router cannot.
|
|
10
|
+
|
|
11
|
+
## Route Patterns
|
|
12
|
+
|
|
13
|
+
Routes are defined with strings called patterns. A pattern defines the shape the URL path must match, with special
|
|
14
|
+
placeholders for variables that appear within the route. Values matched by those placeholders are parsed out and exposed
|
|
15
|
+
to your code (`router` store, `$params` readable). Below are some examples of patterns and how they work.
|
|
16
|
+
|
|
17
|
+
- Static: `/this/is/static` has no params and will match only when the route is exactly `/this/is/static`.
|
|
18
|
+
- Numeric params: `/users/{#id}/edit` has the named param `{#id}` which matches numbers only, such as `123` or `52`. The
|
|
19
|
+
resulting value will be parsed as a number.
|
|
20
|
+
- Generic params: `/users/{name}` has the named param `{name}` which matches anything in that position in the path. The
|
|
21
|
+
resulting value will be a string.
|
|
22
|
+
- Wildcard: `/users/*` will match anything beginning with `/users` and store everything after that in params
|
|
23
|
+
as `wildcard`. `*` is valid only at the end of a route.
|
|
24
|
+
|
|
25
|
+
Now, here are some route examples in the context of an app:
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import Dolla, { createRouter } from "@manyducks.co/dolla";
|
|
29
|
+
import { ThingIndex, ThingDetails, ThingEdit, ThingDelete } from "./views.js";
|
|
30
|
+
|
|
31
|
+
const router = createRouter({
|
|
32
|
+
routes: [
|
|
33
|
+
{
|
|
34
|
+
// A `null` component with subroutes acts as a namespace for those subroutes.
|
|
35
|
+
// Passing a view instead of `null` results in subroutes being rendered inside that view wherever `ctx.outlet()` is called.
|
|
36
|
+
path: "/things",
|
|
37
|
+
view: null,
|
|
38
|
+
routes: [
|
|
39
|
+
{ path: "/", view: ThingIndex }, // matches `/things`
|
|
40
|
+
{ path: "/{#id}", view: ThingDetails }, // matches `/things/{#id}`
|
|
41
|
+
{ path: "/{#id}/edit", view: ThingEdit }, // matches `/things/{#id}/edit`
|
|
42
|
+
{ path: "/{#id}/delete", view: ThingDelete }, // matches `/things/{#id}/delete`
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
// All routes that don't match anything else will redirect to `/things`
|
|
46
|
+
{ path: "*", redirect: "/things" },
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Mount the router in place of a view.
|
|
51
|
+
Dolla.mount(document.body, router);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
When the URL matches a pattern the corresponding view is displayed. If we visit `/people/john`,
|
|
55
|
+
we will see the `PersonDetails` view and the params will be `{ name: "john" }`. Params can be
|
|
56
|
+
accessed from anywhere in the app through `Dolla.router`.
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
import Dolla from "@manyducks.co/dolla";
|
|
60
|
+
|
|
61
|
+
// Info about the current route is exported as a set of Readables. Query params are also Writable through $$query:
|
|
62
|
+
const { $path, $pattern, $params, $query } = router;
|
|
63
|
+
|
|
64
|
+
router.back(); // Step back in the history to the previous route, if any.
|
|
65
|
+
router.back(2); // Hit the back button twice.
|
|
66
|
+
|
|
67
|
+
router.forward(); // Step forward in the history to the next route, if any.
|
|
68
|
+
router.forward(4); // Hit the forward button 4 times.
|
|
69
|
+
|
|
70
|
+
router.go("/things/152"); // Navigate to another path within the same app.
|
|
71
|
+
router.go("https://www.example.com/another/site"); // Navigate to another domain entirely.
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
End.
|
|
77
|
+
|
|
78
|
+
- [🗂️ Docs](./index.md)
|
|
79
|
+
- [🏠 README](../README.md)
|
|
80
|
+
- [🦆 That's a lot of ducks.](https://www.manyducks.co)
|
package/docs/setup.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Setting up Dolla
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
Dolla is published on npm as `@manyducks.co/dolla`. You can install it in your project with the following command:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npm i @manyducks.co/dolla
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## JSX
|
|
12
|
+
|
|
13
|
+
If you want to use JSX in your app you can add the following options to your `tsconfig.json` or `jsconfig.json`. Modern build systems like [Vite](https://vite.dev) will pick these up automatically.
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"compilerOptions": {
|
|
18
|
+
// ... other options ...
|
|
19
|
+
"jsx": "react-jsx",
|
|
20
|
+
"jsxImportSource": "@manyducks.co/dolla"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
End.
|
|
28
|
+
|
|
29
|
+
- [🗂️ Docs](./index.md)
|
|
30
|
+
- [🏠 README](../README.md)
|
|
31
|
+
- [🦆 That's a lot of ducks.](https://www.manyducks.co)
|
package/docs/signals.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
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
|
+
- `atom`
|
|
19
|
+
- `compose`
|
|
20
|
+
- `effect`
|
|
21
|
+
- `get`
|
|
22
|
+
- `peek`
|
|
23
|
+
- `set`
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Basic State API
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
import { createState } from "@manyducks.co/dolla";
|
|
30
|
+
|
|
31
|
+
// Equivalent to React's `useState` or Solid's `createSignal`.
|
|
32
|
+
// A new read-only State and linked Setter are created.
|
|
33
|
+
const [$count, setCount] = createState(72);
|
|
34
|
+
|
|
35
|
+
// Get the current value.
|
|
36
|
+
$count.get(): // 72
|
|
37
|
+
|
|
38
|
+
// Set a new value.
|
|
39
|
+
setCount(300);
|
|
40
|
+
|
|
41
|
+
// The State now reflects the latest value.
|
|
42
|
+
$count.get(); // 300
|
|
43
|
+
|
|
44
|
+
// Data can also be updated by passing a function.
|
|
45
|
+
// This function takes the current state and returns a new one.
|
|
46
|
+
setCount((current) => current + 1);
|
|
47
|
+
$count.get(); // 301
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Deriving States from other States
|
|
51
|
+
|
|
52
|
+
#### Example 1: Doubled
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
import { createState, derive } from "@manyducks.co/dolla";
|
|
56
|
+
|
|
57
|
+
const [$count, setCount] = createState(1);
|
|
58
|
+
|
|
59
|
+
const $doubled = derive([$count], (count) => count * 2);
|
|
60
|
+
|
|
61
|
+
setCount(10);
|
|
62
|
+
$doubled.get(); // 20
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
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.
|
|
66
|
+
|
|
67
|
+
#### Example 2: Selecting a User
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
import { createState, derive } from "@manyducks.co/dolla";
|
|
71
|
+
|
|
72
|
+
const [$users, setUsers] = createState([
|
|
73
|
+
{ id: 1, name: "Audie" },
|
|
74
|
+
{ id: 2, name: "Bob" },
|
|
75
|
+
{ id: 3, name: "Cabel" },
|
|
76
|
+
]);
|
|
77
|
+
const [$selectedUserId, setSelectedUserId] = createState(1);
|
|
78
|
+
|
|
79
|
+
const $selectedUser = derive([$users, $selectedUserId], (users, id) => {
|
|
80
|
+
return users.find((user) => user.id === id);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
$selectedUser.get(); // { id: 1, name: "Audie" }
|
|
84
|
+
|
|
85
|
+
setSelectedId(3);
|
|
86
|
+
|
|
87
|
+
$selectedUser.get(); // { id: 3, name: "Cabel" }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
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.
|
|
91
|
+
|
|
92
|
+
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.
|
|
93
|
+
|
|
94
|
+
#### Example 3: Narrowing Complex Data
|
|
95
|
+
|
|
96
|
+
```jsx
|
|
97
|
+
import { createState, derive } from "@manyducks.co/dolla";
|
|
98
|
+
|
|
99
|
+
const [$user, setUser] = createState({ id: 1, name: "Audie" });
|
|
100
|
+
|
|
101
|
+
const $name = derive([$user], (user) => user.name);
|
|
102
|
+
|
|
103
|
+
$name.get(); // "Audie"
|
|
104
|
+
|
|
105
|
+
// In a view:
|
|
106
|
+
<span class="user-name">{$name}</span>;
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
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.
|
|
110
|
+
|
|
111
|
+
### Converting to and from States
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
import { createState, toState, toValue } from "@manyducks.co/dolla";
|
|
115
|
+
|
|
116
|
+
const [$count, setCount] = createState(512);
|
|
117
|
+
|
|
118
|
+
// Unwrap the value of $count. Returns 512.
|
|
119
|
+
const count = toValue($count);
|
|
120
|
+
// Passing a non-state value will simply return it.
|
|
121
|
+
const name = toValue("World");
|
|
122
|
+
|
|
123
|
+
// Wrap "Hello" into a State containing "Hello"
|
|
124
|
+
const $value = toState("Hello");
|
|
125
|
+
// Passing a state will simply return that same state.
|
|
126
|
+
const $number = toState($count);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### In Views
|
|
130
|
+
|
|
131
|
+
```jsx
|
|
132
|
+
import { derive } from "@manyducks.co/dolla";
|
|
133
|
+
|
|
134
|
+
function UserNameView(props, ctx) {
|
|
135
|
+
const $name = derive([props.$user], (user) => user.name);
|
|
136
|
+
|
|
137
|
+
return <span class={{ "user-name": true, "is-selected": props.$selected }}>{$name}</span>;
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
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.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
End.
|
|
146
|
+
|
|
147
|
+
- [🗂️ Docs](./index.md)
|
|
148
|
+
- [🏠 README](../README.md)
|
|
149
|
+
- [🦆 That's a lot of ducks.](https://www.manyducks.co)
|