efficient-shell 4.132.0 → 4.134.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -12
- package/dist/{Browser-XPVJ2K7B.js → Browser-O3QZGUF2.js} +11 -11
- package/dist/{Browser-XPVJ2K7B.js.map → Browser-O3QZGUF2.js.map} +1 -1
- package/dist/{Calculator-ZXJPXG7F.js → Calculator-L6N5X3KU.js} +9 -9
- package/dist/{Calculator-ZXJPXG7F.js.map → Calculator-L6N5X3KU.js.map} +1 -1
- package/dist/ConfirmDialog-LYSSZSUD.js +4 -0
- package/dist/{ConfirmDialog-MTC7QMUJ.js.map → ConfirmDialog-LYSSZSUD.js.map} +1 -1
- package/dist/{CurrencyConverter-WSJ2BBSX.js → CurrencyConverter-64MKXB3D.js} +9 -9
- package/dist/{CurrencyConverter-WSJ2BBSX.js.map → CurrencyConverter-64MKXB3D.js.map} +1 -1
- package/dist/{Documents-ONGSXUJI.js → Documents-5WXXYCLI.js} +11 -11
- package/dist/{Documents-ONGSXUJI.js.map → Documents-5WXXYCLI.js.map} +1 -1
- package/dist/{Files-GQZMFWXQ.js → Files-AUURUUDN.js} +17 -17
- package/dist/{Files-GQZMFWXQ.js.map → Files-AUURUUDN.js.map} +1 -1
- package/dist/{Notepad-HM73XAM4.js → Notepad-GPXJVPB7.js} +12 -12
- package/dist/{Notepad-HM73XAM4.js.map → Notepad-GPXJVPB7.js.map} +1 -1
- package/dist/PdfViewer-HKO6P4SR.js +5 -0
- package/dist/{PdfViewer-M2YGZIYX.js.map → PdfViewer-HKO6P4SR.js.map} +1 -1
- package/dist/{PomodoroTimer-DG3LI5CX.js → PomodoroTimer-4TQBWLP2.js} +11 -11
- package/dist/{PomodoroTimer-DG3LI5CX.js.map → PomodoroTimer-4TQBWLP2.js.map} +1 -1
- package/dist/{Preview-TUSV4UMO.js → Preview-FOZYPKM2.js} +14 -14
- package/dist/{Preview-TUSV4UMO.js.map → Preview-FOZYPKM2.js.map} +1 -1
- package/dist/{Spreadsheet-3WTXCHYQ.js → Spreadsheet-3C66BBBF.js} +12 -12
- package/dist/{Spreadsheet-3WTXCHYQ.js.map → Spreadsheet-3C66BBBF.js.map} +1 -1
- package/dist/{Stock-YMPOMUAW.js → Stock-IF4IHYLU.js} +9 -9
- package/dist/{Stock-YMPOMUAW.js.map → Stock-IF4IHYLU.js.map} +1 -1
- package/dist/{Weather-NZO3CWLH.js → Weather-725PFR7T.js} +9 -9
- package/dist/{Weather-NZO3CWLH.js.map → Weather-725PFR7T.js.map} +1 -1
- package/dist/{WorldClock-HUC6D3AD.js → WorldClock-UU4NZIQ7.js} +9 -9
- package/dist/{WorldClock-HUC6D3AD.js.map → WorldClock-UU4NZIQ7.js.map} +1 -1
- package/dist/apps/index.d.ts +2 -2
- package/dist/apps/index.js +28 -28
- package/dist/apps/index.js.map +1 -1
- package/dist/brand.css +1 -1
- package/dist/{chunk-TQURTLK2.js → chunk-2TPTHAKI.js} +5 -5
- package/dist/{chunk-TQURTLK2.js.map → chunk-2TPTHAKI.js.map} +1 -1
- package/dist/{chunk-EENVXIVU.js → chunk-4P44JXDS.js} +3 -3
- package/dist/{chunk-EENVXIVU.js.map → chunk-4P44JXDS.js.map} +1 -1
- package/dist/{chunk-CT3HVZRZ.js → chunk-5O42JXQE.js} +2 -2
- package/dist/chunk-5O42JXQE.js.map +1 -0
- package/dist/{chunk-7IN7YZAL.js → chunk-72CQWN7O.js} +3 -3
- package/dist/chunk-72CQWN7O.js.map +1 -0
- package/dist/{chunk-I464GYJJ.js → chunk-BE7FZOFU.js} +2 -2
- package/dist/chunk-BE7FZOFU.js.map +1 -0
- package/dist/{chunk-3L463T5H.js → chunk-DVZWYWSD.js} +2 -2
- package/dist/chunk-DVZWYWSD.js.map +1 -0
- package/dist/{chunk-FONEQRBH.js → chunk-ET6ID4LP.js} +3 -3
- package/dist/{chunk-FONEQRBH.js.map → chunk-ET6ID4LP.js.map} +1 -1
- package/dist/{chunk-CGXMWHQW.js → chunk-GIGV2ZRW.js} +3 -3
- package/dist/{chunk-CGXMWHQW.js.map → chunk-GIGV2ZRW.js.map} +1 -1
- package/dist/{chunk-CJYO4RVV.js → chunk-IULP5BCD.js} +5 -5
- package/dist/chunk-IULP5BCD.js.map +1 -0
- package/dist/{chunk-BJ34QOR3.js → chunk-OLJDJYSU.js} +287 -146
- package/dist/chunk-OLJDJYSU.js.map +1 -0
- package/dist/{chunk-7MA5XBWD.js → chunk-PIGDNN5F.js} +5 -5
- package/dist/{chunk-7MA5XBWD.js.map → chunk-PIGDNN5F.js.map} +1 -1
- package/dist/chunk-Q2MJVSJT.js +7 -0
- package/dist/{chunk-JMKYYPOF.js.map → chunk-Q2MJVSJT.js.map} +1 -1
- package/dist/{chunk-ZKHD4J73.js → chunk-Q3LT745N.js} +10 -10
- package/dist/chunk-Q3LT745N.js.map +1 -0
- package/dist/{chunk-CLGNTGC5.js → chunk-QAGQRNKI.js} +3 -3
- package/dist/{chunk-CLGNTGC5.js.map → chunk-QAGQRNKI.js.map} +1 -1
- package/dist/{chunk-E3TWIN7Z.js → chunk-QRL64J5F.js} +3 -3
- package/dist/{chunk-E3TWIN7Z.js.map → chunk-QRL64J5F.js.map} +1 -1
- package/dist/{chunk-TFXJRHJV.js → chunk-QSKCQUZE.js} +3 -3
- package/dist/{chunk-TFXJRHJV.js.map → chunk-QSKCQUZE.js.map} +1 -1
- package/dist/{chunk-MQPX6ERJ.js → chunk-R4OQIQYX.js} +4 -4
- package/dist/{chunk-MQPX6ERJ.js.map → chunk-R4OQIQYX.js.map} +1 -1
- package/dist/{chunk-LQTVTOSY.js → chunk-RT6DSB5Y.js} +45 -19
- package/dist/chunk-RT6DSB5Y.js.map +1 -0
- package/dist/{chunk-VWZIGZFF.js → chunk-SHQM5CK3.js} +4 -4
- package/dist/{chunk-VWZIGZFF.js.map → chunk-SHQM5CK3.js.map} +1 -1
- package/dist/{chunk-KWEX4ZAF.js → chunk-T4VQHMG4.js} +3 -3
- package/dist/{chunk-KWEX4ZAF.js.map → chunk-T4VQHMG4.js.map} +1 -1
- package/dist/{chunk-BFBWLCO6.js → chunk-TW7LPMVR.js} +2 -2
- package/dist/chunk-TW7LPMVR.js.map +1 -0
- package/dist/{chunk-XH3ELPQZ.js → chunk-W622KQTD.js} +3 -3
- package/dist/chunk-W622KQTD.js.map +1 -0
- package/dist/{chunk-SEWF4O3H.js → chunk-X4Q6G253.js} +3 -3
- package/dist/chunk-X4Q6G253.js.map +1 -0
- package/dist/file-intake/index.js +2 -2
- package/dist/{index-COwksV6G.d.ts → index-DZ7E7bCl.d.ts} +196 -4
- package/dist/index.d.ts +3 -3
- package/dist/index.js +35 -35
- package/dist/index.js.map +1 -1
- package/dist/map/index.js +1 -1
- package/dist/markdown/index.d.ts +4 -4
- package/dist/markdown/index.js.map +1 -1
- package/dist/markup/index.d.ts +2 -2
- package/dist/markup/index.js.map +1 -1
- package/dist/page-tabs/index.js +3 -3
- package/dist/shell.css +1 -1
- package/dist/styles.css +4 -4
- package/dist/themes.css +3 -3
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +12 -12
- package/dist/ui.css +233 -8
- package/package.json +1 -1
- package/dist/ConfirmDialog-MTC7QMUJ.js +0 -4
- package/dist/PdfViewer-M2YGZIYX.js +0 -5
- package/dist/chunk-3L463T5H.js.map +0 -1
- package/dist/chunk-7IN7YZAL.js.map +0 -1
- package/dist/chunk-BFBWLCO6.js.map +0 -1
- package/dist/chunk-BJ34QOR3.js.map +0 -1
- package/dist/chunk-CJYO4RVV.js.map +0 -1
- package/dist/chunk-CT3HVZRZ.js.map +0 -1
- package/dist/chunk-I464GYJJ.js.map +0 -1
- package/dist/chunk-JMKYYPOF.js +0 -7
- package/dist/chunk-LQTVTOSY.js.map +0 -1
- package/dist/chunk-SEWF4O3H.js.map +0 -1
- package/dist/chunk-XH3ELPQZ.js.map +0 -1
- package/dist/chunk-ZKHD4J73.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/apps/_about.tsx"],"names":[],"mappings":"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","file":"chunk-CJYO4RVV.js","sourcesContent":["/**\n * Shared \"About\" dialog for the bundled apps.\n *\n * Each bundled app carries its own version (independent of the package\n * version) so app-level changes can be tracked without bumping every app at\n * once. `<AboutApp app=\"…\" />` registers an \"About <Name>\" item in the\n * window title menu and renders the dialog: app icon, name, app version and\n * the react-os-shell attribution + shell version.\n *\n * Bump an app's version in `BUILTIN_APP_INFO` whenever that app's behaviour\n * changes, alongside the package-level CHANGELOG entry.\n */\nimport { useCallback, useState, isValidElement, cloneElement, type ReactElement } from 'react';\nimport Modal, { useWindowMenuItem } from '../shell/Modal';\nimport { navIcons } from '../shell-config/nav';\nimport { VERSION } from '../version';\n\nexport interface BuiltinAppInfo {\n /** Display name — matches the window-registry label. */\n name: string;\n /** App version, independent of the package version. */\n version: string;\n /** One-line summary shown in the About dialog. */\n description: string;\n /** Registry route — used to look up the consumer-registered nav icon. */\n route: string;\n}\n\nexport const BUILTIN_APP_INFO = {\n spreadsheet: {\n name: 'Spreadsheets',\n version: '1.1.3',\n route: '/spreadsheet',\n description: 'Multi-sheet spreadsheet editor with CSV / TSV import and export.',\n },\n notepad: {\n name: 'Notepad',\n version: '1.0.0',\n route: '/notepad',\n description: 'Color-coded notes with checklists, desktop stickies and entity autolinking.',\n },\n documents: {\n name: 'Documents',\n version: '1.1.0',\n route: '/documents',\n description: 'Letter-page editor for Word documents and plain-text files — images, lists, alignment.',\n },\n preview: {\n name: 'Preview',\n version: '1.3.2',\n route: '/preview',\n description: 'Viewer for PDF documents, images, DXF drawings and 3D models — CAD-style measuring with object snap and an AutoCAD-like command bar.',\n },\n files: {\n name: 'Files',\n version: '1.1.0',\n route: '/files',\n description: 'Personal file manager with folders, uploads, quota, trash and desktop shortcut folders.',\n },\n browser: {\n name: 'Browser',\n version: '1.0.1',\n route: '/browser',\n description: 'Minimal web browser with bookmarks, history and navigation bar.',\n },\n} satisfies Record<string, BuiltinAppInfo>;\n\nexport type BuiltinAppId = keyof typeof BUILTIN_APP_INFO;\n\nconst ABOUT_MENU_ICON = (\n <svg className=\"h-4 w-4 text-gray-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <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\" />\n </svg>\n);\n\n// Generic window glyph shown when the consumer hasn't registered a nav icon\n// for the app's route (same fallback shape as the Modal title bar).\nconst FALLBACK_APP_ICON = (\n <svg className=\"h-9 w-9\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3.75 6.75A2.25 2.25 0 016 4.5h12a2.25 2.25 0 012.25 2.25v10.5A2.25 2.25 0 0118 19.5H6a2.25 2.25 0 01-2.25-2.25V6.75z M3.75 9h16.5\" />\n </svg>\n);\n\n/** Registers \"About <Name>\" in the window title menu and renders the dialog.\n * Render anywhere inside the app's window. */\nexport default function AboutApp({ app }: { app: BuiltinAppId }) {\n const info = BUILTIN_APP_INFO[app];\n const [open, setOpen] = useState(false);\n const openDialog = useCallback(() => setOpen(true), []);\n useWindowMenuItem(`About ${info.name}`, openDialog, ABOUT_MENU_ICON);\n\n if (!open) return null;\n\n const registered = navIcons[info.route];\n const appIcon = isValidElement(registered)\n ? cloneElement(registered as ReactElement, { className: 'h-9 w-9' } as any)\n : FALLBACK_APP_ICON;\n\n return (\n <Modal open onClose={() => setOpen(false)} title={`About ${info.name}`} size=\"sm\" compact bodyScroll={false} autoHeight dimensions={[320, 320]}>\n <div className=\"flex flex-col items-center text-center px-5 pt-5 pb-4\">\n <div className=\"flex h-16 w-16 items-center justify-center rounded-2xl bg-gray-100 text-gray-600 shrink-0\">\n {appIcon}\n </div>\n <h2 className=\"mt-3 text-lg font-bold text-gray-900\">{info.name}</h2>\n <p className=\"text-[11px] font-mono text-gray-400\">Version {info.version}</p>\n <p className=\"mt-2 text-xs text-gray-500 leading-relaxed\">{info.description}</p>\n <div className=\"mt-4 pt-3 border-t border-gray-200 w-full\">\n <p className=\"text-[11px] text-gray-500\">\n Part of the <span className=\"font-medium text-gray-700\">react-os-shell</span> desktop environment\n </p>\n {VERSION && <p className=\"mt-0.5 text-[10px] font-mono text-gray-400\">shell v{VERSION}</p>}\n </div>\n </div>\n </Modal>\n );\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/apps/_spreadsheetStage.ts"],"names":[],"mappings":";AAiCO,IAAM,gCAAA,GAAmC;AAahD,IAAI,kBAAA,GAAqD,IAAA;AACzD,IAAI,oBAAA,GAAuB,CAAA;AAIpB,SAAS,sBAAsB,IAAA,EAAwD;AAC5F,EAAA,MAAM,QAAQ,EAAE,oBAAA;AAChB,EAAA,MAAM,KAAA,GAAiC,EAAE,KAAA,EAAO,IAAA,EAAK;AACrD,EAAA,kBAAA,GAAqB,KAAA;AACrB,EAAA,OAAO;AAAA,IACL,OAAO,IAAA,EAA8B;AAMnC,MAAA,IAAI,kBAAA,KAAuB,KAAA,EAAO,KAAA,CAAM,IAAA,GAAO,IAAA;AAC/C,MAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,MAAA,MAAA,CAAO,aAAA,CAAc,IAAI,WAAA,CAAY,gCAAA,EAAkC,EAAE,MAAA,EAAQ,EAAE,KAAA,EAAO,IAAA,EAAM,IAAA,EAAK,EAAG,CAAC,CAAA;AAAA,IAC3G;AAAA,GACF;AACF;AAGO,SAAS,2BAAA,GAA8D;AAC5E,EAAA,OAAO,kBAAA;AACT;AAKO,SAAS,6BAA6B,KAAA,EAAsC;AACjF,EAAA,IAAI,kBAAA,KAAuB,OAAO,kBAAA,GAAqB,IAAA;AACzD","file":"chunk-CT3HVZRZ.js","sourcesContent":["/**\n * Spreadsheet staging — the tiny consumer-facing surface of the Spreadsheets\n * app. Lives apart from Spreadsheet.tsx so importing `setSpreadsheetPreview`\n * (from `react-os-shell/apps` or shell internals like openPreviewFile) never\n * drags the grid implementation into a host's startup bundle. The Spreadsheet\n * window component stays behind its React.lazy dynamic import and drains the\n * stage on mount via the @internal peek/claim helpers below.\n */\n\nexport interface SpreadsheetPreviewData {\n /** CSV text (comma- or tab-separated). */\n csv: string;\n /** Display name; the title strips a trailing `.csv`/`.tsv`/`.txt`. */\n filename: string;\n /** When provided, the toolbar shows an Email button that calls back with the\n * sheet serialized as CSV at click time (edits included) and a filename\n * derived from the current title. */\n onEmail?: (csv: string, filename: string) => void;\n}\n\n/** Handle returned by `setSpreadsheetPreview`. Holds the identity of the\n * staged payload so a later `.update()` only targets the window that picked\n * it up — opening a second Spreadsheet never clobbers the first. */\nexport interface SpreadsheetPreviewHandle {\n /** Replace the data shown in the window that consumed this staging. Safe to\n * call before that window exists — an update that lands while the payload\n * is still staged replaces the staged payload, so the window opens on the\n * resolved sheet instead of whatever it was staged with.\n * No-op only once the window that consumed the staging has been closed. */\n update(next: SpreadsheetPreviewData): void;\n}\n\n/** @internal Event carrying `.update()` payloads to the claiming window. */\nexport const SPREADSHEET_PREVIEW_UPDATE_EVENT = 'react-os-shell:spreadsheet-preview-update';\n\n/** @internal Staged payload awaiting the next Spreadsheet window mount.\n *\n * `data` is MUTABLE and deliberately so: `update()` rewrites it in place\n * while the stage is still unclaimed. Restaging a fresh object instead would\n * break the identity that `claimSpreadsheetPreviewStage` and the claimant's\n * own `consumedRef` are holding. */\nexport interface PendingSpreadsheetStage {\n token: number;\n data: SpreadsheetPreviewData;\n}\n\nlet pendingSpreadsheet: PendingSpreadsheetStage | null = null;\nlet nextSpreadsheetToken = 0;\n\n/** Stage CSV content for the next Spreadsheet window mount. The returned\n * handle's `update()` method swaps content in *that* specific window only. */\nexport function setSpreadsheetPreview(data: SpreadsheetPreviewData): SpreadsheetPreviewHandle {\n const token = ++nextSpreadsheetToken;\n const stage: PendingSpreadsheetStage = { token, data };\n pendingSpreadsheet = stage;\n return {\n update(next: SpreadsheetPreviewData) {\n // Same delivery gap the Preview stage has (see _previewStage.ts): the\n // window is a lazy chunk, so a consumer that resolves quickly can call\n // this before any window has mounted to hear the event, and the payload\n // would be dropped. Rewrite the stage as well while it is still ours —\n // `pendingSpreadsheet === stage` is exactly \"nobody has claimed it yet\".\n if (pendingSpreadsheet === stage) stage.data = next;\n if (typeof window === 'undefined') return;\n window.dispatchEvent(new CustomEvent(SPREADSHEET_PREVIEW_UPDATE_EVENT, { detail: { token, data: next } }));\n },\n };\n}\n\n/** @internal Render-phase peek — see Spreadsheet.tsx for the drain protocol. */\nexport function peekSpreadsheetPreviewStage(): PendingSpreadsheetStage | null {\n return pendingSpreadsheet;\n}\n\n/** @internal Commit-phase claim. The identity check keeps a payload staged\n * *after* the claimant's render-phase peek available for the window it\n * belongs to. */\nexport function claimSpreadsheetPreviewStage(stage: PendingSpreadsheetStage): void {\n if (pendingSpreadsheet === stage) pendingSpreadsheet = null;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/apps/_filesShared.ts"],"names":[],"mappings":";;;AAwBA,IAAI,aAAA,GAAwC,IAAA;AAErC,SAAS,iBAAiB,IAAA,EAA8B;AAC7D,EAAA,aAAA,GAAgB,IAAA;AAClB;AAGO,SAAS,gBAAA,GAA2C;AACzD,EAAA,OAAO,aAAA;AACT;AAKO,SAAS,oBAAA,GAAuB;AACrC,EAAA,qBAAA,EAAsB;AACxB","file":"chunk-I464GYJJ.js","sourcesContent":["/**\n * Files app config — the tiny consumer-facing surface of the Files app\n * (demo-tree injection + trash-mode open). Lives apart from Files.tsx so\n * importing these from `react-os-shell/apps` never drags the Files\n * implementation into a host's startup bundle; the Files window component\n * stays behind its React.lazy dynamic import.\n */\nimport { requestFilesTrashView } from '../shell/desktopIcons';\n\n/**\n * Demo filesystem. When a consumer (e.g. the demo app) injects a static tree\n * via `setFilesDemoTree`, Files browses it in-memory — no file server needed.\n * Browse-only: upload / new-folder / rename / delete / trash are hidden while a\n * demo tree is set. Consumers with a real file server never call it, so their\n * behaviour is unchanged.\n */\nexport interface FilesDemoNode {\n name: string;\n kind: 'file' | 'folder';\n size?: number;\n modifiedAt?: string;\n children?: FilesDemoNode[];\n}\n\nlet filesDemoTree: FilesDemoNode[] | null = null;\n\nexport function setFilesDemoTree(tree: FilesDemoNode[] | null) {\n filesDemoTree = tree;\n}\n\n/** @internal Current injected demo tree, if any — read by Files.tsx. */\nexport function getFilesDemoTree(): FilesDemoNode[] | null {\n return filesDemoTree;\n}\n\n// Side-channel for opening Files on a specific view (trash, desktop folder).\n// The flag + event plumbing lives in shell/desktopIcons.tsx; this wrapper is\n// kept because consumers import it from the package.\nexport function openFilesInTrashMode() {\n requestFilesTrashView();\n}\n"]}
|
package/dist/chunk-JMKYYPOF.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/forms/Button.tsx"],"names":["Button"],"mappings":"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","file":"chunk-LQTVTOSY.js","sourcesContent":["/**\n * Button — the kit's standard button. `primary` uses `bg-blue-600`, which the\n * theme system remaps to the active accent (and to a readable tone in dark\n * mode), so a primary button always follows the user's chosen accent.\n *\n * Controlled by the consumer like any native button (spread `onClick`, `type`,\n * `form`, …); the component only adds the look, a `loading` spinner state, and\n * optional icon slots.\n */\nimport { forwardRef, useId, type ButtonHTMLAttributes, type ReactNode } from 'react';\n\n/**\n * `ghost-danger` is a destructive action sitting in a row of ordinary ones —\n * Clear, Discard, Remove — where solid `danger` would be wrong. Two solid\n * shouting buttons on one screen means neither is read, so the destructive one\n * borrows `ghost`'s weight and keeps only the colour. The colour is the point:\n * it lets someone tell what the button costs WITHOUT reading it, which under\n * time pressure is what actually happens.\n */\nexport type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'ghost-danger' | 'danger' | 'link';\n/**\n * `sm`/`md`/`lg` are the desktop scale. The `touch-*` rungs are for a finger on\n * glass — a till, a warehouse tablet — and are a SEPARATE ladder on purpose:\n * a till's idea of \"medium\" is 56px, and naming it `md` would have made the\n * same word mean two sizes depending on which app you were reading.\n *\n * Nothing picks a touch size automatically. An app that wants them asks for\n * them, so a desktop portal cannot grow finger-sized buttons by accident.\n */\nexport type ButtonSize = 'sm' | 'md' | 'lg' | 'touch-sm' | 'touch' | 'touch-lg' | 'touch-xl';\n\nexport interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className'> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n /** Show a spinner and disable the button while an action is in flight. */\n loading?: boolean;\n leftIcon?: ReactNode;\n rightIcon?: ReactNode;\n /** Stretch to the full width of the container. */\n block?: boolean;\n className?: string;\n /**\n * Why this button is disabled, rendered as text BESIDE it — never as a\n * `title` tooltip. A tooltip needs a hover, a touchscreen has no hover, and a\n * cashier facing a dead button with no explanation calls a manager.\n *\n * Only rendered while the button is actually disabled (or loading); ignored\n * otherwise, so it is safe to pass unconditionally. Note it changes the\n * rendered shape: the button gains a wrapper element, which matters to a\n * parent `flex gap-*` row or a `:first-child` selector.\n */\n disabledReason?: ReactNode;\n}\n\n/**\n * Shared with `IconButton`, which composes its own class string rather than\n * passing a square size through this component's `className` — two competing\n * padding utilities in one attribute resolve by compiled-stylesheet order, not\n * by the order they were written (the reasoning in forms/styles.ts applies\n * here identically).\n *\n * Package-internal on purpose: exported from the module so IconButton can share\n * it, deliberately absent from `src/ui/kit.ts` so it is not public API. A\n * consumer restyling a button is a consumer the theme system cannot reach.\n */\nexport const BUTTON_BASE =\n 'inline-flex items-center justify-center rounded-md font-medium transition-colors ' +\n 'rosh-focus-ring focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-60';\n\nexport const BUTTON_VARIANTS: Record<ButtonVariant, string> = {\n primary: 'bg-blue-600 text-white shadow-sm hover:bg-blue-700 focus-visible:ring-[color:var(--rosh-focus-ring-strong)]',\n secondary: 'border border-gray-300 bg-white text-gray-700 shadow-sm hover:bg-gray-50 focus-visible:ring-[color:var(--rosh-focus-ring)]',\n ghost: 'text-gray-700 hover:bg-gray-100 focus-visible:ring-[color:var(--rosh-focus-ring)]',\n 'ghost-danger': 'text-red-600 hover:bg-red-50 focus-visible:ring-[color:var(--rosh-focus-ring-invalid)]',\n danger: 'bg-red-600 text-white shadow-sm hover:bg-red-700 focus-visible:ring-[color:var(--rosh-focus-ring-invalid-strong)]',\n /**\n * A button that reads as a link: no box, no padding, underlined on hover.\n * For an action that belongs in running text or beside a field — \"Forgot\n * your password?\", \"Use a different address\" — where a boxed button would\n * claim more of the page than the action deserves.\n *\n * It is still a <button>. Anything that NAVIGATES should be an anchor, so it\n * can be middle-clicked and read off the status bar; this is for actions\n * that merely look quiet.\n */\n link: 'text-blue-600 underline-offset-4 hover:underline focus-visible:ring-[color:var(--rosh-focus-ring)]',\n};\n\n// A Record keyed by the union, not a ternary chain: adding a member without a\n// size here is then a compile error rather than a silent fall-through to the\n// default branch. `sm`/`md` are byte-for-byte what they have always been, and\n// `tests/touchPrimitives.test.tsx` pins them against a captured literal.\n//\n// Every rung is also asserted to be the same height as the IconButton rung of\n// the same name (`tests/buttonLinkAndLarge.test.tsx`) — pinning the two sides\n// separately is how `lg` first shipped 4px short of its square twin.\n//\n// The touch rungs carry an explicit `h-*` where the desktop pair uses `py-*`.\n// That is deliberate: a hit target has to be a guaranteed height, not a height\n// implied by the current font size. They are separate map entries and never\n// concatenated with each other, so no two competing utilities land in one class\n// attribute.\nconst SIZES: Record<ButtonSize, string> = {\n sm: 'gap-1 px-2.5 py-1 text-xs',\n md: 'gap-1.5 px-3 py-1.5 text-sm',\n // `py-2.5`, not `py-2`: this rung has to come out 40px to match IconButton's\n // `h-10`, which is the promise IconButton's docblock makes for every rung.\n // `py-2` gives 36px (20px line box + 8px twice) and puts a 4px step between a\n // Button and an IconButton sitting in the same row.\n lg: 'gap-2 px-4 py-2.5 text-sm',\n // 44px is the WCAG 2.5.5 floor, and a floor is not a comfortable size. Use it\n // ONLY for chrome that sits outside the task — switching screens, ending a\n // shift — and never on the path someone is actually working through. It\n // exists so a dense toolbar has something legitimate to reach for instead of\n // borrowing the desktop `sm`, which is half this height.\n 'touch-sm': 'gap-1.5 h-11 px-4 text-base',\n 'touch': 'gap-2 h-14 px-5 text-base', // 56px — the comfortable default\n 'touch-lg': 'gap-2 h-16 px-6 text-lg', // 64px — primary action on a page\n 'touch-xl': 'gap-3 h-20 px-8 text-2xl', // 80px — keypad keys, tender\n};\n\n// Everything BUTTON_BASE has except the box: no rounding (there is nothing to\n// round) and no `justify-center` (a link sits in a line of text, it does not\n// centre itself in a shape).\nconst LINK_BASE =\n 'inline-flex items-center font-medium transition-colors ' +\n 'rosh-focus-ring focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-60';\n\n// Only the gap and the text size survive from each rung — the padding is what\n// the link variant exists to shed.\nconst LINK_SIZES: Record<ButtonSize, string> = {\n sm: 'gap-1 text-xs',\n md: 'gap-1.5 text-sm',\n lg: 'gap-2 text-sm',\n 'touch-sm': 'gap-1.5 text-base',\n 'touch': 'gap-2 text-base',\n 'touch-lg': 'gap-2 text-lg',\n 'touch-xl': 'gap-3 text-2xl',\n};\n\nfunction Spinner() {\n return (\n <svg className=\"h-3.5 w-3.5 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" />\n <path className=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z\" />\n </svg>\n );\n}\n\nconst Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n { variant = 'primary', size = 'md', loading = false, leftIcon, rightIcon, block = false,\n disabled, type = 'button', children, className = '', disabledReason, ...rest },\n ref,\n) {\n // useId, not a module counter: a counter assigns different ids on the server\n // and the client and breaks hydration. Called unconditionally — it is a hook.\n const generatedId = useId();\n const isDisabled = disabled || loading;\n const showReason = isDisabled && disabledReason != null && disabledReason !== '';\n const reasonId = showReason ? generatedId : undefined;\n\n const button = (\n <button\n ref={ref}\n type={type}\n disabled={isDisabled}\n aria-busy={loading || undefined}\n aria-describedby={reasonId}\n className={[\n // `link` takes the base WITHOUT the box: no rounding, no padding from\n // the size table. It cannot cancel those by overriding — two padding\n // utilities in one class attribute resolve by compiled-stylesheet\n // order, not by the order they were written — so it never receives\n // them, and keeps only the size's gap and text size.\n variant === 'link' ? LINK_BASE : BUTTON_BASE,\n BUTTON_VARIANTS[variant],\n variant === 'link' ? LINK_SIZES[size] : SIZES[size],\n block ? 'w-full' : '',\n className,\n ].filter(Boolean).join(' ')}\n {...rest}\n >\n {loading ? <Spinner /> : leftIcon}\n {children}\n {!loading && rightIcon}\n </button>\n );\n\n // No reason to show ⇒ the bare <button>, byte-identical to every release\n // before this prop existed. Only the disabled-with-a-reason case wraps.\n if (!showReason) return button;\n\n return (\n <span className={block ? 'flex w-full flex-col gap-1' : 'inline-flex flex-col items-start gap-1'}>\n {button}\n <span id={reasonId} className=\"text-xs text-red-600\">{disabledReason}</span>\n </span>\n );\n});\n\nexport default Button;\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/apps/_wasmBytes.ts","../src/apps/_panelActions.tsx","../src/apps/PdfViewer.tsx"],"names":["jsx"],"mappings":"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","file":"chunk-SEWF4O3H.js","sourcesContent":["/**\n * Is what came back over the wire actually a WebAssembly module?\n *\n * `Preview`'s wasm factory used to accept any `res.ok` response. That is not\n * enough on a single-page app: an SPA host answers a path its router does not\n * recognise with its own index.html under HTTP 200, so a build that failed to\n * emit a decoder returns a PAGE rather than a 404 (confirmed against the live\n * console on 2026-08-30 — both `/assets/jbig2.wasm` and a made-up asset path\n * answered 200 with `content-type: text/html`). pdf.js then instantiates the\n * page inside a `try` that only warns before dropping to its JS fallback, which\n * is the same quiet degrading the factory was added to remove — except now\n * nothing in the console points at the emit as the cause.\n *\n * This module is deliberately free of React and of pdfjs-dist so it can be\n * driven directly from a spec. Importing `Preview.tsx` in a Node test is not\n * possible: pdfjs builds a `new DOMMatrix()` at module scope and throws.\n */\n\n/** Every WebAssembly module opens with the four bytes `\\0asm`. Anything else —\n * an HTML fallback page, most often — is not one. */\nexport function hasWasmPreamble(bytes: Uint8Array): boolean {\n return (\n bytes.length >= 4 &&\n bytes[0] === 0x00 &&\n bytes[1] === 0x61 &&\n bytes[2] === 0x73 &&\n bytes[3] === 0x6d\n );\n}\n\n/** A short, safe description of what arrived instead, for the error message.\n * The opening bytes of an HTML page name the problem far better than a length\n * does, so they are worth quoting; the cap keeps a stray binary out of the\n * console. */\nexport function describeNonWasm(bytes: Uint8Array): string {\n if (bytes.length === 0) return 'an empty response';\n const head = Array.from(bytes.subarray(0, 24))\n .map(b => (b >= 0x20 && b < 0x7f ? String.fromCharCode(b) : '.'))\n .join('');\n return `${bytes.length} bytes starting \"${head}\"`;\n}\n\n/** The message a developer reads when the emit went missing. It has to name the\n * emit, because the symptom they are looking at is a preview that renders\n * everything except the scanned page, with a silent console. */\nexport function wasmBytesError(href: string, filename: string, bytes: Uint8Array): Error {\n return new Error(\n `The response for ${href} is not a WebAssembly module (got ` +\n `${describeNonWasm(bytes)}). The bundler did not emit \"${filename}\" as ` +\n `an asset, and the host answered with a fallback page instead of a 404. ` +\n `Serve pdfjs-dist/wasm/ yourself and point ` +\n `window.__REACT_OS_SHELL_PDF_WASM__ at it.`,\n );\n}\n","/**\n * Where a format panel's toolbar buttons go.\n *\n * Inside the Preview window there is one toolbar row — Open, the filename,\n * then a slot at its right end — and each panel (PDF, DXF, 3D, image) portals\n * its own page nav / zoom / layer / download buttons into that slot rather\n * than stacking a second row under it. `ToolbarSlotContext` carries the slot\n * element down.\n *\n * Outside Preview there is no slot, and there is no outer toolbar to merge\n * with either: an embedded `PdfViewer` is the whole widget. So the fallback\n * renders the buttons in place as the panel's own toolbar. It used to return\n * `null`, which was right while every panel lived in a window and wrong the\n * moment one did not — an embedded viewer with no page nav and no zoom.\n */\nimport { createContext, useContext } from 'react';\nimport { createPortal } from 'react-dom';\n\nexport const ToolbarSlotContext = createContext<HTMLElement | null>(null);\n\nexport function PanelActions({ children }: { children: React.ReactNode }) {\n const slot = useContext(ToolbarSlotContext);\n if (slot) return createPortal(children, slot);\n return (\n <div className=\"flex shrink-0 items-center gap-1 border-b border-gray-200 bg-gray-50 px-3 py-2 text-xs\">\n {children}\n </div>\n );\n}\n","/**\n * PdfViewer — the pdf.js reader, on its own so it can be embedded.\n *\n * It was the Preview window's private `PdfPanel` until a consumer needed a PDF\n * inside a dialog rather than in a window of its own. The two consumers that\n * needed one had each reached for `<iframe src={blobUrl}>`, which hands the\n * page to the BROWSER's built-in plugin: its own toolbar, a ~250px thumbnail\n * rail, and its own idea of the zoom — roughly a third of an already small\n * pane spent on chrome nobody asked for, and a portrait page cropped at the\n * fold. Nothing about that is themeable or testable, and it is not this\n * package's viewer.\n *\n * Import it lazily (`react-os-shell/apps` exports it wrapped in `lazy`, like\n * the other document apps) and render it inside a `<Suspense>`: the static\n * `pdfjs-dist` import below must not reach a host's startup bundle. Give it a\n * parent with a resolved height — it fills its container and scrolls inside.\n */\nimport { useState, useEffect, useRef } from 'react';\nimport * as pdfjsLib from 'pdfjs-dist';\nimport toast from '../shell/toast';\nimport { hasWasmPreamble, wasmBytesError } from './_wasmBytes';\nimport { PanelActions } from './_panelActions';\n\n// Default the worker to the matching unpkg build (mirrors the consumer's\n// installed npm version exactly). Consumers can override by setting\n// pdfjsLib.GlobalWorkerOptions.workerSrc themselves before this module loads.\nif (typeof window !== 'undefined' && !pdfjsLib.GlobalWorkerOptions.workerSrc) {\n pdfjsLib.GlobalWorkerOptions.workerSrc =\n `https://unpkg.com/pdfjs-dist@${pdfjsLib.version}/build/pdf.worker.min.mjs`;\n}\n\n// pdf.js does not ship its JBIG2 and JPEG 2000 decoders as JavaScript any\n// more: both are WebAssembly modules it loads at decode time, and it will only\n// load them from a location the caller supplies. With neither `wasmUrl` nor a\n// `BinaryDataFactory` configured — this package's state until now — the\n// decoder asks for one, pdf.js's own base factory throws \"Ensure that the\n// `wasmUrl` API parameter is provided\", and the worker swallows that as a\n// `warn()`. The page still renders; the scanned or JPEG-2000 image on it is\n// just missing. Nothing reaches the user, which is why it went unnoticed.\n//\n// The fix follows the mechanism the admin portal already uses for the pdf\n// WORKER (`import ... from 'pdfjs-dist/build/pdf.worker.min.mjs?url'`, which\n// Vite emits as /assets/pdf.worker.min-<hash>.mjs): let the CONSUMER'S bundler\n// emit the file and hand pdf.js the URL it produced. `new URL(spec,\n// import.meta.url)` is the bundler-neutral spelling of that — Vite resolves\n// the bare specifier and emits the binary as its own hashed asset (verified on\n// Vite 5 and 7), and a bundler that does not understand the form leaves it\n// alone and lands back on today's warn-and-degrade rather than on an error.\n//\n// The specifier points at the CONSUMER'S installed `pdfjs-dist`, not at a copy\n// vendored into this package. That is deliberate: these binaries are built\n// alongside the worker that instantiates them, and a vendored copy would pin\n// them to THIS package's devDependency instead of to the peer they actually\n// run against.\nconst PDF_WASM_MODULES: Record<string, string> = {\n 'jbig2.wasm': new URL('pdfjs-dist/wasm/jbig2.wasm', import.meta.url).href,\n 'openjpeg.wasm': new URL('pdfjs-dist/wasm/openjpeg.wasm', import.meta.url).href,\n};\n\n/** pdf.js instantiates this itself (it is passed to `getDocument` as a class,\n * not an instance) and calls `fetch` once per binary it needs.\n *\n * Only the `wasmUrl` kind is served. cMap and standard-font data are the other\n * two kinds pdf.js can ask for, and this package has never configured a URL\n * for either — so those keep the exact message pdf.js's own base factory\n * raises for an unconfigured URL, and behave as they always have. */\nclass BundledPdfWasmFactory {\n async fetch({ kind, filename }: { kind: string; filename: string }): Promise<Uint8Array> {\n if (kind !== 'wasmUrl') {\n throw new Error(`Ensure that the \\`${kind}\\` API parameter is provided.`);\n }\n const href = PDF_WASM_MODULES[filename];\n if (!href) {\n throw new Error(`No bundled pdf.js wasm module for \"${filename}\".`);\n }\n const res = await fetch(href);\n if (!res.ok) {\n throw new Error(`Unable to load wasm data at: ${href}`);\n }\n // A 200 is not proof the bundler emitted the binary — see _wasmBytes.ts.\n const bytes = new Uint8Array(await res.arrayBuffer());\n if (!hasWasmPreamble(bytes)) {\n throw wasmBytesError(href, filename, bytes);\n }\n return bytes;\n }\n}\n\n/** The `getDocument` parameters that give pdf.js somewhere to load its wasm\n * decoders from.\n *\n * A consumer that serves the whole `pdfjs-dist/wasm/` directory itself (copied\n * into `public/`, say, or on an air-gapped host) points us at it with\n * `window.__REACT_OS_SHELL_PDF_WASM__` — same escape hatch as\n * `__REACT_OS_SHELL_O3DV_LIBS__` below. pdf.js requires a trailing slash on\n * that value and throws \"Invalid factory url\" without one, so add it here\n * rather than making every consumer remember. Everyone else gets the\n * bundler-emitted modules and configures nothing. */\nfunction pdfWasmParams(): { wasmUrl?: string; BinaryDataFactory?: object } {\n const dir = typeof window !== 'undefined'\n ? (window as unknown as { __REACT_OS_SHELL_PDF_WASM__?: unknown }).__REACT_OS_SHELL_PDF_WASM__\n : undefined;\n if (typeof dir === 'string' && dir) {\n return { wasmUrl: dir.endsWith('/') ? dir : `${dir}/` };\n }\n return { BinaryDataFactory: BundledPdfWasmFactory };\n}\n\nexport interface PdfViewerProps {\n /** Object URL or remote URL of the PDF. */\n url: string;\n /** Display name — also the filename the built-in Download uses. */\n filename: string;\n /**\n * What the auto-fit scale tracks until the reader picks a manual zoom.\n *\n * `'width'` fills the container's width and lets a tall page scroll — right\n * for a viewer that owns a whole window, and what the Preview app uses.\n * `'page'` fits the WHOLE page, which is what an embedded preview pane\n * wants: someone checking a document before sending it needs to see that it\n * is the right document, and a page cropped at the fold reads as broken.\n */\n fit?: 'width' | 'page';\n /** Replaces the built-in \"save `url` as `filename`\" download. */\n onDownload?: () => void;\n /** An Email button joins the toolbar only when this is supplied. */\n onEmail?: () => void;\n}\n\nconst ZOOM_PRESETS = [50, 75, 100, 125, 150, 200, 300, 400];\n\n/** The `p-4` gutter the page sits in, on both axes. */\nconst PAGE_GUTTER = 40;\nconst MIN_SCALE = 0.3;\nconst MAX_SCALE = 4;\n\n/**\n * The scale auto-fit lands on.\n *\n * @internal Exported for the spec. The effect that calls it needs a rendered\n * document and a laid-out container to reach, and the wrong answer here does\n * not throw — it crops the page at the fold, which reads as a broken preview\n * rather than as a bug in a formula.\n */\nexport function fitScale(\n fit: 'width' | 'page',\n container: { width: number; height: number },\n page: { width: number; height: number },\n): number {\n const byWidth = (container.width - PAGE_GUTTER) / page.width;\n // Fitting the PAGE means neither axis may overflow, so take whichever\n // constraint binds first. Width alone is what crops a portrait page in a\n // pane wider than it is tall.\n const wanted = fit === 'page'\n ? Math.min(byWidth, (container.height - PAGE_GUTTER) / page.height)\n : byWidth;\n return Math.min(Math.max(wanted, MIN_SCALE), MAX_SCALE);\n}\n\n// Trimmed copy of pdf.js's text-layer rules (pdf_viewer.css), under our own\n// class name so a consumer that loads the full viewer CSS doesn't\n// double-apply. The layer is a sheet of transparent spans positioned over\n// the canvas glyphs — that's what makes the rendered page selectable. Span\n// positions are % of page size; font-size derives from\n// --total-scale-factor, which the render effect sets to the viewport scale.\n// The .endOfContent sentinel plus the .selecting toggle keep a\n// drag-selection alive between lines and past the last line (same trick as\n// pdf.js's own TextLayerBuilder).\nconst TEXT_LAYER_CSS = `\n.preview-pdf-textlayer {\n position: absolute;\n inset: 0;\n overflow: clip;\n line-height: 1;\n text-align: initial;\n text-size-adjust: none;\n forced-color-adjust: none;\n transform-origin: 0 0;\n caret-color: CanvasText;\n --min-font-size: 1;\n --text-scale-factor: calc(var(--total-scale-factor, 1) * var(--min-font-size));\n --min-font-size-inv: calc(1 / var(--min-font-size));\n}\n.preview-pdf-textlayer :is(span, br) {\n color: transparent;\n position: absolute;\n white-space: pre;\n cursor: text;\n transform-origin: 0% 0%;\n}\n.preview-pdf-textlayer > :not(.markedContent),\n.preview-pdf-textlayer .markedContent span:not(.markedContent) {\n z-index: 1;\n --font-height: 0;\n font-size: calc(var(--text-scale-factor) * var(--font-height));\n --scale-x: 1;\n --rotate: 0deg;\n transform: rotate(var(--rotate)) scaleX(var(--scale-x)) scale(var(--min-font-size-inv));\n}\n.preview-pdf-textlayer .markedContent {\n display: contents;\n}\n.preview-pdf-textlayer span[role=\"img\"] {\n user-select: none;\n cursor: default;\n}\n.preview-pdf-textlayer ::selection {\n background: rgba(0, 80, 255, 0.25);\n}\n.preview-pdf-textlayer br::selection {\n background: transparent;\n}\n.preview-pdf-textlayer .endOfContent {\n display: block;\n position: absolute;\n inset: 100% 0 0;\n z-index: 0;\n cursor: default;\n user-select: none;\n}\n.preview-pdf-textlayer.selecting .endOfContent {\n top: 0;\n}\n`;\n\nexport default function PdfViewer({ url, filename, fit = 'width', onDownload, onEmail }: PdfViewerProps) {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const textLayerRef = useRef<HTMLDivElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const [pdf, setPdf] = useState<pdfjsLib.PDFDocumentProxy | null>(null);\n const [page, setPage] = useState(1);\n const [totalPages, setTotalPages] = useState(0);\n const [scale, setScale] = useState(1);\n // Fit mode is the default — scale stays auto-tracking the container width\n // until the user picks a manual zoom (− / + / dropdown). Re-armed by the\n // Fit toolbar button.\n const [fitMode, setFitMode] = useState(true);\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n let cancelled = false;\n setLoading(true);\n // A new document starts at its first page. The page number belongs to the\n // document being read, not to the viewer, and it used to survive a change\n // of `url`: sitting on page 3 and being handed a one-page document asked\n // pdf.js for a page that does not exist, and the canvas went blank with\n // nothing thrown. The Preview window never saw it because it keys its\n // panels by url, but an embedder that re-renders one viewer with a new\n // document — a preview pane next to the options that regenerate it — is\n // exactly the shape this component was extracted for.\n //\n // Reset before the fetch, not in its `.then`: the manual page controls are\n // live while a document loads, and a reset that landed afterwards would\n // undo a page the reader had already picked.\n //\n // Zoom is deliberately NOT reset. It is the reader's own preference for\n // how large they want text, and it holds across documents the way it does\n // in every reader; only the page is a fact about the file.\n setPage(1);\n // Parameter-object form, not the bare string: pdfjs-dist 6.x removed the\n // string overload (\"expected either `data`, `range`, or `url` parameter\"),\n // so a consumer on 6.x had every preview die on this line. The object form\n // works on 5.x too. tests/pdfjsContract.test.ts fails if it reverts.\n pdfjsLib.getDocument({ url, ...pdfWasmParams() }).promise.then(doc => {\n if (cancelled) return;\n setPdf(doc);\n setTotalPages(doc.numPages);\n setLoading(false);\n }).catch(() => {\n if (!cancelled) { toast.error('Failed to load PDF'); setLoading(false); }\n });\n return () => { cancelled = true; };\n }, [url]);\n\n // Auto-fit: re-compute scale from the container whenever fit mode is on, the\n // document changes, the page changes, or the container resizes. Window\n // resize and Modal drag-resize both trigger ResizeObserver. Manual zoom\n // turns this off until the user clicks Fit again.\n useEffect(() => {\n if (!pdf || !fitMode || !containerRef.current) return;\n const recompute = () => {\n const el = containerRef.current;\n if (!el) return;\n pdf.getPage(page).then(p => {\n if (!containerRef.current) return;\n const viewport = p.getViewport({ scale: 1 });\n const next = fitScale(\n fit,\n { width: el.clientWidth, height: el.clientHeight },\n { width: viewport.width, height: viewport.height },\n );\n setScale(prev => (Math.abs(prev - next) < 0.005 ? prev : next));\n });\n };\n recompute();\n const ro = new ResizeObserver(recompute);\n ro.observe(containerRef.current);\n return () => ro.disconnect();\n }, [pdf, page, fitMode, fit]);\n\n useEffect(() => {\n if (!pdf || !canvasRef.current) return;\n let cancelled = false;\n let task: { cancel: () => void; promise: Promise<void> } | null = null;\n let textLayer: pdfjsLib.TextLayer | null = null;\n pdf.getPage(page).then(p => {\n if (cancelled || !canvasRef.current) return;\n const viewport = p.getViewport({ scale });\n const canvas = canvasRef.current;\n canvas.width = viewport.width;\n canvas.height = viewport.height;\n // Lock the displayed (CSS) size to the viewport. pdf.js stamps inline\n // style.width/height on render and inline values stick across re-renders\n // even when canvas.width shrinks — without this, zoom changes only\n // affect resolution, not visible size.\n canvas.style.width = `${viewport.width}px`;\n canvas.style.height = `${viewport.height}px`;\n const ctx = canvas.getContext('2d')!;\n task = p.render({ canvas, canvasContext: ctx, viewport });\n task.promise.catch(() => {});\n // Selectable text layer — transparent spans over the canvas glyphs.\n // Rebuilt per page/zoom render; pdf.js reads --total-scale-factor for\n // span font-size and the container's own width/height.\n const textEl = textLayerRef.current;\n if (textEl) {\n textEl.replaceChildren();\n textEl.style.setProperty('--total-scale-factor', String(viewport.scale));\n textLayer = new pdfjsLib.TextLayer({\n textContentSource: p.streamTextContent(),\n container: textEl,\n viewport,\n });\n textLayer.render().then(() => {\n if (cancelled) return;\n const end = document.createElement('div');\n end.className = 'endOfContent';\n textEl.append(end);\n }).catch(() => {});\n }\n });\n return () => {\n cancelled = true;\n task?.cancel();\n textLayer?.cancel();\n };\n }, [pdf, page, scale]);\n\n // End a selection drag wherever the pointer is released — the .selecting\n // class is added on pointerdown in the layer itself (see JSX below).\n useEffect(() => {\n const up = () => textLayerRef.current?.classList.remove('selecting');\n window.addEventListener('pointerup', up);\n return () => window.removeEventListener('pointerup', up);\n }, []);\n\n const handlePrint = () => {\n if (!pdf) return;\n const win = window.open('', '_blank');\n if (!win) { toast.error('Allow popups to print'); return; }\n const promises: Promise<string>[] = [];\n for (let i = 1; i <= totalPages; i++) {\n promises.push(pdf.getPage(i).then(p => {\n const vp = p.getViewport({ scale: 2 });\n const c = document.createElement('canvas');\n c.width = vp.width; c.height = vp.height;\n return p.render({ canvas: c, canvasContext: c.getContext('2d')!, viewport: vp }).promise.then(() => c.toDataURL());\n }));\n }\n Promise.all(promises).then(images => {\n win.document.write(`<html><head><title>${filename}</title><style>@media print{body{margin:0}img{width:100%;page-break-after:always}}</style></head><body>`);\n win.document.write(images.map(src => `<img src=\"${src}\"/>`).join(''));\n win.document.write('</body></html>');\n win.document.close();\n setTimeout(() => { win.print(); win.close(); }, 300);\n });\n };\n\n const handleDefaultDownload = () => {\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n a.click();\n };\n\n // Re-arm fit mode. The auto-fit effect picks the new scale on its next\n // tick once `fitMode` flips to true.\n const reFit = () => setFitMode(true);\n const fitLabel = fit === 'page' ? 'Fit page' : 'Fit width';\n\n // Wheel and keyboard page navigation. Only takes the wheel event when\n // the container is at the top/bottom edge — otherwise normal scroll\n // moves through a tall page first, matching native PDF readers.\n const onWheelPage = (e: React.WheelEvent<HTMLDivElement>) => {\n if (!pdf) return;\n if (e.ctrlKey || e.metaKey) return; // browser-zoom shortcut, leave alone\n const el = containerRef.current;\n if (!el) return;\n const atTop = el.scrollTop <= 0;\n const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 1;\n if (e.deltaY > 0 && atBottom && page < totalPages) {\n e.preventDefault();\n setPage(p => Math.min(totalPages, p + 1));\n // Land at the top of the next page so wheel-down keeps reading\n // forward. requestAnimationFrame so the canvas has a tick to\n // resize before we scroll.\n requestAnimationFrame(() => { if (containerRef.current) containerRef.current.scrollTop = 0; });\n } else if (e.deltaY < 0 && atTop && page > 1) {\n e.preventDefault();\n setPage(p => Math.max(1, p - 1));\n requestAnimationFrame(() => {\n if (containerRef.current) containerRef.current.scrollTop = containerRef.current.scrollHeight;\n });\n }\n };\n const onKeyPage = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (!pdf) return;\n // Ignore keyboard nav when an input/textarea has focus.\n const tag = (document.activeElement?.tagName || '').toLowerCase();\n if (tag === 'input' || tag === 'textarea' || (document.activeElement as HTMLElement)?.isContentEditable) return;\n if (e.key === 'PageDown' || e.key === 'ArrowRight') {\n if (page < totalPages) { e.preventDefault(); setPage(p => Math.min(totalPages, p + 1)); }\n } else if (e.key === 'PageUp' || e.key === 'ArrowLeft') {\n if (page > 1) { e.preventDefault(); setPage(p => Math.max(1, p - 1)); }\n } else if (e.key === 'Home') {\n e.preventDefault(); setPage(1);\n } else if (e.key === 'End') {\n e.preventDefault(); setPage(totalPages);\n }\n };\n\n const btn = 'px-2 py-1 rounded hover:bg-gray-200 transition-colors text-gray-600 flex items-center gap-1';\n\n return (\n <div className=\"flex flex-col h-full\">\n <PanelActions>\n <button aria-label=\"Previous page\" onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page <= 1} className=\"px-1 py-1 rounded hover:bg-gray-200 disabled:opacity-30 text-gray-600\">\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M15.75 19.5L8.25 12l7.5-7.5\" /></svg>\n </button>\n <span className=\"text-gray-600 font-medium tabular-nums\">{page} / {totalPages}</span>\n <button aria-label=\"Next page\" onClick={() => setPage(p => Math.min(totalPages, p + 1))} disabled={page >= totalPages} className=\"px-1 py-1 rounded hover:bg-gray-200 disabled:opacity-30 text-gray-600\">\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" /></svg>\n </button>\n <div className=\"h-4 w-px bg-gray-300 mx-1\" />\n <button\n onClick={() => { setFitMode(false); setScale(s => Math.max(0.3, Math.round((s - 0.25) * 100) / 100)); }}\n className={btn}\n >−</button>\n <select\n value={fitMode ? 'fit' : (ZOOM_PRESETS.includes(Math.round(scale * 100)) ? Math.round(scale * 100) : 'custom')}\n onChange={e => {\n const v = e.target.value;\n if (v === 'fit') { setFitMode(true); return; }\n if (v !== 'custom') { setFitMode(false); setScale(Number(v) / 100); }\n }}\n className=\"bg-transparent hover:bg-gray-200 rounded px-1 py-1 text-gray-600 tabular-nums cursor-pointer focus:outline-none focus:ring-1 focus:ring-blue-400\"\n title=\"Zoom\"\n >\n <option value=\"fit\">{fitLabel}</option>\n {!fitMode && !ZOOM_PRESETS.includes(Math.round(scale * 100)) && (\n <option value=\"custom\">{Math.round(scale * 100)}%</option>\n )}\n {ZOOM_PRESETS.map(p => <option key={p} value={p}>{p}%</option>)}\n </select>\n <button\n onClick={() => { setFitMode(false); setScale(s => Math.min(4, Math.round((s + 0.25) * 100) / 100)); }}\n className={btn}\n >+</button>\n <button\n onClick={reFit}\n className={btn + (fitMode ? ' bg-gray-200 text-gray-900' : '')}\n title={`${fitLabel} — auto-tracks the container size until you zoom manually`}\n >Fit</button>\n <div className=\"h-4 w-px bg-gray-300 mx-1\" />\n <button onClick={handlePrint} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6.72 13.829c-.24.03-.48.062-.72.096m.72-.096a42.415 42.415 0 0110.56 0m-10.56 0L6.34 18m10.94-4.171c.24.03.48.062.72.096m-.72-.096L17.66 18m0 0l.229 2.523a1.125 1.125 0 01-1.12 1.227H7.231c-.662 0-1.18-.568-1.12-1.227L6.34 18m11.318 0h1.091A2.25 2.25 0 0021 15.75V9.456c0-1.081-.768-2.015-1.837-2.175a48.055 48.055 0 00-1.913-.247M6.34 18H5.25A2.25 2.25 0 013 15.75V9.456c0-1.081.768-2.015 1.837-2.175a48.041 48.041 0 011.913-.247m10.5 0a48.536 48.536 0 00-10.5 0m10.5 0V3.375c0-.621-.504-1.125-1.125-1.125h-8.25c-.621 0-1.125.504-1.125 1.125v3.659M18 10.5h.008v.008H18V10.5zm-3 0h.008v.008H15V10.5z\" /></svg>\n Print\n </button>\n <button onClick={onDownload ?? handleDefaultDownload} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3\" /></svg>\n Download\n </button>\n {onEmail && (\n <button onClick={onEmail} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75\" /></svg>\n Email\n </button>\n )}\n </PanelActions>\n\n <div\n ref={containerRef}\n className=\"flex-1 overflow-auto bg-gray-100 outline-none\"\n // tabIndex makes the scroll container itself focusable so PageUp /\n // PageDown / Arrow keys are captured even when no inner element\n // has focus (e.g. right after the panel mounts). Wheel handler\n // page-flips when the user reaches the top/bottom of a long page.\n tabIndex={0}\n onWheel={onWheelPage}\n onKeyDown={onKeyPage}\n >\n {loading ? (\n <div className=\"flex items-center justify-center h-full text-gray-400 text-sm\">Loading PDF...</div>\n ) : (\n <div className=\"min-h-full flex items-center justify-center p-4\">\n <style>{TEXT_LAYER_CSS}</style>\n <div className=\"relative shadow-lg rounded\">\n <canvas ref={canvasRef} className=\"block rounded\" />\n <div\n ref={textLayerRef}\n className=\"preview-pdf-textlayer\"\n onPointerDown={e => e.currentTarget.classList.add('selecting')}\n />\n </div>\n </div>\n )}\n </div>\n </div>\n );\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shell/focusTrap.ts","../src/shell/escapeInterceptors.ts","../src/shell/overlayEvents.ts","../src/shell/useDismissPopupsOnOpen.ts","../src/shell/zLayers.ts","../src/shell/Dialog.tsx","../src/shell/ConfirmDialog.tsx"],"names":["useEffect","jsx","useRef","jsxs"],"mappings":"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","file":"chunk-XH3ELPQZ.js","sourcesContent":["/**\n * Focus containment and scroll locking for a modal surface — the two things\n * Headless UI was doing for `ConfirmDialog` before it was rewritten to import\n * nothing.\n *\n * Both are small, and both are the kind of small that is wrong in a way nobody\n * notices until someone navigates by keyboard: focus tabbing out of a dialog\n * onto the page behind it reads as the dialog having closed, and a page that\n * scrolls under a fixed overlay loses the user's place when it reopens.\n */\nimport { useEffect, type RefObject } from 'react';\n\n/**\n * Focusable candidates, in DOM order. Deliberately excludes anything with\n * `tabindex=\"-1\"` — that attribute means \"focusable by script, skipped by Tab\",\n * and treating it as a stop is the usual bug in a hand-rolled trap.\n */\nconst FOCUSABLE = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\nfunction focusable(root: HTMLElement): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE))\n // A hidden element still matches the selector. offsetParent is null for\n // anything display:none or inside it — cheap, and right for our markup.\n .filter(el => el.offsetParent !== null || el === document.activeElement);\n}\n\n/**\n * While `active`, keep Tab inside `ref`, move focus in on open, and restore it\n * to wherever it came from on close.\n *\n * Restoring matters more than trapping: a dialog opened from a button in a\n * toolbar should hand focus back to that button, or a keyboard user is dumped\n * at the top of the document and has to find their place again.\n */\nexport function useFocusTrap(\n ref: RefObject<HTMLElement | null>,\n active: boolean,\n initialFocus?: RefObject<HTMLElement | null>,\n): void {\n useEffect(() => {\n if (!active) return;\n const root = ref.current;\n if (!root) return;\n\n const previous = document.activeElement as HTMLElement | null;\n\n // Focus the requested element, else the first natural stop, else the\n // container itself so the trap has somewhere to hold focus at all.\n const first = initialFocus?.current ?? focusable(root)[0] ?? root;\n if (first === root && !root.hasAttribute('tabindex')) root.setAttribute('tabindex', '-1');\n first.focus();\n\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== 'Tab') return;\n const stops = focusable(root);\n if (stops.length === 0) { e.preventDefault(); return; }\n const firstStop = stops[0];\n const lastStop = stops[stops.length - 1];\n const current = document.activeElement;\n\n // Wrap at both ends. Also catches focus having escaped the dialog\n // entirely (current is outside root), which happens if something inside\n // was removed while focused.\n if (e.shiftKey && (current === firstStop || !root.contains(current))) {\n e.preventDefault();\n lastStop.focus();\n } else if (!e.shiftKey && (current === lastStop || !root.contains(current))) {\n e.preventDefault();\n firstStop.focus();\n }\n };\n\n document.addEventListener('keydown', onKeyDown, true);\n return () => {\n document.removeEventListener('keydown', onKeyDown, true);\n // Only take focus back if it is still inside the dialog. If something\n // else claimed it in the meantime, stealing it back is the rude option.\n if (!root.contains(document.activeElement) && document.activeElement !== document.body) return;\n previous?.focus?.();\n };\n }, [ref, active, initialFocus]);\n}\n\n/**\n * While `active`, stop the document behind the overlay from scrolling.\n *\n * Compensates for the scrollbar's width so the page does not visibly jump\n * sideways as it locks — the artefact that makes a dialog feel cheap. Nested\n * locks are counted, so two stacked dialogs unlock exactly once.\n */\nlet lockCount = 0;\nlet restore: { overflow: string; paddingRight: string } | null = null;\n\nexport function useScrollLock(active: boolean): void {\n useEffect(() => {\n if (!active) return;\n const body = document.body;\n if (lockCount === 0) {\n restore = { overflow: body.style.overflow, paddingRight: body.style.paddingRight };\n const gap = window.innerWidth - document.documentElement.clientWidth;\n body.style.overflow = 'hidden';\n if (gap > 0) body.style.paddingRight = `${gap}px`;\n }\n lockCount += 1;\n return () => {\n lockCount -= 1;\n if (lockCount === 0 && restore) {\n body.style.overflow = restore.overflow;\n body.style.paddingRight = restore.paddingRight;\n restore = null;\n }\n };\n }, [active]);\n}\n","/**\n * The Escape-interceptor seam — deliberately a leaf that imports NOTHING.\n *\n * This lived inside `shell/Modal.tsx` until 4.16.0, and that one detail decided\n * what the whole package cost to import. `forms/Select.tsx` registers an\n * interceptor so Escape closes its portaled listbox instead of the window\n * around it — a two-line need — and reaching it through Modal pulled the entire\n * window manager into any bundle containing a dropdown: react-router-dom,\n * @tanstack/react-query, axios, @headlessui/react and @heroicons/react, plus\n * Modal's module-scope `localStorage` reads, which run at import time. An app\n * that wanted a text field and a select paid for a desktop it never rendered.\n *\n * Splitting the Set out is the whole fix: Modal and Select now both import this\n * file, and neither imports the other. `registerModalEscapeInterceptor` is\n * re-exported from Modal (and so from the package root) with the same binding\n * identity, so nothing downstream changes.\n *\n * NOTHING may be imported here — not React, not a type, not a sibling. The\n * moment it has an edge, every consumer of `react-os-shell/ui` inherits that\n * edge's transitive graph, which is exactly the failure this module exists to\n * prevent. `tests/uiEntryIsPeerFree.test.ts` enforces it.\n *\n * ── Escape interceptors ────────────────────────────────────────────────────\n * Window content can claim an Escape press before the topmost-modal handler\n * closes the window — e.g. the DXF Preview's measure tool exits AutoCAD-style\n * (clear the command input, then the tool, and only a further Esc closes the\n * window). Interceptors run in REVERSE registration order — most recently\n * registered first — and the first to return true consumes the event (the\n * modal neither closes nor sees it). An interceptor is global, so it must\n * check it belongs to the *active* modal itself (via `getActiveModalId()`)\n * before consuming.\n *\n * Reverse, because registration order tracks stacking order and Escape belongs\n * to whatever is on top. Two stacked `Dialog`s each register one; oldest-first\n * would hand Escape to the dialog UNDERNEATH and dismiss the wrong surface,\n * leaving the one the user is looking at on screen. This was invisible while\n * `ConfirmDialog` was the only multi-dialog registrant, because it registered\n * a single interceptor and hand-ordered its three dialogs inside it.\n *\n * Registrants that check `getActiveModalId()` are unaffected: at most one of\n * them can consume, so the order they are offered the event in cannot change\n * which one takes it.\n *\n * ── Draining the Set with no shell present ────────────────────────────────\n * This used to say that nothing draining the Set was fine, because a component\n * would handle Escape itself. That holds for `Select`, which has its own\n * `onKeyDown`. It did NOT hold for `Dialog`, which registers here and has no\n * other handler — so on a till or a routed portal, the two places `Dialog`\n * exists to serve, Escape did nothing at all.\n *\n * So the Set drains itself. The first registration attaches one document-level\n * capture listener; the last removal takes it away.\n *\n * It cannot double-fire when a shell IS present. `Modal` listens on `window`\n * in the capture phase, and capture runs window before document: Modal's\n * handler goes first, calls `runEscapeInterceptors`, and stops propagation when\n * one consumes — so this listener never sees the event. When Modal declines\n * (it is not the topmost window), this runs and the dialog floating above it\n * still gets its Escape, which is the right outcome anyway.\n *\n * Ordering across stacked dialogs is unchanged, because it is still the same\n * reverse walk deciding who consumes — not the listener that started it.\n */\nconst escapeInterceptors = new Set<(e: KeyboardEvent) => boolean>();\n\n/**\n * The self-drain listener, attached while at least one interceptor is\n * registered. Kept in module scope rather than per-registration so N dialogs\n * share one listener and the reverse walk stays the only thing deciding who\n * consumes.\n */\nlet detachDrain: (() => void) | null = null;\n\nfunction attachDrain(): void {\n // `document` is absent when this module is imported on a server; there is no\n // Escape to handle there, and registration still works for when it hydrates.\n if (detachDrain || typeof document === 'undefined') return;\n const onKeyDown = (e: KeyboardEvent): void => {\n if (e.key !== 'Escape') return;\n if (runEscapeInterceptors(e)) {\n e.preventDefault();\n e.stopPropagation();\n }\n };\n document.addEventListener('keydown', onKeyDown, true);\n detachDrain = () => document.removeEventListener('keydown', onKeyDown, true);\n}\n\n/** Register an Escape interceptor; returns an unregister function. */\nexport function registerModalEscapeInterceptor(fn: (e: KeyboardEvent) => boolean): () => void {\n escapeInterceptors.add(fn);\n attachDrain();\n return () => {\n escapeInterceptors.delete(fn);\n if (escapeInterceptors.size === 0 && detachDrain) {\n detachDrain();\n detachDrain = null;\n }\n };\n}\n\n/** Run the registered interceptors, most recent first; true if one consumed\n * the event. Internal to the shell — the window key handler calls this,\n * consumers never do. */\nexport function runEscapeInterceptors(e: KeyboardEvent): boolean {\n // A Set iterates in insertion order and has no reverse iterator, so snapshot\n // it. The copy also makes the walk safe against an interceptor that\n // unregisters itself while handling the event — which is exactly what a\n // dialog closing on Escape does.\n const ordered = Array.from(escapeInterceptors);\n for (let i = ordered.length - 1; i >= 0; i--) {\n try {\n if (ordered[i](e)) return true;\n } catch { /* a broken interceptor must not block closing */ }\n }\n return false;\n}\n","/**\n * The overlay-open seam — deliberately a leaf that imports NOTHING, for the same\n * reason as `escapeInterceptors.ts`: every popup in `react-os-shell/ui` reaches\n * it, so any edge added here lands in every consumer's bundle.\n *\n * ── Why popups need telling ──\n * A popup (a listbox, a calendar, a context menu) is portalled to `<body>` and\n * layered above the modal layer, so it can escape the window or dialog that\n * owns it. It closes on an outside pointer-down, on its own keys, and on a\n * pick. None of those happen when a keyboard shortcut opens a full-screen\n * overlay: ⌘K raised the search palette over an open `SearchableSelect`, and\n * the list stayed open, painted crisp ABOVE the palette's blurred backdrop.\n * Raising the palette cannot fix that — a popup opened INSIDE the palette or a\n * dialog must still paint above it, so the popup layer has to stay on top.\n *\n * So an overlay announces itself as it opens, and every open popup closes.\n * An overlay dispatches BEFORE its own content subscribes (a popup subscribes\n * only while open, in a passive effect), so a popup inside the overlay being\n * opened is never the one dismissed.\n *\n * Consumers with an overlay of their own call `dismissPopups()` when it opens;\n * consumers with a popup of their own listen for `OVERLAY_OPEN_EVENT` on\n * `window`.\n */\n\n/** Dispatched on `window` when a full-screen overlay (the search palette, a\n * `Dialog`, a `Drawer`, the shortcut sheet) opens. Open popups close on it. */\nexport const OVERLAY_OPEN_EVENT = 'react-os-shell:overlay-open';\n\n/** Close every open popup — call it as an overlay opens. A no-op on a server. */\nexport function dismissPopups(): void {\n if (typeof window === 'undefined') return;\n window.dispatchEvent(new Event(OVERLAY_OPEN_EVENT));\n}\n\n/** Subscribe to `OVERLAY_OPEN_EVENT`; returns the unsubscribe. Internal to the\n * kit's popups, which call it from the effect that runs while they are open. */\nexport function onOverlayOpen(handler: () => void): () => void {\n if (typeof window === 'undefined') return () => {};\n window.addEventListener(OVERLAY_OPEN_EVENT, handler);\n return () => window.removeEventListener(OVERLAY_OPEN_EVENT, handler);\n}\n","import { useEffect, useLayoutEffect } from 'react';\nimport { dismissPopups } from './overlayEvents';\n\n/**\n * A layout effect in the browser, a passive one on a server — where a layout\n * effect never runs anyway and React 18 warns for calling one.\n */\nconst useBrowserLayoutEffect = typeof document === 'undefined' ? useEffect : useLayoutEffect;\n\n/**\n * For an overlay: close every open popup as `open` turns true.\n *\n * A LAYOUT effect, not a passive one, so the popups close in the same frame the\n * overlay first paints. A state update made from a passive effect is scheduled\n * as a later task, and the browser lane caught the gap: the palette was on\n * screen with the list still open over it, for a frame or more, one run in\n * three.\n *\n * It runs before any popup inside the overlay can have subscribed — those\n * subscribe from a passive effect, and only once open — so the overlay never\n * dismisses its own content (see `overlayEvents.ts`).\n */\nexport function useDismissPopupsOnOpen(open: boolean): void {\n useBrowserLayoutEffect(() => {\n if (open) dismissPopups();\n }, [open]);\n}\n","/**\n * The shell's z-index scale — one map instead of literals scattered across the\n * components that stack against each other. A leaf that imports nothing, like\n * `escapeInterceptors.ts`, because the kit's popups read it.\n *\n * The numbers are the ones those components already used; collecting them\n * changed the order of exactly one thing, the ⌘K search palette. It sat at 200,\n * which is below the window stack once a dozen windows are open\n * (`window + index * windowStep`), below every pinned window, and below every\n * dialog — so it now sits on the overlay layer with `Dialog` and `Drawer`, and\n * is portalled to `<body>` so no window's stacking context can hold it down.\n *\n * Applied as inline `zIndex`, never as a Tailwind `z-[…]` class built from\n * these numbers: an interpolated class name is invisible to the consumer's\n * Tailwind scan and would generate no rule at all.\n *\n * Bottom to top:\n *\n * window 50 base of the window stack; each window above adds\n * windowStep 10 (`Modal`'s activation order)\n * menu 400 context menus and flyouts that stay in their layer\n * (`PopupMenu`)\n * pinnedWindow 999 an always-on-top window\n * overlay 9999 full-screen surfaces that interrupt: the search palette,\n * `Dialog`, `Drawer`, the shortcut sheet\n * popup 10000 portalled popups (listboxes, calendars, the server\n * status card) — above the overlay layer, so one opened\n * from inside a dialog or the palette is not drawn behind\n * it. Popups close when an overlay opens (`overlayEvents`),\n * which is what stops one from floating over an overlay\n * that was opened on top of it.\n */\nexport const Z_LAYERS = {\n window: 50,\n windowStep: 10,\n menu: 400,\n pinnedWindow: 999,\n overlay: 9999,\n popup: 10000,\n} as const;\n\nexport type ZLayer = keyof typeof Z_LAYERS;\n","/**\n * Dialog — a modal sheet that is NOT a shell window.\n *\n * The distinction matters. `Modal` is a window: it has a title bar, it can be\n * minimised, dragged, stacked and restored, and it lives in the window manager's\n * activation order. This is the other thing — an overlay that interrupts, is\n * answered, and goes away. A till has no desktop to put a window on, and a\n * routed portal page has no window manager either, so both need this one.\n *\n * It imports nothing but React, which is the point: it is what lets\n * `ConfirmDialog` — and every consumer of `react-os-shell/ui` — stop depending\n * on Headless UI and Heroicons. Focus containment and scroll locking come from\n * `./focusTrap` rather than a library.\n */\nimport { useEffect, useId, useRef, type ReactNode, type RefObject } from 'react';\nimport { useFocusTrap, useScrollLock } from './focusTrap';\nimport { registerModalEscapeInterceptor } from './escapeInterceptors';\nimport { useDismissPopupsOnOpen } from './useDismissPopupsOnOpen';\nimport { useShellStrings } from './strings';\nimport { Z_LAYERS } from './zLayers';\n\nexport type DialogSize = 'sm' | 'md' | 'lg';\n\nexport interface DialogProps {\n open: boolean;\n /** Called for Escape, a backdrop click, and the close control. */\n onClose: () => void;\n title?: ReactNode;\n children?: ReactNode;\n /** Action row, right-aligned below the body. */\n footer?: ReactNode;\n /**\n * Escape and backdrop clicks do nothing — the only way out is an action in\n * the footer. For a state the user must actually resolve rather than dismiss,\n * such as a sale whose outcome is unknown. Use it sparingly: a dialog that\n * cannot be dismissed is a dead end if its actions ever fail to render.\n */\n blocking?: boolean;\n size?: DialogSize;\n /**\n * Element to focus on open. Defaults to the first focusable thing in the\n * dialog — which for a destructive confirm should be Cancel, not the button\n * that does the irreversible thing.\n */\n initialFocus?: RefObject<HTMLElement | null>;\n /**\n * Names the dialog when it carries no visible `title`. Without a name it is\n * announced as just \"dialog\", which tells a screen reader user nothing about\n * what opened. Ignored when `title` is set — a visible label always wins over\n * a parallel invisible one, or the two drift.\n */\n 'aria-label'?: string;\n className?: string;\n}\n\nconst SIZES: Record<DialogSize, string> = {\n sm: 'max-w-sm',\n md: 'max-w-md',\n lg: 'max-w-lg',\n};\n\nexport default function Dialog({\n open, onClose, title, children, footer, blocking = false, size = 'md', initialFocus,\n 'aria-label': ariaLabel, className = '',\n}: DialogProps) {\n const panelRef = useRef<HTMLDivElement>(null);\n const bodyId = useId();\n // One source of truth for the close control: the button and the title's\n // gutter are the same decision, and computing it twice is how they drift.\n const showClose = !blocking && !footer;\n const titleId = useId();\n const strings = useShellStrings().dialog;\n\n // Opening closes every popup still open elsewhere: they sit on the popup\n // layer, above this one, and would float over the scrim. A popup inside this\n // dialog is unaffected — it cannot be open yet, and it only subscribes while\n // open (see `overlayEvents.ts`).\n useDismissPopupsOnOpen(open);\n\n useFocusTrap(panelRef, open, initialFocus);\n useScrollLock(open);\n\n // Escape goes through the shell's interceptor rather than a local listener.\n // A dialog floats above every shell window but is absent from the window\n // activation order, so an unclaimed Escape reaches the frontmost Modal and\n // closes the WINDOW BEHIND the dialog. Claiming it here means the dialog\n // closes and the window does not. With no shell present nothing else is\n // listening and this is simply where Escape is handled.\n useEffect(() => {\n if (!open) return;\n return registerModalEscapeInterceptor(() => {\n if (blocking) return true; // claimed, but deliberately does nothing\n onClose();\n return true;\n });\n }, [open, blocking, onClose]);\n\n if (!open) return null;\n\n return (\n <div className=\"fixed inset-0\" style={{ zIndex: Z_LAYERS.overlay }} role=\"presentation\">\n {/* No handler here. The dismissal layer below is also `fixed inset-0`\n and sits on top of this, so a click in this region never reaches the\n scrim — an onClick here would be unreachable in every case, and two\n dismissal paths where only one can fire is how the next reader loses\n an afternoon. */}\n <div className=\"fixed inset-0 bg-black/30\" aria-hidden=\"true\" />\n {/* The scrim above covers the viewport, and so does this — which sits on\n top of it. A click \"outside the dialog\" therefore landed HERE and\n never reached the scrim's handler, so clicking away never dismissed\n anything and a dialog with no buttons could only be left with Escape.\n The dismissal lives on this layer as well, gated on the target being\n the layer itself so a click inside the panel does not bubble into it. */}\n <div\n className=\"fixed inset-0 flex items-center justify-center p-4\"\n onClick={blocking ? undefined : (e => { if (e.target === e.currentTarget) onClose(); })}\n >\n <div\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n // Points at the rendered heading rather than re-deriving the name\n // from the prop: `title` is a ReactNode, so an element title — an\n // icon beside a word, a count in a badge — used to fall through the\n // string check and leave the dialog with no name at all.\n aria-labelledby={title ? titleId : undefined}\n aria-label={title ? undefined : ariaLabel}\n // The body is the dialog's description, and saying so is what makes a\n // screen reader read the question along with the title when focus\n // lands here. Without it the user hears \"Cancel order, dialog\" and\n // has to go looking for what cancelling actually does.\n aria-describedby={children ? bodyId : undefined}\n className={`relative w-full ${SIZES[size]} rounded-lg bg-white p-6 shadow-xl ${className}`.trim()}\n >\n {/* `id` for the accessible name, and a right gutter ONLY when the\n close button is actually there to need it — a dialog with a\n footer draws no cross, and reserving 2rem beside its title just\n indents the heading away from the edge for nothing. */}\n {title && (\n <h2 id={titleId} className={`${showClose ? 'pr-8 ' : ''}text-base font-semibold text-gray-900`}>\n {title}\n </h2>\n )}\n {children && <div id={bodyId} className=\"mt-2 text-sm text-gray-600\">{children}</div>}\n {footer && <div className=\"mt-6 flex justify-end gap-3\">{footer}</div>}\n {/* A way out that is visible, for a dialog that offers none of its\n own. Escape and the backdrop both work, but neither is on screen,\n so a dialog whose body is just an image could be left only by a\n keyboard shortcut nothing announces.\n\n NOT when there is a footer. A confirm already offers two labelled\n choices, and an unlabelled cross beside \"Discard\" and \"Keep\n Editing\" is a third exit that says nothing about which one it\n means — worse on the decision where it matters most. It also put\n a stop in the middle of that dialog's Tab cycle, which is the\n thing the browser test guards.\n\n LAST in the DOM though drawn top-right: first, it became the\n dialog's first tab stop and the first button a focus trap finds. */}\n {showClose && (\n <button\n type=\"button\"\n onClick={onClose}\n aria-label={strings.close}\n className=\"absolute right-3 top-3 rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600 rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-solid)]\"\n >\n <svg className=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden=\"true\">\n <path d=\"M6 6l8 8M14 6l-8 8\" />\n </svg>\n </button>\n )}\n </div>\n </div>\n </div>\n );\n}\n","/**\n * confirm / confirmDestructive / prompt — the imperative dialogs, callable from\n * any click handler without a hook.\n *\n * Rebuilt on `./Dialog` in 4.18.0. It previously rendered Headless UI, which\n * meant every consumer of `react-os-shell/ui` inherited `@headlessui/react` and\n * `@heroicons/react` to ask someone a yes/no question. Focus containment and\n * scroll locking now come from `./focusTrap`; the icons are inline SVG. The\n * three dialogs' behaviour is otherwise unchanged, with two deliberate\n * exceptions noted below.\n *\n * FAIL-CLOSED. Each global starts as a function resolving to false/null, so a\n * `confirm()` with no provider mounted answers \"no\" rather than hanging or\n * throwing. A dialog nobody can see must never resolve true — that is the\n * difference between a no-op and a deletion.\n *\n * CONFIRMS QUEUE, they do not drop. A second `confirm()` while one is open\n * waits its turn and shows afterwards. Dropping it would silently resolve a\n * question the user was never asked, and the caller cannot tell that from a\n * genuine \"no\".\n */\nimport { createContext, useContext, useState, useCallback, useRef, useEffect } from 'react';\nimport Dialog from './Dialog';\nimport { splitAround, useShellStrings } from './strings';\n\ninterface ConfirmOptions {\n title?: string;\n message: string;\n confirmLabel?: string;\n cancelLabel?: string;\n variant?: 'danger' | 'warning' | 'info';\n}\n\ntype ConfirmFn = (options: ConfirmOptions | string) => Promise<boolean>;\ninterface PendingConfirm {\n id: number;\n options: ConfirmOptions;\n resolve: (value: boolean) => void;\n}\n\nconst ConfirmContext = createContext<ConfirmFn>(() => Promise.resolve(false));\n\nexport const useConfirm = () => useContext(ConfirmContext);\n\n// Global callable — works without a hook, usable from any click handler\nlet globalConfirmFn: ConfirmFn = () => Promise.resolve(false);\nexport const confirm = (opts: ConfirmOptions | string) => globalConfirmFn(opts);\n\ninterface DestructiveConfirmOptions {\n title?: string;\n message: string;\n /**\n * Require typing this word (case-sensitive) before the action is enabled —\n * for something genuinely irreversible at scale, like dropping a tenant.\n *\n * OPTIONAL since 4.18.0. Omit it for a plain two-button destructive confirm.\n * Type-to-confirm assumes a keyboard, and a touch device that has none (a\n * till, a warehouse scanner) cannot satisfy it at all — the dialog becomes an\n * unanswerable question. Ask for a word when the cost of a mis-tap is high\n * enough to justify making the user work; not by default.\n */\n confirmWord?: string;\n /** Label for the action button when there is no `confirmWord`. */\n confirmLabel?: string;\n cancelLabel?: string;\n variant?: 'danger' | 'warning';\n}\ntype DestructiveConfirmFn = (options: DestructiveConfirmOptions) => Promise<boolean>;\nlet globalDestructiveConfirmFn: DestructiveConfirmFn = () => Promise.resolve(false);\nexport const confirmDestructive = (opts: DestructiveConfirmOptions) => globalDestructiveConfirmFn(opts);\n\n// Prompt — windowed replacement for native window.prompt(). Resolves to the\n// trimmed string, or null if the user cancelled. Empty input counts as\n// cancel by default; pass `allowEmpty: true` to opt in.\ninterface PromptOptions {\n title?: string;\n message?: string;\n defaultValue?: string;\n placeholder?: string;\n confirmLabel?: string;\n cancelLabel?: string;\n allowEmpty?: boolean;\n}\ntype PromptFn = (options: PromptOptions | string) => Promise<string | null>;\nlet globalPromptFn: PromptFn = () => Promise.resolve(null);\nexport const prompt = (opts: PromptOptions | string) => globalPromptFn(opts);\n\nconst TONE: Record<'danger' | 'warning' | 'info', { icon: string; button: string }> = {\n danger: { icon: 'text-red-600 bg-red-100', button: 'bg-red-600 hover:bg-red-700 text-white' },\n warning: { icon: 'text-yellow-600 bg-yellow-100', button: 'bg-yellow-600 hover:bg-yellow-700 text-white' },\n info: { icon: 'text-blue-600 bg-blue-100', button: 'bg-blue-600 hover:bg-blue-700 text-white' },\n};\n\nconst CANCEL_BTN =\n 'bg-white text-gray-700 border border-gray-300 px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-50';\n\n/** Inline replacements for the two Heroicons this file used to import. */\nfunction WarningIcon() {\n return (\n <svg className=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5} aria-hidden=\"true\">\n <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\" />\n </svg>\n );\n}\n\nfunction CloseIcon() {\n return (\n <svg className=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5} aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n );\n}\n\nexport function ConfirmProvider({ children }: { children: React.ReactNode }) {\n const [open, setOpen] = useState(false);\n const [options, setOptions] = useState<ConfirmOptions>({ message: '' });\n const [confirmId, setConfirmId] = useState(0);\n const resolveRef = useRef<(value: boolean) => void>();\n const activeConfirmIdRef = useRef<number | null>(null);\n const nextConfirmIdRef = useRef(0);\n const confirmQueueRef = useRef<PendingConfirm[]>([]);\n\n // Cancel is the default focus target in every dialog here. The destructive\n // action sits on the RIGHT and is never what Enter or a stray keypress hits.\n const cancelRef = useRef<HTMLButtonElement>(null);\n const dCancelRef = useRef<HTMLButtonElement>(null);\n\n const showConfirm = useCallback((request: PendingConfirm) => {\n activeConfirmIdRef.current = request.id;\n resolveRef.current = request.resolve;\n setConfirmId(request.id);\n setOptions(request.options);\n setOpen(true);\n }, []);\n\n const confirmFn: ConfirmFn = useCallback((opts) => {\n const normalized = typeof opts === 'string' ? { message: opts } : opts;\n return new Promise<boolean>((resolve) => {\n const request = { id: ++nextConfirmIdRef.current, options: normalized, resolve };\n if (activeConfirmIdRef.current === null) showConfirm(request);\n else confirmQueueRef.current.push(request);\n });\n }, [showConfirm]);\n\n // Destructive confirm state\n const [dOpen, setDOpen] = useState(false);\n const [dOptions, setDOptions] = useState<DestructiveConfirmOptions>({ message: '' });\n const [dInput, setDInput] = useState('');\n const dResolveRef = useRef<(value: boolean) => void>();\n\n const destructiveConfirmFn: DestructiveConfirmFn = useCallback((opts) => {\n setDOptions(opts);\n setDInput('');\n setDOpen(true);\n return new Promise<boolean>((resolve) => {\n dResolveRef.current = resolve;\n });\n }, []);\n\n // Prompt state\n const [pOpen, setPOpen] = useState(false);\n const [pOptions, setPOptions] = useState<PromptOptions>({});\n const [pInput, setPInput] = useState('');\n const pResolveRef = useRef<(value: string | null) => void>();\n\n const promptFn: PromptFn = useCallback((opts) => {\n const normalized = typeof opts === 'string' ? { message: opts } : opts;\n setPOptions(normalized);\n setPInput(normalized.defaultValue ?? '');\n setPOpen(true);\n return new Promise<string | null>((resolve) => {\n pResolveRef.current = resolve;\n });\n }, []);\n\n useEffect(() => {\n globalConfirmFn = confirmFn;\n globalDestructiveConfirmFn = destructiveConfirmFn;\n globalPromptFn = promptFn;\n }, [confirmFn, destructiveConfirmFn, promptFn]);\n\n const handleClose = (id: number, result: boolean) => {\n // A callback captured by the previous dialog must never settle the next\n // queued request after the active request advances.\n if (activeConfirmIdRef.current !== id) return;\n const resolve = resolveRef.current;\n const next = confirmQueueRef.current.shift();\n if (next) showConfirm(next);\n else {\n activeConfirmIdRef.current = null;\n resolveRef.current = undefined;\n setOpen(false);\n }\n resolve?.(result);\n };\n\n const handleDClose = (result: boolean) => {\n setDOpen(false);\n setDInput('');\n dResolveRef.current?.(result);\n };\n\n const handlePClose = (commit: boolean) => {\n if (commit) {\n const trimmed = pInput.trim();\n if (!trimmed && !pOptions.allowEmpty) {\n // Empty + not opted-in to allow empty: treat Save as Cancel.\n setPOpen(false);\n pResolveRef.current?.(null);\n return;\n }\n setPOpen(false);\n pResolveRef.current?.(trimmed);\n } else {\n setPOpen(false);\n pResolveRef.current?.(null);\n }\n };\n\n // Escape is claimed by each Dialog itself now, and interceptors run most\n // recent first, so a stack of these dismisses top-down without the provider\n // hand-ordering them the way it used to.\n\n const variant = options.variant || (options.confirmLabel?.toLowerCase().includes('delete') || options.message.toLowerCase().includes('delete') ? 'danger' : 'info');\n const tone = TONE[variant];\n const dTone = TONE[dOptions.variant === 'warning' ? 'warning' : 'danger'];\n const dWord = dOptions.confirmWord;\n const dSatisfied = dWord == null || dInput === dWord;\n // Every default below is the catalog's; every option a caller passed still\n // wins, exactly as it did over the hardcoded English.\n const strings = useShellStrings().dialog;\n const [typeBefore, typeAfter] = splitAround(strings.typeToConfirm);\n\n return (\n <ConfirmContext.Provider value={confirmFn}>\n {children}\n\n <Dialog\n open={open}\n onClose={() => handleClose(confirmId, false)}\n initialFocus={cancelRef}\n footer={\n <>\n <button ref={cancelRef} type=\"button\" onClick={() => handleClose(confirmId, false)} className={CANCEL_BTN}>\n {options.cancelLabel || strings.cancel}\n </button>\n <button type=\"button\" onClick={() => handleClose(confirmId, true)}\n className={`px-4 py-2 text-sm font-medium rounded-lg ${tone.button}`}>\n {options.confirmLabel || strings.ok}\n </button>\n </>\n }\n >\n <div className=\"flex gap-4\">\n <div className={`shrink-0 w-10 h-10 rounded-full flex items-center justify-center ${tone.icon}`}>\n <WarningIcon />\n </div>\n <div className=\"flex-1\">\n <h2 className=\"text-base font-semibold text-gray-900\">{options.title || strings.confirmTitle}</h2>\n <p className=\"mt-2 text-sm text-gray-600\">{options.message}</p>\n </div>\n </div>\n </Dialog>\n\n {/* Destructive confirm — optionally gated on typing a word */}\n <Dialog\n open={dOpen}\n onClose={() => handleDClose(false)}\n // With a word to type, focus the input: that is the task. Without one,\n // focus Cancel, so the irreversible button is never one Enter away.\n initialFocus={dWord != null ? undefined : dCancelRef}\n footer={\n <>\n <button ref={dCancelRef} type=\"button\" onClick={() => handleDClose(false)} className={CANCEL_BTN}>\n {dOptions.cancelLabel || strings.destructiveCancel}\n </button>\n <button type=\"button\" onClick={() => handleDClose(true)} disabled={!dSatisfied}\n className={`px-4 py-2 text-sm font-medium rounded-lg disabled:opacity-40 ${dTone.button}`}>\n {dWord ?? dOptions.confirmLabel ?? strings.destructiveConfirm}\n </button>\n </>\n }\n >\n <button\n type=\"button\"\n onClick={() => handleDClose(false)}\n aria-label={strings.close}\n className=\"absolute right-4 top-4 rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600\"\n >\n <CloseIcon />\n </button>\n <div className=\"flex gap-4\">\n <div className={`shrink-0 w-10 h-10 rounded-full flex items-center justify-center ${dTone.icon}`}>\n <WarningIcon />\n </div>\n <div className=\"flex-1\">\n <h2 className=\"text-base font-semibold text-gray-900 pr-6\">{dOptions.title || strings.destructiveTitle}</h2>\n <p className=\"mt-2 text-sm text-gray-600\">{dOptions.message}</p>\n {dWord != null && (\n <>\n <p className=\"mt-3 text-sm text-gray-700\">\n {typeBefore}<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\">{dWord}</kbd>{typeAfter}\n </p>\n <input\n autoFocus\n type=\"text\"\n value={dInput}\n onChange={e => setDInput(e.target.value)}\n onKeyDown={e => { if (e.key === 'Enter' && dInput === dWord) handleDClose(true); }}\n placeholder={dWord}\n className=\"mt-2 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-red-500\"\n />\n </>\n )}\n </div>\n </div>\n </Dialog>\n\n {/* Prompt — windowed replacement for window.prompt() */}\n <Dialog\n open={pOpen}\n onClose={() => handlePClose(false)}\n title={pOptions.title || strings.promptTitle}\n footer={\n <>\n <button type=\"button\" onClick={() => handlePClose(false)} className={CANCEL_BTN}>\n {pOptions.cancelLabel || strings.cancel}\n </button>\n <button type=\"button\" onClick={() => handlePClose(true)}\n disabled={!pOptions.allowEmpty && !pInput.trim()}\n className=\"bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 text-sm font-medium rounded-lg disabled:opacity-40\">\n {pOptions.confirmLabel || strings.ok}\n </button>\n </>\n }\n >\n {pOptions.message && <p className=\"text-sm text-gray-600\">{pOptions.message}</p>}\n <input\n autoFocus\n type=\"text\"\n value={pInput}\n onChange={(e) => setPInput(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') handlePClose(true);\n else if (e.key === 'Escape') handlePClose(false);\n }}\n onFocus={(e) => e.target.select()}\n placeholder={pOptions.placeholder}\n className=\"mt-3 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 rosh-focus-ring focus-visible:ring-1 focus-visible:ring-[color:var(--rosh-focus-ring-accent)]\"\n />\n </Dialog>\n </ConfirmContext.Provider>\n );\n}\n"]}
|