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