@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.
Files changed (126) hide show
  1. package/.turbo/turbo-build.log +12 -0
  2. package/.turbo/turbo-check$colon$types.log +4 -0
  3. package/.turbo/turbo-docs$colon$build.log +14 -0
  4. package/.turbo/turbo-lint.log +8 -0
  5. package/.turbo/turbo-test.log +15 -0
  6. package/CHANGELOG.md +24 -3
  7. package/README.md +217 -41
  8. package/dist/config/status-quo-config.d.ts +13 -0
  9. package/dist/config/status-quo-config.js +14 -0
  10. package/dist/config/status-quo-config.js.map +1 -1
  11. package/dist/hooks/__tests__/state-provider.spec.d.ts +4 -0
  12. package/dist/hooks/__tests__/state-provider.spec.js +179 -0
  13. package/dist/hooks/__tests__/state-provider.spec.js.map +1 -0
  14. package/dist/hooks/__tests__/state-selector.spec.js +11 -12
  15. package/dist/hooks/__tests__/state-selector.spec.js.map +1 -1
  16. package/dist/hooks/__tests__/state-singleton.spec.js +10 -11
  17. package/dist/hooks/__tests__/state-singleton.spec.js.map +1 -1
  18. package/dist/hooks/index.d.ts +1 -0
  19. package/dist/hooks/index.js +1 -0
  20. package/dist/hooks/index.js.map +1 -1
  21. package/dist/hooks/state-factory.js.map +1 -1
  22. package/dist/hooks/state-provider.d.ts +14 -0
  23. package/dist/hooks/state-provider.js +24 -0
  24. package/dist/hooks/state-provider.js.map +1 -0
  25. package/dist/hooks/state-subscription-selector.js +6 -2
  26. package/dist/hooks/state-subscription-selector.js.map +1 -1
  27. package/dist/hooks/state-subscription.js +1 -1
  28. package/dist/hooks/state-subscription.js.map +1 -1
  29. package/dist/index.d.ts +4 -5
  30. package/dist/index.js +2 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/react/hooks/__tests__/state-provider.spec.d.ts +4 -0
  33. package/dist/react/hooks/__tests__/state-provider.spec.js +179 -0
  34. package/dist/react/hooks/__tests__/state-provider.spec.js.map +1 -0
  35. package/dist/react/hooks/__tests__/state-selector.spec.d.ts +4 -0
  36. package/dist/react/hooks/__tests__/state-selector.spec.js +499 -0
  37. package/dist/react/hooks/__tests__/state-selector.spec.js.map +1 -0
  38. package/dist/react/hooks/__tests__/state-singleton.spec.d.ts +4 -0
  39. package/dist/react/hooks/__tests__/state-singleton.spec.js +96 -0
  40. package/dist/react/hooks/__tests__/state-singleton.spec.js.map +1 -0
  41. package/{src/hooks/index.ts → dist/react/hooks/index.d.ts} +1 -0
  42. package/dist/react/hooks/index.js +7 -0
  43. package/dist/react/hooks/index.js.map +1 -0
  44. package/dist/react/hooks/state-actions.d.ts +2 -0
  45. package/dist/react/hooks/state-actions.js +5 -0
  46. package/dist/react/hooks/state-actions.js.map +1 -0
  47. package/dist/react/hooks/state-factory.d.ts +7 -0
  48. package/dist/react/hooks/state-factory.js +13 -0
  49. package/dist/react/hooks/state-factory.js.map +1 -0
  50. package/dist/react/hooks/state-handler.d.ts +2 -0
  51. package/dist/react/hooks/state-handler.js +9 -0
  52. package/dist/react/hooks/state-handler.js.map +1 -0
  53. package/dist/react/hooks/state-provider.d.ts +14 -0
  54. package/dist/react/hooks/state-provider.js +24 -0
  55. package/dist/react/hooks/state-provider.js.map +1 -0
  56. package/dist/react/hooks/state-singleton.d.ts +6 -0
  57. package/dist/react/hooks/state-singleton.js +7 -0
  58. package/dist/react/hooks/state-singleton.js.map +1 -0
  59. package/dist/react/hooks/state-subscription-selector.d.ts +3 -0
  60. package/dist/react/hooks/state-subscription-selector.js +63 -0
  61. package/dist/react/hooks/state-subscription-selector.js.map +1 -0
  62. package/dist/react/hooks/state-subscription.d.ts +9 -0
  63. package/dist/react/hooks/state-subscription.js +53 -0
  64. package/dist/react/hooks/state-subscription.js.map +1 -0
  65. package/dist/react/index.d.ts +1 -0
  66. package/dist/react/index.js +2 -0
  67. package/dist/react/index.js.map +1 -0
  68. package/dist/store/__tests__/observable-state-handler.spec.js +66 -11
  69. package/dist/store/__tests__/observable-state-handler.spec.js.map +1 -1
  70. package/dist/store/__tests__/signal-state-handler.spec.js.map +1 -1
  71. package/dist/store/base-state-handler.d.ts +4 -6
  72. package/dist/store/base-state-handler.js +10 -9
  73. package/dist/store/base-state-handler.js.map +1 -1
  74. package/dist/store/dev-tools.js +0 -3
  75. package/dist/store/dev-tools.js.map +1 -1
  76. package/dist/store/observable-state-handler.d.ts +4 -10
  77. package/dist/store/observable-state-handler.js +4 -11
  78. package/dist/store/observable-state-handler.js.map +1 -1
  79. package/dist/store/signal-state-handler.d.ts +2 -5
  80. package/dist/store/signal-state-handler.js +3 -2
  81. package/dist/store/signal-state-handler.js.map +1 -1
  82. package/dist/store/state-singleton.js +1 -1
  83. package/dist/store/state-singleton.js.map +1 -1
  84. package/eslint.config.mjs +75 -0
  85. package/package.json +18 -18
  86. package/src/config/status-quo-config.ts +31 -1
  87. package/src/index.ts +11 -15
  88. package/src/react/hooks/__tests__/state-provider.spec.tsx +286 -0
  89. package/src/{hooks → react/hooks}/__tests__/state-selector.spec.tsx +52 -44
  90. package/src/{hooks → react/hooks}/__tests__/state-singleton.spec.tsx +21 -20
  91. package/src/react/hooks/index.ts +11 -0
  92. package/src/{hooks → react/hooks}/state-actions.tsx +1 -1
  93. package/src/{hooks → react/hooks}/state-factory.tsx +2 -2
  94. package/src/{hooks → react/hooks}/state-handler.tsx +1 -1
  95. package/src/react/hooks/state-provider.tsx +56 -0
  96. package/src/{hooks → react/hooks}/state-singleton.tsx +1 -1
  97. package/src/{hooks → react/hooks}/state-subscription-selector.tsx +15 -6
  98. package/src/{hooks → react/hooks}/state-subscription.tsx +5 -9
  99. package/src/react/index.ts +1 -0
  100. package/src/store/__tests__/observable-state-handler.spec.ts +92 -13
  101. package/src/store/__tests__/signal-state-handler.spec.ts +5 -1
  102. package/src/store/base-state-handler.ts +17 -24
  103. package/src/store/dev-tools.ts +3 -3
  104. package/src/store/observable-state-handler.ts +12 -22
  105. package/src/store/signal-state-handler.ts +11 -8
  106. package/src/store/state-singleton.ts +1 -1
  107. package/tsconfig.json +2 -3
  108. package/.eslintrc.cjs +0 -132
  109. package/.github/workflows/pages.yml +0 -46
  110. package/.github/workflows/release.yml +0 -33
  111. package/.nvmrc +0 -1
  112. package/.prettierrc +0 -7
  113. package/docs/assets/index-BBmpszOW.css +0 -1
  114. package/docs/assets/index-Cf8El_RO.js +0 -194
  115. package/docs/assets/statusquo-logo-8GVRbxpc.png +0 -0
  116. package/docs/index.html +0 -13
  117. package/playground/index.html +0 -12
  118. package/playground/src/App.tsx +0 -699
  119. package/playground/src/assets/philosophy-agnostic.svg +0 -18
  120. package/playground/src/assets/philosophy-separation.svg +0 -13
  121. package/playground/src/assets/philosophy-swap.svg +0 -17
  122. package/playground/src/assets/statusquo-logo.png +0 -0
  123. package/playground/src/main.tsx +0 -19
  124. package/playground/src/styles.css +0 -534
  125. package/playground/tsconfig.json +0 -12
  126. package/playground/vite.config.ts +0 -18
@@ -1,21 +1,19 @@
1
- import React from 'react';
2
- import { act } from 'react';
1
+ import React, { act } from 'react';
3
2
  import { createRoot } from 'react-dom/client';
4
3
 
5
- import { makeStateSingleton } from '../../store/state-singleton.js';
6
-
4
+ import { makeStateSingleton } from '../../../store/state-singleton.js';
7
5
  import { useStateActions } from '../state-actions.js';
8
6
  import { useStateFactory } from '../state-factory.js';
9
7
  import { useStateHandler } from '../state-handler.js';
10
8
  import { useStateSingleton } from '../state-singleton.js';
11
9
  import { useStateSubscription } from '../state-subscription.js';
12
10
 
13
- import type { StateSingleton } from '../../store/state-singleton.js';
14
- import type { StateSubscriptionHandler } from '../../types/types.js';
11
+ import type { StateSingleton } from '../../../store/state-singleton.js';
12
+ import type { StateSubscriptionHandler } from '../../../types/types.js';
15
13
 
16
14
  declare global {
17
15
  // React 19 requires this flag in test environments that use manual act() calls.
18
- // eslint-disable-next-line no-var
16
+
19
17
  var IS_REACT_ACT_ENVIRONMENT: boolean;
20
18
  }
21
19
 
@@ -153,9 +151,10 @@ class CounterStateHandler implements StateSubscriptionHandler<CounterState, Coun
153
151
  };
154
152
  }
155
153
 
156
- class CounterMirrorStateHandler
157
- implements StateSubscriptionHandler<CounterMirrorState, CounterMirrorActions>
158
- {
154
+ class CounterMirrorStateHandler implements StateSubscriptionHandler<
155
+ CounterMirrorState,
156
+ CounterMirrorActions
157
+ > {
159
158
  private readonly initialState: CounterMirrorState;
160
159
  private state: CounterMirrorState;
161
160
  private readonly listeners = new Set<(value: CounterMirrorState) => void>();
@@ -173,13 +172,15 @@ class CounterMirrorStateHandler
173
172
  mirroredCount: initialCounterState.count,
174
173
  };
175
174
  this.state = this.initialState;
176
- this.unsubscribeFromCounter = counterStateHandler.subscribe((nextCounterState: CounterState) => {
177
- this.state = {
178
- mirroredCount: nextCounterState.count,
179
- };
180
- const nextMirrorState = this.state;
181
- this.listeners.forEach((listener) => listener(nextMirrorState));
182
- });
175
+ this.unsubscribeFromCounter = counterStateHandler.subscribe(
176
+ (nextCounterState: CounterState) => {
177
+ this.state = {
178
+ mirroredCount: nextCounterState.count,
179
+ };
180
+ const nextMirrorState = this.state;
181
+ this.listeners.forEach((listener) => listener(nextMirrorState));
182
+ }
183
+ );
183
184
  }
184
185
 
185
186
  subscribe(listener: () => void): () => void;
@@ -235,7 +236,11 @@ type ActionsOnlyConsumerProps = {
235
236
  onActionsReady: (actions: TestActions) => void;
236
237
  };
237
238
 
238
- const ActionsOnlyConsumer = ({ createStateHandler, onRender, onActionsReady }: ActionsOnlyConsumerProps) => {
239
+ const ActionsOnlyConsumer = ({
240
+ createStateHandler,
241
+ onRender,
242
+ onActionsReady,
243
+ }: ActionsOnlyConsumerProps) => {
239
244
  const stateHandler = useStateHandler(createStateHandler, []);
240
245
  const actions = useStateActions(stateHandler);
241
246
 
@@ -250,13 +255,11 @@ type FactoryShortcutConsumerProps = {
250
255
  onRender: (value: string) => void;
251
256
  };
252
257
 
253
- const FactoryShortcutConsumer = ({ createStateHandler, onRender }: FactoryShortcutConsumerProps) => {
254
- const [userName] = useStateFactory(
255
- createStateHandler,
256
- (state) => state.user.name,
257
- Object.is,
258
- []
259
- );
258
+ const FactoryShortcutConsumer = ({
259
+ createStateHandler,
260
+ onRender,
261
+ }: FactoryShortcutConsumerProps) => {
262
+ const [userName] = useStateFactory(createStateHandler, (state) => state.user.name, Object.is, []);
260
263
  onRender(userName);
261
264
 
262
265
  return <span>{userName}</span>;
@@ -409,7 +412,7 @@ describe('Selector hooks', () => {
409
412
  expect(createStateHandler).toHaveBeenCalledTimes(1);
410
413
 
411
414
  act(() => {
412
- root.render(<></>);
415
+ root.render(<React.Fragment />);
413
416
  });
414
417
 
415
418
  await act(async () => {
@@ -434,7 +437,7 @@ describe('Selector hooks', () => {
434
437
  return stateHandler;
435
438
  });
436
439
  const stateRenderSpy = jest.fn();
437
- const actionsReadySpy = jest.fn();
440
+ const actionsReadySpy = jest.fn<void, [TestActions]>();
438
441
 
439
442
  act(() => {
440
443
  root.render(
@@ -479,7 +482,7 @@ describe('Selector hooks', () => {
479
482
  return stateHandler;
480
483
  });
481
484
  const renderSpy = jest.fn();
482
- const actionsReadySpy = jest.fn();
485
+ const actionsReadySpy = jest.fn<void, [TestActions]>();
483
486
 
484
487
  act(() => {
485
488
  root.render(
@@ -491,7 +494,7 @@ describe('Selector hooks', () => {
491
494
  );
492
495
  });
493
496
 
494
- const actions = actionsReadySpy.mock.calls[0][0] as TestActions;
497
+ const [[actions]] = actionsReadySpy.mock.calls as [[TestActions]];
495
498
 
496
499
  act(() => {
497
500
  actions.increment();
@@ -517,7 +520,9 @@ describe('Selector hooks', () => {
517
520
  const renderSpy = jest.fn();
518
521
 
519
522
  act(() => {
520
- root.render(<FactoryShortcutConsumer createStateHandler={createStateHandler} onRender={renderSpy} />);
523
+ root.render(
524
+ <FactoryShortcutConsumer createStateHandler={createStateHandler} onRender={renderSpy} />
525
+ );
521
526
  });
522
527
 
523
528
  act(() => {
@@ -577,7 +582,7 @@ describe('Selector hooks', () => {
577
582
  return stateHandler;
578
583
  });
579
584
  const renderSpy = jest.fn();
580
- const actionsReadySpy = jest.fn();
585
+ const actionsReadySpy = jest.fn<void, [TestActions]>();
581
586
 
582
587
  act(() => {
583
588
  root.render(
@@ -594,7 +599,7 @@ describe('Selector hooks', () => {
594
599
  counter: 0,
595
600
  });
596
601
 
597
- const actions = actionsReadySpy.mock.calls[0][0] as TestActions;
602
+ const [[actions]] = actionsReadySpy.mock.calls as [[TestActions]];
598
603
 
599
604
  act(() => {
600
605
  actions.increment();
@@ -611,16 +616,18 @@ describe('Selector hooks', () => {
611
616
  user: { name: 'Ada' },
612
617
  counter: 0,
613
618
  });
614
- const singleton = makeStateSingleton(() => stateHandler);
619
+ const singleton = makeStateSingleton(() => stateHandler, {
620
+ destroyOnNoConsumers: true,
621
+ });
615
622
  const firstRenderSpy = jest.fn();
616
623
  const secondRenderSpy = jest.fn();
617
624
 
618
625
  act(() => {
619
626
  root.render(
620
- <>
627
+ <React.Fragment>
621
628
  <SingletonShortcutConsumer singleton={singleton} onRender={firstRenderSpy} />
622
629
  <SingletonShortcutConsumer singleton={singleton} onRender={secondRenderSpy} />
623
- </>
630
+ </React.Fragment>
624
631
  );
625
632
  });
626
633
 
@@ -645,7 +652,7 @@ describe('Selector hooks', () => {
645
652
  expect(stateHandler.destroy).not.toHaveBeenCalled();
646
653
 
647
654
  act(() => {
648
- root.render(<></>);
655
+ root.render(<React.Fragment />);
649
656
  });
650
657
 
651
658
  expect(stateHandler.destroy).toHaveBeenCalledTimes(1);
@@ -683,7 +690,7 @@ describe('Selector hooks', () => {
683
690
  });
684
691
  const singleton = makeStateSingleton(() => stateHandler);
685
692
  const renderSpy = jest.fn();
686
- const actionsReadySpy = jest.fn();
693
+ const actionsReadySpy = jest.fn<void, [TestActions]>();
687
694
 
688
695
  act(() => {
689
696
  root.render(
@@ -695,7 +702,7 @@ describe('Selector hooks', () => {
695
702
  );
696
703
  });
697
704
 
698
- const actions = actionsReadySpy.mock.calls[0][0] as TestActions;
705
+ const [[actions]] = actionsReadySpy.mock.calls as [[TestActions]];
699
706
 
700
707
  act(() => {
701
708
  actions.increment();
@@ -710,7 +717,7 @@ describe('Selector hooks', () => {
710
717
  expect(renderSpy).toHaveBeenCalledTimes(2);
711
718
  });
712
719
 
713
- it('useStateSingleton selector should respect destroyOnNoConsumers false', () => {
720
+ it('useStateSingleton selector should keep the singleton instance alive by default', () => {
714
721
  const firstHandler = new TestStateHandler({
715
722
  user: { name: 'Ada' },
716
723
  counter: 0,
@@ -723,9 +730,7 @@ describe('Selector hooks', () => {
723
730
  .fn(() => firstHandler as StateSubscriptionHandler<TestState, TestActions>)
724
731
  .mockReturnValueOnce(firstHandler)
725
732
  .mockReturnValueOnce(secondHandler);
726
- const singleton = makeStateSingleton<TestState, TestActions>(createStateHandler, {
727
- destroyOnNoConsumers: false,
728
- });
733
+ const singleton = makeStateSingleton<TestState, TestActions>(createStateHandler);
729
734
  const renderSpy = jest.fn();
730
735
 
731
736
  act(() => {
@@ -733,7 +738,7 @@ describe('Selector hooks', () => {
733
738
  });
734
739
 
735
740
  act(() => {
736
- root.render(<></>);
741
+ root.render(<React.Fragment />);
737
742
  });
738
743
 
739
744
  expect(firstHandler.destroy).not.toHaveBeenCalled();
@@ -764,7 +769,10 @@ describe('Selector hooks', () => {
764
769
  act(() => {
765
770
  root.render(
766
771
  <React.StrictMode>
767
- <StrictModeMirrorFactoryConsumer createStateHandler={createStateHandler} onRender={renderSpy} />
772
+ <StrictModeMirrorFactoryConsumer
773
+ createStateHandler={createStateHandler}
774
+ onRender={renderSpy}
775
+ />
768
776
  </React.StrictMode>
769
777
  );
770
778
  });
@@ -1,16 +1,14 @@
1
- import React from 'react';
2
- import { act } from 'react';
1
+ import React, { act } from 'react';
3
2
  import { createRoot } from 'react-dom/client';
4
3
 
4
+ import { makeStateSingleton } from '../../../store';
5
5
  import { useStateSingleton } from '../state-singleton.js';
6
6
 
7
- import { makeStateSingleton } from '../../store';
8
-
9
- import type { StateSubscriptionHandler } from '../../types/types.js';
7
+ import type { StateSubscriptionHandler } from '../../../types/types.js';
10
8
 
11
9
  declare global {
12
10
  // React 19 requires this flag in test environments that use manual act() calls.
13
- // eslint-disable-next-line no-var
11
+
14
12
  var IS_REACT_ACT_ENVIRONMENT: boolean;
15
13
  }
16
14
 
@@ -39,7 +37,11 @@ const createStateHandler = (value: number): TestHandler => {
39
37
  };
40
38
  };
41
39
 
42
- const SingletonConsumer = ({ singleton }: { singleton: ReturnType<typeof makeStateSingleton<TestState, TestActions>> }) => {
40
+ const SingletonConsumer = ({
41
+ singleton,
42
+ }: {
43
+ singleton: ReturnType<typeof makeStateSingleton<TestState, TestActions>>;
44
+ }) => {
43
45
  useStateSingleton(singleton);
44
46
 
45
47
  return null;
@@ -77,10 +79,10 @@ describe('useStateSingleton', () => {
77
79
 
78
80
  act(() => {
79
81
  root.render(
80
- <>
82
+ <React.Fragment>
81
83
  <SingletonConsumer singleton={singleton} />
82
84
  <SingletonConsumer singleton={singleton} />
83
- </>
85
+ </React.Fragment>
84
86
  );
85
87
  });
86
88
 
@@ -91,27 +93,28 @@ describe('useStateSingleton', () => {
91
93
  expect(firstHandler.destroy).not.toHaveBeenCalled();
92
94
 
93
95
  act(() => {
94
- root.render(<></>);
96
+ root.render(<React.Fragment />);
95
97
  });
96
98
 
97
- expect(firstHandler.destroy).toHaveBeenCalledTimes(1);
99
+ expect(firstHandler.destroy).not.toHaveBeenCalled();
98
100
  });
99
101
 
100
- it('should create a new singleton instance after all consumers unmount', () => {
102
+ it('should create a new singleton instance after all consumers unmount when destroyOnNoConsumers is true', () => {
101
103
  const firstHandler = createStateHandler(1);
102
104
  const secondHandler = createStateHandler(2);
103
105
  const factory = jest.fn(() => firstHandler as StateSubscriptionHandler<TestState, TestActions>);
104
106
 
105
107
  factory.mockReturnValueOnce(firstHandler).mockReturnValueOnce(secondHandler);
106
-
107
- const singleton = makeStateSingleton<TestState, TestActions>(factory);
108
+ const singleton = makeStateSingleton<TestState, TestActions>(factory, {
109
+ destroyOnNoConsumers: true,
110
+ });
108
111
 
109
112
  act(() => {
110
113
  root.render(<SingletonConsumer singleton={singleton} />);
111
114
  });
112
115
 
113
116
  act(() => {
114
- root.render(<></>);
117
+ root.render(<React.Fragment />);
115
118
  });
116
119
 
117
120
  expect(firstHandler.destroy).toHaveBeenCalledTimes(1);
@@ -124,19 +127,17 @@ describe('useStateSingleton', () => {
124
127
  expect(singleton.getInstance().getSnapshot()).toStrictEqual({ value: 2 });
125
128
  });
126
129
 
127
- it('should keep singleton instance alive when destroyOnNoConsumers is false', () => {
130
+ it('should keep singleton instance alive by default', () => {
128
131
  const firstHandler = createStateHandler(1);
129
132
  const factory = jest.fn(() => firstHandler as StateSubscriptionHandler<TestState, TestActions>);
130
- const singleton = makeStateSingleton<TestState, TestActions>(factory, {
131
- destroyOnNoConsumers: false,
132
- });
133
+ const singleton = makeStateSingleton<TestState, TestActions>(factory);
133
134
 
134
135
  act(() => {
135
136
  root.render(<SingletonConsumer singleton={singleton} />);
136
137
  });
137
138
 
138
139
  act(() => {
139
- root.render(<></>);
140
+ root.render(<React.Fragment />);
140
141
  });
141
142
 
142
143
  expect(firstHandler.destroy).not.toHaveBeenCalled();
@@ -0,0 +1,11 @@
1
+ export { useStateActions } from './state-actions.js';
2
+ export { useStateFactory } from './state-factory.js';
3
+ export { useStateHandler } from './state-handler.js';
4
+ export {
5
+ StateProvider,
6
+ useProvidedStateActions,
7
+ useProvidedStateHandler,
8
+ useProvidedStateSubscription,
9
+ } from './state-provider.js';
10
+ export { useStateSingleton } from './state-singleton.js';
11
+ export { useStateSubscription } from './state-subscription.js';
@@ -1,6 +1,6 @@
1
1
  import { useMemo } from 'react';
2
2
 
3
- import type { StateSubscriptionHandler } from '../types/types.js';
3
+ import type { StateSubscriptionHandler } from '../../types/types.js';
4
4
 
5
5
  export function useStateActions<V, A>(stateSubscriptionHandler: StateSubscriptionHandler<V, A>) {
6
6
  return useMemo(() => stateSubscriptionHandler.getActions(), [stateSubscriptionHandler]);
@@ -1,7 +1,7 @@
1
1
  import { useStateHandler } from './state-handler.js';
2
2
  import { useStateSubscription } from './state-subscription.js';
3
3
 
4
- import type { StateSubscriptionHandler } from '../types/types.js';
4
+ import type { StateSubscriptionHandler } from '../../types/types.js';
5
5
 
6
6
  type StateSelector<State, SelectedState> = (state: State) => SelectedState;
7
7
  type EqualityFn<SelectedState> = (current: SelectedState, next: SelectedState) => boolean;
@@ -32,7 +32,7 @@ export function useStateFactory<V, A, P extends unknown[], Sel = V>(
32
32
  const hasSelector = typeof selectorOrParams === 'function';
33
33
  const selector = (hasSelector ? selectorOrParams : identitySelector) as StateSelector<V, Sel>;
34
34
  const hasCustomEquality = hasSelector && typeof isEqualOrParams === 'function';
35
- const isEqual = (hasCustomEquality ? isEqualOrParams : Object.is) as EqualityFn<Sel>;
35
+ const isEqual = (hasCustomEquality ? isEqualOrParams : Object.is);
36
36
  const stateFactoryParams = (
37
37
  hasSelector ? (hasCustomEquality ? params : isEqualOrParams) : selectorOrParams
38
38
  ) as P;
@@ -1,6 +1,6 @@
1
1
  import { useRef } from 'react';
2
2
 
3
- import type { StateSubscriptionHandler } from '../types/types.js';
3
+ import type { StateSubscriptionHandler } from '../../types/types.js';
4
4
 
5
5
  export function useStateHandler<V, A, P extends unknown[]>(
6
6
  stateFactoryFunction: (...args: P) => StateSubscriptionHandler<V, A>,
@@ -0,0 +1,56 @@
1
+ import React, { createContext, useContext } from 'react';
2
+
3
+ import { useStateActions } from './state-actions.js';
4
+ import { useStateSubscription } from './state-subscription.js';
5
+
6
+ import type { PropsWithChildren } from 'react';
7
+ import type { StateSubscriptionHandler } from '../../types/types.js';
8
+
9
+ type StateSelector<State, SelectedState> = (state: State) => SelectedState;
10
+ type EqualityFn<SelectedState> = (current: SelectedState, next: SelectedState) => boolean;
11
+ type SharedStateHandler = StateSubscriptionHandler<unknown, unknown>;
12
+
13
+ const StateProviderContext = createContext<SharedStateHandler | null>(null);
14
+ const identitySelector = <State,>(state: State) => state;
15
+
16
+ export type StateProviderProps<V, A> = PropsWithChildren<{
17
+ instance: StateSubscriptionHandler<V, A>;
18
+ }>;
19
+
20
+ export function useProvidedStateHandler<V, A>() {
21
+ const stateHandler = useContext(StateProviderContext);
22
+
23
+ if (!stateHandler) {
24
+ throw new Error('No StateProvider instance found in the current React tree.');
25
+ }
26
+
27
+ return stateHandler as StateSubscriptionHandler<V, A>;
28
+ }
29
+
30
+ export function StateProvider<V, A>({ children, instance }: StateProviderProps<V, A>) {
31
+ return (
32
+ <StateProviderContext.Provider value={instance as SharedStateHandler}>
33
+ {children}
34
+ </StateProviderContext.Provider>
35
+ );
36
+ }
37
+
38
+ export function useProvidedStateActions<V, A>() {
39
+ const stateHandler = useProvidedStateHandler<V, A>();
40
+
41
+ return useStateActions(stateHandler);
42
+ }
43
+
44
+ export function useProvidedStateSubscription<V, A>(): [V, A];
45
+ export function useProvidedStateSubscription<V, A, Sel>(
46
+ selector: StateSelector<V, Sel>,
47
+ isEqual?: EqualityFn<Sel>
48
+ ): [Sel, A];
49
+ export function useProvidedStateSubscription<V, A, Sel = V>(
50
+ selector: StateSelector<V, Sel> = identitySelector as StateSelector<V, Sel>,
51
+ isEqual: EqualityFn<Sel> = Object.is
52
+ ) {
53
+ const stateHandler = useProvidedStateHandler<V, A>();
54
+
55
+ return useStateSubscription(stateHandler, selector, isEqual);
56
+ }
@@ -1,6 +1,6 @@
1
1
  import { useStateSubscription } from './state-subscription.js';
2
2
 
3
- import type { StateSingleton } from '../store/state-singleton.js';
3
+ import type { StateSingleton } from '../../store/state-singleton.js';
4
4
 
5
5
  type StateSelector<State, SelectedState> = (state: State) => SelectedState;
6
6
  type EqualityFn<SelectedState> = (current: SelectedState, next: SelectedState) => boolean;
@@ -1,8 +1,9 @@
1
- import { useCallback, useMemo, useSyncExternalStore } from 'react';
2
- import { createSelectorCache, selectWithCache } from '../utils/selector-cache.js';
1
+ import { useCallback, useRef, useSyncExternalStore } from 'react';
3
2
 
4
- import type { StateSubscriptionHandler } from '../types/types.js';
5
- import type { EqualityFn, Selector } from '../utils/selector-cache.js';
3
+ import { createSelectorCache, selectWithCache } from '../../utils/selector-cache.js';
4
+
5
+ import type { StateSubscriptionHandler } from '../../types/types.js';
6
+ import type { EqualityFn, Selector } from '../../utils/selector-cache.js';
6
7
 
7
8
  type Listener = () => void;
8
9
  type SharedStateSubscriptionHandler = StateSubscriptionHandler<unknown, unknown>;
@@ -38,7 +39,13 @@ export function useStateSubscriptionSelector<V, A, Sel>(
38
39
  isEqual: EqualityFn<Sel> = Object.is,
39
40
  destroyOnCleanup = true
40
41
  ) {
41
- const selectorCache = useMemo(() => createSelectorCache<Sel>(), [stateSubscriptionHandler]);
42
+ const selectorCacheRef = useRef<ReturnType<typeof createSelectorCache<Sel>> | null>(null);
43
+
44
+ if (!selectorCacheRef.current) {
45
+ selectorCacheRef.current = createSelectorCache<Sel>();
46
+ }
47
+
48
+ const selectorCache = selectorCacheRef.current;
42
49
 
43
50
  const subscribe = useCallback(
44
51
  (listener: Listener) => {
@@ -75,7 +82,9 @@ export function useStateSubscriptionSelector<V, A, Sel>(
75
82
 
76
83
  activeDeferredDestroyState.refCount = 0;
77
84
  activeDeferredDestroyState.timeoutId = setTimeout(() => {
78
- const pendingDeferredDestroyState = deferredDestroyMap.get(sharedStateSubscriptionHandler);
85
+ const pendingDeferredDestroyState = deferredDestroyMap.get(
86
+ sharedStateSubscriptionHandler
87
+ );
79
88
 
80
89
  if (!pendingDeferredDestroyState || pendingDeferredDestroyState.refCount > 0) {
81
90
  return;
@@ -3,8 +3,8 @@ import { useEffect, useMemo } from 'react';
3
3
  import { useStateActions } from './state-actions.js';
4
4
  import { useStateSubscriptionSelector } from './state-subscription-selector.js';
5
5
 
6
- import type { StateSingleton } from '../store/state-singleton.js';
7
- import type { StateSubscriptionHandler } from '../types/types.js';
6
+ import type { StateSingleton } from '../../store/state-singleton.js';
7
+ import type { StateSubscriptionHandler } from '../../types/types.js';
8
8
 
9
9
  type StateSelector<State, SelectedState> = (state: State) => SelectedState;
10
10
  type EqualityFn<SelectedState> = (current: SelectedState, next: SelectedState) => boolean;
@@ -27,17 +27,13 @@ function isStateSingleton<V, A>(
27
27
  return 'getInstance' in source;
28
28
  }
29
29
 
30
- export function useStateSubscription<V, A>(
31
- source: StateSubscriptionHandler<V, A>
32
- ): [V, A];
30
+ export function useStateSubscription<V, A>(source: StateSubscriptionHandler<V, A>): [V, A];
33
31
  export function useStateSubscription<V, A, Sel>(
34
32
  source: StateSubscriptionHandler<V, A>,
35
33
  selector: StateSelector<V, Sel>,
36
34
  isEqual?: EqualityFn<Sel>
37
35
  ): [Sel, A];
38
- export function useStateSubscription<V, A>(
39
- source: StateSingleton<V, A>
40
- ): [V, A];
36
+ export function useStateSubscription<V, A>(source: StateSingleton<V, A>): [V, A];
41
37
  export function useStateSubscription<V, A, Sel>(
42
38
  source: StateSingleton<V, A>,
43
39
  selector: StateSelector<V, Sel>,
@@ -83,7 +79,7 @@ export function useStateSubscription<V, A, Sel = V>(
83
79
 
84
80
  if (activeReference.count <= 0) {
85
81
  singletonReferences.delete(singleton);
86
- if (singleton.destroyOnNoConsumers === false) {
82
+ if (singleton.destroyOnNoConsumers !== true) {
87
83
  return;
88
84
  }
89
85
 
@@ -0,0 +1 @@
1
+ export * from './hooks/index.js';