@manyducks.co/dolla 2.0.0-alpha.34 → 2.0.0-alpha.35

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 (45) hide show
  1. package/README.md +12 -14
  2. package/dist/core/{batch.d.ts → _batch.d.ts} +4 -0
  3. package/dist/core/context.d.ts +20 -17
  4. package/dist/core/dolla.d.ts +7 -48
  5. package/dist/core/markup.d.ts +9 -31
  6. package/dist/core/nodes/{observer.d.ts → dynamic.d.ts} +8 -10
  7. package/dist/core/nodes/html.d.ts +5 -7
  8. package/dist/core/nodes/{repeat.d.ts → list.d.ts} +12 -14
  9. package/dist/core/nodes/outlet.d.ts +4 -4
  10. package/dist/core/nodes/view.d.ts +22 -18
  11. package/dist/core/ref.d.ts +16 -0
  12. package/dist/core/signals.d.ts +128 -0
  13. package/dist/core/store.d.ts +9 -7
  14. package/dist/core/symbols.d.ts +0 -2
  15. package/dist/{views → core/views}/default-crash-view.d.ts +1 -1
  16. package/dist/{views → core/views}/passthrough.d.ts +2 -2
  17. package/dist/{modules/http.d.ts → http/index.d.ts} +1 -2
  18. package/dist/index.d.ts +8 -11
  19. package/dist/index.js +709 -846
  20. package/dist/index.js.map +1 -1
  21. package/dist/jsx-dev-runtime.d.ts +1 -1
  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 +1 -1
  25. package/dist/jsx-runtime.js +2 -2
  26. package/dist/jsx-runtime.js.map +1 -1
  27. package/dist/markup-BWJWLvDF.js +1634 -0
  28. package/dist/markup-BWJWLvDF.js.map +1 -0
  29. package/dist/{modules/router.d.ts → router/index.d.ts} +5 -5
  30. package/dist/router/router.utils.test.d.ts +1 -0
  31. package/dist/{modules/i18n.d.ts → translate/index.d.ts} +20 -16
  32. package/dist/types.d.ts +9 -9
  33. package/docs/signals.md +149 -0
  34. package/docs/views.md +1 -1
  35. package/notes/atomic.md +146 -0
  36. package/package.json +11 -7
  37. package/vite.config.js +3 -0
  38. package/dist/core/state.d.ts +0 -126
  39. package/dist/core/stats.d.ts +0 -31
  40. package/dist/markup-B3FV_fq9.js +0 -1525
  41. package/dist/markup-B3FV_fq9.js.map +0 -1
  42. package/notes/viewstate.md +0 -15
  43. package/tests/state.test.js +0 -135
  44. /package/dist/{modules/router.utils.test.d.ts → core/signals.test.d.ts} +0 -0
  45. /package/dist/{modules → router}/router.utils.d.ts +0 -0
package/dist/types.d.ts CHANGED
@@ -1,17 +1,17 @@
1
1
  import type * as CSS from "csstype";
2
2
  import type { Markup } from "./core/markup.js";
3
- import type { State } from "./core/state.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 | State<any> | (string | number | Markup | false | null | undefined | State<any>)[];
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 MaybeState<T> = T | State<T> | State<T | undefined>;
13
- type OptionalProperty<T> = T | State<T> | State<T | undefined>;
14
- type RequiredProperty<T> = T | State<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)[]>;
@@ -110,7 +110,7 @@ export interface ElementProps {
110
110
  *
111
111
  * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/style
112
112
  */
113
- style?: string | CSSProperties | State<string> | State<CSSProperties> | State<string | CSSProperties> | State<string | undefined> | State<CSSProperties | undefined> | State<string | CSSProperties | undefined>;
113
+ style?: string | CSSProperties | Reactive<string> | Reactive<CSSProperties> | Reactive<string | CSSProperties> | Reactive<string | undefined> | Reactive<CSSProperties | undefined> | Reactive<string | CSSProperties | undefined>;
114
114
  /**
115
115
  * Fired when a CSS animation unexpectedly aborts.
116
116
  *
@@ -1210,7 +1210,7 @@ export type CSSProperties = {
1210
1210
  [K in keyof Styles]: OptionalProperty<Styles[K]>;
1211
1211
  };
1212
1212
  export interface ClassMap {
1213
- [className: string]: MaybeState<any>;
1213
+ [className: string]: MaybeReactive<any>;
1214
1214
  }
1215
1215
  export type EventHandler<E> = (event: E) => void;
1216
1216
  export interface PropertiesOf<E extends HTMLElement> extends HTMLElementProps {
@@ -2098,7 +2098,7 @@ interface HTMLMediaElementProps<T extends HTMLMediaElement> extends HTMLElementP
2098
2098
  *
2099
2099
  * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject
2100
2100
  */
2101
- srcObject?: MediaStream | MediaSource | Blob | File | State<MediaStream> | State<MediaStream | undefined> | State<MediaSource> | State<MediaSource | undefined> | State<Blob> | State<Blob | undefined> | State<File> | State<File | undefined>;
2101
+ 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
2102
  /**
2103
2103
  * The current audio volume of the media element. Must be a number between 0 and 1.
2104
2104
  *
@@ -2597,7 +2597,7 @@ interface HTMLImageElementProps extends PropertiesOf<HTMLImageElement> {
2597
2597
  *
2598
2598
  * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/sizes
2599
2599
  */
2600
- sizes?: MaybeState<string>;
2600
+ sizes?: MaybeReactive<string>;
2601
2601
  /**
2602
2602
  * The image URL.
2603
2603
  *
@@ -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)
package/docs/views.md CHANGED
@@ -118,7 +118,7 @@ function ExampleView(props, ctx) {
118
118
  ```jsx
119
119
  function ExampleView(props, ctx) {
120
120
  // Set the name of this view's context. Console messages are prefixed with name.
121
- ctx.setName("CustomName");
121
+ ctx.name = "CustomName";
122
122
 
123
123
  // Print messages to the console. These are suppressed by default in the app's "production" mode.
124
124
  // You can also change which of these are printed and filter messages from certain contexts in the `createApp` options object.
@@ -0,0 +1,146 @@
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
+
59
+ const $count = atom(5);
60
+ $count.value++;
61
+ $count.value; // 6
62
+
63
+ const $doubled = compose(() => get($count) * 2);
64
+ const $quadrupled = compose(() => get($doubled) * 2);
65
+
66
+ ctx.effect(() => {
67
+ if (get($count) > 25) {
68
+ console.log($doubled.value);
69
+ get($quadrupled);
70
+ }
71
+ });
72
+ ```
73
+
74
+ vs old API:
75
+
76
+ ```js
77
+ // ----- Basic State ----- //
78
+
79
+ // Old
80
+ const [$count, setCount] = createState(5);
81
+ setCount((count) => count + 1);
82
+ $count.get(); // 6
83
+
84
+ // New
85
+ const count = atom(5);
86
+ count.value++;
87
+ count.value; // 6
88
+
89
+ // ----- Derived State ----- //
90
+
91
+ // Old
92
+ const $doubled = derive([$count], (count) => count * 2);
93
+ const $quadrupled = derive([$doubled], (doubled) => doubled * 2);
94
+
95
+ // New
96
+ const doubled = compose((get) => get(count) * 2);
97
+ const quadrupled = compose((get) => get(doubled) * 2);
98
+
99
+ // ----- Side Effects ----- //
100
+
101
+ // Old
102
+ ctx.watch([$count, $quadrupled], (count, quadrupled) => {
103
+ if (count > 25) {
104
+ console.log($doubled.get()); // not tracked
105
+
106
+ console.log(quadrupled);
107
+ // changes to $quadrupled will trigger this callback to re-run, even if count is still <= 25
108
+ }
109
+ });
110
+
111
+ // New
112
+ ctx.effect((get) => {
113
+ // count is tracked by reading it with 'get'
114
+ if (get(count) > 25) {
115
+ console.log(doubled.value); // not tracked
116
+
117
+ get(quadrupled); // only tracked while count > 25 (this 'get' doesn't run otherwise)
118
+ // changes to 'quadrupled' will NOT trigger this callback to re-run unless 'count' is already >= 25
119
+ }
120
+ });
121
+ ```
122
+
123
+ 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.
124
+
125
+ 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.
126
+
127
+ 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.
128
+
129
+ Further API:
130
+
131
+ ```js
132
+ // If count is reactive we get its current value. Otherwise we get it as is.
133
+ const value = unwrap(count);
134
+
135
+ // If count is reactive we get it as is (typed as Reactive<T>). Otherwise we get it wrapped as a Reactive<T>.
136
+ const value = reactive(count);
137
+ ```
138
+
139
+ ```js
140
+ const me = compose((get) => {
141
+ const id = get(userId);
142
+ return get(users)_?.find((u) => u.id === id);
143
+ });
144
+
145
+ const $me = derive([$userId, $users], (id, users) => users?.find((u) => u.id === id));
146
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manyducks.co/dolla",
3
- "version": "2.0.0-alpha.34",
3
+ "version": "2.0.0-alpha.35",
4
4
  "description": "Front-end components, routing and state management.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,7 +11,7 @@
11
11
  "url": "git+https://github.com/manyducksco/dolla.git"
12
12
  },
13
13
  "scripts": {
14
- "test": "npm run build && node --test",
14
+ "test": "vitest",
15
15
  "build:esbuild": "tsc && node build.js",
16
16
  "build": "vite build && tsc",
17
17
  "start": "tsc --watch",
@@ -39,15 +39,19 @@
39
39
  "types": "./jsx-dev-runtime.d.ts"
40
40
  }
41
41
  },
42
- "devDependencies": {
42
+ "dependencies": {
43
43
  "@manyducks.co/emitter": "^1.1.2",
44
- "@types/node": "^22.12.0",
45
- "csstype": "^3.1.3",
44
+ "alien-signals": "^1.0.3",
46
45
  "fast-deep-equal": "^3.1.3",
47
46
  "htm": "^3.1.1",
47
+ "simple-color-hash": "^1.0.2"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^22.12.0",
51
+ "csstype": "^3.1.3",
48
52
  "prettier": "^3.4.2",
49
- "simple-color-hash": "^1.0.2",
50
53
  "typescript": "^5.7.3",
51
- "vite": "^6.0.11"
54
+ "vite": "^6.0.11",
55
+ "vitest": "^3.0.5"
52
56
  }
53
57
  }
package/vite.config.js CHANGED
@@ -8,6 +8,9 @@ export default defineConfig({
8
8
  lib: {
9
9
  entry: {
10
10
  index: resolve(__dirname, "src/index.ts"),
11
+ // http: resolve(__dirname, "src/http/index.ts"),
12
+ // router: resolve(__dirname, "src/router/index.ts"),
13
+ // translate: resolve(__dirname, "src/translate/index.ts"),
11
14
  "jsx-runtime": resolve(__dirname, "src/jsx-runtime.js"),
12
15
  "jsx-dev-runtime": resolve(__dirname, "src/jsx-dev-runtime.js"),
13
16
  },
@@ -1,126 +0,0 @@
1
- import { strictEqual } from "../utils";
2
- import { IS_STATE } from "./symbols";
3
- /**
4
- * Stops the observer that created it when called.
5
- */
6
- export type StopFunction = () => void;
7
- type Unwrapped<T> = T extends State<infer V> ? V : T;
8
- /**
9
- * Extracts value types from an array of states.
10
- */
11
- export type StateValues<T extends MaybeState<any>[]> = {
12
- [K in keyof T]: Unwrapped<T[K]>;
13
- };
14
- export interface CreateStateOptions<T> {
15
- /**
16
- * Determines if the `next` value is equal to the `current` value.
17
- * If this function returns true, watchers will be notified of changes. If it returns false, watchers will not be notified.
18
- * Default equals check is `===` (strict) equality.
19
- *
20
- * @param next - The new value being set.
21
- * @param current - The current value being replaced.
22
- */
23
- equals?: (next: T, current: T) => boolean;
24
- }
25
- export interface WatchOptions<T> {
26
- /**
27
- * If true the watch callback will be called for the first time on the next change.
28
- * Callback is immediately called with the state's current value by default.
29
- */
30
- lazy?: boolean;
31
- }
32
- export interface State<T> {
33
- /**
34
- * Returns the current value.
35
- */
36
- get(): T;
37
- /**
38
- * Watch this state's value with a `callback` function.
39
- * The `callback` is only called if the value is not equal to the current value.
40
- *
41
- * > NOTE: If watching a state inside a view, use the `.watch` method on the `ViewContext`. That method will automatically
42
- * clean up all watchers when the view is disconnected. Watchers created here must be cleaned up manually.
43
- */
44
- watch(callback: (value: T) => void, options?: WatchOptions<T>): StopFunction;
45
- }
46
- /** A new value for a state, or a callback that receives the current value and returns a new one. */
47
- export type SetAction<I, O = I> = O | SetFunction<I, O>;
48
- export type SetFunction<I, O = I> = (current: I) => O;
49
- /** Callback that updates the value of a state. */
50
- export type Setter<I, O = I> = (value: SetAction<I, O>) => void;
51
- export type MaybeState<T> = State<T> | T;
52
- export declare function isState<T>(value: any): value is State<T>;
53
- /**
54
- * Retrieves a plain value from a variable that may be a state.
55
- */
56
- export declare function toValue<T>(source: MaybeState<T>): T;
57
- /**
58
- * Ensures a variable that may be a state or plain value is a state.
59
- */
60
- export declare function toState<T>(value: MaybeState<T>): State<T>;
61
- /**
62
- * ValueHolder implements the core functionality of a State.
63
- * It holds a value, which can be retrieved with `get`, updated with `set` and observed with `watch`.
64
- * The user-facing API splits up access into a read-only State and a setter function.
65
- */
66
- export declare class ValueHolder<T> implements State<T> {
67
- value: T;
68
- watchers: ((value: T) => void)[];
69
- equals: typeof strictEqual;
70
- constructor(value: T, options?: CreateStateOptions<T>);
71
- get(): T;
72
- set(action: T | SetFunction<T>): void;
73
- watch(callback: (value: T) => void, options?: WatchOptions<T>): () => void;
74
- }
75
- /**
76
- * Signal is the implementation of a read-only State.
77
- */
78
- export declare class Signal<T> implements State<T> {
79
- [IS_STATE]: boolean;
80
- __value: State<T>;
81
- constructor(value: State<T>);
82
- get(): T;
83
- watch(callback: (value: T) => void, options?: WatchOptions<T>): StopFunction;
84
- }
85
- /**
86
- * Creates a state and setter.
87
- */
88
- export declare function createState<T>(value: T, options?: CreateStateOptions<T>): [State<T>, Setter<T>];
89
- /**
90
- * Creates a state and setter.
91
- */
92
- export declare function createState<T>(value?: T, options?: CreateStateOptions<T | undefined>): [State<T | undefined>, Setter<T | undefined>];
93
- export interface DeriveOptions {
94
- equals?: (next: unknown, current: unknown) => boolean;
95
- }
96
- /**
97
- * Derives a new `State` from one or more existing states.
98
- *
99
- * @param sources - Array of source states to track.
100
- * @param fn - A function called to recompute the value when any tracked source states receive a new value.
101
- *
102
- * @example
103
- * // With one source...
104
- * const [$count, setCount] = createState(5);
105
- * const $doubled = derive([$count], count => count * 2);
106
- * // ... or many:
107
- * const [$greeting, setGreeting] = createState("Hello");
108
- * const [$name, setName] = createState("World");
109
- * const $hello = derive([$greeting, name], (greeting, name) => `${greeting}, ${name}!`);
110
- */
111
- export declare function derive<Sources extends MaybeState<any>[], T>(sources: [...Sources], fn: (...values: StateValues<Sources>) => T | State<T>, options?: DeriveOptions): State<T>;
112
- export interface StateWatcher {
113
- /**
114
- * Watch one or more states, calling the provided `fn` each time one of their values changes.
115
- *
116
- * @param states - An array of states or plain values. States will be unwrapped before being passed to `fn`.
117
- * @param fn - A function that takes the values of `states` in the same order they were passed.
118
- */
119
- watch<I extends MaybeState<any>[]>(states: [...I], fn: (...currentValues: StateValues<I>) => void): StopFunction;
120
- /**
121
- * Stop all watch callbacks registered to this watcher.
122
- */
123
- stopAll(): void;
124
- }
125
- export declare function createWatcher(): StateWatcher;
126
- export {};
@@ -1,31 +0,0 @@
1
- import { Emitter } from "@manyducks.co/emitter";
2
- import type { Dolla } from "./dolla";
3
- interface StatsStore {
4
- emitter: Emitter<StatsStoreEvents>;
5
- stats: {
6
- watcherCount: number;
7
- viewCount: number;
8
- };
9
- }
10
- type StatsStoreEvents = {
11
- /**
12
- * Emitted when any stats are updated in the store.
13
- */
14
- statsChanged: [];
15
- _incrementWatcherCount: [amount: number];
16
- _incrementViewCount: [amount: number];
17
- };
18
- /**
19
- * Tracks runtime statistics.
20
- */
21
- export declare class Stats {
22
- #private;
23
- constructor(dolla: Dolla);
24
- }
25
- export declare function _createStore(): StatsStore;
26
- export declare function _getStore(): StatsStore;
27
- export declare function _onWatcherAdded(): void;
28
- export declare function _onWatcherRemoved(): void;
29
- export declare function _onViewMounted(): void;
30
- export declare function _onViewUnmounted(): void;
31
- export {};