@juulsgaard/signal-tools 18.0.0 → 18.0.2
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/dist/async.mjs +96 -0
- package/dist/data-structures/signal-map.mjs +134 -0
- package/dist/data-structures/signal-queue.js +115 -0
- package/dist/data-structures/signal-queue.mjs +115 -0
- package/dist/data-structures/signal-set.mjs +143 -0
- package/dist/data-structures/signal-stack.js +90 -0
- package/dist/data-structures/signal-stack.mjs +90 -0
- package/dist/index.d.ts +564 -62
- package/dist/index.js +12 -1
- package/dist/index.mjs +16 -0
- package/dist/interop.mjs +11 -0
- package/dist/loading-signal.mjs +68 -0
- package/dist/operators.js +13 -0
- package/dist/operators.mjs +13 -0
- package/dist/request-state/async-request-state.js +48 -0
- package/dist/request-state/async-request-state.mjs +48 -0
- package/dist/request-state/constructors.js +25 -0
- package/dist/request-state/constructors.mjs +25 -0
- package/dist/request-state/empty-request-state.js +20 -0
- package/dist/request-state/empty-request-state.mjs +20 -0
- package/dist/request-state/error-request-state.js +20 -0
- package/dist/request-state/error-request-state.mjs +20 -0
- package/dist/request-state/loading-request-state.js +25 -0
- package/dist/request-state/loading-request-state.mjs +25 -0
- package/dist/request-state/request-state.js +39 -0
- package/dist/request-state/request-state.mjs +39 -0
- package/dist/request-state/request-tracker.js +64 -0
- package/dist/request-state/request-tracker.mjs +64 -0
- package/dist/request-state/static-request-state.js +32 -0
- package/dist/request-state/static-request-state.mjs +32 -0
- package/dist/request-state/writable-request-state.js +75 -0
- package/dist/request-state/writable-request-state.mjs +75 -0
- package/package.json +12 -11
package/dist/async.mjs
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { assertInInjectionContext, signal, effect, DestroyRef, inject } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
function debouncedSignal(inputSignal, delay = 500, options) {
|
|
4
|
+
if (options?.injector) assertInInjectionContext(debouncedSignal);
|
|
5
|
+
const output = signal(inputSignal());
|
|
6
|
+
effect((onDestroy) => {
|
|
7
|
+
const value = inputSignal();
|
|
8
|
+
const timeout = setTimeout(() => output.set(value), delay);
|
|
9
|
+
onDestroy(() => clearTimeout(timeout));
|
|
10
|
+
}, { allowSignalWrites: true, injector: options?.injector });
|
|
11
|
+
return output;
|
|
12
|
+
}
|
|
13
|
+
function throttledSignal(inputSignal, delay = 500, options) {
|
|
14
|
+
if (options?.injector) assertInInjectionContext(throttledSignal);
|
|
15
|
+
const output = signal(inputSignal());
|
|
16
|
+
let nextValue = inputSignal();
|
|
17
|
+
let timeout = void 0;
|
|
18
|
+
effect(() => {
|
|
19
|
+
const value = inputSignal();
|
|
20
|
+
nextValue = value;
|
|
21
|
+
if (timeout) return;
|
|
22
|
+
if (options?.emitLeading) {
|
|
23
|
+
output.set(value);
|
|
24
|
+
}
|
|
25
|
+
timeout = setTimeout(() => {
|
|
26
|
+
output.set(nextValue);
|
|
27
|
+
timeout = void 0;
|
|
28
|
+
}, delay);
|
|
29
|
+
}, { allowSignalWrites: true, injector: options?.injector });
|
|
30
|
+
const onDestroy = options?.injector?.get(DestroyRef) ?? inject(DestroyRef);
|
|
31
|
+
onDestroy.onDestroy(() => clearTimeout(timeout));
|
|
32
|
+
return output;
|
|
33
|
+
}
|
|
34
|
+
function debouncedThrottledSignal(inputSignal, throttleDelay = 1e3, debounceDelay = 300, options) {
|
|
35
|
+
if (options?.injector) assertInInjectionContext(debouncedThrottledSignal);
|
|
36
|
+
const output = signal(inputSignal());
|
|
37
|
+
let nextValue = inputSignal();
|
|
38
|
+
let longTimeout = void 0;
|
|
39
|
+
function clearThrottle() {
|
|
40
|
+
if (!longTimeout) return;
|
|
41
|
+
clearTimeout(longTimeout);
|
|
42
|
+
longTimeout = void 0;
|
|
43
|
+
}
|
|
44
|
+
effect((onDestroy2) => {
|
|
45
|
+
const value = inputSignal();
|
|
46
|
+
const shortTimeout = setTimeout(() => {
|
|
47
|
+
output.set(value);
|
|
48
|
+
clearThrottle();
|
|
49
|
+
}, debounceDelay);
|
|
50
|
+
onDestroy2(() => clearTimeout(shortTimeout));
|
|
51
|
+
nextValue = value;
|
|
52
|
+
if (longTimeout) return;
|
|
53
|
+
longTimeout = setTimeout(() => {
|
|
54
|
+
output.set(nextValue);
|
|
55
|
+
longTimeout = void 0;
|
|
56
|
+
}, throttleDelay);
|
|
57
|
+
}, { allowSignalWrites: true, injector: options?.injector });
|
|
58
|
+
const onDestroy = options?.injector?.get(DestroyRef) ?? inject(DestroyRef);
|
|
59
|
+
onDestroy.onDestroy(() => clearTimeout(longTimeout));
|
|
60
|
+
return output;
|
|
61
|
+
}
|
|
62
|
+
function searchSignal(inputSignal, throttleDelay = 1e3, debounceDelay = 300, options) {
|
|
63
|
+
if (options?.injector) assertInInjectionContext(searchSignal);
|
|
64
|
+
const query = signal(inputSignal());
|
|
65
|
+
let nextValue = inputSignal();
|
|
66
|
+
let longTimeout = void 0;
|
|
67
|
+
function clearThrottle() {
|
|
68
|
+
if (!longTimeout) return;
|
|
69
|
+
clearTimeout(longTimeout);
|
|
70
|
+
longTimeout = void 0;
|
|
71
|
+
}
|
|
72
|
+
effect((onDestroy2) => {
|
|
73
|
+
const value = inputSignal();
|
|
74
|
+
if (value === void 0 || value === "") {
|
|
75
|
+
query.set(value);
|
|
76
|
+
clearThrottle();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const shortTimeout = setTimeout(() => {
|
|
80
|
+
query.set(value);
|
|
81
|
+
clearThrottle();
|
|
82
|
+
}, debounceDelay);
|
|
83
|
+
onDestroy2(() => clearTimeout(shortTimeout));
|
|
84
|
+
nextValue = value;
|
|
85
|
+
if (longTimeout) return;
|
|
86
|
+
longTimeout = setTimeout(() => {
|
|
87
|
+
query.set(nextValue);
|
|
88
|
+
longTimeout = void 0;
|
|
89
|
+
}, throttleDelay);
|
|
90
|
+
}, { allowSignalWrites: true, injector: options?.injector });
|
|
91
|
+
const onDestroy = options?.injector?.get(DestroyRef) ?? inject(DestroyRef);
|
|
92
|
+
onDestroy.onDestroy(() => clearTimeout(longTimeout));
|
|
93
|
+
return query;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { debouncedSignal, debouncedThrottledSignal, searchSignal, throttledSignal };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { signal, computed, untracked } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
class SignalMap {
|
|
4
|
+
constructor(values) {
|
|
5
|
+
this._map = signal(new Map(values));
|
|
6
|
+
this.value = this._map.asReadonly();
|
|
7
|
+
this.size = computed(() => this.value().size);
|
|
8
|
+
this.empty = computed(() => this.size() <= 0);
|
|
9
|
+
}
|
|
10
|
+
[Symbol.iterator]() {
|
|
11
|
+
return this.value()[Symbol.iterator]();
|
|
12
|
+
}
|
|
13
|
+
getCopy() {
|
|
14
|
+
return new Map(untracked(this.value));
|
|
15
|
+
}
|
|
16
|
+
//<editor-fold desc="Actions">
|
|
17
|
+
/**
|
|
18
|
+
* Remove all keys not in the whitelist
|
|
19
|
+
* @param whitelist - The keys to keep
|
|
20
|
+
*/
|
|
21
|
+
filter(whitelist) {
|
|
22
|
+
const length = whitelist && "size" in whitelist ? whitelist.size : whitelist?.length;
|
|
23
|
+
if (!length) {
|
|
24
|
+
return this.clear();
|
|
25
|
+
}
|
|
26
|
+
const whitelistSet = whitelist instanceof Set ? whitelist : new Set(whitelist);
|
|
27
|
+
const map = this.getCopy();
|
|
28
|
+
for (let [key] of untracked(this.value)) {
|
|
29
|
+
if (whitelistSet.has(key)) continue;
|
|
30
|
+
map.delete(key);
|
|
31
|
+
}
|
|
32
|
+
if (untracked(this.size) !== map.size) {
|
|
33
|
+
this._map.set(map);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
/** Clear the collection */
|
|
39
|
+
clear() {
|
|
40
|
+
if (!untracked(this.size)) return false;
|
|
41
|
+
this._map.set(/* @__PURE__ */ new Map());
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Add a value to the collection if the key is not in use
|
|
46
|
+
* @param key - The key to use
|
|
47
|
+
* @param value - The value to add
|
|
48
|
+
* @return added - Returns true if the value was added
|
|
49
|
+
*/
|
|
50
|
+
add(key, value) {
|
|
51
|
+
if (untracked(this.value).has(key)) return false;
|
|
52
|
+
const map = this.getCopy();
|
|
53
|
+
map.set(key, value);
|
|
54
|
+
this._map.set(map);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Add a value to the collection
|
|
59
|
+
* @param key - The key to use
|
|
60
|
+
* @param value - The value to add
|
|
61
|
+
* @return added - Returns true if the value was added or changed
|
|
62
|
+
*/
|
|
63
|
+
set(key, value) {
|
|
64
|
+
const state = untracked(this.value);
|
|
65
|
+
if (state.has(key) && state.get(key) === value) return false;
|
|
66
|
+
const map = this.getCopy();
|
|
67
|
+
map.set(key, value);
|
|
68
|
+
this._map.set(map);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Remove a key from the collection
|
|
73
|
+
* @param key - The key to remove
|
|
74
|
+
* @return removed - True if an item was removed
|
|
75
|
+
*/
|
|
76
|
+
delete(key) {
|
|
77
|
+
if (!untracked(this.value).has(key)) return false;
|
|
78
|
+
const map = this.getCopy();
|
|
79
|
+
map.delete(key);
|
|
80
|
+
this._map.set(map);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Remove keys from the collection
|
|
85
|
+
* @param values - The keys to remove
|
|
86
|
+
* @return removed - True if items were removed
|
|
87
|
+
*/
|
|
88
|
+
deleteRange(values) {
|
|
89
|
+
const map = this.getCopy();
|
|
90
|
+
const size = map.size;
|
|
91
|
+
values.forEach((v) => map.delete(v));
|
|
92
|
+
if (map.size === size) return false;
|
|
93
|
+
this._map.set(map);
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Toggle a value in the map
|
|
98
|
+
* @param key - The key to toggle
|
|
99
|
+
* @param value - The value to insert if applicable
|
|
100
|
+
* @param state - A forced state (`true` = always add, `false` = always delete)
|
|
101
|
+
* @returns The applied change (`true` = item added, `false` = item removed, `undefined` = nothing changed)
|
|
102
|
+
*/
|
|
103
|
+
toggle(key, value, state) {
|
|
104
|
+
if (untracked(this.value).has(key)) {
|
|
105
|
+
if (state === true) return void 0;
|
|
106
|
+
const map2 = this.getCopy();
|
|
107
|
+
map2.delete(key);
|
|
108
|
+
this._map.set(map2);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
if (state === false) return void 0;
|
|
112
|
+
const map = this.getCopy();
|
|
113
|
+
map.set(key, value);
|
|
114
|
+
this._map.set(map);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
/** Manually modify the inner collection */
|
|
118
|
+
modify(modify) {
|
|
119
|
+
const map = this.getCopy();
|
|
120
|
+
modify(map);
|
|
121
|
+
this._map.set(map);
|
|
122
|
+
}
|
|
123
|
+
/** @inheritDoc */
|
|
124
|
+
has(key) {
|
|
125
|
+
return computed(() => this.value().has(key));
|
|
126
|
+
}
|
|
127
|
+
/** @inheritDoc */
|
|
128
|
+
get(key) {
|
|
129
|
+
return computed(() => this.value().get(key));
|
|
130
|
+
}
|
|
131
|
+
//</editor-fold>
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export { SignalMap };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { signal, computed, untracked } from '@angular/core';
|
|
2
|
+
import { pairwiseSignal } from '../operators.js';
|
|
3
|
+
|
|
4
|
+
class SignalQueue {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this._items = signal([]);
|
|
7
|
+
/** A signal emitting the items of the queue */
|
|
8
|
+
this.items = this._items.asReadonly();
|
|
9
|
+
/** A signal emitting the number of items in the queue */
|
|
10
|
+
this.length = computed(() => this.items().length);
|
|
11
|
+
/** A signal emitting true if there are no items in the queue */
|
|
12
|
+
this.empty = computed(() => this.length() <= 0);
|
|
13
|
+
this._maxSize = options?.size;
|
|
14
|
+
this.front = computed(() => this.items().at(0));
|
|
15
|
+
this.back = computed(() => this.items().at(-1));
|
|
16
|
+
const delta = pairwiseSignal(this.items, []);
|
|
17
|
+
this.frontDelta = computed(() => {
|
|
18
|
+
const [prev, next] = delta();
|
|
19
|
+
const item = next.at(0);
|
|
20
|
+
return { item, added: !item ? false : !prev.includes(item) };
|
|
21
|
+
}, { equal: (a, b) => a.item === b.item });
|
|
22
|
+
this.backDelta = computed(() => {
|
|
23
|
+
const [prev, next] = delta();
|
|
24
|
+
const item = next.at(-1);
|
|
25
|
+
return { item, added: !item ? false : !prev.includes(item) };
|
|
26
|
+
}, { equal: (a, b) => a.item === b.item });
|
|
27
|
+
}
|
|
28
|
+
/** The max size of the queue */
|
|
29
|
+
get maxSize() {
|
|
30
|
+
return this._maxSize;
|
|
31
|
+
}
|
|
32
|
+
//</editor-fold>
|
|
33
|
+
//<editor-fold desc="Actions">
|
|
34
|
+
/**
|
|
35
|
+
* Removes an item from the scheduler
|
|
36
|
+
* @param item
|
|
37
|
+
*/
|
|
38
|
+
removeItem(item) {
|
|
39
|
+
const index = untracked(this.items).indexOf(item);
|
|
40
|
+
if (index < 0) return false;
|
|
41
|
+
const arr = [...untracked(this.items)];
|
|
42
|
+
arr.splice(index, 1);
|
|
43
|
+
this._items.set(arr);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
dequeue(count) {
|
|
47
|
+
if (count !== void 0) {
|
|
48
|
+
if (count < 1) return [];
|
|
49
|
+
if (!untracked(this.length)) return [];
|
|
50
|
+
const items = untracked(this.items).slice(0, count);
|
|
51
|
+
this._items.set(untracked(this.items).slice(count));
|
|
52
|
+
return items;
|
|
53
|
+
}
|
|
54
|
+
if (!untracked(this.length)) return void 0;
|
|
55
|
+
const item = untracked(this.items).at(0);
|
|
56
|
+
this._items.set(untracked(this.items).slice(1));
|
|
57
|
+
return item;
|
|
58
|
+
}
|
|
59
|
+
removeFromBack(count) {
|
|
60
|
+
if (count !== void 0) {
|
|
61
|
+
if (count < 1) return [];
|
|
62
|
+
if (!untracked(this.length)) return [];
|
|
63
|
+
const start = count * -1;
|
|
64
|
+
const items = untracked(this.items).slice(start);
|
|
65
|
+
this._items.set(untracked(this.items).slice(0, start));
|
|
66
|
+
return items;
|
|
67
|
+
}
|
|
68
|
+
if (!untracked(this.length)) return void 0;
|
|
69
|
+
const item = untracked(this.items).at(-1);
|
|
70
|
+
this._items.set(untracked(this.items).slice(0, -1));
|
|
71
|
+
return item;
|
|
72
|
+
}
|
|
73
|
+
enqueue(...items) {
|
|
74
|
+
let list = [...untracked(this.items), ...items];
|
|
75
|
+
if (this.maxSize !== void 0 && list.length > this.maxSize) {
|
|
76
|
+
const delta = list.length - this.maxSize;
|
|
77
|
+
list = list.slice(delta);
|
|
78
|
+
}
|
|
79
|
+
this._items.set(list);
|
|
80
|
+
}
|
|
81
|
+
addToFront(...items) {
|
|
82
|
+
let list = [...items, ...untracked(this.items)];
|
|
83
|
+
if (this.maxSize !== void 0 && list.length > this.maxSize) {
|
|
84
|
+
const delta = list.length - this.maxSize;
|
|
85
|
+
list = list.slice(0, -delta);
|
|
86
|
+
}
|
|
87
|
+
this._items.set(list);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Checks if the item exists in the queue
|
|
91
|
+
* @param item
|
|
92
|
+
*/
|
|
93
|
+
contains(item) {
|
|
94
|
+
return untracked(this.items).includes(item);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Remove all elements from the set
|
|
98
|
+
*/
|
|
99
|
+
clear() {
|
|
100
|
+
this._items.set([]);
|
|
101
|
+
}
|
|
102
|
+
//</editor-fold>
|
|
103
|
+
/**
|
|
104
|
+
* Change the max size of the queue
|
|
105
|
+
* @param size - The new max size
|
|
106
|
+
*/
|
|
107
|
+
setMaxSize(size) {
|
|
108
|
+
this._maxSize = size;
|
|
109
|
+
if (size === void 0 || untracked(this.length) <= size) return;
|
|
110
|
+
const delta = untracked(this.length) - size;
|
|
111
|
+
this._items.set(untracked(this.items).slice(delta));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export { SignalQueue };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { signal, computed, untracked } from '@angular/core';
|
|
2
|
+
import { pairwiseSignal } from '../operators.mjs';
|
|
3
|
+
|
|
4
|
+
class SignalQueue {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this._items = signal([]);
|
|
7
|
+
/** A signal emitting the items of the queue */
|
|
8
|
+
this.items = this._items.asReadonly();
|
|
9
|
+
/** A signal emitting the number of items in the queue */
|
|
10
|
+
this.length = computed(() => this.items().length);
|
|
11
|
+
/** A signal emitting true if there are no items in the queue */
|
|
12
|
+
this.empty = computed(() => this.length() <= 0);
|
|
13
|
+
this._maxSize = options?.size;
|
|
14
|
+
this.front = computed(() => this.items().at(0));
|
|
15
|
+
this.back = computed(() => this.items().at(-1));
|
|
16
|
+
const delta = pairwiseSignal(this.items, []);
|
|
17
|
+
this.frontDelta = computed(() => {
|
|
18
|
+
const [prev, next] = delta();
|
|
19
|
+
const item = next.at(0);
|
|
20
|
+
return { item, added: !item ? false : !prev.includes(item) };
|
|
21
|
+
}, { equal: (a, b) => a.item === b.item });
|
|
22
|
+
this.backDelta = computed(() => {
|
|
23
|
+
const [prev, next] = delta();
|
|
24
|
+
const item = next.at(-1);
|
|
25
|
+
return { item, added: !item ? false : !prev.includes(item) };
|
|
26
|
+
}, { equal: (a, b) => a.item === b.item });
|
|
27
|
+
}
|
|
28
|
+
/** The max size of the queue */
|
|
29
|
+
get maxSize() {
|
|
30
|
+
return this._maxSize;
|
|
31
|
+
}
|
|
32
|
+
//</editor-fold>
|
|
33
|
+
//<editor-fold desc="Actions">
|
|
34
|
+
/**
|
|
35
|
+
* Removes an item from the scheduler
|
|
36
|
+
* @param item
|
|
37
|
+
*/
|
|
38
|
+
removeItem(item) {
|
|
39
|
+
const index = untracked(this.items).indexOf(item);
|
|
40
|
+
if (index < 0) return false;
|
|
41
|
+
const arr = [...untracked(this.items)];
|
|
42
|
+
arr.splice(index, 1);
|
|
43
|
+
this._items.set(arr);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
dequeue(count) {
|
|
47
|
+
if (count !== void 0) {
|
|
48
|
+
if (count < 1) return [];
|
|
49
|
+
if (!untracked(this.length)) return [];
|
|
50
|
+
const items = untracked(this.items).slice(0, count);
|
|
51
|
+
this._items.set(untracked(this.items).slice(count));
|
|
52
|
+
return items;
|
|
53
|
+
}
|
|
54
|
+
if (!untracked(this.length)) return void 0;
|
|
55
|
+
const item = untracked(this.items).at(0);
|
|
56
|
+
this._items.set(untracked(this.items).slice(1));
|
|
57
|
+
return item;
|
|
58
|
+
}
|
|
59
|
+
removeFromBack(count) {
|
|
60
|
+
if (count !== void 0) {
|
|
61
|
+
if (count < 1) return [];
|
|
62
|
+
if (!untracked(this.length)) return [];
|
|
63
|
+
const start = count * -1;
|
|
64
|
+
const items = untracked(this.items).slice(start);
|
|
65
|
+
this._items.set(untracked(this.items).slice(0, start));
|
|
66
|
+
return items;
|
|
67
|
+
}
|
|
68
|
+
if (!untracked(this.length)) return void 0;
|
|
69
|
+
const item = untracked(this.items).at(-1);
|
|
70
|
+
this._items.set(untracked(this.items).slice(0, -1));
|
|
71
|
+
return item;
|
|
72
|
+
}
|
|
73
|
+
enqueue(...items) {
|
|
74
|
+
let list = [...untracked(this.items), ...items];
|
|
75
|
+
if (this.maxSize !== void 0 && list.length > this.maxSize) {
|
|
76
|
+
const delta = list.length - this.maxSize;
|
|
77
|
+
list = list.slice(delta);
|
|
78
|
+
}
|
|
79
|
+
this._items.set(list);
|
|
80
|
+
}
|
|
81
|
+
addToFront(...items) {
|
|
82
|
+
let list = [...items, ...untracked(this.items)];
|
|
83
|
+
if (this.maxSize !== void 0 && list.length > this.maxSize) {
|
|
84
|
+
const delta = list.length - this.maxSize;
|
|
85
|
+
list = list.slice(0, -delta);
|
|
86
|
+
}
|
|
87
|
+
this._items.set(list);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Checks if the item exists in the queue
|
|
91
|
+
* @param item
|
|
92
|
+
*/
|
|
93
|
+
contains(item) {
|
|
94
|
+
return untracked(this.items).includes(item);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Remove all elements from the set
|
|
98
|
+
*/
|
|
99
|
+
clear() {
|
|
100
|
+
this._items.set([]);
|
|
101
|
+
}
|
|
102
|
+
//</editor-fold>
|
|
103
|
+
/**
|
|
104
|
+
* Change the max size of the queue
|
|
105
|
+
* @param size - The new max size
|
|
106
|
+
*/
|
|
107
|
+
setMaxSize(size) {
|
|
108
|
+
this._maxSize = size;
|
|
109
|
+
if (size === void 0 || untracked(this.length) <= size) return;
|
|
110
|
+
const delta = untracked(this.length) - size;
|
|
111
|
+
this._items.set(untracked(this.items).slice(delta));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export { SignalQueue };
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { signal, computed, untracked } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
class SignalSet {
|
|
4
|
+
constructor(values) {
|
|
5
|
+
this._set = signal(new Set(values));
|
|
6
|
+
this.value = this._set.asReadonly();
|
|
7
|
+
this.size = computed(() => this.value().size);
|
|
8
|
+
this.empty = computed(() => this.size() <= 0);
|
|
9
|
+
this.array = computed(() => Array.from(this.value()));
|
|
10
|
+
}
|
|
11
|
+
/** @inheritDoc */
|
|
12
|
+
[Symbol.iterator]() {
|
|
13
|
+
return this.value()[Symbol.iterator]();
|
|
14
|
+
}
|
|
15
|
+
getCopy() {
|
|
16
|
+
return new Set(untracked(this.value));
|
|
17
|
+
}
|
|
18
|
+
//<editor-fold desc="Actions">
|
|
19
|
+
/**
|
|
20
|
+
* Remove all keys not in the whitelist
|
|
21
|
+
* @param whitelist - The keys to keep
|
|
22
|
+
*/
|
|
23
|
+
filter(whitelist) {
|
|
24
|
+
const length = whitelist && "size" in whitelist ? whitelist.size : whitelist?.length;
|
|
25
|
+
if (!length) {
|
|
26
|
+
return this.clear();
|
|
27
|
+
}
|
|
28
|
+
const whitelistSet = whitelist instanceof Set ? whitelist : new Set(whitelist);
|
|
29
|
+
const set = this.getCopy();
|
|
30
|
+
for (let value of untracked(this.value)) {
|
|
31
|
+
if (whitelistSet.has(value)) continue;
|
|
32
|
+
set.delete(value);
|
|
33
|
+
}
|
|
34
|
+
if (untracked(this.size) !== set.size) {
|
|
35
|
+
this._set.set(set);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
/** Clear the collection */
|
|
41
|
+
clear() {
|
|
42
|
+
if (!untracked(this.size)) return false;
|
|
43
|
+
this._set.set(/* @__PURE__ */ new Set());
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Add a value to the collection is not already present
|
|
48
|
+
* @param value - The value to add
|
|
49
|
+
* @return added - Returns true if the value was added
|
|
50
|
+
*/
|
|
51
|
+
add(value) {
|
|
52
|
+
if (untracked(this.value).has(value)) return false;
|
|
53
|
+
const set = this.getCopy();
|
|
54
|
+
set.add(value);
|
|
55
|
+
this._set.set(set);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Add a list of values to the collection is not already present
|
|
60
|
+
* @param values - The values to add
|
|
61
|
+
* @return added - Returns true if any value was added
|
|
62
|
+
*/
|
|
63
|
+
addRange(values) {
|
|
64
|
+
const set = this.getCopy();
|
|
65
|
+
const size = set.size;
|
|
66
|
+
values.forEach((v) => set.add(v));
|
|
67
|
+
if (set.size === size) return false;
|
|
68
|
+
this._set.set(set);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Reset the values in the collection to the provided list
|
|
73
|
+
* @param values - The values to set
|
|
74
|
+
* @return changed - Returns true if the collection changed
|
|
75
|
+
*/
|
|
76
|
+
set(values = []) {
|
|
77
|
+
if (!values.length && !this.size) return false;
|
|
78
|
+
if (values.length === untracked(this.size)) {
|
|
79
|
+
const same = values.every((x) => this.has(x));
|
|
80
|
+
if (same) return false;
|
|
81
|
+
}
|
|
82
|
+
this._set.set(new Set(values));
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Remove a value from the collection
|
|
87
|
+
* @param value - The key to remove
|
|
88
|
+
* @return removed - True if the value existed
|
|
89
|
+
*/
|
|
90
|
+
delete(value) {
|
|
91
|
+
if (!untracked(this.value).has(value)) return false;
|
|
92
|
+
const set = this.getCopy();
|
|
93
|
+
set.delete(value);
|
|
94
|
+
this._set.set(set);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Remove values from the collection
|
|
99
|
+
* @param values - The values to remove
|
|
100
|
+
* @return removed - True if any values were removed
|
|
101
|
+
*/
|
|
102
|
+
deleteRange(values) {
|
|
103
|
+
const set = this.getCopy();
|
|
104
|
+
const size = set.size;
|
|
105
|
+
values.forEach((v) => set.delete(v));
|
|
106
|
+
if (set.size === size) return false;
|
|
107
|
+
this._set.set(set);
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Toggle a value in the set
|
|
112
|
+
* @param value - The value to toggle
|
|
113
|
+
* @param state - A forced state (`true` = always add, `false` = always delete)
|
|
114
|
+
* @returns The applied change (`true` = item added, `false` = item removed, `undefined` = nothing changed)
|
|
115
|
+
*/
|
|
116
|
+
toggle(value, state) {
|
|
117
|
+
if (untracked(this.value).has(value)) {
|
|
118
|
+
if (state === true) return void 0;
|
|
119
|
+
const set2 = this.getCopy();
|
|
120
|
+
set2.delete(value);
|
|
121
|
+
this._set.set(set2);
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
if (state === false) return void 0;
|
|
125
|
+
const set = this.getCopy();
|
|
126
|
+
set.add(value);
|
|
127
|
+
this._set.set(set);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
/** Manually modify the inner collection */
|
|
131
|
+
modify(modify) {
|
|
132
|
+
const set = this.getCopy();
|
|
133
|
+
modify(set);
|
|
134
|
+
this._set.set(set);
|
|
135
|
+
}
|
|
136
|
+
/** @inheritDoc */
|
|
137
|
+
has(value) {
|
|
138
|
+
return computed(() => this.value().has(value));
|
|
139
|
+
}
|
|
140
|
+
//</editor-fold>
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export { SignalSet };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { signal, computed, untracked } from '@angular/core';
|
|
2
|
+
import { pairwiseSignal } from '../operators.js';
|
|
3
|
+
|
|
4
|
+
class SignalStack {
|
|
5
|
+
constructor() {
|
|
6
|
+
this._items = signal([]);
|
|
7
|
+
/** A signal containing the items of the stack from bottom to top */
|
|
8
|
+
this.items = this._items.asReadonly();
|
|
9
|
+
/** A signal emitting the number of items in the stack */
|
|
10
|
+
this.length = computed(() => this.items().length);
|
|
11
|
+
/** A signal emitting true if there are no items in the stack */
|
|
12
|
+
this.empty = computed(() => this.length() <= 0);
|
|
13
|
+
this.top = computed(() => this.items().at(-1));
|
|
14
|
+
this.bottom = computed(() => this.items().at(0));
|
|
15
|
+
const delta = pairwiseSignal(this.items, []);
|
|
16
|
+
this.topDelta = computed(() => {
|
|
17
|
+
const [prev, next] = delta();
|
|
18
|
+
const item = next.at(-1);
|
|
19
|
+
return { item, added: !item ? false : !prev.includes(item) };
|
|
20
|
+
}, { equal: (a, b) => a.item === b.item });
|
|
21
|
+
this.bottomDelta = computed(() => {
|
|
22
|
+
const [prev, next] = delta();
|
|
23
|
+
const item = next.at(0);
|
|
24
|
+
return { item, added: !item ? false : !prev.includes(item) };
|
|
25
|
+
}, { equal: (a, b) => a.item === b.item });
|
|
26
|
+
}
|
|
27
|
+
//</editor-fold>
|
|
28
|
+
//<editor-fold desc="Actions">
|
|
29
|
+
/**
|
|
30
|
+
* Removes an item from the scheduler
|
|
31
|
+
* @param item
|
|
32
|
+
*/
|
|
33
|
+
removeItem(item) {
|
|
34
|
+
const index = untracked(this.items).indexOf(item);
|
|
35
|
+
if (index < 0) return false;
|
|
36
|
+
const arr = [...untracked(this.items)];
|
|
37
|
+
arr.splice(index, 1);
|
|
38
|
+
this._items.set(arr);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
removeFromBottom(count) {
|
|
42
|
+
if (count !== void 0) {
|
|
43
|
+
if (count < 1) return [];
|
|
44
|
+
if (!untracked(this.length)) return [];
|
|
45
|
+
const items = untracked(this.items).slice(0, count);
|
|
46
|
+
this._items.set(untracked(this.items).slice(count));
|
|
47
|
+
return items;
|
|
48
|
+
}
|
|
49
|
+
if (!untracked(this.length)) return void 0;
|
|
50
|
+
const item = untracked(this.items).at(0);
|
|
51
|
+
this._items.set(untracked(this.items).slice(1));
|
|
52
|
+
return item;
|
|
53
|
+
}
|
|
54
|
+
pop(count) {
|
|
55
|
+
if (count !== void 0) {
|
|
56
|
+
if (count < 1) return [];
|
|
57
|
+
if (!untracked(this.length)) return [];
|
|
58
|
+
const start = count * -1;
|
|
59
|
+
const items = untracked(this.items).slice(start);
|
|
60
|
+
this._items.set(untracked(this.items).slice(0, start));
|
|
61
|
+
return items;
|
|
62
|
+
}
|
|
63
|
+
if (!untracked(this.length)) return void 0;
|
|
64
|
+
const item = untracked(this.items).at(-1);
|
|
65
|
+
this._items.set(untracked(this.items).slice(0, -1));
|
|
66
|
+
return item;
|
|
67
|
+
}
|
|
68
|
+
push(...items) {
|
|
69
|
+
this._items.set([...untracked(this.items), ...items]);
|
|
70
|
+
}
|
|
71
|
+
addToBottom(...items) {
|
|
72
|
+
this._items.set([...items, ...untracked(this.items)]);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Checks if the item exists in the queue
|
|
76
|
+
* @param item
|
|
77
|
+
*/
|
|
78
|
+
contains(item) {
|
|
79
|
+
return untracked(this.items).includes(item);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Remove all elements from the set
|
|
83
|
+
*/
|
|
84
|
+
clear() {
|
|
85
|
+
this._items.set([]);
|
|
86
|
+
}
|
|
87
|
+
//</editor-fold>
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { SignalStack };
|