@okyrychenko-dev/react-action-guard 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -204,26 +204,6 @@ interface ShallowStoreBindings<StoreState> {
204
204
  useStoreApi: StoreApi<StoreState>;
205
205
  }
206
206
 
207
- declare const uiBlockingStoreApi: Omit<zustand.StoreApi<UIBlockingStore>, "setState" | "devtools"> & {
208
- setState(partial: UIBlockingStore | Partial<UIBlockingStore> | ((state: UIBlockingStore) => UIBlockingStore | Partial<UIBlockingStore>), replace?: false | undefined, action?: (string | {
209
- [x: string]: unknown;
210
- [x: number]: unknown;
211
- [x: symbol]: unknown;
212
- type: string;
213
- }) | undefined): void;
214
- setState(state: UIBlockingStore | ((state: UIBlockingStore) => UIBlockingStore), replace: true, action?: (string | {
215
- [x: string]: unknown;
216
- [x: number]: unknown;
217
- [x: symbol]: unknown;
218
- type: string;
219
- }) | undefined): void;
220
- devtools: {
221
- cleanup: () => void;
222
- };
223
- };
224
- declare function useUIBlockingStore(): UIBlockingStore;
225
- declare function useUIBlockingStore<T>(selector: (state: UIBlockingStore) => T, equalityFn?: (a: T, b: T) => boolean): T;
226
-
227
207
  type BlockingAction = "add" | "update" | "remove" | "timeout" | "clear" | "clear_scope";
228
208
  type MiddlewareBlockerConfig = BlockerConfig;
229
209
  interface MiddlewareContext {
@@ -379,7 +359,7 @@ declare function configureMiddleware(middlewares: ReadonlyArray<Middleware>): vo
379
359
  *
380
360
  * Logs every blocker action (add, remove, update, timeout, clear) with emoji
381
361
  * indicators and formatted data. Useful for debugging blocking behavior during
382
- * development. Automatically disabled in production builds for performance.
362
+ * development. Register it conditionally when production logging is not desired.
383
363
  *
384
364
  * Output format: `[UIBlocking] 🔒 blocker-id {scope: "form", reason: "..."}`
385
365
  *
@@ -798,6 +778,26 @@ interface UseAsyncActionOptions {
798
778
  */
799
779
  declare function useAsyncAction<T = unknown>(actionId: string, scope?: string | ReadonlyArray<string>, options?: UseAsyncActionOptions): (asyncFn: () => Promise<T>) => Promise<T>;
800
780
 
781
+ declare const uiBlockingStoreApi: Omit<zustand.StoreApi<UIBlockingStore>, "setState" | "devtools"> & {
782
+ setState(partial: UIBlockingStore | Partial<UIBlockingStore> | ((state: UIBlockingStore) => UIBlockingStore | Partial<UIBlockingStore>), replace?: false | undefined, action?: (string | {
783
+ [x: string]: unknown;
784
+ [x: number]: unknown;
785
+ [x: symbol]: unknown;
786
+ type: string;
787
+ }) | undefined): void;
788
+ setState(state: UIBlockingStore | ((state: UIBlockingStore) => UIBlockingStore), replace: true, action?: (string | {
789
+ [x: string]: unknown;
790
+ [x: number]: unknown;
791
+ [x: symbol]: unknown;
792
+ type: string;
793
+ }) | undefined): void;
794
+ devtools: {
795
+ cleanup: () => void;
796
+ };
797
+ };
798
+ declare function useUIBlockingStore(): UIBlockingStore;
799
+ declare function useUIBlockingStore<T>(selector: (state: UIBlockingStore) => T, equalityFn?: (a: T, b: T) => boolean): T;
800
+
801
801
  /**
802
802
  * Automatically manages a UI blocker based on component lifecycle.
803
803
  *
package/dist/index.d.ts CHANGED
@@ -204,26 +204,6 @@ interface ShallowStoreBindings<StoreState> {
204
204
  useStoreApi: StoreApi<StoreState>;
205
205
  }
206
206
 
207
- declare const uiBlockingStoreApi: Omit<zustand.StoreApi<UIBlockingStore>, "setState" | "devtools"> & {
208
- setState(partial: UIBlockingStore | Partial<UIBlockingStore> | ((state: UIBlockingStore) => UIBlockingStore | Partial<UIBlockingStore>), replace?: false | undefined, action?: (string | {
209
- [x: string]: unknown;
210
- [x: number]: unknown;
211
- [x: symbol]: unknown;
212
- type: string;
213
- }) | undefined): void;
214
- setState(state: UIBlockingStore | ((state: UIBlockingStore) => UIBlockingStore), replace: true, action?: (string | {
215
- [x: string]: unknown;
216
- [x: number]: unknown;
217
- [x: symbol]: unknown;
218
- type: string;
219
- }) | undefined): void;
220
- devtools: {
221
- cleanup: () => void;
222
- };
223
- };
224
- declare function useUIBlockingStore(): UIBlockingStore;
225
- declare function useUIBlockingStore<T>(selector: (state: UIBlockingStore) => T, equalityFn?: (a: T, b: T) => boolean): T;
226
-
227
207
  type BlockingAction = "add" | "update" | "remove" | "timeout" | "clear" | "clear_scope";
228
208
  type MiddlewareBlockerConfig = BlockerConfig;
229
209
  interface MiddlewareContext {
@@ -379,7 +359,7 @@ declare function configureMiddleware(middlewares: ReadonlyArray<Middleware>): vo
379
359
  *
380
360
  * Logs every blocker action (add, remove, update, timeout, clear) with emoji
381
361
  * indicators and formatted data. Useful for debugging blocking behavior during
382
- * development. Automatically disabled in production builds for performance.
362
+ * development. Register it conditionally when production logging is not desired.
383
363
  *
384
364
  * Output format: `[UIBlocking] 🔒 blocker-id {scope: "form", reason: "..."}`
385
365
  *
@@ -798,6 +778,26 @@ interface UseAsyncActionOptions {
798
778
  */
799
779
  declare function useAsyncAction<T = unknown>(actionId: string, scope?: string | ReadonlyArray<string>, options?: UseAsyncActionOptions): (asyncFn: () => Promise<T>) => Promise<T>;
800
780
 
781
+ declare const uiBlockingStoreApi: Omit<zustand.StoreApi<UIBlockingStore>, "setState" | "devtools"> & {
782
+ setState(partial: UIBlockingStore | Partial<UIBlockingStore> | ((state: UIBlockingStore) => UIBlockingStore | Partial<UIBlockingStore>), replace?: false | undefined, action?: (string | {
783
+ [x: string]: unknown;
784
+ [x: number]: unknown;
785
+ [x: symbol]: unknown;
786
+ type: string;
787
+ }) | undefined): void;
788
+ setState(state: UIBlockingStore | ((state: UIBlockingStore) => UIBlockingStore), replace: true, action?: (string | {
789
+ [x: string]: unknown;
790
+ [x: number]: unknown;
791
+ [x: symbol]: unknown;
792
+ type: string;
793
+ }) | undefined): void;
794
+ devtools: {
795
+ cleanup: () => void;
796
+ };
797
+ };
798
+ declare function useUIBlockingStore(): UIBlockingStore;
799
+ declare function useUIBlockingStore<T>(selector: (state: UIBlockingStore) => T, equalityFn?: (a: T, b: T) => boolean): T;
800
+
801
801
  /**
802
802
  * Automatically manages a UI blocker based on component lifecycle.
803
803
  *
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import 'zustand/react/shallow';
4
4
  import { jsx } from 'react/jsx-runtime';
5
5
  import { useShallow, shallow } from 'zustand/shallow';
6
6
  import { devtools } from 'zustand/middleware';
7
- import { useRef, useCallback, useEffect, useMemo, useState } from 'react';
7
+ import { useCallback, useRef, useMemo, useEffect, useState } from 'react';
8
8
 
9
9
  // src/context/UIBlockingContext.tsx
10
10
 
@@ -34,6 +34,16 @@ function createMiddlewareContext(action, blockerId, config, prevState) {
34
34
  ...prevState && { prevState }
35
35
  };
36
36
  }
37
+ function toPublicBlockerConfig(blocker) {
38
+ return {
39
+ scope: blocker.scope,
40
+ reason: blocker.reason,
41
+ priority: blocker.priority,
42
+ timestamp: blocker.timestamp,
43
+ timeout: blocker.timeout,
44
+ onTimeout: blocker.onTimeout
45
+ };
46
+ }
37
47
  var createUIBlockingActions = (set, get) => ({
38
48
  // State
39
49
  activeBlockers: /* @__PURE__ */ new Map(),
@@ -104,14 +114,15 @@ var createUIBlockingActions = (set, get) => ({
104
114
  let timeoutId;
105
115
  if (config.timeout && config.timeout > 0) {
106
116
  timeoutId = setTimeout(() => {
107
- if (get().activeBlockers.has(id)) {
108
- config.onTimeout?.(id);
117
+ const blocker = get().activeBlockers.get(id);
118
+ if (blocker) {
119
+ blocker.onTimeout?.(id);
109
120
  void get().runMiddlewares(
110
121
  createMiddlewareContext("timeout", id, {
111
- scope: config.scope,
112
- reason: config.reason,
113
- priority: config.priority,
114
- timeout: config.timeout
122
+ scope: blocker.scope,
123
+ reason: blocker.reason,
124
+ priority: blocker.priority,
125
+ timeout: blocker.timeout
115
126
  })
116
127
  );
117
128
  get().removeBlocker(id);
@@ -182,16 +193,16 @@ var createUIBlockingActions = (set, get) => ({
182
193
  }
183
194
  if (config.timeout && config.timeout > 0) {
184
195
  const newTimeout = config.timeout;
185
- const newOnTimeout = config.onTimeout ?? existingBlocker.onTimeout;
186
196
  timeoutId = setTimeout(() => {
187
- if (get().activeBlockers.has(id)) {
188
- newOnTimeout?.(id);
197
+ const blocker = get().activeBlockers.get(id);
198
+ if (blocker) {
199
+ blocker.onTimeout?.(id);
189
200
  void get().runMiddlewares(
190
201
  createMiddlewareContext("timeout", id, {
191
- scope: config.scope ?? existingBlocker.scope,
192
- reason: config.reason ?? existingBlocker.reason,
193
- priority: config.priority ?? existingBlocker.priority,
194
- timeout: newTimeout
202
+ scope: blocker.scope,
203
+ reason: blocker.reason,
204
+ priority: blocker.priority,
205
+ timeout: blocker.timeout
195
206
  })
196
207
  );
197
208
  get().removeBlocker(id);
@@ -269,7 +280,8 @@ var createUIBlockingActions = (set, get) => ({
269
280
  return { activeBlockers: newBlockers };
270
281
  });
271
282
  if (prevBlocker) {
272
- void get().runMiddlewares(createMiddlewareContext("remove", id, prevBlocker, prevBlocker));
283
+ const publicConfig = toPublicBlockerConfig(prevBlocker);
284
+ void get().runMiddlewares(createMiddlewareContext("remove", id, publicConfig, publicConfig));
273
285
  }
274
286
  },
275
287
  /**
@@ -501,16 +513,36 @@ function useResolvedValue(selector) {
501
513
  const store = useResolvedStoreApi();
502
514
  return useStore(store, useShallow(selector));
503
515
  }
516
+
517
+ // src/store/blockerId.utils.ts
518
+ var storeSequences = /* @__PURE__ */ new WeakMap();
519
+ function allocateBlockerId(store, prefix) {
520
+ let sequence = storeSequences.get(store) ?? 0;
521
+ let blockerId;
522
+ do {
523
+ sequence += 1;
524
+ blockerId = `${prefix}-${String(sequence)}`;
525
+ } while (store.getState().activeBlockers.has(blockerId));
526
+ storeSequences.set(store, sequence);
527
+ return blockerId;
528
+ }
529
+
530
+ // src/hooks/useBlockerIdAllocator/useBlockerIdAllocator.ts
531
+ function useBlockerIdAllocator() {
532
+ const store = useResolvedStoreApi();
533
+ return useCallback((prefix) => allocateBlockerId(store, prefix), [store]);
534
+ }
535
+
536
+ // src/hooks/useAsyncAction/useAsyncAction.ts
504
537
  function useAsyncAction(actionId, scope, options) {
538
+ const allocateBlockerId2 = useBlockerIdAllocator();
505
539
  const { addBlocker, removeBlocker } = useResolvedValue((state) => ({
506
540
  addBlocker: state.addBlocker,
507
541
  removeBlocker: state.removeBlocker
508
542
  }));
509
- const counterRef = useRef(0);
510
543
  const executeWithBlocking = useCallback(
511
544
  async (asyncFn) => {
512
- counterRef.current += 1;
513
- const blockerId = `${actionId}-${String(counterRef.current)}`;
545
+ const blockerId = allocateBlockerId2(actionId);
514
546
  try {
515
547
  addBlocker(blockerId, {
516
548
  scope,
@@ -524,7 +556,15 @@ function useAsyncAction(actionId, scope, options) {
524
556
  removeBlocker(blockerId);
525
557
  }
526
558
  },
527
- [actionId, scope, options?.timeout, options?.onTimeout, addBlocker, removeBlocker]
559
+ [
560
+ actionId,
561
+ scope,
562
+ options?.timeout,
563
+ options?.onTimeout,
564
+ allocateBlockerId2,
565
+ addBlocker,
566
+ removeBlocker
567
+ ]
528
568
  );
529
569
  return executeWithBlocking;
530
570
  }
@@ -569,12 +609,24 @@ function useBlocker(blockerId, config, isActive = true) {
569
609
  updateBlocker: state.updateBlocker
570
610
  }));
571
611
  const lastConfigRef = useRef(null);
612
+ const onTimeoutRef = useRef(config.onTimeout);
613
+ onTimeoutRef.current = config.onTimeout;
614
+ const handleTimeout = useCallback((id) => {
615
+ onTimeoutRef.current?.(id);
616
+ }, []);
617
+ const storeConfig = useMemo(
618
+ () => ({
619
+ ...config,
620
+ onTimeout: config.onTimeout ? handleTimeout : void 0
621
+ }),
622
+ [config, handleTimeout]
623
+ );
572
624
  useEffect(() => {
573
625
  if (!isActive || !blockerId) {
574
626
  return;
575
627
  }
576
- addBlocker(blockerId, config);
577
- lastConfigRef.current = config;
628
+ addBlocker(blockerId, storeConfig);
629
+ lastConfigRef.current = storeConfig;
578
630
  return () => {
579
631
  lastConfigRef.current = null;
580
632
  removeBlocker(blockerId);
@@ -584,12 +636,12 @@ function useBlocker(blockerId, config, isActive = true) {
584
636
  if (!isActive || !blockerId) {
585
637
  return;
586
638
  }
587
- if (lastConfigRef.current !== null && areBlockerConfigsEqual(lastConfigRef.current, config)) {
639
+ if (lastConfigRef.current !== null && areBlockerConfigsEqual(lastConfigRef.current, storeConfig)) {
588
640
  return;
589
641
  }
590
- updateBlocker(blockerId, config);
591
- lastConfigRef.current = config;
592
- }, [blockerId, config, isActive, updateBlocker]);
642
+ updateBlocker(blockerId, storeConfig);
643
+ lastConfigRef.current = storeConfig;
644
+ }, [blockerId, storeConfig, isActive, updateBlocker]);
593
645
  }
594
646
  function useBlockingInfo(scope = DEFAULT_SCOPE) {
595
647
  const store = useResolvedStoreApi();
@@ -607,6 +659,7 @@ function useConfigRef(config) {
607
659
  }
608
660
 
609
661
  // src/hooks/useConditionalBlocker/useConditionalBlocker.ts
662
+ var DEFAULT_CHECK_INTERVAL = 1e3;
610
663
  function useConditionalBlocker(blockerId, config) {
611
664
  const { addBlocker, removeBlocker } = useResolvedValue((state) => ({
612
665
  addBlocker: state.addBlocker,
@@ -615,6 +668,7 @@ function useConditionalBlocker(blockerId, config) {
615
668
  const intervalRef = useRef(null);
616
669
  const isBlockedRef = useRef(false);
617
670
  const configRef = useConfigRef(config);
671
+ const checkInterval = config.checkInterval !== void 0 && config.checkInterval > 0 ? config.checkInterval : DEFAULT_CHECK_INTERVAL;
618
672
  const checkCondition = useCallback(() => {
619
673
  const currentConfig = configRef.current;
620
674
  const shouldBlock = currentConfig.condition(currentConfig.state);
@@ -630,8 +684,7 @@ function useConditionalBlocker(blockerId, config) {
630
684
  }, [blockerId, addBlocker, removeBlocker, configRef]);
631
685
  useEffect(() => {
632
686
  checkCondition();
633
- const interval = configRef.current.checkInterval ?? 1e3;
634
- intervalRef.current = setInterval(checkCondition, interval);
687
+ intervalRef.current = setInterval(checkCondition, checkInterval);
635
688
  return () => {
636
689
  if (intervalRef.current) {
637
690
  clearInterval(intervalRef.current);
@@ -642,7 +695,7 @@ function useConditionalBlocker(blockerId, config) {
642
695
  isBlockedRef.current = false;
643
696
  }
644
697
  };
645
- }, [checkCondition, blockerId, removeBlocker]);
698
+ }, [checkCondition, blockerId, removeBlocker, checkInterval]);
646
699
  }
647
700
  function useConfirmableBlocker(blockerId, config) {
648
701
  const [isDialogOpen, setIsDialogOpen] = useState(false);
@@ -736,6 +789,9 @@ function useScheduledBlocker(blockerId, config) {
736
789
  }));
737
790
  const timeoutsRef = useRef({});
738
791
  const configRef = useConfigRef(config);
792
+ const scheduleStart = parseDate(config.schedule.start);
793
+ const scheduleEnd = config.schedule.end === void 0 ? void 0 : parseDate(config.schedule.end);
794
+ const scheduleDuration = config.schedule.duration;
739
795
  const cleanup = useCallback(() => {
740
796
  if (timeoutsRef.current.start) {
741
797
  clearTimeout(timeoutsRef.current.start);
@@ -753,7 +809,7 @@ function useScheduledBlocker(blockerId, config) {
753
809
  if (!isValidTimestamp(startTime)) {
754
810
  return;
755
811
  }
756
- const scheduleEnd = () => {
812
+ const scheduleEnd2 = () => {
757
813
  const endTime = calculateEndTime(currentConfig.schedule, startTime);
758
814
  if (!endTime) {
759
815
  return;
@@ -770,7 +826,7 @@ function useScheduledBlocker(blockerId, config) {
770
826
  const cfg = configRef.current;
771
827
  addBlocker(blockerId, createBlockerConfig(cfg));
772
828
  cfg.onScheduleStart?.();
773
- scheduleEnd();
829
+ scheduleEnd2();
774
830
  };
775
831
  if (startTime > now) {
776
832
  const delay = startTime - now;
@@ -790,7 +846,16 @@ function useScheduledBlocker(blockerId, config) {
790
846
  cleanup();
791
847
  removeBlocker(blockerId);
792
848
  };
793
- }, [blockerId, addBlocker, removeBlocker, cleanup]);
849
+ }, [
850
+ blockerId,
851
+ addBlocker,
852
+ removeBlocker,
853
+ cleanup,
854
+ scheduleStart,
855
+ scheduleEnd,
856
+ scheduleDuration,
857
+ configRef
858
+ ]);
794
859
  }
795
860
 
796
861
  // src/middleware/analyticsMiddleware.utils.ts