@okyrychenko-dev/react-action-guard 0.1.0 → 0.2.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 +33 -37
- package/dist/index.cjs +361 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +162 -2
- package/dist/index.d.ts +162 -2
- package/dist/index.js +357 -2
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -20,16 +20,12 @@ npm install @okyrychenko-dev/react-action-guard zustand
|
|
|
20
20
|
## Quick Start
|
|
21
21
|
|
|
22
22
|
```jsx
|
|
23
|
-
import { useBlocker, useIsBlocked } from
|
|
23
|
+
import { useBlocker, useIsBlocked } from "@okyrychenko-dev/react-action-guard";
|
|
24
24
|
|
|
25
25
|
function MyComponent() {
|
|
26
|
-
const isBlocked = useIsBlocked(
|
|
26
|
+
const isBlocked = useIsBlocked("my-scope");
|
|
27
27
|
|
|
28
|
-
return
|
|
29
|
-
<button disabled={isBlocked}>
|
|
30
|
-
Click me
|
|
31
|
-
</button>
|
|
32
|
-
);
|
|
28
|
+
return <button disabled={isBlocked}>Click me</button>;
|
|
33
29
|
}
|
|
34
30
|
```
|
|
35
31
|
|
|
@@ -42,6 +38,7 @@ function MyComponent() {
|
|
|
42
38
|
Automatically adds a blocker when the component mounts and removes it on unmount.
|
|
43
39
|
|
|
44
40
|
**Parameters:**
|
|
41
|
+
|
|
45
42
|
- `blockerId: string` - Unique identifier for the blocker
|
|
46
43
|
- `config: BlockerConfig` - Configuration object
|
|
47
44
|
- `scope?: string | string[]` - Scope(s) to block (default: "global")
|
|
@@ -53,10 +50,10 @@ Automatically adds a blocker when the component mounts and removes it on unmount
|
|
|
53
50
|
|
|
54
51
|
```jsx
|
|
55
52
|
function MyComponent() {
|
|
56
|
-
useBlocker(
|
|
57
|
-
scope:
|
|
58
|
-
reason:
|
|
59
|
-
priority: 10
|
|
53
|
+
useBlocker("my-blocker", {
|
|
54
|
+
scope: "form",
|
|
55
|
+
reason: "Form is saving",
|
|
56
|
+
priority: 10,
|
|
60
57
|
});
|
|
61
58
|
|
|
62
59
|
return <div>Content</div>;
|
|
@@ -68,6 +65,7 @@ function MyComponent() {
|
|
|
68
65
|
Checks if a specific scope (or scopes) is currently blocked.
|
|
69
66
|
|
|
70
67
|
**Parameters:**
|
|
68
|
+
|
|
71
69
|
- `scope?: string | string[]` - Scope(s) to check (default: "global")
|
|
72
70
|
|
|
73
71
|
**Returns:** `boolean` - Whether the scope is blocked
|
|
@@ -76,13 +74,9 @@ Checks if a specific scope (or scopes) is currently blocked.
|
|
|
76
74
|
|
|
77
75
|
```jsx
|
|
78
76
|
function SubmitButton() {
|
|
79
|
-
const isFormBlocked = useIsBlocked(
|
|
77
|
+
const isFormBlocked = useIsBlocked("form");
|
|
80
78
|
|
|
81
|
-
return
|
|
82
|
-
<button disabled={isFormBlocked}>
|
|
83
|
-
Submit
|
|
84
|
-
</button>
|
|
85
|
-
);
|
|
79
|
+
return <button disabled={isFormBlocked}>Submit</button>;
|
|
86
80
|
}
|
|
87
81
|
```
|
|
88
82
|
|
|
@@ -91,6 +85,7 @@ function SubmitButton() {
|
|
|
91
85
|
Wraps an async function with automatic blocking/unblocking.
|
|
92
86
|
|
|
93
87
|
**Parameters:**
|
|
88
|
+
|
|
94
89
|
- `actionId: string` - Identifier for the action
|
|
95
90
|
- `scope?: string | string[]` - Scope(s) to block during execution
|
|
96
91
|
|
|
@@ -100,7 +95,7 @@ Wraps an async function with automatic blocking/unblocking.
|
|
|
100
95
|
|
|
101
96
|
```jsx
|
|
102
97
|
function MyComponent() {
|
|
103
|
-
const executeWithBlocking = useAsyncAction(
|
|
98
|
+
const executeWithBlocking = useAsyncAction("save-data", "form");
|
|
104
99
|
|
|
105
100
|
const handleSave = async () => {
|
|
106
101
|
await executeWithBlocking(async () => {
|
|
@@ -119,6 +114,7 @@ function MyComponent() {
|
|
|
119
114
|
Direct access to the Zustand store for advanced use cases.
|
|
120
115
|
|
|
121
116
|
**Methods:**
|
|
117
|
+
|
|
122
118
|
- `addBlocker(id, config)` - Manually add a blocker
|
|
123
119
|
- `removeBlocker(id)` - Manually remove a blocker
|
|
124
120
|
- `isBlocked(scope)` - Check if scope is blocked
|
|
@@ -129,21 +125,21 @@ Direct access to the Zustand store for advanced use cases.
|
|
|
129
125
|
**Example:**
|
|
130
126
|
|
|
131
127
|
```jsx
|
|
132
|
-
import { useUIBlockingStore } from
|
|
128
|
+
import { useUIBlockingStore } from "@okyrychenko-dev/react-action-guard";
|
|
133
129
|
|
|
134
130
|
function AdvancedComponent() {
|
|
135
131
|
const { addBlocker, removeBlocker } = useUIBlockingStore();
|
|
136
132
|
|
|
137
133
|
const startBlocking = () => {
|
|
138
|
-
addBlocker(
|
|
139
|
-
scope: [
|
|
140
|
-
reason:
|
|
141
|
-
priority: 100
|
|
134
|
+
addBlocker("custom-blocker", {
|
|
135
|
+
scope: ["form", "navigation"],
|
|
136
|
+
reason: "Critical operation in progress",
|
|
137
|
+
priority: 100,
|
|
142
138
|
});
|
|
143
139
|
};
|
|
144
140
|
|
|
145
141
|
const stopBlocking = () => {
|
|
146
|
-
removeBlocker(
|
|
142
|
+
removeBlocker("custom-blocker");
|
|
147
143
|
};
|
|
148
144
|
|
|
149
145
|
return (
|
|
@@ -160,11 +156,7 @@ function AdvancedComponent() {
|
|
|
160
156
|
The package is written in TypeScript and includes full type definitions.
|
|
161
157
|
|
|
162
158
|
```typescript
|
|
163
|
-
import type {
|
|
164
|
-
BlockerConfig,
|
|
165
|
-
BlockerInfo,
|
|
166
|
-
UIBlockingState
|
|
167
|
-
} from '@okyrychenko-dev/react-action-guard';
|
|
159
|
+
import type { BlockerConfig, BlockerInfo, UIBlockingState } from "@okyrychenko-dev/react-action-guard";
|
|
168
160
|
```
|
|
169
161
|
|
|
170
162
|
## Use Cases
|
|
@@ -175,10 +167,14 @@ import type {
|
|
|
175
167
|
function DataLoader() {
|
|
176
168
|
const [isLoading, setIsLoading] = useState(false);
|
|
177
169
|
|
|
178
|
-
useBlocker(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
170
|
+
useBlocker(
|
|
171
|
+
"data-loader",
|
|
172
|
+
{
|
|
173
|
+
scope: "content",
|
|
174
|
+
reason: "Loading data",
|
|
175
|
+
},
|
|
176
|
+
isLoading
|
|
177
|
+
);
|
|
182
178
|
|
|
183
179
|
// ... rest of component
|
|
184
180
|
}
|
|
@@ -188,8 +184,8 @@ function DataLoader() {
|
|
|
188
184
|
|
|
189
185
|
```jsx
|
|
190
186
|
function UserForm() {
|
|
191
|
-
const executeWithBlocking = useAsyncAction(
|
|
192
|
-
const isBlocked = useIsBlocked(
|
|
187
|
+
const executeWithBlocking = useAsyncAction("submit-form", "form");
|
|
188
|
+
const isBlocked = useIsBlocked("form");
|
|
193
189
|
|
|
194
190
|
const handleSubmit = async (data) => {
|
|
195
191
|
await executeWithBlocking(async () => {
|
|
@@ -210,7 +206,7 @@ function UserForm() {
|
|
|
210
206
|
|
|
211
207
|
```jsx
|
|
212
208
|
function App() {
|
|
213
|
-
const isGloballyBlocked = useIsBlocked(
|
|
209
|
+
const isGloballyBlocked = useIsBlocked("global");
|
|
214
210
|
|
|
215
211
|
return (
|
|
216
212
|
<div>
|
|
@@ -239,4 +235,4 @@ npm run dev
|
|
|
239
235
|
|
|
240
236
|
## License
|
|
241
237
|
|
|
242
|
-
MIT ©
|
|
238
|
+
MIT © Olexii Kyrychenko
|
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,33 @@ function normalizeScopeToArray(scope) {
|
|
|
25
25
|
var createUIBlockingActions = (set, get) => ({
|
|
26
26
|
// State
|
|
27
27
|
activeBlockers: /* @__PURE__ */ new Map(),
|
|
28
|
+
middlewares: /* @__PURE__ */ new Map(),
|
|
29
|
+
registerMiddleware: (name, middleware) => {
|
|
30
|
+
set((state) => {
|
|
31
|
+
const newMiddlewares = new Map(state.middlewares);
|
|
32
|
+
newMiddlewares.set(name, middleware);
|
|
33
|
+
return { middlewares: newMiddlewares };
|
|
34
|
+
});
|
|
35
|
+
console.log(`[UIBlocking] Registered middleware: ${name}`);
|
|
36
|
+
},
|
|
37
|
+
unregisterMiddleware: (name) => {
|
|
38
|
+
set((state) => {
|
|
39
|
+
const newMiddlewares = new Map(state.middlewares);
|
|
40
|
+
newMiddlewares.delete(name);
|
|
41
|
+
return { middlewares: newMiddlewares };
|
|
42
|
+
});
|
|
43
|
+
console.log(`[UIBlocking] Unregistered middleware: ${name}`);
|
|
44
|
+
},
|
|
45
|
+
runMiddlewares: async (context) => {
|
|
46
|
+
const { middlewares } = get();
|
|
47
|
+
for (const [name, middleware] of middlewares) {
|
|
48
|
+
try {
|
|
49
|
+
await middleware(context);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(`[UIBlocking] Middleware "${name}" error:`, error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
28
55
|
// Actions
|
|
29
56
|
/**
|
|
30
57
|
* Add a blocker to the store
|
|
@@ -45,6 +72,16 @@ var createUIBlockingActions = (set, get) => ({
|
|
|
45
72
|
newBlockers.set(id, storedBlocker);
|
|
46
73
|
return { activeBlockers: newBlockers };
|
|
47
74
|
});
|
|
75
|
+
void get().runMiddlewares({
|
|
76
|
+
action: "add",
|
|
77
|
+
blockerId: id,
|
|
78
|
+
config: {
|
|
79
|
+
scope: config.scope,
|
|
80
|
+
reason: config.reason,
|
|
81
|
+
priority: config.priority
|
|
82
|
+
},
|
|
83
|
+
timestamp: Date.now()
|
|
84
|
+
});
|
|
48
85
|
},
|
|
49
86
|
/**
|
|
50
87
|
* Remove a blocker from the store
|
|
@@ -53,11 +90,21 @@ var createUIBlockingActions = (set, get) => ({
|
|
|
53
90
|
*
|
|
54
91
|
*/
|
|
55
92
|
removeBlocker: (id) => {
|
|
93
|
+
const prevBlocker = get().activeBlockers.get(id);
|
|
56
94
|
set((state) => {
|
|
57
95
|
const newBlockers = new Map(state.activeBlockers);
|
|
58
96
|
newBlockers.delete(id);
|
|
59
97
|
return { activeBlockers: newBlockers };
|
|
60
98
|
});
|
|
99
|
+
if (prevBlocker) {
|
|
100
|
+
void get().runMiddlewares({
|
|
101
|
+
action: "remove",
|
|
102
|
+
blockerId: id,
|
|
103
|
+
config: prevBlocker,
|
|
104
|
+
timestamp: Date.now(),
|
|
105
|
+
prevState: prevBlocker
|
|
106
|
+
});
|
|
107
|
+
}
|
|
61
108
|
},
|
|
62
109
|
/**
|
|
63
110
|
* Check if a scope is currently blocked
|
|
@@ -173,10 +220,324 @@ var useAsyncAction = (actionId, scope) => {
|
|
|
173
220
|
);
|
|
174
221
|
return executeWithBlocking;
|
|
175
222
|
};
|
|
223
|
+
function useConfigRef(config) {
|
|
224
|
+
const configRef = react.useRef(config);
|
|
225
|
+
react.useEffect(() => {
|
|
226
|
+
configRef.current = config;
|
|
227
|
+
}, [config]);
|
|
228
|
+
return configRef;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/hooks/useBlocker.utils.ts
|
|
232
|
+
function createBlockerConfig(config) {
|
|
233
|
+
return {
|
|
234
|
+
scope: config.scope,
|
|
235
|
+
reason: config.reason,
|
|
236
|
+
priority: config.priority
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/hooks/useConditionalBlocker.ts
|
|
241
|
+
var useConditionalBlocker = (blockerId, config) => {
|
|
242
|
+
const { addBlocker, removeBlocker } = useUIBlockingStore();
|
|
243
|
+
const intervalRef = react.useRef(null);
|
|
244
|
+
const isBlockedRef = react.useRef(false);
|
|
245
|
+
const configRef = useConfigRef(config);
|
|
246
|
+
const checkCondition = react.useCallback(() => {
|
|
247
|
+
const currentConfig = configRef.current;
|
|
248
|
+
const shouldBlock = currentConfig.condition(currentConfig.state);
|
|
249
|
+
if (shouldBlock === isBlockedRef.current) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (shouldBlock) {
|
|
253
|
+
addBlocker(blockerId, createBlockerConfig(currentConfig));
|
|
254
|
+
isBlockedRef.current = true;
|
|
255
|
+
} else {
|
|
256
|
+
removeBlocker(blockerId);
|
|
257
|
+
isBlockedRef.current = false;
|
|
258
|
+
}
|
|
259
|
+
}, [blockerId, addBlocker, removeBlocker, configRef]);
|
|
260
|
+
react.useEffect(() => {
|
|
261
|
+
checkCondition();
|
|
262
|
+
const interval = configRef.current.checkInterval ?? 1e3;
|
|
263
|
+
intervalRef.current = setInterval(checkCondition, interval);
|
|
264
|
+
return () => {
|
|
265
|
+
if (intervalRef.current) {
|
|
266
|
+
clearInterval(intervalRef.current);
|
|
267
|
+
intervalRef.current = null;
|
|
268
|
+
}
|
|
269
|
+
if (isBlockedRef.current) {
|
|
270
|
+
removeBlocker(blockerId);
|
|
271
|
+
isBlockedRef.current = false;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}, [checkCondition, blockerId, removeBlocker]);
|
|
275
|
+
};
|
|
276
|
+
var useConfirmableBlocker = (blockerId, config) => {
|
|
277
|
+
const [isDialogOpen, setIsDialogOpen] = react.useState(false);
|
|
278
|
+
const [isExecuting, setIsExecuting] = react.useState(false);
|
|
279
|
+
useBlocker(
|
|
280
|
+
blockerId,
|
|
281
|
+
{
|
|
282
|
+
...config,
|
|
283
|
+
reason: config.reason ?? config.confirmMessage
|
|
284
|
+
},
|
|
285
|
+
isDialogOpen || isExecuting
|
|
286
|
+
);
|
|
287
|
+
const execute = react.useCallback(() => {
|
|
288
|
+
setIsDialogOpen(true);
|
|
289
|
+
}, []);
|
|
290
|
+
const onConfirm = react.useCallback(async () => {
|
|
291
|
+
setIsDialogOpen(false);
|
|
292
|
+
setIsExecuting(true);
|
|
293
|
+
try {
|
|
294
|
+
await config.onConfirm();
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error("[UIBlocking] Confirmable action error:", error);
|
|
297
|
+
throw error;
|
|
298
|
+
} finally {
|
|
299
|
+
setIsExecuting(false);
|
|
300
|
+
}
|
|
301
|
+
}, [config]);
|
|
302
|
+
const onCancel = react.useCallback(() => {
|
|
303
|
+
setIsDialogOpen(false);
|
|
304
|
+
config.onCancel?.();
|
|
305
|
+
}, [config]);
|
|
306
|
+
return {
|
|
307
|
+
execute,
|
|
308
|
+
isDialogOpen,
|
|
309
|
+
isExecuting,
|
|
310
|
+
confirmConfig: {
|
|
311
|
+
title: config.confirmTitle ?? "Confirm Action",
|
|
312
|
+
message: config.confirmMessage,
|
|
313
|
+
confirmText: config.confirmButtonText ?? "Confirm",
|
|
314
|
+
cancelText: config.cancelButtonText ?? "Cancel"
|
|
315
|
+
},
|
|
316
|
+
onConfirm,
|
|
317
|
+
onCancel
|
|
318
|
+
};
|
|
319
|
+
};
|
|
320
|
+
var useScheduledBlocker = (blockerId, config) => {
|
|
321
|
+
const { addBlocker, removeBlocker } = useUIBlockingStore();
|
|
322
|
+
const timeoutsRef = react.useRef({});
|
|
323
|
+
const configRef = useConfigRef(config);
|
|
324
|
+
const cleanup = react.useCallback(() => {
|
|
325
|
+
if (timeoutsRef.current.start) {
|
|
326
|
+
clearTimeout(timeoutsRef.current.start);
|
|
327
|
+
timeoutsRef.current.start = void 0;
|
|
328
|
+
}
|
|
329
|
+
if (timeoutsRef.current.end) {
|
|
330
|
+
clearTimeout(timeoutsRef.current.end);
|
|
331
|
+
timeoutsRef.current.end = void 0;
|
|
332
|
+
}
|
|
333
|
+
}, []);
|
|
334
|
+
react.useEffect(() => {
|
|
335
|
+
const currentConfig = configRef.current;
|
|
336
|
+
const startTime = new Date(currentConfig.schedule.start).getTime();
|
|
337
|
+
const now = Date.now();
|
|
338
|
+
let endTime;
|
|
339
|
+
if (currentConfig.schedule.duration) {
|
|
340
|
+
endTime = startTime + currentConfig.schedule.duration;
|
|
341
|
+
} else if (currentConfig.schedule.end) {
|
|
342
|
+
endTime = new Date(currentConfig.schedule.end).getTime();
|
|
343
|
+
}
|
|
344
|
+
const scheduleEnd = (end) => {
|
|
345
|
+
const remainingTime = end - Date.now();
|
|
346
|
+
if (remainingTime > 0) {
|
|
347
|
+
timeoutsRef.current.end = setTimeout(() => {
|
|
348
|
+
removeBlocker(blockerId);
|
|
349
|
+
configRef.current.onScheduleEnd?.();
|
|
350
|
+
}, remainingTime);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
if (startTime > now) {
|
|
354
|
+
timeoutsRef.current.start = setTimeout(() => {
|
|
355
|
+
const cfg = configRef.current;
|
|
356
|
+
addBlocker(blockerId, createBlockerConfig(cfg));
|
|
357
|
+
cfg.onScheduleStart?.();
|
|
358
|
+
if (endTime) {
|
|
359
|
+
scheduleEnd(endTime);
|
|
360
|
+
}
|
|
361
|
+
}, startTime - now);
|
|
362
|
+
} else if (endTime && endTime > now) {
|
|
363
|
+
addBlocker(blockerId, createBlockerConfig(currentConfig));
|
|
364
|
+
currentConfig.onScheduleStart?.();
|
|
365
|
+
scheduleEnd(endTime);
|
|
366
|
+
} else if (!endTime && startTime <= now) {
|
|
367
|
+
addBlocker(blockerId, createBlockerConfig(currentConfig));
|
|
368
|
+
currentConfig.onScheduleStart?.();
|
|
369
|
+
} else {
|
|
370
|
+
console.warn(`[UIBlocking] Schedule is in the past for blocker "${blockerId}"`);
|
|
371
|
+
}
|
|
372
|
+
return () => {
|
|
373
|
+
cleanup();
|
|
374
|
+
removeBlocker(blockerId);
|
|
375
|
+
};
|
|
376
|
+
}, [blockerId, addBlocker, removeBlocker, cleanup]);
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// src/middleware/loggerMiddleware.utils.ts
|
|
380
|
+
var getActionEmoji = (action) => {
|
|
381
|
+
switch (action) {
|
|
382
|
+
case "add":
|
|
383
|
+
return "\u2795";
|
|
384
|
+
case "remove":
|
|
385
|
+
return "\u2796";
|
|
386
|
+
case "update":
|
|
387
|
+
return "\u{1F504}";
|
|
388
|
+
case "cancel":
|
|
389
|
+
return "\u274C";
|
|
390
|
+
case "timeout":
|
|
391
|
+
return "\u23F1\uFE0F";
|
|
392
|
+
default:
|
|
393
|
+
return "\u2753";
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
var extractConfigDetails = (config) => {
|
|
397
|
+
if (!config) {
|
|
398
|
+
return {};
|
|
399
|
+
}
|
|
400
|
+
const { scope, reason, priority } = config;
|
|
401
|
+
const details = {
|
|
402
|
+
scope,
|
|
403
|
+
reason
|
|
404
|
+
};
|
|
405
|
+
if (priority !== void 0) {
|
|
406
|
+
details.priority = priority;
|
|
407
|
+
}
|
|
408
|
+
return details;
|
|
409
|
+
};
|
|
410
|
+
var formatLogData = (context) => {
|
|
411
|
+
const configDetails = extractConfigDetails(context.config);
|
|
412
|
+
if (context.prevState) {
|
|
413
|
+
return {
|
|
414
|
+
config: configDetails,
|
|
415
|
+
prevState: extractConfigDetails(context.prevState)
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
return configDetails;
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
// src/middleware/loggerMiddleware.ts
|
|
422
|
+
var LOG_PREFIX = "[UIBlocking]";
|
|
423
|
+
var loggerMiddleware = (context) => {
|
|
424
|
+
const emoji = getActionEmoji(context.action);
|
|
425
|
+
const logData = formatLogData(context);
|
|
426
|
+
console.log(LOG_PREFIX, emoji, context.blockerId, logData);
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// src/middleware/analyticsMiddleware.utils.ts
|
|
430
|
+
var capitalizeFirst = (str) => {
|
|
431
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
432
|
+
};
|
|
433
|
+
var buildEventData = (context) => {
|
|
434
|
+
const eventData = {
|
|
435
|
+
blocker_id: context.blockerId
|
|
436
|
+
};
|
|
437
|
+
const { scope, reason, priority } = context.config ?? {};
|
|
438
|
+
if (scope !== void 0) {
|
|
439
|
+
eventData.scope = Array.isArray(scope) ? scope.join(",") : scope;
|
|
440
|
+
}
|
|
441
|
+
if (reason !== void 0) {
|
|
442
|
+
eventData.reason = reason;
|
|
443
|
+
}
|
|
444
|
+
if (priority !== void 0) {
|
|
445
|
+
eventData.priority = priority;
|
|
446
|
+
}
|
|
447
|
+
return eventData;
|
|
448
|
+
};
|
|
449
|
+
var trackGoogleAnalytics = (action, eventData) => {
|
|
450
|
+
if (window.gtag) {
|
|
451
|
+
window.gtag("event", `ui_blocking_${action}`, eventData);
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
var trackMixpanel = (action, eventData) => {
|
|
455
|
+
if (window.mixpanel) {
|
|
456
|
+
const eventName = `UI Blocking ${action.split("_").map(capitalizeFirst).join(" ")}`;
|
|
457
|
+
window.mixpanel.track(eventName, eventData);
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
var trackAmplitude = (action, eventData) => {
|
|
461
|
+
if (window.amplitude) {
|
|
462
|
+
const eventName = `UI Blocking ${action.split("_").map(capitalizeFirst).join(" ")}`;
|
|
463
|
+
window.amplitude.track(eventName, eventData);
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
var trackCustom = (trackFn, action, eventData) => {
|
|
467
|
+
try {
|
|
468
|
+
trackFn(`ui_blocking_${action}`, eventData);
|
|
469
|
+
} catch {
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// src/middleware/analyticsMiddleware.ts
|
|
474
|
+
var trackEvent = (config, context, eventData) => {
|
|
475
|
+
if ("provider" in config) {
|
|
476
|
+
switch (config.provider) {
|
|
477
|
+
case "ga":
|
|
478
|
+
trackGoogleAnalytics(context.action, eventData);
|
|
479
|
+
return;
|
|
480
|
+
case "mixpanel":
|
|
481
|
+
trackMixpanel(context.action, eventData);
|
|
482
|
+
return;
|
|
483
|
+
case "amplitude":
|
|
484
|
+
trackAmplitude(context.action, eventData);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if ("track" in config) {
|
|
489
|
+
trackCustom(config.track, context.action, eventData);
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
var createAnalyticsMiddleware = (config) => {
|
|
493
|
+
return (context) => {
|
|
494
|
+
const eventData = buildEventData(context);
|
|
495
|
+
trackEvent(config, context, eventData);
|
|
496
|
+
};
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
// src/middleware/performanceMiddleware.utils.ts
|
|
500
|
+
var handleAddAction = (blockerId, timestamp, blockStartTimes) => {
|
|
501
|
+
blockStartTimes.set(blockerId, timestamp);
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
// src/middleware/performanceMiddleware.ts
|
|
505
|
+
var DEFAULT_SLOW_BLOCK_THRESHOLD = 3e3;
|
|
506
|
+
var ACTION_ADD = "add";
|
|
507
|
+
var ACTION_REMOVE = "remove";
|
|
508
|
+
var handleRemoveAction = (context, blockStartTimes, slowBlockThreshold, onSlowBlock) => {
|
|
509
|
+
const startTime = blockStartTimes.get(context.blockerId);
|
|
510
|
+
if (startTime === void 0) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const duration = context.timestamp - startTime;
|
|
514
|
+
blockStartTimes.delete(context.blockerId);
|
|
515
|
+
if (duration >= slowBlockThreshold) {
|
|
516
|
+
console.warn(`[UIBlocking] Slow block detected: "${context.blockerId}" took ${duration.toString()}ms`);
|
|
517
|
+
onSlowBlock?.(context.blockerId, duration);
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
var createPerformanceMiddleware = (config = {}) => {
|
|
521
|
+
const { slowBlockThreshold = DEFAULT_SLOW_BLOCK_THRESHOLD, onSlowBlock } = config;
|
|
522
|
+
const blockStartTimes = /* @__PURE__ */ new Map();
|
|
523
|
+
return (context) => {
|
|
524
|
+
if (context.action === ACTION_ADD) {
|
|
525
|
+
handleAddAction(context.blockerId, context.timestamp, blockStartTimes);
|
|
526
|
+
} else if (context.action === ACTION_REMOVE) {
|
|
527
|
+
handleRemoveAction(context, blockStartTimes, slowBlockThreshold, onSlowBlock);
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
};
|
|
176
531
|
|
|
532
|
+
exports.createAnalyticsMiddleware = createAnalyticsMiddleware;
|
|
533
|
+
exports.createPerformanceMiddleware = createPerformanceMiddleware;
|
|
534
|
+
exports.loggerMiddleware = loggerMiddleware;
|
|
177
535
|
exports.useAsyncAction = useAsyncAction;
|
|
178
536
|
exports.useBlocker = useBlocker;
|
|
537
|
+
exports.useConditionalBlocker = useConditionalBlocker;
|
|
538
|
+
exports.useConfirmableBlocker = useConfirmableBlocker;
|
|
179
539
|
exports.useIsBlocked = useIsBlocked;
|
|
540
|
+
exports.useScheduledBlocker = useScheduledBlocker;
|
|
180
541
|
exports.useUIBlockingStore = useUIBlockingStore;
|
|
181
542
|
//# sourceMappingURL=index.cjs.map
|
|
182
543
|
//# sourceMappingURL=index.cjs.map
|