@cognite/signal-utils 1.0.1 → 1.48535.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.
Files changed (47) hide show
  1. package/README.md +7 -6
  2. package/dist/core/array-signal.d.ts +52 -52
  3. package/dist/core/array-signal.js +105 -34
  4. package/dist/core/array-signal.js.map +1 -0
  5. package/dist/core/batch.d.ts +21 -21
  6. package/dist/core/batch.js +26 -6
  7. package/dist/core/batch.js.map +1 -0
  8. package/dist/core/computed.d.ts +17 -17
  9. package/dist/core/computed.js +27 -10
  10. package/dist/core/computed.js.map +1 -0
  11. package/dist/core/effect.d.ts +29 -29
  12. package/dist/core/effect.js +34 -6
  13. package/dist/core/effect.js.map +1 -0
  14. package/dist/core/signal.d.ts +73 -73
  15. package/dist/core/signal.js +25 -17
  16. package/dist/core/signal.js.map +1 -0
  17. package/dist/core/untracked.d.ts +26 -26
  18. package/dist/core/untracked.js +31 -6
  19. package/dist/core/untracked.js.map +1 -0
  20. package/dist/extensions/fragmentSignal.d.ts +84 -84
  21. package/dist/extensions/fragmentSignal.js +38 -20
  22. package/dist/extensions/fragmentSignal.js.map +1 -0
  23. package/dist/extensions.d.ts +1 -1
  24. package/dist/extensions.js +3 -2
  25. package/dist/extensions.js.map +1 -0
  26. package/dist/index.d.ts +10 -10
  27. package/dist/index.js +9 -8
  28. package/dist/index.js.map +1 -0
  29. package/dist/misc/debounced-computed.d.ts +20 -20
  30. package/dist/misc/debounced-computed.js +37 -16
  31. package/dist/misc/debounced-computed.js.map +1 -0
  32. package/dist/react/use-signal-value.d.ts +7 -7
  33. package/dist/react/use-signal-value.js +11 -6
  34. package/dist/react/use-signal-value.js.map +1 -0
  35. package/dist/react.d.ts +1 -1
  36. package/dist/react.js +3 -2
  37. package/dist/react.js.map +1 -0
  38. package/dist/utils/arrays.d.ts +22 -22
  39. package/dist/utils/arrays.js +41 -13
  40. package/dist/utils/arrays.js.map +1 -0
  41. package/dist/utils/debounce.d.ts +8 -8
  42. package/dist/utils/debounce.js +30 -11
  43. package/dist/utils/debounce.js.map +1 -0
  44. package/dist/utils/repeat.d.ts +5 -5
  45. package/dist/utils/repeat.js +12 -0
  46. package/dist/utils/repeat.js.map +1 -0
  47. package/package.json +7 -3
@@ -1,88 +1,88 @@
1
1
  declare const signalSymbol: unique symbol;
2
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
- */
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
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;
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
22
  };
23
23
  export type Subscriber<T> = (value: T) => void;
24
24
  export type Unsubscribe = VoidFunction;
25
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
- */
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
30
  export type Signal<T> = ReadonlySignal<T> & {
31
- (value: T): void;
32
- [signalSymbol]?: boolean;
31
+ (value: T): void;
32
+ [signalSymbol]?: boolean;
33
33
  };
34
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
- */
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
78
  export declare function signal<T>(): Signal<T | undefined>;
79
79
  export declare function signal<T>(value: T): Signal<T>;
80
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
- */
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
87
  export declare function isSignal<T>(value: unknown): value is Signal<T>;
88
88
  export {};
@@ -1,19 +1,27 @@
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;
1
+ import { signal as preactSignal } from "@preact/signals-core";
2
+ const signalSymbol = Symbol('signal');
3
+ export function signal(value) {
4
+ const innerSignal = preactSignal(value);
5
+ function fn(newValue) {
6
+ if (arguments.length === 1) {
7
+ innerSignal.value = newValue;
8
+ return;
9
+ }
10
+ return innerSignal.value;
11
+ }
12
+ fn[signalSymbol] = true;
13
+ fn.peek = ()=>innerSignal.peek();
14
+ fn.subscribe = (fn)=>innerSignal.subscribe(fn);
15
+ return fn;
14
16
  }
15
- function r(e) {
16
- return typeof e == "function" && t in e;
17
+ /**
18
+ * Checks if the given value is a Signal.
19
+ *
20
+ * @template T - The value type of the signal.
21
+ * @param value - The value to check.
22
+ * @returns A boolean indicating whether the value is a Signal.
23
+ */ export function isSignal(value) {
24
+ return typeof value === 'function' && signalSymbol in value;
17
25
  }
18
- //#endregion
19
- export { r as isSignal, n as signal };
26
+
27
+ //# sourceMappingURL=signal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/signal.ts"],"sourcesContent":["import { signal as preactSignal } from '@preact/signals-core';\n\nconst signalSymbol: unique symbol = Symbol('signal');\n\n/**\n * Represents a readonly signal that can be used to get the current value.\n *\n * @template T - The type of the signal value.\n */\nexport type ReadonlySignal<T> = {\n (): T;\n /**\n * Let you peek the signal value without taking dependency on the signal.\n * Peeking is useful in scenarios where you want to read the value, but not update on value changes.\n *\n * @returns The current signal value.\n */\n peek(): T;\n /**\n * Subscribes to value changes. The provided function will be called once initially with the current value.\n * @param fn The provided function will be invoked with a single argument, the newest signal value.\n * @returns A function to unsubscribe.\n */\n subscribe: (fn: Subscriber<T>) => Unsubscribe;\n};\n\nexport type Subscriber<T> = (value: T) => void;\nexport type Unsubscribe = VoidFunction;\n\n/**\n * Represents a signal that can be used to get and set the current value.\n *\n * @template T - The type of the signal value.\n */\nexport type Signal<T> = ReadonlySignal<T> & {\n (value: T): void;\n [signalSymbol]?: boolean;\n};\n\n/**\n * Creates a new signal with an optional initial value.\n *\n * A signal is a reactive primitive that holds a value and can be used to track changes to that value.\n * The signal can be read by calling it as a function, and it can be updated by calling it with a new value.\n * The `peek` method can be used to read the current value without taking dependency on it.\n *\n * @template T - The type of the signal value.\n * @param {T} [value] - The initial value of the signal.\n * @returns {Signal<T | undefined> | Signal<T>} - The created signal.\n *\n * @example\n * ```ts\n * // Create a signal with an initial value\n * const mySignal = signal(42); // Will have the type Signal<number>\n * console.log(mySignal()); // 42\n * mySignal(43);\n * console.log(mySignal()); // 43\n * ```\n *\n * @example\n * ```ts\n * // Create a signal without an initial value\n * const mySignal = signal<string>(); // Will have the type Signal<string | undefined>\n * console.log(mySignal()); // undefined\n * mySignal('hello');\n * console.log(mySignal()); // 'hello'\n * ```\n *\n * @example\n * ```ts\n * // Peek the value without creating a dependency\n * const mySignal = signal(42);\n * mySignal.peek(); // 42\n * ```\n *\n * @example\n * ```ts\n * // Subscribe to value changes\n * const mySignal = signal(42);\n * mySignal.subscribe(console.log); // Logs 42\n * mySignal(43); // Logs 43\n * ```\n */\nexport function signal<T>(): Signal<T | undefined>;\nexport function signal<T>(value: T): Signal<T>;\nexport function signal<T>(value?: T): Signal<T | undefined> | Signal<T> {\n const innerSignal = preactSignal(value as T);\n\n function fn(): T; // Getter\n function fn(newValue: T): void; // Setter\n function fn(newValue?: T): T | void {\n if (arguments.length === 1) {\n innerSignal.value = newValue as T;\n return;\n }\n return innerSignal.value;\n }\n\n fn[signalSymbol] = true;\n fn.peek = (): T => innerSignal.peek();\n fn.subscribe = (fn: Subscriber<T>): Unsubscribe => innerSignal.subscribe(fn);\n\n return fn;\n}\n\n/**\n * Checks if the given value is a Signal.\n *\n * @template T - The value type of the signal.\n * @param value - The value to check.\n * @returns A boolean indicating whether the value is a Signal.\n */\nexport function isSignal<T>(value: unknown): value is Signal<T> {\n return typeof value === 'function' && signalSymbol in value;\n}\n"],"names":["signal","preactSignal","signalSymbol","Symbol","value","innerSignal","fn","newValue","arguments","length","peek","subscribe","isSignal"],"mappings":"AAAA,SAASA,UAAUC,YAAY,QAAQ,uBAAuB;AAE9D,MAAMC,eAA8BC,OAAO;AAmF3C,OAAO,SAASH,OAAUI,KAAS;IACjC,MAAMC,cAAcJ,aAAaG;IAIjC,SAASE,GAAGC,QAAY;QACtB,IAAIC,UAAUC,MAAM,KAAK,GAAG;YAC1BJ,YAAYD,KAAK,GAAGG;YACpB;QACF;QACA,OAAOF,YAAYD,KAAK;IAC1B;IAEAE,EAAE,CAACJ,aAAa,GAAG;IACnBI,GAAGI,IAAI,GAAG,IAASL,YAAYK,IAAI;IACnCJ,GAAGK,SAAS,GAAG,CAACL,KAAmCD,YAAYM,SAAS,CAACL;IAEzE,OAAOA;AACT;AAEA;;;;;;CAMC,GACD,OAAO,SAASM,SAAYR,KAAc;IACxC,OAAO,OAAOA,UAAU,cAAcF,gBAAgBE;AACxD"}
@@ -1,28 +1,28 @@
1
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
- */
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
28
  export declare function untracked<T>(fn: () => T): T;
@@ -1,7 +1,32 @@
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);
1
+ import { untracked as preactUntracked } from "@preact/signals-core";
2
+ /**
3
+ * Calls the provided function, without taking dependency on any signals called within it.
4
+ * @param fn
5
+ * @returns The value returned from `fn`.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * const signalA = signal('A');
10
+ * const signalB = signal('B');
11
+ *
12
+ * function someFunction() {
13
+ * console.log(signalB());
14
+ * }
15
+ *
16
+ * effect(() => {
17
+ * console.log(signalA());
18
+ * untracked(someFunction);
19
+ * })
20
+ * // A & B is logged
21
+ *
22
+ * signalA('A2');
23
+ * // A2 & B is logged
24
+ *
25
+ * signalB('B2');
26
+ * // Nothing is logged as we didn't take a dependency on signalB
27
+ * ```
28
+ */ export function untracked(fn) {
29
+ return preactUntracked(fn);
5
30
  }
6
- //#endregion
7
- export { t as untracked };
31
+
32
+ //# sourceMappingURL=untracked.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/untracked.ts"],"sourcesContent":["import { untracked as preactUntracked } from '@preact/signals-core';\n\n/**\n * Calls the provided function, without taking dependency on any signals called within it.\n * @param fn\n * @returns The value returned from `fn`.\n *\n * @example\n * ```ts\n * const signalA = signal('A');\n * const signalB = signal('B');\n *\n * function someFunction() {\n * console.log(signalB());\n * }\n *\n * effect(() => {\n * console.log(signalA());\n * untracked(someFunction);\n * })\n * // A & B is logged\n *\n * signalA('A2');\n * // A2 & B is logged\n *\n * signalB('B2');\n * // Nothing is logged as we didn't take a dependency on signalB\n * ```\n */\nexport function untracked<T>(fn: () => T): T {\n return preactUntracked(fn);\n}\n"],"names":["untracked","preactUntracked","fn"],"mappings":"AAAA,SAASA,aAAaC,eAAe,QAAQ,uBAAuB;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BC,GACD,OAAO,SAASD,UAAaE,EAAW;IACtC,OAAOD,gBAAgBC;AACzB"}
@@ -1,89 +1,89 @@
1
- import { Signal } from '../core/signal';
1
+ import type { Signal } from "../core/signal";
2
2
  export type FragmentSignal<T> = Signal<T> & {
3
- dispose: () => void;
3
+ dispose: () => void;
4
4
  };
5
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
- */
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
88
  export declare function fragmentSignal<T extends NonNullable<unknown>>(key: string, defaultValue: T, serialize?: (value: T) => string, deserialize?: (hashString: string) => T): FragmentSignal<T>;
89
89
  export declare function fragmentSignal<T>(key: string, serialize?: (value: T) => string, deserialize?: (hashString: string) => T): FragmentSignal<T | undefined | null>;
@@ -1,21 +1,39 @@
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
- }
1
+ import { signal } from "../core/signal.js";
2
+ export function fragmentSignal(key, defaultValue, serialize = JSON.stringify, deserialize = JSON.parse) {
3
+ const urlParam = getHashParams().get(key);
4
+ const defaultSignalValue = urlParam !== null ? deserialize(urlParam) : defaultValue;
5
+ const backingSignal = signal(defaultSignalValue);
6
+ const abortController = new AbortController();
7
+ window.addEventListener('hashchange', ()=>{
8
+ const urlParam = getHashParams().get(key);
9
+ const newValue = urlParam !== null ? deserialize(urlParam) : defaultValue;
10
+ backingSignal(newValue);
11
+ }, {
12
+ signal: abortController.signal
13
+ });
14
+ const proxySignal = new Proxy(backingSignal, {
15
+ apply: function(target, thisArg, argList) {
16
+ if (argList.length === 0) {
17
+ return Reflect.apply(target, thisArg, argList);
18
+ }
19
+ const newHashParams = getHashParams();
20
+ const value = argList[0];
21
+ if (value === undefined || value === null) {
22
+ newHashParams.delete(key);
23
+ } else {
24
+ newHashParams.set(key, serialize(value));
25
+ }
26
+ window.location.hash = newHashParams.toString();
27
+ return value;
28
+ }
29
+ });
30
+ return Object.assign(proxySignal, {
31
+ dispose: ()=>abortController.abort()
32
+ });
33
+ function getHashParams() {
34
+ const currentHashString = window.location.hash.substring(1);
35
+ return new URLSearchParams(currentHashString);
36
+ }
19
37
  }
20
- //#endregion
21
- export { t as fragmentSignal };
38
+
39
+ //# sourceMappingURL=fragmentSignal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/extensions/fragmentSignal.ts"],"sourcesContent":["import type { Signal } from '../core/signal';\nimport { signal } from '../core/signal';\n\nexport type FragmentSignal<T> = Signal<T> & {\n dispose: () => void;\n};\n\n/**\n * Creates a signal that synchronizes its value with the URL hash fragment.\n *\n * This function creates a reactive signal that automatically reads from and writes to\n * the URL hash fragment (the part after `#`). When the signal value changes, the URL\n * hash is updated. When the URL hash changes (e.g., via browser navigation), the\n * signal value is updated accordingly.\n *\n * @template T - The type of the value stored in the signal\n *\n * @param key - The parameter name to use in the URL hash fragment\n * @param defaultValue - The default value to use when the URL parameter is not present\n * @param serialize - Optional function to serialize the value to a string. Defaults to `JSON.stringify`\n * @param deserialize - Optional function to deserialize a string to a value. Defaults to `JSON.parse`\n *\n * @returns A signal that synchronizes with the URL hash fragment, including a `dispose()` method to clean up event listeners\n *\n * @example\n * ```ts\n * // Basic usage with a default value\n * const pageSignal = fragmentSignal('page', 1);\n *\n * // Read the current value\n * console.log(pageSignal()); // 1 (or value from URL if present)\n *\n * // Update the value (also updates URL to #page=2)\n * pageSignal(2);\n *\n * // Clear the value (removes from URL and resets to default)\n * pageSignal(null);\n *\n * // Clean up when done\n * pageSignal.dispose();\n * ```\n *\n * @example\n * ```ts\n * // Usage without default value (signal will be undefined if not in URL)\n * const optionalSignal = fragmentSignal<string>('filter');\n *\n * console.log(optionalSignal()); // undefined (if not in URL)\n * optionalSignal('active'); // Sets URL to #filter=active\n * optionalSignal(undefined); // Removes from URL\n * ```\n *\n * @example\n * ```ts\n * // Custom serialization for complex objects\n * interface Filter {\n * category: string;\n * priceRange: [number, number];\n * }\n *\n * const filterSignal = fragmentSignal<Filter>(\n * 'filter',\n * { category: 'all', priceRange: [0, 100] },\n * (filter) => btoa(JSON.stringify(filter)), // Base64 encode\n * (str) => JSON.parse(atob(str)) // Base64 decode\n * );\n * ```\n *\n * @example\n * ```ts\n * // Multiple fragment signals can coexist\n * const searchSignal = fragmentSignal('q', '');\n * const sortSignal = fragmentSignal('sort', 'name');\n *\n * searchSignal('react');\n * sortSignal('date');\n * // URL becomes: #q=react&sort=date\n * ```\n *\n * @remarks\n * - The signal automatically listens to `hashchange` events and updates accordingly\n * - Setting the signal to `null` or `undefined` resets the parameter to the default value.\n * If it is initialized without a default value, then it will remove the hash key from the URL.\n * - The URL hash uses `URLSearchParams` format for encoding multiple parameters\n * - The signal persists across page reloads as long as the URL hash remains\n * - Call `dispose()` on the returned signal to remove the `hashchange` event listener when no longer needed\n * - Two overloads are available:\n * 1. With default value: `fragmentSignal(key, defaultValue, serialize?, deserialize?)` - returns `Signal<T>`\n * 2. Without default value: `fragmentSignal(key, serialize?, deserialize?)` - returns `Signal<T | undefined>`\n */\nexport function fragmentSignal<T extends NonNullable<unknown>>(\n key: string,\n defaultValue: T,\n serialize?: (value: T) => string,\n deserialize?: (hashString: string) => T\n): FragmentSignal<T>;\nexport function fragmentSignal<T>(\n key: string,\n serialize?: (value: T) => string,\n deserialize?: (hashString: string) => T\n): FragmentSignal<T | undefined | null>;\nexport function fragmentSignal<T>(\n key: string,\n defaultValue?: T,\n serialize: (value: T) => string = JSON.stringify,\n deserialize: (hashString: string) => T = JSON.parse\n): FragmentSignal<T | undefined> {\n const urlParam = getHashParams().get(key);\n const defaultSignalValue =\n urlParam !== null ? deserialize(urlParam) : defaultValue;\n const backingSignal = signal(defaultSignalValue);\n\n const abortController = new AbortController();\n\n window.addEventListener(\n 'hashchange',\n () => {\n const urlParam = getHashParams().get(key);\n const newValue = urlParam !== null ? deserialize(urlParam) : defaultValue;\n backingSignal(newValue);\n },\n {\n signal: abortController.signal,\n }\n );\n\n const proxySignal = new Proxy(backingSignal, {\n apply: function (target, thisArg, argList): unknown {\n if (argList.length === 0) {\n return Reflect.apply(target, thisArg, argList);\n }\n const newHashParams = getHashParams();\n const value = argList[0];\n if (value === undefined || value === null) {\n newHashParams.delete(key);\n } else {\n newHashParams.set(key, serialize(value));\n }\n window.location.hash = newHashParams.toString();\n return value;\n },\n });\n\n return Object.assign(proxySignal, {\n dispose: () => abortController.abort(),\n });\n\n function getHashParams(): URLSearchParams {\n const currentHashString = window.location.hash.substring(1);\n return new URLSearchParams(currentHashString);\n }\n}\n"],"names":["signal","fragmentSignal","key","defaultValue","serialize","JSON","stringify","deserialize","parse","urlParam","getHashParams","get","defaultSignalValue","backingSignal","abortController","AbortController","window","addEventListener","newValue","proxySignal","Proxy","apply","target","thisArg","argList","length","Reflect","newHashParams","value","undefined","delete","set","location","hash","toString","Object","assign","dispose","abort","currentHashString","substring","URLSearchParams"],"mappings":"AACA,SAASA,MAAM,QAAQ,oBAAiB;AAoGxC,OAAO,SAASC,eACdC,GAAW,EACXC,YAAgB,EAChBC,YAAkCC,KAAKC,SAAS,EAChDC,cAAyCF,KAAKG,KAAK;IAEnD,MAAMC,WAAWC,gBAAgBC,GAAG,CAACT;IACrC,MAAMU,qBACJH,aAAa,OAAOF,YAAYE,YAAYN;IAC9C,MAAMU,gBAAgBb,OAAOY;IAE7B,MAAME,kBAAkB,IAAIC;IAE5BC,OAAOC,gBAAgB,CACrB,cACA;QACE,MAAMR,WAAWC,gBAAgBC,GAAG,CAACT;QACrC,MAAMgB,WAAWT,aAAa,OAAOF,YAAYE,YAAYN;QAC7DU,cAAcK;IAChB,GACA;QACElB,QAAQc,gBAAgBd,MAAM;IAChC;IAGF,MAAMmB,cAAc,IAAIC,MAAMP,eAAe;QAC3CQ,OAAO,SAAUC,MAAM,EAAEC,OAAO,EAAEC,OAAO;YACvC,IAAIA,QAAQC,MAAM,KAAK,GAAG;gBACxB,OAAOC,QAAQL,KAAK,CAACC,QAAQC,SAASC;YACxC;YACA,MAAMG,gBAAgBjB;YACtB,MAAMkB,QAAQJ,OAAO,CAAC,EAAE;YACxB,IAAII,UAAUC,aAAaD,UAAU,MAAM;gBACzCD,cAAcG,MAAM,CAAC5B;YACvB,OAAO;gBACLyB,cAAcI,GAAG,CAAC7B,KAAKE,UAAUwB;YACnC;YACAZ,OAAOgB,QAAQ,CAACC,IAAI,GAAGN,cAAcO,QAAQ;YAC7C,OAAON;QACT;IACF;IAEA,OAAOO,OAAOC,MAAM,CAACjB,aAAa;QAChCkB,SAAS,IAAMvB,gBAAgBwB,KAAK;IACtC;IAEA,SAAS5B;QACP,MAAM6B,oBAAoBvB,OAAOgB,QAAQ,CAACC,IAAI,CAACO,SAAS,CAAC;QACzD,OAAO,IAAIC,gBAAgBF;IAC7B;AACF"}
@@ -1 +1 @@
1
- export { fragmentSignal, type FragmentSignal, } from './extensions/fragmentSignal';
1
+ export { fragmentSignal, type FragmentSignal } from "./extensions/fragmentSignal";
@@ -1,2 +1,3 @@
1
- import { fragmentSignal as e } from "./extensions/fragmentSignal.js";
2
- export { e as fragmentSignal };
1
+ export { fragmentSignal } from "./extensions/fragmentSignal.js";
2
+
3
+ //# sourceMappingURL=extensions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/extensions.ts"],"sourcesContent":["export {\n fragmentSignal,\n type FragmentSignal,\n} from './extensions/fragmentSignal';\n"],"names":["fragmentSignal"],"mappings":"AAAA,SACEA,cAAc,QAET,iCAA8B"}
package/dist/index.d.ts CHANGED
@@ -1,10 +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';
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 CHANGED
@@ -1,8 +1,9 @@
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 };
1
+ export { arraySignal, isArraySignal } from "./core/array-signal.js";
2
+ export { batch } from "./core/batch.js";
3
+ export { computed } from "./core/computed.js";
4
+ export { debouncedComputed } from "./misc/debounced-computed.js";
5
+ export { effect } from "./core/effect.js";
6
+ export { signal, isSignal } from "./core/signal.js";
7
+ export { untracked } from "./core/untracked.js";
8
+
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { arraySignal, isArraySignal } from './core/array-signal';\nexport { batch } from './core/batch';\nexport { computed } from './core/computed';\nexport { debouncedComputed } from './misc/debounced-computed';\nexport { effect } from './core/effect';\nexport { signal, isSignal } from './core/signal';\nexport { untracked } from './core/untracked';\nexport type { ArraySignal, ReadonlyArraySignal } from './core/array-signal';\nexport type { DebouncedComputed } from './misc/debounced-computed';\nexport type { Signal, ReadonlySignal } from './core/signal';\n"],"names":["arraySignal","isArraySignal","batch","computed","debouncedComputed","effect","signal","isSignal","untracked"],"mappings":"AAAA,SAASA,WAAW,EAAEC,aAAa,QAAQ,yBAAsB;AACjE,SAASC,KAAK,QAAQ,kBAAe;AACrC,SAASC,QAAQ,QAAQ,qBAAkB;AAC3C,SAASC,iBAAiB,QAAQ,+BAA4B;AAC9D,SAASC,MAAM,QAAQ,mBAAgB;AACvC,SAASC,MAAM,EAAEC,QAAQ,QAAQ,mBAAgB;AACjD,SAASC,SAAS,QAAQ,sBAAmB"}