@veams/status-quo 1.1.0 → 1.3.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/CHANGELOG.md +53 -0
- package/README.md +252 -18
- package/dist/config/status-quo-config.d.ts +21 -0
- package/dist/config/status-quo-config.js +48 -0
- package/dist/config/status-quo-config.js.map +1 -0
- package/dist/hooks/__tests__/state-selector.spec.d.ts +4 -0
- package/dist/hooks/__tests__/state-selector.spec.js +384 -0
- package/dist/hooks/__tests__/state-selector.spec.js.map +1 -0
- package/dist/hooks/__tests__/state-singleton.spec.d.ts +4 -0
- package/dist/hooks/__tests__/state-singleton.spec.js +97 -0
- package/dist/hooks/__tests__/state-singleton.spec.js.map +1 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/state-actions.d.ts +2 -0
- package/dist/hooks/state-actions.js +5 -0
- package/dist/hooks/state-actions.js.map +1 -0
- package/dist/hooks/state-factory.d.ts +5 -0
- package/dist/hooks/state-factory.js +10 -6
- package/dist/hooks/state-factory.js.map +1 -1
- package/dist/hooks/state-handler.d.ts +2 -0
- package/dist/hooks/state-handler.js +9 -0
- package/dist/hooks/state-handler.js.map +1 -0
- package/dist/hooks/state-singleton.d.ts +4 -0
- package/dist/hooks/state-singleton.js +3 -5
- package/dist/hooks/state-singleton.js.map +1 -1
- package/dist/hooks/state-subscription-selector.d.ts +5 -0
- package/dist/hooks/state-subscription-selector.js +29 -0
- package/dist/hooks/state-subscription-selector.js.map +1 -0
- package/dist/hooks/state-subscription.d.ts +8 -1
- package/dist/hooks/state-subscription.js +49 -10
- package/dist/hooks/state-subscription.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/store/__tests__/observable-state-handler.spec.js +68 -5
- package/dist/store/__tests__/observable-state-handler.spec.js.map +1 -1
- package/dist/store/__tests__/signal-state-handler.spec.js +64 -5
- package/dist/store/__tests__/signal-state-handler.spec.js.map +1 -1
- package/dist/store/index.d.ts +1 -1
- package/dist/store/observable-state-handler.d.ts +7 -2
- package/dist/store/observable-state-handler.js +17 -22
- package/dist/store/observable-state-handler.js.map +1 -1
- package/dist/store/signal-state-handler.d.ts +3 -1
- package/dist/store/signal-state-handler.js +5 -14
- package/dist/store/signal-state-handler.js.map +1 -1
- package/dist/store/state-singleton.d.ts +4 -1
- package/dist/store/state-singleton.js +11 -2
- package/dist/store/state-singleton.js.map +1 -1
- package/docs/assets/index-BBmpszOW.css +1 -0
- package/docs/assets/index-Cf8El_RO.js +194 -0
- package/docs/assets/statusquo-logo-8GVRbxpc.png +0 -0
- package/docs/index.html +13 -0
- package/package.json +1 -1
- package/playground/src/App.tsx +269 -48
- package/playground/src/styles.css +123 -0
- package/src/config/status-quo-config.ts +76 -0
- package/src/hooks/__tests__/state-selector.spec.tsx +607 -0
- package/src/hooks/__tests__/state-singleton.spec.tsx +151 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/state-actions.tsx +7 -0
- package/src/hooks/state-factory.tsx +32 -6
- package/src/hooks/state-handler.tsx +16 -0
- package/src/hooks/state-singleton.tsx +17 -7
- package/src/hooks/state-subscription-selector.tsx +70 -0
- package/src/hooks/state-subscription.tsx +98 -21
- package/src/index.ts +23 -4
- package/src/store/__tests__/observable-state-handler.spec.ts +98 -11
- package/src/store/__tests__/signal-state-handler.spec.ts +92 -11
- package/src/store/index.ts +1 -1
- package/src/store/observable-state-handler.ts +25 -27
- package/src/store/signal-state-handler.ts +7 -16
- package/src/store/state-singleton.ts +21 -3
|
Binary file
|
package/docs/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Status Quo Playground</title>
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-Cf8El_RO.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/index-BBmpszOW.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div id="root"></div>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/package.json
CHANGED
package/playground/src/App.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
makeStateSingleton,
|
|
@@ -30,7 +30,10 @@ type CounterActions = {
|
|
|
30
30
|
|
|
31
31
|
class ObservableCounterStateHandler extends ObservableStateHandler<CounterState, CounterActions> {
|
|
32
32
|
constructor(startCount = 0) {
|
|
33
|
-
super({
|
|
33
|
+
super({
|
|
34
|
+
initialState: { count: startCount },
|
|
35
|
+
options: { devTools: { enabled: true, namespace: 'Counter as Observable' } },
|
|
36
|
+
});
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
getActions(): CounterActions {
|
|
@@ -51,7 +54,9 @@ class ObservableCounterStateHandler extends ObservableStateHandler<CounterState,
|
|
|
51
54
|
|
|
52
55
|
class SignalCounterStateHandler extends SignalStateHandler<CounterState, CounterActions> {
|
|
53
56
|
constructor(startCount = 0) {
|
|
54
|
-
super({ initialState: { count: startCount }
|
|
57
|
+
super({ initialState: { count: startCount },
|
|
58
|
+
options: { devTools: { enabled: true, namespace: 'Counter as Signal' } },
|
|
59
|
+
});
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
getActions(): CounterActions {
|
|
@@ -159,6 +164,7 @@ function SingletonDisplay() {
|
|
|
159
164
|
}
|
|
160
165
|
|
|
161
166
|
export function App() {
|
|
167
|
+
const [isNavOpen, setIsNavOpen] = useState(false);
|
|
162
168
|
const [observableState, observableActions] = useStateFactory(ObservableCounterFactory, [0]);
|
|
163
169
|
const [signalState, signalActions] = useStateFactory(SignalCounterFactory, [0]);
|
|
164
170
|
|
|
@@ -222,11 +228,64 @@ const [state, actions] = useStateFactory(() => new CounterStore(), []);`;
|
|
|
222
228
|
unsubscribe();
|
|
223
229
|
store.destroy();`;
|
|
224
230
|
|
|
225
|
-
const
|
|
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
|
+
);`;
|
|
226
257
|
|
|
227
|
-
const
|
|
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
|
+
);`;
|
|
228
266
|
|
|
229
|
-
const
|
|
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
|
+
});`;
|
|
230
289
|
|
|
231
290
|
const composeSnippet = `import { combineLatest } from "rxjs";
|
|
232
291
|
|
|
@@ -278,22 +337,59 @@ class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
|
|
|
278
337
|
Prism.highlightAll();
|
|
279
338
|
}, []);
|
|
280
339
|
|
|
340
|
+
const closeNav = () => {
|
|
341
|
+
setIsNavOpen(false);
|
|
342
|
+
};
|
|
343
|
+
|
|
281
344
|
return (
|
|
282
345
|
<div className="app">
|
|
283
346
|
<div className="brand-bar">
|
|
284
347
|
<img src={statusQuoLogo} alt="StatusQuo logo" className="brand-logo" />
|
|
285
348
|
</div>
|
|
286
349
|
<nav className="nav">
|
|
287
|
-
<
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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>
|
|
297
393
|
</div>
|
|
298
394
|
</nav>
|
|
299
395
|
<header id="overview" className="hero intro">
|
|
@@ -303,14 +399,13 @@ class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
|
|
|
303
399
|
<p className="subtext">
|
|
304
400
|
<span>StatusQuo</span> treats state handlers as small, composable objects with explicit
|
|
305
401
|
lifecycle and a tiny interface. Components subscribe to snapshots, not
|
|
306
|
-
framework‑specific store APIs. That makes it easy to swap the engine under the
|
|
307
|
-
|
|
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.
|
|
308
404
|
</p>
|
|
309
405
|
<p className="subtext">
|
|
310
|
-
Handlers encapsulate state transitions, expose actions, and clean up after
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
chosen implementation.
|
|
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.
|
|
314
409
|
</p>
|
|
315
410
|
</div>
|
|
316
411
|
</header>
|
|
@@ -321,8 +416,8 @@ class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
|
|
|
321
416
|
<div>
|
|
322
417
|
<h3>Swap the engine, keep the API</h3>
|
|
323
418
|
<p>
|
|
324
|
-
Move between RxJS observables and Signals without rewriting your components.
|
|
325
|
-
|
|
419
|
+
Move between RxJS observables and Signals without rewriting your components. Stores
|
|
420
|
+
keep the same shape; only the internal runtime changes.
|
|
326
421
|
</p>
|
|
327
422
|
</div>
|
|
328
423
|
</article>
|
|
@@ -331,8 +426,8 @@ class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
|
|
|
331
426
|
<div>
|
|
332
427
|
<h3>Separate view and state</h3>
|
|
333
428
|
<p>
|
|
334
|
-
Handlers own transitions and expose actions. Views subscribe to snapshots and
|
|
335
|
-
|
|
429
|
+
Handlers own transitions and expose actions. Views subscribe to snapshots and never
|
|
430
|
+
touch store internals.
|
|
336
431
|
</p>
|
|
337
432
|
</div>
|
|
338
433
|
</article>
|
|
@@ -353,8 +448,8 @@ class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
|
|
|
353
448
|
<p className="eyebrow">Quickstart</p>
|
|
354
449
|
<h2>Install and wire up a handler</h2>
|
|
355
450
|
<p>
|
|
356
|
-
Pick either observable or signal stores. The hooks stay the same, so you can
|
|
357
|
-
|
|
451
|
+
Pick either observable or signal stores. The hooks stay the same, so you can swap the
|
|
452
|
+
backend without changing your components.
|
|
358
453
|
</p>
|
|
359
454
|
</div>
|
|
360
455
|
<div className="doc-snippets">
|
|
@@ -373,30 +468,148 @@ class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
|
|
|
373
468
|
<p className="eyebrow">Factory</p>
|
|
374
469
|
<h2>Fresh handler per mount</h2>
|
|
375
470
|
<p>
|
|
376
|
-
Use `useStateFactory` when
|
|
377
|
-
pass a factory function and dependencies, and the hook handles lifecycle
|
|
378
|
-
cleanup for you.
|
|
471
|
+
Use `useStateFactory` when each component instance needs its own store.
|
|
379
472
|
</p>
|
|
473
|
+
<p className="guide-highlight">Local state, zero cross-talk, predictable lifecycles.</p>
|
|
380
474
|
</div>
|
|
381
|
-
<pre className="code-block compact">
|
|
382
|
-
<code className="language-ts">{`const [state, actions] = useStateFactory(CounterFactory, [0]);`}</code>
|
|
383
|
-
</pre>
|
|
384
475
|
</article>
|
|
385
476
|
<article id="singleton" className="guide-card">
|
|
386
477
|
<div>
|
|
387
478
|
<p className="eyebrow">Singleton</p>
|
|
388
479
|
<h2>One handler, many consumers</h2>
|
|
389
480
|
<p>
|
|
390
|
-
Use `makeStateSingleton`
|
|
391
|
-
global counters, user sessions, or any shared UI state.
|
|
481
|
+
Use `makeStateSingleton` + `useStateSingleton` for shared state across components.
|
|
392
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>
|
|
393
494
|
</div>
|
|
394
|
-
<pre className="code-block compact">
|
|
395
|
-
<code className="language-ts">{singletonSnippet}</code>
|
|
396
|
-
</pre>
|
|
397
495
|
</article>
|
|
398
496
|
</section>
|
|
399
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
|
+
|
|
400
613
|
<div id="demo" className="grid">
|
|
401
614
|
<CounterCard
|
|
402
615
|
title="Observable State Handler"
|
|
@@ -419,10 +632,19 @@ class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
|
|
|
419
632
|
<p className="eyebrow">Singleton demo</p>
|
|
420
633
|
<h2>One store, multiple components</h2>
|
|
421
634
|
<p>
|
|
422
|
-
Both panels below are subscribed to the same singleton handler. Update the
|
|
423
|
-
|
|
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.
|
|
424
643
|
</p>
|
|
425
644
|
</div>
|
|
645
|
+
<pre className="code-block compact">
|
|
646
|
+
<code className="language-ts">{singletonOptionsSnippet}</code>
|
|
647
|
+
</pre>
|
|
426
648
|
<div className="singleton-grid">
|
|
427
649
|
<SingletonControls />
|
|
428
650
|
<SingletonDisplay />
|
|
@@ -435,8 +657,8 @@ class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
|
|
|
435
657
|
<h2>Combine multiple handlers</h2>
|
|
436
658
|
<p>
|
|
437
659
|
Use only the slice you need. RxJS makes multi-source composition powerful and
|
|
438
|
-
declarative with operators like `combineLatest`, `switchMap`, or `debounceTime`.
|
|
439
|
-
|
|
660
|
+
declarative with operators like `combineLatest`, `switchMap`, or `debounceTime`. Signals
|
|
661
|
+
can derive values with `computed` and wire them into a parent store via
|
|
440
662
|
`bindSubscribable`. This keeps parent stores lean and focused.
|
|
441
663
|
</p>
|
|
442
664
|
</div>
|
|
@@ -450,8 +672,8 @@ class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
|
|
|
450
672
|
<p className="eyebrow">Devtools</p>
|
|
451
673
|
<h2>Redux devtools integration</h2>
|
|
452
674
|
<p>
|
|
453
|
-
Enable devtools to inspect actions and state transitions. Both handlers share
|
|
454
|
-
|
|
675
|
+
Enable devtools to inspect actions and state transitions. Both handlers share the same
|
|
676
|
+
options shape.
|
|
455
677
|
</p>
|
|
456
678
|
</div>
|
|
457
679
|
<pre className="code-block">
|
|
@@ -464,15 +686,14 @@ class AppSignalStore extends SignalStateHandler<AppState, AppActions> {
|
|
|
464
686
|
<p className="eyebrow">Cleanup</p>
|
|
465
687
|
<h2>Controlled teardown</h2>
|
|
466
688
|
<p>
|
|
467
|
-
Each handler exposes `subscribe`, `getSnapshot`, and `destroy`. Use them when
|
|
468
|
-
|
|
689
|
+
Each handler exposes `subscribe`, `getSnapshot`, and `destroy`. Use them when you wire
|
|
690
|
+
custom integrations or combine multiple stores manually.
|
|
469
691
|
</p>
|
|
470
692
|
</div>
|
|
471
693
|
<pre className="code-block">
|
|
472
694
|
<code className="language-ts">{cleanupSnippet}</code>
|
|
473
695
|
</pre>
|
|
474
696
|
</section>
|
|
475
|
-
|
|
476
697
|
</div>
|
|
477
698
|
);
|
|
478
699
|
}
|
|
@@ -64,15 +64,32 @@ body {
|
|
|
64
64
|
display: flex;
|
|
65
65
|
align-items: center;
|
|
66
66
|
justify-content: center;
|
|
67
|
+
flex-direction: column;
|
|
67
68
|
padding: 10px 6px;
|
|
69
|
+
width: 100%;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.nav-toggle {
|
|
73
|
+
display: none;
|
|
74
|
+
border: 1px solid rgba(27, 27, 31, 0.18);
|
|
75
|
+
background: rgba(255, 255, 255, 0.85);
|
|
76
|
+
color: var(--ink);
|
|
77
|
+
border-radius: 999px;
|
|
78
|
+
padding: 8px 14px;
|
|
79
|
+
font: inherit;
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
box-shadow: var(--shadow);
|
|
68
83
|
}
|
|
69
84
|
|
|
70
85
|
.nav-links {
|
|
71
86
|
display: inline-flex;
|
|
72
87
|
align-items: center;
|
|
88
|
+
justify-content: center;
|
|
73
89
|
gap: 10px;
|
|
74
90
|
flex-wrap: wrap;
|
|
75
91
|
padding: 6px 12px;
|
|
92
|
+
width: min(100%, 980px);
|
|
76
93
|
background: rgba(255, 255, 255, 0.7);
|
|
77
94
|
border-radius: 999px;
|
|
78
95
|
border: 1px solid rgba(27, 27, 31, 0.1);
|
|
@@ -165,6 +182,7 @@ p {
|
|
|
165
182
|
display: grid;
|
|
166
183
|
gap: 24px;
|
|
167
184
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
185
|
+
align-items: stretch;
|
|
168
186
|
}
|
|
169
187
|
|
|
170
188
|
.doc-section {
|
|
@@ -191,6 +209,7 @@ p {
|
|
|
191
209
|
display: grid;
|
|
192
210
|
gap: 20px;
|
|
193
211
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
212
|
+
align-items: stretch;
|
|
194
213
|
}
|
|
195
214
|
|
|
196
215
|
.singleton-card {
|
|
@@ -201,6 +220,8 @@ p {
|
|
|
201
220
|
box-shadow: var(--shadow);
|
|
202
221
|
display: grid;
|
|
203
222
|
gap: 14px;
|
|
223
|
+
align-content: start;
|
|
224
|
+
height: 100%;
|
|
204
225
|
}
|
|
205
226
|
|
|
206
227
|
.singleton-count {
|
|
@@ -227,12 +248,75 @@ p {
|
|
|
227
248
|
display: grid;
|
|
228
249
|
gap: 20px;
|
|
229
250
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
251
|
+
align-items: stretch;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.api-flow {
|
|
255
|
+
display: grid;
|
|
256
|
+
gap: 30px;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.api-group {
|
|
260
|
+
display: grid;
|
|
261
|
+
gap: 18px;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.api-grid {
|
|
265
|
+
display: grid;
|
|
266
|
+
gap: 18px;
|
|
267
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
268
|
+
align-items: stretch;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.api-composition-card {
|
|
272
|
+
padding: 2px 0 18px;
|
|
273
|
+
border-bottom: 1px solid rgba(27, 27, 31, 0.16);
|
|
274
|
+
display: grid;
|
|
275
|
+
gap: 10px;
|
|
276
|
+
align-content: start;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.api-composition-card h3 {
|
|
280
|
+
margin: 0;
|
|
281
|
+
font-size: 1rem;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.api-composition-punch {
|
|
285
|
+
margin-top: 8px;
|
|
286
|
+
font-weight: 600;
|
|
287
|
+
color: var(--ink);
|
|
288
|
+
background: linear-gradient(120deg, rgba(47, 107, 255, 0.14), rgba(255, 159, 28, 0.14));
|
|
289
|
+
border: 1px solid rgba(47, 107, 255, 0.2);
|
|
290
|
+
border-radius: var(--radius-sm);
|
|
291
|
+
padding: 10px 12px;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.api-card {
|
|
295
|
+
background: #fff;
|
|
296
|
+
border-radius: var(--radius-sm);
|
|
297
|
+
padding: 18px;
|
|
298
|
+
border: 1px solid rgba(27, 27, 31, 0.08);
|
|
299
|
+
box-shadow: var(--shadow);
|
|
300
|
+
display: grid;
|
|
301
|
+
gap: 10px;
|
|
302
|
+
align-content: start;
|
|
303
|
+
height: 100%;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.api-card h3 {
|
|
307
|
+
margin: 0;
|
|
308
|
+
font-size: 1rem;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.api-card-wide {
|
|
312
|
+
grid-column: 1 / -1;
|
|
230
313
|
}
|
|
231
314
|
|
|
232
315
|
.philosophy-grid {
|
|
233
316
|
display: grid;
|
|
234
317
|
gap: 20px;
|
|
235
318
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
319
|
+
align-items: stretch;
|
|
236
320
|
}
|
|
237
321
|
|
|
238
322
|
.philosophy-card {
|
|
@@ -243,6 +327,8 @@ p {
|
|
|
243
327
|
overflow: hidden;
|
|
244
328
|
display: grid;
|
|
245
329
|
gap: 16px;
|
|
330
|
+
align-content: start;
|
|
331
|
+
height: 100%;
|
|
246
332
|
}
|
|
247
333
|
|
|
248
334
|
.philosophy-card img {
|
|
@@ -266,6 +352,18 @@ p {
|
|
|
266
352
|
box-shadow: var(--shadow);
|
|
267
353
|
display: grid;
|
|
268
354
|
gap: 16px;
|
|
355
|
+
align-content: start;
|
|
356
|
+
height: 100%;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.guide-highlight {
|
|
360
|
+
margin-top: 4px;
|
|
361
|
+
font-weight: 600;
|
|
362
|
+
color: var(--ink);
|
|
363
|
+
background: linear-gradient(120deg, rgba(47, 107, 255, 0.16), rgba(255, 159, 28, 0.16));
|
|
364
|
+
border: 1px solid rgba(47, 107, 255, 0.2);
|
|
365
|
+
border-radius: var(--radius-sm);
|
|
366
|
+
padding: 10px 12px;
|
|
269
367
|
}
|
|
270
368
|
|
|
271
369
|
.code-block.compact {
|
|
@@ -283,6 +381,7 @@ p {
|
|
|
283
381
|
flex-direction: column;
|
|
284
382
|
gap: 20px;
|
|
285
383
|
animation: fade-up 0.6s ease both;
|
|
384
|
+
height: 100%;
|
|
286
385
|
}
|
|
287
386
|
|
|
288
387
|
.card:nth-child(2) {
|
|
@@ -392,14 +491,38 @@ p {
|
|
|
392
491
|
padding: 0;
|
|
393
492
|
}
|
|
394
493
|
|
|
494
|
+
.nav-toggle {
|
|
495
|
+
display: inline-flex;
|
|
496
|
+
align-items: center;
|
|
497
|
+
justify-content: center;
|
|
498
|
+
margin-bottom: 10px;
|
|
499
|
+
}
|
|
500
|
+
|
|
395
501
|
.nav-links {
|
|
502
|
+
display: none;
|
|
503
|
+
width: 100%;
|
|
396
504
|
border-radius: var(--radius-md);
|
|
505
|
+
padding: 10px;
|
|
506
|
+
gap: 8px;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.nav-links.is-open {
|
|
510
|
+
display: grid;
|
|
511
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.nav-links a {
|
|
515
|
+
text-align: center;
|
|
397
516
|
}
|
|
398
517
|
|
|
399
518
|
.doc-section {
|
|
400
519
|
padding: 22px;
|
|
401
520
|
}
|
|
402
521
|
|
|
522
|
+
.api-grid {
|
|
523
|
+
grid-template-columns: 1fr;
|
|
524
|
+
}
|
|
525
|
+
|
|
403
526
|
.hero {
|
|
404
527
|
padding: 26px;
|
|
405
528
|
}
|