react-os-shell 4.15.0 → 4.21.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.
Files changed (95) hide show
  1. package/README.md +46 -0
  2. package/dist/{Browser-XV34CJFP.js → Browser-6ACRB3XK.js} +9 -6
  3. package/dist/Browser-6ACRB3XK.js.map +1 -0
  4. package/dist/{Calculator-FFCYNYU3.js → Calculator-QCAJ6ITI.js} +8 -5
  5. package/dist/Calculator-QCAJ6ITI.js.map +1 -0
  6. package/dist/ConfirmDialog-WAM5OKAV.js +3 -0
  7. package/dist/{ConfirmDialog-CXHJCDUL.js.map → ConfirmDialog-WAM5OKAV.js.map} +1 -1
  8. package/dist/{CurrencyConverter-3NCK7WY6.js → CurrencyConverter-PCYC6RL5.js} +8 -5
  9. package/dist/CurrencyConverter-PCYC6RL5.js.map +1 -0
  10. package/dist/{Documents-S6EQOLUF.js → Documents-XME7555L.js} +10 -7
  11. package/dist/Documents-XME7555L.js.map +1 -0
  12. package/dist/{Files-TFZNYXAH.js → Files-6Q4HZKZD.js} +16 -12
  13. package/dist/Files-6Q4HZKZD.js.map +1 -0
  14. package/dist/{Notepad-EAA6CXZX.js → Notepad-W6LXMKWD.js} +10 -7
  15. package/dist/Notepad-W6LXMKWD.js.map +1 -0
  16. package/dist/{PomodoroTimer-BXI752XM.js → PomodoroTimer-YABFEPEV.js} +10 -7
  17. package/dist/PomodoroTimer-YABFEPEV.js.map +1 -0
  18. package/dist/{Preview-ZKYM7A5X.js → Preview-TYJQ6ASR.js} +11 -8
  19. package/dist/Preview-TYJQ6ASR.js.map +1 -0
  20. package/dist/{Sidebar-IMACKSHS.js → Sidebar-B3LY5T2U.js} +5 -4
  21. package/dist/Sidebar-B3LY5T2U.js.map +1 -0
  22. package/dist/{Spreadsheet-BXOPROMB.js → Spreadsheet-TAO2PWRL.js} +10 -7
  23. package/dist/Spreadsheet-TAO2PWRL.js.map +1 -0
  24. package/dist/{Stock-BEQCSYNT.js → Stock-IPLLRUOC.js} +8 -5
  25. package/dist/Stock-IPLLRUOC.js.map +1 -0
  26. package/dist/{Weather-QPV6G7UN.js → Weather-SI5QN7X2.js} +8 -5
  27. package/dist/Weather-SI5QN7X2.js.map +1 -0
  28. package/dist/{WorldClock-I3ZE57V4.js → WorldClock-WYDSMUWX.js} +8 -5
  29. package/dist/WorldClock-WYDSMUWX.js.map +1 -0
  30. package/dist/apps/index.js +22 -19
  31. package/dist/apps/index.js.map +1 -1
  32. package/dist/chunk-22BNPLM4.js +46 -0
  33. package/dist/chunk-22BNPLM4.js.map +1 -0
  34. package/dist/{chunk-IVLDAUYM.js → chunk-3CUSBZWG.js} +3 -56
  35. package/dist/chunk-3CUSBZWG.js.map +1 -0
  36. package/dist/{chunk-VENYVK3L.js → chunk-4AKVHY4B.js} +48 -12
  37. package/dist/chunk-4AKVHY4B.js.map +1 -0
  38. package/dist/chunk-4OPGKWMT.js +7 -0
  39. package/dist/{chunk-F562QDVS.js.map → chunk-4OPGKWMT.js.map} +1 -1
  40. package/dist/chunk-C22EFSND.js +188 -0
  41. package/dist/chunk-C22EFSND.js.map +1 -0
  42. package/dist/{chunk-H4IEH6FK.js → chunk-EWDYCO65.js} +3 -3
  43. package/dist/{chunk-H4IEH6FK.js.map → chunk-EWDYCO65.js.map} +1 -1
  44. package/dist/{chunk-DINNWYVB.js → chunk-HYAW6KIL.js} +4 -45
  45. package/dist/chunk-HYAW6KIL.js.map +1 -0
  46. package/dist/{chunk-H3HFM4JQ.js → chunk-IGPSRRL7.js} +262 -712
  47. package/dist/chunk-IGPSRRL7.js.map +1 -0
  48. package/dist/chunk-L2O6B5KT.js +394 -0
  49. package/dist/chunk-L2O6B5KT.js.map +1 -0
  50. package/dist/{chunk-YS4ZW2P7.js → chunk-OI253GOA.js} +4 -4
  51. package/dist/{chunk-YS4ZW2P7.js.map → chunk-OI253GOA.js.map} +1 -1
  52. package/dist/{chunk-ZPCGIR62.js → chunk-OQ7GRU7G.js} +3 -3
  53. package/dist/{chunk-ZPCGIR62.js.map → chunk-OQ7GRU7G.js.map} +1 -1
  54. package/dist/{chunk-7J3HTJJ3.js → chunk-Q3AOIJ7B.js} +3 -3
  55. package/dist/{chunk-7J3HTJJ3.js.map → chunk-Q3AOIJ7B.js.map} +1 -1
  56. package/dist/{chunk-N2UW3CAS.js → chunk-QJS6RBH4.js} +3 -3
  57. package/dist/{chunk-N2UW3CAS.js.map → chunk-QJS6RBH4.js.map} +1 -1
  58. package/dist/chunk-UPGCISED.js +5015 -0
  59. package/dist/chunk-UPGCISED.js.map +1 -0
  60. package/dist/{chunk-LFTHOYKW.js → chunk-VX634UB2.js} +5 -5
  61. package/dist/{chunk-LFTHOYKW.js.map → chunk-VX634UB2.js.map} +1 -1
  62. package/dist/chunk-WNLPTLT6.js +56 -0
  63. package/dist/chunk-WNLPTLT6.js.map +1 -0
  64. package/dist/{chunk-QU5L5ZYM.js → chunk-X5PZYTAK.js} +3 -3
  65. package/dist/{chunk-QU5L5ZYM.js.map → chunk-X5PZYTAK.js.map} +1 -1
  66. package/dist/index-Czn1re5M.d.ts +1939 -0
  67. package/dist/index.d.ts +7 -1375
  68. package/dist/index.js +159 -4082
  69. package/dist/index.js.map +1 -1
  70. package/dist/shell.css +105 -0
  71. package/dist/styles.css +12 -501
  72. package/dist/ui/index.d.ts +3 -0
  73. package/dist/ui/index.js +11 -0
  74. package/dist/ui/index.js.map +1 -0
  75. package/dist/ui.css +425 -0
  76. package/package.json +7 -2
  77. package/dist/Browser-XV34CJFP.js.map +0 -1
  78. package/dist/Calculator-FFCYNYU3.js.map +0 -1
  79. package/dist/ConfirmDialog-CXHJCDUL.js +0 -4
  80. package/dist/CurrencyConverter-3NCK7WY6.js.map +0 -1
  81. package/dist/Documents-S6EQOLUF.js.map +0 -1
  82. package/dist/Files-TFZNYXAH.js.map +0 -1
  83. package/dist/Notepad-EAA6CXZX.js.map +0 -1
  84. package/dist/PomodoroTimer-BXI752XM.js.map +0 -1
  85. package/dist/Preview-ZKYM7A5X.js.map +0 -1
  86. package/dist/Sidebar-IMACKSHS.js.map +0 -1
  87. package/dist/Spreadsheet-BXOPROMB.js.map +0 -1
  88. package/dist/Stock-BEQCSYNT.js.map +0 -1
  89. package/dist/Weather-QPV6G7UN.js.map +0 -1
  90. package/dist/WorldClock-I3ZE57V4.js.map +0 -1
  91. package/dist/chunk-DINNWYVB.js.map +0 -1
  92. package/dist/chunk-F562QDVS.js +0 -7
  93. package/dist/chunk-H3HFM4JQ.js.map +0 -1
  94. package/dist/chunk-IVLDAUYM.js.map +0 -1
  95. package/dist/chunk-VENYVK3L.js.map +0 -1
@@ -1,64 +1,12 @@
1
- import { useShellAuth, glassStyle, GLASS_DIVIDER, navIcons } from './chunk-IVLDAUYM.js';
2
- import { createContext, Component, useReducer, useRef, useCallback, useState, useEffect, useMemo, useContext, useId, useLayoutEffect, useSyncExternalStore, isValidElement, cloneElement, Suspense } from 'react';
3
- import { Dialog, DialogBackdrop, DialogPanel, DialogTitle } from '@headlessui/react';
4
- import { XMarkIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline';
1
+ import { useIsMobile, useShellPrefs, PopupMenu, PopupMenuItem, PopupMenuDivider } from './chunk-C22EFSND.js';
2
+ import { confirm, runEscapeInterceptors } from './chunk-L2O6B5KT.js';
3
+ import { useShellAuth, navIcons } from './chunk-WNLPTLT6.js';
4
+ import { createContext, Component, useContext, useRef, useLayoutEffect, useState, useEffect, useCallback, useSyncExternalStore, useMemo, isValidElement, cloneElement, useReducer, useId, Suspense } from 'react';
5
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
6
  import { createPortal } from 'react-dom';
7
+ import { XMarkIcon } from '@heroicons/react/24/outline';
6
8
  import { useLocation } from 'react-router-dom';
7
9
  import { useQueryClient, useQuery } from '@tanstack/react-query';
8
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
-
10
- // src/api/client.ts
11
- var _apiClient = null;
12
- function setShellApiClient(instance) {
13
- _apiClient = instance;
14
- }
15
- function isShellApiClientConfigured() {
16
- return _apiClient !== null;
17
- }
18
- var NOOP_METHODS = /* @__PURE__ */ new Set([
19
- "get",
20
- "post",
21
- "put",
22
- "patch",
23
- "delete",
24
- "head",
25
- "options",
26
- "request"
27
- ]);
28
- var apiClient = new Proxy({}, {
29
- get(_t, prop) {
30
- if (_apiClient) return _apiClient[prop];
31
- if (typeof prop === "string" && NOOP_METHODS.has(prop)) {
32
- return () => Promise.resolve({ data: null, status: 0, statusText: "", headers: {}, config: {} });
33
- }
34
- return void 0;
35
- }
36
- });
37
- var client_default = apiClient;
38
-
39
- // src/shell/entityFetchPolicy.ts
40
- var RETRYABLE_CLIENT_STATUSES = /* @__PURE__ */ new Set([
41
- 408,
42
- // Request Timeout
43
- 429
44
- // Too Many Requests
45
- ]);
46
- var ENTITY_FETCH_MAX_RETRIES = 3;
47
- var ENTITY_REFETCH_INTERVAL_MS = 6e4;
48
- function isPermanentClientError(error) {
49
- const status = error?.response?.status;
50
- if (typeof status !== "number") return false;
51
- return status >= 400 && status < 500 && !RETRYABLE_CLIENT_STATUSES.has(status);
52
- }
53
- function shouldRetryEntityFetch(failureCount, error) {
54
- return !isPermanentClientError(error) && failureCount < ENTITY_FETCH_MAX_RETRIES;
55
- }
56
- function entityRefetchInterval(error) {
57
- return isPermanentClientError(error) ? false : ENTITY_REFETCH_INTERVAL_MS;
58
- }
59
- function entityDetailUrl(endpoint, entityId) {
60
- return `${endpoint}${encodeURIComponent(entityId)}/`;
61
- }
62
10
 
63
11
  // src/windowRegistry/types.ts
64
12
  function isPageEntry(entry) {
@@ -88,6 +36,35 @@ var WINDOW_REGISTRY = new Proxy({}, {
88
36
  return void 0;
89
37
  }
90
38
  });
39
+
40
+ // src/api/client.ts
41
+ var _apiClient = null;
42
+ function setShellApiClient(instance) {
43
+ _apiClient = instance;
44
+ }
45
+ function isShellApiClientConfigured() {
46
+ return _apiClient !== null;
47
+ }
48
+ var NOOP_METHODS = /* @__PURE__ */ new Set([
49
+ "get",
50
+ "post",
51
+ "put",
52
+ "patch",
53
+ "delete",
54
+ "head",
55
+ "options",
56
+ "request"
57
+ ]);
58
+ var apiClient = new Proxy({}, {
59
+ get(_t, prop) {
60
+ if (_apiClient) return _apiClient[prop];
61
+ if (typeof prop === "string" && NOOP_METHODS.has(prop)) {
62
+ return () => Promise.resolve({ data: null, status: 0, statusText: "", headers: {}, config: {} });
63
+ }
64
+ return void 0;
65
+ }
66
+ });
67
+ var client_default = apiClient;
91
68
  function WindowCrashedFallback({ error, onReload }) {
92
69
  return /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-3 px-6 py-12 text-center", children: [
93
70
  /* @__PURE__ */ jsx("svg", { className: "h-8 w-8 text-amber-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" }) }),
@@ -124,215 +101,28 @@ var WindowErrorBoundary = class extends Component {
124
101
  }
125
102
  };
126
103
 
127
- // src/hooks/undoHistory.ts
128
- var UNDO_LIMIT = 100;
129
- var emptyUndoState = { past: [], future: [] };
130
- function undoReducer(state, action) {
131
- switch (action.type) {
132
- case "record": {
133
- const newest = state.past[state.past.length - 1];
134
- if (action.step.coalesceKey !== null && newest?.coalesceKey === action.step.coalesceKey) {
135
- return state.future.length === 0 ? state : { ...state, future: [] };
136
- }
137
- const past = [...state.past, action.step];
138
- return {
139
- past: past.length > UNDO_LIMIT ? past.slice(past.length - UNDO_LIMIT) : past,
140
- future: []
141
- };
142
- }
143
- case "undo": {
144
- if (state.past.length === 0) return state;
145
- return {
146
- past: state.past.slice(0, -1),
147
- future: [action.redoStep, ...state.future]
148
- };
149
- }
150
- case "redo": {
151
- if (state.future.length === 0) return state;
152
- return {
153
- past: [...state.past, action.undoStep],
154
- future: state.future.slice(1)
155
- };
156
- }
157
- case "clear":
158
- return state.past.length === 0 && state.future.length === 0 ? state : emptyUndoState;
159
- }
160
- }
161
- var TEXT_ENTRY = /* @__PURE__ */ new Set(["INPUT", "TEXTAREA", "SELECT"]);
162
- function matchUndoHotkey(e) {
163
- if (!(e.metaKey || e.ctrlKey) || e.altKey) return null;
164
- const key = e.key.toLowerCase();
165
- const action = key === "z" ? e.shiftKey ? "redo" : "undo" : key === "y" && e.ctrlKey && !e.metaKey && !e.shiftKey ? "redo" : null;
166
- if (!action) return null;
167
- const target = e.target;
168
- if (target?.isContentEditable) return null;
169
- if (target?.tagName && TEXT_ENTRY.has(target.tagName.toUpperCase())) return null;
170
- return action;
171
- }
172
- var UndoContext = createContext(null);
173
- var RUNAWAY_LIMIT = 500;
174
- var RUNAWAY_QUIET_MS = 250;
175
- function UndoProvider({ children, canEdit = true, perms, windowId }) {
176
- const [state, dispatch] = useReducer(undoReducer, emptyUndoState);
177
- const slices = useRef(/* @__PURE__ */ new Map());
178
- const { hasAnyPerm } = useShellAuth();
179
- const enabled = canEdit && (perms && perms.length > 0 ? hasAnyPerm(perms) : true);
180
- const pending = useRef(null);
181
- const suspended = useRef(false);
182
- const snapshot = useCallback(() => {
183
- const values = {};
184
- for (const [id, slice] of slices.current) values[id] = slice.getLast();
185
- return values;
186
- }, []);
187
- const register = useCallback((id, slice) => {
188
- slices.current.set(id, slice);
189
- }, []);
190
- const unregister = useCallback((id) => {
191
- slices.current.delete(id);
192
- }, []);
193
- const runaway = useRef({ last: 0, count: 0, tripped: false });
194
- const record = useCallback((label, coalesceKey) => {
195
- if (!enabled || suspended.current || pending.current) return;
196
- const r = runaway.current;
197
- if (r.tripped) return;
198
- const now = Date.now();
199
- if (now - r.last > RUNAWAY_QUIET_MS) r.count = 0;
200
- r.last = now;
201
- if (++r.count > RUNAWAY_LIMIT) {
202
- r.tripped = true;
203
- console.error(
204
- `[react-os-shell] UndoProvider: "${label}" recorded ${RUNAWAY_LIMIT} undo steps with no pause between them, which no user action can do. The value passed to useUndoable for this slice is almost certainly rebuilt on every render (a .map/.filter/object literal in the call) rather than held in state, so it reads as changed every time \u2014 and since recording a step re-renders the form, that is a loop. Undo recording is now off for this form to keep the tab responsive.`
205
- );
206
- return;
207
- }
208
- pending.current = { values: snapshot(), label, coalesceKey };
209
- queueMicrotask(() => {
210
- const step = pending.current;
211
- pending.current = null;
212
- if (step) dispatch({ type: "record", step });
213
- });
214
- }, [enabled, snapshot]);
215
- const applyValues = useCallback((values) => {
216
- for (const [id, value2] of Object.entries(values)) {
217
- const slice = slices.current.get(id);
218
- if (!slice) continue;
219
- slice.setLast(value2);
220
- slice.apply(value2);
221
- }
222
- }, []);
223
- const undo = useCallback(() => {
224
- const step = state.past[state.past.length - 1];
225
- if (!step) return;
226
- const redoStep = { values: snapshot(), label: step.label, coalesceKey: null };
227
- applyValues(step.values);
228
- dispatch({ type: "undo", redoStep });
229
- }, [state.past, snapshot, applyValues]);
230
- const redo = useCallback(() => {
231
- const step = state.future[0];
232
- if (!step) return;
233
- const undoStep = { values: snapshot(), label: step.label, coalesceKey: null };
234
- applyValues(step.values);
235
- dispatch({ type: "redo", undoStep });
236
- }, [state.future, snapshot, applyValues]);
237
- const [baselineToken, setBaselineToken] = useState(0);
238
- const baseline = useCallback(() => {
239
- suspended.current = true;
240
- pending.current = null;
241
- setBaselineToken((t) => t + 1);
242
- dispatch({ type: "clear" });
243
- }, []);
244
- useEffect(() => {
245
- if (!suspended.current) return;
246
- suspended.current = false;
247
- pending.current = null;
248
- dispatch({ type: "clear" });
249
- }, [baselineToken]);
250
- const clear = baseline;
251
- const canUndo = enabled && state.past.length > 0;
252
- const canRedo = enabled && state.future.length > 0;
253
- const isActive = useIsActiveWindow(windowId);
254
- const keyed = useRef({ undo, redo, canUndo, canRedo });
255
- keyed.current = { undo, redo, canUndo, canRedo };
256
- useEffect(() => {
257
- if (!isActive || !enabled) return;
258
- const handler = (e) => {
259
- const action = matchUndoHotkey(e);
260
- if (!action) return;
261
- const k = keyed.current;
262
- if (action === "undo" ? !k.canUndo : !k.canRedo) return;
263
- e.preventDefault();
264
- if (action === "undo") k.undo();
265
- else k.redo();
266
- };
267
- window.addEventListener("keydown", handler);
268
- return () => window.removeEventListener("keydown", handler);
269
- }, [isActive, enabled]);
270
- const value = useMemo(() => ({
271
- register,
272
- unregister,
273
- record,
274
- undo,
275
- redo,
276
- clear,
277
- baseline,
278
- canUndo,
279
- canRedo,
280
- enabled,
281
- undoLabel: canUndo ? state.past[state.past.length - 1].label : null,
282
- redoLabel: canRedo ? state.future[0].label : null
283
- }), [register, unregister, record, undo, redo, clear, baseline, canUndo, canRedo, enabled, state.past, state.future]);
284
- return /* @__PURE__ */ jsx(UndoContext.Provider, { value, children });
104
+ // src/shell/entityFetchPolicy.ts
105
+ var RETRYABLE_CLIENT_STATUSES = /* @__PURE__ */ new Set([
106
+ 408,
107
+ // Request Timeout
108
+ 429
109
+ // Too Many Requests
110
+ ]);
111
+ var ENTITY_FETCH_MAX_RETRIES = 3;
112
+ var ENTITY_REFETCH_INTERVAL_MS = 6e4;
113
+ function isPermanentClientError(error) {
114
+ const status = error?.response?.status;
115
+ if (typeof status !== "number") return false;
116
+ return status >= 400 && status < 500 && !RETRYABLE_CLIENT_STATUSES.has(status);
285
117
  }
286
- function useUndo() {
287
- const ctx = useContext(UndoContext);
288
- const noop = useCallback(() => {
289
- }, []);
290
- return {
291
- undo: ctx?.undo ?? noop,
292
- redo: ctx?.redo ?? noop,
293
- clear: ctx?.clear ?? noop,
294
- baseline: ctx?.baseline ?? noop,
295
- canUndo: ctx?.canUndo ?? false,
296
- canRedo: ctx?.canRedo ?? false,
297
- undoLabel: ctx?.undoLabel ?? null,
298
- redoLabel: ctx?.redoLabel ?? null,
299
- enabled: ctx?.enabled ?? false
300
- };
118
+ function shouldRetryEntityFetch(failureCount, error) {
119
+ return !isPermanentClientError(error) && failureCount < ENTITY_FETCH_MAX_RETRIES;
301
120
  }
302
- function useUndoable(value, apply, opts) {
303
- const ctx = useContext(UndoContext);
304
- const id = useId();
305
- const last = useRef(value);
306
- const applyRef = useRef(apply);
307
- applyRef.current = apply;
308
- useEffect(() => {
309
- if (!ctx) return;
310
- ctx.register(id, {
311
- getLast: () => last.current,
312
- setLast: (v) => {
313
- last.current = v;
314
- },
315
- apply: (v) => applyRef.current(v)
316
- });
317
- return () => ctx.unregister(id);
318
- }, [ctx, id]);
319
- const { label, coalesceKey = null } = opts;
320
- const ctxRef = useRef(ctx);
321
- ctxRef.current = ctx;
322
- const optsRef = useRef({ label, coalesceKey });
323
- optsRef.current = { label, coalesceKey };
324
- useEffect(() => {
325
- const c = ctxRef.current;
326
- if (!c) return;
327
- if (Object.is(value, last.current)) return;
328
- c.record(optsRef.current.label, optsRef.current.coalesceKey);
329
- last.current = value;
330
- }, [value]);
121
+ function entityRefetchInterval(error) {
122
+ return isPermanentClientError(error) ? false : ENTITY_REFETCH_INTERVAL_MS;
331
123
  }
332
- function useUndoableState(initial, opts) {
333
- const [value, setValue] = useState(initial);
334
- useUndoable(value, (next) => setValue(() => next), opts);
335
- return [value, setValue];
124
+ function entityDetailUrl(endpoint, entityId) {
125
+ return `${endpoint}${encodeURIComponent(entityId)}/`;
336
126
  }
337
127
  function LoadingSpinner() {
338
128
  return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-8 text-sm text-gray-400", children: "Loading..." });
@@ -1123,151 +913,6 @@ function WindowManagerProvider({ children, windowAccentForRoute }) {
1123
913
  })
1124
914
  ] });
1125
915
  }
1126
- function getDensity() {
1127
- return getComputedStyle(document.documentElement).getPropertyValue("--menu-density")?.trim() || "normal";
1128
- }
1129
- function PopupMenu({ children, style, className = "", onClose, minWidth = 180, portal = false }) {
1130
- const ref = useRef(null);
1131
- useEffect(() => {
1132
- if (!onClose) return;
1133
- const handleClick = (e) => {
1134
- if (e.target.closest("[data-menu-toggle]")) return;
1135
- if (ref.current && !ref.current.contains(e.target)) onClose();
1136
- };
1137
- const handleKey = (e) => {
1138
- if (e.key === "Escape") onClose();
1139
- };
1140
- window.addEventListener("pointerdown", handleClick);
1141
- window.addEventListener("keydown", handleKey);
1142
- return () => {
1143
- window.removeEventListener("pointerdown", handleClick);
1144
- window.removeEventListener("keydown", handleKey);
1145
- };
1146
- }, [onClose]);
1147
- useEffect(() => {
1148
- const el = ref.current;
1149
- if (!el) return;
1150
- requestAnimationFrame(() => {
1151
- const rect = el.getBoundingClientRect();
1152
- const margin = 8;
1153
- if (rect.right > window.innerWidth - margin) {
1154
- const overflow = rect.right - window.innerWidth + margin;
1155
- if (el.style.left) {
1156
- el.style.left = `${parseFloat(el.style.left) - overflow}px`;
1157
- } else if (!el.style.right) {
1158
- el.style.left = `${rect.left - overflow}px`;
1159
- }
1160
- }
1161
- if (rect.left < margin) {
1162
- if (el.style.left) {
1163
- el.style.left = `${margin}px`;
1164
- } else if (el.style.right) {
1165
- el.style.right = `${window.innerWidth - rect.width - margin}px`;
1166
- }
1167
- }
1168
- if (rect.bottom > window.innerHeight - margin) {
1169
- const overflow = rect.bottom - window.innerHeight + margin;
1170
- if (el.style.top) {
1171
- el.style.top = `${parseFloat(el.style.top) - overflow}px`;
1172
- } else if (el.style.bottom) {
1173
- el.style.bottom = `${margin}px`;
1174
- } else {
1175
- el.style.top = `${rect.top - overflow}px`;
1176
- }
1177
- }
1178
- if (rect.top < margin) {
1179
- if (el.style.top) {
1180
- el.style.top = `${margin}px`;
1181
- } else if (el.style.bottom) {
1182
- el.style.bottom = `${window.innerHeight - rect.height - margin}px`;
1183
- }
1184
- }
1185
- });
1186
- });
1187
- const density = getDensity();
1188
- const menu = /* @__PURE__ */ jsxs(
1189
- "div",
1190
- {
1191
- ref,
1192
- className: `fixed z-[400] rounded-2xl ${density === "tight" ? "py-1" : density === "large" ? "py-2" : "py-1.5"} ${className}`,
1193
- style: { minWidth, animation: "popup-in 0.12s ease-out", ...glassStyle(), ...style },
1194
- children: [
1195
- children,
1196
- /* @__PURE__ */ jsx("style", { children: `@keyframes popup-in { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }` })
1197
- ]
1198
- }
1199
- );
1200
- return portal ? createPortal(menu, document.body) : menu;
1201
- }
1202
- function PopupMenuItem({ onClick, children, className = "", danger, disabled }) {
1203
- const density = getDensity();
1204
- const itemPadY = density === "tight" ? "0.25rem" : density === "large" ? "0.6rem" : "max(0.25rem, calc(var(--menu-padding-y, 0.5rem) - 0.1rem))";
1205
- return /* @__PURE__ */ jsx(
1206
- "button",
1207
- {
1208
- onClick,
1209
- disabled,
1210
- className: `w-full flex items-center gap-2 text-left transition-colors rounded-lg mx-auto
1211
- ${danger ? "text-red-600 hover:bg-red-50" : "text-gray-700 hover:bg-blue-50 hover:text-blue-700"}
1212
- ${disabled ? "opacity-40 cursor-not-allowed" : ""}
1213
- ${className}`,
1214
- style: {
1215
- width: "calc(100% - 8px)",
1216
- marginLeft: 4,
1217
- marginRight: 4,
1218
- fontSize: "var(--menu-font-size, 14px)",
1219
- paddingLeft: "var(--menu-padding-x, 1rem)",
1220
- paddingRight: "var(--menu-padding-x, 1rem)",
1221
- paddingTop: itemPadY,
1222
- paddingBottom: itemPadY
1223
- },
1224
- children
1225
- }
1226
- );
1227
- }
1228
- function PopupMenuDivider() {
1229
- const density = getDensity();
1230
- return /* @__PURE__ */ jsx("div", { className: `border-t ${GLASS_DIVIDER} ${density === "tight" ? "my-0.5" : density === "large" ? "my-1.5" : "my-1"} mx-3` });
1231
- }
1232
- function PopupMenuLabel({ children }) {
1233
- const density = getDensity();
1234
- const labelPadY = density === "tight" ? "0.125rem" : density === "large" ? "0.375rem" : "0.25rem";
1235
- return /* @__PURE__ */ jsx(
1236
- "div",
1237
- {
1238
- className: "text-[10px] font-medium text-gray-400 uppercase tracking-wider",
1239
- style: {
1240
- paddingLeft: "var(--menu-padding-x, 1rem)",
1241
- paddingRight: "var(--menu-padding-x, 1rem)",
1242
- paddingTop: labelPadY,
1243
- paddingBottom: labelPadY
1244
- },
1245
- children
1246
- }
1247
- );
1248
- }
1249
- var QUERY = "(max-width: 767px), (pointer: coarse)";
1250
- function getSnapshot() {
1251
- if (typeof window === "undefined" || !window.matchMedia) return false;
1252
- return window.matchMedia(QUERY).matches;
1253
- }
1254
- function getServerSnapshot() {
1255
- return false;
1256
- }
1257
- function subscribe(cb) {
1258
- if (typeof window === "undefined" || !window.matchMedia) return () => {
1259
- };
1260
- const mql = window.matchMedia(QUERY);
1261
- if (mql.addEventListener) {
1262
- mql.addEventListener("change", cb);
1263
- return () => mql.removeEventListener("change", cb);
1264
- }
1265
- mql.addListener(cb);
1266
- return () => mql.removeListener(cb);
1267
- }
1268
- function useIsMobile() {
1269
- return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
1270
- }
1271
916
 
1272
917
  // src/shell/mobileSwipeStore.ts
1273
918
  var _parentKey = null;
@@ -1321,40 +966,6 @@ function drainPerfEvents() {
1321
966
  counts = empty();
1322
967
  return drained;
1323
968
  }
1324
- var ShellPrefsContext = createContext(null);
1325
- function ShellPrefsProvider({
1326
- value,
1327
- children
1328
- }) {
1329
- return /* @__PURE__ */ jsx(ShellPrefsContext.Provider, { value, children });
1330
- }
1331
- function useLocalStoragePrefs(storageKey = "react-os-shell:prefs", defaults) {
1332
- const [prefs, setPrefs] = useState(() => {
1333
- try {
1334
- const stored = JSON.parse(localStorage.getItem(storageKey) || "{}");
1335
- return { ...defaults ?? {}, ...stored };
1336
- } catch {
1337
- return { ...defaults ?? {} };
1338
- }
1339
- });
1340
- const save = useCallback((patch) => {
1341
- setPrefs((prev) => {
1342
- const next = { ...prev, ...patch };
1343
- try {
1344
- localStorage.setItem(storageKey, JSON.stringify(next));
1345
- } catch {
1346
- }
1347
- return next;
1348
- });
1349
- }, [storageKey]);
1350
- return { prefs, save };
1351
- }
1352
- function useShellPrefs() {
1353
- const ctx = useContext(ShellPrefsContext);
1354
- if (ctx) return ctx;
1355
- return { prefs: {}, save: () => {
1356
- } };
1357
- }
1358
969
 
1359
970
  // src/shell/workArea.ts
1360
971
  var SIDEBAR_STRIP_W = 280;
@@ -1497,7 +1108,7 @@ function CancelButton({ onClick, children, className }) {
1497
1108
  const ctx = useContext(ModalActionsContext);
1498
1109
  const handleClick = async () => {
1499
1110
  if (ctx?.isDirty) {
1500
- const { confirm: confirm2 } = await import('./ConfirmDialog-CXHJCDUL.js');
1111
+ const { confirm: confirm2 } = await import('./ConfirmDialog-WAM5OKAV.js');
1501
1112
  const ok = await confirm2({
1502
1113
  title: "Discard changes?",
1503
1114
  message: "You have unsaved changes. Are you sure you want to cancel? All changes will be lost.",
@@ -1897,27 +1508,11 @@ function subscribeActive(cb) {
1897
1508
  activeListeners.delete(cb);
1898
1509
  };
1899
1510
  }
1900
- var escapeInterceptors = /* @__PURE__ */ new Set();
1901
- function registerModalEscapeInterceptor(fn) {
1902
- escapeInterceptors.add(fn);
1903
- return () => {
1904
- escapeInterceptors.delete(fn);
1905
- };
1906
- }
1907
1511
  var MODAL_CLOSE_REQUEST_EVENT = "modal-close-request";
1908
1512
  function requestModalClose(windowKey) {
1909
1513
  window.dispatchEvent(new CustomEvent(MODAL_CLOSE_REQUEST_EVENT, {
1910
- detail: { windowKey }
1911
- }));
1912
- }
1913
- function runEscapeInterceptors(e) {
1914
- for (const fn of escapeInterceptors) {
1915
- try {
1916
- if (fn(e)) return true;
1917
- } catch {
1918
- }
1919
- }
1920
- return false;
1514
+ detail: { windowKey }
1515
+ }));
1921
1516
  }
1922
1517
  function useIsActiveModal(modalId) {
1923
1518
  const activeId = useSyncExternalStore(subscribeActive, getActiveModalId);
@@ -3315,263 +2910,218 @@ function Modal({ open, onClose, title, icon, copyText, size = "lg", dirty = fals
3315
2910
  document.body
3316
2911
  );
3317
2912
  }
3318
- var ConfirmContext = createContext(() => Promise.resolve(false));
3319
- var useConfirm = () => useContext(ConfirmContext);
3320
- var globalConfirmFn = () => Promise.resolve(false);
3321
- var confirm = (opts) => globalConfirmFn(opts);
3322
- var globalDestructiveConfirmFn = () => Promise.resolve(false);
3323
- var confirmDestructive = (opts) => globalDestructiveConfirmFn(opts);
3324
- var globalPromptFn = () => Promise.resolve(null);
3325
- var prompt = (opts) => globalPromptFn(opts);
3326
- function ConfirmProvider({ children }) {
3327
- const [open, setOpen] = useState(false);
3328
- const [options, setOptions] = useState({ message: "" });
3329
- const [confirmId, setConfirmId] = useState(0);
3330
- const resolveRef = useRef();
3331
- const activeConfirmIdRef = useRef(null);
3332
- const nextConfirmIdRef = useRef(0);
3333
- const confirmQueueRef = useRef([]);
3334
- const showConfirm = useCallback((request) => {
3335
- activeConfirmIdRef.current = request.id;
3336
- resolveRef.current = request.resolve;
3337
- setConfirmId(request.id);
3338
- setOptions(request.options);
3339
- setOpen(true);
2913
+
2914
+ // src/hooks/undoHistory.ts
2915
+ var UNDO_LIMIT = 100;
2916
+ var emptyUndoState = { past: [], future: [] };
2917
+ function undoReducer(state, action) {
2918
+ switch (action.type) {
2919
+ case "record": {
2920
+ const newest = state.past[state.past.length - 1];
2921
+ if (action.step.coalesceKey !== null && newest?.coalesceKey === action.step.coalesceKey) {
2922
+ return state.future.length === 0 ? state : { ...state, future: [] };
2923
+ }
2924
+ const past = [...state.past, action.step];
2925
+ return {
2926
+ past: past.length > UNDO_LIMIT ? past.slice(past.length - UNDO_LIMIT) : past,
2927
+ future: []
2928
+ };
2929
+ }
2930
+ case "undo": {
2931
+ if (state.past.length === 0) return state;
2932
+ return {
2933
+ past: state.past.slice(0, -1),
2934
+ future: [action.redoStep, ...state.future]
2935
+ };
2936
+ }
2937
+ case "redo": {
2938
+ if (state.future.length === 0) return state;
2939
+ return {
2940
+ past: [...state.past, action.undoStep],
2941
+ future: state.future.slice(1)
2942
+ };
2943
+ }
2944
+ case "clear":
2945
+ return state.past.length === 0 && state.future.length === 0 ? state : emptyUndoState;
2946
+ }
2947
+ }
2948
+ var TEXT_ENTRY = /* @__PURE__ */ new Set(["INPUT", "TEXTAREA", "SELECT"]);
2949
+ function matchUndoHotkey(e) {
2950
+ if (!(e.metaKey || e.ctrlKey) || e.altKey) return null;
2951
+ const key = e.key.toLowerCase();
2952
+ const action = key === "z" ? e.shiftKey ? "redo" : "undo" : key === "y" && e.ctrlKey && !e.metaKey && !e.shiftKey ? "redo" : null;
2953
+ if (!action) return null;
2954
+ const target = e.target;
2955
+ if (target?.isContentEditable) return null;
2956
+ if (target?.tagName && TEXT_ENTRY.has(target.tagName.toUpperCase())) return null;
2957
+ return action;
2958
+ }
2959
+ var UndoContext = createContext(null);
2960
+ var RUNAWAY_LIMIT = 500;
2961
+ var RUNAWAY_QUIET_MS = 250;
2962
+ function UndoProvider({ children, canEdit = true, perms, windowId }) {
2963
+ const [state, dispatch] = useReducer(undoReducer, emptyUndoState);
2964
+ const slices = useRef(/* @__PURE__ */ new Map());
2965
+ const { hasAnyPerm } = useShellAuth();
2966
+ const enabled = canEdit && (perms && perms.length > 0 ? hasAnyPerm(perms) : true);
2967
+ const pending = useRef(null);
2968
+ const suspended = useRef(false);
2969
+ const snapshot = useCallback(() => {
2970
+ const values = {};
2971
+ for (const [id, slice] of slices.current) values[id] = slice.getLast();
2972
+ return values;
3340
2973
  }, []);
3341
- const confirmFn = useCallback((opts) => {
3342
- const normalized = typeof opts === "string" ? { message: opts } : opts;
3343
- return new Promise((resolve) => {
3344
- const request = { id: ++nextConfirmIdRef.current, options: normalized, resolve };
3345
- if (activeConfirmIdRef.current === null) showConfirm(request);
3346
- else confirmQueueRef.current.push(request);
3347
- });
3348
- }, [showConfirm]);
3349
- const [dOpen, setDOpen] = useState(false);
3350
- const [dOptions, setDOptions] = useState({ message: "", confirmWord: "Delete" });
3351
- const [dInput, setDInput] = useState("");
3352
- const dResolveRef = useRef();
3353
- const destructiveConfirmFn = useCallback((opts) => {
3354
- setDOptions(opts);
3355
- setDInput("");
3356
- setDOpen(true);
3357
- return new Promise((resolve) => {
3358
- dResolveRef.current = resolve;
3359
- });
2974
+ const register = useCallback((id, slice) => {
2975
+ slices.current.set(id, slice);
3360
2976
  }, []);
3361
- const [pOpen, setPOpen] = useState(false);
3362
- const [pOptions, setPOptions] = useState({});
3363
- const [pInput, setPInput] = useState("");
3364
- const pResolveRef = useRef();
3365
- const promptFn = useCallback((opts) => {
3366
- const normalized = typeof opts === "string" ? { message: opts } : opts;
3367
- setPOptions(normalized);
3368
- setPInput(normalized.defaultValue ?? "");
3369
- setPOpen(true);
3370
- return new Promise((resolve) => {
3371
- pResolveRef.current = resolve;
3372
- });
2977
+ const unregister = useCallback((id) => {
2978
+ slices.current.delete(id);
3373
2979
  }, []);
3374
- useEffect(() => {
3375
- globalConfirmFn = confirmFn;
3376
- globalDestructiveConfirmFn = destructiveConfirmFn;
3377
- globalPromptFn = promptFn;
3378
- }, [confirmFn, destructiveConfirmFn, promptFn]);
3379
- const handleClose = (id, result) => {
3380
- if (activeConfirmIdRef.current !== id) return;
3381
- const resolve = resolveRef.current;
3382
- const next = confirmQueueRef.current.shift();
3383
- if (next) showConfirm(next);
3384
- else {
3385
- activeConfirmIdRef.current = null;
3386
- resolveRef.current = void 0;
3387
- setOpen(false);
2980
+ const runaway = useRef({ last: 0, count: 0, tripped: false });
2981
+ const record = useCallback((label, coalesceKey) => {
2982
+ if (!enabled || suspended.current || pending.current) return;
2983
+ const r = runaway.current;
2984
+ if (r.tripped) return;
2985
+ const now = Date.now();
2986
+ if (now - r.last > RUNAWAY_QUIET_MS) r.count = 0;
2987
+ r.last = now;
2988
+ if (++r.count > RUNAWAY_LIMIT) {
2989
+ r.tripped = true;
2990
+ console.error(
2991
+ `[react-os-shell] UndoProvider: "${label}" recorded ${RUNAWAY_LIMIT} undo steps with no pause between them, which no user action can do. The value passed to useUndoable for this slice is almost certainly rebuilt on every render (a .map/.filter/object literal in the call) rather than held in state, so it reads as changed every time \u2014 and since recording a step re-renders the form, that is a loop. Undo recording is now off for this form to keep the tab responsive.`
2992
+ );
2993
+ return;
3388
2994
  }
3389
- resolve?.(result);
3390
- };
3391
- const handleDClose = (result) => {
3392
- setDOpen(false);
3393
- setDInput("");
3394
- dResolveRef.current?.(result);
3395
- };
3396
- const handlePClose = (commit) => {
3397
- if (commit) {
3398
- const trimmed = pInput.trim();
3399
- if (!trimmed && !pOptions.allowEmpty) {
3400
- setPOpen(false);
3401
- pResolveRef.current?.(null);
3402
- return;
3403
- }
3404
- setPOpen(false);
3405
- pResolveRef.current?.(trimmed);
3406
- } else {
3407
- setPOpen(false);
3408
- pResolveRef.current?.(null);
2995
+ pending.current = { values: snapshot(), label, coalesceKey };
2996
+ queueMicrotask(() => {
2997
+ const step = pending.current;
2998
+ pending.current = null;
2999
+ if (step) dispatch({ type: "record", step });
3000
+ });
3001
+ }, [enabled, snapshot]);
3002
+ const applyValues = useCallback((values) => {
3003
+ for (const [id, value2] of Object.entries(values)) {
3004
+ const slice = slices.current.get(id);
3005
+ if (!slice) continue;
3006
+ slice.setLast(value2);
3007
+ slice.apply(value2);
3409
3008
  }
3009
+ }, []);
3010
+ const undo = useCallback(() => {
3011
+ const step = state.past[state.past.length - 1];
3012
+ if (!step) return;
3013
+ const redoStep = { values: snapshot(), label: step.label, coalesceKey: null };
3014
+ applyValues(step.values);
3015
+ dispatch({ type: "undo", redoStep });
3016
+ }, [state.past, snapshot, applyValues]);
3017
+ const redo = useCallback(() => {
3018
+ const step = state.future[0];
3019
+ if (!step) return;
3020
+ const undoStep = { values: snapshot(), label: step.label, coalesceKey: null };
3021
+ applyValues(step.values);
3022
+ dispatch({ type: "redo", undoStep });
3023
+ }, [state.future, snapshot, applyValues]);
3024
+ const [baselineToken, setBaselineToken] = useState(0);
3025
+ const baseline = useCallback(() => {
3026
+ suspended.current = true;
3027
+ pending.current = null;
3028
+ setBaselineToken((t) => t + 1);
3029
+ dispatch({ type: "clear" });
3030
+ }, []);
3031
+ useEffect(() => {
3032
+ if (!suspended.current) return;
3033
+ suspended.current = false;
3034
+ pending.current = null;
3035
+ dispatch({ type: "clear" });
3036
+ }, [baselineToken]);
3037
+ const clear = baseline;
3038
+ const canUndo = enabled && state.past.length > 0;
3039
+ const canRedo = enabled && state.future.length > 0;
3040
+ const isActive = useIsActiveWindow(windowId);
3041
+ const keyed = useRef({ undo, redo, canUndo, canRedo });
3042
+ keyed.current = { undo, redo, canUndo, canRedo };
3043
+ useEffect(() => {
3044
+ if (!isActive || !enabled) return;
3045
+ const handler = (e) => {
3046
+ const action = matchUndoHotkey(e);
3047
+ if (!action) return;
3048
+ const k = keyed.current;
3049
+ if (action === "undo" ? !k.canUndo : !k.canRedo) return;
3050
+ e.preventDefault();
3051
+ if (action === "undo") k.undo();
3052
+ else k.redo();
3053
+ };
3054
+ window.addEventListener("keydown", handler);
3055
+ return () => window.removeEventListener("keydown", handler);
3056
+ }, [isActive, enabled]);
3057
+ const value = useMemo(() => ({
3058
+ register,
3059
+ unregister,
3060
+ record,
3061
+ undo,
3062
+ redo,
3063
+ clear,
3064
+ baseline,
3065
+ canUndo,
3066
+ canRedo,
3067
+ enabled,
3068
+ undoLabel: canUndo ? state.past[state.past.length - 1].label : null,
3069
+ redoLabel: canRedo ? state.future[0].label : null
3070
+ }), [register, unregister, record, undo, redo, clear, baseline, canUndo, canRedo, enabled, state.past, state.future]);
3071
+ return /* @__PURE__ */ jsx(UndoContext.Provider, { value, children });
3072
+ }
3073
+ function useUndo() {
3074
+ const ctx = useContext(UndoContext);
3075
+ const noop = useCallback(() => {
3076
+ }, []);
3077
+ return {
3078
+ undo: ctx?.undo ?? noop,
3079
+ redo: ctx?.redo ?? noop,
3080
+ clear: ctx?.clear ?? noop,
3081
+ baseline: ctx?.baseline ?? noop,
3082
+ canUndo: ctx?.canUndo ?? false,
3083
+ canRedo: ctx?.canRedo ?? false,
3084
+ undoLabel: ctx?.undoLabel ?? null,
3085
+ redoLabel: ctx?.redoLabel ?? null,
3086
+ enabled: ctx?.enabled ?? false
3410
3087
  };
3088
+ }
3089
+ function useUndoable(value, apply, opts) {
3090
+ const ctx = useContext(UndoContext);
3091
+ const id = useId();
3092
+ const last = useRef(value);
3093
+ const applyRef = useRef(apply);
3094
+ applyRef.current = apply;
3411
3095
  useEffect(() => {
3412
- if (!open && !dOpen && !pOpen) return;
3413
- return registerModalEscapeInterceptor(() => {
3414
- if (pOpen) {
3415
- handlePClose(false);
3416
- return true;
3417
- }
3418
- if (dOpen) {
3419
- handleDClose(false);
3420
- return true;
3421
- }
3422
- if (open) {
3423
- handleClose(confirmId, false);
3424
- return true;
3425
- }
3426
- return false;
3096
+ if (!ctx) return;
3097
+ ctx.register(id, {
3098
+ getLast: () => last.current,
3099
+ setLast: (v) => {
3100
+ last.current = v;
3101
+ },
3102
+ apply: (v) => applyRef.current(v)
3427
3103
  });
3428
- }, [open, dOpen, pOpen, confirmId]);
3429
- const variant = options.variant || (options.confirmLabel?.toLowerCase().includes("delete") || options.message.toLowerCase().includes("delete") ? "danger" : "info");
3430
- const confirmBtnClass = variant === "danger" ? "bg-red-600 hover:bg-red-700 text-white" : variant === "warning" ? "bg-yellow-600 hover:bg-yellow-700 text-white" : "bg-blue-600 hover:bg-blue-700 text-white";
3431
- const iconClass = variant === "danger" ? "text-red-600 bg-red-100" : variant === "warning" ? "text-yellow-600 bg-yellow-100" : "text-blue-600 bg-blue-100";
3432
- return /* @__PURE__ */ jsxs(ConfirmContext.Provider, { value: confirmFn, children: [
3433
- children,
3434
- /* @__PURE__ */ jsxs(Dialog, { open, onClose: () => handleClose(confirmId, false), className: "relative z-[9999]", children: [
3435
- /* @__PURE__ */ jsx(DialogBackdrop, { className: "fixed inset-0 bg-black/30 transition-opacity" }),
3436
- /* @__PURE__ */ jsx("div", { className: "fixed inset-0 flex items-center justify-center p-4", children: /* @__PURE__ */ jsxs(DialogPanel, { className: "w-full max-w-md rounded-lg bg-white p-6 shadow-xl", children: [
3437
- /* @__PURE__ */ jsxs("div", { className: "flex gap-4", children: [
3438
- /* @__PURE__ */ jsx("div", { className: `shrink-0 w-10 h-10 rounded-full flex items-center justify-center ${iconClass}`, children: /* @__PURE__ */ jsx(ExclamationTriangleIcon, { className: "h-5 w-5" }) }),
3439
- /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
3440
- /* @__PURE__ */ jsx(DialogTitle, { className: "text-base font-semibold text-gray-900", children: options.title || "Confirm" }),
3441
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-gray-600", children: options.message })
3442
- ] })
3443
- ] }),
3444
- /* @__PURE__ */ jsxs("div", { className: "mt-6 flex justify-end gap-3", children: [
3445
- /* @__PURE__ */ jsx(
3446
- "button",
3447
- {
3448
- type: "button",
3449
- onClick: () => handleClose(confirmId, false),
3450
- className: "bg-white text-gray-700 border border-gray-300 px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-50",
3451
- children: options.cancelLabel || "Cancel"
3452
- }
3453
- ),
3454
- /* @__PURE__ */ jsx(
3455
- "button",
3456
- {
3457
- type: "button",
3458
- onClick: () => handleClose(confirmId, true),
3459
- className: `px-4 py-2 text-sm font-medium rounded-lg ${confirmBtnClass}`,
3460
- children: options.confirmLabel || "OK"
3461
- }
3462
- )
3463
- ] })
3464
- ] }) })
3465
- ] }),
3466
- /* @__PURE__ */ jsxs(Dialog, { open: dOpen, onClose: () => handleDClose(false), className: "relative z-[9999]", children: [
3467
- /* @__PURE__ */ jsx(DialogBackdrop, { className: "fixed inset-0 bg-black/30 transition-opacity" }),
3468
- /* @__PURE__ */ jsx("div", { className: "fixed inset-0 flex items-center justify-center p-4", children: /* @__PURE__ */ jsxs(DialogPanel, { className: "relative w-full max-w-md rounded-lg bg-white p-6 shadow-xl", children: [
3469
- /* @__PURE__ */ jsx(
3470
- "button",
3471
- {
3472
- type: "button",
3473
- onClick: () => handleDClose(false),
3474
- "aria-label": "Close",
3475
- className: "absolute right-4 top-4 rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600",
3476
- children: /* @__PURE__ */ jsx(XMarkIcon, { className: "h-5 w-5" })
3477
- }
3478
- ),
3479
- /* @__PURE__ */ jsxs("div", { className: "flex gap-4", children: [
3480
- /* @__PURE__ */ jsx("div", { className: `shrink-0 w-10 h-10 rounded-full flex items-center justify-center ${dOptions.variant === "warning" ? "text-yellow-600 bg-yellow-100" : "text-red-600 bg-red-100"}`, children: /* @__PURE__ */ jsx(ExclamationTriangleIcon, { className: "h-5 w-5" }) }),
3481
- /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
3482
- /* @__PURE__ */ jsx(DialogTitle, { className: "text-base font-semibold text-gray-900 pr-6", children: dOptions.title || "Confirm Action" }),
3483
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-gray-600", children: dOptions.message }),
3484
- /* @__PURE__ */ jsxs("p", { className: "mt-3 text-sm text-gray-700", children: [
3485
- "Type ",
3486
- /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 bg-gray-50 px-1.5 py-0.5 text-xs font-bold text-red-600 font-mono", children: dOptions.confirmWord }),
3487
- " to confirm:"
3488
- ] }),
3489
- /* @__PURE__ */ jsx(
3490
- "input",
3491
- {
3492
- autoFocus: true,
3493
- type: "text",
3494
- value: dInput,
3495
- onChange: (e) => setDInput(e.target.value),
3496
- onKeyDown: (e) => {
3497
- if (e.key === "Enter" && dInput === dOptions.confirmWord) handleDClose(true);
3498
- },
3499
- placeholder: dOptions.confirmWord,
3500
- className: "mt-2 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-red-500 focus:ring-red-500"
3501
- }
3502
- )
3503
- ] })
3504
- ] }),
3505
- /* @__PURE__ */ jsxs("div", { className: "mt-6 flex justify-end gap-3", children: [
3506
- /* @__PURE__ */ jsx(
3507
- "button",
3508
- {
3509
- type: "button",
3510
- onClick: () => handleDClose(false),
3511
- className: "bg-white text-gray-700 border border-gray-300 px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-50",
3512
- children: "Dismiss"
3513
- }
3514
- ),
3515
- /* @__PURE__ */ jsx(
3516
- "button",
3517
- {
3518
- type: "button",
3519
- onClick: () => handleDClose(true),
3520
- disabled: dInput !== dOptions.confirmWord,
3521
- className: `px-4 py-2 text-sm font-medium rounded-lg disabled:opacity-40 ${dOptions.variant === "warning" ? "bg-yellow-600 hover:bg-yellow-700 text-white" : "bg-red-600 hover:bg-red-700 text-white"}`,
3522
- children: dOptions.confirmWord
3523
- }
3524
- )
3525
- ] })
3526
- ] }) })
3527
- ] }),
3528
- /* @__PURE__ */ jsxs(Dialog, { open: pOpen, onClose: () => handlePClose(false), className: "relative z-[9999]", children: [
3529
- /* @__PURE__ */ jsx(DialogBackdrop, { className: "fixed inset-0 bg-black/30 transition-opacity" }),
3530
- /* @__PURE__ */ jsx("div", { className: "fixed inset-0 flex items-center justify-center p-4", children: /* @__PURE__ */ jsxs(DialogPanel, { className: "w-full max-w-md rounded-lg bg-white p-6 shadow-xl", children: [
3531
- /* @__PURE__ */ jsx(DialogTitle, { className: "text-base font-semibold text-gray-900", children: pOptions.title || "Enter a value" }),
3532
- pOptions.message && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-gray-600", children: pOptions.message }),
3533
- /* @__PURE__ */ jsx(
3534
- "input",
3535
- {
3536
- autoFocus: true,
3537
- type: "text",
3538
- value: pInput,
3539
- onChange: (e) => setPInput(e.target.value),
3540
- onKeyDown: (e) => {
3541
- if (e.key === "Enter") handlePClose(true);
3542
- else if (e.key === "Escape") handlePClose(false);
3543
- },
3544
- onFocus: (e) => e.target.select(),
3545
- placeholder: pOptions.placeholder,
3546
- className: "mt-3 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
3547
- }
3548
- ),
3549
- /* @__PURE__ */ jsxs("div", { className: "mt-6 flex justify-end gap-3", children: [
3550
- /* @__PURE__ */ jsx(
3551
- "button",
3552
- {
3553
- type: "button",
3554
- onClick: () => handlePClose(false),
3555
- className: "bg-white text-gray-700 border border-gray-300 px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-50",
3556
- children: pOptions.cancelLabel || "Cancel"
3557
- }
3558
- ),
3559
- /* @__PURE__ */ jsx(
3560
- "button",
3561
- {
3562
- type: "button",
3563
- onClick: () => handlePClose(true),
3564
- disabled: !pOptions.allowEmpty && !pInput.trim(),
3565
- className: "bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 text-sm font-medium rounded-lg disabled:opacity-40",
3566
- children: pOptions.confirmLabel || "OK"
3567
- }
3568
- )
3569
- ] })
3570
- ] }) })
3571
- ] })
3572
- ] });
3104
+ return () => ctx.unregister(id);
3105
+ }, [ctx, id]);
3106
+ const { label, coalesceKey = null } = opts;
3107
+ const ctxRef = useRef(ctx);
3108
+ ctxRef.current = ctx;
3109
+ const optsRef = useRef({ label, coalesceKey });
3110
+ optsRef.current = { label, coalesceKey };
3111
+ useEffect(() => {
3112
+ const c = ctxRef.current;
3113
+ if (!c) return;
3114
+ if (Object.is(value, last.current)) return;
3115
+ c.record(optsRef.current.label, optsRef.current.coalesceKey);
3116
+ last.current = value;
3117
+ }, [value]);
3118
+ }
3119
+ function useUndoableState(initial, opts) {
3120
+ const [value, setValue] = useState(initial);
3121
+ useUndoable(value, (next) => setValue(() => next), opts);
3122
+ return [value, setValue];
3573
3123
  }
3574
3124
 
3575
- export { CancelButton, ConfirmProvider, CopyButton, DocFavStar, LoadingSpinner, Modal, ModalActions, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, SIDEBAR_STRIP_W, ShellPrefsProvider, UndoProvider, WINDOW_REGISTRY, WindowCrashedFallback, WindowErrorBoundary, WindowManagerProvider, WindowTitle, beginWindowGesture, cancelWindowFront, client_default, commitExposeHighlight, confirm, confirmDestructive, drainPerfEvents, exitExposeMode, forgetMaximizedWindowBoxes, getActiveModalId, getActiveWindowRoute, getExposeHighlight, getWindowPosition, isEntityEntry, isPageEntry, isShellApiClientConfigured, markMenuOpen, prompt, registerModalEscapeInterceptor, requestWindowFront, setExposeHighlight, setPerfCollecting, setShellApiClient, setShellWindowRegistry, setWindowDefaultPosition, setWindowPosition, subscribeExposeHighlight, toggleExposeMode, useConfirm, useIsActiveWindow, useIsMobile, useLocalStoragePrefs, useModalActive, useShellPrefs, useUndo, useUndoable, useUndoableState, useWidgetSettings, useWindowDirty, useWindowManager, useWindowMenuItem, useWindowTitle };
3576
- //# sourceMappingURL=chunk-H3HFM4JQ.js.map
3577
- //# sourceMappingURL=chunk-H3HFM4JQ.js.map
3125
+ export { CancelButton, CopyButton, DocFavStar, LoadingSpinner, Modal, ModalActions, SIDEBAR_STRIP_W, UndoProvider, WINDOW_REGISTRY, WindowCrashedFallback, WindowErrorBoundary, WindowManagerProvider, WindowTitle, beginWindowGesture, cancelWindowFront, client_default, commitExposeHighlight, drainPerfEvents, exitExposeMode, forgetMaximizedWindowBoxes, getActiveModalId, getActiveWindowRoute, getExposeHighlight, getWindowPosition, isEntityEntry, isPageEntry, isShellApiClientConfigured, markMenuOpen, requestWindowFront, setExposeHighlight, setPerfCollecting, setShellApiClient, setShellWindowRegistry, setWindowDefaultPosition, setWindowPosition, subscribeExposeHighlight, toggleExposeMode, useIsActiveWindow, useModalActive, useUndo, useUndoable, useUndoableState, useWidgetSettings, useWindowDirty, useWindowManager, useWindowMenuItem, useWindowTitle };
3126
+ //# sourceMappingURL=chunk-IGPSRRL7.js.map
3127
+ //# sourceMappingURL=chunk-IGPSRRL7.js.map