efficient-shell 0.0.0 → 4.126.3

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 (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +758 -3
  3. package/dist/Browser-LM4RRZIL.js +478 -0
  4. package/dist/Browser-LM4RRZIL.js.map +1 -0
  5. package/dist/Calculator-HCKFTSJR.js +188 -0
  6. package/dist/Calculator-HCKFTSJR.js.map +1 -0
  7. package/dist/ConfirmDialog-LJXMB45A.js +3 -0
  8. package/dist/ConfirmDialog-LJXMB45A.js.map +1 -0
  9. package/dist/CurrencyConverter-BKKORN6K.js +232 -0
  10. package/dist/CurrencyConverter-BKKORN6K.js.map +1 -0
  11. package/dist/Documents-DD3QI5ER.js +437 -0
  12. package/dist/Documents-DD3QI5ER.js.map +1 -0
  13. package/dist/Files-BOPCPMJE.js +801 -0
  14. package/dist/Files-BOPCPMJE.js.map +1 -0
  15. package/dist/Notepad-KJOLF63O.js +406 -0
  16. package/dist/Notepad-KJOLF63O.js.map +1 -0
  17. package/dist/PdfViewer-LSP3YNFC.js +4 -0
  18. package/dist/PdfViewer-LSP3YNFC.js.map +1 -0
  19. package/dist/PomodoroTimer-SVJ62RXM.js +629 -0
  20. package/dist/PomodoroTimer-SVJ62RXM.js.map +1 -0
  21. package/dist/Preview-5UOC7ZRU.js +2955 -0
  22. package/dist/Preview-5UOC7ZRU.js.map +1 -0
  23. package/dist/Sidebar-J76PV4QF.js +290 -0
  24. package/dist/Sidebar-J76PV4QF.js.map +1 -0
  25. package/dist/Spreadsheet-J7BH7UHC.js +515 -0
  26. package/dist/Spreadsheet-J7BH7UHC.js.map +1 -0
  27. package/dist/Stock-CDUS4VCZ.js +154 -0
  28. package/dist/Stock-CDUS4VCZ.js.map +1 -0
  29. package/dist/Weather-EEDBWBPQ.js +427 -0
  30. package/dist/Weather-EEDBWBPQ.js.map +1 -0
  31. package/dist/WorldClock-FFFULVTJ.js +198 -0
  32. package/dist/WorldClock-FFFULVTJ.js.map +1 -0
  33. package/dist/apps/index.d.ts +267 -0
  34. package/dist/apps/index.js +147 -0
  35. package/dist/apps/index.js.map +1 -0
  36. package/dist/brand.css +2 -0
  37. package/dist/chunk-2VM2FRZJ.js +215 -0
  38. package/dist/chunk-2VM2FRZJ.js.map +1 -0
  39. package/dist/chunk-32W6VY2J.js +549 -0
  40. package/dist/chunk-32W6VY2J.js.map +1 -0
  41. package/dist/chunk-3CUSBZWG.js +59 -0
  42. package/dist/chunk-3CUSBZWG.js.map +1 -0
  43. package/dist/chunk-3L463T5H.js +26 -0
  44. package/dist/chunk-3L463T5H.js.map +1 -0
  45. package/dist/chunk-4TVV2BIU.js +13334 -0
  46. package/dist/chunk-4TVV2BIU.js.map +1 -0
  47. package/dist/chunk-6XQHH7D2.js +105 -0
  48. package/dist/chunk-6XQHH7D2.js.map +1 -0
  49. package/dist/chunk-7CCHEEYC.js +94 -0
  50. package/dist/chunk-7CCHEEYC.js.map +1 -0
  51. package/dist/chunk-7KIOEKHS.js +617 -0
  52. package/dist/chunk-7KIOEKHS.js.map +1 -0
  53. package/dist/chunk-AXWC3332.js +1103 -0
  54. package/dist/chunk-AXWC3332.js.map +1 -0
  55. package/dist/chunk-BFBWLCO6.js +16 -0
  56. package/dist/chunk-BFBWLCO6.js.map +1 -0
  57. package/dist/chunk-CT3HVZRZ.js +26 -0
  58. package/dist/chunk-CT3HVZRZ.js.map +1 -0
  59. package/dist/chunk-D7PYW2QS.js +265 -0
  60. package/dist/chunk-D7PYW2QS.js.map +1 -0
  61. package/dist/chunk-DMCFOWOJ.js +153 -0
  62. package/dist/chunk-DMCFOWOJ.js.map +1 -0
  63. package/dist/chunk-FQB24B42.js +416 -0
  64. package/dist/chunk-FQB24B42.js.map +1 -0
  65. package/dist/chunk-GA35NEND.js +56 -0
  66. package/dist/chunk-GA35NEND.js.map +1 -0
  67. package/dist/chunk-HNH7FOYB.js +69 -0
  68. package/dist/chunk-HNH7FOYB.js.map +1 -0
  69. package/dist/chunk-HYZ5QMVO.js +462 -0
  70. package/dist/chunk-HYZ5QMVO.js.map +1 -0
  71. package/dist/chunk-I464GYJJ.js +17 -0
  72. package/dist/chunk-I464GYJJ.js.map +1 -0
  73. package/dist/chunk-JFQGPY7W.js +819 -0
  74. package/dist/chunk-JFQGPY7W.js.map +1 -0
  75. package/dist/chunk-MGGY74RY.js +14 -0
  76. package/dist/chunk-MGGY74RY.js.map +1 -0
  77. package/dist/chunk-N3CG5HG5.js +144 -0
  78. package/dist/chunk-N3CG5HG5.js.map +1 -0
  79. package/dist/chunk-QRGFTK2X.js +79 -0
  80. package/dist/chunk-QRGFTK2X.js.map +1 -0
  81. package/dist/chunk-SZ4WMHUX.js +7 -0
  82. package/dist/chunk-SZ4WMHUX.js.map +1 -0
  83. package/dist/chunk-W6XTG23K.js +3708 -0
  84. package/dist/chunk-W6XTG23K.js.map +1 -0
  85. package/dist/chunk-YG45DAUT.js +96 -0
  86. package/dist/chunk-YG45DAUT.js.map +1 -0
  87. package/dist/chunk-YNGPTOL6.js +46 -0
  88. package/dist/chunk-YNGPTOL6.js.map +1 -0
  89. package/dist/chunk-YNHLQ24V.js +400 -0
  90. package/dist/chunk-YNHLQ24V.js.map +1 -0
  91. package/dist/chunk-YNKK7NE3.js +171 -0
  92. package/dist/chunk-YNKK7NE3.js.map +1 -0
  93. package/dist/chunk-YPO6RDMX.js +20 -0
  94. package/dist/chunk-YPO6RDMX.js.map +1 -0
  95. package/dist/file-intake/index.d.ts +87 -0
  96. package/dist/file-intake/index.js +3 -0
  97. package/dist/file-intake/index.js.map +1 -0
  98. package/dist/index-dQia6NpB.d.ts +5904 -0
  99. package/dist/index.d.ts +2495 -0
  100. package/dist/index.js +7460 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/markdown/index.d.ts +118 -0
  103. package/dist/markdown/index.js +184 -0
  104. package/dist/markdown/index.js.map +1 -0
  105. package/dist/markup/index.d.ts +235 -0
  106. package/dist/markup/index.js +135 -0
  107. package/dist/markup/index.js.map +1 -0
  108. package/dist/page-tabs/index.d.ts +154 -0
  109. package/dist/page-tabs/index.js +706 -0
  110. package/dist/page-tabs/index.js.map +1 -0
  111. package/dist/shell.css +105 -0
  112. package/dist/sounds-NT4DEZGD.js +3 -0
  113. package/dist/sounds-NT4DEZGD.js.map +1 -0
  114. package/dist/styles.css +22 -0
  115. package/dist/themes.css +249 -0
  116. package/dist/types-BOinJXUf.d.ts +118 -0
  117. package/dist/ui/index.d.ts +4 -0
  118. package/dist/ui/index.js +15 -0
  119. package/dist/ui/index.js.map +1 -0
  120. package/dist/ui.css +1518 -0
  121. package/package.json +172 -3
@@ -0,0 +1,96 @@
1
+ import { useState, useRef, useCallback, useEffect } from 'react';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+
4
+ // src/shell/SidebarLayout.tsx
5
+ var clamp = (n, lo, hi) => Math.min(Math.max(n, lo), hi);
6
+ function SidebarLayout({
7
+ sidebar,
8
+ sidebarTop,
9
+ sidebarBottom,
10
+ children,
11
+ side = "left",
12
+ storageKey,
13
+ defaultWidth = 256,
14
+ minWidth = 180,
15
+ maxWidth = 480,
16
+ className = "",
17
+ sidebarClassName = side === "right" ? "border-l border-gray-200 bg-gray-50" : "border-r border-gray-200 bg-gray-50",
18
+ contentClassName = "bg-white"
19
+ }) {
20
+ const sideRight = side === "right";
21
+ const [width, setWidth] = useState(() => {
22
+ if (storageKey && typeof window !== "undefined") {
23
+ const saved = window.localStorage.getItem(storageKey);
24
+ const n = saved ? parseInt(saved, 10) : NaN;
25
+ if (!Number.isNaN(n)) return clamp(n, minWidth, maxWidth);
26
+ }
27
+ return clamp(defaultWidth, minWidth, maxWidth);
28
+ });
29
+ const dragRef = useRef(null);
30
+ const onMove = useCallback((e) => {
31
+ const d = dragRef.current;
32
+ if (!d) return;
33
+ const delta = (e.clientX - d.startX) * (sideRight ? -1 : 1);
34
+ setWidth(clamp(d.startWidth + delta, minWidth, maxWidth));
35
+ }, [minWidth, maxWidth, sideRight]);
36
+ const onUp = useCallback(() => {
37
+ dragRef.current = null;
38
+ window.removeEventListener("pointermove", onMove);
39
+ window.removeEventListener("pointerup", onUp);
40
+ document.body.style.removeProperty("cursor");
41
+ document.body.style.removeProperty("user-select");
42
+ }, [onMove]);
43
+ const startDrag = useCallback((e) => {
44
+ e.preventDefault();
45
+ dragRef.current = { startX: e.clientX, startWidth: width };
46
+ window.addEventListener("pointermove", onMove);
47
+ window.addEventListener("pointerup", onUp);
48
+ document.body.style.cursor = "col-resize";
49
+ document.body.style.userSelect = "none";
50
+ }, [width, onMove, onUp]);
51
+ useEffect(() => {
52
+ if (!storageKey) return;
53
+ const id = window.setTimeout(() => {
54
+ try {
55
+ window.localStorage.setItem(storageKey, String(Math.round(width)));
56
+ } catch {
57
+ }
58
+ }, 200);
59
+ return () => window.clearTimeout(id);
60
+ }, [width, storageKey]);
61
+ useEffect(() => () => {
62
+ window.removeEventListener("pointermove", onMove);
63
+ window.removeEventListener("pointerup", onUp);
64
+ }, [onMove, onUp]);
65
+ const edge = sideRight ? "left-0" : "right-0";
66
+ const hasSlots = sidebarTop != null || sidebarBottom != null;
67
+ const sidebarPane = /* @__PURE__ */ jsxs("div", { className: "relative flex h-full shrink-0 flex-col", style: { width }, children: [
68
+ /* @__PURE__ */ jsxs("div", { className: `flex h-full flex-col ${hasSlots ? "overflow-hidden" : "overflow-y-auto"} ${sidebarClassName}`, children: [
69
+ sidebarTop != null && /* @__PURE__ */ jsx("div", { className: "shrink-0 px-2 pt-3 pb-2", children: sidebarTop }),
70
+ hasSlots ? /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: sidebar }) : sidebar,
71
+ sidebarBottom != null && /* @__PURE__ */ jsx("div", { className: "mt-auto shrink-0 border-t border-gray-200 px-2 py-3", children: sidebarBottom })
72
+ ] }),
73
+ /* @__PURE__ */ jsx(
74
+ "div",
75
+ {
76
+ onPointerDown: startDrag,
77
+ onDoubleClick: () => setWidth(clamp(defaultWidth, minWidth, maxWidth)),
78
+ title: "Drag to resize \xB7 double-click to reset",
79
+ className: `group/resize absolute inset-y-0 ${edge} z-10 w-2 cursor-col-resize`,
80
+ children: /* @__PURE__ */ jsx("div", { className: `absolute inset-y-0 ${edge} w-px bg-transparent transition-colors group-hover/resize:bg-[var(--accent-500,#3b82f6)]` })
81
+ }
82
+ )
83
+ ] });
84
+ const contentPane = /* @__PURE__ */ jsx("div", { className: `flex min-w-0 flex-1 flex-col ${contentClassName}`, children });
85
+ return /* @__PURE__ */ jsx("div", { className: `flex h-full w-full overflow-hidden ${className}`, children: sideRight ? /* @__PURE__ */ jsxs(Fragment, { children: [
86
+ contentPane,
87
+ sidebarPane
88
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
89
+ sidebarPane,
90
+ contentPane
91
+ ] }) });
92
+ }
93
+
94
+ export { SidebarLayout };
95
+ //# sourceMappingURL=chunk-YG45DAUT.js.map
96
+ //# sourceMappingURL=chunk-YG45DAUT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shell/SidebarLayout.tsx"],"names":[],"mappings":"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","file":"chunk-YG45DAUT.js","sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport type { ReactNode, PointerEvent as ReactPointerEvent } from 'react';\n\n/**\n * Two-pane layout with a drag-to-resize sidebar (left or right).\n *\n * Renders flush (`h-full w-full`) — pair it with a `flushBody` window so the\n * sidebar runs from just under the title bar to the very bottom with no\n * surrounding padding. The user can drag the sidebar's inner edge to resize\n * it; pass `storageKey` to persist that width across reopens, or double-click\n * the handle to reset to `defaultWidth`. Set `side=\"right\"` to put the sidebar\n * on the right (e.g. a detail-panel layout).\n *\n * @example\n * <SidebarLayout sidebar={<MyNav />} storageKey=\"todo.sidebarWidth\">\n * <MyContent />\n * </SidebarLayout>\n */\nexport interface SidebarLayoutProps {\n /** Content of the left sidebar pane. When `sidebarTop`/`sidebarBottom` are\n * set this is the scrolling middle region (the filter nav); otherwise it is\n * the whole pane. */\n sidebar: ReactNode;\n /** Optional node pinned at the TOP of the sidebar, above `sidebar`, in a\n * padded non-scrolling region (e.g. a primary \"New X\" `SidebarActionButton`).\n * The list-window convention: primary create action lives here. */\n sidebarTop?: ReactNode;\n /** Optional node pinned at the BOTTOM of the sidebar, below `sidebar`, in a\n * padded, top-bordered non-scrolling region (e.g. an \"Export CSV\" button).\n * The middle `sidebar` grows to fill, so this stays flush to the bottom. */\n sidebarBottom?: ReactNode;\n /** Content of the main pane (opposite the sidebar). */\n children: ReactNode;\n /** Which side the sidebar sits on. Default `'left'`. When `'right'`, the\n * sidebar renders on the right with the resize handle on its left edge. */\n side?: 'left' | 'right';\n /** localStorage key to persist the sidebar width across reopens. When set,\n * the last dragged width is restored on mount. Omit for session-only width. */\n storageKey?: string;\n /** Initial sidebar width in px (used when nothing is persisted). Default 256. */\n defaultWidth?: number;\n /** Minimum sidebar width in px. Default 180. */\n minWidth?: number;\n /** Maximum sidebar width in px. Default 480. */\n maxWidth?: number;\n /** Extra classes for the outer flex row. */\n className?: string;\n /** Classes for the sidebar pane (background, border, …). The pane is a flex\n * column that scrolls its own overflow. Defaults to a light-grey,\n * right-bordered sidebar. */\n sidebarClassName?: string;\n /** Classes for the main content pane. Defaults to a white background. */\n contentClassName?: string;\n}\n\nconst clamp = (n: number, lo: number, hi: number) => Math.min(Math.max(n, lo), hi);\n\nexport default function SidebarLayout({\n sidebar,\n sidebarTop,\n sidebarBottom,\n children,\n side = 'left',\n storageKey,\n defaultWidth = 256,\n minWidth = 180,\n maxWidth = 480,\n className = '',\n sidebarClassName = side === 'right'\n ? 'border-l border-gray-200 bg-gray-50'\n : 'border-r border-gray-200 bg-gray-50',\n contentClassName = 'bg-white',\n}: SidebarLayoutProps) {\n const sideRight = side === 'right';\n const [width, setWidth] = useState<number>(() => {\n if (storageKey && typeof window !== 'undefined') {\n const saved = window.localStorage.getItem(storageKey);\n const n = saved ? parseInt(saved, 10) : NaN;\n if (!Number.isNaN(n)) return clamp(n, minWidth, maxWidth);\n }\n return clamp(defaultWidth, minWidth, maxWidth);\n });\n\n const dragRef = useRef<{ startX: number; startWidth: number } | null>(null);\n\n const onMove = useCallback((e: PointerEvent) => {\n const d = dragRef.current;\n if (!d) return;\n // Right-side sidebar grows as the handle is dragged left → invert the delta.\n const delta = (e.clientX - d.startX) * (sideRight ? -1 : 1);\n setWidth(clamp(d.startWidth + delta, minWidth, maxWidth));\n }, [minWidth, maxWidth, sideRight]);\n\n const onUp = useCallback(() => {\n dragRef.current = null;\n window.removeEventListener('pointermove', onMove);\n window.removeEventListener('pointerup', onUp);\n document.body.style.removeProperty('cursor');\n document.body.style.removeProperty('user-select');\n }, [onMove]);\n\n const startDrag = useCallback((e: ReactPointerEvent<HTMLDivElement>) => {\n e.preventDefault();\n dragRef.current = { startX: e.clientX, startWidth: width };\n window.addEventListener('pointermove', onMove);\n window.addEventListener('pointerup', onUp);\n document.body.style.cursor = 'col-resize';\n document.body.style.userSelect = 'none';\n }, [width, onMove, onUp]);\n\n // Persist (debounced) once the width settles.\n useEffect(() => {\n if (!storageKey) return;\n const id = window.setTimeout(() => {\n try { window.localStorage.setItem(storageKey, String(Math.round(width))); } catch { /* ignore quota / privacy mode */ }\n }, 200);\n return () => window.clearTimeout(id);\n }, [width, storageKey]);\n\n // Drop listeners if we unmount mid-drag.\n useEffect(() => () => {\n window.removeEventListener('pointermove', onMove);\n window.removeEventListener('pointerup', onUp);\n }, [onMove, onUp]);\n\n const edge = sideRight ? 'left-0' : 'right-0';\n // With a top/bottom slot the pane becomes a 3-region flex column: a padded\n // pinned header, the scrolling middle (`sidebar`, grows to fill), and a\n // padded, top-bordered pinned footer. Without slots it renders exactly as\n // before (the whole pane scrolls) so existing consumers are unaffected.\n const hasSlots = sidebarTop != null || sidebarBottom != null;\n const sidebarPane = (\n <div className=\"relative flex h-full shrink-0 flex-col\" style={{ width }}>\n <div className={`flex h-full flex-col ${hasSlots ? 'overflow-hidden' : 'overflow-y-auto'} ${sidebarClassName}`}>\n {sidebarTop != null && (\n <div className=\"shrink-0 px-2 pt-3 pb-2\">{sidebarTop}</div>\n )}\n {hasSlots ? (\n <div className=\"min-h-0 flex-1 overflow-y-auto\">{sidebar}</div>\n ) : (\n sidebar\n )}\n {sidebarBottom != null && (\n <div className=\"mt-auto shrink-0 border-t border-gray-200 px-2 py-3\">{sidebarBottom}</div>\n )}\n </div>\n {/* Resize handle — pinned to the sidebar's inner edge, fixed while it scrolls. */}\n <div\n onPointerDown={startDrag}\n onDoubleClick={() => setWidth(clamp(defaultWidth, minWidth, maxWidth))}\n title=\"Drag to resize · double-click to reset\"\n className={`group/resize absolute inset-y-0 ${edge} z-10 w-2 cursor-col-resize`}\n >\n <div className={`absolute inset-y-0 ${edge} w-px bg-transparent transition-colors group-hover/resize:bg-[var(--accent-500,#3b82f6)]`} />\n </div>\n </div>\n );\n const contentPane = (\n <div className={`flex min-w-0 flex-1 flex-col ${contentClassName}`}>\n {children}\n </div>\n );\n\n return (\n <div className={`flex h-full w-full overflow-hidden ${className}`}>\n {sideRight ? (\n <>{contentPane}{sidebarPane}</>\n ) : (\n <>{sidebarPane}{contentPane}</>\n )}\n </div>\n );\n}\n"]}
@@ -0,0 +1,46 @@
1
+ import { setPdfPreview } from './chunk-3L463T5H.js';
2
+ import { setSpreadsheetPreview } from './chunk-CT3HVZRZ.js';
3
+ import { toast_default } from './chunk-2VM2FRZJ.js';
4
+
5
+ // src/utils/openPreviewFile.ts
6
+ var PREVIEW_OPENED_EVENT = "react-os-shell:preview-opened";
7
+ function getServer() {
8
+ const override = typeof window !== "undefined" && window.__REACT_OS_SHELL_FILE_SERVER__;
9
+ return (override || "http://localhost:4000").replace(/\/$/, "");
10
+ }
11
+ async function openPreviewFile(opts) {
12
+ const { filePath, filename, kind, onStaged } = opts;
13
+ try {
14
+ const res = await fetch(
15
+ `${getServer()}/api/file?path=${encodeURIComponent(filePath)}`,
16
+ { credentials: "include" }
17
+ );
18
+ if (!res.ok) {
19
+ toast_default.error(`Download failed (${res.status})`);
20
+ return false;
21
+ }
22
+ const blob = await res.blob();
23
+ if (kind === "csv") {
24
+ const text = await blob.text();
25
+ setSpreadsheetPreview({ csv: text, filename });
26
+ onStaged?.("/spreadsheet");
27
+ } else {
28
+ const url = URL.createObjectURL(blob);
29
+ setPdfPreview({ url, filename, kind });
30
+ onStaged?.("/preview");
31
+ }
32
+ if (typeof window !== "undefined") {
33
+ window.dispatchEvent(new CustomEvent(PREVIEW_OPENED_EVENT, {
34
+ detail: { filePath, filename, kind }
35
+ }));
36
+ }
37
+ return true;
38
+ } catch (e) {
39
+ toast_default.error(e?.message || "Open failed");
40
+ return false;
41
+ }
42
+ }
43
+
44
+ export { PREVIEW_OPENED_EVENT, openPreviewFile };
45
+ //# sourceMappingURL=chunk-YNGPTOL6.js.map
46
+ //# sourceMappingURL=chunk-YNGPTOL6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/openPreviewFile.ts"],"names":[],"mappings":";;;;;AA6BO,IAAM,oBAAA,GAAuB;AAQpC,SAAS,SAAA,GAAY;AACnB,EAAA,MAAM,QAAA,GAAY,OAAO,MAAA,KAAW,WAAA,IAAgB,MAAA,CAAe,8BAAA;AACnE,EAAA,OAAA,CAAQ,QAAA,IAAY,uBAAA,EAAyB,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAChE;AAEA,eAAsB,gBAAgB,IAAA,EAA6C;AACjF,EAAA,MAAM,EAAE,QAAA,EAAU,QAAA,EAAU,IAAA,EAAM,UAAS,GAAI,IAAA;AAC/C,EAAA,IAAI;AACF,IAAA,MAAM,MAAM,MAAM,KAAA;AAAA,MAChB,GAAG,SAAA,EAAW,CAAA,eAAA,EAAkB,kBAAA,CAAmB,QAAQ,CAAC,CAAA,CAAA;AAAA,MAC5D,EAAE,aAAa,SAAA;AAAU,KAC3B;AACA,IAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,MAAA,aAAA,CAAM,KAAA,CAAM,CAAA,iBAAA,EAAoB,GAAA,CAAI,MAAM,CAAA,CAAA,CAAG,CAAA;AAC7C,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,IAAA,IAAI,SAAS,KAAA,EAAO;AAClB,MAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,IAAA,EAAK;AAC7B,MAAA,qBAAA,CAAsB,EAAE,GAAA,EAAK,IAAA,EAAM,QAAA,EAAU,CAAA;AAC7C,MAAA,QAAA,GAAW,cAAc,CAAA;AAAA,IAC3B,CAAA,MAAO;AACL,MAAA,MAAM,GAAA,GAAM,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AACpC,MAAA,aAAA,CAAc,EAAE,GAAA,EAAK,QAAA,EAAU,IAAA,EAAM,CAAA;AACrC,MAAA,QAAA,GAAW,UAAU,CAAA;AAAA,IACvB;AACA,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,MAAA,MAAA,CAAO,aAAA,CAAc,IAAI,WAAA,CAAiC,oBAAA,EAAsB;AAAA,QAC9E,MAAA,EAAQ,EAAE,QAAA,EAAU,QAAA,EAAU,IAAA;AAAK,OACpC,CAAC,CAAA;AAAA,IACJ;AACA,IAAA,OAAO,IAAA;AAAA,EACT,SAAS,CAAA,EAAQ;AACf,IAAA,aAAA,CAAM,KAAA,CAAM,CAAA,EAAG,OAAA,IAAW,aAAa,CAAA;AACvC,IAAA,OAAO,KAAA;AAAA,EACT;AACF","file":"chunk-YNGPTOL6.js","sourcesContent":["/**\n * Fetch a file from the user's file-server and route it into the right\n * preview window. Shared by the Files app and the desktop Documents\n * folder's shortcut click-through.\n *\n * On success, dispatches `react-os-shell:preview-opened` so Desktop can\n * record the file as a shortcut without this util needing access to the\n * prefs adapter.\n */\nimport toast from '../shell/toast';\nimport { setPdfPreview } from '../apps/_previewStage';\nimport { setSpreadsheetPreview } from '../apps/_spreadsheetStage';\n\nexport type PreviewFileKind = 'pdf' | 'dxf' | '3d' | 'image' | 'csv';\n\nexport interface OpenPreviewFileOpts {\n /** Server-relative path, e.g. \"/reports/Q1.pdf\". */\n filePath: string;\n /** Display name (also used as the download filename). */\n filename: string;\n /** Which viewer to route into. CSV opens in Spreadsheet; the rest open in Preview. */\n kind: PreviewFileKind;\n /** Optional callback invoked after staging the preview, with the route to\n * open (e.g. '/preview' or '/spreadsheet'). The caller is responsible\n * for actually opening the page since the window manager hook is\n * React-scoped. */\n onStaged?: (route: '/preview' | '/spreadsheet') => void;\n}\n\nexport const PREVIEW_OPENED_EVENT = 'react-os-shell:preview-opened';\n\nexport interface PreviewOpenedDetail {\n filePath: string;\n filename: string;\n kind: PreviewFileKind;\n}\n\nfunction getServer() {\n const override = (typeof window !== 'undefined' && (window as any).__REACT_OS_SHELL_FILE_SERVER__) as string | undefined;\n return (override || 'http://localhost:4000').replace(/\\/$/, '');\n}\n\nexport async function openPreviewFile(opts: OpenPreviewFileOpts): Promise<boolean> {\n const { filePath, filename, kind, onStaged } = opts;\n try {\n const res = await fetch(\n `${getServer()}/api/file?path=${encodeURIComponent(filePath)}`,\n { credentials: 'include' },\n );\n if (!res.ok) {\n toast.error(`Download failed (${res.status})`);\n return false;\n }\n const blob = await res.blob();\n if (kind === 'csv') {\n const text = await blob.text();\n setSpreadsheetPreview({ csv: text, filename });\n onStaged?.('/spreadsheet');\n } else {\n const url = URL.createObjectURL(blob);\n setPdfPreview({ url, filename, kind });\n onStaged?.('/preview');\n }\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent<PreviewOpenedDetail>(PREVIEW_OPENED_EVENT, {\n detail: { filePath, filename, kind },\n }));\n }\n return true;\n } catch (e: any) {\n toast.error(e?.message || 'Open failed');\n return false;\n }\n}\n"]}
@@ -0,0 +1,400 @@
1
+ import { forwardRef, useId, createContext, useSyncExternalStore, useState, useCallback, useContext, useMemo } from 'react';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+
4
+ // src/forms/Button.tsx
5
+ var BUTTON_BASE = "inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-60";
6
+ var BUTTON_VARIANTS = {
7
+ primary: "bg-blue-600 text-white shadow-sm hover:bg-blue-700 focus:ring-blue-400/40",
8
+ secondary: "border border-gray-300 bg-white text-gray-700 shadow-sm hover:bg-gray-50 focus:ring-blue-400/30",
9
+ ghost: "text-gray-700 hover:bg-gray-100 focus:ring-blue-400/30",
10
+ "ghost-danger": "text-red-600 hover:bg-red-50 focus:ring-red-400/30",
11
+ danger: "bg-red-600 text-white shadow-sm hover:bg-red-700 focus:ring-red-400/40",
12
+ /**
13
+ * A button that reads as a link: no box, no padding, underlined on hover.
14
+ * For an action that belongs in running text or beside a field — "Forgot
15
+ * your password?", "Use a different address" — where a boxed button would
16
+ * claim more of the page than the action deserves.
17
+ *
18
+ * It is still a <button>. Anything that NAVIGATES should be an anchor, so it
19
+ * can be middle-clicked and read off the status bar; this is for actions
20
+ * that merely look quiet.
21
+ */
22
+ link: "text-blue-600 underline-offset-4 hover:underline focus:ring-blue-400/30"
23
+ };
24
+ var SIZES = {
25
+ sm: "gap-1 px-2.5 py-1 text-xs",
26
+ md: "gap-1.5 px-3 py-1.5 text-sm",
27
+ // `py-2.5`, not `py-2`: this rung has to come out 40px to match IconButton's
28
+ // `h-10`, which is the promise IconButton's docblock makes for every rung.
29
+ // `py-2` gives 36px (20px line box + 8px twice) and puts a 4px step between a
30
+ // Button and an IconButton sitting in the same row.
31
+ lg: "gap-2 px-4 py-2.5 text-sm",
32
+ // 44px is the WCAG 2.5.5 floor, and a floor is not a comfortable size. Use it
33
+ // ONLY for chrome that sits outside the task — switching screens, ending a
34
+ // shift — and never on the path someone is actually working through. It
35
+ // exists so a dense toolbar has something legitimate to reach for instead of
36
+ // borrowing the desktop `sm`, which is half this height.
37
+ "touch-sm": "gap-1.5 h-11 px-4 text-base",
38
+ "touch": "gap-2 h-14 px-5 text-base",
39
+ // 56px — the comfortable default
40
+ "touch-lg": "gap-2 h-16 px-6 text-lg",
41
+ // 64px — primary action on a page
42
+ "touch-xl": "gap-3 h-20 px-8 text-2xl"
43
+ // 80px — keypad keys, tender
44
+ };
45
+ var LINK_BASE = "inline-flex items-center font-medium transition-colors focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-60";
46
+ var LINK_SIZES = {
47
+ sm: "gap-1 text-xs",
48
+ md: "gap-1.5 text-sm",
49
+ lg: "gap-2 text-sm",
50
+ "touch-sm": "gap-1.5 text-base",
51
+ "touch": "gap-2 text-base",
52
+ "touch-lg": "gap-2 text-lg",
53
+ "touch-xl": "gap-3 text-2xl"
54
+ };
55
+ function Spinner() {
56
+ return /* @__PURE__ */ jsxs("svg", { className: "h-3.5 w-3.5 animate-spin", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
57
+ /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
58
+ /* @__PURE__ */ jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })
59
+ ] });
60
+ }
61
+ var Button = forwardRef(function Button2({
62
+ variant = "primary",
63
+ size = "md",
64
+ loading = false,
65
+ leftIcon,
66
+ rightIcon,
67
+ block = false,
68
+ disabled,
69
+ type = "button",
70
+ children,
71
+ className = "",
72
+ disabledReason,
73
+ ...rest
74
+ }, ref) {
75
+ const generatedId = useId();
76
+ const isDisabled = disabled || loading;
77
+ const showReason = isDisabled && disabledReason != null && disabledReason !== "";
78
+ const reasonId = showReason ? generatedId : void 0;
79
+ const button = /* @__PURE__ */ jsxs(
80
+ "button",
81
+ {
82
+ ref,
83
+ type,
84
+ disabled: isDisabled,
85
+ "aria-busy": loading || void 0,
86
+ "aria-describedby": reasonId,
87
+ className: [
88
+ // `link` takes the base WITHOUT the box: no rounding, no padding from
89
+ // the size table. It cannot cancel those by overriding — two padding
90
+ // utilities in one class attribute resolve by compiled-stylesheet
91
+ // order, not by the order they were written — so it never receives
92
+ // them, and keeps only the size's gap and text size.
93
+ variant === "link" ? LINK_BASE : BUTTON_BASE,
94
+ BUTTON_VARIANTS[variant],
95
+ variant === "link" ? LINK_SIZES[size] : SIZES[size],
96
+ block ? "w-full" : "",
97
+ className
98
+ ].filter(Boolean).join(" "),
99
+ ...rest,
100
+ children: [
101
+ loading ? /* @__PURE__ */ jsx(Spinner, {}) : leftIcon,
102
+ children,
103
+ !loading && rightIcon
104
+ ]
105
+ }
106
+ );
107
+ if (!showReason) return button;
108
+ return /* @__PURE__ */ jsxs("span", { className: block ? "flex w-full flex-col gap-1" : "inline-flex flex-col items-start gap-1", children: [
109
+ button,
110
+ /* @__PURE__ */ jsx("span", { id: reasonId, className: "text-xs text-red-600", children: disabledReason })
111
+ ] });
112
+ });
113
+ var Button_default = Button;
114
+ var QUERY = "(max-width: 767px), (pointer: coarse)";
115
+ function getSnapshot() {
116
+ if (typeof window === "undefined" || !window.matchMedia) return false;
117
+ return window.matchMedia(QUERY).matches;
118
+ }
119
+ function getServerSnapshot() {
120
+ return false;
121
+ }
122
+ function subscribe(cb) {
123
+ if (typeof window === "undefined" || !window.matchMedia) return () => {
124
+ };
125
+ const mql = window.matchMedia(QUERY);
126
+ if (mql.addEventListener) {
127
+ mql.addEventListener("change", cb);
128
+ return () => mql.removeEventListener("change", cb);
129
+ }
130
+ mql.addListener(cb);
131
+ return () => mql.removeListener(cb);
132
+ }
133
+ function useIsMobile() {
134
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
135
+ }
136
+ var STATUS_TONE = {
137
+ success: "success",
138
+ error: "error",
139
+ warning: "warning",
140
+ info: "info",
141
+ // A missing page is not an error the user made, and colouring it red says it
142
+ // was. Refused and broken are genuinely different from not-found.
143
+ 404: "info",
144
+ 403: "warning",
145
+ 500: "error"
146
+ };
147
+ var TONE_CLASS = {
148
+ success: "text-green-600 bg-green-100",
149
+ error: "text-red-600 bg-red-100",
150
+ warning: "text-amber-600 bg-amber-100",
151
+ info: "text-blue-600 bg-blue-100"
152
+ };
153
+ var DEFAULT_TITLE = {
154
+ 404: "Page not found",
155
+ 403: "You don't have access to this",
156
+ 500: "Something went wrong"
157
+ };
158
+ function StatusIcon({ tone }) {
159
+ const common = { className: "h-6 w-6", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, "aria-hidden": true };
160
+ switch (tone) {
161
+ case "success":
162
+ return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4.5 12.75l6 6 9-13.5" }) });
163
+ case "error":
164
+ return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) });
165
+ case "warning":
166
+ return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" }) });
167
+ default:
168
+ return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" }) });
169
+ }
170
+ }
171
+ function Result({
172
+ status = "info",
173
+ title,
174
+ subTitle,
175
+ extra,
176
+ icon,
177
+ children,
178
+ className = ""
179
+ }) {
180
+ const tone = STATUS_TONE[status];
181
+ const announces = tone === "error";
182
+ return /* @__PURE__ */ jsxs(
183
+ "div",
184
+ {
185
+ role: announces ? "alert" : void 0,
186
+ className: `flex flex-col items-center justify-center px-6 py-12 text-center ${className}`.trim(),
187
+ children: [
188
+ /* @__PURE__ */ jsx("div", { className: `flex h-14 w-14 items-center justify-center rounded-full ${TONE_CLASS[tone]}`, children: icon ?? /* @__PURE__ */ jsx(StatusIcon, { tone }) }),
189
+ /* @__PURE__ */ jsx("h2", { className: "mt-4 text-lg font-semibold text-gray-900", children: title ?? DEFAULT_TITLE[status] ?? "" }),
190
+ subTitle && /* @__PURE__ */ jsx("p", { className: "mt-1 max-w-md text-sm text-gray-500", children: subTitle }),
191
+ children && /* @__PURE__ */ jsx("div", { className: "mt-4 w-full max-w-md text-left", children }),
192
+ extra && /* @__PURE__ */ jsx("div", { className: "mt-6 flex flex-wrap items-center justify-center gap-3", children: extra })
193
+ ]
194
+ }
195
+ );
196
+ }
197
+ function LoadingSpinner({ size = "md", padding = "py-12", label, className = "" }) {
198
+ const dim = size === "sm" ? "h-5 w-5" : size === "lg" ? "h-12 w-12" : "h-8 w-8";
199
+ const ring = /* @__PURE__ */ jsx("div", { className: `${dim} animate-spin rounded-full border-2 border-gray-200 border-t-blue-600` });
200
+ if (label == null) {
201
+ return /* @__PURE__ */ jsx("div", { className: `flex items-center justify-center ${padding} ${className}`, children: ring });
202
+ }
203
+ return /* @__PURE__ */ jsxs("div", { role: "status", className: `flex flex-col items-center justify-center gap-3 ${padding} ${className}`, children: [
204
+ ring,
205
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: label })
206
+ ] });
207
+ }
208
+ var ShellPrefsContext = createContext(null);
209
+ function ShellPrefsProvider({
210
+ value,
211
+ children
212
+ }) {
213
+ return /* @__PURE__ */ jsx(ShellPrefsContext.Provider, { value, children });
214
+ }
215
+ function useLocalStoragePrefs(storageKey = "react-os-shell:prefs", defaults) {
216
+ const [prefs, setPrefs] = useState(() => {
217
+ try {
218
+ const stored = JSON.parse(localStorage.getItem(storageKey) || "{}");
219
+ return { ...defaults ?? {}, ...stored };
220
+ } catch {
221
+ return { ...defaults ?? {} };
222
+ }
223
+ });
224
+ const save = useCallback((patch) => {
225
+ setPrefs((prev) => {
226
+ const next = { ...prev, ...patch };
227
+ try {
228
+ localStorage.setItem(storageKey, JSON.stringify(next));
229
+ } catch {
230
+ }
231
+ return next;
232
+ });
233
+ }, [storageKey]);
234
+ return { prefs, save };
235
+ }
236
+ var NO_PROVIDER = { prefs: {}, save: () => {
237
+ } };
238
+ function useShellPrefsAdapter() {
239
+ return useContext(ShellPrefsContext) ?? NO_PROVIDER;
240
+ }
241
+ function useShellPrefs() {
242
+ const { prefs, save: rawSave } = useShellPrefsAdapter();
243
+ const save = useCallback((patch) => {
244
+ const result = rawSave(patch);
245
+ return result && typeof result.then === "function" ? result.catch(() => {
246
+ }) : result;
247
+ }, [rawSave]);
248
+ return useMemo(() => ({ prefs, save }), [prefs, save]);
249
+ }
250
+
251
+ // src/shell/staleChunk.ts
252
+ var STALE_CHUNK_RELOAD_KEY = "react-os-shell:stale-chunk-reload-at";
253
+ var STALE_CHUNK_RELOAD_COOLDOWN_MS = 3e4;
254
+ var VITE_PRELOAD_ERROR_EVENT = "vite:preloadError";
255
+ var NEXT_CHUNK_PATH_PREFIX = "/_next/static/";
256
+ var STALE_CHUNK_MESSAGE = /Failed to fetch dynamically imported module|error loading dynamically imported module|Importing a module script failed|Unable to preload CSS/i;
257
+ var CHUNK_LOAD_ERROR_NAME = "ChunkLoadError";
258
+ function isStaleChunkError(error) {
259
+ if (error instanceof Error && error.name === CHUNK_LOAD_ERROR_NAME) return true;
260
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
261
+ return STALE_CHUNK_MESSAGE.test(message);
262
+ }
263
+ function linkLoadsChunk(link) {
264
+ const rels = link.rel.toLowerCase().split(/\s+/);
265
+ if (rels.includes("stylesheet") || rels.includes("modulepreload")) return true;
266
+ const as = (link.getAttribute("as") ?? "").toLowerCase();
267
+ return rels.includes("preload") && (as === "script" || as === "style");
268
+ }
269
+ function failedChunkUrl(event, prefix) {
270
+ const target = event.target;
271
+ let src = "";
272
+ if (target instanceof window.HTMLScriptElement) src = target.src;
273
+ else if (target instanceof window.HTMLLinkElement && linkLoadsChunk(target)) src = target.href;
274
+ else if (event instanceof window.ErrorEvent && event.error?.name === "SyntaxError") {
275
+ src = event.filename;
276
+ }
277
+ if (!src) return null;
278
+ try {
279
+ const base = new URL(prefix, window.location.origin).href;
280
+ const url = new URL(src, window.location.href).href;
281
+ return url.startsWith(base) ? url : null;
282
+ } catch {
283
+ return null;
284
+ }
285
+ }
286
+ async function isGoneFromServer(url) {
287
+ try {
288
+ const response = await fetch(url, { method: "HEAD", cache: "no-store" });
289
+ if (response.status === 404 || response.status === 410) return true;
290
+ return response.ok && /\btext\/html\b/i.test(response.headers.get("content-type") ?? "");
291
+ } catch {
292
+ return false;
293
+ }
294
+ }
295
+ function defaultStorage() {
296
+ try {
297
+ const s = window.sessionStorage;
298
+ s.getItem(STALE_CHUNK_RELOAD_KEY);
299
+ return s;
300
+ } catch {
301
+ return null;
302
+ }
303
+ }
304
+ function readLastReload(storage) {
305
+ if (!storage) return null;
306
+ try {
307
+ const raw = storage.getItem(STALE_CHUNK_RELOAD_KEY);
308
+ const at = raw === null ? NaN : Number(raw);
309
+ return Number.isFinite(at) ? at : null;
310
+ } catch {
311
+ return null;
312
+ }
313
+ }
314
+ function writeLastReload(storage, at) {
315
+ if (!storage) return;
316
+ try {
317
+ storage.setItem(STALE_CHUNK_RELOAD_KEY, String(at));
318
+ } catch {
319
+ }
320
+ }
321
+ function installStaleChunkReload(options = {}) {
322
+ if (typeof window === "undefined") return () => {
323
+ };
324
+ const cooldownMs = options.cooldownMs ?? STALE_CHUNK_RELOAD_COOLDOWN_MS;
325
+ const reload = options.reload ?? (() => window.location.reload());
326
+ const now = options.now ?? Date.now;
327
+ const storage = options.storage === void 0 ? defaultStorage() : options.storage;
328
+ const chunkPathPrefix = options.chunkPathPrefix === void 0 ? NEXT_CHUNK_PATH_PREFIX : options.chunkPathPrefix;
329
+ const claimReload = (chunk) => {
330
+ const at = now();
331
+ const last = readLastReload(storage);
332
+ if (last !== null && at - last < cooldownMs) {
333
+ console.error(
334
+ "[react-os-shell] a lazy chunk is still missing after a reload; letting the failure through:",
335
+ chunk
336
+ );
337
+ return false;
338
+ }
339
+ writeLastReload(storage, at);
340
+ console.warn(
341
+ "[react-os-shell] a lazy chunk is no longer on the server (new deploy?); reloading onto the current build:",
342
+ chunk
343
+ );
344
+ return true;
345
+ };
346
+ const onPreloadError = (event) => {
347
+ const payload = event.payload;
348
+ if (!isStaleChunkError(payload) || !claimReload(payload)) return;
349
+ event.preventDefault();
350
+ reload();
351
+ };
352
+ let active = true;
353
+ let reloading = false;
354
+ const asking = /* @__PURE__ */ new Set();
355
+ const onChunkError = chunkPathPrefix === null ? null : (event) => {
356
+ const chunk = failedChunkUrl(event, chunkPathPrefix);
357
+ if (!chunk || reloading || asking.has(chunk)) return;
358
+ asking.add(chunk);
359
+ void isGoneFromServer(chunk).then((gone) => {
360
+ asking.delete(chunk);
361
+ if (!gone || !active || reloading || !claimReload(chunk)) return;
362
+ reloading = true;
363
+ reload();
364
+ });
365
+ };
366
+ window.addEventListener(VITE_PRELOAD_ERROR_EVENT, onPreloadError);
367
+ if (onChunkError) window.addEventListener("error", onChunkError, true);
368
+ return () => {
369
+ active = false;
370
+ window.removeEventListener(VITE_PRELOAD_ERROR_EVENT, onPreloadError);
371
+ if (onChunkError) window.removeEventListener("error", onChunkError, true);
372
+ };
373
+ }
374
+ var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
375
+ var MOD = isMac ? "\u2318" : "Ctrl";
376
+ var ALT = isMac ? "\u2325" : "Alt";
377
+ var SHIFT = "\u21E7";
378
+ var ENTER = "\u23CE";
379
+ var CMD_ENTER = isMac ? "\u2318\u23CE" : "Ctrl\u23CE";
380
+ var CMD_S = isMac ? "\u2318S" : "Ctrl+S";
381
+ var CMD_K = isMac ? "\u2318K" : "Ctrl+K";
382
+ var CMD_DOT = isMac ? "\u2318." : "Ctrl+.";
383
+ var CMD_A = isMac ? "\u2318A" : "Ctrl+A";
384
+ var CMD_Z = isMac ? "\u2318Z" : "Ctrl+Z";
385
+ var CMD_SHIFT_Z = isMac ? "\u2318\u21E7Z" : "Ctrl+Shift+Z";
386
+ var ALT_SHIFT_D = isMac ? "\u2325\u21E7D" : "Alt+Shift+D";
387
+ var ALT_SHIFT_E = isMac ? "\u2325\u21E7E" : "Alt+Shift+E";
388
+ var ALT_SHIFT_N = isMac ? "\u2325\u21E7N" : "Alt+Shift+N";
389
+ var KBD_BASE = "inline-flex items-center justify-center rounded border border-gray-200 bg-gray-50 font-medium text-gray-500";
390
+ var KBD_SIZES = {
391
+ sm: "px-1 py-0.5 text-[10px]",
392
+ md: "px-1.5 py-0.5 text-[11px]"
393
+ };
394
+ function Kbd({ keys, size = "md", className = "" }) {
395
+ return /* @__PURE__ */ jsx("kbd", { className: [KBD_BASE, KBD_SIZES[size], className].filter(Boolean).join(" "), children: keys });
396
+ }
397
+
398
+ export { ALT, ALT_SHIFT_D, ALT_SHIFT_E, ALT_SHIFT_N, BUTTON_BASE, BUTTON_VARIANTS, Button_default, CMD_A, CMD_DOT, CMD_ENTER, CMD_K, CMD_S, CMD_SHIFT_Z, CMD_Z, ENTER, Kbd, LoadingSpinner, MOD, NEXT_CHUNK_PATH_PREFIX, Result, SHIFT, STALE_CHUNK_RELOAD_COOLDOWN_MS, STALE_CHUNK_RELOAD_KEY, ShellPrefsProvider, VITE_PRELOAD_ERROR_EVENT, installStaleChunkReload, isMac, isStaleChunkError, useIsMobile, useLocalStoragePrefs, useShellPrefs, useShellPrefsAdapter };
399
+ //# sourceMappingURL=chunk-YNHLQ24V.js.map
400
+ //# sourceMappingURL=chunk-YNHLQ24V.js.map