easy-signal 2.0.1 → 3.0.1

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.
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ printWidth: 120,
3
+ singleQuote: true,
4
+ trailingComma: 'es5',
5
+ semi: true,
6
+ bracketSpacing: true,
7
+ arrowParens: 'avoid',
8
+ };
@@ -0,0 +1,18 @@
1
+ {
2
+ "[typescript]": {
3
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
4
+ },
5
+ "[json]": {
6
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
7
+ },
8
+ "eslint.format.enable": true,
9
+ "editor.codeActionsOnSave": {
10
+ "source.organizeImports": "explicit"
11
+ },
12
+ "editor.formatOnSave": true,
13
+ "eslint.validate": [
14
+ "javascript",
15
+ "javascriptreact",
16
+ "svelte"
17
+ ]
18
+ }
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Easy Signal
2
2
 
3
- A simple interface for creating a defined event or action that can be triggered and listened to by any number of
4
- subscribers. Producing a single function that can be used to subscribe to the events, subscribe to errors, and dispatch
5
- events and errors.
3
+ Two simple interfaces for creating two types of signals. The first (and original signal in this module) is a defined
4
+ event that can be triggered and listened to with a single function. The second is a defined data store that allows you
5
+ to react to changes to that data (popularized by solid-js). These two are `EventSignal` and `ReactiveSignal`.
6
6
 
7
- Full type safety with TypeScript providing good autocomplete.
7
+ Full type safety with TypeScript with both use-cases.
8
8
 
9
9
  ## Installation
10
10
 
@@ -12,24 +12,24 @@ Full type safety with TypeScript providing good autocomplete.
12
12
  npm install easy-signal
13
13
  ```
14
14
 
15
- ## Usage
15
+ ## EventSignal Usage
16
16
 
17
- A signal is a function that represents a single event. The function can be used to subscribe to be notified of the
18
- events as well as to trigger them.
17
+ An EventSignal is a function that represents a single event. The function can be used to subscribe to be notified of
18
+ the events as well as to trigger them.
19
19
 
20
- Signals offer similar functionality as the browser's `eventDispatcher` API, but rather than a general API for any event,
21
- each event would use its own signal. This allows each signal to have a specific function signature as opposed to the
22
- browser's generic `event` object. This is a great system in TypeScript being able to see the exact data each event
20
+ EventSignals offer similar functionality as the browser's `eventDispatcher` API, but rather than a general API for any
21
+ event, each event would use its own signal. This allows each signal to have a specific function signature as opposed to
22
+ the browser's generic `event` object. This is a great system in TypeScript being able to see the exact data each event
23
23
  produces.
24
24
 
25
- ### Basic Usage
25
+ ### EventSignal Basic Usage
26
26
 
27
27
  ```ts
28
28
  // file seconds.ts
29
- import { signal } from 'easy-signal';
29
+ import { eventSignal } from 'easy-signal';
30
30
 
31
31
  // Create the signal and export it for use. Optionally provide the subscriber signature
32
- export const onSecond = signal<number>();
32
+ export const onSecond = eventSignal<number>();
33
33
 
34
34
  // Passing a non-function value will dispatch the event
35
35
  setInterval(() => {
@@ -51,9 +51,9 @@ Errors may also be listened to from the signal by passing `ForErrors` as the sec
51
51
  and errors may be dispatched by passing an Error object to the signal method.
52
52
 
53
53
  ```ts
54
- import { signal, ForErrors } from 'easy-signal';
54
+ import { eventSignal, ForErrors } from 'easy-signal';
55
55
 
56
- const dataStream = signal();
56
+ const dataStream = eventSignal();
57
57
 
58
58
  dataStream(data => console.log('data is:' data));
59
59
  dataStream(error => console.log('Error is:' error), ForErrors);
@@ -65,11 +65,10 @@ stream.on('error', err => dataStream(err));
65
65
  To get a subscriber-only method for external use, pass in the `GetOnSignal` constant.
66
66
 
67
67
  ```ts
68
- import { signal, GetOnSignal } from 'easy-signal';
69
-
68
+ import { eventSignal, GetOnSignal } from 'easy-signal';
70
69
 
71
70
  function getMyAPI() {
72
- const doSomething = signal();
71
+ const doSomething = eventSignal();
73
72
 
74
73
  // doSomething() will trigger subscribers that were added in onSomething(...). This protects the signal from being
75
74
  // triggered/dispatched outside of `getMyAPI`. Sometimes you may want more control to prevent just anyone from
@@ -81,7 +80,6 @@ function getMyAPI() {
81
80
  }
82
81
  ```
83
82
 
84
-
85
83
  To clear the listeners from the signal, pass in the `ClearSignal` constant.
86
84
 
87
85
  ```ts
@@ -91,3 +89,85 @@ const onSomething = signal();
91
89
 
92
90
  onSomething(ClearSignal); // clears signal
93
91
  ```
92
+
93
+ ## ReactiveSignal Usage
94
+
95
+ A ReactiveSignal is a function that represents a single piece of data. The function can be used to get the data, set the
96
+ data, and update the data with an updater function. To subscribe to changes use the separate `subscribe` function. The
97
+ ReactiveSignal allows for Observers to be created, which are functions that will be rerun whenever any ReactiveSignals
98
+ they access are updated, and ComputedSignals which are read-only signals whose value is derived from other signals and
99
+ which will be updated whenever they are.
100
+
101
+ ### ReactiveSignal Basic Usage
102
+
103
+ Here we will use an example similar to the EventSignal, but unlike the EventSignal, the current seconds since epoch will
104
+ be stored and can be accessed any time, whereas the EventSignal only fires an event with the data provided. This
105
+ particular example isn't very compelling.
106
+
107
+ ```ts
108
+ // file seconds.ts
109
+ import { reactiveSignal } from 'easy-signal';
110
+
111
+ // Create the signal and export it for use. Optionally provide the subscriber signature
112
+ export const onSecond = reactiveSignal(0);
113
+
114
+ // Passing a non-function value will dispatch the event
115
+ setInterval(() => {
116
+ const currentSecond = Math.floor(Date.now() / 1000);
117
+ onSecond(currentSecond);
118
+ // or onSecond(currentValue => newValue) to update
119
+ });
120
+ ```
121
+
122
+ ```ts
123
+ import { onSecond, subscribe } from './seconds.ts';
124
+
125
+ // Get the value of onSecond() at any time
126
+ console.log(onSecond(), 'since epoc');
127
+
128
+ // Typescript knows that seconds is a number because of the concrete type definition in seconds.ts
129
+ const unsubscribe = subscribe(onSecond, seconds => {
130
+ console.log(seconds, 'since epoc');
131
+ });
132
+ ```
133
+
134
+ ### Observer Basic Usage
135
+
136
+ To take action whenever data changes, you can observe or more signals by accessing them in a function call. You can also
137
+ prevent that function from being called too often when data changes by using the Timings. Below, we update the content
138
+ of the DOM whenever the user or billing data is updated, but we only do it after an animation frame to prevent the DOM
139
+ updates from being too frequent. Providing no Timing will call the function immediately after any data is changed.
140
+
141
+ Because `user()` and `billing()` are called the first time the observe function is run, it automatically subscribes to
142
+ know when they are changed so that the function may be rerun.
143
+
144
+ ```ts
145
+ import { reactiveSignal, observe, Timing } from 'easy-signal';
146
+
147
+ const user = reactiveSignal(userData);
148
+ const billing = reactiveSignal(billingData);
149
+
150
+ const unobserve = observe(() => {
151
+ document.body.innerText = `${user().name} has the plan ${billing().plan}`;
152
+ }, Timing.AnimationFrame);
153
+ ```
154
+
155
+ ### ComputedSignal Basic Usage
156
+
157
+ Create read-only signals whose value is derived from other signals and which will be updated whenever they are.
158
+
159
+ ```ts
160
+ import { computedSignal, subscribe } from 'easy-signal';
161
+ import { user, billing } from 'my-other-signals';
162
+
163
+ const delinquent = computedSignal(() => {
164
+ if (user().subscribed) {
165
+ return billing().status === 'delinquent';
166
+ }
167
+ return false;
168
+ });
169
+
170
+ subscribe(delinquent, delinquent => {
171
+ console.log(`The user is${delinquent ? '' : ' not'} delinquent`);
172
+ });
173
+ ```
@@ -0,0 +1,36 @@
1
+ declare type Args<T> = T extends (...args: infer A) => any ? A : never;
2
+ export declare type EventSignalSubscriber = (...args: any[]) => any;
3
+ export declare type ErrorSubscriber = (error: Error) => any;
4
+ export declare type Unsubscriber = () => void;
5
+ export declare type OnSignal<T extends EventSignalSubscriber = EventSignalSubscriber> = {
6
+ (subscriber: T): Unsubscriber;
7
+ (errorListener: ErrorSubscriber, what: typeof ForErrors): Unsubscriber;
8
+ };
9
+ export declare type EventSignal<T extends EventSignalSubscriber = EventSignalSubscriber> = OnSignal<T> & {
10
+ (...args: Args<T>): void;
11
+ (data: Error): void;
12
+ (data: typeof ClearSignal): void;
13
+ (data: typeof GetOnSignal): OnSignal<T>;
14
+ };
15
+ export declare const ClearSignal: unique symbol;
16
+ export declare const GetOnSignal: unique symbol;
17
+ export declare const ForErrors: unique symbol;
18
+ /**
19
+ * Creates a signal, a function that can be used to subscribe to events. The signal can be called with a subscriber
20
+ * function, which will be called when the signal is dispatched. The signal can also be called with data, which will
21
+ * dispatch to all subscribers. An optional second argument can be passed to subscribe to errors instead. When the
22
+ * signal is called with an instance of Error, it will dispatch to all error listeners.
23
+ * The signal can also be called with `ClearSignal`, which will clear all subscribers.
24
+ * @example
25
+ * const onLoad = signal();
26
+ *
27
+ * // Subscribe to data
28
+ * onLoad((data) => console.log('loaded', data));
29
+ * onLoad((error) => console.error('error', error), true);
30
+ *
31
+ * // Dispatch data
32
+ * onLoad('data'); // logs 'loaded data'
33
+ * onLoad(new Error('error')); // logs 'error Error: error'
34
+ */
35
+ export declare function eventSignal<T extends EventSignalSubscriber = EventSignalSubscriber>(): EventSignal<T>;
36
+ export {};
package/eventSignal.js ADDED
@@ -0,0 +1,51 @@
1
+ export const ClearSignal = Symbol();
2
+ export const GetOnSignal = Symbol();
3
+ export const ForErrors = Symbol();
4
+ /**
5
+ * Creates a signal, a function that can be used to subscribe to events. The signal can be called with a subscriber
6
+ * function, which will be called when the signal is dispatched. The signal can also be called with data, which will
7
+ * dispatch to all subscribers. An optional second argument can be passed to subscribe to errors instead. When the
8
+ * signal is called with an instance of Error, it will dispatch to all error listeners.
9
+ * The signal can also be called with `ClearSignal`, which will clear all subscribers.
10
+ * @example
11
+ * const onLoad = signal();
12
+ *
13
+ * // Subscribe to data
14
+ * onLoad((data) => console.log('loaded', data));
15
+ * onLoad((error) => console.error('error', error), true);
16
+ *
17
+ * // Dispatch data
18
+ * onLoad('data'); // logs 'loaded data'
19
+ * onLoad(new Error('error')); // logs 'error Error: error'
20
+ */
21
+ export function eventSignal() {
22
+ const subscribers = new Set();
23
+ const errorListeners = new Set();
24
+ function onSignal(subscriber, what) {
25
+ const listeners = what === ForErrors ? errorListeners : subscribers;
26
+ listeners.add(subscriber);
27
+ return () => {
28
+ listeners.delete(subscriber);
29
+ };
30
+ }
31
+ function signal(...args) {
32
+ const arg = args[0];
33
+ if (typeof arg === 'function') {
34
+ return onSignal(arg);
35
+ }
36
+ else if (arg === ClearSignal) {
37
+ subscribers.clear();
38
+ errorListeners.clear();
39
+ }
40
+ else if (arg === GetOnSignal) {
41
+ return onSignal;
42
+ }
43
+ else if (arg instanceof Error) {
44
+ errorListeners.forEach(listener => listener(arg));
45
+ }
46
+ else {
47
+ subscribers.forEach(listener => listener(...args));
48
+ }
49
+ }
50
+ return signal;
51
+ }
package/eventSignal.ts ADDED
@@ -0,0 +1,74 @@
1
+ type Args<T> = T extends (...args: infer A) => any ? A : never;
2
+ export type EventSignalSubscriber = (...args: any[]) => any;
3
+ export type ErrorSubscriber = (error: Error) => any;
4
+ export type Unsubscriber = () => void;
5
+
6
+ export type OnSignal<T extends EventSignalSubscriber = EventSignalSubscriber> = {
7
+ (subscriber: T): Unsubscriber;
8
+ (errorListener: ErrorSubscriber, what: typeof ForErrors): Unsubscriber;
9
+ };
10
+
11
+ export type EventSignal<T extends EventSignalSubscriber = EventSignalSubscriber> = OnSignal<T> & {
12
+ (...args: Args<T>): void;
13
+ (data: Error): void;
14
+ (data: typeof ClearSignal): void;
15
+ (data: typeof GetOnSignal): OnSignal<T>;
16
+ };
17
+
18
+ export const ClearSignal = Symbol();
19
+ export const GetOnSignal = Symbol();
20
+ export const ForErrors = Symbol();
21
+
22
+ /**
23
+ * Creates a signal, a function that can be used to subscribe to events. The signal can be called with a subscriber
24
+ * function, which will be called when the signal is dispatched. The signal can also be called with data, which will
25
+ * dispatch to all subscribers. An optional second argument can be passed to subscribe to errors instead. When the
26
+ * signal is called with an instance of Error, it will dispatch to all error listeners.
27
+ * The signal can also be called with `ClearSignal`, which will clear all subscribers.
28
+ * @example
29
+ * const onLoad = signal();
30
+ *
31
+ * // Subscribe to data
32
+ * onLoad((data) => console.log('loaded', data));
33
+ * onLoad((error) => console.error('error', error), true);
34
+ *
35
+ * // Dispatch data
36
+ * onLoad('data'); // logs 'loaded data'
37
+ * onLoad(new Error('error')); // logs 'error Error: error'
38
+ */
39
+ export function eventSignal<T extends EventSignalSubscriber = EventSignalSubscriber>(): EventSignal<T> {
40
+ const subscribers = new Set<EventSignalSubscriber>();
41
+ const errorListeners = new Set<EventSignalSubscriber>();
42
+
43
+ function onSignal(subscriber: T | ErrorSubscriber, what?: typeof ForErrors): Unsubscriber {
44
+ const listeners = what === ForErrors ? errorListeners : subscribers;
45
+ listeners.add(subscriber);
46
+ return () => {
47
+ listeners.delete(subscriber);
48
+ };
49
+ }
50
+
51
+ function signal(...args: Args<T>): void;
52
+ function signal(error: Error): void;
53
+ function signal(data: typeof ClearSignal): void;
54
+ function signal(data: typeof GetOnSignal): OnSignal<T>;
55
+ function signal(subscriber: T): Unsubscriber;
56
+ function signal(errorListener: EventSignalSubscriber, what: typeof ForErrors): Unsubscriber;
57
+ function signal(...args: any[]): Unsubscriber | OnSignal<T> | void {
58
+ const arg = args[0];
59
+ if (typeof arg === 'function') {
60
+ return onSignal(arg);
61
+ } else if (arg === ClearSignal) {
62
+ subscribers.clear();
63
+ errorListeners.clear();
64
+ } else if (arg === GetOnSignal) {
65
+ return onSignal as OnSignal<T>;
66
+ } else if (arg instanceof Error) {
67
+ errorListeners.forEach(listener => listener(arg));
68
+ } else {
69
+ subscribers.forEach(listener => listener(...args));
70
+ }
71
+ }
72
+
73
+ return signal;
74
+ }
package/index.d.ts CHANGED
@@ -1,36 +1,3 @@
1
- declare type Args<T> = T extends (...args: infer A) => any ? A : never;
2
- export declare type Subscriber = (...args: any[]) => any;
3
- export declare type ErrorSubscriber = (error: Error) => any;
4
- export declare type Unsubscriber = () => void;
5
- export declare type OnSignal<T extends Subscriber = Subscriber> = {
6
- (subscriber: T): Unsubscriber;
7
- (errorListener: ErrorSubscriber, what: typeof ForErrors): Unsubscriber;
8
- };
9
- export declare type Signal<T extends Subscriber = Subscriber> = OnSignal<T> & {
10
- (...args: Args<T>): void;
11
- (data: Error): void;
12
- (data: typeof ClearSignal): void;
13
- (data: typeof GetOnSignal): OnSignal<T>;
14
- };
15
- export declare const ClearSignal: unique symbol;
16
- export declare const GetOnSignal: unique symbol;
17
- export declare const ForErrors: unique symbol;
18
- /**
19
- * Creates a signal, a function that can be used to subscribe to events. The signal can be called with a subscriber
20
- * function, which will be called when the signal is dispatched. The signal can also be called with data, which will
21
- * dispatch to all subscribers. An optional second argument can be passed to subscribe to errors instead. When the
22
- * signal is called with an instance of Error, it will dispatch to all error listeners.
23
- * The signal can also be called with `ClearSignal`, which will clear all subscribers.
24
- * @example
25
- * const onLoad = signal();
26
- *
27
- * // Subscribe to data
28
- * onLoad((data) => console.log('loaded', data));
29
- * onLoad((error) => console.error('error', error), true);
30
- *
31
- * // Dispatch data
32
- * onLoad('data'); // logs 'loaded data'
33
- * onLoad(new Error('error')); // logs 'error Error: error'
34
- */
35
- export declare function signal<T extends Subscriber = Subscriber>(): Signal<T>;
36
- export {};
1
+ export * from './eventSignal';
2
+ export * from './reactiveSignal';
3
+ export * from './signalStore';
package/index.js CHANGED
@@ -1,51 +1,3 @@
1
- export const ClearSignal = Symbol();
2
- export const GetOnSignal = Symbol();
3
- export const ForErrors = Symbol();
4
- /**
5
- * Creates a signal, a function that can be used to subscribe to events. The signal can be called with a subscriber
6
- * function, which will be called when the signal is dispatched. The signal can also be called with data, which will
7
- * dispatch to all subscribers. An optional second argument can be passed to subscribe to errors instead. When the
8
- * signal is called with an instance of Error, it will dispatch to all error listeners.
9
- * The signal can also be called with `ClearSignal`, which will clear all subscribers.
10
- * @example
11
- * const onLoad = signal();
12
- *
13
- * // Subscribe to data
14
- * onLoad((data) => console.log('loaded', data));
15
- * onLoad((error) => console.error('error', error), true);
16
- *
17
- * // Dispatch data
18
- * onLoad('data'); // logs 'loaded data'
19
- * onLoad(new Error('error')); // logs 'error Error: error'
20
- */
21
- export function signal() {
22
- const subscribers = new Set();
23
- const errorListeners = new Set();
24
- function onSignal(subscriber, what) {
25
- const listeners = what === ForErrors ? errorListeners : subscribers;
26
- listeners.add(subscriber);
27
- return () => {
28
- listeners.delete(subscriber);
29
- };
30
- }
31
- function signal(...args) {
32
- const arg = args[0];
33
- if (typeof arg === 'function') {
34
- return onSignal(arg);
35
- }
36
- else if (arg === ClearSignal) {
37
- subscribers.clear();
38
- errorListeners.clear();
39
- }
40
- else if (arg === GetOnSignal) {
41
- return onSignal;
42
- }
43
- else if (arg instanceof Error) {
44
- errorListeners.forEach(listener => listener(arg));
45
- }
46
- else {
47
- subscribers.forEach(listener => listener(...args));
48
- }
49
- }
50
- return signal;
51
- }
1
+ export * from './eventSignal';
2
+ export * from './reactiveSignal';
3
+ export * from './signalStore';
package/index.ts CHANGED
@@ -1,75 +1,3 @@
1
- type Args<T> = T extends (...args: infer A) => any ? A : never;
2
- export type Subscriber = (...args: any[]) => any;
3
- export type ErrorSubscriber = (error: Error) => any;
4
- export type Unsubscriber = () => void;
5
-
6
- export type OnSignal<T extends Subscriber = Subscriber> = {
7
- (subscriber: T): Unsubscriber;
8
- (errorListener: ErrorSubscriber, what: typeof ForErrors): Unsubscriber;
9
- }
10
-
11
- export type Signal<T extends Subscriber = Subscriber> = OnSignal<T> & {
12
- (...args: Args<T>): void;
13
- (data: Error): void;
14
- (data: typeof ClearSignal): void;
15
- (data: typeof GetOnSignal): OnSignal<T>;
16
- }
17
-
18
- export const ClearSignal = Symbol();
19
- export const GetOnSignal = Symbol();
20
- export const ForErrors = Symbol();
21
-
22
-
23
- /**
24
- * Creates a signal, a function that can be used to subscribe to events. The signal can be called with a subscriber
25
- * function, which will be called when the signal is dispatched. The signal can also be called with data, which will
26
- * dispatch to all subscribers. An optional second argument can be passed to subscribe to errors instead. When the
27
- * signal is called with an instance of Error, it will dispatch to all error listeners.
28
- * The signal can also be called with `ClearSignal`, which will clear all subscribers.
29
- * @example
30
- * const onLoad = signal();
31
- *
32
- * // Subscribe to data
33
- * onLoad((data) => console.log('loaded', data));
34
- * onLoad((error) => console.error('error', error), true);
35
- *
36
- * // Dispatch data
37
- * onLoad('data'); // logs 'loaded data'
38
- * onLoad(new Error('error')); // logs 'error Error: error'
39
- */
40
- export function signal<T extends Subscriber = Subscriber>(): Signal<T> {
41
- const subscribers = new Set<Subscriber>();
42
- const errorListeners = new Set<Subscriber>();
43
-
44
- function onSignal(subscriber: T | ErrorSubscriber, what?: typeof ForErrors): Unsubscriber {
45
- const listeners = what === ForErrors ? errorListeners : subscribers;
46
- listeners.add(subscriber);
47
- return () => {
48
- listeners.delete(subscriber);
49
- };
50
- }
51
-
52
- function signal(...args: Args<T>): void;
53
- function signal(error: Error): void;
54
- function signal(data: typeof ClearSignal): void;
55
- function signal(data: typeof GetOnSignal): OnSignal<T>;
56
- function signal(subscriber: T): Unsubscriber;
57
- function signal(errorListener: Subscriber, what: typeof ForErrors): Unsubscriber;
58
- function signal(...args: any[]): Unsubscriber | OnSignal<T> | void {
59
- const arg = args[0];
60
- if (typeof arg === 'function') {
61
- return onSignal(arg);
62
- } else if (arg === ClearSignal) {
63
- subscribers.clear();
64
- errorListeners.clear();
65
- } else if (arg === GetOnSignal) {
66
- return onSignal as OnSignal<T>;
67
- } else if (arg instanceof Error) {
68
- errorListeners.forEach(listener => listener(arg));
69
- } else {
70
- subscribers.forEach(listener => listener(...args));
71
- }
72
- }
73
-
74
- return signal;
75
- }
1
+ export * from './eventSignal';
2
+ export * from './reactiveSignal';
3
+ export * from './signalStore';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easy-signal",
3
- "version": "2.0.1",
3
+ "version": "3.0.1",
4
4
  "description": "A tiny (25 LOC), simple utility for alerting subscribers when an event happens, allowing for error handling and clearing.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1,102 @@
1
+ export interface SignalOptions<T> {
2
+ equals?: false | ((prev: T, next: T) => boolean);
3
+ }
4
+ export declare type Unsubscribe = () => void;
5
+ export declare type Cancel = () => void;
6
+ export declare const Timing: {
7
+ Tick: (fn: () => void) => void;
8
+ AnimationFrame: (fn: () => void) => void;
9
+ };
10
+ /**
11
+ * A Signal is a single getter/setter function that holds a value and notifies subscribers when the value changes
12
+ * The signal function can be called with no arguments to get the current value, with a function argument to run an
13
+ * update function which should receive the value and return a new one, or with a value argument which will replace the
14
+ * signal's current value. The signal function always returns the current value.
15
+ *
16
+ * The optional second argument, `set`, can be used to force the first argument as the new value. This is useful when
17
+ * the first argument is a function or `undefined` since the signal will assume any function is an updater function and
18
+ * any `undefined` value is a request to get the current value.
19
+ */
20
+ export declare type ReactiveSignal<T> = {
21
+ (): T;
22
+ (value: T | ReactiveSignalUpdater<T>, set?: false): T;
23
+ (value: T, set: true): T;
24
+ };
25
+ /**
26
+ * A Computed Signal is a signal that is the result of a function that depends on other signals. The function is called
27
+ * whenever the computed signal is accessed if there are no subscribers, or whenever its dependent signals change if
28
+ * there are subscribers so that subscribers to the computed signal can be informed.
29
+ */
30
+ export declare type ComputedSignal<T> = () => T;
31
+ /**
32
+ * A Signal Subscriber is a function that will be called whenever the signal's value changes. The subscriber will be
33
+ * called with the new value. The subscriber can be used to update the DOM or trigger other side effects.
34
+ */
35
+ export declare type ReactiveSignalSubscriber<T> = (value: T) => void;
36
+ /**
37
+ * A Signal Updater is a function that will be called with the current value of the signal and should return a new
38
+ * value. The updater can be used to update the signal's value based on its current value.
39
+ */
40
+ export declare type ReactiveSignalUpdater<T> = (prev: T) => T;
41
+ /**
42
+ * An Observer is a function that will be called whenever any of the signals it depends on change. The observer can be
43
+ * used to update the DOM or trigger other side effects.
44
+ * The observer will be called immediately (or after certain a timing option) and whenever any of the signals it depends
45
+ * on change.
46
+ */
47
+ export declare type ReactiveSignalObserver = () => void;
48
+ /**
49
+ * A Timing is a function that will be called with a function to execute. The timing function should execute the passed
50
+ * function at some point in the future. The default timing is `Timing.Immediate` which executes the function
51
+ * immediately.
52
+ */
53
+ export declare type Timing = (fn: () => void) => Cancel;
54
+ /**
55
+ * A Subscription Change is a function that will be called whenever the signal's subscribers changes from none to some
56
+ * or some to none. The subscription change will be called with a boolean indicating whether there are any subscribers.
57
+ */
58
+ export declare type SubscriptionChange = (hasSubscribers: boolean) => void;
59
+ /**
60
+ * Create a Signal with an initial value and optional options. The options can include an `equals` function which will
61
+ * be used to determine if the new value is different from the current value. If the new value is different, the signal
62
+ * will be updated and all subscribers will be notified.
63
+ * The returned signal function can be called with no arguments to get the current value, with a function argument to
64
+ * run an update function which should receive the value and return a new one, or with a value argument which will
65
+ * replace the signal's current value. The signal function always returns the current value.
66
+ */
67
+ export declare function reactiveSignal<T>(value: T, options?: SignalOptions<T>): ReactiveSignal<T>;
68
+ /**
69
+ * Subscribe to be notified whenever a Signal's value changes. The Subscriber function will be called immediately with
70
+ * the current value of the Signal and again whenever the Signal's value changes.
71
+ *
72
+ * The optional third argument, `timing`, can be used to specify when the function should be called. The default is
73
+ * `Timing.Immediate` which executes the function immediately.
74
+ *
75
+ * The returned function can be called to unsubscribe from the Signal.
76
+ */
77
+ export declare function subscribe<T>(signal: ReactiveSignal<T>, subscriber: ReactiveSignalSubscriber<T>, timing?: Timing): Unsubscribe;
78
+ /**
79
+ * Get notified when a Signal's subscribers changes from none to some or some to none.
80
+ */
81
+ export declare function onSubscriptionChange(signal: ReactiveSignal<any>, onChange: (hasSubscribers: boolean) => void): Unsubscribe;
82
+ /**
83
+ * Calls an Observer function after Timing amount of time (default is immediate, but can be on the next tick or the next
84
+ * animation frame) and again after Timing whenever any of the signals it depends on change.
85
+ * The Observer function will be called immediately (or after the timing) and again whenever any of the signals it
86
+ * depends on change.
87
+ * The returned function can be called to unsubscribe from the signals that are called when the effect is run.
88
+ *
89
+ * The optional second argument, `timing`, can be used to specify when the function should be called. The default is
90
+ * undefined which executes the function immediately.
91
+ */
92
+ export declare function observe(fn: ReactiveSignalObserver, timing?: Timing): Unsubscribe;
93
+ /**
94
+ * Create a Computed Signal which is a signal that is the result of a function that depends on other signals. The
95
+ * function is called immediately whenever the computed signal is accessed if there are no subscribers, or whenever its
96
+ * dependent signals change if there are subscribers so that subscribers to the computed signal can be informed.
97
+ *
98
+ * The optional second argument, `when`, can be used to specify when updater function should be called. The default is
99
+ * undefined which executes the function immediately after any change to any signal it relies on. This can
100
+ * prevent unnecessary updates if the function is expensive to run.
101
+ */
102
+ export declare function computedSignal<T>(fn: ReactiveSignalUpdater<T>, when?: Timing): ComputedSignal<T>;
@@ -0,0 +1,210 @@
1
+ // Different timings for when to execute an observer observing a signal
2
+ export const Timing = {
3
+ // Execute the function on the next tick of the event loop
4
+ Tick: (fn) => {
5
+ Promise.resolve(fn);
6
+ },
7
+ // Execute the function on the next animation frame
8
+ AnimationFrame: (fn) => {
9
+ globalThis.requestAnimationFrame(fn);
10
+ },
11
+ };
12
+ // The context for the current run and its unsubscribes
13
+ let context = null;
14
+ // A map to keep track of listeners to subscription changes
15
+ const onSubscriptionChanges = new WeakMap();
16
+ /**
17
+ * Create a Signal with an initial value and optional options. The options can include an `equals` function which will
18
+ * be used to determine if the new value is different from the current value. If the new value is different, the signal
19
+ * will be updated and all subscribers will be notified.
20
+ * The returned signal function can be called with no arguments to get the current value, with a function argument to
21
+ * run an update function which should receive the value and return a new one, or with a value argument which will
22
+ * replace the signal's current value. The signal function always returns the current value.
23
+ */
24
+ export function reactiveSignal(value, options) {
25
+ // A map to keep track of subscribers
26
+ const subscribers = new Map();
27
+ // The signal is a function that will return a value when called without arguments, or update the value when called
28
+ // with an argument. The update value can be a new value or an updater function.
29
+ const signal = ((newValue, set) => {
30
+ // If no new value is provided, subscribe the current run to this signal and return the current value
31
+ if (!set && newValue === undefined) {
32
+ // If there is a context (an observer is running), add the observer's subscriber to the signal
33
+ if (context) {
34
+ const { subscriber: run, unsubscribes } = context;
35
+ let unsubscribe = subscribers.get(run);
36
+ // If the run is not already subscribed, subscribe it
37
+ if (!unsubscribe) {
38
+ // Create the unsubscribe function
39
+ unsubscribe = () => {
40
+ subscribers.delete(run);
41
+ // If there are no more subscribers, notify the subscription changes
42
+ if (subscribers.size === 0) {
43
+ const onChanges = onSubscriptionChanges.get(signal);
44
+ if (onChanges)
45
+ onChanges.forEach(onChange => onChange(false));
46
+ }
47
+ };
48
+ // Add the unsubscribe function to the signal's subscribers
49
+ subscribers.set(run, unsubscribe);
50
+ // If this changed the number of subscribers from 0 to 1, notify any subscription change subscribers
51
+ if (subscribers.size === 1) {
52
+ const onChanges = onSubscriptionChanges.get(signal);
53
+ if (onChanges)
54
+ onChanges.forEach(onChange => onChange(true));
55
+ }
56
+ }
57
+ // Add the unsubscribe function to the run's unsubscribes
58
+ unsubscribes.add(unsubscribe);
59
+ }
60
+ // Return the current value
61
+ return value;
62
+ }
63
+ // If the new value is a function, call it with the current value as an argument
64
+ if (!set && typeof newValue === 'function') {
65
+ newValue = newValue(value);
66
+ }
67
+ // If the new value is different from the current value (according to the equals function if provided), update the
68
+ // value and notify all subscribers
69
+ if (options?.equals ? !options.equals(value, newValue) : value !== newValue) {
70
+ value = newValue;
71
+ subscribers.forEach((_, run) => run(value));
72
+ }
73
+ return value;
74
+ });
75
+ // Return the signal function
76
+ return signal;
77
+ }
78
+ /**
79
+ * Subscribe to be notified whenever a Signal's value changes. The Subscriber function will be called immediately with
80
+ * the current value of the Signal and again whenever the Signal's value changes.
81
+ *
82
+ * The optional third argument, `timing`, can be used to specify when the function should be called. The default is
83
+ * `Timing.Immediate` which executes the function immediately.
84
+ *
85
+ * The returned function can be called to unsubscribe from the Signal.
86
+ */
87
+ export function subscribe(signal, subscriber, timing) {
88
+ if (timing) {
89
+ let queued = false;
90
+ const subFn = subscriber;
91
+ subscriber = () => {
92
+ if (!queued) {
93
+ queued = true;
94
+ timing(() => {
95
+ queued = false;
96
+ subFn(signal());
97
+ });
98
+ }
99
+ };
100
+ }
101
+ // Set the current context so we can get the unsubscribe
102
+ context = { subscriber, unsubscribes: new Set() };
103
+ // Get the current value of the signal
104
+ const value = signal();
105
+ // Get the unsubscribe function for the subscriber
106
+ const unsubscribe = context.unsubscribes.values().next().value;
107
+ // Clear the current context
108
+ context = null;
109
+ // Call the subscriber with the current value
110
+ subscriber(value);
111
+ // Return the unsubscribe function
112
+ return unsubscribe;
113
+ }
114
+ /**
115
+ * Get notified when a Signal's subscribers changes from none to some or some to none.
116
+ */
117
+ export function onSubscriptionChange(signal, onChange) {
118
+ // Get the set of onChange functions for the signal
119
+ let onChanges = onSubscriptionChanges.get(signal);
120
+ // If there is no set, create one and add it to the map
121
+ if (!onChanges)
122
+ onSubscriptionChanges.set(signal, (onChanges = new Set()));
123
+ // Add the onChange function to the set
124
+ onChanges.add(onChange);
125
+ // Return a function that removes the onChange function from the set
126
+ return () => {
127
+ onChanges.delete(onChange);
128
+ };
129
+ }
130
+ /**
131
+ * Calls an Observer function after Timing amount of time (default is immediate, but can be on the next tick or the next
132
+ * animation frame) and again after Timing whenever any of the signals it depends on change.
133
+ * The Observer function will be called immediately (or after the timing) and again whenever any of the signals it
134
+ * depends on change.
135
+ * The returned function can be called to unsubscribe from the signals that are called when the effect is run.
136
+ *
137
+ * The optional second argument, `timing`, can be used to specify when the function should be called. The default is
138
+ * undefined which executes the function immediately.
139
+ */
140
+ export function observe(fn, timing) {
141
+ let dirty = true;
142
+ let unsubscribes = new Set();
143
+ // Subscribe to all the signals that are called when the effect is run
144
+ const subscriber = () => {
145
+ if (dirty)
146
+ return;
147
+ dirty = true;
148
+ if (timing)
149
+ timing(() => onChange());
150
+ else
151
+ onChange();
152
+ };
153
+ // Called immediately and whenever any of the signals it depends on change (after the timing)
154
+ const onChange = () => {
155
+ if (!dirty)
156
+ return;
157
+ dirty = false;
158
+ // Set the context for the effect
159
+ context = { subscriber, unsubscribes: new Set() };
160
+ // Run the effect collecting all the unsubscribes from the signals that are called when it is run
161
+ fn();
162
+ // Filter out unchanged unsubscribes, leaving only those which no longer apply
163
+ context.unsubscribes.forEach(u => unsubscribes.delete(u));
164
+ // Unsubscribe from all the signals that are no longer needed
165
+ unsubscribes.forEach(u => u());
166
+ // Set the new unsubscribes
167
+ unsubscribes = context.unsubscribes;
168
+ // Clear the context
169
+ context = null;
170
+ };
171
+ // Call immediately (or on the next timing)
172
+ if (timing)
173
+ timing(() => onChange());
174
+ else
175
+ onChange();
176
+ // Return a function that unsubscribes from all the signals that are called when the effect is run
177
+ return () => unsubscribes.forEach(u => u());
178
+ }
179
+ /**
180
+ * Create a Computed Signal which is a signal that is the result of a function that depends on other signals. The
181
+ * function is called immediately whenever the computed signal is accessed if there are no subscribers, or whenever its
182
+ * dependent signals change if there are subscribers so that subscribers to the computed signal can be informed.
183
+ *
184
+ * The optional second argument, `when`, can be used to specify when updater function should be called. The default is
185
+ * undefined which executes the function immediately after any change to any signal it relies on. This can
186
+ * prevent unnecessary updates if the function is expensive to run.
187
+ */
188
+ export function computedSignal(fn, when) {
189
+ // Create the signal
190
+ const signal = reactiveSignal(undefined);
191
+ // Store the unsubscribe function from the observer. We will only observe the function when there are subscribers to
192
+ // this computed signal.
193
+ let unsubscribe = null;
194
+ // Subscribe to the signal's subscription changes so we know when to start and stop observing
195
+ onSubscriptionChange(signal, hasSubscribers => {
196
+ // If there are subscribers, start observing the function
197
+ if (hasSubscribers) {
198
+ if (!unsubscribe)
199
+ unsubscribe = observe(() => signal(fn), when);
200
+ }
201
+ else if (unsubscribe) {
202
+ // If there are no subscribers, stop observing the function
203
+ unsubscribe();
204
+ unsubscribe = null;
205
+ }
206
+ });
207
+ const computed = () => (unsubscribe ? signal() : signal(fn));
208
+ // Return the signal
209
+ return computed;
210
+ }
@@ -0,0 +1,311 @@
1
+ // The options for a Signal
2
+ export interface SignalOptions<T> {
3
+ equals?: false | ((prev: T, next: T) => boolean);
4
+ }
5
+
6
+ // The types for an unsubscribe and cancel function
7
+ export type Unsubscribe = () => void;
8
+ export type Cancel = () => void;
9
+
10
+ // Different timings for when to execute an observer observing a signal
11
+ export const Timing = {
12
+ // Execute the function on the next tick of the event loop
13
+ Tick: (fn: () => void) => {
14
+ Promise.resolve(fn);
15
+ },
16
+ // Execute the function on the next animation frame
17
+ AnimationFrame: (fn: () => void) => {
18
+ (globalThis as any).requestAnimationFrame(fn);
19
+ },
20
+ };
21
+
22
+ // The context for the current run and its unsubscribes
23
+ let context: { subscriber: ReactiveSignalSubscriber<any>; unsubscribes: Set<Unsubscribe> } | null = null;
24
+
25
+ // A map to keep track of listeners to subscription changes
26
+ const onSubscriptionChanges = new WeakMap<ReactiveSignal<any>, Set<SubscriptionChange>>();
27
+
28
+ /**
29
+ * A Signal is a single getter/setter function that holds a value and notifies subscribers when the value changes
30
+ * The signal function can be called with no arguments to get the current value, with a function argument to run an
31
+ * update function which should receive the value and return a new one, or with a value argument which will replace the
32
+ * signal's current value. The signal function always returns the current value.
33
+ *
34
+ * The optional second argument, `set`, can be used to force the first argument as the new value. This is useful when
35
+ * the first argument is a function or `undefined` since the signal will assume any function is an updater function and
36
+ * any `undefined` value is a request to get the current value.
37
+ */
38
+ export type ReactiveSignal<T> = {
39
+ (): T;
40
+ (value: T | ReactiveSignalUpdater<T>, set?: false): T;
41
+ (value: T, set: true): T;
42
+ };
43
+
44
+ /**
45
+ * A Computed Signal is a signal that is the result of a function that depends on other signals. The function is called
46
+ * whenever the computed signal is accessed if there are no subscribers, or whenever its dependent signals change if
47
+ * there are subscribers so that subscribers to the computed signal can be informed.
48
+ */
49
+ export type ComputedSignal<T> = () => T;
50
+
51
+ /**
52
+ * A Signal Subscriber is a function that will be called whenever the signal's value changes. The subscriber will be
53
+ * called with the new value. The subscriber can be used to update the DOM or trigger other side effects.
54
+ */
55
+ export type ReactiveSignalSubscriber<T> = (value: T) => void;
56
+
57
+ /**
58
+ * A Signal Updater is a function that will be called with the current value of the signal and should return a new
59
+ * value. The updater can be used to update the signal's value based on its current value.
60
+ */
61
+ export type ReactiveSignalUpdater<T> = (prev: T) => T;
62
+
63
+ /**
64
+ * An Observer is a function that will be called whenever any of the signals it depends on change. The observer can be
65
+ * used to update the DOM or trigger other side effects.
66
+ * The observer will be called immediately (or after certain a timing option) and whenever any of the signals it depends
67
+ * on change.
68
+ */
69
+ export type ReactiveSignalObserver = () => void;
70
+
71
+ /**
72
+ * A Timing is a function that will be called with a function to execute. The timing function should execute the passed
73
+ * function at some point in the future. The default timing is `Timing.Immediate` which executes the function
74
+ * immediately.
75
+ */
76
+ export type Timing = (fn: () => void) => Cancel;
77
+
78
+ /**
79
+ * A Subscription Change is a function that will be called whenever the signal's subscribers changes from none to some
80
+ * or some to none. The subscription change will be called with a boolean indicating whether there are any subscribers.
81
+ */
82
+ export type SubscriptionChange = (hasSubscribers: boolean) => void;
83
+
84
+ /**
85
+ * Create a Signal with an initial value and optional options. The options can include an `equals` function which will
86
+ * be used to determine if the new value is different from the current value. If the new value is different, the signal
87
+ * will be updated and all subscribers will be notified.
88
+ * The returned signal function can be called with no arguments to get the current value, with a function argument to
89
+ * run an update function which should receive the value and return a new one, or with a value argument which will
90
+ * replace the signal's current value. The signal function always returns the current value.
91
+ */
92
+ export function reactiveSignal<T>(value: T, options?: SignalOptions<T>): ReactiveSignal<T> {
93
+ // A map to keep track of subscribers
94
+ const subscribers = new Map<ReactiveSignalSubscriber<T>, Unsubscribe>();
95
+
96
+ // The signal is a function that will return a value when called without arguments, or update the value when called
97
+ // with an argument. The update value can be a new value or an updater function.
98
+ const signal = ((newValue?: T | ReactiveSignalUpdater<T>, set?: boolean) => {
99
+ // If no new value is provided, subscribe the current run to this signal and return the current value
100
+ if (!set && newValue === undefined) {
101
+ // If there is a context (an observer is running), add the observer's subscriber to the signal
102
+ if (context) {
103
+ const { subscriber: run, unsubscribes } = context;
104
+ let unsubscribe = subscribers.get(run);
105
+
106
+ // If the run is not already subscribed, subscribe it
107
+ if (!unsubscribe) {
108
+ // Create the unsubscribe function
109
+ unsubscribe = () => {
110
+ subscribers.delete(run);
111
+
112
+ // If there are no more subscribers, notify the subscription changes
113
+ if (subscribers.size === 0) {
114
+ const onChanges = onSubscriptionChanges.get(signal);
115
+ if (onChanges) onChanges.forEach(onChange => onChange(false));
116
+ }
117
+ };
118
+
119
+ // Add the unsubscribe function to the signal's subscribers
120
+ subscribers.set(run, unsubscribe);
121
+
122
+ // If this changed the number of subscribers from 0 to 1, notify any subscription change subscribers
123
+ if (subscribers.size === 1) {
124
+ const onChanges = onSubscriptionChanges.get(signal);
125
+ if (onChanges) onChanges.forEach(onChange => onChange(true));
126
+ }
127
+ }
128
+
129
+ // Add the unsubscribe function to the run's unsubscribes
130
+ unsubscribes.add(unsubscribe);
131
+ }
132
+
133
+ // Return the current value
134
+ return value;
135
+ }
136
+
137
+ // If the new value is a function, call it with the current value as an argument
138
+ if (!set && typeof newValue === 'function') {
139
+ newValue = (newValue as ReactiveSignalUpdater<T>)(value);
140
+ }
141
+
142
+ // If the new value is different from the current value (according to the equals function if provided), update the
143
+ // value and notify all subscribers
144
+ if (options?.equals ? !options.equals(value!, newValue as T) : value !== newValue) {
145
+ value = newValue as T;
146
+ subscribers.forEach((_, run) => run(value!));
147
+ }
148
+ return value;
149
+ }) as ReactiveSignal<T>;
150
+
151
+ // Return the signal function
152
+ return signal;
153
+ }
154
+
155
+ /**
156
+ * Subscribe to be notified whenever a Signal's value changes. The Subscriber function will be called immediately with
157
+ * the current value of the Signal and again whenever the Signal's value changes.
158
+ *
159
+ * The optional third argument, `timing`, can be used to specify when the function should be called. The default is
160
+ * `Timing.Immediate` which executes the function immediately.
161
+ *
162
+ * The returned function can be called to unsubscribe from the Signal.
163
+ */
164
+ export function subscribe<T>(
165
+ signal: ReactiveSignal<T>,
166
+ subscriber: ReactiveSignalSubscriber<T>,
167
+ timing?: Timing
168
+ ): Unsubscribe {
169
+ if (timing) {
170
+ let queued = false;
171
+ const subFn = subscriber;
172
+ subscriber = () => {
173
+ if (!queued) {
174
+ queued = true;
175
+ timing(() => {
176
+ queued = false;
177
+ subFn(signal());
178
+ });
179
+ }
180
+ };
181
+ }
182
+
183
+ // Set the current context so we can get the unsubscribe
184
+ context = { subscriber, unsubscribes: new Set() };
185
+
186
+ // Get the current value of the signal
187
+ const value = signal();
188
+
189
+ // Get the unsubscribe function for the subscriber
190
+ const unsubscribe = context.unsubscribes.values().next().value;
191
+
192
+ // Clear the current context
193
+ context = null;
194
+
195
+ // Call the subscriber with the current value
196
+ subscriber(value);
197
+
198
+ // Return the unsubscribe function
199
+ return unsubscribe;
200
+ }
201
+
202
+ /**
203
+ * Get notified when a Signal's subscribers changes from none to some or some to none.
204
+ */
205
+ export function onSubscriptionChange(
206
+ signal: ReactiveSignal<any>,
207
+ onChange: (hasSubscribers: boolean) => void
208
+ ): Unsubscribe {
209
+ // Get the set of onChange functions for the signal
210
+ let onChanges = onSubscriptionChanges.get(signal);
211
+
212
+ // If there is no set, create one and add it to the map
213
+ if (!onChanges) onSubscriptionChanges.set(signal, (onChanges = new Set()));
214
+
215
+ // Add the onChange function to the set
216
+ onChanges.add(onChange);
217
+
218
+ // Return a function that removes the onChange function from the set
219
+ return () => {
220
+ onChanges!.delete(onChange);
221
+ };
222
+ }
223
+
224
+ /**
225
+ * Calls an Observer function after Timing amount of time (default is immediate, but can be on the next tick or the next
226
+ * animation frame) and again after Timing whenever any of the signals it depends on change.
227
+ * The Observer function will be called immediately (or after the timing) and again whenever any of the signals it
228
+ * depends on change.
229
+ * The returned function can be called to unsubscribe from the signals that are called when the effect is run.
230
+ *
231
+ * The optional second argument, `timing`, can be used to specify when the function should be called. The default is
232
+ * undefined which executes the function immediately.
233
+ */
234
+ export function observe(fn: ReactiveSignalObserver, timing?: Timing): Unsubscribe {
235
+ let dirty = true;
236
+ let unsubscribes = new Set<Unsubscribe>();
237
+
238
+ // Subscribe to all the signals that are called when the effect is run
239
+ const subscriber = () => {
240
+ if (dirty) return;
241
+ dirty = true;
242
+ if (timing) timing(() => onChange());
243
+ else onChange();
244
+ };
245
+
246
+ // Called immediately and whenever any of the signals it depends on change (after the timing)
247
+ const onChange = () => {
248
+ if (!dirty) return;
249
+ dirty = false;
250
+
251
+ // Set the context for the effect
252
+ context = { subscriber, unsubscribes: new Set() };
253
+
254
+ // Run the effect collecting all the unsubscribes from the signals that are called when it is run
255
+ fn();
256
+
257
+ // Filter out unchanged unsubscribes, leaving only those which no longer apply
258
+ context.unsubscribes.forEach(u => unsubscribes.delete(u));
259
+
260
+ // Unsubscribe from all the signals that are no longer needed
261
+ unsubscribes.forEach(u => u());
262
+
263
+ // Set the new unsubscribes
264
+ unsubscribes = context.unsubscribes;
265
+
266
+ // Clear the context
267
+ context = null;
268
+ };
269
+
270
+ // Call immediately (or on the next timing)
271
+ if (timing) timing(() => onChange());
272
+ else onChange();
273
+
274
+ // Return a function that unsubscribes from all the signals that are called when the effect is run
275
+ return () => unsubscribes.forEach(u => u());
276
+ }
277
+
278
+ /**
279
+ * Create a Computed Signal which is a signal that is the result of a function that depends on other signals. The
280
+ * function is called immediately whenever the computed signal is accessed if there are no subscribers, or whenever its
281
+ * dependent signals change if there are subscribers so that subscribers to the computed signal can be informed.
282
+ *
283
+ * The optional second argument, `when`, can be used to specify when updater function should be called. The default is
284
+ * undefined which executes the function immediately after any change to any signal it relies on. This can
285
+ * prevent unnecessary updates if the function is expensive to run.
286
+ */
287
+ export function computedSignal<T>(fn: ReactiveSignalUpdater<T>, when?: Timing): ComputedSignal<T> {
288
+ // Create the signal
289
+ const signal = reactiveSignal<T>(undefined as T);
290
+
291
+ // Store the unsubscribe function from the observer. We will only observe the function when there are subscribers to
292
+ // this computed signal.
293
+ let unsubscribe: Unsubscribe | null = null;
294
+
295
+ // Subscribe to the signal's subscription changes so we know when to start and stop observing
296
+ onSubscriptionChange(signal, hasSubscribers => {
297
+ // If there are subscribers, start observing the function
298
+ if (hasSubscribers) {
299
+ if (!unsubscribe) unsubscribe = observe(() => signal(fn), when);
300
+ } else if (unsubscribe) {
301
+ // If there are no subscribers, stop observing the function
302
+ unsubscribe();
303
+ unsubscribe = null;
304
+ }
305
+ });
306
+
307
+ const computed = () => (unsubscribe ? signal() : signal(fn));
308
+
309
+ // Return the signal
310
+ return computed;
311
+ }
@@ -0,0 +1,11 @@
1
+ import { ReactiveSignal, ReactiveSignalSubscriber } from './reactiveSignal';
2
+ /**
3
+ * A store wrapper around a reactive signal. The store can be used to get, set, update, and subscribe to the signal.
4
+ * This is for use in Svelte 3-4.
5
+ */
6
+ export declare function signalStore<T>(signal: ReactiveSignal<T>): {
7
+ get: ReactiveSignal<T>;
8
+ set: ReactiveSignal<T>;
9
+ update: ReactiveSignal<T>;
10
+ subscribe: (sub: ReactiveSignalSubscriber<T>) => import("./reactiveSignal").Unsubscribe;
11
+ };
package/signalStore.js ADDED
@@ -0,0 +1,13 @@
1
+ import { subscribe } from './reactiveSignal';
2
+ /**
3
+ * A store wrapper around a reactive signal. The store can be used to get, set, update, and subscribe to the signal.
4
+ * This is for use in Svelte 3-4.
5
+ */
6
+ export function signalStore(signal) {
7
+ return {
8
+ get: signal,
9
+ set: signal,
10
+ update: signal,
11
+ subscribe: (sub) => subscribe(signal, sub),
12
+ };
13
+ }
package/signalStore.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { ReactiveSignal, ReactiveSignalSubscriber, subscribe } from './reactiveSignal';
2
+
3
+ /**
4
+ * A store wrapper around a reactive signal. The store can be used to get, set, update, and subscribe to the signal.
5
+ * This is for use in Svelte 3-4.
6
+ */
7
+ export function signalStore<T>(signal: ReactiveSignal<T>) {
8
+ return {
9
+ get: signal,
10
+ set: signal,
11
+ update: signal,
12
+ subscribe: (sub: ReactiveSignalSubscriber<T>) => subscribe(signal, sub),
13
+ };
14
+ }