@okyrychenko-dev/react-action-guard 0.2.1 → 0.2.2
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 +467 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,8 +7,15 @@
|
|
|
7
7
|
- Priority-based blocking system
|
|
8
8
|
- Scoped blocking (global, specific areas, or multiple scopes)
|
|
9
9
|
- Automatic cleanup on unmount
|
|
10
|
-
-
|
|
10
|
+
- Advanced hooks for different use cases:
|
|
11
|
+
- Confirmable blockers with custom dialogs
|
|
12
|
+
- Scheduled blocking for maintenance windows
|
|
13
|
+
- Conditional blocking based on application state
|
|
14
|
+
- Async action wrapping
|
|
15
|
+
- Advanced middleware system for analytics, logging, and performance monitoring
|
|
16
|
+
- TypeScript support with full type safety
|
|
11
17
|
- Built on Zustand for efficient state management
|
|
18
|
+
- Tree-shakeable - import only what you need
|
|
12
19
|
- Hooks-based API
|
|
13
20
|
|
|
14
21
|
## Installation
|
|
@@ -107,6 +114,138 @@ function MyComponent() {
|
|
|
107
114
|
}
|
|
108
115
|
```
|
|
109
116
|
|
|
117
|
+
#### `useConfirmableBlocker(blockerId, config, options)`
|
|
118
|
+
|
|
119
|
+
Creates a blocker that requires user confirmation before being removed.
|
|
120
|
+
|
|
121
|
+
**Parameters:**
|
|
122
|
+
|
|
123
|
+
- `blockerId: string` - Unique identifier for the blocker
|
|
124
|
+
- `config: BlockerConfig` - Configuration object
|
|
125
|
+
- `options: ConfirmableBlockerOptions`
|
|
126
|
+
- `enabled?: boolean` - Whether the blocker is active (default: true)
|
|
127
|
+
- `confirmMessage?: string` - Message to show in confirmation dialog
|
|
128
|
+
- `onConfirm?: () => void | Promise<void>` - Callback when user confirms
|
|
129
|
+
|
|
130
|
+
**Returns:**
|
|
131
|
+
|
|
132
|
+
- `isBlocking: boolean` - Whether the blocker is currently active
|
|
133
|
+
- `cancel: () => void` - Manually remove the blocker without confirmation
|
|
134
|
+
- `requestRemoval: () => Promise<boolean>` - Request removal with confirmation
|
|
135
|
+
|
|
136
|
+
**Example:**
|
|
137
|
+
|
|
138
|
+
```jsx
|
|
139
|
+
function UnsavedChangesGuard() {
|
|
140
|
+
const { isBlocking, requestRemoval } = useConfirmableBlocker(
|
|
141
|
+
"unsaved-changes",
|
|
142
|
+
{
|
|
143
|
+
scope: "navigation",
|
|
144
|
+
reason: "Unsaved changes",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
enabled: hasUnsavedChanges,
|
|
148
|
+
confirmMessage: "You have unsaved changes. Are you sure you want to leave?",
|
|
149
|
+
onConfirm: async () => {
|
|
150
|
+
await discardChanges();
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<div>
|
|
157
|
+
{isBlocking && <UnsavedIndicator />}
|
|
158
|
+
<button onClick={requestRemoval}>Leave</button>
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### `useScheduledBlocker(blockerId, config)`
|
|
165
|
+
|
|
166
|
+
Blocks UI during a scheduled time period or maintenance window.
|
|
167
|
+
|
|
168
|
+
**Parameters:**
|
|
169
|
+
|
|
170
|
+
- `blockerId: string` - Unique identifier for the blocker
|
|
171
|
+
- `config: ScheduledBlockerConfig`
|
|
172
|
+
- `schedule: BlockingSchedule`
|
|
173
|
+
- `start: string | Date | number` - Start time (ISO string, Date, or timestamp)
|
|
174
|
+
- `end?: string | Date | number` - End time (optional)
|
|
175
|
+
- `duration?: number` - Duration in milliseconds (takes precedence over end)
|
|
176
|
+
- `onScheduleStart?: () => void` - Callback when blocking starts
|
|
177
|
+
- `onScheduleEnd?: () => void` - Callback when blocking ends
|
|
178
|
+
- Plus all `BlockerConfig` properties (scope, reason, priority)
|
|
179
|
+
|
|
180
|
+
**Example:**
|
|
181
|
+
|
|
182
|
+
```jsx
|
|
183
|
+
function MaintenanceWindow() {
|
|
184
|
+
useScheduledBlocker("maintenance", {
|
|
185
|
+
scope: "global",
|
|
186
|
+
reason: "Scheduled maintenance",
|
|
187
|
+
priority: 1000,
|
|
188
|
+
schedule: {
|
|
189
|
+
start: "2024-01-15T02:00:00Z",
|
|
190
|
+
duration: 3600000, // 1 hour in milliseconds
|
|
191
|
+
},
|
|
192
|
+
onScheduleStart: () => {
|
|
193
|
+
console.log("Maintenance started");
|
|
194
|
+
},
|
|
195
|
+
onScheduleEnd: () => {
|
|
196
|
+
console.log("Maintenance completed");
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
return <div>App content</div>;
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### `useConditionalBlocker(blockerId, config)`
|
|
205
|
+
|
|
206
|
+
Periodically checks a condition and blocks/unblocks based on the result.
|
|
207
|
+
|
|
208
|
+
**Parameters:**
|
|
209
|
+
|
|
210
|
+
- `blockerId: string` - Unique identifier for the blocker
|
|
211
|
+
- `config: ConditionalBlockerConfig<TState>`
|
|
212
|
+
- `scope: string | string[]` - Required scope(s) to block
|
|
213
|
+
- `condition: (state?: TState) => boolean` - Function that determines if blocking should be active
|
|
214
|
+
- `checkInterval?: number` - How often to check the condition in ms (default: 1000)
|
|
215
|
+
- `state?: TState` - Optional state to pass to the condition function
|
|
216
|
+
- Plus all other `BlockerConfig` properties (reason, priority)
|
|
217
|
+
|
|
218
|
+
**Example:**
|
|
219
|
+
|
|
220
|
+
```jsx
|
|
221
|
+
function NetworkStatusBlocker() {
|
|
222
|
+
const [isOnline, setIsOnline] = useState(navigator.onLine);
|
|
223
|
+
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
const handleOnline = () => setIsOnline(true);
|
|
226
|
+
const handleOffline = () => setIsOnline(false);
|
|
227
|
+
|
|
228
|
+
window.addEventListener("online", handleOnline);
|
|
229
|
+
window.addEventListener("offline", handleOffline);
|
|
230
|
+
|
|
231
|
+
return () => {
|
|
232
|
+
window.removeEventListener("online", handleOnline);
|
|
233
|
+
window.removeEventListener("offline", handleOffline);
|
|
234
|
+
};
|
|
235
|
+
}, []);
|
|
236
|
+
|
|
237
|
+
useConditionalBlocker("network-check", {
|
|
238
|
+
scope: ["form", "navigation"],
|
|
239
|
+
reason: "No network connection",
|
|
240
|
+
priority: 100,
|
|
241
|
+
condition: () => !isOnline,
|
|
242
|
+
checkInterval: 2000,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return <div>App content</div>;
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
110
249
|
### Store
|
|
111
250
|
|
|
112
251
|
#### `useUIBlockingStore`
|
|
@@ -117,6 +256,7 @@ Direct access to the Zustand store for advanced use cases.
|
|
|
117
256
|
|
|
118
257
|
- `addBlocker(id, config)` - Manually add a blocker
|
|
119
258
|
- `removeBlocker(id)` - Manually remove a blocker
|
|
259
|
+
- `updateBlocker(id, config)` - Update an existing blocker
|
|
120
260
|
- `isBlocked(scope)` - Check if scope is blocked
|
|
121
261
|
- `getBlockingInfo(scope)` - Get detailed blocking information
|
|
122
262
|
- `clearAllBlockers()` - Remove all blockers
|
|
@@ -128,7 +268,7 @@ Direct access to the Zustand store for advanced use cases.
|
|
|
128
268
|
import { useUIBlockingStore } from "@okyrychenko-dev/react-action-guard";
|
|
129
269
|
|
|
130
270
|
function AdvancedComponent() {
|
|
131
|
-
const { addBlocker, removeBlocker } = useUIBlockingStore();
|
|
271
|
+
const { addBlocker, removeBlocker, updateBlocker } = useUIBlockingStore();
|
|
132
272
|
|
|
133
273
|
const startBlocking = () => {
|
|
134
274
|
addBlocker("custom-blocker", {
|
|
@@ -138,6 +278,12 @@ function AdvancedComponent() {
|
|
|
138
278
|
});
|
|
139
279
|
};
|
|
140
280
|
|
|
281
|
+
const updatePriority = () => {
|
|
282
|
+
updateBlocker("custom-blocker", {
|
|
283
|
+
priority: 200,
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
|
|
141
287
|
const stopBlocking = () => {
|
|
142
288
|
removeBlocker("custom-blocker");
|
|
143
289
|
};
|
|
@@ -145,18 +291,159 @@ function AdvancedComponent() {
|
|
|
145
291
|
return (
|
|
146
292
|
<div>
|
|
147
293
|
<button onClick={startBlocking}>Start</button>
|
|
294
|
+
<button onClick={updatePriority}>Increase Priority</button>
|
|
148
295
|
<button onClick={stopBlocking}>Stop</button>
|
|
149
296
|
</div>
|
|
150
297
|
);
|
|
151
298
|
}
|
|
152
299
|
```
|
|
153
300
|
|
|
301
|
+
## Middleware System
|
|
302
|
+
|
|
303
|
+
The library includes a powerful middleware system that allows you to hook into blocker lifecycle events for analytics, logging, and performance monitoring.
|
|
304
|
+
|
|
305
|
+
### Built-in Middleware
|
|
306
|
+
|
|
307
|
+
#### Analytics Middleware
|
|
308
|
+
|
|
309
|
+
Track blocker events with your analytics provider (Google Analytics, Mixpanel, Amplitude, or custom).
|
|
310
|
+
|
|
311
|
+
```jsx
|
|
312
|
+
import { configureMiddleware, analyticsMiddleware } from "@okyrychenko-dev/react-action-guard";
|
|
313
|
+
|
|
314
|
+
// Google Analytics
|
|
315
|
+
configureMiddleware([analyticsMiddleware({ provider: "ga" })]);
|
|
316
|
+
|
|
317
|
+
// Mixpanel
|
|
318
|
+
configureMiddleware([analyticsMiddleware({ provider: "mixpanel" })]);
|
|
319
|
+
|
|
320
|
+
// Amplitude
|
|
321
|
+
configureMiddleware([analyticsMiddleware({ provider: "amplitude" })]);
|
|
322
|
+
|
|
323
|
+
// Custom analytics
|
|
324
|
+
configureMiddleware([
|
|
325
|
+
analyticsMiddleware({
|
|
326
|
+
track: (event, data) => {
|
|
327
|
+
myAnalytics.track(event, data);
|
|
328
|
+
},
|
|
329
|
+
}),
|
|
330
|
+
]);
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
#### Logger Middleware
|
|
334
|
+
|
|
335
|
+
Log blocker lifecycle events to the console for debugging.
|
|
336
|
+
|
|
337
|
+
```jsx
|
|
338
|
+
import { configureMiddleware, loggerMiddleware } from "@okyrychenko-dev/react-action-guard";
|
|
339
|
+
|
|
340
|
+
configureMiddleware([loggerMiddleware]);
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
#### Performance Middleware
|
|
344
|
+
|
|
345
|
+
Monitor blocker performance and detect slow operations.
|
|
346
|
+
|
|
347
|
+
```jsx
|
|
348
|
+
import { configureMiddleware, performanceMiddleware } from "@okyrychenko-dev/react-action-guard";
|
|
349
|
+
|
|
350
|
+
configureMiddleware([
|
|
351
|
+
performanceMiddleware({
|
|
352
|
+
onSlowBlock: (blockerId, duration) => {
|
|
353
|
+
console.warn(`Blocker ${blockerId} was active for ${duration}ms`);
|
|
354
|
+
},
|
|
355
|
+
slowBlockThreshold: 5000, // 5 seconds
|
|
356
|
+
}),
|
|
357
|
+
]);
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Custom Middleware
|
|
361
|
+
|
|
362
|
+
Create your own middleware to handle blocker events:
|
|
363
|
+
|
|
364
|
+
```jsx
|
|
365
|
+
import { configureMiddleware } from "@okyrychenko-dev/react-action-guard";
|
|
366
|
+
|
|
367
|
+
const myCustomMiddleware = (context) => {
|
|
368
|
+
const { action, blockerId, config, timestamp } = context;
|
|
369
|
+
|
|
370
|
+
if (action === "add") {
|
|
371
|
+
console.log(`Blocker added: ${blockerId}`, config);
|
|
372
|
+
} else if (action === "remove") {
|
|
373
|
+
console.log(`Blocker removed: ${blockerId}`);
|
|
374
|
+
} else if (action === "update") {
|
|
375
|
+
console.log(`Blocker updated: ${blockerId}`, config);
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
configureMiddleware([myCustomMiddleware]);
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Combining Middleware
|
|
383
|
+
|
|
384
|
+
You can combine multiple middleware for comprehensive monitoring:
|
|
385
|
+
|
|
386
|
+
```jsx
|
|
387
|
+
import {
|
|
388
|
+
configureMiddleware,
|
|
389
|
+
analyticsMiddleware,
|
|
390
|
+
loggerMiddleware,
|
|
391
|
+
performanceMiddleware,
|
|
392
|
+
} from "@okyrychenko-dev/react-action-guard";
|
|
393
|
+
|
|
394
|
+
configureMiddleware([
|
|
395
|
+
loggerMiddleware,
|
|
396
|
+
analyticsMiddleware({ provider: "ga" }),
|
|
397
|
+
performanceMiddleware({
|
|
398
|
+
slowBlockThreshold: 3000,
|
|
399
|
+
onSlowBlock: (blockerId, duration) => {
|
|
400
|
+
// Send to error tracking service
|
|
401
|
+
errorTracker.captureMessage(`Slow blocker: ${blockerId}`, {
|
|
402
|
+
duration,
|
|
403
|
+
});
|
|
404
|
+
},
|
|
405
|
+
}),
|
|
406
|
+
]);
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## Tree Shaking
|
|
410
|
+
|
|
411
|
+
The library is fully tree-shakeable. Import only the features you need to keep your bundle size small:
|
|
412
|
+
|
|
413
|
+
```jsx
|
|
414
|
+
// Only imports the hook you need
|
|
415
|
+
import { useBlocker } from "@okyrychenko-dev/react-action-guard";
|
|
416
|
+
|
|
417
|
+
// Middleware is not included unless you import it
|
|
418
|
+
import { configureMiddleware, analyticsMiddleware } from "@okyrychenko-dev/react-action-guard";
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
The package is configured with `"sideEffects": false`, allowing modern bundlers (Webpack, Rollup, Vite) to eliminate unused code automatically.
|
|
422
|
+
|
|
154
423
|
## TypeScript
|
|
155
424
|
|
|
156
425
|
The package is written in TypeScript and includes full type definitions.
|
|
157
426
|
|
|
158
427
|
```typescript
|
|
159
|
-
import type {
|
|
428
|
+
import type {
|
|
429
|
+
// Core types
|
|
430
|
+
BlockerConfig,
|
|
431
|
+
BlockerInfo,
|
|
432
|
+
UIBlockingState,
|
|
433
|
+
|
|
434
|
+
// Hook types
|
|
435
|
+
ConfirmableBlockerOptions,
|
|
436
|
+
ScheduledBlockerConfig,
|
|
437
|
+
ConditionalBlockerConfig,
|
|
438
|
+
BlockingSchedule,
|
|
439
|
+
|
|
440
|
+
// Middleware types
|
|
441
|
+
Middleware,
|
|
442
|
+
MiddlewareContext,
|
|
443
|
+
AnalyticsConfig,
|
|
444
|
+
AnalyticsProvider,
|
|
445
|
+
PerformanceMiddlewareConfig,
|
|
446
|
+
} from "@okyrychenko-dev/react-action-guard";
|
|
160
447
|
```
|
|
161
448
|
|
|
162
449
|
## Use Cases
|
|
@@ -180,9 +467,11 @@ function DataLoader() {
|
|
|
180
467
|
}
|
|
181
468
|
```
|
|
182
469
|
|
|
183
|
-
### Form Submission
|
|
470
|
+
### Form Submission with Analytics
|
|
184
471
|
|
|
185
472
|
```jsx
|
|
473
|
+
import { useAsyncAction, useIsBlocked } from "@okyrychenko-dev/react-action-guard";
|
|
474
|
+
|
|
186
475
|
function UserForm() {
|
|
187
476
|
const executeWithBlocking = useAsyncAction("submit-form", "form");
|
|
188
477
|
const isBlocked = useIsBlocked("form");
|
|
@@ -202,6 +491,46 @@ function UserForm() {
|
|
|
202
491
|
}
|
|
203
492
|
```
|
|
204
493
|
|
|
494
|
+
### Unsaved Changes Protection
|
|
495
|
+
|
|
496
|
+
```jsx
|
|
497
|
+
import { useConfirmableBlocker } from "@okyrychenko-dev/react-action-guard";
|
|
498
|
+
|
|
499
|
+
function FormWithUnsavedWarning() {
|
|
500
|
+
const [formData, setFormData] = useState({});
|
|
501
|
+
const [hasChanges, setHasChanges] = useState(false);
|
|
502
|
+
|
|
503
|
+
const { requestRemoval } = useConfirmableBlocker(
|
|
504
|
+
"unsaved-form",
|
|
505
|
+
{
|
|
506
|
+
scope: "navigation",
|
|
507
|
+
reason: "Unsaved form data",
|
|
508
|
+
priority: 100,
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
enabled: hasChanges,
|
|
512
|
+
confirmMessage: "You have unsaved changes. Discard them?",
|
|
513
|
+
onConfirm: () => {
|
|
514
|
+
setFormData({});
|
|
515
|
+
setHasChanges(false);
|
|
516
|
+
},
|
|
517
|
+
}
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
return (
|
|
521
|
+
<form>
|
|
522
|
+
<input
|
|
523
|
+
onChange={(e) => {
|
|
524
|
+
setFormData({ ...formData, name: e.target.value });
|
|
525
|
+
setHasChanges(true);
|
|
526
|
+
}}
|
|
527
|
+
/>
|
|
528
|
+
<button onClick={requestRemoval}>Cancel</button>
|
|
529
|
+
</form>
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
205
534
|
### Global Loading Overlay
|
|
206
535
|
|
|
207
536
|
```jsx
|
|
@@ -217,22 +546,156 @@ function App() {
|
|
|
217
546
|
}
|
|
218
547
|
```
|
|
219
548
|
|
|
549
|
+
### Multi-Step Process with Priority
|
|
550
|
+
|
|
551
|
+
```jsx
|
|
552
|
+
function MultiStepWizard() {
|
|
553
|
+
const [step, setStep] = useState(1);
|
|
554
|
+
|
|
555
|
+
// Higher priority for payment step
|
|
556
|
+
useBlocker(
|
|
557
|
+
"payment-step",
|
|
558
|
+
{
|
|
559
|
+
scope: ["navigation", "form"],
|
|
560
|
+
reason: "Processing payment",
|
|
561
|
+
priority: 100,
|
|
562
|
+
},
|
|
563
|
+
step === 3
|
|
564
|
+
);
|
|
565
|
+
|
|
566
|
+
// Lower priority for other steps
|
|
567
|
+
useBlocker(
|
|
568
|
+
"wizard-step",
|
|
569
|
+
{
|
|
570
|
+
scope: "navigation",
|
|
571
|
+
reason: "Wizard in progress",
|
|
572
|
+
priority: 50,
|
|
573
|
+
},
|
|
574
|
+
step < 3
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
return <div>Step {step}</div>;
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### Scheduled Maintenance Window
|
|
582
|
+
|
|
583
|
+
```jsx
|
|
584
|
+
import { useScheduledBlocker } from "@okyrychenko-dev/react-action-guard";
|
|
585
|
+
|
|
586
|
+
function App() {
|
|
587
|
+
useScheduledBlocker("weekly-maintenance", {
|
|
588
|
+
scope: "global",
|
|
589
|
+
reason: "Weekly system maintenance",
|
|
590
|
+
priority: 500,
|
|
591
|
+
schedule: {
|
|
592
|
+
start: new Date("2024-01-21T03:00:00Z"),
|
|
593
|
+
duration: 1800000, // 30 minutes
|
|
594
|
+
},
|
|
595
|
+
onScheduleStart: () => {
|
|
596
|
+
showNotification("System maintenance in progress");
|
|
597
|
+
},
|
|
598
|
+
onScheduleEnd: () => {
|
|
599
|
+
showNotification("Maintenance completed");
|
|
600
|
+
window.location.reload();
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
return <YourApp />;
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### Dynamic Blocking Based on State
|
|
609
|
+
|
|
610
|
+
```jsx
|
|
611
|
+
import { useConditionalBlocker } from "@okyrychenko-dev/react-action-guard";
|
|
612
|
+
|
|
613
|
+
function StorageQuotaGuard() {
|
|
614
|
+
const [storageUsed, setStorageUsed] = useState(0);
|
|
615
|
+
const STORAGE_LIMIT = 1000000; // 1MB
|
|
616
|
+
|
|
617
|
+
useConditionalBlocker("storage-limit", {
|
|
618
|
+
scope: ["upload", "save"],
|
|
619
|
+
reason: "Storage quota exceeded",
|
|
620
|
+
priority: 200,
|
|
621
|
+
condition: () => storageUsed > STORAGE_LIMIT,
|
|
622
|
+
state: storageUsed,
|
|
623
|
+
checkInterval: 5000, // Check every 5 seconds
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
return (
|
|
627
|
+
<div>
|
|
628
|
+
<p>
|
|
629
|
+
Storage used: {storageUsed} / {STORAGE_LIMIT} bytes
|
|
630
|
+
</p>
|
|
631
|
+
<UploadButton />
|
|
632
|
+
</div>
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
```
|
|
636
|
+
|
|
220
637
|
## Development
|
|
221
638
|
|
|
222
639
|
```bash
|
|
223
640
|
# Install dependencies
|
|
224
641
|
npm install
|
|
225
642
|
|
|
643
|
+
# Run tests
|
|
644
|
+
npm run test
|
|
645
|
+
|
|
646
|
+
# Run tests with coverage
|
|
647
|
+
npm run test:coverage
|
|
648
|
+
|
|
226
649
|
# Build the package
|
|
227
650
|
npm run build
|
|
228
651
|
|
|
229
652
|
# Type checking
|
|
230
653
|
npm run typecheck
|
|
231
654
|
|
|
655
|
+
# Lint code
|
|
656
|
+
npm run lint
|
|
657
|
+
|
|
658
|
+
# Fix lint errors
|
|
659
|
+
npm run lint:fix
|
|
660
|
+
|
|
661
|
+
# Format code
|
|
662
|
+
npm run format
|
|
663
|
+
|
|
232
664
|
# Watch mode for development
|
|
233
665
|
npm run dev
|
|
234
666
|
```
|
|
235
667
|
|
|
668
|
+
## Contributing
|
|
669
|
+
|
|
670
|
+
Contributions are welcome! Please ensure:
|
|
671
|
+
|
|
672
|
+
1. All tests pass (`npm run test`)
|
|
673
|
+
2. Code is properly typed (`npm run typecheck`)
|
|
674
|
+
3. Linting passes (`npm run lint`)
|
|
675
|
+
4. Code is formatted (`npm run format`)
|
|
676
|
+
|
|
677
|
+
## Changelog
|
|
678
|
+
|
|
679
|
+
### v0.2.0
|
|
680
|
+
|
|
681
|
+
- Added middleware system (analytics, logging, performance)
|
|
682
|
+
- Added advanced hooks:
|
|
683
|
+
- `useConfirmableBlocker` - blockers with user confirmation
|
|
684
|
+
- `useScheduledBlocker` - time-based blocking for maintenance windows
|
|
685
|
+
- `useConditionalBlocker` - dynamic blocking based on conditions
|
|
686
|
+
- Added `updateBlocker` method to store
|
|
687
|
+
- Improved TypeScript types with discriminated unions
|
|
688
|
+
- Added tree-shaking support
|
|
689
|
+
- Added support for Amplitude analytics provider
|
|
690
|
+
- Performance improvements and code refactoring
|
|
691
|
+
|
|
692
|
+
### v0.1.0
|
|
693
|
+
|
|
694
|
+
- Initial release
|
|
695
|
+
- Basic blocking system with priorities and scopes
|
|
696
|
+
- `useBlocker`, `useIsBlocked`, `useAsyncAction` hooks
|
|
697
|
+
- Zustand-based state management
|
|
698
|
+
|
|
236
699
|
## License
|
|
237
700
|
|
|
238
701
|
MIT © Olexii Kyrychenko
|
package/package.json
CHANGED