@cognite/signal-utils 1.0.0

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 ADDED
@@ -0,0 +1,204 @@
1
+ # Signals
2
+
3
+ > [!WARNING]
4
+ > This package is developed and published for Cognite's internal use. It is made publicly available on npm as a
5
+ > side effect of our internal tooling, but no support, stability, or continued availability is guaranteed for
6
+ > external users. Cognite makes no warranty regarding this package and accepts no liability for any security
7
+ > vulnerabilities, defects, or issues arising from its use, by internal or external consumers.
8
+
9
+ This is a library for dealing with signals. A signal contains a value, and you can be notified of changes to the value. Signal is not a new concept and has been in the frontend space for some time. There is even [a proposal](https://github.com/tc39/proposal-signals) to make signals part of the language. Several frontend frameworks use signals in some shape or form. This library is a thin wrapper on top of [Preact Signals](https://github.com/preactjs/signals). We want to control the signals interface, and we hope that we can change the underlying implementation in the future with a native solution.
10
+
11
+ ## API
12
+
13
+ ### `signal(value?)`
14
+
15
+ ```ts
16
+ import { signal } from '@cognite/signal-utils';
17
+
18
+ // Create a signal with an initial value
19
+ const mySignal = signal(42); // Will have the type Signal<number>
20
+ console.log(mySignal()); // 42
21
+ mySignal(43);
22
+ console.log(mySignal()); // 43
23
+
24
+ // Create a signal without an initial value
25
+ const mySignal = signal<string>(); // Will have the type Signal<string | undefined>
26
+ console.log(mySignal()); // undefined
27
+ mySignal('hello');
28
+ console.log(mySignal()); // 'hello'
29
+ ```
30
+
31
+ #### `signal.peek()`
32
+
33
+ ```ts
34
+ import { signal } from '@cognite/signal-utils';
35
+
36
+ // Peek the value without creating a dependency
37
+ const mySignal = signal(42);
38
+ mySignal.peek(); // 42
39
+ ```
40
+
41
+ ### `computed(fn)`
42
+
43
+ ```ts
44
+ import { signal, computed } from '@cognite/signal-utils';
45
+
46
+ const myNumber = signal(42);
47
+ const double = computed(() => myNumber() * 2);
48
+ double() // 84
49
+ myNumber(43);
50
+ double() // 86
51
+ ```
52
+
53
+ ### `effect(fn)`
54
+
55
+ ```ts
56
+ import { signal, effect } from '@cognite/signal-utils';
57
+
58
+ const mySignal = signal(42);
59
+ effect(() => {
60
+ console.log(mySignal());
61
+ });
62
+ // 42 is logged
63
+ mySignal(43);
64
+ // 43 is logged
65
+
66
+ // Cleanup
67
+ effect(() => {
68
+ addEventListener('click', handler);
69
+ return () => { removeEventListener('click', handler); };
70
+ });
71
+ ```
72
+
73
+
74
+ ### `batch(fn)`
75
+
76
+ ```ts
77
+ import { signal, computed, batch, effect } from '@cognite/signal-utils';
78
+
79
+ const givenName = signal('Jane');
80
+ const familyName = signal('Doe');
81
+ const fullName = computed(() => givenName() + ' ' + familyName());
82
+
83
+ effect(() => console.log(fullName()));
84
+ // "Jane Doe" logged
85
+
86
+ // Combines both signal writes into one atomic update.
87
+ batch(() => {
88
+ givenName('Foo');
89
+ familyName('Bar');
90
+ });
91
+ // "Foo Bar" logged
92
+ ```
93
+
94
+ ### `untracked(fn)`
95
+
96
+ ```ts
97
+ import { signal, effect, untracked } from '@cognite/signal-utils';
98
+
99
+ const signalA = signal('A');
100
+ const signalB = signal('B');
101
+
102
+ function someFunction() {
103
+ console.log(signalB());
104
+ }
105
+
106
+ effect(() => {
107
+ console.log(signalA());
108
+ untracked(someFunction);
109
+ })
110
+ // A & B is logged
111
+
112
+ signalA('A2');
113
+ // A2 & B is logged
114
+
115
+ signalB('B2');
116
+ // Nothing is logged as we didn't take a dependency on signalB
117
+ ```
118
+
119
+ ### `arraySignal(value=[])`
120
+
121
+ ```ts
122
+ import { arraySignal } from '@cognite/signal-utils';
123
+
124
+ const myArraySignal = arraySignal([1, 2, 3]);
125
+
126
+ // Getter
127
+ const currentArray = myArraySignal();
128
+
129
+ // Setter
130
+ myArraySignal([4, 5, 6]);
131
+
132
+ // Push new values
133
+ myArraySignal.push(7, 8);
134
+
135
+ // Insert value at specific index
136
+ myArraySignal.insertAt(1, 9);
137
+
138
+ // Remove specific value
139
+ myArraySignal.remove(2);
140
+
141
+ // Remove value at specific index
142
+ myArraySignal.removeAt(0);
143
+
144
+ // Swap values at two indices
145
+ myArraySignal.swap(0, 1);
146
+
147
+ // Clear the array
148
+ myArraySignal.clear();
149
+
150
+ // Peek the current value without taking a dependency
151
+ const peekedArray = myArraySignal.peek();
152
+ ```
153
+
154
+ ### `debouncedComputed(fn, [delayInMs], [immediate=false])`
155
+
156
+ ```ts
157
+ import { signal, debouncedComputed } from '@cognite/signal-utils';
158
+
159
+ const mySignal = signal(42);
160
+
161
+ // `double` is a read-only signal (computed) which updates at a maximum frequency of once every 250 ms.
162
+ const double = debouncedComputed(() => mySignal() * 2, 250);
163
+
164
+ double(); // 84
165
+
166
+ mySignal(12);
167
+ double(); // 84
168
+
169
+ await sleep(250);
170
+ double(); // 24
171
+ ```
172
+
173
+ ### `fragmentSignal(key, defaultValue?, serialize?, deserialize?)`
174
+
175
+ ```ts
176
+ import { fragmentSignal } from '@cognite/signal-utils/extensions';
177
+
178
+ // Create a signal that syncs with the URL hash fragment
179
+ const pageSignal = fragmentSignal('page', 1);
180
+
181
+ // Read the current value (from URL if present, otherwise default)
182
+ pageSignal(); // 1
183
+
184
+ // Update the value (also updates URL to #page=2)
185
+ pageSignal(2);
186
+
187
+ // Clean up when done
188
+ pageSignal.dispose();
189
+ ```
190
+
191
+ ## API (React)
192
+
193
+ ### `useSignalValue(signal)`
194
+
195
+ ```ts
196
+ import { signal } from '@cognite/signal-utils';
197
+ import { useSignalValue } from '@cognite/signal-utils/react';
198
+
199
+ const mySignal = signal(42);
200
+
201
+ // React hook to read the signal value and keep it up-to-date.
202
+ // This works for any ReadonlySignal, including signal, computed, arraySignal.
203
+ const value = useSignalValue(mySignal); // 42
204
+ ```
@@ -0,0 +1,61 @@
1
+ import { ReadonlySignal, Signal } from './signal';
2
+ declare const arraySignalSymbol: unique symbol;
3
+ export type ReadonlyArraySignal<T> = ReadonlySignal<readonly T[]>;
4
+ export type ArraySignal<T> = Signal<readonly T[]> & {
5
+ push(...values: T[]): void;
6
+ insertAt(index: number, value: T): void;
7
+ remove(value: T): void;
8
+ removeAt(index: number): void;
9
+ swap(i: number, j: number): void;
10
+ clear(): void;
11
+ [arraySignalSymbol]: boolean;
12
+ };
13
+ /**
14
+ * Creates an `ArraySignal` which is a reactive signal for an array of values.
15
+ *
16
+ * @template T - The type of elements in the array.
17
+ * @param {readonly T[]} [value=[]] - The initial value of the array.
18
+ * @returns {ArraySignal<T>} A reactive signal for the array.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const myArraySignal = arraySignal([1, 2, 3]);
23
+ *
24
+ * // Getter
25
+ * const currentArray = myArraySignal();
26
+ *
27
+ * // Setter
28
+ * myArraySignal([4, 5, 6]);
29
+ *
30
+ * // Push new values
31
+ * myArraySignal.push(7, 8);
32
+ *
33
+ * // Insert value at specific index
34
+ * myArraySignal.insertAt(1, 9);
35
+ *
36
+ * // Remove specific value
37
+ * myArraySignal.remove(2);
38
+ *
39
+ * // Remove value at specific index
40
+ * myArraySignal.removeAt(0);
41
+ *
42
+ * // Swap values at two indices
43
+ * myArraySignal.swap(0, 1);
44
+ *
45
+ * // Clear the array
46
+ * myArraySignal.clear();
47
+ *
48
+ * // Peek the current value without taking a dependency
49
+ * const peekedArray = myArraySignal.peek();
50
+ * ```
51
+ */
52
+ export declare function arraySignal<T>(value?: readonly T[]): ArraySignal<T>;
53
+ /**
54
+ * Checks if the given value is an ArraySignal.
55
+ *
56
+ * @template T - The type of elements in the array signal.
57
+ * @param value - The value to check.
58
+ * @returns A boolean indicating whether the value is an ArraySignal.
59
+ */
60
+ export declare function isArraySignal<T>(value: unknown): value is ArraySignal<T>;
61
+ export {};
@@ -0,0 +1,36 @@
1
+ import { insertAt as e, remove as t, removeAt as n, swap as r } from "../utils/arrays.js";
2
+ import { signal as i } from "./signal.js";
3
+ //#region libs/shared/signal-utils/src/core/array-signal.ts
4
+ var a = Symbol("ArraySignal");
5
+ function o(o = []) {
6
+ let s = i([...o]);
7
+ function c(e) {
8
+ if (arguments.length === 1) {
9
+ s([...e]);
10
+ return;
11
+ }
12
+ return s();
13
+ }
14
+ return c[a] = !0, c.push = (...e) => {
15
+ s([...s.peek(), ...e]);
16
+ }, c.insertAt = (t, n) => {
17
+ let r = [...s.peek()];
18
+ e(r, t, n), s(r);
19
+ }, c.remove = (e) => {
20
+ let n = [...s.peek()];
21
+ t(n, e), s(n);
22
+ }, c.removeAt = (e) => {
23
+ let t = [...s.peek()];
24
+ n(t, e), s(t);
25
+ }, c.swap = (e, t) => {
26
+ let n = [...s.peek()];
27
+ r(n, e, t), s(n);
28
+ }, c.clear = () => {
29
+ s([]);
30
+ }, c.peek = () => s.peek(), c.subscribe = (e) => s.subscribe(e), c;
31
+ }
32
+ function s(e) {
33
+ return typeof e == "function" && a in e;
34
+ }
35
+ //#endregion
36
+ export { o as arraySignal, s as isArraySignal };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Executes a function within a batch, ensuring that signal updates are batched together (atomic operation).
3
+ *
4
+ * @param fn - The function to be executed within the batch.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const givenName = signal('Jane');
9
+ * const familyName = signal('Doe');
10
+ * const fullName = computed(() => givenName() + ' ' + familyName());
11
+ *
12
+ * effect(() => console.log(fullName()));
13
+ * // "Jane Doe" logged
14
+ *
15
+ * // Combines both signal writes into one atomic update.
16
+ * batch(() => {
17
+ * givenName('Foo');
18
+ * familyName('Bar');
19
+ * });
20
+ * // "Foo Bar" logged
21
+ * ```
22
+ */
23
+ export declare function batch(fn: VoidFunction): void;
@@ -0,0 +1,7 @@
1
+ import { batch as e } from "@preact/signals-core";
2
+ //#region libs/shared/signal-utils/src/core/batch.ts
3
+ function t(t) {
4
+ e(t);
5
+ }
6
+ //#endregion
7
+ export { t as batch };
@@ -0,0 +1,19 @@
1
+ import { ReadonlySignal } from './signal';
2
+ /**
3
+ * Creates a computed signal that derives its value from the provided function.
4
+ *
5
+ * @template T - The type of the value returned by the computed function.
6
+ * @param {() => T} fn - A function that returns the value for the computed signal.
7
+ * @returns {ReadonlySignal<T>} A readonly signal that provides the computed value.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const myNumber = signal(42);
12
+ * const double = computed(() => myNumber() * 2);
13
+ * double() // 84
14
+ *
15
+ * myNumber(43);
16
+ * double() // 86
17
+ * ```
18
+ */
19
+ export declare function computed<T>(fn: () => T): ReadonlySignal<T>;
@@ -0,0 +1,11 @@
1
+ import { computed as e } from "@preact/signals-core";
2
+ //#region libs/shared/signal-utils/src/core/computed.ts
3
+ function t(t) {
4
+ let n = e(t);
5
+ function r() {
6
+ return n.value;
7
+ }
8
+ return r.peek = () => n.peek(), r.subscribe = (e) => n.subscribe(e), r;
9
+ }
10
+ //#endregion
11
+ export { t as computed };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Calls the provided function, first initially, and when a signal dependency changes.
3
+ *
4
+ * The provided function can optionally return a cleanup function that will run only once.
5
+ * It will run when the effect is disposed or right before the next invocation of provided function.
6
+ *
7
+ * @param fn
8
+ * @returns A method to dispose the effect.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const mySignal = signal(42);
13
+ * effect(() => {
14
+ * console.log(mySignal());
15
+ * });
16
+ * // 42 is logged
17
+ *
18
+ * mySignal(43);
19
+ * // 43 is logged
20
+ * ```
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * // Cleanup
25
+ * effect(() => {
26
+ * addEventListener('click', handler);
27
+ * return () => { removeEventListener('click', handler); };
28
+ * });
29
+ * ```
30
+ */
31
+ export declare function effect(fn: EffectFn): Dispose;
32
+ type Dispose = VoidFunction;
33
+ type EffectFn = () => void | Dispose;
34
+ export {};
@@ -0,0 +1,7 @@
1
+ import { effect as e } from "@preact/signals-core";
2
+ //#region libs/shared/signal-utils/src/core/effect.ts
3
+ function t(t) {
4
+ return e(t);
5
+ }
6
+ //#endregion
7
+ export { t as effect };
@@ -0,0 +1,88 @@
1
+ declare const signalSymbol: unique symbol;
2
+ /**
3
+ * Represents a readonly signal that can be used to get the current value.
4
+ *
5
+ * @template T - The type of the signal value.
6
+ */
7
+ export type ReadonlySignal<T> = {
8
+ (): T;
9
+ /**
10
+ * Let you peek the signal value without taking dependency on the signal.
11
+ * Peeking is useful in scenarios where you want to read the value, but not update on value changes.
12
+ *
13
+ * @returns The current signal value.
14
+ */
15
+ peek(): T;
16
+ /**
17
+ * Subscribes to value changes. The provided function will be called once initially with the current value.
18
+ * @param fn The provided function will be invoked with a single argument, the newest signal value.
19
+ * @returns A function to unsubscribe.
20
+ */
21
+ subscribe: (fn: Subscriber<T>) => Unsubscribe;
22
+ };
23
+ export type Subscriber<T> = (value: T) => void;
24
+ export type Unsubscribe = VoidFunction;
25
+ /**
26
+ * Represents a signal that can be used to get and set the current value.
27
+ *
28
+ * @template T - The type of the signal value.
29
+ */
30
+ export type Signal<T> = ReadonlySignal<T> & {
31
+ (value: T): void;
32
+ [signalSymbol]?: boolean;
33
+ };
34
+ /**
35
+ * Creates a new signal with an optional initial value.
36
+ *
37
+ * A signal is a reactive primitive that holds a value and can be used to track changes to that value.
38
+ * The signal can be read by calling it as a function, and it can be updated by calling it with a new value.
39
+ * The `peek` method can be used to read the current value without taking dependency on it.
40
+ *
41
+ * @template T - The type of the signal value.
42
+ * @param {T} [value] - The initial value of the signal.
43
+ * @returns {Signal<T | undefined> | Signal<T>} - The created signal.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * // Create a signal with an initial value
48
+ * const mySignal = signal(42); // Will have the type Signal<number>
49
+ * console.log(mySignal()); // 42
50
+ * mySignal(43);
51
+ * console.log(mySignal()); // 43
52
+ * ```
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * // Create a signal without an initial value
57
+ * const mySignal = signal<string>(); // Will have the type Signal<string | undefined>
58
+ * console.log(mySignal()); // undefined
59
+ * mySignal('hello');
60
+ * console.log(mySignal()); // 'hello'
61
+ * ```
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * // Peek the value without creating a dependency
66
+ * const mySignal = signal(42);
67
+ * mySignal.peek(); // 42
68
+ * ```
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * // Subscribe to value changes
73
+ * const mySignal = signal(42);
74
+ * mySignal.subscribe(console.log); // Logs 42
75
+ * mySignal(43); // Logs 43
76
+ * ```
77
+ */
78
+ export declare function signal<T>(): Signal<T | undefined>;
79
+ export declare function signal<T>(value: T): Signal<T>;
80
+ /**
81
+ * Checks if the given value is a Signal.
82
+ *
83
+ * @template T - The value type of the signal.
84
+ * @param value - The value to check.
85
+ * @returns A boolean indicating whether the value is a Signal.
86
+ */
87
+ export declare function isSignal<T>(value: unknown): value is Signal<T>;
88
+ export {};
@@ -0,0 +1,19 @@
1
+ import { signal as e } from "@preact/signals-core";
2
+ //#region libs/shared/signal-utils/src/core/signal.ts
3
+ var t = Symbol("signal");
4
+ function n(n) {
5
+ let r = e(n);
6
+ function i(e) {
7
+ if (arguments.length === 1) {
8
+ r.value = e;
9
+ return;
10
+ }
11
+ return r.value;
12
+ }
13
+ return i[t] = !0, i.peek = () => r.peek(), i.subscribe = (e) => r.subscribe(e), i;
14
+ }
15
+ function r(e) {
16
+ return typeof e == "function" && t in e;
17
+ }
18
+ //#endregion
19
+ export { r as isSignal, n as signal };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Calls the provided function, without taking dependency on any signals called within it.
3
+ * @param fn
4
+ * @returns The value returned from `fn`.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const signalA = signal('A');
9
+ * const signalB = signal('B');
10
+ *
11
+ * function someFunction() {
12
+ * console.log(signalB());
13
+ * }
14
+ *
15
+ * effect(() => {
16
+ * console.log(signalA());
17
+ * untracked(someFunction);
18
+ * })
19
+ * // A & B is logged
20
+ *
21
+ * signalA('A2');
22
+ * // A2 & B is logged
23
+ *
24
+ * signalB('B2');
25
+ * // Nothing is logged as we didn't take a dependency on signalB
26
+ * ```
27
+ */
28
+ export declare function untracked<T>(fn: () => T): T;
@@ -0,0 +1,7 @@
1
+ import { untracked as e } from "@preact/signals-core";
2
+ //#region libs/shared/signal-utils/src/core/untracked.ts
3
+ function t(t) {
4
+ return e(t);
5
+ }
6
+ //#endregion
7
+ export { t as untracked };
@@ -0,0 +1,89 @@
1
+ import { Signal } from '../core/signal';
2
+ export type FragmentSignal<T> = Signal<T> & {
3
+ dispose: () => void;
4
+ };
5
+ /**
6
+ * Creates a signal that synchronizes its value with the URL hash fragment.
7
+ *
8
+ * This function creates a reactive signal that automatically reads from and writes to
9
+ * the URL hash fragment (the part after `#`). When the signal value changes, the URL
10
+ * hash is updated. When the URL hash changes (e.g., via browser navigation), the
11
+ * signal value is updated accordingly.
12
+ *
13
+ * @template T - The type of the value stored in the signal
14
+ *
15
+ * @param key - The parameter name to use in the URL hash fragment
16
+ * @param defaultValue - The default value to use when the URL parameter is not present
17
+ * @param serialize - Optional function to serialize the value to a string. Defaults to `JSON.stringify`
18
+ * @param deserialize - Optional function to deserialize a string to a value. Defaults to `JSON.parse`
19
+ *
20
+ * @returns A signal that synchronizes with the URL hash fragment, including a `dispose()` method to clean up event listeners
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * // Basic usage with a default value
25
+ * const pageSignal = fragmentSignal('page', 1);
26
+ *
27
+ * // Read the current value
28
+ * console.log(pageSignal()); // 1 (or value from URL if present)
29
+ *
30
+ * // Update the value (also updates URL to #page=2)
31
+ * pageSignal(2);
32
+ *
33
+ * // Clear the value (removes from URL and resets to default)
34
+ * pageSignal(null);
35
+ *
36
+ * // Clean up when done
37
+ * pageSignal.dispose();
38
+ * ```
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * // Usage without default value (signal will be undefined if not in URL)
43
+ * const optionalSignal = fragmentSignal<string>('filter');
44
+ *
45
+ * console.log(optionalSignal()); // undefined (if not in URL)
46
+ * optionalSignal('active'); // Sets URL to #filter=active
47
+ * optionalSignal(undefined); // Removes from URL
48
+ * ```
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * // Custom serialization for complex objects
53
+ * interface Filter {
54
+ * category: string;
55
+ * priceRange: [number, number];
56
+ * }
57
+ *
58
+ * const filterSignal = fragmentSignal<Filter>(
59
+ * 'filter',
60
+ * { category: 'all', priceRange: [0, 100] },
61
+ * (filter) => btoa(JSON.stringify(filter)), // Base64 encode
62
+ * (str) => JSON.parse(atob(str)) // Base64 decode
63
+ * );
64
+ * ```
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * // Multiple fragment signals can coexist
69
+ * const searchSignal = fragmentSignal('q', '');
70
+ * const sortSignal = fragmentSignal('sort', 'name');
71
+ *
72
+ * searchSignal('react');
73
+ * sortSignal('date');
74
+ * // URL becomes: #q=react&sort=date
75
+ * ```
76
+ *
77
+ * @remarks
78
+ * - The signal automatically listens to `hashchange` events and updates accordingly
79
+ * - Setting the signal to `null` or `undefined` resets the parameter to the default value.
80
+ * If it is initialized without a default value, then it will remove the hash key from the URL.
81
+ * - The URL hash uses `URLSearchParams` format for encoding multiple parameters
82
+ * - The signal persists across page reloads as long as the URL hash remains
83
+ * - Call `dispose()` on the returned signal to remove the `hashchange` event listener when no longer needed
84
+ * - Two overloads are available:
85
+ * 1. With default value: `fragmentSignal(key, defaultValue, serialize?, deserialize?)` - returns `Signal<T>`
86
+ * 2. Without default value: `fragmentSignal(key, serialize?, deserialize?)` - returns `Signal<T | undefined>`
87
+ */
88
+ export declare function fragmentSignal<T extends NonNullable<unknown>>(key: string, defaultValue: T, serialize?: (value: T) => string, deserialize?: (hashString: string) => T): FragmentSignal<T>;
89
+ export declare function fragmentSignal<T>(key: string, serialize?: (value: T) => string, deserialize?: (hashString: string) => T): FragmentSignal<T | undefined | null>;
@@ -0,0 +1,21 @@
1
+ import { signal as e } from "../core/signal.js";
2
+ //#region libs/shared/signal-utils/src/extensions/fragmentSignal.ts
3
+ function t(t, n, r = JSON.stringify, i = JSON.parse) {
4
+ let a = u().get(t), o = a === null ? n : i(a), s = e(o), c = new AbortController();
5
+ window.addEventListener("hashchange", () => {
6
+ let e = u().get(t), r = e === null ? n : i(e);
7
+ s(r);
8
+ }, { signal: c.signal });
9
+ let l = new Proxy(s, { apply: function(e, n, i) {
10
+ if (i.length === 0) return Reflect.apply(e, n, i);
11
+ let a = u(), o = i[0];
12
+ return o == null ? a.delete(t) : a.set(t, r(o)), window.location.hash = a.toString(), o;
13
+ } });
14
+ return Object.assign(l, { dispose: () => c.abort() });
15
+ function u() {
16
+ let e = window.location.hash.substring(1);
17
+ return new URLSearchParams(e);
18
+ }
19
+ }
20
+ //#endregion
21
+ export { t as fragmentSignal };
@@ -0,0 +1 @@
1
+ export { fragmentSignal, type FragmentSignal, } from './extensions/fragmentSignal';
@@ -0,0 +1,2 @@
1
+ import { fragmentSignal as e } from "./extensions/fragmentSignal.js";
2
+ export { e as fragmentSignal };
@@ -0,0 +1,10 @@
1
+ export { arraySignal, isArraySignal } from './core/array-signal';
2
+ export { batch } from './core/batch';
3
+ export { computed } from './core/computed';
4
+ export { debouncedComputed } from './misc/debounced-computed';
5
+ export { effect } from './core/effect';
6
+ export { signal, isSignal } from './core/signal';
7
+ export { untracked } from './core/untracked';
8
+ export type { ArraySignal, ReadonlyArraySignal } from './core/array-signal';
9
+ export type { DebouncedComputed } from './misc/debounced-computed';
10
+ export type { Signal, ReadonlySignal } from './core/signal';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ import { isSignal as e, signal as t } from "./core/signal.js";
2
+ import { arraySignal as n, isArraySignal as r } from "./core/array-signal.js";
3
+ import { batch as i } from "./core/batch.js";
4
+ import { computed as a } from "./core/computed.js";
5
+ import { effect as o } from "./core/effect.js";
6
+ import { debouncedComputed as s } from "./misc/debounced-computed.js";
7
+ import { untracked as c } from "./core/untracked.js";
8
+ export { n as arraySignal, i as batch, a as computed, s as debouncedComputed, o as effect, r as isArraySignal, e as isSignal, t as signal, c as untracked };
@@ -0,0 +1,25 @@
1
+ import { ReadonlySignal } from '../core/signal';
2
+ /**
3
+ * Represents a debounced computed value.
4
+ *
5
+ * @template T - The type of the computed value.
6
+ *
7
+ * @property {ReadonlySignal<T>} value - The debounced computed value.
8
+ * @property {VoidFunction} dispose - A function to dispose updates to the computed value.
9
+ */
10
+ export type DebouncedComputed<T> = {
11
+ value: ReadonlySignal<T>;
12
+ dispose: VoidFunction;
13
+ };
14
+ /**
15
+ * Creates a debounced computed value based on the provided function.
16
+ * The value will be set immediately, and debounce thereafter. This means the computed value will be updated at a maximum
17
+ * frequency of once every `delayInMs` milliseconds.
18
+ *
19
+ * @template T - The type of the computed value.
20
+ * @param {() => T} func - The function that returns the computed value.
21
+ * @param {number} [delayInMs] - The minimum amount of time that must pass between between each value update. If not provided, a default delay will be used.
22
+ * @param {boolean} [immediate=false] - If true, the function will be called at the start of the wait period, rather than the end.
23
+ * @returns {DebouncedComputed<T>} An object containing the debounced computed value and a dispose function.
24
+ */
25
+ export declare function debouncedComputed<T>(func: () => T, delayInMs?: number, immediate?: boolean): DebouncedComputed<T>;
@@ -0,0 +1,17 @@
1
+ import { signal as e } from "../core/signal.js";
2
+ import { computed as t } from "../core/computed.js";
3
+ import { effect as n } from "../core/effect.js";
4
+ import { debounce as r } from "../utils/debounce.js";
5
+ //#region libs/shared/signal-utils/src/misc/debounced-computed.ts
6
+ function i(i, a, o = !1) {
7
+ let s = !1, c = e(void 0), l = r(c, a, o), u = t(i), d = n(() => {
8
+ let e = u();
9
+ s ? l(e) : (c(e), s = !0);
10
+ });
11
+ return {
12
+ value: t(() => c()),
13
+ dispose: d
14
+ };
15
+ }
16
+ //#endregion
17
+ export { i as debouncedComputed };
@@ -0,0 +1,9 @@
1
+ import { ReadonlySignal } from '../core/signal';
2
+ /**
3
+ * A custom hook that returns the signal's value and keeps the value up-to-date.
4
+ *
5
+ * @template T - The type of the value held by the signal.
6
+ * @param {ReadonlySignal<T>} signal - The signal.
7
+ * @returns {T} - The current value of the signal.
8
+ */
9
+ export declare function useSignalValue<T>(signal: ReadonlySignal<T>): T;
@@ -0,0 +1,7 @@
1
+ import { useSyncExternalStore as e } from "react";
2
+ //#region libs/shared/signal-utils/src/react/use-signal-value.ts
3
+ function t(t) {
4
+ return e(t.subscribe, t.peek);
5
+ }
6
+ //#endregion
7
+ export { t as useSignalValue };
@@ -0,0 +1 @@
1
+ export { useSignalValue } from './react/use-signal-value';
package/dist/react.js ADDED
@@ -0,0 +1,2 @@
1
+ import { useSignalValue as e } from "./react/use-signal-value.js";
2
+ export { e as useSignalValue };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Inserts an element at a specific index in the array.
3
+ *
4
+ * @param array
5
+ * @param index
6
+ * @param item
7
+ */
8
+ export declare function insertAt<T>(array: T[], index: number, item: T): void;
9
+ /**
10
+ * Removes an element from the array.
11
+ *
12
+ * @param array
13
+ * @param item
14
+ */
15
+ export declare function remove<T>(array: T[], item: T): void;
16
+ /**
17
+ * Removes an element at the index from the array.
18
+ *
19
+ * @param array
20
+ * @param item
21
+ */
22
+ export declare function removeAt(array: unknown[], index: number): void;
23
+ /**
24
+ * Swaps two elements in the array on index `i` and `j`.
25
+ *
26
+ * @param array
27
+ * @param i
28
+ * @param j
29
+ */
30
+ export declare function swap(array: unknown[], i: number, j: number): void;
@@ -0,0 +1,17 @@
1
+ //#region libs/shared/signal-utils/src/utils/arrays.ts
2
+ function e(e, t, n) {
3
+ e.splice(t, 0, n);
4
+ }
5
+ function t(e, t) {
6
+ let r = e.indexOf(t);
7
+ r !== -1 && n(e, r);
8
+ }
9
+ function n(e, t) {
10
+ e.splice(t, 1);
11
+ }
12
+ function r(e, t, n) {
13
+ if (t < 0 || n < 0 || t >= e.length || n >= e.length) throw Error("Index out of bounds");
14
+ [e[t], e[n]] = [e[n], e[t]];
15
+ }
16
+ //#endregion
17
+ export { e as insertAt, t as remove, n as removeAt, r as swap };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Wraps a function in a debounce function that will only execute
3
+ * the provided function at a maximum frequency of once every `delayInMs` milliseconds.
4
+ *
5
+ * @param callback The function to debounce.
6
+ * @param delayInMs The minimum amount of time that must pass between calls to the function.
7
+ * @param immediate If true, the function will be called at the start of the wait period, rather than the end.
8
+ * @returns
9
+ */
10
+ export declare function debounce<T extends (...args: any[]) => void>(callback: T, delayInMs?: number, immediate?: boolean): <U>(this: U, ...args: Parameters<typeof callback>) => void;
@@ -0,0 +1,12 @@
1
+ //#region libs/shared/signal-utils/src/utils/debounce.ts
2
+ function e(e, t = 250, n = !1) {
3
+ let r = null;
4
+ return function(...i) {
5
+ let a = () => {
6
+ r = null, n || e.apply(this, i);
7
+ }, o = n && r === null;
8
+ r !== null && clearTimeout(r), r = setTimeout(a, t), o && e.apply(this, i);
9
+ };
10
+ }
11
+ //#endregion
12
+ export { e as debounce };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Repeats the execution of a given function a specified number of times.
3
+ *
4
+ * @param fn - The function to be executed repeatedly. It should not take any arguments and should not return any value.
5
+ * @param times - The number of times to execute the function.
6
+ */
7
+ export declare function repeat(fn: VoidFunction, times: number): void;
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@cognite/signal-utils",
3
+ "version": "1.0.0",
4
+ "description": "A thin, framework-agnostic wrapper around Preact Signals for reactive state management.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.js",
13
+ "types": "./dist/index.d.ts"
14
+ },
15
+ "./package.json": "./package.json",
16
+ "./react": {
17
+ "import": "./dist/react.js",
18
+ "types": "./dist/react.d.ts"
19
+ },
20
+ "./extensions": {
21
+ "import": "./dist/extensions.js",
22
+ "types": "./dist/extensions.d.ts"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "dependencies": {
29
+ "@preact/signals-core": "^1.8.0"
30
+ },
31
+ "devDependencies": {
32
+ "@nx/eslint-plugin": "22.7.8",
33
+ "@testing-library/react": "16.0.1",
34
+ "@types/react": "18.3.14",
35
+ "@vitejs/plugin-react": "^5.2.0",
36
+ "react": "^18.3.1",
37
+ "vite": "8.2.2",
38
+ "vitest": "^4.1.10"
39
+ },
40
+ "peerDependencies": {
41
+ "react": "^18.3.1"
42
+ }
43
+ }