@veams/status-quo 1.5.0 → 1.6.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/.turbo/turbo-build.log +12 -0
- package/.turbo/turbo-check$colon$types.log +4 -0
- package/.turbo/turbo-docs$colon$build.log +14 -0
- package/.turbo/turbo-lint.log +8 -0
- package/.turbo/turbo-test.log +15 -0
- package/CHANGELOG.md +24 -3
- package/README.md +217 -41
- package/dist/config/status-quo-config.d.ts +13 -0
- package/dist/config/status-quo-config.js +14 -0
- package/dist/config/status-quo-config.js.map +1 -1
- package/dist/hooks/__tests__/state-provider.spec.d.ts +4 -0
- package/dist/hooks/__tests__/state-provider.spec.js +179 -0
- package/dist/hooks/__tests__/state-provider.spec.js.map +1 -0
- package/dist/hooks/__tests__/state-selector.spec.js +11 -12
- package/dist/hooks/__tests__/state-selector.spec.js.map +1 -1
- package/dist/hooks/__tests__/state-singleton.spec.js +10 -11
- package/dist/hooks/__tests__/state-singleton.spec.js.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/state-factory.js.map +1 -1
- package/dist/hooks/state-provider.d.ts +14 -0
- package/dist/hooks/state-provider.js +24 -0
- package/dist/hooks/state-provider.js.map +1 -0
- package/dist/hooks/state-subscription-selector.js +6 -2
- package/dist/hooks/state-subscription-selector.js.map +1 -1
- package/dist/hooks/state-subscription.js +1 -1
- package/dist/hooks/state-subscription.js.map +1 -1
- package/dist/index.d.ts +4 -5
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/react/hooks/__tests__/state-provider.spec.d.ts +4 -0
- package/dist/react/hooks/__tests__/state-provider.spec.js +179 -0
- package/dist/react/hooks/__tests__/state-provider.spec.js.map +1 -0
- package/dist/react/hooks/__tests__/state-selector.spec.d.ts +4 -0
- package/dist/react/hooks/__tests__/state-selector.spec.js +499 -0
- package/dist/react/hooks/__tests__/state-selector.spec.js.map +1 -0
- package/dist/react/hooks/__tests__/state-singleton.spec.d.ts +4 -0
- package/dist/react/hooks/__tests__/state-singleton.spec.js +96 -0
- package/dist/react/hooks/__tests__/state-singleton.spec.js.map +1 -0
- package/{src/hooks/index.ts → dist/react/hooks/index.d.ts} +1 -0
- package/dist/react/hooks/index.js +7 -0
- package/dist/react/hooks/index.js.map +1 -0
- package/dist/react/hooks/state-actions.d.ts +2 -0
- package/dist/react/hooks/state-actions.js +5 -0
- package/dist/react/hooks/state-actions.js.map +1 -0
- package/dist/react/hooks/state-factory.d.ts +7 -0
- package/dist/react/hooks/state-factory.js +13 -0
- package/dist/react/hooks/state-factory.js.map +1 -0
- package/dist/react/hooks/state-handler.d.ts +2 -0
- package/dist/react/hooks/state-handler.js +9 -0
- package/dist/react/hooks/state-handler.js.map +1 -0
- package/dist/react/hooks/state-provider.d.ts +14 -0
- package/dist/react/hooks/state-provider.js +24 -0
- package/dist/react/hooks/state-provider.js.map +1 -0
- package/dist/react/hooks/state-singleton.d.ts +6 -0
- package/dist/react/hooks/state-singleton.js +7 -0
- package/dist/react/hooks/state-singleton.js.map +1 -0
- package/dist/react/hooks/state-subscription-selector.d.ts +3 -0
- package/dist/react/hooks/state-subscription-selector.js +63 -0
- package/dist/react/hooks/state-subscription-selector.js.map +1 -0
- package/dist/react/hooks/state-subscription.d.ts +9 -0
- package/dist/react/hooks/state-subscription.js +53 -0
- package/dist/react/hooks/state-subscription.js.map +1 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +2 -0
- package/dist/react/index.js.map +1 -0
- package/dist/store/__tests__/observable-state-handler.spec.js +66 -11
- package/dist/store/__tests__/observable-state-handler.spec.js.map +1 -1
- package/dist/store/__tests__/signal-state-handler.spec.js.map +1 -1
- package/dist/store/base-state-handler.d.ts +4 -6
- package/dist/store/base-state-handler.js +10 -9
- package/dist/store/base-state-handler.js.map +1 -1
- package/dist/store/dev-tools.js +0 -3
- package/dist/store/dev-tools.js.map +1 -1
- package/dist/store/observable-state-handler.d.ts +4 -10
- package/dist/store/observable-state-handler.js +4 -11
- package/dist/store/observable-state-handler.js.map +1 -1
- package/dist/store/signal-state-handler.d.ts +2 -5
- package/dist/store/signal-state-handler.js +3 -2
- package/dist/store/signal-state-handler.js.map +1 -1
- package/dist/store/state-singleton.js +1 -1
- package/dist/store/state-singleton.js.map +1 -1
- package/eslint.config.mjs +75 -0
- package/package.json +18 -18
- package/src/config/status-quo-config.ts +31 -1
- package/src/index.ts +11 -15
- package/src/react/hooks/__tests__/state-provider.spec.tsx +286 -0
- package/src/{hooks → react/hooks}/__tests__/state-selector.spec.tsx +52 -44
- package/src/{hooks → react/hooks}/__tests__/state-singleton.spec.tsx +21 -20
- package/src/react/hooks/index.ts +11 -0
- package/src/{hooks → react/hooks}/state-actions.tsx +1 -1
- package/src/{hooks → react/hooks}/state-factory.tsx +2 -2
- package/src/{hooks → react/hooks}/state-handler.tsx +1 -1
- package/src/react/hooks/state-provider.tsx +56 -0
- package/src/{hooks → react/hooks}/state-singleton.tsx +1 -1
- package/src/{hooks → react/hooks}/state-subscription-selector.tsx +15 -6
- package/src/{hooks → react/hooks}/state-subscription.tsx +5 -9
- package/src/react/index.ts +1 -0
- package/src/store/__tests__/observable-state-handler.spec.ts +92 -13
- package/src/store/__tests__/signal-state-handler.spec.ts +5 -1
- package/src/store/base-state-handler.ts +17 -24
- package/src/store/dev-tools.ts +3 -3
- package/src/store/observable-state-handler.ts +12 -22
- package/src/store/signal-state-handler.ts +11 -8
- package/src/store/state-singleton.ts +1 -1
- package/tsconfig.json +2 -3
- package/.eslintrc.cjs +0 -132
- package/.github/workflows/pages.yml +0 -46
- package/.github/workflows/release.yml +0 -33
- package/.nvmrc +0 -1
- package/.prettierrc +0 -7
- package/docs/assets/index-BBmpszOW.css +0 -1
- package/docs/assets/index-Cf8El_RO.js +0 -194
- package/docs/assets/statusquo-logo-8GVRbxpc.png +0 -0
- package/docs/index.html +0 -13
- package/playground/index.html +0 -12
- package/playground/src/App.tsx +0 -699
- package/playground/src/assets/philosophy-agnostic.svg +0 -18
- package/playground/src/assets/philosophy-separation.svg +0 -13
- package/playground/src/assets/philosophy-swap.svg +0 -17
- package/playground/src/assets/statusquo-logo.png +0 -0
- package/playground/src/main.tsx +0 -19
- package/playground/src/styles.css +0 -534
- package/playground/tsconfig.json +0 -12
- package/playground/vite.config.ts +0 -18
|
@@ -2,30 +2,30 @@ import { lastValueFrom, Subject, take } from 'rxjs';
|
|
|
2
2
|
|
|
3
3
|
import { resetStatusQuoForTests, setupStatusQuo } from '../../config/status-quo-config.js';
|
|
4
4
|
import { ObservableStateHandler } from '../observable-state-handler.js';
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
import type { DevToolsOptions, DistinctOptions } from '../../config/status-quo-config.js';
|
|
6
7
|
|
|
7
8
|
type TestState = { test: string; test2: string };
|
|
8
9
|
type TestActions = { testAction: () => void };
|
|
9
10
|
type TestObservableHandlerOptions = {
|
|
10
|
-
|
|
11
|
+
devTools?: DevToolsOptions;
|
|
11
12
|
distinct?: DistinctOptions<TestState>;
|
|
12
13
|
useDistinctUntilChanged?: boolean;
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
class TestObservableStateHandler extends ObservableStateHandler<TestState, TestActions> {
|
|
16
|
-
constructor({
|
|
17
|
+
constructor({
|
|
18
|
+
devTools,
|
|
19
|
+
distinct,
|
|
20
|
+
useDistinctUntilChanged,
|
|
21
|
+
}: TestObservableHandlerOptions = {}) {
|
|
17
22
|
super({
|
|
18
23
|
initialState: {
|
|
19
24
|
test: 'testValue',
|
|
20
25
|
test2: 'testValue2',
|
|
21
26
|
},
|
|
22
27
|
options: {
|
|
23
|
-
...(
|
|
24
|
-
devTools: {
|
|
25
|
-
enabled: true,
|
|
26
|
-
namespace: 'TestObservableStateHandler',
|
|
27
|
-
},
|
|
28
|
-
}),
|
|
28
|
+
...(devTools && { devTools }),
|
|
29
29
|
...(distinct && {
|
|
30
30
|
distinct,
|
|
31
31
|
}),
|
|
@@ -48,12 +48,26 @@ class TestObservableStateHandler extends ObservableStateHandler<TestState, TestA
|
|
|
48
48
|
describe('Observable State Handler', () => {
|
|
49
49
|
let stateHandler: TestObservableStateHandler;
|
|
50
50
|
|
|
51
|
+
function mockDevToolsExtension() {
|
|
52
|
+
const devTools = {
|
|
53
|
+
init: jest.fn(),
|
|
54
|
+
send: jest.fn(),
|
|
55
|
+
subscribe: jest.fn(),
|
|
56
|
+
};
|
|
57
|
+
const connect = jest.fn(() => devTools);
|
|
58
|
+
|
|
59
|
+
window.__REDUX_DEVTOOLS_EXTENSION__ = { connect };
|
|
60
|
+
|
|
61
|
+
return { connect, devTools };
|
|
62
|
+
}
|
|
63
|
+
|
|
51
64
|
beforeEach(() => {
|
|
52
65
|
resetStatusQuoForTests();
|
|
53
66
|
stateHandler = new TestObservableStateHandler();
|
|
54
67
|
});
|
|
55
68
|
|
|
56
69
|
afterEach(() => {
|
|
70
|
+
delete window.__REDUX_DEVTOOLS_EXTENSION__;
|
|
57
71
|
resetStatusQuoForTests();
|
|
58
72
|
});
|
|
59
73
|
|
|
@@ -79,7 +93,7 @@ describe('Observable State Handler', () => {
|
|
|
79
93
|
|
|
80
94
|
stateHandler.setState(expected);
|
|
81
95
|
|
|
82
|
-
const state = await lastValueFrom(stateHandler.
|
|
96
|
+
const state = await lastValueFrom(stateHandler.getObservable().pipe(take(1)));
|
|
83
97
|
|
|
84
98
|
expect(state).toStrictEqual(expected);
|
|
85
99
|
expect(stateHandler.getState()).toStrictEqual(expected);
|
|
@@ -102,13 +116,13 @@ describe('Observable State Handler', () => {
|
|
|
102
116
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
103
117
|
});
|
|
104
118
|
|
|
105
|
-
it('should expose state item observable via
|
|
106
|
-
const observableValue = await lastValueFrom(stateHandler.
|
|
119
|
+
it('should expose state item observable via getObservableItem', async () => {
|
|
120
|
+
const observableValue = await lastValueFrom(stateHandler.getObservableItem('test').pipe(take(1)));
|
|
107
121
|
|
|
108
122
|
expect(observableValue).toBe('testValue');
|
|
109
123
|
});
|
|
110
124
|
|
|
111
|
-
it('should only call subscriber when object state has changed',
|
|
125
|
+
it('should only call subscriber when object state has changed', () => {
|
|
112
126
|
const spy = jest.fn();
|
|
113
127
|
|
|
114
128
|
const unsubscribe = stateHandler.subscribe(spy);
|
|
@@ -192,4 +206,69 @@ describe('Observable State Handler', () => {
|
|
|
192
206
|
|
|
193
207
|
expect(spy).toHaveBeenCalledTimes(2);
|
|
194
208
|
});
|
|
209
|
+
|
|
210
|
+
it('should enable devtools from global setup and fall back to the class name as namespace', () => {
|
|
211
|
+
const { connect, devTools } = mockDevToolsExtension();
|
|
212
|
+
|
|
213
|
+
setupStatusQuo({
|
|
214
|
+
devTools: {
|
|
215
|
+
enabled: true,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
new TestObservableStateHandler();
|
|
220
|
+
|
|
221
|
+
expect(connect).toHaveBeenCalledWith(
|
|
222
|
+
expect.objectContaining({
|
|
223
|
+
instanceId: 'testobservablestatehandler',
|
|
224
|
+
name: 'TestObservableStateHandler',
|
|
225
|
+
})
|
|
226
|
+
);
|
|
227
|
+
expect(devTools.init).toHaveBeenCalledWith({
|
|
228
|
+
test: 'testValue',
|
|
229
|
+
test2: 'testValue2',
|
|
230
|
+
});
|
|
231
|
+
expect(devTools.subscribe).toHaveBeenCalledTimes(1);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should prefer per-handler devtools options over global setup', () => {
|
|
235
|
+
const { connect } = mockDevToolsExtension();
|
|
236
|
+
|
|
237
|
+
setupStatusQuo({
|
|
238
|
+
devTools: {
|
|
239
|
+
enabled: true,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
new TestObservableStateHandler({
|
|
244
|
+
devTools: {
|
|
245
|
+
namespace: 'LocalHandler',
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
expect(connect).toHaveBeenCalledWith(
|
|
250
|
+
expect.objectContaining({
|
|
251
|
+
instanceId: 'localhandler',
|
|
252
|
+
name: 'LocalHandler',
|
|
253
|
+
})
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should allow per-handler devtools settings to disable a global devtools setup', () => {
|
|
258
|
+
const { connect } = mockDevToolsExtension();
|
|
259
|
+
|
|
260
|
+
setupStatusQuo({
|
|
261
|
+
devTools: {
|
|
262
|
+
enabled: true,
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
new TestObservableStateHandler({
|
|
267
|
+
devTools: {
|
|
268
|
+
enabled: false,
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
expect(connect).not.toHaveBeenCalled();
|
|
273
|
+
});
|
|
195
274
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resetStatusQuoForTests, setupStatusQuo } from '../../config/status-quo-config.js';
|
|
2
2
|
import { SignalStateHandler } from '../signal-state-handler.js';
|
|
3
3
|
import { makeStateSingleton } from '../state-singleton.js';
|
|
4
|
+
|
|
4
5
|
import type { DistinctOptions } from '../../config/status-quo-config.js';
|
|
5
6
|
|
|
6
7
|
type TestState = { test: string; test2: string };
|
|
@@ -67,7 +68,10 @@ class CounterSignalStateHandler extends SignalStateHandler<CounterState, Counter
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
class CounterSignalBridgeStateHandler extends SignalStateHandler<
|
|
71
|
+
class CounterSignalBridgeStateHandler extends SignalStateHandler<
|
|
72
|
+
CounterState,
|
|
73
|
+
{ noop: () => void }
|
|
74
|
+
> {
|
|
71
75
|
constructor(
|
|
72
76
|
counterSingleton: ReturnType<typeof makeStateSingleton<CounterState, CounterActions>>,
|
|
73
77
|
onCounterSync: (counterState: CounterState) => void
|
|
@@ -1,22 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveDevToolsOptions } from '../config/status-quo-config.js';
|
|
2
2
|
import { createSelectorCache, selectWithCache } from '../utils/selector-cache.js';
|
|
3
|
+
import { withDevTools } from './dev-tools.js';
|
|
3
4
|
|
|
4
5
|
import type { StateSubscriptionHandler } from '../types/types.js';
|
|
5
|
-
import type { DevTools, MessagePayload } from './dev-tools.js';
|
|
6
6
|
import type { EqualityFn, Selector } from '../utils/selector-cache.js';
|
|
7
|
-
|
|
8
|
-
type DevToolsOptions
|
|
9
|
-
enabled?: boolean;
|
|
10
|
-
namespace: string;
|
|
11
|
-
};
|
|
7
|
+
import type { DevTools, MessagePayload } from './dev-tools.js';
|
|
8
|
+
import type { DevToolsOptions } from '../config/status-quo-config.js';
|
|
12
9
|
|
|
13
10
|
type Subscribable<T> = {
|
|
14
11
|
subscribe: (listener: (value: T) => void) => () => void;
|
|
15
12
|
getSnapshot?: () => T;
|
|
16
13
|
};
|
|
17
14
|
|
|
18
|
-
const defaultDevToolsOptions = { enabled: false, namespace: 'Store' };
|
|
19
|
-
|
|
20
15
|
const devToolsFeatures = {
|
|
21
16
|
pause: true,
|
|
22
17
|
lock: true,
|
|
@@ -41,19 +36,18 @@ export abstract class BaseStateHandler<S, A> implements StateSubscriptionHandler
|
|
|
41
36
|
}
|
|
42
37
|
|
|
43
38
|
protected initDevTools(devToolsOptions?: DevToolsOptions) {
|
|
44
|
-
const
|
|
45
|
-
...defaultDevToolsOptions,
|
|
46
|
-
...devToolsOptions,
|
|
47
|
-
};
|
|
39
|
+
const resolvedOptions = resolveDevToolsOptions(devToolsOptions);
|
|
48
40
|
|
|
49
|
-
if (!
|
|
41
|
+
if (!resolvedOptions.enabled) {
|
|
50
42
|
this.devTools = null;
|
|
51
43
|
return;
|
|
52
44
|
}
|
|
53
45
|
|
|
46
|
+
const namespace = devToolsOptions?.namespace ?? this.getDevToolsNamespace();
|
|
47
|
+
|
|
54
48
|
this.devTools = withDevTools(this.initialState, {
|
|
55
|
-
name:
|
|
56
|
-
instanceId:
|
|
49
|
+
name: namespace,
|
|
50
|
+
instanceId: namespace.toLowerCase().replaceAll(' ', '-'),
|
|
57
51
|
actionCreators: this.getActions(),
|
|
58
52
|
features: devToolsFeatures,
|
|
59
53
|
});
|
|
@@ -86,25 +80,24 @@ export abstract class BaseStateHandler<S, A> implements StateSubscriptionHandler
|
|
|
86
80
|
protected abstract getStateValue(): S;
|
|
87
81
|
protected abstract setStateValue(nextState: S): void;
|
|
88
82
|
|
|
89
|
-
protected
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
isEqual?: EqualityFn<T>
|
|
94
|
-
): void;
|
|
83
|
+
protected getDevToolsNamespace() {
|
|
84
|
+
return this.constructor.name || 'Store';
|
|
85
|
+
}
|
|
86
|
+
|
|
95
87
|
protected bindSubscribable<T, Sel>(
|
|
96
88
|
service: Subscribable<T>,
|
|
97
89
|
onChange: (value: Sel) => void,
|
|
98
90
|
selector: Selector<T, Sel>,
|
|
99
91
|
isEqual?: EqualityFn<Sel>
|
|
100
92
|
): void;
|
|
93
|
+
protected bindSubscribable<T>(service: Subscribable<T>, onChange: (value: T) => void): void;
|
|
101
94
|
protected bindSubscribable<T, Sel = T>(
|
|
102
95
|
service: Subscribable<T>,
|
|
103
96
|
onChange: (value: Sel) => void,
|
|
104
97
|
selector?: Selector<T, Sel>,
|
|
105
98
|
isEqual: EqualityFn<Sel> = Object.is
|
|
106
99
|
) {
|
|
107
|
-
const selectorFn = (selector ?? ((value: T) => value as unknown as Sel))
|
|
100
|
+
const selectorFn = (selector ?? ((value: T) => value as unknown as Sel));
|
|
108
101
|
const selectorCache = createSelectorCache<Sel>();
|
|
109
102
|
let receivedSyncValue = false;
|
|
110
103
|
|
|
@@ -152,7 +145,7 @@ export abstract class BaseStateHandler<S, A> implements StateSubscriptionHandler
|
|
|
152
145
|
|
|
153
146
|
case 'JUMP_TO_STATE':
|
|
154
147
|
case 'JUMP_TO_ACTION':
|
|
155
|
-
this.setStateValue(JSON.parse(message.state));
|
|
148
|
+
this.setStateValue(JSON.parse(message.state) as S);
|
|
156
149
|
break;
|
|
157
150
|
|
|
158
151
|
default:
|
package/src/store/dev-tools.ts
CHANGED
|
@@ -28,17 +28,17 @@ export function withDevTools<S>(initialState: S, options = {}): DevTools | null
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
if (!window.__REDUX_DEVTOOLS_EXTENSION__) {
|
|
33
33
|
console.error('Status Quo :: Devtools Extension is not installed!');
|
|
34
34
|
return null;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
const devTools = window.__REDUX_DEVTOOLS_EXTENSION__.connect(options);
|
|
39
39
|
|
|
40
40
|
devTools.init(initialState);
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
return devTools;
|
|
44
44
|
}
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import { BehaviorSubject, distinctUntilChanged, distinctUntilKeyChanged, map } from 'rxjs';
|
|
2
2
|
|
|
3
|
-
import { BaseStateHandler } from './base-state-handler.js';
|
|
4
3
|
import { resolveDistinctOptions } from '../config/status-quo-config.js';
|
|
4
|
+
import { BaseStateHandler } from './base-state-handler.js';
|
|
5
5
|
|
|
6
|
+
import type { DevToolsOptions, DistinctOptions } from '../config/status-quo-config.js';
|
|
6
7
|
import type { Observable } from 'rxjs';
|
|
7
|
-
import type { DistinctOptions } from '../config/status-quo-config.js';
|
|
8
8
|
|
|
9
9
|
type ObservableStateHandlerProps<S> = {
|
|
10
10
|
initialState: S;
|
|
11
11
|
options?: {
|
|
12
|
-
devTools?:
|
|
13
|
-
enabled?: boolean;
|
|
14
|
-
namespace: string;
|
|
15
|
-
};
|
|
12
|
+
devTools?: DevToolsOptions;
|
|
16
13
|
distinct?: DistinctOptions<S>;
|
|
17
14
|
useDistinctUntilChanged?: boolean;
|
|
18
15
|
};
|
|
@@ -27,7 +24,10 @@ export abstract class ObservableStateHandler<S, A> extends BaseStateHandler<S, A
|
|
|
27
24
|
protected constructor({ initialState, options }: ObservableStateHandlerProps<S>) {
|
|
28
25
|
super(initialState);
|
|
29
26
|
this.state$ = new BehaviorSubject<S>(initialState);
|
|
30
|
-
this.distinctOptions = resolveDistinctOptions(
|
|
27
|
+
this.distinctOptions = resolveDistinctOptions(
|
|
28
|
+
options?.distinct,
|
|
29
|
+
options?.useDistinctUntilChanged
|
|
30
|
+
);
|
|
31
31
|
this.initDevTools(options?.devTools);
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -39,16 +39,15 @@ export abstract class ObservableStateHandler<S, A> extends BaseStateHandler<S, A
|
|
|
39
39
|
this.state$.next(nextState);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
getObservableItem<K extends keyof S>(key: K): Observable<S[K]> {
|
|
43
43
|
return this.state$.pipe(
|
|
44
44
|
distinctUntilKeyChanged(key),
|
|
45
45
|
map((state) => state[key])
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
const useDistinctUntilChanged =
|
|
51
|
-
options.useDistinctUntilChanged ?? this.distinctOptions.enabled;
|
|
49
|
+
getObservable(options: StateObservableOptions = {}): Observable<S> {
|
|
50
|
+
const useDistinctUntilChanged = options.useDistinctUntilChanged ?? this.distinctOptions.enabled;
|
|
52
51
|
|
|
53
52
|
if (!useDistinctUntilChanged) {
|
|
54
53
|
return this.state$;
|
|
@@ -58,22 +57,13 @@ export abstract class ObservableStateHandler<S, A> extends BaseStateHandler<S, A
|
|
|
58
57
|
distinctUntilChanged((previous, next) => {
|
|
59
58
|
return this.distinctOptions.comparator(previous, next);
|
|
60
59
|
})
|
|
61
|
-
)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
getObservable(key: keyof S) {
|
|
65
|
-
return this.getStateItemAsObservable(key);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** @deprecated Use getObservable instead. */
|
|
69
|
-
getObservableItem(key: keyof S) {
|
|
70
|
-
return this.getObservable(key);
|
|
60
|
+
);
|
|
71
61
|
}
|
|
72
62
|
|
|
73
63
|
subscribe(listener: () => void): () => void;
|
|
74
64
|
subscribe(listener: (value: S) => void): () => void;
|
|
75
65
|
subscribe(listener: (value: S) => void) {
|
|
76
|
-
const subscription = this.
|
|
66
|
+
const subscription = this.getObservable().subscribe((nextState) => {
|
|
77
67
|
listener(nextState);
|
|
78
68
|
});
|
|
79
69
|
return () => subscription.unsubscribe();
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import { signal } from '@preact/signals-core';
|
|
2
2
|
|
|
3
|
-
import { BaseStateHandler } from './base-state-handler.js';
|
|
4
3
|
import { resolveDistinctOptions } from '../config/status-quo-config.js';
|
|
4
|
+
import { BaseStateHandler } from './base-state-handler.js';
|
|
5
5
|
|
|
6
|
+
import type { DevToolsOptions, DistinctOptions } from '../config/status-quo-config.js';
|
|
6
7
|
import type { Signal } from '@preact/signals-core';
|
|
7
|
-
import type { DistinctOptions } from '../config/status-quo-config.js';
|
|
8
8
|
|
|
9
9
|
type SignalStateHandlerProps<S> = {
|
|
10
10
|
initialState: S;
|
|
11
11
|
options?: {
|
|
12
|
-
devTools?:
|
|
13
|
-
enabled?: boolean;
|
|
14
|
-
namespace: string;
|
|
15
|
-
};
|
|
12
|
+
devTools?: DevToolsOptions;
|
|
16
13
|
distinct?: DistinctOptions<S>;
|
|
17
14
|
useDistinctUntilChanged?: boolean;
|
|
18
15
|
};
|
|
@@ -26,7 +23,10 @@ export abstract class SignalStateHandler<S, A> extends BaseStateHandler<S, A> {
|
|
|
26
23
|
super(initialState);
|
|
27
24
|
|
|
28
25
|
this.state = signal<S>(initialState);
|
|
29
|
-
this.distinctOptions = resolveDistinctOptions(
|
|
26
|
+
this.distinctOptions = resolveDistinctOptions(
|
|
27
|
+
options?.distinct,
|
|
28
|
+
options?.useDistinctUntilChanged
|
|
29
|
+
);
|
|
30
30
|
this.initDevTools(options?.devTools);
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -48,7 +48,10 @@ export abstract class SignalStateHandler<S, A> extends BaseStateHandler<S, A> {
|
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
if (
|
|
51
|
+
if (
|
|
52
|
+
this.distinctOptions.enabled &&
|
|
53
|
+
this.distinctOptions.comparator(previousSnapshot, nextState)
|
|
54
|
+
) {
|
|
52
55
|
previousSnapshot = nextState;
|
|
53
56
|
return;
|
|
54
57
|
}
|
|
@@ -10,7 +10,7 @@ export interface StateSingletonOptions {
|
|
|
10
10
|
|
|
11
11
|
export function makeStateSingleton<S, A>(
|
|
12
12
|
stateHandlerFactory: () => StateSubscriptionHandler<S, A>,
|
|
13
|
-
{ destroyOnNoConsumers =
|
|
13
|
+
{ destroyOnNoConsumers = false }: StateSingletonOptions = {}
|
|
14
14
|
): StateSingleton<S, A> {
|
|
15
15
|
let instance: StateSubscriptionHandler<S, A> | null = null;
|
|
16
16
|
const singleton: StateSingleton<S, A> & {
|
package/tsconfig.json
CHANGED
|
@@ -24,9 +24,8 @@
|
|
|
24
24
|
"declarationDir": "dist",
|
|
25
25
|
"outDir": "dist",
|
|
26
26
|
"typeRoots": [
|
|
27
|
-
"
|
|
28
|
-
"node_modules/@
|
|
29
|
-
"node_modules/@redux-devtools/extension"
|
|
27
|
+
"../../node_modules/@types",
|
|
28
|
+
"../../node_modules/@redux-devtools/extension"
|
|
30
29
|
],
|
|
31
30
|
"jsx": "react"
|
|
32
31
|
},
|
package/.eslintrc.cjs
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
root: true,
|
|
3
|
-
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
|
|
4
|
-
plugins: ['react', '@typescript-eslint', 'simple-import-sort'],
|
|
5
|
-
ignorePatterns: [
|
|
6
|
-
'.eslintrc.js',
|
|
7
|
-
'*.config.js',
|
|
8
|
-
'setupTests.js',
|
|
9
|
-
'setupTests.ts',
|
|
10
|
-
'env.js',
|
|
11
|
-
'env.local.js',
|
|
12
|
-
],
|
|
13
|
-
extends: [
|
|
14
|
-
'plugin:@typescript-eslint/recommended', // Uses the recommended rules from @typescript-eslint/eslint-plugin
|
|
15
|
-
'airbnb-base',
|
|
16
|
-
'airbnb-typescript/base',
|
|
17
|
-
'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
|
|
18
|
-
'plugin:react/recommended',
|
|
19
|
-
'airbnb',
|
|
20
|
-
'airbnb/hooks',
|
|
21
|
-
'airbnb-typescript',
|
|
22
|
-
// Enables eslint-plugin-prettier and displays prettier errors as ESLint errors.
|
|
23
|
-
// Make sure this is always the last configuration in the extends array.
|
|
24
|
-
'plugin:prettier/recommended',
|
|
25
|
-
],
|
|
26
|
-
parserOptions: {
|
|
27
|
-
project: './tsconfig.json',
|
|
28
|
-
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
|
|
29
|
-
sourceType: 'module', // Allows for the use of imports
|
|
30
|
-
ecmaFeatures: {
|
|
31
|
-
jsx: true, // Allows for the parsing of JSX
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
settings: {
|
|
35
|
-
react: {
|
|
36
|
-
version: 'detect', // React version. "detect" automatically picks the version you have installed.
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
|
|
40
|
-
rules: {
|
|
41
|
-
'max-classes-per-file': ['error', 2],
|
|
42
|
-
'no-console': 'off',
|
|
43
|
-
'@typescript-eslint/consistent-type-imports': 'error',
|
|
44
|
-
'no-param-reassign': ['error', {props: false}], // for reducer and simple reference changes
|
|
45
|
-
'import/order': 'off', // Is handled by simple-import-sort
|
|
46
|
-
'import/prefer-default-export': 'off', // This is not really useful, because named exports are easier to import (IDE)
|
|
47
|
-
'import/no-default-export': 'error', // Prefer named exports over default exports since they are easier to find and refactor
|
|
48
|
-
'import/extensions': [
|
|
49
|
-
'error',
|
|
50
|
-
'always',
|
|
51
|
-
{
|
|
52
|
-
ignorePackages: true,
|
|
53
|
-
js: 'always',
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
'simple-import-sort/exports': 'error',
|
|
57
|
-
'simple-import-sort/imports': [
|
|
58
|
-
'error',
|
|
59
|
-
{
|
|
60
|
-
/**
|
|
61
|
-
* The default grouping, but with type imports last as a separate group.
|
|
62
|
-
* From https://github.com/lydell/eslint-plugin-simple-import-sort/blob/37f9448cdfed85dacf27e34c515653ff96f0377a/examples/.eslintrc.js.
|
|
63
|
-
*/
|
|
64
|
-
groups: [['^\\u0000'], ['^@?\\w'], ['^'], ['^\\.'], ['^.+\\u0000$']],
|
|
65
|
-
},
|
|
66
|
-
],
|
|
67
|
-
'@typescript-eslint/no-use-before-define': ['error', {functions: false}], // function declarations are always
|
|
68
|
-
// hoisted so it's safe
|
|
69
|
-
'@typescript-eslint/lines-between-class-members': [
|
|
70
|
-
'error',
|
|
71
|
-
'always',
|
|
72
|
-
{exceptAfterSingleLine: true},
|
|
73
|
-
], // Avoid blowing up classes
|
|
74
|
-
|
|
75
|
-
// Forbid the use of extraneous packages
|
|
76
|
-
// https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-extraneous-dependencies.md
|
|
77
|
-
// paths are treated both as absolute paths, and relative to process.cwd()
|
|
78
|
-
'import/no-extraneous-dependencies': [
|
|
79
|
-
'error',
|
|
80
|
-
{
|
|
81
|
-
devDependencies: [
|
|
82
|
-
'**/setupTests.{js,ts}', // test setup files
|
|
83
|
-
'test/**', // tape, common npm pattern
|
|
84
|
-
'tests/**', // also common npm pattern
|
|
85
|
-
'spec/**', // mocha, rspec-like pattern
|
|
86
|
-
'**/__tests__/**', // jest pattern
|
|
87
|
-
'**/__mocks__/**', // jest pattern
|
|
88
|
-
'test.{js,jsx}', // repos with a single test file
|
|
89
|
-
'test-*.{js,jsx}', // repos with multiple top-level test files
|
|
90
|
-
'**/*{.,_}{test,spec}.{js,jsx}', // tests where the extension or filename suffix denotes that it is a test
|
|
91
|
-
'**/jest.config.js', // jest config
|
|
92
|
-
'**/jest.setup.js', // jest setup
|
|
93
|
-
'**/vue.config.js', // vue-cli config
|
|
94
|
-
'**/webpack.config.js', // webpack config
|
|
95
|
-
'**/webpack.config.*.js', // webpack config
|
|
96
|
-
'**/rollup.config.js', // rollup config
|
|
97
|
-
'**/rollup.config.*.js', // rollup config
|
|
98
|
-
'**/gulpfile.js', // gulp config
|
|
99
|
-
'**/gulpfile.*.js', // gulp config
|
|
100
|
-
'**/Gruntfile{,.js}', // grunt config
|
|
101
|
-
'**/protractor.conf.js', // protractor config
|
|
102
|
-
'**/protractor.conf.*.js', // protractor config
|
|
103
|
-
'**/karma.conf.js', // karma config
|
|
104
|
-
'**/.eslintrc.js', // eslint config
|
|
105
|
-
],
|
|
106
|
-
optionalDependencies: false,
|
|
107
|
-
},
|
|
108
|
-
],
|
|
109
|
-
'react/prop-types': 'off', // Since we do not use prop-types
|
|
110
|
-
'react/require-default-props': 'off', // Since we do not use prop-types
|
|
111
|
-
// Many of our loops are server side rendered, so we can rely on the index in general
|
|
112
|
-
'react/no-array-index-key': 0,
|
|
113
|
-
// To support hydration of components, a string is necessary so that the minification of bundles
|
|
114
|
-
// do not affect our markup generation on the server.
|
|
115
|
-
'react/display-name': [2, {ignoreTranspilerName: true}],
|
|
116
|
-
// aria roles ignored (0) instead of warning (1) / errors (2).
|
|
117
|
-
'jsx-a11y/role-supports-aria-props': 0,
|
|
118
|
-
'react/function-component-definition': [2, {namedComponents: 'arrow-function'}],
|
|
119
|
-
// Conditional spreads are easier to do so we can deactivate this rule
|
|
120
|
-
'react/jsx-props-no-spreading': 0,
|
|
121
|
-
// Enforce the definition of Fragment instead of shorthand syntax.
|
|
122
|
-
// The thing is, that keys can only be applied to the long version. So we should stick to one version.
|
|
123
|
-
'react/jsx-fragments': [2, 'element'],
|
|
124
|
-
// We need to use setDangerouslyInnerHtml for article and server side rendered markup prepared by external helpers.
|
|
125
|
-
// So it makes no sense to have this rule in place.
|
|
126
|
-
'react/no-danger': 0,
|
|
127
|
-
// strict null-checking is not necessary.
|
|
128
|
-
// The syntax itself should be avoided for sure but in some cases where we know we get the data,
|
|
129
|
-
// we can use this functionality
|
|
130
|
-
'@typescript-eslint/no-non-null-assertion': 0,
|
|
131
|
-
},
|
|
132
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
name: Deploy Docs
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [master]
|
|
6
|
-
workflow_dispatch:
|
|
7
|
-
|
|
8
|
-
permissions:
|
|
9
|
-
contents: read
|
|
10
|
-
pages: write
|
|
11
|
-
id-token: write
|
|
12
|
-
|
|
13
|
-
concurrency:
|
|
14
|
-
group: pages
|
|
15
|
-
cancel-in-progress: true
|
|
16
|
-
|
|
17
|
-
jobs:
|
|
18
|
-
build:
|
|
19
|
-
runs-on: ubuntu-latest
|
|
20
|
-
steps:
|
|
21
|
-
- name: Checkout
|
|
22
|
-
uses: actions/checkout@v4
|
|
23
|
-
- name: Setup Node
|
|
24
|
-
uses: actions/setup-node@v4
|
|
25
|
-
with:
|
|
26
|
-
node-version: 20
|
|
27
|
-
cache: npm
|
|
28
|
-
- name: Install dependencies
|
|
29
|
-
run: npm ci
|
|
30
|
-
- name: Build docs
|
|
31
|
-
run: npm run docs:build
|
|
32
|
-
- name: Upload artifact
|
|
33
|
-
uses: actions/upload-pages-artifact@v3
|
|
34
|
-
with:
|
|
35
|
-
path: ./docs
|
|
36
|
-
|
|
37
|
-
deploy:
|
|
38
|
-
runs-on: ubuntu-latest
|
|
39
|
-
needs: build
|
|
40
|
-
environment:
|
|
41
|
-
name: github-pages
|
|
42
|
-
url: ${{ steps.deployment.outputs.page_url }}
|
|
43
|
-
steps:
|
|
44
|
-
- name: Deploy
|
|
45
|
-
id: deployment
|
|
46
|
-
uses: actions/deploy-pages@v4
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
name: Release
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
tags:
|
|
6
|
-
- 'v*'
|
|
7
|
-
|
|
8
|
-
permissions:
|
|
9
|
-
contents: write
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
release:
|
|
13
|
-
runs-on: ubuntu-latest
|
|
14
|
-
steps:
|
|
15
|
-
- name: Checkout
|
|
16
|
-
uses: actions/checkout@v4
|
|
17
|
-
- name: Setup Node
|
|
18
|
-
uses: actions/setup-node@v4
|
|
19
|
-
with:
|
|
20
|
-
node-version: 20
|
|
21
|
-
cache: npm
|
|
22
|
-
- name: Install dependencies
|
|
23
|
-
run: npm ci
|
|
24
|
-
- name: Build dist
|
|
25
|
-
run: npm run build
|
|
26
|
-
- name: Package dist
|
|
27
|
-
run: |
|
|
28
|
-
zip -r "dist-${{ github.ref_name }}.zip" dist
|
|
29
|
-
- name: Create GitHub release
|
|
30
|
-
uses: softprops/action-gh-release@v2
|
|
31
|
-
with:
|
|
32
|
-
files: dist-${{ github.ref_name }}.zip
|
|
33
|
-
generate_release_notes: true
|
package/.nvmrc
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
v22
|