@okyrychenko-dev/react-action-guard 0.5.0 → 0.6.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 CHANGED
@@ -154,10 +154,12 @@ Gets detailed information about all active blockers for a specific scope.
154
154
  **BlockerInfo:**
155
155
 
156
156
  - `id: string` - Unique identifier of the blocker
157
- - `reason?: string` - Reason for blocking
158
- - `priority: number` - Priority level (0-100)
157
+ - `reason: string` - Reason for blocking (defaults to "Unknown")
158
+ - `priority: number` - Priority level (higher = higher priority, minimum value is 0)
159
159
  - `scope: string | string[]` - Scope(s) being blocked
160
- - `timestamp: number` - When the blocker was added
160
+ - `timestamp: number` - When the blocker was added (milliseconds since epoch)
161
+ - `timeout?: number` - Optional timeout duration in milliseconds
162
+ - `onTimeout?: (blockerId: string) => void` - Optional callback when timeout expires
161
163
 
162
164
  **Example:**
163
165
 
@@ -433,22 +435,30 @@ Direct access to the Zustand store for advanced use cases (requires a selector).
433
435
  **Methods:**
434
436
 
435
437
  - `addBlocker(id, config)` - Manually add a blocker (re-adding with the same ID replaces config)
438
+ - `updateBlocker(id, config)` - Update blocker metadata (timeout restarts if new `timeout` value provided)
436
439
  - `removeBlocker(id)` - Manually remove a blocker
437
440
  - `isBlocked(scope)` - Check if scope is blocked
438
441
  - `getBlockingInfo(scope)` - Get detailed blocking information
439
- - `clearAllBlockers()` - Remove all blockers
440
- - `clearBlockersForScope(scope)` - Remove blockers for specific scope
442
+ - `clearAllBlockers()` - Remove all blockers (emits `"clear"` middleware event)
443
+ - `clearBlockersForScope(scope)` - Remove blockers for specific scope (emits `"clear_scope"` middleware event)
441
444
  - `registerMiddleware(name, middleware)` - Register middleware manually
442
445
  - `unregisterMiddleware(name)` - Unregister middleware manually
443
446
 
447
+ **Note about `updateBlocker` and timeouts:**
448
+ - If you pass a **new** `timeout` value, the timer will be **restarted**
449
+ - If you **don't** pass `timeout`, the existing timer continues unchanged
450
+ - Set `timeout: 0` to **clear** an existing timeout
451
+ - `addBlocker` always resets/creates a new timeout timer when called with the same ID
452
+
444
453
  **Example:**
445
454
 
446
455
  ```jsx
447
456
  import { useUIBlockingStore } from "@okyrychenko-dev/react-action-guard";
448
457
 
449
458
  function AdvancedComponent() {
450
- const { addBlocker, removeBlocker } = useUIBlockingStore((state) => ({
459
+ const { addBlocker, updateBlocker, removeBlocker } = useUIBlockingStore((state) => ({
451
460
  addBlocker: state.addBlocker,
461
+ updateBlocker: state.updateBlocker,
452
462
  removeBlocker: state.removeBlocker,
453
463
  }));
454
464
 
@@ -461,12 +471,13 @@ function AdvancedComponent() {
461
471
  };
462
472
 
463
473
  const updatePriority = () => {
464
- // Re-add with the same ID to update the config
465
- addBlocker("custom-blocker", {
466
- scope: ["form", "navigation"],
467
- reason: "Critical operation in progress",
468
- priority: 200,
469
- });
474
+ // Update metadata - timeout continues if not changed
475
+ updateBlocker("custom-blocker", { priority: 200 });
476
+ };
477
+
478
+ const extendTimeout = () => {
479
+ // Update timeout - timer restarts with new value
480
+ updateBlocker("custom-blocker", { timeout: 60000 });
470
481
  };
471
482
 
472
483
  const stopBlocking = () => {
@@ -507,6 +518,30 @@ Advanced store helpers are re-exported from the internal toolkit for custom setu
507
518
 
508
519
  The library includes a powerful middleware system that allows you to hook into blocker lifecycle events for analytics, logging, and performance monitoring.
509
520
 
521
+ ### Middleware Actions
522
+
523
+ Middleware receives events for the following actions:
524
+
525
+ - `"add"` - Blocker was added
526
+ - `"update"` - Blocker metadata was updated
527
+ - `"remove"` - Blocker was removed
528
+ - `"timeout"` - Blocker was auto-removed due to timeout
529
+ - `"clear"` - All blockers were cleared (includes `count` field)
530
+ - `"clear_scope"` - Blockers for specific scope were cleared (includes `scope` and `count` fields)
531
+
532
+ **MiddlewareContext type:**
533
+ ```typescript
534
+ {
535
+ action: "add" | "update" | "remove" | "timeout" | "clear" | "clear_scope";
536
+ blockerId: string;
537
+ config?: BlockerConfig;
538
+ timestamp: number;
539
+ prevState?: BlockerConfig; // Available for "update" and "remove"
540
+ scope?: string; // Available for "clear_scope"
541
+ count?: number; // Available for "clear" and "clear_scope"
542
+ }
543
+ ```
544
+
510
545
  ### Built-in Middleware
511
546
 
512
547
  #### Analytics Middleware
@@ -570,8 +605,6 @@ configureMiddleware([
570
605
 
571
606
  Note: `configureMiddleware` registers middleware on the global store. If you use `UIBlockingProvider`, register middleware via the provider's `middlewares` prop instead.
572
607
 
573
- Note: Middleware action types include `update` and `cancel`, but the store currently emits `add`, `remove`, and `timeout`.
574
-
575
608
  ### Custom Middleware
576
609
 
577
610
  Create your own middleware to handle blocker events:
@@ -580,14 +613,20 @@ Create your own middleware to handle blocker events:
580
613
  import { configureMiddleware } from "@okyrychenko-dev/react-action-guard";
581
614
 
582
615
  const myCustomMiddleware = (context) => {
583
- const { action, blockerId, config, timestamp } = context;
616
+ const { action, blockerId, config, timestamp, scope, count } = context;
584
617
 
585
618
  if (action === "add") {
586
619
  console.log(`Blocker added: ${blockerId}`, config);
587
- } else if (action === "remove") {
588
- console.log(`Blocker removed: ${blockerId}`);
589
620
  } else if (action === "update") {
590
621
  console.log(`Blocker updated: ${blockerId}`, config);
622
+ } else if (action === "remove") {
623
+ console.log(`Blocker removed: ${blockerId}`);
624
+ } else if (action === "timeout") {
625
+ console.log(`Blocker timed out: ${blockerId}`);
626
+ } else if (action === "clear") {
627
+ console.log(`All blockers cleared (${count} total)`);
628
+ } else if (action === "clear_scope") {
629
+ console.log(`Cleared ${count} blocker(s) for scope: ${scope}`);
591
630
  }
592
631
  };
593
632
 
@@ -887,6 +926,97 @@ function StorageQuotaGuard() {
887
926
  }
888
927
  ```
889
928
 
929
+ ### Clearing Blockers on Navigation
930
+
931
+ Clear blockers when navigating away or on specific events:
932
+
933
+ ```jsx
934
+ import { useUIBlockingStore } from "@okyrychenko-dev/react-action-guard";
935
+ import { useEffect } from "react";
936
+
937
+ function CheckoutPage() {
938
+ const { clearBlockersForScope, clearAllBlockers } = useUIBlockingStore(
939
+ (state) => ({
940
+ clearBlockersForScope: state.clearBlockersForScope,
941
+ clearAllBlockers: state.clearAllBlockers,
942
+ })
943
+ );
944
+
945
+ // Clear checkout-specific blockers when leaving the page
946
+ useEffect(() => {
947
+ return () => {
948
+ // Clean up checkout blockers on unmount
949
+ clearBlockersForScope("checkout");
950
+ };
951
+ }, [clearBlockersForScope]);
952
+
953
+ const handleCancelOrder = () => {
954
+ // Clear all blockers when user explicitly cancels
955
+ clearAllBlockers();
956
+ navigate("/");
957
+ };
958
+
959
+ return (
960
+ <div>
961
+ <CheckoutForm />
962
+ <button onClick={handleCancelOrder}>Cancel Order</button>
963
+ </div>
964
+ );
965
+ }
966
+ ```
967
+
968
+ ### Managing Session Timeouts with Dynamic Updates
969
+
970
+ Extend or update blocker timeouts dynamically:
971
+
972
+ ```jsx
973
+ import { useUIBlockingStore } from "@okyrychenko-dev/react-action-guard";
974
+
975
+ function SessionManager() {
976
+ const { addBlocker, updateBlocker, removeBlocker } = useUIBlockingStore(
977
+ (state) => ({
978
+ addBlocker: state.addBlocker,
979
+ updateBlocker: state.updateBlocker,
980
+ removeBlocker: state.removeBlocker,
981
+ })
982
+ );
983
+
984
+ const startSession = () => {
985
+ addBlocker("session-timeout", {
986
+ scope: "global",
987
+ reason: "Session expiring soon",
988
+ priority: 50,
989
+ timeout: 300000, // 5 minutes
990
+ onTimeout: () => {
991
+ logout();
992
+ showNotification("Session expired");
993
+ },
994
+ });
995
+ };
996
+
997
+ const extendSession = () => {
998
+ // Extend timeout - timer restarts with new value
999
+ updateBlocker("session-timeout", {
1000
+ timeout: 600000, // 10 minutes
1001
+ reason: "Session extended",
1002
+ });
1003
+ };
1004
+
1005
+ const endSession = () => {
1006
+ removeBlocker("session-timeout");
1007
+ logout();
1008
+ };
1009
+
1010
+ return (
1011
+ <div>
1012
+ <button onClick={startSession}>Start Session</button>
1013
+ <button onClick={extendSession}>Extend Session</button>
1014
+ <button onClick={endSession}>Logout</button>
1015
+ </div>
1016
+ );
1017
+ }
1018
+ ```
1019
+
890
1020
  ## Development
891
1021
 
892
1022
  ```bash