@okyrychenko-dev/react-action-guard 0.4.0 → 0.6.0

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.cjs CHANGED
@@ -1,10 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  var react = require('react');
4
+ var reactZustandToolkit = require('@okyrychenko-dev/react-zustand-toolkit');
4
5
  var zustand = require('zustand');
5
- var middleware = require('zustand/middleware');
6
- var shallow = require('zustand/shallow');
6
+ require('zustand/react/shallow');
7
7
  var jsxRuntime = require('react/jsx-runtime');
8
+ var shallow = require('zustand/shallow');
9
+ var middleware = require('zustand/middleware');
8
10
 
9
11
  // src/hooks/useAsyncAction/useAsyncAction.ts
10
12
 
@@ -14,13 +16,6 @@ var DEFAULT_REASON = "Unknown";
14
16
  var DEFAULT_PRIORITY = 0;
15
17
  var ASYNC_ACTION_PRIORITY = 20;
16
18
  var DEVTOOLS_NAME = "UIBlocking";
17
- function createShallowStore(storeCreator) {
18
- const storeApi = zustand.createStore(storeCreator);
19
- const useScopedStore = (selector) => {
20
- return zustand.useStore(storeApi, shallow.useShallow(selector));
21
- };
22
- return { useStore: useScopedStore, useStoreApi: storeApi };
23
- }
24
19
  function normalizeScopeToArray(scope) {
25
20
  if (typeof scope === "string") {
26
21
  return [scope];
@@ -29,6 +24,16 @@ function normalizeScopeToArray(scope) {
29
24
  }
30
25
 
31
26
  // src/store/uiBlockingStore.actions.ts
27
+ var normalizePriority = (priority, fallback = DEFAULT_PRIORITY) => {
28
+ return priority !== void 0 ? Math.max(0, priority) : fallback;
29
+ };
30
+ var createMiddlewareContext = (action, blockerId, config, prevState) => ({
31
+ action,
32
+ blockerId,
33
+ config,
34
+ timestamp: Date.now(),
35
+ ...prevState && { prevState }
36
+ });
32
37
  var createUIBlockingActions = (set, get) => ({
33
38
  // State
34
39
  activeBlockers: /* @__PURE__ */ new Map(),
@@ -39,7 +44,6 @@ var createUIBlockingActions = (set, get) => ({
39
44
  newMiddlewares.set(name, middleware);
40
45
  return { middlewares: newMiddlewares };
41
46
  });
42
- console.log(`[UIBlocking] Registered middleware: ${name}`);
43
47
  },
44
48
  unregisterMiddleware: (name) => {
45
49
  set((state) => {
@@ -47,7 +51,6 @@ var createUIBlockingActions = (set, get) => ({
47
51
  newMiddlewares.delete(name);
48
52
  return { middlewares: newMiddlewares };
49
53
  });
50
- console.log(`[UIBlocking] Unregistered middleware: ${name}`);
51
54
  },
52
55
  runMiddlewares: async (context) => {
53
56
  const { middlewares } = get();
@@ -77,17 +80,14 @@ var createUIBlockingActions = (set, get) => ({
77
80
  timeoutId = setTimeout(() => {
78
81
  if (get().activeBlockers.has(id)) {
79
82
  config.onTimeout?.(id);
80
- void get().runMiddlewares({
81
- action: "timeout",
82
- blockerId: id,
83
- config: {
83
+ void get().runMiddlewares(
84
+ createMiddlewareContext("timeout", id, {
84
85
  scope: config.scope,
85
86
  reason: config.reason,
86
87
  priority: config.priority,
87
88
  timeout: config.timeout
88
- },
89
- timestamp: Date.now()
90
- });
89
+ })
90
+ );
91
91
  get().removeBlocker(id);
92
92
  }
93
93
  }, config.timeout);
@@ -97,7 +97,7 @@ var createUIBlockingActions = (set, get) => ({
97
97
  const storedBlocker = {
98
98
  scope: config.scope ?? DEFAULT_SCOPE,
99
99
  reason: config.reason ?? DEFAULT_REASON,
100
- priority: config.priority ?? DEFAULT_PRIORITY,
100
+ priority: normalizePriority(config.priority),
101
101
  timestamp: config.timestamp ?? Date.now(),
102
102
  timeout: config.timeout,
103
103
  timeoutId,
@@ -106,17 +106,92 @@ var createUIBlockingActions = (set, get) => ({
106
106
  newBlockers.set(id, storedBlocker);
107
107
  return { activeBlockers: newBlockers };
108
108
  });
109
- void get().runMiddlewares({
110
- action: "add",
111
- blockerId: id,
112
- config: {
109
+ void get().runMiddlewares(
110
+ createMiddlewareContext("add", id, {
113
111
  scope: config.scope,
114
112
  reason: config.reason,
115
113
  priority: config.priority,
116
114
  timeout: config.timeout
117
- },
118
- timestamp: Date.now()
115
+ })
116
+ );
117
+ },
118
+ /**
119
+ * Update blocker metadata. If timeout is updated, the timer will be restarted.
120
+ *
121
+ * @param id - Unique blocker identifier
122
+ * @param config - Partial blocker configuration to merge
123
+ *
124
+ */
125
+ updateBlocker: (id, config = {}) => {
126
+ const existingBlocker = get().activeBlockers.get(id);
127
+ if (!existingBlocker) {
128
+ get().addBlocker(id, config);
129
+ return;
130
+ }
131
+ let timeoutId = existingBlocker.timeoutId;
132
+ const timeoutChanged = config.timeout !== void 0 && config.timeout !== existingBlocker.timeout;
133
+ if (timeoutChanged) {
134
+ if (existingBlocker.timeoutId) {
135
+ clearTimeout(existingBlocker.timeoutId);
136
+ }
137
+ if (config.timeout && config.timeout > 0) {
138
+ const newTimeout = config.timeout;
139
+ const newOnTimeout = config.onTimeout ?? existingBlocker.onTimeout;
140
+ timeoutId = setTimeout(() => {
141
+ if (get().activeBlockers.has(id)) {
142
+ newOnTimeout?.(id);
143
+ void get().runMiddlewares(
144
+ createMiddlewareContext("timeout", id, {
145
+ scope: config.scope ?? existingBlocker.scope,
146
+ reason: config.reason ?? existingBlocker.reason,
147
+ priority: config.priority ?? existingBlocker.priority,
148
+ timeout: newTimeout
149
+ })
150
+ );
151
+ get().removeBlocker(id);
152
+ }
153
+ }, newTimeout);
154
+ } else {
155
+ timeoutId = void 0;
156
+ }
157
+ }
158
+ const updatedBlocker = {
159
+ ...existingBlocker,
160
+ scope: config.scope ?? existingBlocker.scope,
161
+ reason: config.reason ?? existingBlocker.reason,
162
+ priority: normalizePriority(config.priority, existingBlocker.priority),
163
+ timestamp: config.timestamp ?? existingBlocker.timestamp,
164
+ timeout: config.timeout ?? existingBlocker.timeout,
165
+ timeoutId,
166
+ onTimeout: config.onTimeout ?? existingBlocker.onTimeout
167
+ };
168
+ set((state) => {
169
+ const newBlockers = new Map(state.activeBlockers);
170
+ newBlockers.set(id, updatedBlocker);
171
+ return { activeBlockers: newBlockers };
119
172
  });
173
+ void get().runMiddlewares(
174
+ createMiddlewareContext(
175
+ "update",
176
+ id,
177
+ {
178
+ scope: updatedBlocker.scope,
179
+ reason: updatedBlocker.reason,
180
+ priority: updatedBlocker.priority,
181
+ timeout: updatedBlocker.timeout,
182
+ onTimeout: updatedBlocker.onTimeout,
183
+ timestamp: updatedBlocker.timestamp
184
+ },
185
+ {
186
+ scope: existingBlocker.scope,
187
+ reason: existingBlocker.reason,
188
+ priority: existingBlocker.priority,
189
+ timeout: existingBlocker.timeout,
190
+ onTimeout: existingBlocker.onTimeout,
191
+ timestamp: existingBlocker.timestamp
192
+ }
193
+ )
194
+ );
120
195
  },
121
196
  /**
122
197
  * Remove a blocker from the store
@@ -135,13 +210,7 @@ var createUIBlockingActions = (set, get) => ({
135
210
  return { activeBlockers: newBlockers };
136
211
  });
137
212
  if (prevBlocker) {
138
- void get().runMiddlewares({
139
- action: "remove",
140
- blockerId: id,
141
- config: prevBlocker,
142
- timestamp: Date.now(),
143
- prevState: prevBlocker
144
- });
213
+ void get().runMiddlewares(createMiddlewareContext("remove", id, prevBlocker, prevBlocker));
145
214
  }
146
215
  },
147
216
  /**
@@ -188,12 +257,21 @@ var createUIBlockingActions = (set, get) => ({
188
257
  */
189
258
  clearAllBlockers: () => {
190
259
  const { activeBlockers } = get();
260
+ const count = activeBlockers.size;
191
261
  for (const [, blocker] of activeBlockers) {
192
262
  if (blocker.timeoutId) {
193
263
  clearTimeout(blocker.timeoutId);
194
264
  }
195
265
  }
196
266
  set({ activeBlockers: /* @__PURE__ */ new Map() });
267
+ if (count > 0) {
268
+ void get().runMiddlewares({
269
+ action: "clear",
270
+ blockerId: "*",
271
+ timestamp: Date.now(),
272
+ count
273
+ });
274
+ }
197
275
  },
198
276
  /**
199
277
  * Clear all blockers for a specific scope
@@ -203,10 +281,14 @@ var createUIBlockingActions = (set, get) => ({
203
281
  */
204
282
  clearBlockersForScope: (scope) => {
205
283
  const { activeBlockers } = get();
284
+ let count = 0;
206
285
  for (const [, blocker] of activeBlockers) {
207
286
  const blockerScopes = normalizeScopeToArray(blocker.scope);
208
- if (blockerScopes.includes(scope) && blocker.timeoutId) {
209
- clearTimeout(blocker.timeoutId);
287
+ if (blockerScopes.includes(scope)) {
288
+ if (blocker.timeoutId) {
289
+ clearTimeout(blocker.timeoutId);
290
+ }
291
+ count++;
210
292
  }
211
293
  }
212
294
  set((state) => {
@@ -219,46 +301,44 @@ var createUIBlockingActions = (set, get) => ({
219
301
  }
220
302
  return { activeBlockers: newBlockers };
221
303
  });
304
+ if (count > 0) {
305
+ void get().runMiddlewares({
306
+ action: "clear_scope",
307
+ blockerId: "*",
308
+ timestamp: Date.now(),
309
+ scope,
310
+ count
311
+ });
312
+ }
222
313
  }
223
314
  });
224
- var UIBlockingContext = react.createContext(null);
315
+ var {
316
+ Provider: BaseUIBlockingProvider,
317
+ useContext: useUIBlockingContext,
318
+ useContextStore: useUIBlockingStoreFromContext,
319
+ useIsInsideProvider: useIsInsideUIBlockingProvider,
320
+ useOptionalContext
321
+ } = reactZustandToolkit.createStoreProvider(createUIBlockingActions, "UIBlocking");
225
322
  function UIBlockingProvider({
226
323
  children,
227
324
  enableDevtools = process.env.NODE_ENV === "development",
228
325
  devtoolsName = "UIBlocking",
229
326
  middlewares = []
230
327
  }) {
231
- const middlewaresRef = react.useRef(middlewares);
232
- const store = react.useMemo(() => {
233
- const baseStore = enableDevtools ? zustand.createStore(
234
- middleware.devtools(createUIBlockingActions, {
235
- name: devtoolsName,
236
- enabled: enableDevtools
237
- })
238
- ) : zustand.createStore(createUIBlockingActions);
239
- middlewaresRef.current.forEach((mw, index) => {
240
- baseStore.getState().registerMiddleware(`provider-middleware-${index.toString()}`, mw);
328
+ const handleStoreCreate = (store) => {
329
+ middlewares.forEach((mw, index) => {
330
+ store.getState().registerMiddleware(`provider-middleware-${index.toString()}`, mw);
241
331
  });
242
- return baseStore;
243
- }, [enableDevtools, devtoolsName]);
244
- return /* @__PURE__ */ jsxRuntime.jsx(UIBlockingContext.Provider, { value: store, children });
245
- }
246
- function useUIBlockingContext() {
247
- const store = react.useContext(UIBlockingContext);
248
- if (!store) {
249
- throw new Error(
250
- "useUIBlockingContext must be used within UIBlockingProvider. Either wrap your app in <UIBlockingProvider> or use the global store via useUIBlockingStore."
251
- );
252
- }
253
- return store;
254
- }
255
- function useIsInsideUIBlockingProvider() {
256
- const store = react.useContext(UIBlockingContext);
257
- return store !== null;
258
- }
259
- function useUIBlockingStoreFromContext(selector) {
260
- const store = useUIBlockingContext();
261
- return zustand.useStore(store, shallow.useShallow(selector));
332
+ };
333
+ return /* @__PURE__ */ jsxRuntime.jsx(
334
+ BaseUIBlockingProvider,
335
+ {
336
+ enableDevtools,
337
+ devtoolsName,
338
+ onStoreCreate: handleStoreCreate,
339
+ children
340
+ }
341
+ );
262
342
  }
263
343
 
264
344
  // src/store/uiBlockingStore.config.ts
@@ -268,11 +348,14 @@ var devtoolsConfig = {
268
348
  };
269
349
 
270
350
  // src/store/uiBlockingStore.store.ts
271
- var { useStore: useUIBlockingStore, useStoreApi: uiBlockingStoreApi } = createShallowStore(middleware.devtools(createUIBlockingActions, devtoolsConfig));
351
+ var { useStore: useUIBlockingStore, useStoreApi: uiBlockingStoreApi } = reactZustandToolkit.createShallowStore(middleware.devtools(createUIBlockingActions, devtoolsConfig));
272
352
 
273
353
  // src/context/useResolvedStore.ts
354
+ function useOptionalContextStore() {
355
+ return useOptionalContext();
356
+ }
274
357
  function useResolvedStore() {
275
- const contextStore = react.useContext(UIBlockingContext);
358
+ const contextStore = useOptionalContextStore();
276
359
  return contextStore ?? uiBlockingStoreApi;
277
360
  }
278
361
  function useResolvedStoreWithSelector(selector) {
@@ -286,9 +369,11 @@ var useAsyncAction = (actionId, scope, options) => {
286
369
  addBlocker: state.addBlocker,
287
370
  removeBlocker: state.removeBlocker
288
371
  }));
372
+ const counterRef = react.useRef(0);
289
373
  const executeWithBlocking = react.useCallback(
290
374
  async (asyncFn) => {
291
- const blockerId = `${actionId}-${Date.now().toString()}`;
375
+ counterRef.current += 1;
376
+ const blockerId = `${actionId}-${String(counterRef.current)}`;
292
377
  try {
293
378
  addBlocker(blockerId, {
294
379
  scope,
@@ -623,14 +708,16 @@ var getActionEmoji = (action) => {
623
708
  switch (action) {
624
709
  case "add":
625
710
  return "\u2795";
626
- case "remove":
627
- return "\u2796";
628
711
  case "update":
629
712
  return "\u{1F504}";
630
- case "cancel":
631
- return "\u274C";
713
+ case "remove":
714
+ return "\u2796";
632
715
  case "timeout":
633
716
  return "\u23F1\uFE0F";
717
+ case "clear":
718
+ return "\u{1F9F9}";
719
+ case "clear_scope":
720
+ return "\u{1F3AF}";
634
721
  default:
635
722
  return "\u2753";
636
723
  }
@@ -651,13 +738,23 @@ var extractConfigDetails = (config) => {
651
738
  };
652
739
  var formatLogData = (context) => {
653
740
  const configDetails = extractConfigDetails(context.config);
741
+ const logData = {};
742
+ if (context.scope !== void 0) {
743
+ logData.scope = context.scope;
744
+ }
745
+ if (context.count !== void 0) {
746
+ logData.count = context.count;
747
+ }
748
+ if (Object.keys(configDetails).length > 0) {
749
+ logData.config = configDetails;
750
+ }
654
751
  if (context.prevState) {
655
- return {
656
- config: configDetails,
657
- prevState: extractConfigDetails(context.prevState)
658
- };
752
+ logData.prevState = extractConfigDetails(context.prevState);
753
+ }
754
+ if (Object.keys(logData).length === 0) {
755
+ return configDetails;
659
756
  }
660
- return configDetails;
757
+ return Object.keys(logData).length === 1 && logData.config ? configDetails : logData;
661
758
  };
662
759
 
663
760
  // src/middleware/loggerMiddleware.ts
@@ -719,6 +816,10 @@ function createTypedHooks() {
719
816
  };
720
817
  }
721
818
 
819
+ Object.defineProperty(exports, "createShallowStore", {
820
+ enumerable: true,
821
+ get: function () { return reactZustandToolkit.createShallowStore; }
822
+ });
722
823
  exports.UIBlockingProvider = UIBlockingProvider;
723
824
  exports.configureMiddleware = configureMiddleware;
724
825
  exports.createAnalyticsMiddleware = createAnalyticsMiddleware;