@okyrychenko-dev/react-action-guard 0.7.0 → 1.0.1
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/README.md +96 -39
- package/dist/index.cjs +92 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +437 -402
- package/dist/index.d.ts +437 -402
- package/dist/index.js +87 -45
- package/dist/index.js.map +1 -1
- package/package.json +10 -9
package/dist/index.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { useRef, useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
1
|
import { createStoreProvider, createShallowStore } from '@okyrychenko-dev/react-zustand-toolkit';
|
|
3
|
-
export { createShallowStore } from '@okyrychenko-dev/react-zustand-toolkit';
|
|
4
2
|
import { useStore } from 'zustand';
|
|
5
3
|
import 'zustand/react/shallow';
|
|
6
4
|
import { jsx } from 'react/jsx-runtime';
|
|
7
5
|
import { useShallow } from 'zustand/shallow';
|
|
8
6
|
import { devtools } from 'zustand/middleware';
|
|
7
|
+
import { useRef, useCallback, useEffect, useMemo, useState } from 'react';
|
|
9
8
|
|
|
10
|
-
// src/
|
|
9
|
+
// src/context/UIBlockingContext.tsx
|
|
11
10
|
|
|
12
11
|
// src/store/uiBlockingStore.constants.ts
|
|
13
12
|
var DEFAULT_SCOPE = "global";
|
|
@@ -55,11 +54,10 @@ var createUIBlockingActions = (set, get) => ({
|
|
|
55
54
|
},
|
|
56
55
|
runMiddlewares: async (context) => {
|
|
57
56
|
const { middlewares } = get();
|
|
58
|
-
for (const [
|
|
57
|
+
for (const [, middleware] of middlewares) {
|
|
59
58
|
try {
|
|
60
59
|
await middleware(context);
|
|
61
|
-
} catch
|
|
62
|
-
console.error(`[UIBlocking] Middleware "${name}" error:`, error);
|
|
60
|
+
} catch {
|
|
63
61
|
}
|
|
64
62
|
}
|
|
65
63
|
},
|
|
@@ -353,7 +351,8 @@ var createUIBlockingActions = (set, get) => ({
|
|
|
353
351
|
for (const [id, blocker] of activeBlockers) {
|
|
354
352
|
const blockerScopes = normalizeScopeToArray(blocker.scope);
|
|
355
353
|
if (blocker.scope === DEFAULT_SCOPE || blockerScopes.includes(scope)) {
|
|
356
|
-
|
|
354
|
+
const { timeoutId: _timeoutId, ...publicBlocker } = blocker;
|
|
355
|
+
blockers.push({ id, ...publicBlocker });
|
|
357
356
|
}
|
|
358
357
|
}
|
|
359
358
|
return blockers.sort((a, b) => b.priority - a.priority);
|
|
@@ -449,18 +448,24 @@ var createUIBlockingActions = (set, get) => ({
|
|
|
449
448
|
});
|
|
450
449
|
var {
|
|
451
450
|
Provider: BaseUIBlockingProvider,
|
|
452
|
-
|
|
453
|
-
useContextStore:
|
|
451
|
+
useContextStoreApi: useUIBlockingContext,
|
|
452
|
+
useContextStore: baseUseUIBlockingStoreFromContext,
|
|
454
453
|
useIsInsideProvider: useIsInsideUIBlockingProvider,
|
|
455
|
-
|
|
454
|
+
useContextStoreOptional: useOptionalUIBlockingContext
|
|
456
455
|
} = createStoreProvider(createUIBlockingActions, "UIBlocking");
|
|
456
|
+
function useUIBlockingStoreFromContext(selector, equalityFn) {
|
|
457
|
+
if (selector) {
|
|
458
|
+
return baseUseUIBlockingStoreFromContext(selector, equalityFn);
|
|
459
|
+
}
|
|
460
|
+
return baseUseUIBlockingStoreFromContext();
|
|
461
|
+
}
|
|
457
462
|
function UIBlockingProvider({
|
|
458
463
|
children,
|
|
459
464
|
enableDevtools = process.env.NODE_ENV === "development",
|
|
460
465
|
devtoolsName = "UIBlocking",
|
|
461
466
|
middlewares = []
|
|
462
467
|
}) {
|
|
463
|
-
const
|
|
468
|
+
const handleStoreInit = (store) => {
|
|
464
469
|
middlewares.forEach((mw, index) => {
|
|
465
470
|
store.getState().registerMiddleware(`provider-middleware-${index.toString()}`, mw);
|
|
466
471
|
});
|
|
@@ -470,7 +475,7 @@ function UIBlockingProvider({
|
|
|
470
475
|
{
|
|
471
476
|
enableDevtools,
|
|
472
477
|
devtoolsName,
|
|
473
|
-
|
|
478
|
+
onStoreInit: handleStoreInit,
|
|
474
479
|
children
|
|
475
480
|
}
|
|
476
481
|
);
|
|
@@ -483,24 +488,31 @@ var devtoolsConfig = {
|
|
|
483
488
|
};
|
|
484
489
|
|
|
485
490
|
// src/store/uiBlockingStore.store.ts
|
|
486
|
-
var
|
|
491
|
+
var storeBindings = createShallowStore(
|
|
492
|
+
devtools(createUIBlockingActions, devtoolsConfig)
|
|
493
|
+
);
|
|
494
|
+
var uiBlockingStoreApi = storeBindings.useStoreApi;
|
|
495
|
+
function useUIBlockingStore(selector, equalityFn) {
|
|
496
|
+
if (selector) {
|
|
497
|
+
return storeBindings.useStore(selector, equalityFn);
|
|
498
|
+
}
|
|
499
|
+
return storeBindings.useStore();
|
|
500
|
+
}
|
|
487
501
|
|
|
488
502
|
// src/context/useResolvedStore.ts
|
|
489
503
|
function useOptionalContextStore() {
|
|
490
|
-
return
|
|
504
|
+
return useOptionalUIBlockingContext();
|
|
491
505
|
}
|
|
492
|
-
function
|
|
506
|
+
function useResolvedStoreApi() {
|
|
493
507
|
const contextStore = useOptionalContextStore();
|
|
494
508
|
return contextStore ?? uiBlockingStoreApi;
|
|
495
509
|
}
|
|
496
|
-
function
|
|
497
|
-
const store =
|
|
510
|
+
function useResolvedValue(selector) {
|
|
511
|
+
const store = useResolvedStoreApi();
|
|
498
512
|
return useStore(store, useShallow(selector));
|
|
499
513
|
}
|
|
500
|
-
|
|
501
|
-
// src/hooks/useAsyncAction/useAsyncAction.ts
|
|
502
514
|
function useAsyncAction(actionId, scope, options) {
|
|
503
|
-
const { addBlocker, removeBlocker } =
|
|
515
|
+
const { addBlocker, removeBlocker } = useResolvedValue((state) => ({
|
|
504
516
|
addBlocker: state.addBlocker,
|
|
505
517
|
removeBlocker: state.removeBlocker
|
|
506
518
|
}));
|
|
@@ -527,19 +539,33 @@ function useAsyncAction(actionId, scope, options) {
|
|
|
527
539
|
return executeWithBlocking;
|
|
528
540
|
}
|
|
529
541
|
function useBlocker(blockerId, config, isActive = true) {
|
|
530
|
-
const { addBlocker, removeBlocker } =
|
|
542
|
+
const { addBlocker, removeBlocker, updateBlocker } = useResolvedValue((state) => ({
|
|
531
543
|
addBlocker: state.addBlocker,
|
|
532
|
-
removeBlocker: state.removeBlocker
|
|
544
|
+
removeBlocker: state.removeBlocker,
|
|
545
|
+
updateBlocker: state.updateBlocker
|
|
533
546
|
}));
|
|
547
|
+
const lastConfigRef = useRef(null);
|
|
534
548
|
useEffect(() => {
|
|
535
549
|
if (!isActive || !blockerId) {
|
|
536
550
|
return;
|
|
537
551
|
}
|
|
538
552
|
addBlocker(blockerId, config);
|
|
553
|
+
lastConfigRef.current = config;
|
|
539
554
|
return () => {
|
|
555
|
+
lastConfigRef.current = null;
|
|
540
556
|
removeBlocker(blockerId);
|
|
541
557
|
};
|
|
542
558
|
}, [blockerId, isActive, addBlocker, removeBlocker]);
|
|
559
|
+
useEffect(() => {
|
|
560
|
+
if (!isActive || !blockerId) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
if (lastConfigRef.current === config) {
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
updateBlocker(blockerId, config);
|
|
567
|
+
lastConfigRef.current = config;
|
|
568
|
+
}, [blockerId, config, isActive, updateBlocker]);
|
|
543
569
|
}
|
|
544
570
|
|
|
545
571
|
// src/hooks/useBlocker/useBlocker.utils.ts
|
|
@@ -551,7 +577,7 @@ function createBlockerConfig(config) {
|
|
|
551
577
|
};
|
|
552
578
|
}
|
|
553
579
|
function useBlockingInfo(scope = DEFAULT_SCOPE) {
|
|
554
|
-
const store =
|
|
580
|
+
const store = useResolvedStoreApi();
|
|
555
581
|
const activeBlockers = useStore(store, (state) => state.activeBlockers);
|
|
556
582
|
return useMemo(() => {
|
|
557
583
|
return store.getState().getBlockingInfo(scope);
|
|
@@ -567,7 +593,7 @@ function useConfigRef(config) {
|
|
|
567
593
|
|
|
568
594
|
// src/hooks/useConditionalBlocker/useConditionalBlocker.ts
|
|
569
595
|
function useConditionalBlocker(blockerId, config) {
|
|
570
|
-
const { addBlocker, removeBlocker } =
|
|
596
|
+
const { addBlocker, removeBlocker } = useResolvedValue((state) => ({
|
|
571
597
|
addBlocker: state.addBlocker,
|
|
572
598
|
removeBlocker: state.removeBlocker
|
|
573
599
|
}));
|
|
@@ -622,9 +648,6 @@ function useConfirmableBlocker(blockerId, config) {
|
|
|
622
648
|
setIsExecuting(true);
|
|
623
649
|
try {
|
|
624
650
|
await config.onConfirm();
|
|
625
|
-
} catch (error) {
|
|
626
|
-
console.error("[UIBlocking] Confirmable action error:", error);
|
|
627
|
-
throw error;
|
|
628
651
|
} finally {
|
|
629
652
|
setIsExecuting(false);
|
|
630
653
|
}
|
|
@@ -650,7 +673,7 @@ function useConfirmableBlocker(blockerId, config) {
|
|
|
650
673
|
|
|
651
674
|
// src/hooks/useIsBlocked/useIsBlocked.ts
|
|
652
675
|
function useIsBlocked(scope) {
|
|
653
|
-
return
|
|
676
|
+
return useResolvedValue((state) => state.isBlocked(scope));
|
|
654
677
|
}
|
|
655
678
|
|
|
656
679
|
// src/hooks/useScheduledBlocker/useScheduledBlocker.utils.ts
|
|
@@ -692,7 +715,7 @@ function isScheduleInPast(startTime, endTime, now) {
|
|
|
692
715
|
|
|
693
716
|
// src/hooks/useScheduledBlocker/useScheduledBlocker.ts
|
|
694
717
|
function useScheduledBlocker(blockerId, config) {
|
|
695
|
-
const { addBlocker, removeBlocker } =
|
|
718
|
+
const { addBlocker, removeBlocker } = useResolvedValue((state) => ({
|
|
696
719
|
addBlocker: state.addBlocker,
|
|
697
720
|
removeBlocker: state.removeBlocker
|
|
698
721
|
}));
|
|
@@ -713,7 +736,6 @@ function useScheduledBlocker(blockerId, config) {
|
|
|
713
736
|
const startTime = parseDate(currentConfig.schedule.start);
|
|
714
737
|
const now = Date.now();
|
|
715
738
|
if (!isValidTimestamp(startTime)) {
|
|
716
|
-
console.error(`[UIBlocking] Invalid start time for blocker "${blockerId}"`);
|
|
717
739
|
return;
|
|
718
740
|
}
|
|
719
741
|
const scheduleEnd = () => {
|
|
@@ -738,9 +760,6 @@ function useScheduledBlocker(blockerId, config) {
|
|
|
738
760
|
if (startTime > now) {
|
|
739
761
|
const delay = startTime - now;
|
|
740
762
|
if (!isSafeTimeout(delay)) {
|
|
741
|
-
console.warn(
|
|
742
|
-
`[UIBlocking] Schedule delay exceeds maximum timeout (${MAX_TIMEOUT_MS.toString()}ms) for blocker "${blockerId}"`
|
|
743
|
-
);
|
|
744
763
|
return;
|
|
745
764
|
}
|
|
746
765
|
timeoutsRef.current.start = setTimeout(startBlocking, delay);
|
|
@@ -749,7 +768,7 @@ function useScheduledBlocker(blockerId, config) {
|
|
|
749
768
|
if (isInBlockingPeriod(startTime, endTime, now)) {
|
|
750
769
|
startBlocking();
|
|
751
770
|
} else if (isScheduleInPast(startTime, endTime, now)) {
|
|
752
|
-
|
|
771
|
+
return;
|
|
753
772
|
}
|
|
754
773
|
}
|
|
755
774
|
return () => {
|
|
@@ -763,6 +782,12 @@ function useScheduledBlocker(blockerId, config) {
|
|
|
763
782
|
function capitalizeFirst(str) {
|
|
764
783
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
765
784
|
}
|
|
785
|
+
function getAnalyticsWindow() {
|
|
786
|
+
if (typeof window === "undefined") {
|
|
787
|
+
return void 0;
|
|
788
|
+
}
|
|
789
|
+
return window;
|
|
790
|
+
}
|
|
766
791
|
function buildEventData(context) {
|
|
767
792
|
const eventData = {
|
|
768
793
|
blocker_id: context.blockerId
|
|
@@ -780,20 +805,23 @@ function buildEventData(context) {
|
|
|
780
805
|
return eventData;
|
|
781
806
|
}
|
|
782
807
|
function trackGoogleAnalytics(action, eventData) {
|
|
783
|
-
|
|
784
|
-
|
|
808
|
+
const analyticsWindow = getAnalyticsWindow();
|
|
809
|
+
if (analyticsWindow?.gtag) {
|
|
810
|
+
analyticsWindow.gtag("event", `ui_blocking_${action}`, eventData);
|
|
785
811
|
}
|
|
786
812
|
}
|
|
787
813
|
function trackMixpanel(action, eventData) {
|
|
788
|
-
|
|
814
|
+
const analyticsWindow = getAnalyticsWindow();
|
|
815
|
+
if (analyticsWindow?.mixpanel) {
|
|
789
816
|
const eventName = `UI Blocking ${action.split("_").map(capitalizeFirst).join(" ")}`;
|
|
790
|
-
|
|
817
|
+
analyticsWindow.mixpanel.track(eventName, eventData);
|
|
791
818
|
}
|
|
792
819
|
}
|
|
793
820
|
function trackAmplitude(action, eventData) {
|
|
794
|
-
|
|
821
|
+
const analyticsWindow = getAnalyticsWindow();
|
|
822
|
+
if (analyticsWindow?.amplitude) {
|
|
795
823
|
const eventName = `UI Blocking ${action.split("_").map(capitalizeFirst).join(" ")}`;
|
|
796
|
-
|
|
824
|
+
analyticsWindow.amplitude.track(eventName, eventData);
|
|
797
825
|
}
|
|
798
826
|
}
|
|
799
827
|
function trackCustom(trackFn, action, eventData) {
|
|
@@ -830,10 +858,16 @@ function createAnalyticsMiddleware(config) {
|
|
|
830
858
|
}
|
|
831
859
|
|
|
832
860
|
// src/middleware/configureMiddleware.ts
|
|
861
|
+
var CONFIGURE_MIDDLEWARE_PREFIX = "middleware-";
|
|
833
862
|
function configureMiddleware(middlewares) {
|
|
834
863
|
const store = uiBlockingStoreApi.getState();
|
|
864
|
+
store.middlewares.forEach((_, name) => {
|
|
865
|
+
if (name.startsWith(CONFIGURE_MIDDLEWARE_PREFIX)) {
|
|
866
|
+
store.unregisterMiddleware(name);
|
|
867
|
+
}
|
|
868
|
+
});
|
|
835
869
|
middlewares.forEach((middleware, index) => {
|
|
836
|
-
const name =
|
|
870
|
+
const name = `${CONFIGURE_MIDDLEWARE_PREFIX}${index.toString()}`;
|
|
837
871
|
store.registerMiddleware(name, middleware);
|
|
838
872
|
});
|
|
839
873
|
}
|
|
@@ -917,9 +951,6 @@ function handleRemoveAction(context, blockStartTimes, slowBlockThreshold, onSlow
|
|
|
917
951
|
const duration = context.timestamp - startTime;
|
|
918
952
|
blockStartTimes.delete(context.blockerId);
|
|
919
953
|
if (duration >= slowBlockThreshold) {
|
|
920
|
-
console.warn(
|
|
921
|
-
`[UIBlocking] Slow block detected: "${context.blockerId}" took ${duration.toString()}ms`
|
|
922
|
-
);
|
|
923
954
|
onSlowBlock?.(context.blockerId, duration);
|
|
924
955
|
}
|
|
925
956
|
}
|
|
@@ -951,6 +982,17 @@ function createTypedHooks() {
|
|
|
951
982
|
};
|
|
952
983
|
}
|
|
953
984
|
|
|
954
|
-
|
|
985
|
+
// src/index.ts
|
|
986
|
+
function useOptionalContext() {
|
|
987
|
+
return useOptionalUIBlockingContext();
|
|
988
|
+
}
|
|
989
|
+
function useResolvedStore() {
|
|
990
|
+
return useResolvedStoreApi();
|
|
991
|
+
}
|
|
992
|
+
function useResolvedStoreWithSelector(selector) {
|
|
993
|
+
return useResolvedValue(selector);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
export { UIBlockingProvider, configureMiddleware, createAnalyticsMiddleware, createPerformanceMiddleware, createTypedHooks, loggerMiddleware, uiBlockingStoreApi, useAsyncAction, useBlocker, useBlockingInfo, useConditionalBlocker, useConfirmableBlocker, useIsBlocked, useIsInsideUIBlockingProvider, useOptionalContext, useOptionalUIBlockingContext, useResolvedStore, useResolvedStoreApi, useResolvedStoreWithSelector, useResolvedValue, useScheduledBlocker, useUIBlockingContext, useUIBlockingStore, useUIBlockingStoreFromContext };
|
|
955
997
|
//# sourceMappingURL=index.js.map
|
|
956
998
|
//# sourceMappingURL=index.js.map
|