@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
package/playground/src/App.tsx
DELETED
|
@@ -1,699 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
makeStateSingleton,
|
|
5
|
-
ObservableStateHandler,
|
|
6
|
-
SignalStateHandler,
|
|
7
|
-
useStateFactory,
|
|
8
|
-
useStateSingleton,
|
|
9
|
-
} from '@veams/status-quo';
|
|
10
|
-
|
|
11
|
-
import Prism from 'prismjs';
|
|
12
|
-
import 'prismjs/components/prism-typescript';
|
|
13
|
-
import 'prismjs/components/prism-jsx';
|
|
14
|
-
import 'prismjs/components/prism-bash';
|
|
15
|
-
|
|
16
|
-
import philosophySwap from './assets/philosophy-swap.svg';
|
|
17
|
-
import philosophySeparation from './assets/philosophy-separation.svg';
|
|
18
|
-
import philosophyAgnostic from './assets/philosophy-agnostic.svg';
|
|
19
|
-
import statusQuoLogo from './assets/statusquo-logo.png';
|
|
20
|
-
|
|
21
|
-
type CounterState = {
|
|
22
|
-
count: number;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
type CounterActions = {
|
|
26
|
-
increase: () => void;
|
|
27
|
-
decrease: () => void;
|
|
28
|
-
reset: () => void;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
class ObservableCounterStateHandler extends ObservableStateHandler<CounterState, CounterActions> {
|
|
32
|
-
constructor(startCount = 0) {
|
|
33
|
-
super({
|
|
34
|
-
initialState: { count: startCount },
|
|
35
|
-
options: { devTools: { enabled: true, namespace: 'Counter as Observable' } },
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
getActions(): CounterActions {
|
|
40
|
-
return {
|
|
41
|
-
increase: () => {
|
|
42
|
-
this.setState({ count: this.getState().count + 1 }, 'increase');
|
|
43
|
-
},
|
|
44
|
-
decrease: () => {
|
|
45
|
-
const current = this.getState().count;
|
|
46
|
-
this.setState({ count: Math.max(0, current - 1) }, 'decrease');
|
|
47
|
-
},
|
|
48
|
-
reset: () => {
|
|
49
|
-
this.setState(this.getInitialState(), 'reset');
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
class SignalCounterStateHandler extends SignalStateHandler<CounterState, CounterActions> {
|
|
56
|
-
constructor(startCount = 0) {
|
|
57
|
-
super({ initialState: { count: startCount },
|
|
58
|
-
options: { devTools: { enabled: true, namespace: 'Counter as Signal' } },
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
getActions(): CounterActions {
|
|
63
|
-
return {
|
|
64
|
-
increase: () => {
|
|
65
|
-
this.setState({ count: this.getState().count + 1 }, 'increase');
|
|
66
|
-
},
|
|
67
|
-
decrease: () => {
|
|
68
|
-
const current = this.getState().count;
|
|
69
|
-
this.setState({ count: Math.max(0, current - 1) }, 'decrease');
|
|
70
|
-
},
|
|
71
|
-
reset: () => {
|
|
72
|
-
this.setState(this.getInitialState(), 'reset');
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const SingletonCounterStore = makeStateSingleton(() => new ObservableCounterStateHandler(5));
|
|
79
|
-
|
|
80
|
-
function ObservableCounterFactory(start = 0) {
|
|
81
|
-
return new ObservableCounterStateHandler(start);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function SignalCounterFactory(start = 0) {
|
|
85
|
-
return new SignalCounterStateHandler(start);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
type CounterCardProps = {
|
|
89
|
-
title: string;
|
|
90
|
-
subtitle: string;
|
|
91
|
-
state: CounterState;
|
|
92
|
-
actions: CounterActions;
|
|
93
|
-
snippet: string;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
function CounterCard({ title, subtitle, state, actions, snippet }: CounterCardProps) {
|
|
97
|
-
return (
|
|
98
|
-
<section className="card">
|
|
99
|
-
<div className="card-header">
|
|
100
|
-
<div>
|
|
101
|
-
<p className="eyebrow">{subtitle}</p>
|
|
102
|
-
<h2>{title}</h2>
|
|
103
|
-
</div>
|
|
104
|
-
<div className="count-chip">{state.count}</div>
|
|
105
|
-
</div>
|
|
106
|
-
<pre className="code-block">
|
|
107
|
-
<code className="language-ts">{snippet}</code>
|
|
108
|
-
</pre>
|
|
109
|
-
<div className="count-display">
|
|
110
|
-
<span className="count-label">Count</span>
|
|
111
|
-
<span className="count-value">{state.count}</span>
|
|
112
|
-
</div>
|
|
113
|
-
<div className="actions">
|
|
114
|
-
<button type="button" className="btn" onClick={actions.decrease}>
|
|
115
|
-
Decrease
|
|
116
|
-
</button>
|
|
117
|
-
<button type="button" className="btn primary" onClick={actions.increase}>
|
|
118
|
-
Increase
|
|
119
|
-
</button>
|
|
120
|
-
<button type="button" className="btn ghost" onClick={actions.reset}>
|
|
121
|
-
Reset
|
|
122
|
-
</button>
|
|
123
|
-
</div>
|
|
124
|
-
</section>
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function SingletonControls() {
|
|
129
|
-
const [state, actions] = useStateSingleton(SingletonCounterStore);
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
<div className="singleton-card">
|
|
133
|
-
<p className="eyebrow">Controller</p>
|
|
134
|
-
<h3>Shared Counter</h3>
|
|
135
|
-
<p className="muted">Interact with the singleton store.</p>
|
|
136
|
-
<div className="singleton-count">{state.count}</div>
|
|
137
|
-
<div className="actions">
|
|
138
|
-
<button type="button" className="btn" onClick={actions.decrease}>
|
|
139
|
-
Decrease
|
|
140
|
-
</button>
|
|
141
|
-
<button type="button" className="btn primary" onClick={actions.increase}>
|
|
142
|
-
Increase
|
|
143
|
-
</button>
|
|
144
|
-
<button type="button" className="btn ghost" onClick={actions.reset}>
|
|
145
|
-
Reset
|
|
146
|
-
</button>
|
|
147
|
-
</div>
|
|
148
|
-
</div>
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function SingletonDisplay() {
|
|
153
|
-
const [state] = useStateSingleton(SingletonCounterStore);
|
|
154
|
-
|
|
155
|
-
return (
|
|
156
|
-
<div className="singleton-card">
|
|
157
|
-
<p className="eyebrow">Viewer</p>
|
|
158
|
-
<h3>Another Consumer</h3>
|
|
159
|
-
<p className="muted">Updates whenever the singleton changes.</p>
|
|
160
|
-
<div className="singleton-count highlight">{state.count}</div>
|
|
161
|
-
<div className="singleton-hint">Same store instance, different component.</div>
|
|
162
|
-
</div>
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function App() {
|
|
167
|
-
const [isNavOpen, setIsNavOpen] = useState(false);
|
|
168
|
-
const [observableState, observableActions] = useStateFactory(ObservableCounterFactory, [0]);
|
|
169
|
-
const [signalState, signalActions] = useStateFactory(SignalCounterFactory, [0]);
|
|
170
|
-
|
|
171
|
-
const installSnippet = `npm install @veams/status-quo rxjs @preact/signals-core`;
|
|
172
|
-
|
|
173
|
-
const quickstartSnippet = `import { ObservableStateHandler, useStateFactory } from '@veams/status-quo';
|
|
174
|
-
|
|
175
|
-
class CounterStore extends ObservableStateHandler<CounterState, CounterActions> {
|
|
176
|
-
constructor() {
|
|
177
|
-
super({ initialState: { count: 0 } });
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
getActions() {
|
|
181
|
-
return {
|
|
182
|
-
increase: () => this.setState({ count: this.getState().count + 1 })
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const [state, actions] = useStateFactory(() => new CounterStore(), []);`;
|
|
188
|
-
|
|
189
|
-
const observableSnippet = `class CounterStore extends ObservableStateHandler<CounterState, CounterActions> {
|
|
190
|
-
constructor() {
|
|
191
|
-
super({ initialState: { count: 0 } });
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
getActions() {
|
|
195
|
-
return {
|
|
196
|
-
increase: () => this.setState({ count: this.getState().count + 1 }),
|
|
197
|
-
decrease: () => this.setState({ count: this.getState().count - 1 })
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
}`;
|
|
201
|
-
|
|
202
|
-
const signalSnippet = `class CounterStore extends SignalStateHandler<CounterState, CounterActions> {
|
|
203
|
-
constructor() {
|
|
204
|
-
super({ initialState: { count: 0 } });
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
getActions() {
|
|
208
|
-
return {
|
|
209
|
-
increase: () => this.setState({ count: this.getState().count + 1 }),
|
|
210
|
-
decrease: () => this.setState({ count: this.getState().count - 1 })
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
}`;
|
|
214
|
-
|
|
215
|
-
const devToolsSnippet = `class CounterStore extends ObservableStateHandler<CounterState, CounterActions> {
|
|
216
|
-
constructor() {
|
|
217
|
-
super({
|
|
218
|
-
initialState: { count: 0 },
|
|
219
|
-
options: { devTools: { enabled: true, namespace: 'Counter' } }
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
}`;
|
|
223
|
-
|
|
224
|
-
const cleanupSnippet = `const unsubscribe = store.subscribe(() => {
|
|
225
|
-
console.log(store.getSnapshot());
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
unsubscribe();
|
|
229
|
-
store.destroy();`;
|
|
230
|
-
|
|
231
|
-
const singletonOptionsSnippet = `import { makeStateSingleton, useStateSingleton } from '@veams/status-quo';
|
|
232
|
-
|
|
233
|
-
const SessionSingleton = makeStateSingleton(() => new SessionStore(), {
|
|
234
|
-
// true (default): destroy when no consumers are mounted
|
|
235
|
-
// false: keep singleton instance alive across unmounts
|
|
236
|
-
destroyOnNoConsumers: false
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
const [state, actions] = useStateSingleton(SessionSingleton);`;
|
|
240
|
-
|
|
241
|
-
const apiUseStateHandlerSnippet = `const handler = useStateHandler(createUserStore, []);`;
|
|
242
|
-
|
|
243
|
-
const apiUseStateActionsSnippet = `const handler = useStateHandler(createUserStore, []);
|
|
244
|
-
const actions = useStateActions(handler);
|
|
245
|
-
|
|
246
|
-
actions.rename('Ada');`;
|
|
247
|
-
|
|
248
|
-
const apiUseStateSubscriptionSnippet = `const handler = useStateHandler(createUserStore, []);
|
|
249
|
-
|
|
250
|
-
const [fullState] = useStateSubscription(handler);
|
|
251
|
-
const [name] = useStateSubscription(handler, (state) => state.user.name);
|
|
252
|
-
const [profile] = useStateSubscription(
|
|
253
|
-
handler,
|
|
254
|
-
(state) => state.user.profile,
|
|
255
|
-
(current, next) => current.id === next.id && current.role === next.role
|
|
256
|
-
);`;
|
|
257
|
-
|
|
258
|
-
const apiUseStateFactorySnippet = `const [state, actions] = useStateFactory(createUserStore, []);
|
|
259
|
-
const [name] = useStateFactory(createUserStore, (state) => state.user.name, []);
|
|
260
|
-
const [profile] = useStateFactory(
|
|
261
|
-
createUserStore,
|
|
262
|
-
(state) => state.user.profile,
|
|
263
|
-
(current, next) => current.id === next.id,
|
|
264
|
-
[]
|
|
265
|
-
);`;
|
|
266
|
-
|
|
267
|
-
const apiMakeStateSingletonSnippet = `const SessionSingleton = makeStateSingleton(() => new SessionStore(), {
|
|
268
|
-
destroyOnNoConsumers: true // default
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
const PersistentSessionSingleton = makeStateSingleton(() => new SessionStore(), {
|
|
272
|
-
destroyOnNoConsumers: false
|
|
273
|
-
});`;
|
|
274
|
-
|
|
275
|
-
const apiUseStateSingletonSnippet = `const [state, actions] = useStateSingleton(SessionSingleton);
|
|
276
|
-
const [userName] = useStateSingleton(SessionSingleton, (state) => state.user.name);
|
|
277
|
-
|
|
278
|
-
// Same behavior via useStateSubscription:
|
|
279
|
-
const [session] = useStateSubscription(SessionSingleton);`;
|
|
280
|
-
|
|
281
|
-
const apiSetupStatusQuoSnippet = `import equal from 'fast-deep-equal';
|
|
282
|
-
import { setupStatusQuo } from '@veams/status-quo';
|
|
283
|
-
|
|
284
|
-
setupStatusQuo({
|
|
285
|
-
distinct: {
|
|
286
|
-
comparator: equal
|
|
287
|
-
}
|
|
288
|
-
});`;
|
|
289
|
-
|
|
290
|
-
const composeSnippet = `import { combineLatest } from "rxjs";
|
|
291
|
-
|
|
292
|
-
// RxJS: combine handler streams (RxJS shines here)
|
|
293
|
-
class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
|
|
294
|
-
private counter$ = CounterObservableStore.getInstance().getStateAsObservable();
|
|
295
|
-
private card$ = new CardObservableHandler();
|
|
296
|
-
|
|
297
|
-
constructor() {
|
|
298
|
-
super({ initialState: { counter: 0, cardTitle: "" }});
|
|
299
|
-
|
|
300
|
-
this.subscriptions.push(
|
|
301
|
-
combineLatest([
|
|
302
|
-
this.counter$,
|
|
303
|
-
this.card$,
|
|
304
|
-
]).subscribe(([counterState, cardState]) => {
|
|
305
|
-
this.setState({
|
|
306
|
-
counter: counterState,
|
|
307
|
-
cardTitle: cardState.title,
|
|
308
|
-
}, "sync-combined");
|
|
309
|
-
})
|
|
310
|
-
)
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Signals: combine derived values via computed + bindSubscribable
|
|
316
|
-
import { computed } from "@preact/signals-core";
|
|
317
|
-
|
|
318
|
-
class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
|
|
319
|
-
private counter = CounterSignalHandler.getInstance().getSignal();
|
|
320
|
-
private card = new CardSignalHandler();
|
|
321
|
-
private combined = computed(() => ({
|
|
322
|
-
counter: this.counter.getSignal().value,
|
|
323
|
-
cardTitle: this.card.getSignal().value.title,
|
|
324
|
-
}));
|
|
325
|
-
|
|
326
|
-
constructor() {
|
|
327
|
-
super({ initialState: { counter: 0, cardTitle: "" }});
|
|
328
|
-
|
|
329
|
-
this.bindSubscribable(
|
|
330
|
-
{ subscribe: this.combined.subscribe.bind(this.combined), getSnapshot: () => this.combined.value },
|
|
331
|
-
(nextState) => this.setState(nextState, "sync-combined")
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
}`;
|
|
335
|
-
|
|
336
|
-
useEffect(() => {
|
|
337
|
-
Prism.highlightAll();
|
|
338
|
-
}, []);
|
|
339
|
-
|
|
340
|
-
const closeNav = () => {
|
|
341
|
-
setIsNavOpen(false);
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
return (
|
|
345
|
-
<div className="app">
|
|
346
|
-
<div className="brand-bar">
|
|
347
|
-
<img src={statusQuoLogo} alt="StatusQuo logo" className="brand-logo" />
|
|
348
|
-
</div>
|
|
349
|
-
<nav className="nav">
|
|
350
|
-
<button
|
|
351
|
-
type="button"
|
|
352
|
-
className="nav-toggle"
|
|
353
|
-
aria-expanded={isNavOpen}
|
|
354
|
-
aria-controls="docs-nav-links"
|
|
355
|
-
onClick={() => setIsNavOpen((open) => !open)}
|
|
356
|
-
>
|
|
357
|
-
Sections
|
|
358
|
-
</button>
|
|
359
|
-
<div id="docs-nav-links" className={`nav-links${isNavOpen ? ' is-open' : ''}`}>
|
|
360
|
-
<a href="#overview" onClick={closeNav}>
|
|
361
|
-
Overview
|
|
362
|
-
</a>
|
|
363
|
-
<a href="#quickstart" onClick={closeNav}>
|
|
364
|
-
Quickstart
|
|
365
|
-
</a>
|
|
366
|
-
<a href="#factory" onClick={closeNav}>
|
|
367
|
-
Factory
|
|
368
|
-
</a>
|
|
369
|
-
<a href="#selectors" onClick={closeNav}>
|
|
370
|
-
Selectors
|
|
371
|
-
</a>
|
|
372
|
-
<a href="#api" onClick={closeNav}>
|
|
373
|
-
API
|
|
374
|
-
</a>
|
|
375
|
-
<a href="#demo" onClick={closeNav}>
|
|
376
|
-
Demo
|
|
377
|
-
</a>
|
|
378
|
-
<a href="#singleton" onClick={closeNav}>
|
|
379
|
-
Singleton
|
|
380
|
-
</a>
|
|
381
|
-
<a href="#singleton-demo" onClick={closeNav}>
|
|
382
|
-
Singleton demo
|
|
383
|
-
</a>
|
|
384
|
-
<a href="#compose" onClick={closeNav}>
|
|
385
|
-
Compose
|
|
386
|
-
</a>
|
|
387
|
-
<a href="#devtools" onClick={closeNav}>
|
|
388
|
-
Devtools
|
|
389
|
-
</a>
|
|
390
|
-
<a href="#cleanup" onClick={closeNav}>
|
|
391
|
-
Cleanup
|
|
392
|
-
</a>
|
|
393
|
-
</div>
|
|
394
|
-
</nav>
|
|
395
|
-
<header id="overview" className="hero intro">
|
|
396
|
-
<div>
|
|
397
|
-
<p className="eyebrow">Philosophy</p>
|
|
398
|
-
<h1>State management that stays out of your way</h1>
|
|
399
|
-
<p className="subtext">
|
|
400
|
-
<span>StatusQuo</span> treats state handlers as small, composable objects with explicit
|
|
401
|
-
lifecycle and a tiny interface. Components subscribe to snapshots, not
|
|
402
|
-
framework‑specific store APIs. That makes it easy to swap the engine under the hood—RxJS
|
|
403
|
-
for observable streams or Preact Signals for ultra‑light reactive state.
|
|
404
|
-
</p>
|
|
405
|
-
<p className="subtext">
|
|
406
|
-
Handlers encapsulate state transitions, expose actions, and clean up after themselves.
|
|
407
|
-
You decide whether each component should have its own instance (factory) or share a
|
|
408
|
-
singleton, while the UI stays blissfully unaware of the chosen implementation.
|
|
409
|
-
</p>
|
|
410
|
-
</div>
|
|
411
|
-
</header>
|
|
412
|
-
|
|
413
|
-
<section className="philosophy-grid">
|
|
414
|
-
<article className="philosophy-card">
|
|
415
|
-
<img src={philosophySwap} alt="Swapping state engines illustration" />
|
|
416
|
-
<div>
|
|
417
|
-
<h3>Swap the engine, keep the API</h3>
|
|
418
|
-
<p>
|
|
419
|
-
Move between RxJS observables and Signals without rewriting your components. Stores
|
|
420
|
-
keep the same shape; only the internal runtime changes.
|
|
421
|
-
</p>
|
|
422
|
-
</div>
|
|
423
|
-
</article>
|
|
424
|
-
<article className="philosophy-card">
|
|
425
|
-
<img src={philosophySeparation} alt="View and state separation illustration" />
|
|
426
|
-
<div>
|
|
427
|
-
<h3>Separate view and state</h3>
|
|
428
|
-
<p>
|
|
429
|
-
Handlers own transitions and expose actions. Views subscribe to snapshots and never
|
|
430
|
-
touch store internals.
|
|
431
|
-
</p>
|
|
432
|
-
</div>
|
|
433
|
-
</article>
|
|
434
|
-
<article className="philosophy-card">
|
|
435
|
-
<img src={philosophyAgnostic} alt="Framework-agnostic state core illustration" />
|
|
436
|
-
<div>
|
|
437
|
-
<h3>Framework‑agnostic core</h3>
|
|
438
|
-
<p>
|
|
439
|
-
Your state logic lives outside any UI library. Hooks provide the glue, not the
|
|
440
|
-
business logic.
|
|
441
|
-
</p>
|
|
442
|
-
</div>
|
|
443
|
-
</article>
|
|
444
|
-
</section>
|
|
445
|
-
|
|
446
|
-
<section id="quickstart" className="doc-section">
|
|
447
|
-
<div className="doc-copy">
|
|
448
|
-
<p className="eyebrow">Quickstart</p>
|
|
449
|
-
<h2>Install and wire up a handler</h2>
|
|
450
|
-
<p>
|
|
451
|
-
Pick either observable or signal stores. The hooks stay the same, so you can swap the
|
|
452
|
-
backend without changing your components.
|
|
453
|
-
</p>
|
|
454
|
-
</div>
|
|
455
|
-
<div className="doc-snippets">
|
|
456
|
-
<pre className="code-block compact">
|
|
457
|
-
<code className="language-bash">{installSnippet}</code>
|
|
458
|
-
</pre>
|
|
459
|
-
<pre className="code-block compact">
|
|
460
|
-
<code className="language-ts">{quickstartSnippet}</code>
|
|
461
|
-
</pre>
|
|
462
|
-
</div>
|
|
463
|
-
</section>
|
|
464
|
-
|
|
465
|
-
<section className="guide">
|
|
466
|
-
<article id="factory" className="guide-card">
|
|
467
|
-
<div>
|
|
468
|
-
<p className="eyebrow">Factory</p>
|
|
469
|
-
<h2>Fresh handler per mount</h2>
|
|
470
|
-
<p>
|
|
471
|
-
Use `useStateFactory` when each component instance needs its own store.
|
|
472
|
-
</p>
|
|
473
|
-
<p className="guide-highlight">Local state, zero cross-talk, predictable lifecycles.</p>
|
|
474
|
-
</div>
|
|
475
|
-
</article>
|
|
476
|
-
<article id="singleton" className="guide-card">
|
|
477
|
-
<div>
|
|
478
|
-
<p className="eyebrow">Singleton</p>
|
|
479
|
-
<h2>One handler, many consumers</h2>
|
|
480
|
-
<p>
|
|
481
|
-
Use `makeStateSingleton` + `useStateSingleton` for shared state across components.
|
|
482
|
-
</p>
|
|
483
|
-
<p className="guide-highlight">One source of truth, synced everywhere.</p>
|
|
484
|
-
</div>
|
|
485
|
-
</article>
|
|
486
|
-
<article id="selectors" className="guide-card">
|
|
487
|
-
<div>
|
|
488
|
-
<p className="eyebrow">Selectors</p>
|
|
489
|
-
<h2>Avoid rerender fanout</h2>
|
|
490
|
-
<p>
|
|
491
|
-
Subscribe only to the state slice you need with selector + equality functions.
|
|
492
|
-
</p>
|
|
493
|
-
<p className="guide-highlight">Read less, rerender less, ship smoother UIs.</p>
|
|
494
|
-
</div>
|
|
495
|
-
</article>
|
|
496
|
-
</section>
|
|
497
|
-
|
|
498
|
-
<section id="api" className="doc-section">
|
|
499
|
-
<div className="doc-copy">
|
|
500
|
-
<p className="eyebrow">API</p>
|
|
501
|
-
<h2>Detailed hook and singleton reference</h2>
|
|
502
|
-
</div>
|
|
503
|
-
<div className="api-flow">
|
|
504
|
-
<div className="api-group">
|
|
505
|
-
<article className="api-composition-card">
|
|
506
|
-
<h3>Base composition</h3>
|
|
507
|
-
<p>
|
|
508
|
-
Build explicit lifecycles with `useStateHandler`, pull actions with
|
|
509
|
-
`useStateActions`, and subscribe to exact slices with `useStateSubscription`.
|
|
510
|
-
</p>
|
|
511
|
-
<p className="api-composition-punch">Maximum control, minimum magic.</p>
|
|
512
|
-
</article>
|
|
513
|
-
<div className="api-grid">
|
|
514
|
-
<article className="api-card">
|
|
515
|
-
<h3>useStateHandler(factory, params?)</h3>
|
|
516
|
-
<p>Creates one handler instance per component mount and reuses it across rerenders.</p>
|
|
517
|
-
<pre className="code-block compact">
|
|
518
|
-
<code className="language-ts">{apiUseStateHandlerSnippet}</code>
|
|
519
|
-
</pre>
|
|
520
|
-
</article>
|
|
521
|
-
|
|
522
|
-
<article className="api-card">
|
|
523
|
-
<h3>useStateActions(handler)</h3>
|
|
524
|
-
<p>Returns actions without subscribing to state updates.</p>
|
|
525
|
-
<pre className="code-block compact">
|
|
526
|
-
<code className="language-ts">{apiUseStateActionsSnippet}</code>
|
|
527
|
-
</pre>
|
|
528
|
-
</article>
|
|
529
|
-
|
|
530
|
-
<article className="api-card api-card-wide">
|
|
531
|
-
<h3>useStateSubscription(source, selector?, isEqual?)</h3>
|
|
532
|
-
<p>
|
|
533
|
-
Core subscription hook. Source can be a handler or singleton. Returns
|
|
534
|
-
`[selectedState, actions]`.
|
|
535
|
-
</p>
|
|
536
|
-
<pre className="code-block compact">
|
|
537
|
-
<code className="language-ts">{apiUseStateSubscriptionSnippet}</code>
|
|
538
|
-
</pre>
|
|
539
|
-
</article>
|
|
540
|
-
</div>
|
|
541
|
-
</div>
|
|
542
|
-
|
|
543
|
-
<div className="api-group">
|
|
544
|
-
<article className="api-composition-card">
|
|
545
|
-
<h3>Shortcut composition</h3>
|
|
546
|
-
<p>
|
|
547
|
-
Reduce wiring with `useStateFactory` and `useStateSingleton` when you want
|
|
548
|
-
the same behavior in a smaller API surface.
|
|
549
|
-
</p>
|
|
550
|
-
<p className="api-composition-punch">Less wiring, same behavior.</p>
|
|
551
|
-
</article>
|
|
552
|
-
<div className="api-grid">
|
|
553
|
-
<article className="api-card">
|
|
554
|
-
<h3>useStateFactory(factory, selector?, isEqual?, params?)</h3>
|
|
555
|
-
<p>
|
|
556
|
-
Shortcut for handler creation + subscription. Supports full state and selected
|
|
557
|
-
slices.
|
|
558
|
-
</p>
|
|
559
|
-
<pre className="code-block compact">
|
|
560
|
-
<code className="language-ts">{apiUseStateFactorySnippet}</code>
|
|
561
|
-
</pre>
|
|
562
|
-
</article>
|
|
563
|
-
|
|
564
|
-
<article className="api-card">
|
|
565
|
-
<h3>useStateSingleton(singleton, selector?, isEqual?)</h3>
|
|
566
|
-
<p>
|
|
567
|
-
Shortcut for subscribing to a singleton. Selector and equality semantics match
|
|
568
|
-
`useStateSubscription`.
|
|
569
|
-
</p>
|
|
570
|
-
<pre className="code-block compact">
|
|
571
|
-
<code className="language-ts">{apiUseStateSingletonSnippet}</code>
|
|
572
|
-
</pre>
|
|
573
|
-
</article>
|
|
574
|
-
</div>
|
|
575
|
-
</div>
|
|
576
|
-
|
|
577
|
-
<div className="api-group">
|
|
578
|
-
<article className="api-composition-card">
|
|
579
|
-
<h3>Helper function</h3>
|
|
580
|
-
<p>
|
|
581
|
-
Configure global distinct behavior once with `setupStatusQuo` and create shared
|
|
582
|
-
singleton providers with `makeStateSingleton`.
|
|
583
|
-
</p>
|
|
584
|
-
<p className="api-composition-punch">One setup, consistent behavior.</p>
|
|
585
|
-
</article>
|
|
586
|
-
<div className="api-grid">
|
|
587
|
-
<article className="api-card">
|
|
588
|
-
<h3>setupStatusQuo(config?)</h3>
|
|
589
|
-
<p>
|
|
590
|
-
Defines global runtime defaults for distinct update behavior. Per-handler options
|
|
591
|
-
still override global config.
|
|
592
|
-
</p>
|
|
593
|
-
<pre className="code-block compact">
|
|
594
|
-
<code className="language-ts">{apiSetupStatusQuoSnippet}</code>
|
|
595
|
-
</pre>
|
|
596
|
-
</article>
|
|
597
|
-
|
|
598
|
-
<article className="api-card">
|
|
599
|
-
<h3>makeStateSingleton(factory, options?)</h3>
|
|
600
|
-
<p>
|
|
601
|
-
Creates a shared handler provider. `destroyOnNoConsumers` controls instance
|
|
602
|
-
teardown.
|
|
603
|
-
</p>
|
|
604
|
-
<pre className="code-block compact">
|
|
605
|
-
<code className="language-ts">{apiMakeStateSingletonSnippet}</code>
|
|
606
|
-
</pre>
|
|
607
|
-
</article>
|
|
608
|
-
</div>
|
|
609
|
-
</div>
|
|
610
|
-
</div>
|
|
611
|
-
</section>
|
|
612
|
-
|
|
613
|
-
<div id="demo" className="grid">
|
|
614
|
-
<CounterCard
|
|
615
|
-
title="Observable State Handler"
|
|
616
|
-
subtitle="RxJS BehaviorSubject"
|
|
617
|
-
state={observableState}
|
|
618
|
-
actions={observableActions}
|
|
619
|
-
snippet={observableSnippet}
|
|
620
|
-
/>
|
|
621
|
-
<CounterCard
|
|
622
|
-
title="Signal State Handler"
|
|
623
|
-
subtitle="Preact Signals"
|
|
624
|
-
state={signalState}
|
|
625
|
-
actions={signalActions}
|
|
626
|
-
snippet={signalSnippet}
|
|
627
|
-
/>
|
|
628
|
-
</div>
|
|
629
|
-
|
|
630
|
-
<section id="singleton-demo" className="doc-section">
|
|
631
|
-
<div className="doc-copy">
|
|
632
|
-
<p className="eyebrow">Singleton demo</p>
|
|
633
|
-
<h2>One store, multiple components</h2>
|
|
634
|
-
<p>
|
|
635
|
-
Both panels below are subscribed to the same singleton handler. Update the controller
|
|
636
|
-
and watch the viewer react instantly. The handler is only disposed once the last
|
|
637
|
-
consumer unmounts (unless persistence is enabled).
|
|
638
|
-
</p>
|
|
639
|
-
<p>
|
|
640
|
-
`makeStateSingleton(factory, options)` always returns one shared instance.
|
|
641
|
-
`destroyOnNoConsumers` controls whether that instance is torn down when the last
|
|
642
|
-
`useStateSingleton` consumer unmounts.
|
|
643
|
-
</p>
|
|
644
|
-
</div>
|
|
645
|
-
<pre className="code-block compact">
|
|
646
|
-
<code className="language-ts">{singletonOptionsSnippet}</code>
|
|
647
|
-
</pre>
|
|
648
|
-
<div className="singleton-grid">
|
|
649
|
-
<SingletonControls />
|
|
650
|
-
<SingletonDisplay />
|
|
651
|
-
</div>
|
|
652
|
-
</section>
|
|
653
|
-
|
|
654
|
-
<section id="compose" className="doc-section">
|
|
655
|
-
<div className="doc-copy">
|
|
656
|
-
<p className="eyebrow">Compose</p>
|
|
657
|
-
<h2>Combine multiple handlers</h2>
|
|
658
|
-
<p>
|
|
659
|
-
Use only the slice you need. RxJS makes multi-source composition powerful and
|
|
660
|
-
declarative with operators like `combineLatest`, `switchMap`, or `debounceTime`. Signals
|
|
661
|
-
can derive values with `computed` and wire them into a parent store via
|
|
662
|
-
`bindSubscribable`. This keeps parent stores lean and focused.
|
|
663
|
-
</p>
|
|
664
|
-
</div>
|
|
665
|
-
<pre className="code-block">
|
|
666
|
-
<code className="language-ts">{composeSnippet}</code>
|
|
667
|
-
</pre>
|
|
668
|
-
</section>
|
|
669
|
-
|
|
670
|
-
<section id="devtools" className="doc-section">
|
|
671
|
-
<div className="doc-copy">
|
|
672
|
-
<p className="eyebrow">Devtools</p>
|
|
673
|
-
<h2>Redux devtools integration</h2>
|
|
674
|
-
<p>
|
|
675
|
-
Enable devtools to inspect actions and state transitions. Both handlers share the same
|
|
676
|
-
options shape.
|
|
677
|
-
</p>
|
|
678
|
-
</div>
|
|
679
|
-
<pre className="code-block">
|
|
680
|
-
<code className="language-ts">{devToolsSnippet}</code>
|
|
681
|
-
</pre>
|
|
682
|
-
</section>
|
|
683
|
-
|
|
684
|
-
<section id="cleanup" className="doc-section">
|
|
685
|
-
<div className="doc-copy">
|
|
686
|
-
<p className="eyebrow">Cleanup</p>
|
|
687
|
-
<h2>Controlled teardown</h2>
|
|
688
|
-
<p>
|
|
689
|
-
Each handler exposes `subscribe`, `getSnapshot`, and `destroy`. Use them when you wire
|
|
690
|
-
custom integrations or combine multiple stores manually.
|
|
691
|
-
</p>
|
|
692
|
-
</div>
|
|
693
|
-
<pre className="code-block">
|
|
694
|
-
<code className="language-ts">{cleanupSnippet}</code>
|
|
695
|
-
</pre>
|
|
696
|
-
</section>
|
|
697
|
-
</div>
|
|
698
|
-
);
|
|
699
|
-
}
|