@okyrychenko-dev/react-action-guard-devtools 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,893 @@
1
+ 'use strict';
2
+
3
+ var reactActionGuard = require('@okyrychenko-dev/react-action-guard');
4
+ var react = require('react');
5
+ var reactZustandToolkit = require('@okyrychenko-dev/react-zustand-toolkit');
6
+ var clsx2 = require('clsx');
7
+ var zustand = require('zustand');
8
+ var jsxRuntime = require('react/jsx-runtime');
9
+ var shallow = require('zustand/react/shallow');
10
+
11
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
+
13
+ var clsx2__default = /*#__PURE__*/_interopDefault(clsx2);
14
+
15
+ // src/components/actionGuardDevtools/ActionGuardDevtools.tsx
16
+
17
+ // src/store/devtoolsStore.constants.ts
18
+ var DEFAULT_FILTER = {
19
+ actions: ["add", "remove", "update", "cancel", "timeout"],
20
+ scopes: [],
21
+ search: ""
22
+ };
23
+ var DEFAULT_MAX_EVENTS = 200;
24
+ var DEFAULT_TAB = "timeline";
25
+
26
+ // src/store/devtoolsStore.actions.ts
27
+ var createDevtoolsActions = (set, get) => ({
28
+ // Initial State
29
+ events: [],
30
+ maxEvents: DEFAULT_MAX_EVENTS,
31
+ isOpen: false,
32
+ isMinimized: false,
33
+ activeTab: DEFAULT_TAB,
34
+ filter: DEFAULT_FILTER,
35
+ selectedEventId: null,
36
+ isPaused: false,
37
+ // Actions
38
+ /**
39
+ * Add a new event to history
40
+ *
41
+ * @param eventData - Event data without ID (auto-generated)
42
+ */
43
+ addEvent: (eventData) => {
44
+ if (get().isPaused) {
45
+ return;
46
+ }
47
+ const event = {
48
+ ...eventData,
49
+ id: `${String(eventData.timestamp)}-${eventData.blockerId}-${Math.random().toString(36).slice(2, 8)}`
50
+ };
51
+ set((state) => {
52
+ const newEvents = [event, ...state.events];
53
+ if (newEvents.length > state.maxEvents) {
54
+ newEvents.pop();
55
+ }
56
+ return { events: newEvents };
57
+ });
58
+ },
59
+ /**
60
+ * Clear all events from history
61
+ */
62
+ clearEvents: () => {
63
+ set({ events: [], selectedEventId: null });
64
+ },
65
+ /**
66
+ * Toggle panel open/closed state
67
+ */
68
+ toggleOpen: () => {
69
+ set((state) => ({ isOpen: !state.isOpen }));
70
+ },
71
+ /**
72
+ * Set panel open state
73
+ *
74
+ * @param open - Whether panel should be open
75
+ */
76
+ setOpen: (open) => {
77
+ set({ isOpen: open });
78
+ },
79
+ /**
80
+ * Toggle minimized state
81
+ */
82
+ toggleMinimized: () => {
83
+ set((state) => ({ isMinimized: !state.isMinimized }));
84
+ },
85
+ /**
86
+ * Set active tab
87
+ *
88
+ * @param tab - Tab to activate
89
+ */
90
+ setActiveTab: (tab) => {
91
+ set({ activeTab: tab, selectedEventId: null });
92
+ },
93
+ /**
94
+ * Update filter settings (partial update)
95
+ *
96
+ * @param filterUpdate - Partial filter update
97
+ */
98
+ setFilter: (filterUpdate) => {
99
+ set((state) => ({
100
+ filter: { ...state.filter, ...filterUpdate }
101
+ }));
102
+ },
103
+ /**
104
+ * Reset filters to default
105
+ */
106
+ resetFilter: () => {
107
+ set({ filter: DEFAULT_FILTER });
108
+ },
109
+ /**
110
+ * Select an event for detail view
111
+ *
112
+ * @param eventId - Event ID to select (null to deselect)
113
+ */
114
+ selectEvent: (eventId) => {
115
+ set({ selectedEventId: eventId });
116
+ },
117
+ /**
118
+ * Toggle pause state (stops/resumes recording)
119
+ */
120
+ togglePause: () => {
121
+ set((state) => ({ isPaused: !state.isPaused }));
122
+ },
123
+ /**
124
+ * Set maximum events limit
125
+ *
126
+ * @param max - Maximum number of events to keep
127
+ */
128
+ setMaxEvents: (max) => {
129
+ set((state) => {
130
+ const events = state.events.length > max ? state.events.slice(0, max) : state.events;
131
+ return { maxEvents: max, events };
132
+ });
133
+ }
134
+ });
135
+
136
+ // src/store/devtoolsStore.store.ts
137
+ var {
138
+ useStore: useDevtoolsStore,
139
+ useStoreApi: devtoolsStoreApi
140
+ } = reactZustandToolkit.createShallowStore(createDevtoolsActions);
141
+
142
+ // src/store/devtoolsStore.selectors.ts
143
+ function selectFilteredEvents(state) {
144
+ const { events, filter } = state;
145
+ return events.filter((event) => {
146
+ if (filter.actions.length > 0 && !filter.actions.includes(event.action)) {
147
+ return false;
148
+ }
149
+ if (filter.scopes.length > 0) {
150
+ if (!event.config?.scope) {
151
+ return false;
152
+ }
153
+ const eventScopes = Array.isArray(event.config.scope) ? event.config.scope : [event.config.scope];
154
+ const hasMatchingScope = eventScopes.some((s) => filter.scopes.includes(s));
155
+ if (!hasMatchingScope) {
156
+ return false;
157
+ }
158
+ }
159
+ if (filter.search) {
160
+ const searchLower = filter.search.toLowerCase();
161
+ const matchesId = event.blockerId.toLowerCase().includes(searchLower);
162
+ const matchesReason = event.config?.reason?.toLowerCase().includes(searchLower);
163
+ if (!matchesId && !matchesReason) {
164
+ return false;
165
+ }
166
+ }
167
+ return true;
168
+ });
169
+ }
170
+ function selectUniqueScopes(state) {
171
+ const scopes = /* @__PURE__ */ new Set();
172
+ state.events.forEach((event) => {
173
+ if (event.config?.scope) {
174
+ const eventScopes = Array.isArray(event.config.scope) ? event.config.scope : [event.config.scope];
175
+ eventScopes.forEach((s) => scopes.add(s));
176
+ }
177
+ });
178
+ return Array.from(scopes).sort();
179
+ }
180
+ function selectAllEvents(state) {
181
+ return state.events;
182
+ }
183
+
184
+ // src/middleware/devtoolsMiddleware.ts
185
+ var DEVTOOLS_MIDDLEWARE_NAME = "action-guard-devtools";
186
+ function createDevtoolsMiddleware() {
187
+ const addTimestamps = /* @__PURE__ */ new Map();
188
+ return (context) => {
189
+ const { addEvent } = devtoolsStoreApi.getState();
190
+ if (context.action === "add") {
191
+ addTimestamps.set(context.blockerId, context.timestamp);
192
+ }
193
+ let duration;
194
+ if (context.action === "remove" || context.action === "timeout" || context.action === "cancel") {
195
+ const addTime = addTimestamps.get(context.blockerId);
196
+ if (addTime !== void 0) {
197
+ duration = context.timestamp - addTime;
198
+ addTimestamps.delete(context.blockerId);
199
+ }
200
+ }
201
+ addEvent({
202
+ action: context.action,
203
+ blockerId: context.blockerId,
204
+ config: context.config,
205
+ timestamp: context.timestamp,
206
+ prevState: context.prevState,
207
+ duration
208
+ });
209
+ };
210
+ }
211
+
212
+ // src/styles/position.module.css
213
+ var position_default = {};
214
+
215
+ // src/components/devtoolsPanel/DevtoolsPanel.module.css
216
+ var DevtoolsPanel_default = {};
217
+
218
+ // src/components/devtoolsPanel/DevtoolsPanel.utils.ts
219
+ function getPositionClass(position) {
220
+ return position === "left" ? position_default.positionLeft : position_default.positionRight;
221
+ }
222
+ function getPanelClassName(position, isMinimized) {
223
+ return clsx2.clsx(
224
+ DevtoolsPanel_default.panel,
225
+ position_default.positionBase,
226
+ getPositionClass(position),
227
+ position_default.overlayLayer,
228
+ isMinimized ? DevtoolsPanel_default.panelMinimized : DevtoolsPanel_default.panelExpanded
229
+ );
230
+ }
231
+
232
+ // src/components/activeBlockers/ActiveBlockers.utils.ts
233
+ function formatScope(scope) {
234
+ if (Array.isArray(scope)) {
235
+ return scope.join(", ");
236
+ }
237
+ return scope;
238
+ }
239
+ function formatTimestamp(timestamp) {
240
+ const seconds = Math.floor((Date.now() - timestamp) / 1e3);
241
+ if (seconds < 60) {
242
+ return `${String(seconds)}s ago`;
243
+ }
244
+ if (seconds < 3600) {
245
+ return `${String(Math.floor(seconds / 60))}m ago`;
246
+ }
247
+ return `${String(Math.floor(seconds / 3600))}h ago`;
248
+ }
249
+ function getSortedBlockers(activeBlockers) {
250
+ return Array.from(activeBlockers.entries()).sort(([, a], [, b]) => b.priority - a.priority);
251
+ }
252
+ function createSvgIcon(children, { viewBox = "0 0 24 24" } = {}) {
253
+ return function SvgIcon(props) {
254
+ const { size = 16, color = "currentColor" } = props;
255
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox, fill: color, children });
256
+ };
257
+ }
258
+ var BlockIcon = createSvgIcon(
259
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2M4 12c0-4.42 3.58-8 8-8 1.85 0 3.55.63 4.9 1.69L5.69 16.9C4.63 15.55 4 13.85 4 12m8 8c-1.85 0-3.55-.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8" })
260
+ );
261
+ var CloseIcon = createSvgIcon(
262
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" })
263
+ );
264
+ var MinimizeIcon = createSvgIcon(/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 19h12v2H6z" }));
265
+ var PauseIcon = createSvgIcon(/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 19h4V5H6zm8-14v14h4V5z" }));
266
+ var PlayIcon = createSvgIcon(/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 8.64 15.27 12 10 15.36zM8 5v14l11-7z" }));
267
+ var ShieldIcon = createSvgIcon(
268
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 2 4 5v6.09c0 5.05 3.41 9.76 8 10.91 4.59-1.15 8-5.86 8-10.91V5zm6 9.09c0 4-2.55 7.7-6 8.83-3.45-1.13-6-4.82-6-8.83v-4.7l6-2.25 6 2.25z" })
269
+ );
270
+ var TrashIcon = createSvgIcon(
271
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6zM8 9h8v10H8zm7.5-5-1-1h-5l-1 1H5v2h14V4z" })
272
+ );
273
+
274
+ // src/styles/tokens.ts
275
+ var COLORS = {
276
+ primary: "var(--ag-primary)",
277
+ textDim: "var(--ag-text-dim)"};
278
+
279
+ // src/styles/shared.module.css
280
+ var shared_default = {};
281
+ function Badge(props) {
282
+ const { children, className, style } = props;
283
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx2__default.default(shared_default.badge, className), style, children });
284
+ }
285
+ var Badge_default = Badge;
286
+ function Content(props) {
287
+ const { children, className } = props;
288
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx2__default.default(shared_default.content, className), children });
289
+ }
290
+ var Content_default = Content;
291
+ function EmptyState(props) {
292
+ const { children, className } = props;
293
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx2__default.default(shared_default.emptyState, className), children });
294
+ }
295
+ var EmptyState_default = EmptyState;
296
+ function EventBadge(props) {
297
+ const { children, className, style, action } = props;
298
+ const classes = clsx2.clsx(shared_default.eventBadge, action && shared_default.eventBadgeAction, className);
299
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: classes, style, "data-action": action, children });
300
+ }
301
+ var EventBadge_default = EventBadge;
302
+ function IconButton(props) {
303
+ const { children, className, type = "button", ...others } = props;
304
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { type, className: clsx2__default.default(shared_default.iconButton, className), ...others, children });
305
+ }
306
+ var IconButton_default = IconButton;
307
+
308
+ // src/components/activeBlockers/ActiveBlockers.module.css
309
+ var ActiveBlockers_default = {};
310
+ function ActiveBlockersEmptyState() {
311
+ return /* @__PURE__ */ jsxRuntime.jsxs(EmptyState_default, { children: [
312
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ActiveBlockers_default.emptyIcon, children: /* @__PURE__ */ jsxRuntime.jsx(BlockIcon, { size: 32, color: COLORS.textDim }) }),
313
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: ActiveBlockers_default.emptyTitle, children: "No active blockers" }),
314
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: ActiveBlockers_default.emptySubtext, children: "The UI is currently unblocked." })
315
+ ] });
316
+ }
317
+ var ActiveBlockersEmptyState_default = ActiveBlockersEmptyState;
318
+ function ActiveBlockerItem(props) {
319
+ const { id, blocker } = props;
320
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ActiveBlockers_default.blockerItem, children: [
321
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ActiveBlockers_default.blockerHeader, children: [
322
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ActiveBlockers_default.blockerId, children: id }),
323
+ /* @__PURE__ */ jsxRuntime.jsx(Badge_default, { className: ActiveBlockers_default.activeBadge, children: "Active" })
324
+ ] }),
325
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ActiveBlockers_default.blockerMeta, children: [
326
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
327
+ "Scope: ",
328
+ formatScope(blocker.scope)
329
+ ] }),
330
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
331
+ "Priority: ",
332
+ blocker.priority
333
+ ] }),
334
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
335
+ "Reason: ",
336
+ blocker.reason
337
+ ] }),
338
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
339
+ "Started: ",
340
+ formatTimestamp(blocker.timestamp)
341
+ ] })
342
+ ] })
343
+ ] });
344
+ }
345
+ var ActiveBlockerItem_default = ActiveBlockerItem;
346
+ function ActiveBlockersList(props) {
347
+ const { blockers } = props;
348
+ return /* @__PURE__ */ jsxRuntime.jsx(Content_default, { children: blockers.map(([id, blocker]) => /* @__PURE__ */ jsxRuntime.jsx(ActiveBlockerItem_default, { id, blocker }, id)) });
349
+ }
350
+ var ActiveBlockersList_default = ActiveBlockersList;
351
+ function ActiveBlockers(props) {
352
+ const { store } = props;
353
+ const targetStore = store ?? reactActionGuard.uiBlockingStoreApi;
354
+ const activeBlockers = zustand.useStore(targetStore, (state) => state.activeBlockers);
355
+ const blockers = react.useMemo(() => getSortedBlockers(activeBlockers), [activeBlockers]);
356
+ if (blockers.length === 0) {
357
+ return /* @__PURE__ */ jsxRuntime.jsx(ActiveBlockersEmptyState_default, {});
358
+ }
359
+ return /* @__PURE__ */ jsxRuntime.jsx(ActiveBlockersList_default, { blockers });
360
+ }
361
+ var ActiveBlockers_default2 = ActiveBlockers;
362
+
363
+ // src/components/eventDetails/EventDetails.module.css
364
+ var EventDetails_default = {};
365
+
366
+ // src/components/eventItem/EventItem.module.css
367
+ var EventItem_default = {};
368
+
369
+ // src/components/eventItem/EventItem.utils.ts
370
+ function formatDuration(ms) {
371
+ if (ms < 1e3) {
372
+ return `${String(ms)}ms`;
373
+ }
374
+ if (ms < 6e4) {
375
+ return `${(ms / 1e3).toFixed(1)}s`;
376
+ }
377
+ return `${(ms / 6e4).toFixed(1)}m`;
378
+ }
379
+ function formatScope2(scope) {
380
+ if (!scope) {
381
+ return "global";
382
+ }
383
+ if (Array.isArray(scope)) {
384
+ return scope.join(", ");
385
+ }
386
+ return scope;
387
+ }
388
+ function formatTime(timestamp) {
389
+ const date = new Date(timestamp);
390
+ return date.toLocaleTimeString("en-US", {
391
+ hour12: false,
392
+ hour: "2-digit",
393
+ minute: "2-digit",
394
+ second: "2-digit"
395
+ });
396
+ }
397
+ function EventItemDetails(props) {
398
+ const { event } = props;
399
+ const { config, duration } = event;
400
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: EventItem_default.eventDetails, children: [
401
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
402
+ "scope: ",
403
+ formatScope2(config?.scope)
404
+ ] }),
405
+ duration !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
406
+ "duration: ",
407
+ formatDuration(duration)
408
+ ] }),
409
+ config?.priority !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
410
+ "priority: ",
411
+ config.priority
412
+ ] })
413
+ ] });
414
+ }
415
+ var EventItemDetails_default = EventItemDetails;
416
+ function EventItemHeader(props) {
417
+ const { event } = props;
418
+ const { action, blockerId, timestamp } = event;
419
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: EventItem_default.eventHeader, children: [
420
+ /* @__PURE__ */ jsxRuntime.jsx(EventBadge_default, { action, children: action }),
421
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: EventItem_default.eventBlockerId, children: blockerId }),
422
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: EventItem_default.eventTime, children: formatTime(timestamp) })
423
+ ] });
424
+ }
425
+ var EventItemHeader_default = EventItemHeader;
426
+ function EventItem(props) {
427
+ const { event, selected, onSelect } = props;
428
+ const handleSelect = () => {
429
+ onSelect(selected ? null : event.id);
430
+ };
431
+ return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: clsx2.clsx(EventItem_default.eventItem, selected && EventItem_default.selected), onClick: handleSelect, children: [
432
+ /* @__PURE__ */ jsxRuntime.jsx(EventItemHeader_default, { event }),
433
+ /* @__PURE__ */ jsxRuntime.jsx(EventItemDetails_default, { event })
434
+ ] });
435
+ }
436
+ var EventItem_default2 = react.memo(EventItem);
437
+
438
+ // src/components/eventDetails/EventDetails.utils.ts
439
+ function formatFullTimestamp(timestamp) {
440
+ const date = new Date(timestamp);
441
+ return date.toLocaleString("en-US", {
442
+ year: "numeric",
443
+ month: "2-digit",
444
+ day: "2-digit",
445
+ hour: "2-digit",
446
+ minute: "2-digit",
447
+ second: "2-digit",
448
+ hour12: false
449
+ });
450
+ }
451
+ function formatRelativeTime(timestamp) {
452
+ const seconds = Math.floor((Date.now() - timestamp) / 1e3);
453
+ if (seconds < 60) {
454
+ return `${String(seconds)}s ago`;
455
+ }
456
+ if (seconds < 3600) {
457
+ return `${String(Math.floor(seconds / 60))}m ago`;
458
+ }
459
+ return `${String(Math.floor(seconds / 3600))}h ago`;
460
+ }
461
+ function EventDetailsContent(props) {
462
+ const { event } = props;
463
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: EventDetails_default.content, children: [
464
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: EventDetails_default.section, children: [
465
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: EventDetails_default.label, children: "Blocker ID" }),
466
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: EventDetails_default.value, children: event.blockerId })
467
+ ] }),
468
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: EventDetails_default.section, children: [
469
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: EventDetails_default.label, children: "Timestamp" }),
470
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: EventDetails_default.value, children: [
471
+ formatFullTimestamp(event.timestamp),
472
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: EventDetails_default.mutedInline, children: [
473
+ "(",
474
+ formatRelativeTime(event.timestamp),
475
+ ")"
476
+ ] })
477
+ ] })
478
+ ] }),
479
+ event.duration !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: EventDetails_default.section, children: [
480
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: EventDetails_default.label, children: "Duration" }),
481
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: EventDetails_default.value, children: formatDuration(event.duration) })
482
+ ] }),
483
+ event.config && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: EventDetails_default.section, children: [
484
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: EventDetails_default.label, children: "Config" }),
485
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: EventDetails_default.config, children: [
486
+ event.config.scope !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
487
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: EventDetails_default.mutedLabel, children: "scope: " }),
488
+ formatScope2(event.config.scope)
489
+ ] }),
490
+ event.config.reason !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
491
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: EventDetails_default.mutedLabel, children: "reason: " }),
492
+ event.config.reason
493
+ ] }),
494
+ event.config.priority !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
495
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: EventDetails_default.mutedLabel, children: "priority: " }),
496
+ event.config.priority
497
+ ] })
498
+ ] })
499
+ ] }),
500
+ event.prevState && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: EventDetails_default.section, children: [
501
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: EventDetails_default.label, children: "Previous State" }),
502
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: EventDetails_default.config, children: [
503
+ event.prevState.scope !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
504
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: EventDetails_default.mutedLabel, children: "scope: " }),
505
+ formatScope2(event.prevState.scope)
506
+ ] }),
507
+ event.prevState.reason !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
508
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: EventDetails_default.mutedLabel, children: "reason: " }),
509
+ event.prevState.reason
510
+ ] }),
511
+ event.prevState.priority !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
512
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: EventDetails_default.mutedLabel, children: "priority: " }),
513
+ event.prevState.priority
514
+ ] })
515
+ ] })
516
+ ] }),
517
+ event.source && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: EventDetails_default.section, children: [
518
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: EventDetails_default.label, children: "Source" }),
519
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: EventDetails_default.value, children: event.source })
520
+ ] })
521
+ ] });
522
+ }
523
+ var EventDetailsContent_default = EventDetailsContent;
524
+ function EventDetailsHeader(props) {
525
+ const { action, onClose } = props;
526
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: EventDetails_default.header, children: [
527
+ /* @__PURE__ */ jsxRuntime.jsx(EventBadge_default, { action, children: action }),
528
+ /* @__PURE__ */ jsxRuntime.jsx(IconButton_default, { title: "Close details", onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, { size: 16 }) })
529
+ ] });
530
+ }
531
+ var EventDetailsHeader_default = EventDetailsHeader;
532
+ function EventDetails(props) {
533
+ const { event, onClose } = props;
534
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: EventDetails_default.panel, children: [
535
+ /* @__PURE__ */ jsxRuntime.jsx(EventDetailsHeader_default, { action: event.action, onClose }),
536
+ /* @__PURE__ */ jsxRuntime.jsx(EventDetailsContent_default, { event })
537
+ ] });
538
+ }
539
+ var EventDetails_default2 = EventDetails;
540
+
541
+ // src/components/timeline/Timeline.utils.ts
542
+ function isFilterActive(filter) {
543
+ if (filter.search.length > 0 || filter.scopes.length > 0) {
544
+ return true;
545
+ }
546
+ if (filter.actions.length !== DEFAULT_FILTER.actions.length) {
547
+ return true;
548
+ }
549
+ return !DEFAULT_FILTER.actions.every((action) => filter.actions.includes(action));
550
+ }
551
+
552
+ // src/components/timeline/Timeline.module.css
553
+ var Timeline_default = {};
554
+ function TimelineContent(props) {
555
+ const { events, selectedEventId, onSelectEvent } = props;
556
+ return /* @__PURE__ */ jsxRuntime.jsxs(Content_default, { children: [
557
+ events.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(EmptyState_default, { children: "No matching events" }),
558
+ events.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: Timeline_default.eventList, children: events.map((event) => /* @__PURE__ */ jsxRuntime.jsx(
559
+ EventItem_default2,
560
+ {
561
+ event,
562
+ selected: selectedEventId === event.id,
563
+ onSelect: onSelectEvent
564
+ },
565
+ event.id
566
+ )) })
567
+ ] });
568
+ }
569
+ var TimelineContent_default = TimelineContent;
570
+ function TimelineEmptyState() {
571
+ return /* @__PURE__ */ jsxRuntime.jsxs(EmptyState_default, { children: [
572
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "No events recorded yet." }),
573
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: Timeline_default.emptyHint, children: "Events will appear here as blockers are added/removed." })
574
+ ] });
575
+ }
576
+ var TimelineEmptyState_default = TimelineEmptyState;
577
+ function TimelineToolbar(props) {
578
+ const { search, onSearchChange } = props;
579
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: Timeline_default.toolbar, children: /* @__PURE__ */ jsxRuntime.jsx(
580
+ "input",
581
+ {
582
+ type: "text",
583
+ placeholder: "Search by ID or reason...",
584
+ value: search,
585
+ className: Timeline_default.searchInput,
586
+ onChange: onSearchChange
587
+ }
588
+ ) });
589
+ }
590
+ var TimelineToolbar_default = TimelineToolbar;
591
+ function Timeline() {
592
+ const events = useDevtoolsStore(selectFilteredEvents);
593
+ const allEvents = useDevtoolsStore(selectAllEvents);
594
+ const { selectedEventId, selectEvent, filter, setFilter } = useDevtoolsStore(
595
+ shallow.useShallow((state) => ({
596
+ selectedEventId: state.selectedEventId,
597
+ selectEvent: state.selectEvent,
598
+ filter: state.filter,
599
+ setFilter: state.setFilter
600
+ }))
601
+ );
602
+ const selectedEvent = selectedEventId ? events.find((e) => e.id === selectedEventId) : null;
603
+ react.useEffect(() => {
604
+ if (selectedEventId && !allEvents.some((e) => e.id === selectedEventId)) {
605
+ selectEvent(null);
606
+ }
607
+ }, [selectedEventId, allEvents, selectEvent]);
608
+ const handleSearchChange = react.useCallback(
609
+ (e) => {
610
+ setFilter({ search: e.target.value });
611
+ },
612
+ [setFilter]
613
+ );
614
+ const handleCloseDetails = react.useCallback(() => {
615
+ selectEvent(null);
616
+ }, [selectEvent]);
617
+ if (events.length === 0 && !isFilterActive(filter)) {
618
+ return /* @__PURE__ */ jsxRuntime.jsx(TimelineEmptyState_default, {});
619
+ }
620
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
621
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineToolbar_default, { search: filter.search, onSearchChange: handleSearchChange }),
622
+ /* @__PURE__ */ jsxRuntime.jsx(
623
+ TimelineContent_default,
624
+ {
625
+ events,
626
+ selectedEventId,
627
+ onSelectEvent: selectEvent
628
+ }
629
+ ),
630
+ selectedEvent && /* @__PURE__ */ jsxRuntime.jsx(EventDetails_default2, { event: selectedEvent, onClose: handleCloseDetails })
631
+ ] });
632
+ }
633
+ var Timeline_default2 = Timeline;
634
+ function DevtoolsPanelContent(props) {
635
+ const { activeTab, store } = props;
636
+ return activeTab === "timeline" ? /* @__PURE__ */ jsxRuntime.jsx(Timeline_default2, {}) : /* @__PURE__ */ jsxRuntime.jsx(ActiveBlockers_default2, { store });
637
+ }
638
+ var DevtoolsPanelContent_default = DevtoolsPanelContent;
639
+ var MaximizeIcon = createSvgIcon(/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 3h18v2H3z" }));
640
+ function getPauseButtonTitle(isPaused) {
641
+ return isPaused ? "Resume recording" : "Pause recording";
642
+ }
643
+ function getMinimizeButtonTitle(isMinimized) {
644
+ return isMinimized ? "Maximize" : "Minimize";
645
+ }
646
+ function getPauseButtonClassName(isPaused) {
647
+ return clsx2.clsx(isPaused && DevtoolsPanel_default.pauseButtonActive);
648
+ }
649
+ function DevtoolsPanelHeader(props) {
650
+ const {
651
+ eventsCount,
652
+ isPaused,
653
+ isMinimized,
654
+ onTogglePause,
655
+ onClearEvents,
656
+ onToggleMinimized,
657
+ onClose
658
+ } = props;
659
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: DevtoolsPanel_default.header, children: [
660
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: DevtoolsPanel_default.headerTitle, children: [
661
+ /* @__PURE__ */ jsxRuntime.jsx(ShieldIcon, { size: 16, color: COLORS.primary }),
662
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Action Guard" }),
663
+ /* @__PURE__ */ jsxRuntime.jsx(Badge_default, { className: DevtoolsPanel_default.headerCount, children: eventsCount })
664
+ ] }),
665
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: DevtoolsPanel_default.headerActions, children: [
666
+ /* @__PURE__ */ jsxRuntime.jsx(
667
+ IconButton_default,
668
+ {
669
+ className: getPauseButtonClassName(isPaused),
670
+ title: getPauseButtonTitle(isPaused),
671
+ onClick: onTogglePause,
672
+ children: isPaused ? /* @__PURE__ */ jsxRuntime.jsx(PlayIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(PauseIcon, {})
673
+ }
674
+ ),
675
+ /* @__PURE__ */ jsxRuntime.jsx(IconButton_default, { title: "Clear events", onClick: onClearEvents, children: /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, {}) }),
676
+ /* @__PURE__ */ jsxRuntime.jsx(IconButton_default, { title: getMinimizeButtonTitle(isMinimized), onClick: onToggleMinimized, children: isMinimized ? /* @__PURE__ */ jsxRuntime.jsx(MinimizeIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(MaximizeIcon, {}) }),
677
+ /* @__PURE__ */ jsxRuntime.jsx(IconButton_default, { title: "Close", onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {}) })
678
+ ] })
679
+ ] });
680
+ }
681
+ var DevtoolsPanelHeader_default = DevtoolsPanelHeader;
682
+
683
+ // src/components/devtoolsPanel/DevtoolsPanelTabs.utils.ts
684
+ var DEVTOOLS_TABS = [
685
+ { id: "timeline", label: "Timeline" },
686
+ { id: "blockers", label: "Active Blockers" }
687
+ ];
688
+ function DevtoolsPanelTabs(props) {
689
+ const { activeTab, onSelectTab } = props;
690
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: DevtoolsPanel_default.tabs, children: DEVTOOLS_TABS.map((tab) => /* @__PURE__ */ jsxRuntime.jsx(
691
+ "button",
692
+ {
693
+ onClick: () => {
694
+ onSelectTab(tab.id);
695
+ },
696
+ className: clsx2.clsx(DevtoolsPanel_default.tab, activeTab === tab.id && DevtoolsPanel_default.tabActive),
697
+ children: tab.label
698
+ },
699
+ tab.id
700
+ )) });
701
+ }
702
+ var DevtoolsPanelTabs_default = DevtoolsPanelTabs;
703
+ function DevtoolsPanel(props) {
704
+ const { position, store } = props;
705
+ const {
706
+ isOpen,
707
+ isMinimized,
708
+ activeTab,
709
+ setActiveTab,
710
+ toggleOpen,
711
+ toggleMinimized,
712
+ clearEvents,
713
+ isPaused,
714
+ togglePause,
715
+ events
716
+ } = useDevtoolsStore();
717
+ if (!isOpen) {
718
+ return null;
719
+ }
720
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: getPanelClassName(position, isMinimized), children: [
721
+ /* @__PURE__ */ jsxRuntime.jsx(
722
+ DevtoolsPanelHeader_default,
723
+ {
724
+ eventsCount: events.length,
725
+ isPaused,
726
+ isMinimized,
727
+ onTogglePause: togglePause,
728
+ onClearEvents: clearEvents,
729
+ onToggleMinimized: toggleMinimized,
730
+ onClose: toggleOpen
731
+ }
732
+ ),
733
+ !isMinimized && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
734
+ /* @__PURE__ */ jsxRuntime.jsx(DevtoolsPanelTabs_default, { activeTab, onSelectTab: setActiveTab }),
735
+ /* @__PURE__ */ jsxRuntime.jsx(DevtoolsPanelContent_default, { activeTab, store })
736
+ ] })
737
+ ] });
738
+ }
739
+ var DevtoolsPanel_default2 = DevtoolsPanel;
740
+
741
+ // src/components/toggleButton/ToggleButton.module.css
742
+ var ToggleButton_default = {};
743
+
744
+ // src/components/toggleButton/ToggleButton.utils.ts
745
+ function getPositionClass2(position) {
746
+ return position === "left" ? position_default.positionLeft : position_default.positionRight;
747
+ }
748
+ function getToggleButtonClassName(position) {
749
+ return clsx2.clsx(
750
+ ToggleButton_default.toggleButton,
751
+ position_default.positionBase,
752
+ getPositionClass2(position),
753
+ position_default.overlayLayer
754
+ );
755
+ }
756
+ function shouldShowBadge(count) {
757
+ return count > 0;
758
+ }
759
+ function getBadgeClassName(isPaused) {
760
+ return clsx2.clsx(ToggleButton_default.badgePosition, isPaused ? ToggleButton_default.badgePaused : ToggleButton_default.badgeActive);
761
+ }
762
+ function ToggleButtonBadge(props) {
763
+ const { count, isPaused } = props;
764
+ if (!shouldShowBadge(count)) {
765
+ return null;
766
+ }
767
+ return /* @__PURE__ */ jsxRuntime.jsx(Badge_default, { className: getBadgeClassName(isPaused), children: count });
768
+ }
769
+ var ToggleButtonBadge_default = ToggleButtonBadge;
770
+ function ToggleButton(props) {
771
+ const { position, store } = props;
772
+ const { isOpen, toggleOpen, isPaused } = useDevtoolsStore();
773
+ const targetStore = store ?? reactActionGuard.uiBlockingStoreApi;
774
+ const activeBlockers = zustand.useStore(targetStore, (state) => state.activeBlockers);
775
+ const activeCount = activeBlockers.size;
776
+ if (isOpen) {
777
+ return null;
778
+ }
779
+ return /* @__PURE__ */ jsxRuntime.jsxs(
780
+ "button",
781
+ {
782
+ className: getToggleButtonClassName(position),
783
+ title: "Open Action Guard Devtools",
784
+ onClick: toggleOpen,
785
+ children: [
786
+ /* @__PURE__ */ jsxRuntime.jsx(ShieldIcon, { size: 22 }),
787
+ /* @__PURE__ */ jsxRuntime.jsx(ToggleButtonBadge_default, { count: activeCount, isPaused })
788
+ ]
789
+ }
790
+ );
791
+ }
792
+ var ToggleButton_default2 = ToggleButton;
793
+ function ActionGuardDevtoolsContent(props) {
794
+ const { position, store } = props;
795
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
796
+ /* @__PURE__ */ jsxRuntime.jsx(ToggleButton_default2, { position, store }),
797
+ /* @__PURE__ */ jsxRuntime.jsx(DevtoolsPanel_default2, { position, store })
798
+ ] });
799
+ }
800
+ var ActionGuardDevtoolsContent_default = ActionGuardDevtoolsContent;
801
+
802
+ // src/components/actionGuardDevtools/ActionGuardDevtools.utils.ts
803
+ function isTypingTarget(target) {
804
+ return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement;
805
+ }
806
+ function getDevtoolsKeyboardAction(event, isOpen) {
807
+ if (!isOpen) {
808
+ return null;
809
+ }
810
+ if (isTypingTarget(event.target)) {
811
+ return null;
812
+ }
813
+ switch (event.key) {
814
+ case "Escape":
815
+ return { action: "close", preventDefault: false };
816
+ case " ":
817
+ return { action: "togglePause", preventDefault: true };
818
+ case "c":
819
+ case "C":
820
+ if (!event.metaKey && !event.ctrlKey) {
821
+ return { action: "clearEvents", preventDefault: false };
822
+ }
823
+ return null;
824
+ default:
825
+ return null;
826
+ }
827
+ }
828
+ function ActionGuardDevtools(props) {
829
+ const {
830
+ position = "right",
831
+ defaultOpen = false,
832
+ maxEvents = 200,
833
+ showInProduction = false,
834
+ store: customStore
835
+ } = props;
836
+ const { setOpen, setMaxEvents, isOpen, togglePause, clearEvents } = useDevtoolsStore();
837
+ const targetStore = react.useMemo(() => customStore ?? reactActionGuard.uiBlockingStoreApi, [customStore]);
838
+ react.useEffect(() => {
839
+ const storeState = targetStore.getState();
840
+ const middleware = createDevtoolsMiddleware();
841
+ storeState.registerMiddleware(DEVTOOLS_MIDDLEWARE_NAME, middleware);
842
+ return () => {
843
+ storeState.unregisterMiddleware(DEVTOOLS_MIDDLEWARE_NAME);
844
+ };
845
+ }, [targetStore]);
846
+ react.useEffect(() => {
847
+ setOpen(defaultOpen);
848
+ setMaxEvents(maxEvents);
849
+ }, [defaultOpen, maxEvents, setOpen, setMaxEvents]);
850
+ const handleKeyDown = react.useCallback(
851
+ (event) => {
852
+ const action = getDevtoolsKeyboardAction(event, isOpen);
853
+ if (!action) {
854
+ return;
855
+ }
856
+ if (action.preventDefault) {
857
+ event.preventDefault();
858
+ }
859
+ switch (action.action) {
860
+ case "close":
861
+ setOpen(false);
862
+ break;
863
+ case "togglePause":
864
+ togglePause();
865
+ break;
866
+ case "clearEvents":
867
+ clearEvents();
868
+ break;
869
+ }
870
+ },
871
+ [isOpen, setOpen, togglePause, clearEvents]
872
+ );
873
+ react.useEffect(() => {
874
+ document.addEventListener("keydown", handleKeyDown);
875
+ return () => {
876
+ document.removeEventListener("keydown", handleKeyDown);
877
+ };
878
+ }, [handleKeyDown]);
879
+ if (process.env.NODE_ENV === "production" && !showInProduction) {
880
+ return null;
881
+ }
882
+ return /* @__PURE__ */ jsxRuntime.jsx(ActionGuardDevtoolsContent_default, { position, store: customStore });
883
+ }
884
+ var ActionGuardDevtools_default = ActionGuardDevtools;
885
+
886
+ exports.ActionGuardDevtools = ActionGuardDevtools_default;
887
+ exports.DEVTOOLS_MIDDLEWARE_NAME = DEVTOOLS_MIDDLEWARE_NAME;
888
+ exports.createDevtoolsMiddleware = createDevtoolsMiddleware;
889
+ exports.selectFilteredEvents = selectFilteredEvents;
890
+ exports.selectUniqueScopes = selectUniqueScopes;
891
+ exports.useDevtoolsStore = useDevtoolsStore;
892
+ //# sourceMappingURL=index.cjs.map
893
+ //# sourceMappingURL=index.cjs.map