efficient-shell 4.134.0 → 4.137.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +81 -7
  2. package/dist/{Browser-O3QZGUF2.js → Browser-ZNX4WFUH.js} +11 -11
  3. package/dist/{Browser-O3QZGUF2.js.map → Browser-ZNX4WFUH.js.map} +1 -1
  4. package/dist/{Calculator-L6N5X3KU.js → Calculator-6FGNKOXU.js} +10 -10
  5. package/dist/{Calculator-L6N5X3KU.js.map → Calculator-6FGNKOXU.js.map} +1 -1
  6. package/dist/ConfirmDialog-NPQMIGUR.js +4 -0
  7. package/dist/{ConfirmDialog-LYSSZSUD.js.map → ConfirmDialog-NPQMIGUR.js.map} +1 -1
  8. package/dist/{CurrencyConverter-64MKXB3D.js → CurrencyConverter-FF7EXRYL.js} +10 -10
  9. package/dist/{CurrencyConverter-64MKXB3D.js.map → CurrencyConverter-FF7EXRYL.js.map} +1 -1
  10. package/dist/{Documents-5WXXYCLI.js → Documents-H3R7XSBH.js} +12 -12
  11. package/dist/{Documents-5WXXYCLI.js.map → Documents-H3R7XSBH.js.map} +1 -1
  12. package/dist/{Files-AUURUUDN.js → Files-ZHJIAUC2.js} +15 -15
  13. package/dist/{Files-AUURUUDN.js.map → Files-ZHJIAUC2.js.map} +1 -1
  14. package/dist/{Notepad-GPXJVPB7.js → Notepad-VE7ISWRQ.js} +13 -13
  15. package/dist/{Notepad-GPXJVPB7.js.map → Notepad-VE7ISWRQ.js.map} +1 -1
  16. package/dist/PdfViewer-SQBFYAGW.js +5 -0
  17. package/dist/{PdfViewer-HKO6P4SR.js.map → PdfViewer-SQBFYAGW.js.map} +1 -1
  18. package/dist/{PomodoroTimer-4TQBWLP2.js → PomodoroTimer-IR3WREED.js} +12 -12
  19. package/dist/{PomodoroTimer-4TQBWLP2.js.map → PomodoroTimer-IR3WREED.js.map} +1 -1
  20. package/dist/{Preview-FOZYPKM2.js → Preview-DAQGRNRM.js} +14 -14
  21. package/dist/{Preview-FOZYPKM2.js.map → Preview-DAQGRNRM.js.map} +1 -1
  22. package/dist/{Sidebar-BPU7RCZP.js → Sidebar-YW6H4NDJ.js} +3 -3
  23. package/dist/{Sidebar-BPU7RCZP.js.map → Sidebar-YW6H4NDJ.js.map} +1 -1
  24. package/dist/{Spreadsheet-3C66BBBF.js → Spreadsheet-HOCPHJOC.js} +12 -12
  25. package/dist/{Spreadsheet-3C66BBBF.js.map → Spreadsheet-HOCPHJOC.js.map} +1 -1
  26. package/dist/{Stock-IF4IHYLU.js → Stock-LVRQPURG.js} +10 -10
  27. package/dist/{Stock-IF4IHYLU.js.map → Stock-LVRQPURG.js.map} +1 -1
  28. package/dist/{Weather-725PFR7T.js → Weather-LZQGVDJL.js} +10 -10
  29. package/dist/{Weather-725PFR7T.js.map → Weather-LZQGVDJL.js.map} +1 -1
  30. package/dist/{WorldClock-UU4NZIQ7.js → WorldClock-XPD7WOZT.js} +10 -10
  31. package/dist/{WorldClock-UU4NZIQ7.js.map → WorldClock-XPD7WOZT.js.map} +1 -1
  32. package/dist/apps/index.js +23 -23
  33. package/dist/brand.css +1 -1
  34. package/dist/{chunk-GIGV2ZRW.js → chunk-5RPTS3BT.js} +3 -3
  35. package/dist/{chunk-GIGV2ZRW.js.map → chunk-5RPTS3BT.js.map} +1 -1
  36. package/dist/{chunk-D276EMXS.js → chunk-6XJEJWNJ.js} +7 -3
  37. package/dist/chunk-6XJEJWNJ.js.map +1 -0
  38. package/dist/{chunk-IULP5BCD.js → chunk-COWYSHG2.js} +4 -4
  39. package/dist/{chunk-IULP5BCD.js.map → chunk-COWYSHG2.js.map} +1 -1
  40. package/dist/{chunk-R4OQIQYX.js → chunk-E7TB6WD2.js} +4 -4
  41. package/dist/{chunk-R4OQIQYX.js.map → chunk-E7TB6WD2.js.map} +1 -1
  42. package/dist/{chunk-X4Q6G253.js → chunk-FIHOGVUP.js} +3 -3
  43. package/dist/{chunk-X4Q6G253.js.map → chunk-FIHOGVUP.js.map} +1 -1
  44. package/dist/{chunk-2TPTHAKI.js → chunk-HUBJAUGG.js} +3 -3
  45. package/dist/{chunk-2TPTHAKI.js.map → chunk-HUBJAUGG.js.map} +1 -1
  46. package/dist/{chunk-W622KQTD.js → chunk-I2TAAJHS.js} +3 -3
  47. package/dist/{chunk-W622KQTD.js.map → chunk-I2TAAJHS.js.map} +1 -1
  48. package/dist/{chunk-Q3LT745N.js → chunk-ICTRCKUE.js} +7 -7
  49. package/dist/{chunk-Q3LT745N.js.map → chunk-ICTRCKUE.js.map} +1 -1
  50. package/dist/{chunk-QRL64J5F.js → chunk-JOPU6WPY.js} +7 -6
  51. package/dist/chunk-JOPU6WPY.js.map +1 -0
  52. package/dist/{chunk-PIGDNN5F.js → chunk-PVPPAWVM.js} +3 -3
  53. package/dist/{chunk-PIGDNN5F.js.map → chunk-PVPPAWVM.js.map} +1 -1
  54. package/dist/{chunk-ET6ID4LP.js → chunk-R7QGMK6N.js} +3 -3
  55. package/dist/{chunk-ET6ID4LP.js.map → chunk-R7QGMK6N.js.map} +1 -1
  56. package/dist/{chunk-4P44JXDS.js → chunk-UTYKLGGT.js} +3 -3
  57. package/dist/{chunk-4P44JXDS.js.map → chunk-UTYKLGGT.js.map} +1 -1
  58. package/dist/{chunk-T4VQHMG4.js → chunk-VNJJCH5A.js} +3 -3
  59. package/dist/{chunk-T4VQHMG4.js.map → chunk-VNJJCH5A.js.map} +1 -1
  60. package/dist/{chunk-QSKCQUZE.js → chunk-XJHZWSKV.js} +3 -3
  61. package/dist/{chunk-QSKCQUZE.js.map → chunk-XJHZWSKV.js.map} +1 -1
  62. package/dist/{chunk-SHQM5CK3.js → chunk-Y3GYJ7YR.js} +4 -4
  63. package/dist/{chunk-SHQM5CK3.js.map → chunk-Y3GYJ7YR.js.map} +1 -1
  64. package/dist/{chunk-OLJDJYSU.js → chunk-YA4EE7ST.js} +574 -145
  65. package/dist/chunk-YA4EE7ST.js.map +1 -0
  66. package/dist/{chunk-QAGQRNKI.js → chunk-YDHXWCCJ.js} +3 -3
  67. package/dist/{chunk-QAGQRNKI.js.map → chunk-YDHXWCCJ.js.map} +1 -1
  68. package/dist/{chunk-72CQWN7O.js → chunk-YE6GUDGP.js} +3 -2
  69. package/dist/chunk-YE6GUDGP.js.map +1 -0
  70. package/dist/chunk-ZS7PMGUW.js +7 -0
  71. package/dist/{chunk-Q2MJVSJT.js.map → chunk-ZS7PMGUW.js.map} +1 -1
  72. package/dist/file-intake/index.js +2 -2
  73. package/dist/{index-DZ7E7bCl.d.ts → index-DVI9DMSu.d.ts} +189 -18
  74. package/dist/index.d.ts +2 -2
  75. package/dist/index.js +31 -31
  76. package/dist/page-tabs/index.js +5 -5
  77. package/dist/ui/index.d.ts +1 -1
  78. package/dist/ui/index.js +13 -13
  79. package/dist/ui.css +78 -10
  80. package/package.json +1 -1
  81. package/dist/ConfirmDialog-LYSSZSUD.js +0 -4
  82. package/dist/PdfViewer-HKO6P4SR.js +0 -5
  83. package/dist/chunk-72CQWN7O.js.map +0 -1
  84. package/dist/chunk-D276EMXS.js.map +0 -1
  85. package/dist/chunk-OLJDJYSU.js.map +0 -1
  86. package/dist/chunk-Q2MJVSJT.js +0 -7
  87. package/dist/chunk-QRL64J5F.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { onOverlayOpen, Z_LAYERS, registerModalEscapeInterceptor } from './chunk-W622KQTD.js';
2
- import { glassStyle, GLASS_DIVIDER } from './chunk-D276EMXS.js';
1
+ import { onOverlayOpen, Z_LAYERS, registerModalEscapeInterceptor } from './chunk-I2TAAJHS.js';
2
+ import { glassStyle, GLASS_DIVIDER } from './chunk-6XJEJWNJ.js';
3
3
  import { useHydrated, useBrowserLayoutEffect } from './chunk-IRWSRJ4K.js';
4
4
  import { createContext, forwardRef, useContext, useRef, useId, useState, useEffect, useMemo, useCallback } from 'react';
5
5
  import { createPortal } from 'react-dom';
@@ -483,8 +483,9 @@ function PopupMenuLabel({ children }) {
483
483
  return /* @__PURE__ */ jsx(
484
484
  "div",
485
485
  {
486
- className: "text-[10px] font-medium text-gray-400 uppercase tracking-wider",
486
+ className: "text-xs font-medium uppercase tracking-wide",
487
487
  style: {
488
+ color: "var(--ink-muted)",
488
489
  paddingLeft: "var(--menu-padding-x, 1rem)",
489
490
  paddingRight: "var(--menu-padding-x, 1rem)",
490
491
  paddingTop: labelPadY,
@@ -498,6 +499,6 @@ function MenuIcon({ d, className = "h-4 w-4 text-gray-400" }) {
498
499
  return /* @__PURE__ */ jsx("svg", { className, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d }) });
499
500
  }
500
501
 
501
- export { MenuIcon, NATIVE_MENU_ATTR, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, PopupSubmenu, clampMenuTop, closeMenuBelow, describeContextTarget, keepsNativeMenu, menuPanelLeft, openMenuLevel, resolveMenuLevels };
502
- //# sourceMappingURL=chunk-QRL64J5F.js.map
503
- //# sourceMappingURL=chunk-QRL64J5F.js.map
502
+ export { MenuIcon, NATIVE_MENU_ATTR, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, PopupSubmenu, clampMenuTop, closeMenuBelow, describeContextTarget, itemLook, keepsNativeMenu, menuPanelLeft, openMenuLevel, resolveMenuLevels };
503
+ //# sourceMappingURL=chunk-JOPU6WPY.js.map
504
+ //# sourceMappingURL=chunk-JOPU6WPY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shell/contextMenuTarget.ts","../src/shell/menuPath.ts","../src/shell/PopupMenu.tsx"],"names":["PopupMenuItem"],"mappings":"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","file":"chunk-JOPU6WPY.js","sourcesContent":["/**\n * What the shell's global context menu should offer for a given right-click.\n *\n * Kept free of React (and of the DOM beyond the element it is handed) so the\n * two rules that matter can be tested directly: which targets keep the\n * browser's own menu, and which items a click produces.\n */\n\n/** Input types that put a text caret on screen. Everything else — a checkbox,\n * a range, a color swatch, a file button — has no spellcheck and no Paste,\n * so there is nothing in the browser's menu worth keeping for it. */\nconst TEXT_INPUT_TYPES = new Set([\n 'text', 'search', 'url', 'tel', 'email', 'password', 'number',\n 'date', 'datetime-local', 'month', 'week', 'time',\n]);\n\n/** Elements whose browser menu does what a page cannot: Save image as and Copy\n * image — the picture itself, not its address — on an image or a canvas, and\n * the playback controls on video and audio. */\nconst NATIVE_MEDIA = 'img, picture, canvas, video, audio';\n\n/** Opt-out attribute: anything inside `[data-native-context-menu]` keeps the\n * browser's menu. For surfaces the shell has no business re-skinning — an\n * embedded viewer, a third-party widget. */\nexport const NATIVE_MENU_ATTR = 'data-native-context-menu';\n\nfunction closest(el: Element | null, selector: string): Element | null {\n if (!el || typeof (el as Element).closest !== 'function') return null;\n return el.closest(selector);\n}\n\n/**\n * Does this target keep the browser's native menu?\n *\n * Text inputs, textareas and contenteditable do, deliberately: the browser's\n * spellcheck suggestions and \"Add to dictionary\" cannot be rebuilt by a web\n * page, and a Paste item of our own would need a clipboard permission prompt.\n * Images, canvases and media do for the same reason — Save / Copy image and\n * the playback controls are the browser's. The shell menu is for everywhere\n * else.\n */\nexport function keepsNativeMenu(target: EventTarget | null): boolean {\n const el = target as Element | null;\n if (!el || typeof (el as Element).closest !== 'function') return false;\n\n if (closest(el, `[${NATIVE_MENU_ATTR}]`)) return true;\n if (closest(el, 'textarea')) return true;\n if (closest(el, NATIVE_MEDIA)) return true;\n\n const input = closest(el, 'input') as HTMLInputElement | null;\n if (input) {\n // The property, not the attribute: the browser normalizes it, so a type it\n // does not recognise (`type=\"currency\"`, a typo) reads as the text box it\n // actually renders.\n const type = (input.type || 'text').toLowerCase();\n if (TEXT_INPUT_TYPES.has(type)) return true;\n }\n\n // `isContentEditable` is the property to ask, but jsdom does not implement\n // it, so walk the attribute — which is also what the nesting rules are\n // written in (`contenteditable=\"false\"` re-enables our menu inside an\n // editable region).\n const editable = closest(el, '[contenteditable]');\n if (editable) {\n const value = (editable.getAttribute('contenteditable') || '').toLowerCase();\n if (value === '' || value === 'true' || value === 'plaintext-only') return true;\n }\n\n return false;\n}\n\nexport type ShellContextKind = 'selection' | 'link' | 'default';\n\nexport interface ShellContextTarget {\n /** The most specific thing under the pointer — what the menu leads with. */\n kind: ShellContextKind;\n /** Trimmed selection text, when something is selected. */\n selectionText?: string;\n /** Absolute href of the nearest enclosing link. */\n linkUrl?: string;\n}\n\n/** Schemes worth an Open / Copy link address. A `javascript:` URL is a button\n * wearing a link's clothes, and `href=\"#\"` is the same trick. */\nconst LINK_SCHEMES = new Set(['http:', 'https:', 'mailto:', 'tel:', 'blob:']);\n\nfunction linkAddress(href: string | null | undefined, base?: string): string | undefined {\n if (!href || href === '#') return undefined;\n try {\n // `document.baseURI`, not `location.href`: a page with a `<base>` resolves\n // this the way its own links do.\n const url = new URL(href, base ?? (typeof document !== 'undefined' ? document.baseURI : undefined));\n return LINK_SCHEMES.has(url.protocol) ? url.href : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Describe a right-click: what was under the pointer, and what is selected.\n *\n * A selection inside a link reports both, so the menu can offer both sections\n * the way a browser's does; `kind` names whichever the menu should lead with.\n */\nexport function describeContextTarget(\n target: EventTarget | null,\n selectionText?: string | null,\n baseUrl?: string,\n): ShellContextTarget {\n const el = target as Element | null;\n const link = closest(el, 'a[href]');\n\n const text = (selectionText || '').trim();\n const linkUrl = linkAddress(link?.getAttribute('href'), baseUrl);\n\n const kind: ShellContextKind = text ? 'selection' : linkUrl ? 'link' : 'default';\n\n return {\n kind,\n ...(text ? { selectionText: text } : {}),\n ...(linkUrl ? { linkUrl } : {}),\n };\n}\n","/**\n * The open-submenu path of a hover menu — the whole state machine <StartMenu>\n * uses to decide which flyouts are open, at any depth.\n *\n * ## Why this exists\n *\n * The menu used to have TWO mechanisms: one that opened a section's flyout\n * (`hoveredSection` + its timer) and a second, separately written one that\n * opened a flyout under an item inside that flyout (`hoveredChild` + its own\n * timer). They looked alike but behaved differently, and the second one had\n * the bugs — a stale close-timer from a sibling row could fire *after* a\n * submenu had opened and shut it under the cursor, which is what \"the next\n * level is slow, or sometimes doesn't open at all\" actually was. There was\n * also no third mechanism, so a 4th level simply never rendered.\n *\n * One path, one set of rules, any depth. Panels are numbered from the root\n * menu: panel 0 is the menu itself, and `path[i]` is the row that opened panel\n * `i + 1`. So `path.length` is the number of open flyouts, and closing\n * everything under panel `d` is just truncating the path to `d`.\n *\n * All of it is pure: hover geometry comes in as numbers, so the rules can be\n * tested without a DOM.\n */\n\n/** One open flyout: which row opened it, and where it goes. */\nexport interface MenuAnchor {\n /** Identity of the row that opened this panel — a section label or an item\n * route. Also what marks that row as the open one. */\n key: string;\n /** Viewport Y of the opening row's centre; the panel is centred on it. */\n y: number;\n /** Viewport X of the panel's left edge. Resolved when the row is hovered,\n * because it depends on where the panel that owns the row ended up. */\n left: number;\n /** Whether this panel had to open to the LEFT of the one that owns its row.\n * Carried so the next level down can keep going the same way. */\n flipped: boolean;\n}\n\n/**\n * Open `anchor` as the submenu of a row in panel `depth`, closing anything\n * deeper (the previous branch the user was down).\n *\n * Returns the SAME array when that panel is already open for that key and\n * nothing deeper is open, so a repeated `mouseenter` on a row the user is\n * already on can't re-render the menu — and can't reset the panel's open\n * animation.\n */\nexport function openMenuLevel(path: MenuAnchor[], depth: number, anchor: MenuAnchor): MenuAnchor[] {\n if (path.length === depth + 1 && path[depth].key === anchor.key) return path;\n return [...path.slice(0, depth), anchor];\n}\n\n/**\n * Close panel `depth + 1` and everything under it — what hovering a row with\n * no submenu, or leaving a panel, eventually leads to.\n *\n * Also identity-stable: nothing to close means the same array back.\n */\nexport function closeMenuBelow(path: MenuAnchor[], depth: number): MenuAnchor[] {\n const keep = Math.max(0, depth);\n return path.length > keep ? path.slice(0, keep) : path;\n}\n\n/**\n * The items each open panel shows: `[0]` is the flyout the root row opened,\n * `[1]` its child, and so on for as long as the path keeps resolving.\n *\n * Stops early rather than rendering an empty panel, so a path left over from a\n * branch that has since disappeared (a permission change, nav data swapped\n * under the menu) degrades to the levels that still exist.\n */\nexport function resolveMenuLevels<T extends { to: string }>(\n rootItems: T[],\n path: MenuAnchor[],\n childrenOf: (item: T) => T[],\n): T[][] {\n if (path.length === 0 || rootItems.length === 0) return [];\n const levels: T[][] = [rootItems];\n for (let i = 1; i < path.length; i++) {\n const parent = levels[i - 1].find(item => item.to === path[i].key);\n const kids = parent ? childrenOf(parent) : [];\n if (kids.length === 0) break;\n levels.push(kids);\n }\n return levels;\n}\n\n/**\n * Where a panel of height `height` sits when centred on `anchorY`, kept inside\n * the usable vertical span (`minTop`..`maxBottom` — the viewport minus the\n * taskbar edge and a gutter).\n *\n * A panel taller than the span is pinned to the top of it and scrolls; that is\n * why the height is clamped before it is centred, and not after.\n */\nexport function clampMenuTop(anchorY: number, height: number, minTop: number, maxBottom: number): number {\n const span = Math.max(0, maxBottom - minTop);\n const h = Math.min(height, span);\n const top = anchorY - h / 2;\n if (top < minTop) return minTop;\n if (top + h > maxBottom) return Math.max(minTop, maxBottom - h);\n return top;\n}\n\n/**\n * Where a panel of width `width` sits horizontally: to the right of the panel\n * that owns the hovered row, or flipped to its left when there isn't room.\n *\n * The flip is what makes depth open-ended. Every level costs another panel\n * width, so a deep enough branch always runs out of screen — and a menu opened\n * from a right-hand taskbar runs out on the very first one.\n *\n * `preferLeft` keeps a branch that has already turned around going the same\n * way. Without it the levels ping-pong: one flips left over its grandparent,\n * the next finds room on the right again and lands back on top of its\n * grandparent, and the chain folds into two stacks of panels.\n */\nexport function menuPanelLeft(\n ownerLeft: number,\n ownerRight: number,\n width: number,\n viewportW: number,\n gap: number,\n preferLeft = false,\n margin = 8,\n): { left: number; flipped: boolean } {\n const onRight = ownerRight + gap;\n const onLeft = ownerLeft - gap - width;\n const rightFits = onRight + width <= viewportW - margin;\n const leftFits = onLeft >= margin;\n if (preferLeft && leftFits) return { left: onLeft, flipped: true };\n if (rightFits) return { left: onRight, flipped: false };\n if (leftFits) return { left: onLeft, flipped: true };\n // Neither side fits: hug the edge the panel was heading for rather than\n // hang off the screen.\n return preferLeft\n ? { left: margin, flipped: true }\n : { left: Math.max(margin, viewportW - margin - width), flipped: false };\n}\n","import {\n createContext, forwardRef, useCallback, useContext, useEffect, useId, useMemo, useRef, useState,\n type ReactNode, type CSSProperties,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { glassStyle, GLASS_DIVIDER } from '../utils/glass';\nimport { keepsNativeMenu } from './contextMenuTarget';\nimport { registerModalEscapeInterceptor } from './escapeInterceptors';\nimport { onOverlayOpen } from './overlayEvents';\nimport { useBrowserLayoutEffect, useHydrated } from './hydration';\nimport { Z_LAYERS } from './zLayers';\nimport { clampMenuTop, closeMenuBelow, menuPanelLeft, openMenuLevel, type MenuAnchor } from './menuPath';\n\n/**\n * Unified popup menu component — used for all context menus, dropdowns, and flyouts.\n * Reads --menu-density CSS variable: 'tight', 'normal' (default), or 'large'.\n */\n\nexport interface PopupMenuProps {\n children: ReactNode;\n style?: CSSProperties;\n className?: string;\n onClose?: () => void;\n minWidth?: number;\n /** Render into document.body instead of in place. Required when the menu\n * is opened from INSIDE a window: the window panel is a transformed /\n * backdrop-filtered `overflow-hidden` container, which both re-anchors\n * `position: fixed` descendants to itself and clips them — a menu\n * positioned at viewport coordinates ends up offset or invisible.\n * Portaling restores true viewport positioning. */\n portal?: boolean;\n}\n\n/** Guarded like `glassStyle`'s reads: a server has no stylesheet to ask, and a\n * menu rendered there takes the density the variable defaults to. */\nfunction getDensity(): 'tight' | 'normal' | 'large' {\n try {\n return (getComputedStyle(document.documentElement).getPropertyValue('--menu-density')?.trim() as any) || 'normal';\n } catch {\n return 'normal';\n }\n}\n\n/** Grace period before a submenu closes, so the pointer can cut the corner\n * across sibling rows on its way into the panel. The Start menu's value. */\nconst SUBMENU_CLOSE_DELAY = 200;\n/** How long the pointer rests on a submenu row before it opens. Short enough\n * to feel immediate, long enough that sweeping down a menu past the row does\n * not flash its panel open. */\nconst SUBMENU_OPEN_DELAY = 120;\n/** Gap between a submenu and the menu it opened from. */\nconst SUBMENU_GAP = 4;\n\n/**\n * What every submenu under one `PopupMenu` shares.\n *\n * The open submenus are ONE path for the whole tree — the same state machine\n * the Start menu runs (`menuPath.ts`): `path[d]` is the submenu open from a row\n * in panel `d`, and there is one close timer. That is what makes sibling\n * submenus exclusive, and it is what stops a stale timer from a row the\n * pointer merely passed over from closing a submenu that has since opened —\n * the bug the Start menu had while it ran a timer per level.\n */\ninterface MenuTree {\n /** Marks every panel of this tree, so a click inside a portalled submenu is\n * not \"outside\" the menu. */\n id: string;\n /** 0 for rows in the root menu, 1 inside its submenu, and so on. */\n depth: number;\n path: MenuAnchor[];\n open: (depth: number, anchor: MenuAnchor) => void;\n /** Close the submenus under panel `depth`, after the grace period. */\n scheduleClose: (depth: number) => void;\n /** Close the submenus under panel `depth` now. */\n closeBelow: (depth: number) => void;\n cancelClose: () => void;\n /** Close every panel — an item inside a submenu was chosen. */\n closeTree: () => void;\n /** The root panel, for measuring what it is layered at. */\n root: React.RefObject<HTMLDivElement | null>;\n}\n\nconst MenuTreeContext = createContext<MenuTree | null>(null);\n\n/** Container for a popup menu — auto-clamps to stay within viewport */\nexport function PopupMenu({ children, style, className = '', onClose, minWidth = 180, portal = false }: PopupMenuProps) {\n const ref = useRef<HTMLDivElement>(null);\n const treeId = useId();\n const [path, setPath] = useState<MenuAnchor[]>([]);\n const closeTimer = useRef<ReturnType<typeof setTimeout>>();\n useEffect(() => () => clearTimeout(closeTimer.current), []);\n // A portal has no place in server HTML — there is no <body> to put it in —\n // so a portalled menu that is open on the first render waits for hydration\n // to end, and both sides agree that it is not there yet.\n const hydrated = useHydrated();\n\n const tree = useMemo<MenuTree>(() => {\n const cancelClose = () => clearTimeout(closeTimer.current);\n return {\n id: treeId,\n depth: 0,\n path,\n open: (depth, anchor) => { cancelClose(); setPath(prev => openMenuLevel(prev, depth, anchor)); },\n scheduleClose: depth => {\n cancelClose();\n closeTimer.current = setTimeout(() => setPath(prev => closeMenuBelow(prev, depth)), SUBMENU_CLOSE_DELAY);\n },\n closeBelow: depth => { cancelClose(); setPath(prev => closeMenuBelow(prev, depth)); },\n cancelClose,\n closeTree: () => { cancelClose(); setPath([]); onClose?.(); },\n root: ref,\n };\n }, [treeId, path, onClose]);\n\n useEffect(() => {\n if (!onClose) return;\n const handleClick = (e: PointerEvent | MouseEvent) => {\n if ((e.target as HTMLElement).closest('[data-menu-toggle]')) return;\n // A submenu is portalled to <body>, so it is outside `ref` in the DOM\n // while still being part of this menu.\n if ((e.target as HTMLElement).closest?.(`[data-popup-menu-tree=\"${treeId}\"]`)) return;\n if (ref.current && !ref.current.contains(e.target as Node)) onClose();\n };\n const handleKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };\n window.addEventListener('pointerdown', handleClick);\n window.addEventListener('keydown', handleKey);\n // An overlay opening (⌘K's palette, a Dialog) is an outside interaction\n // the pointer listener never sees; left open, the menu would sit over the\n // overlay's backdrop or behind it (see `overlayEvents.ts`).\n const offOverlay = onOverlayOpen(onClose);\n return () => {\n window.removeEventListener('pointerdown', handleClick);\n window.removeEventListener('keydown', handleKey);\n offOverlay();\n };\n }, [onClose, treeId]);\n\n // After render, clamp position to viewport boundaries\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n requestAnimationFrame(() => {\n const rect = el.getBoundingClientRect();\n const margin = 8;\n // Clamp right edge\n if (rect.right > window.innerWidth - margin) {\n const overflow = rect.right - window.innerWidth + margin;\n if (el.style.left) {\n el.style.left = `${parseFloat(el.style.left) - overflow}px`;\n } else if (!el.style.right) {\n el.style.left = `${rect.left - overflow}px`;\n }\n }\n // Clamp left edge\n if (rect.left < margin) {\n if (el.style.left) {\n el.style.left = `${margin}px`;\n } else if (el.style.right) {\n el.style.right = `${window.innerWidth - rect.width - margin}px`;\n }\n }\n // Clamp bottom edge\n if (rect.bottom > window.innerHeight - margin) {\n const overflow = rect.bottom - window.innerHeight + margin;\n if (el.style.top) {\n el.style.top = `${parseFloat(el.style.top) - overflow}px`;\n } else if (el.style.bottom) {\n // Already anchored to bottom, just clamp\n el.style.bottom = `${margin}px`;\n } else {\n el.style.top = `${rect.top - overflow}px`;\n }\n }\n // Clamp top edge\n if (rect.top < margin) {\n if (el.style.top) {\n el.style.top = `${margin}px`;\n } else if (el.style.bottom) {\n el.style.bottom = `${window.innerHeight - rect.height - margin}px`;\n }\n }\n });\n });\n\n const density = getDensity();\n const ownsZ = /(^|[\\s:])!?z-/.test(className);\n\n // A right-click on an open menu is not a request for another menu. Claiming\n // it stops the shell-wide `ShellContextMenu` (which stands down on a\n // prevented event) and the browser's from opening on top of this one. A text\n // field inside a menu still keeps the browser's, as it does everywhere else.\n const menu = (\n <div ref={ref}\n data-popup-menu-tree={treeId}\n className={`fixed rounded-2xl ${density === 'tight' ? 'py-1' : density === 'large' ? 'py-2' : 'py-1.5'} ${className}`}\n onContextMenu={e => { if (!keepsNativeMenu(e.target)) e.preventDefault(); }}\n // The menu layer is inline now, and an inline z-index beats any class —\n // so a caller that layered its menu with a `z-…` class keeps its own.\n // `popup-in` (and the submenus' keyframes) are declared in ui.css: a\n // `<style>` element here was refused by a strict style-src.\n style={{ ...(ownsZ ? {} : { zIndex: Z_LAYERS.menu }), minWidth, animation: 'popup-in 0.12s ease-out', ...glassStyle(), ...style }}>\n <MenuTreeContext.Provider value={tree}>{children}</MenuTreeContext.Provider>\n </div>\n );\n\n if (!portal) return menu;\n return hydrated ? createPortal(menu, document.body) : null;\n}\n\n/** A clickable menu item */\nexport interface PopupMenuItemProps {\n onClick?: () => void;\n children: ReactNode;\n className?: string;\n danger?: boolean;\n disabled?: boolean;\n /**\n * The menu semantics, when this item is part of one.\n *\n * A context menu on the desktop is a list of buttons and needs none of this;\n * a dropdown attached to a trigger is a `menu` whose items are `menuitem`s\n * with one tab stop between them. Both draw the same item, so the styling\n * lives here rather than being copied into whichever one is newer.\n */\n role?: 'menuitem';\n tabIndex?: number;\n id?: string;\n onMouseEnter?: () => void;\n}\n\n/**\n * `forwardRef`, not a `ref` prop.\n *\n * Taking `ref` as an ordinary prop is React 19 only. This package's peer range\n * is `react: \">=18\"`, and React 18 strips `ref` from a function component's\n * props and warns — so on an 18 consumer the ref would silently never attach.\n * `DropdownMenu` once focused its items through these refs; with them\n * null its arrow keys moved `tabIndex` while DOM focus stayed put, a\n * keyboard-dead menu with no error. A caller's ref still has to work.\n */\n/** The row look shared by an item and a submenu's row — and by `DropdownMenu`'s\n * rows, which are links and radios as well as buttons, so cannot all be a\n * `PopupMenuItem`. Exported for that sibling only; `ui/kit.ts` does not\n * re-export it. */\nexport function itemLook(danger: boolean | undefined, disabled: boolean | undefined, className: string, highlighted = false) {\n const density = getDensity();\n // Vertical gap between items. `normal` sits a little tighter than the raw\n // size padding; `large` adds a bit more room. Floored at the tight value so\n // the reduced `normal` never drops below `tight` at the smallest menu size.\n const itemPadY = density === 'tight' ? '0.25rem'\n : density === 'large' ? '0.6rem'\n : 'max(0.25rem, calc(var(--menu-padding-y, 0.5rem) - 0.1rem))';\n const tone = danger ? 'text-red-600 hover:bg-red-50'\n : highlighted ? 'bg-blue-50 text-blue-700'\n : 'text-gray-700 hover:bg-blue-50 hover:text-blue-700';\n return {\n className: `w-full flex items-center gap-2 text-left transition-colors rounded-lg mx-auto\n ${tone}\n ${disabled ? 'opacity-40 cursor-not-allowed' : ''}\n ${className}`,\n style: {\n width: 'calc(100% - 8px)',\n marginLeft: 4,\n marginRight: 4,\n fontSize: 'var(--menu-font-size, 14px)',\n paddingLeft: 'var(--menu-padding-x, 1rem)',\n paddingRight: 'var(--menu-padding-x, 1rem)',\n paddingTop: itemPadY,\n paddingBottom: itemPadY,\n } satisfies CSSProperties,\n };\n}\n\nexport const PopupMenuItem = forwardRef<HTMLButtonElement, PopupMenuItemProps>(function PopupMenuItem({\n onClick, children, className = '', danger, disabled,\n role, tabIndex, id, onMouseEnter,\n}, ref) {\n const tree = useContext(MenuTreeContext);\n const look = itemLook(danger, disabled, className);\n return (\n <button\n ref={ref}\n id={id}\n role={role}\n tabIndex={tabIndex}\n onClick={() => {\n onClick?.();\n // Inside a submenu, choosing an item is the end of the whole menu —\n // not just the panel it sat in. A root item keeps its old contract:\n // the caller's own onClick decides.\n if (tree && tree.depth > 0) tree.closeTree();\n }}\n onMouseEnter={() => {\n // Resting on a plain row retires any submenu open beside it, after the\n // same grace the Start menu gives.\n tree?.scheduleClose(tree.depth);\n onMouseEnter?.();\n }}\n disabled={disabled}\n className={look.className}\n style={look.style}\n >\n {children}\n </button>\n );\n});\n\nexport interface PopupSubmenuProps {\n /** The row's text. */\n label: ReactNode;\n /** Drawn before the label, as in a `PopupMenuItem`. */\n icon?: ReactNode;\n /** The submenu's contents — `PopupMenuItem`, `PopupMenuLabel`,\n * `PopupMenuDivider`, or another `PopupSubmenu`. */\n children: ReactNode;\n disabled?: boolean;\n className?: string;\n /** The submenu panel's minimum width. */\n minWidth?: number;\n /** The tallest the panel may grow before its items scroll — a long target\n * list, say `'min(60vh, 420px)'`. Never taller than the viewport less its\n * 8px gutters, whatever is passed. */\n maxHeight?: number | string;\n}\n\nconst chevron = (\n <svg aria-hidden className=\"h-3.5 w-3.5 shrink-0 ml-auto text-gray-500\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" />\n </svg>\n);\n\n/** The z-index a panel must beat: the outermost stacking level `el` sits in\n * at <body>, since a portalled submenu is compared against that, not against\n * whatever the root menu is nested inside. */\nfunction layerOf(el: HTMLElement | null): number {\n let z: number = Z_LAYERS.menu;\n for (let node = el; node && node !== document.body; node = node.parentElement) {\n const value = parseInt(getComputedStyle(node).zIndex, 10);\n if (!Number.isNaN(value)) z = value;\n }\n return z;\n}\n\n/** Enabled items of a panel, in order — what the arrow keys walk. */\nfunction focusables(panel: HTMLElement | null): HTMLElement[] {\n if (!panel) return [];\n return [...panel.querySelectorAll<HTMLElement>('button:not([disabled])')]\n .filter(b => b.closest('[data-popup-submenu-panel]') === panel);\n}\n\n/**\n * A row in a `PopupMenu` that opens a nested menu.\n *\n * Opens on hover after a short rest, and at once on click, Enter, Space or\n * ArrowRight (which also move focus to its first item). ArrowLeft and Escape\n * close it and give focus back to the row. It opens to the right of the menu,\n * or to the left when there is no room, and moves up when it would run off the\n * bottom — the Start menu's placement rules, from `menuPath.ts`. Choosing an\n * item inside closes the whole menu (the root `PopupMenu`'s `onClose`).\n */\nexport function PopupSubmenu({ label, icon, children, disabled, className = '', minWidth = 180, maxHeight }: PopupSubmenuProps) {\n const tree = useContext(MenuTreeContext);\n const key = useId();\n const rowRef = useRef<HTMLButtonElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n const openTimer = useRef<ReturnType<typeof setTimeout>>();\n /** Whether the current opening came from the keyboard — then focus moves in. */\n const focusOnOpen = useRef(false);\n const panelId = `${key}-menu`;\n useEffect(() => () => clearTimeout(openTimer.current), []);\n\n const depth = tree?.depth ?? 0;\n const anchor = tree?.path[depth];\n const isOpen = !!anchor && anchor.key === key && !disabled;\n\n const openNow = useCallback((withFocus: boolean) => {\n if (!tree || disabled || !rowRef.current) return;\n clearTimeout(openTimer.current);\n focusOnOpen.current = withFocus;\n const row = rowRef.current.getBoundingClientRect();\n const owner = (rowRef.current.closest('[data-popup-submenu-panel]')\n ?? rowRef.current.closest('[data-popup-menu-tree]')) as HTMLElement | null;\n const ownerRect = owner?.getBoundingClientRect() ?? row;\n const preferLeft = depth > 0 && (tree.path[depth - 1]?.flipped ?? false);\n const place = menuPanelLeft(ownerRect.left, ownerRect.right, minWidth, window.innerWidth, SUBMENU_GAP, preferLeft);\n tree.open(depth, { key, y: row.top, ...place });\n }, [tree, disabled, depth, key, minWidth]);\n\n const closeSelf = (restoreFocus: boolean) => {\n tree?.closeBelow(depth);\n if (restoreFocus) rowRef.current?.focus();\n };\n\n // Escape through the shell's interceptor seam: inside a window, `Modal`\n // takes Escape in the capture phase and would close the whole window.\n useEffect(() => {\n if (!isOpen) return;\n return registerModalEscapeInterceptor(e => {\n if (e.key !== 'Escape') return false;\n // Registration order cannot say which submenu is deepest: React runs a\n // parent's effects after its children's, so once both re-render the\n // outer submenu is the newest registrant and is asked first. Defer to\n // the open path instead — a deeper level open under this one takes it.\n if (tree && tree.path.length > depth + 1) return false;\n closeSelf(true);\n return true;\n });\n });\n\n // Place the panel against its real size before paint — the estimate used\n // `minWidth` and the row's top — then move focus in if a key opened it.\n useBrowserLayoutEffect(() => {\n if (!isOpen || !anchor) return;\n const place = () => {\n const panel = panelRef.current;\n if (!panel || !rowRef.current) return;\n const margin = 8;\n const owner = (rowRef.current.closest('[data-popup-submenu-panel]')\n ?? rowRef.current.closest('[data-popup-menu-tree]')) as HTMLElement | null;\n const ownerRect = owner?.getBoundingClientRect() ?? rowRef.current.getBoundingClientRect();\n const preferLeft = depth > 0 && (tree?.path[depth - 1]?.flipped ?? false);\n const width = panel.offsetWidth || minWidth;\n const spot = menuPanelLeft(ownerRect.left, ownerRect.right, width, window.innerWidth, SUBMENU_GAP, preferLeft, margin);\n const height = panel.offsetHeight;\n // Top-aligned with the row: centring the clamp on `row top + half the\n // panel` gives exactly that, and still moves the panel up at the bottom.\n const rowTop = rowRef.current.getBoundingClientRect().top;\n const top = clampMenuTop(rowTop + height / 2, height, margin, window.innerHeight - margin);\n panel.style.left = `${spot.left}px`;\n panel.style.top = `${top}px`;\n panel.dataset.flipped = String(spot.flipped);\n };\n place();\n // Again after the root menu's own viewport clamp, which runs in a frame\n // AFTER this effect: on a re-render it can still move the root a few\n // pixels, and the submenu has to follow the menu it hangs off.\n let second = 0;\n const first = requestAnimationFrame(() => { second = requestAnimationFrame(place); });\n return () => { cancelAnimationFrame(first); cancelAnimationFrame(second); };\n });\n\n useEffect(() => {\n if (!isOpen || !focusOnOpen.current) return;\n focusOnOpen.current = false;\n focusables(panelRef.current)[0]?.focus();\n }, [isOpen]);\n\n const childTree = useMemo<MenuTree | null>(() => tree && { ...tree, depth: depth + 1 }, [tree, depth]);\n\n if (!tree) throw new Error('PopupSubmenu must be rendered inside a PopupMenu');\n\n const onRowKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {\n if (e.key === 'ArrowRight') {\n e.preventDefault();\n e.stopPropagation();\n openNow(true);\n }\n };\n\n const onPanelKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n // React bubbles through portals, so a key pressed in a deeper submenu\n // reaches this handler too. That panel handled it already.\n if ((e.target as HTMLElement).closest('[data-popup-submenu-panel]') !== e.currentTarget) return;\n const items = focusables(panelRef.current);\n const at = items.indexOf(document.activeElement as HTMLElement);\n const move = (to: number) => { e.preventDefault(); e.stopPropagation(); items[(to + items.length) % items.length]?.focus(); };\n if (e.key === 'ArrowDown') move(at + 1);\n else if (e.key === 'ArrowUp') move(at < 0 ? items.length - 1 : at - 1);\n else if (e.key === 'Home') move(0);\n else if (e.key === 'End') move(items.length - 1);\n else if (e.key === 'ArrowLeft') { e.preventDefault(); e.stopPropagation(); closeSelf(true); }\n };\n\n const look = itemLook(false, disabled, className, isOpen);\n const density = getDensity();\n\n return (\n <>\n <button\n ref={rowRef}\n type=\"button\"\n role=\"menuitem\"\n aria-haspopup=\"menu\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? panelId : undefined}\n disabled={disabled}\n data-popup-submenu-row\n className={look.className}\n style={look.style}\n onClick={e => {\n // `detail` is 0 for a click the keyboard made (Enter, Space).\n const byKey = e.detail === 0;\n if (!isOpen) openNow(byKey);\n else if (byKey) focusables(panelRef.current)[0]?.focus();\n }}\n onKeyDown={onRowKeyDown}\n onMouseEnter={() => {\n if (disabled) return;\n tree.cancelClose();\n if (isOpen) { tree.closeBelow(depth + 1); return; }\n clearTimeout(openTimer.current);\n openTimer.current = setTimeout(() => openNow(false), SUBMENU_OPEN_DELAY);\n }}\n onMouseLeave={() => {\n clearTimeout(openTimer.current);\n tree.scheduleClose(depth);\n }}\n >\n {icon}\n <span className=\"min-w-0 flex-1 truncate\">{label}</span>\n {chevron}\n </button>\n {isOpen && createPortal(\n <div\n ref={panelRef}\n id={panelId}\n role=\"menu\"\n data-popup-menu-tree={tree.id}\n data-popup-submenu-panel\n className={`fixed rounded-2xl overflow-x-hidden overflow-y-auto overscroll-contain ${density === 'tight' ? 'py-1' : density === 'large' ? 'py-2' : 'py-1.5'}`}\n style={{\n left: anchor!.left, top: anchor!.y, minWidth,\n // The viewport cap always applies, so a long list scrolls rather\n // than running off the screen even when no maxHeight is passed.\n maxHeight: maxHeight === undefined\n ? 'calc(100vh - 16px)'\n : `min(${typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight}, calc(100vh - 16px))`,\n zIndex: layerOf(tree.root.current) + depth + 1,\n animation: `${anchor!.flipped ? 'popup-submenu-in-left' : 'popup-submenu-in'} 0.1s ease-out`,\n ...glassStyle(),\n }}\n onKeyDown={onPanelKeyDown}\n onMouseEnter={() => { tree.cancelClose(); clearTimeout(openTimer.current); }}\n onMouseLeave={() => tree.scheduleClose(depth + 1)}\n onContextMenu={e => { if (!keepsNativeMenu(e.target)) e.preventDefault(); }}\n >\n <MenuTreeContext.Provider value={childTree}>{children}</MenuTreeContext.Provider>\n </div>,\n document.body,\n )}\n </>\n );\n}\n\n/** A divider line between menu items */\nexport function PopupMenuDivider() {\n const density = getDensity();\n return <div className={`border-t ${GLASS_DIVIDER} ${density === 'tight' ? 'my-0.5' : density === 'large' ? 'my-1.5' : 'my-1'} mx-3`} />;\n}\n\n/**\n * A section header label.\n *\n * It was 10px `text-gray-400`: 2.5:1 on a white menu, under WCAG AA's 4.5:1\n * for text of any size, so an axe sweep with a menu open failed on every\n * label — and it was the smallest, faintest text in the menu, which is the\n * wrong way round for the words that say what the rows below are.\n *\n * Now 12px in `--ink-muted`, read as a token rather than through a grey\n * utility so it needs no dark remap: #4b5563 on white (7.6:1) and #9399b2 on\n * the dark `--surface` (5.8:1). The frosted glass is held to the same 4.5:1 at\n * its thinnest stop over the theme's `--line-strong` — `tests/menuLabelContrast`\n * does the sums from ui.css, so a ramp change that breaks it fails there. Still\n * a quieter voice than the rows: smaller, uppercase, a step lighter.\n */\nexport function PopupMenuLabel({ children }: { children: ReactNode }) {\n const density = getDensity();\n const labelPadY = density === 'tight' ? '0.125rem' : density === 'large' ? '0.375rem' : '0.25rem';\n return (\n <div\n className=\"text-xs font-medium uppercase tracking-wide\"\n style={{\n color: 'var(--ink-muted)',\n paddingLeft: 'var(--menu-padding-x, 1rem)',\n paddingRight: 'var(--menu-padding-x, 1rem)',\n paddingTop: labelPadY,\n paddingBottom: labelPadY,\n }}\n >\n {children}\n </div>\n );\n}\n\n/** An icon helper */\nexport function MenuIcon({ d, className = 'h-4 w-4 text-gray-400' }: { d: string; className?: string }) {\n return (\n <svg className={className} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d={d} />\n </svg>\n );\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { setPdfPreview } from './chunk-DVZWYWSD.js';
2
2
  import { setSpreadsheetPreview } from './chunk-5O42JXQE.js';
3
- import { toast_default } from './chunk-4P44JXDS.js';
3
+ import { toast_default } from './chunk-UTYKLGGT.js';
4
4
 
5
5
  // src/utils/openPreviewFile.ts
6
6
  var PREVIEW_OPENED_EVENT = "react-os-shell:preview-opened";
@@ -42,5 +42,5 @@ async function openPreviewFile(opts) {
42
42
  }
43
43
 
44
44
  export { PREVIEW_OPENED_EVENT, openPreviewFile };
45
- //# sourceMappingURL=chunk-PIGDNN5F.js.map
46
- //# sourceMappingURL=chunk-PIGDNN5F.js.map
45
+ //# sourceMappingURL=chunk-PVPPAWVM.js.map
46
+ //# sourceMappingURL=chunk-PVPPAWVM.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/openPreviewFile.ts"],"names":[],"mappings":";;;;;AA6BO,IAAM,oBAAA,GAAuB;AAQpC,SAAS,SAAA,GAAY;AACnB,EAAA,MAAM,QAAA,GAAY,OAAO,MAAA,KAAW,WAAA,IAAgB,MAAA,CAAe,8BAAA;AACnE,EAAA,OAAA,CAAQ,QAAA,IAAY,uBAAA,EAAyB,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAChE;AAEA,eAAsB,gBAAgB,IAAA,EAA6C;AACjF,EAAA,MAAM,EAAE,QAAA,EAAU,QAAA,EAAU,IAAA,EAAM,UAAS,GAAI,IAAA;AAC/C,EAAA,IAAI;AACF,IAAA,MAAM,MAAM,MAAM,KAAA;AAAA,MAChB,GAAG,SAAA,EAAW,CAAA,eAAA,EAAkB,kBAAA,CAAmB,QAAQ,CAAC,CAAA,CAAA;AAAA,MAC5D,EAAE,aAAa,SAAA;AAAU,KAC3B;AACA,IAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,MAAA,aAAA,CAAM,KAAA,CAAM,CAAA,iBAAA,EAAoB,GAAA,CAAI,MAAM,CAAA,CAAA,CAAG,CAAA;AAC7C,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,IAAA,IAAI,SAAS,KAAA,EAAO;AAClB,MAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,IAAA,EAAK;AAC7B,MAAA,qBAAA,CAAsB,EAAE,GAAA,EAAK,IAAA,EAAM,QAAA,EAAU,CAAA;AAC7C,MAAA,QAAA,GAAW,cAAc,CAAA;AAAA,IAC3B,CAAA,MAAO;AACL,MAAA,MAAM,GAAA,GAAM,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AACpC,MAAA,aAAA,CAAc,EAAE,GAAA,EAAK,QAAA,EAAU,IAAA,EAAM,CAAA;AACrC,MAAA,QAAA,GAAW,UAAU,CAAA;AAAA,IACvB;AACA,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,MAAA,MAAA,CAAO,aAAA,CAAc,IAAI,WAAA,CAAiC,oBAAA,EAAsB;AAAA,QAC9E,MAAA,EAAQ,EAAE,QAAA,EAAU,QAAA,EAAU,IAAA;AAAK,OACpC,CAAC,CAAA;AAAA,IACJ;AACA,IAAA,OAAO,IAAA;AAAA,EACT,SAAS,CAAA,EAAQ;AACf,IAAA,aAAA,CAAM,KAAA,CAAM,CAAA,EAAG,OAAA,IAAW,aAAa,CAAA;AACvC,IAAA,OAAO,KAAA;AAAA,EACT;AACF","file":"chunk-PIGDNN5F.js","sourcesContent":["/**\n * Fetch a file from the user's file-server and route it into the right\n * preview window. Shared by the Files app and the desktop Documents\n * folder's shortcut click-through.\n *\n * On success, dispatches `react-os-shell:preview-opened` so Desktop can\n * record the file as a shortcut without this util needing access to the\n * prefs adapter.\n */\nimport toast from '../shell/toast';\nimport { setPdfPreview } from '../apps/_previewStage';\nimport { setSpreadsheetPreview } from '../apps/_spreadsheetStage';\n\nexport type PreviewFileKind = 'pdf' | 'dxf' | '3d' | 'image' | 'csv';\n\nexport interface OpenPreviewFileOpts {\n /** Server-relative path, e.g. \"/reports/Q1.pdf\". */\n filePath: string;\n /** Display name (also used as the download filename). */\n filename: string;\n /** Which viewer to route into. CSV opens in Spreadsheet; the rest open in Preview. */\n kind: PreviewFileKind;\n /** Optional callback invoked after staging the preview, with the route to\n * open (e.g. '/preview' or '/spreadsheet'). The caller is responsible\n * for actually opening the page since the window manager hook is\n * React-scoped. */\n onStaged?: (route: '/preview' | '/spreadsheet') => void;\n}\n\nexport const PREVIEW_OPENED_EVENT = 'react-os-shell:preview-opened';\n\nexport interface PreviewOpenedDetail {\n filePath: string;\n filename: string;\n kind: PreviewFileKind;\n}\n\nfunction getServer() {\n const override = (typeof window !== 'undefined' && (window as any).__REACT_OS_SHELL_FILE_SERVER__) as string | undefined;\n return (override || 'http://localhost:4000').replace(/\\/$/, '');\n}\n\nexport async function openPreviewFile(opts: OpenPreviewFileOpts): Promise<boolean> {\n const { filePath, filename, kind, onStaged } = opts;\n try {\n const res = await fetch(\n `${getServer()}/api/file?path=${encodeURIComponent(filePath)}`,\n { credentials: 'include' },\n );\n if (!res.ok) {\n toast.error(`Download failed (${res.status})`);\n return false;\n }\n const blob = await res.blob();\n if (kind === 'csv') {\n const text = await blob.text();\n setSpreadsheetPreview({ csv: text, filename });\n onStaged?.('/spreadsheet');\n } else {\n const url = URL.createObjectURL(blob);\n setPdfPreview({ url, filename, kind });\n onStaged?.('/preview');\n }\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent<PreviewOpenedDetail>(PREVIEW_OPENED_EVENT, {\n detail: { filePath, filename, kind },\n }));\n }\n return true;\n } catch (e: any) {\n toast.error(e?.message || 'Open failed');\n return false;\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/utils/openPreviewFile.ts"],"names":[],"mappings":";;;;;AA6BO,IAAM,oBAAA,GAAuB;AAQpC,SAAS,SAAA,GAAY;AACnB,EAAA,MAAM,QAAA,GAAY,OAAO,MAAA,KAAW,WAAA,IAAgB,MAAA,CAAe,8BAAA;AACnE,EAAA,OAAA,CAAQ,QAAA,IAAY,uBAAA,EAAyB,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAChE;AAEA,eAAsB,gBAAgB,IAAA,EAA6C;AACjF,EAAA,MAAM,EAAE,QAAA,EAAU,QAAA,EAAU,IAAA,EAAM,UAAS,GAAI,IAAA;AAC/C,EAAA,IAAI;AACF,IAAA,MAAM,MAAM,MAAM,KAAA;AAAA,MAChB,GAAG,SAAA,EAAW,CAAA,eAAA,EAAkB,kBAAA,CAAmB,QAAQ,CAAC,CAAA,CAAA;AAAA,MAC5D,EAAE,aAAa,SAAA;AAAU,KAC3B;AACA,IAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,MAAA,aAAA,CAAM,KAAA,CAAM,CAAA,iBAAA,EAAoB,GAAA,CAAI,MAAM,CAAA,CAAA,CAAG,CAAA;AAC7C,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,IAAA,IAAI,SAAS,KAAA,EAAO;AAClB,MAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,IAAA,EAAK;AAC7B,MAAA,qBAAA,CAAsB,EAAE,GAAA,EAAK,IAAA,EAAM,QAAA,EAAU,CAAA;AAC7C,MAAA,QAAA,GAAW,cAAc,CAAA;AAAA,IAC3B,CAAA,MAAO;AACL,MAAA,MAAM,GAAA,GAAM,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AACpC,MAAA,aAAA,CAAc,EAAE,GAAA,EAAK,QAAA,EAAU,IAAA,EAAM,CAAA;AACrC,MAAA,QAAA,GAAW,UAAU,CAAA;AAAA,IACvB;AACA,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,MAAA,MAAA,CAAO,aAAA,CAAc,IAAI,WAAA,CAAiC,oBAAA,EAAsB;AAAA,QAC9E,MAAA,EAAQ,EAAE,QAAA,EAAU,QAAA,EAAU,IAAA;AAAK,OACpC,CAAC,CAAA;AAAA,IACJ;AACA,IAAA,OAAO,IAAA;AAAA,EACT,SAAS,CAAA,EAAQ;AACf,IAAA,aAAA,CAAM,KAAA,CAAM,CAAA,EAAG,OAAA,IAAW,aAAa,CAAA;AACvC,IAAA,OAAO,KAAA;AAAA,EACT;AACF","file":"chunk-PVPPAWVM.js","sourcesContent":["/**\n * Fetch a file from the user's file-server and route it into the right\n * preview window. Shared by the Files app and the desktop Documents\n * folder's shortcut click-through.\n *\n * On success, dispatches `react-os-shell:preview-opened` so Desktop can\n * record the file as a shortcut without this util needing access to the\n * prefs adapter.\n */\nimport toast from '../shell/toast';\nimport { setPdfPreview } from '../apps/_previewStage';\nimport { setSpreadsheetPreview } from '../apps/_spreadsheetStage';\n\nexport type PreviewFileKind = 'pdf' | 'dxf' | '3d' | 'image' | 'csv';\n\nexport interface OpenPreviewFileOpts {\n /** Server-relative path, e.g. \"/reports/Q1.pdf\". */\n filePath: string;\n /** Display name (also used as the download filename). */\n filename: string;\n /** Which viewer to route into. CSV opens in Spreadsheet; the rest open in Preview. */\n kind: PreviewFileKind;\n /** Optional callback invoked after staging the preview, with the route to\n * open (e.g. '/preview' or '/spreadsheet'). The caller is responsible\n * for actually opening the page since the window manager hook is\n * React-scoped. */\n onStaged?: (route: '/preview' | '/spreadsheet') => void;\n}\n\nexport const PREVIEW_OPENED_EVENT = 'react-os-shell:preview-opened';\n\nexport interface PreviewOpenedDetail {\n filePath: string;\n filename: string;\n kind: PreviewFileKind;\n}\n\nfunction getServer() {\n const override = (typeof window !== 'undefined' && (window as any).__REACT_OS_SHELL_FILE_SERVER__) as string | undefined;\n return (override || 'http://localhost:4000').replace(/\\/$/, '');\n}\n\nexport async function openPreviewFile(opts: OpenPreviewFileOpts): Promise<boolean> {\n const { filePath, filename, kind, onStaged } = opts;\n try {\n const res = await fetch(\n `${getServer()}/api/file?path=${encodeURIComponent(filePath)}`,\n { credentials: 'include' },\n );\n if (!res.ok) {\n toast.error(`Download failed (${res.status})`);\n return false;\n }\n const blob = await res.blob();\n if (kind === 'csv') {\n const text = await blob.text();\n setSpreadsheetPreview({ csv: text, filename });\n onStaged?.('/spreadsheet');\n } else {\n const url = URL.createObjectURL(blob);\n setPdfPreview({ url, filename, kind });\n onStaged?.('/preview');\n }\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent<PreviewOpenedDetail>(PREVIEW_OPENED_EVENT, {\n detail: { filePath, filename, kind },\n }));\n }\n return true;\n } catch (e: any) {\n toast.error(e?.message || 'Open failed');\n return false;\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { useShellStrings } from './chunk-72CQWN7O.js';
1
+ import { useShellStrings } from './chunk-YE6GUDGP.js';
2
2
  import { useBrowserState } from './chunk-IRWSRJ4K.js';
3
3
  import { useRef, useCallback, useEffect } from 'react';
4
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -98,5 +98,5 @@ function SidebarLayout({
98
98
  }
99
99
 
100
100
  export { SidebarLayout };
101
- //# sourceMappingURL=chunk-ET6ID4LP.js.map
102
- //# sourceMappingURL=chunk-ET6ID4LP.js.map
101
+ //# sourceMappingURL=chunk-R7QGMK6N.js.map
102
+ //# sourceMappingURL=chunk-R7QGMK6N.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shell/SidebarLayout.tsx"],"names":[],"mappings":"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","file":"chunk-ET6ID4LP.js","sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport type { ReactNode, PointerEvent as ReactPointerEvent } from 'react';\nimport { useBrowserState } from './hydration';\nimport { useShellStrings } from './strings';\n\n/**\n * Two-pane layout with a drag-to-resize sidebar (left or right).\n *\n * Renders flush (`h-full w-full`) — pair it with a `flushBody` window so the\n * sidebar runs from just under the title bar to the very bottom with no\n * surrounding padding. The user can drag the sidebar's inner edge to resize\n * it; pass `storageKey` to persist that width across reopens, or double-click\n * the handle to reset to `defaultWidth`. Set `side=\"right\"` to put the sidebar\n * on the right (e.g. a detail-panel layout).\n *\n * @example\n * <SidebarLayout sidebar={<MyNav />} storageKey=\"todo.sidebarWidth\">\n * <MyContent />\n * </SidebarLayout>\n */\nexport interface SidebarLayoutProps {\n /** Content of the left sidebar pane. When `sidebarTop`/`sidebarBottom` are\n * set this is the scrolling middle region (the filter nav); otherwise it is\n * the whole pane. */\n sidebar: ReactNode;\n /** Optional node pinned at the TOP of the sidebar, above `sidebar`, in a\n * padded non-scrolling region (e.g. a primary \"New X\" `SidebarActionButton`).\n * The list-window convention: primary create action lives here. */\n sidebarTop?: ReactNode;\n /** Optional node pinned at the BOTTOM of the sidebar, below `sidebar`, in a\n * padded, top-bordered non-scrolling region (e.g. an \"Export CSV\" button).\n * The middle `sidebar` grows to fill, so this stays flush to the bottom. */\n sidebarBottom?: ReactNode;\n /** Content of the main pane (opposite the sidebar). */\n children: ReactNode;\n /** Which side the sidebar sits on. Default `'left'`. When `'right'`, the\n * sidebar renders on the right with the resize handle on its left edge. */\n side?: 'left' | 'right';\n /** localStorage key to persist the sidebar width across reopens. When set,\n * the last dragged width is restored on mount. Omit for session-only width. */\n storageKey?: string;\n /** Initial sidebar width in px (used when nothing is persisted). Default 256. */\n defaultWidth?: number;\n /** Minimum sidebar width in px. Default 180. */\n minWidth?: number;\n /** Maximum sidebar width in px. Default 480. */\n maxWidth?: number;\n /** Extra classes for the outer flex row. */\n className?: string;\n /** Classes for the sidebar pane (background, border, …). The pane is a flex\n * column that scrolls its own overflow. Defaults to a light-grey,\n * right-bordered sidebar. */\n sidebarClassName?: string;\n /** Classes for the main content pane. Defaults to a white background. */\n contentClassName?: string;\n}\n\nconst clamp = (n: number, lo: number, hi: number) => Math.min(Math.max(n, lo), hi);\n\nexport default function SidebarLayout({\n sidebar,\n sidebarTop,\n sidebarBottom,\n children,\n side = 'left',\n storageKey,\n defaultWidth = 256,\n minWidth = 180,\n maxWidth = 480,\n className = '',\n sidebarClassName = side === 'right'\n ? 'border-l border-gray-200 bg-gray-50'\n : 'border-r border-gray-200 bg-gray-50',\n contentClassName = 'bg-white',\n}: SidebarLayoutProps) {\n const sideRight = side === 'right';\n const strings = useShellStrings().sidebar;\n // The saved width is the READER's, and a server has no reader: it draws\n // `defaultWidth`, hydration agrees with it, and the saved width replaces it\n // in a layout effect the moment hydration ends. A page that was never\n // server-rendered reads storage on its first render, as this always did.\n const [width, setWidth] = useBrowserState<number>(() => {\n if (storageKey) {\n let saved: string | null = null;\n // Guarded: a sandboxed frame or a locked-down profile throws on access.\n try { saved = window.localStorage.getItem(storageKey); } catch { /* treat as unsaved */ }\n const n = saved ? parseInt(saved, 10) : NaN;\n if (!Number.isNaN(n)) return clamp(n, minWidth, maxWidth);\n }\n return clamp(defaultWidth, minWidth, maxWidth);\n }, clamp(defaultWidth, minWidth, maxWidth));\n\n const dragRef = useRef<{ startX: number; startWidth: number } | null>(null);\n\n const onMove = useCallback((e: PointerEvent) => {\n const d = dragRef.current;\n if (!d) return;\n // Right-side sidebar grows as the handle is dragged left → invert the delta.\n const delta = (e.clientX - d.startX) * (sideRight ? -1 : 1);\n setWidth(clamp(d.startWidth + delta, minWidth, maxWidth));\n }, [minWidth, maxWidth, sideRight]);\n\n const onUp = useCallback(() => {\n dragRef.current = null;\n window.removeEventListener('pointermove', onMove);\n window.removeEventListener('pointerup', onUp);\n document.body.style.removeProperty('cursor');\n document.body.style.removeProperty('user-select');\n }, [onMove]);\n\n const startDrag = useCallback((e: ReactPointerEvent<HTMLDivElement>) => {\n e.preventDefault();\n dragRef.current = { startX: e.clientX, startWidth: width };\n window.addEventListener('pointermove', onMove);\n window.addEventListener('pointerup', onUp);\n document.body.style.cursor = 'col-resize';\n document.body.style.userSelect = 'none';\n }, [width, onMove, onUp]);\n\n // Persist (debounced) once the width settles.\n useEffect(() => {\n if (!storageKey) return;\n const id = window.setTimeout(() => {\n try { window.localStorage.setItem(storageKey, String(Math.round(width))); } catch { /* ignore quota / privacy mode */ }\n }, 200);\n return () => window.clearTimeout(id);\n }, [width, storageKey]);\n\n // Drop listeners if we unmount mid-drag.\n useEffect(() => () => {\n window.removeEventListener('pointermove', onMove);\n window.removeEventListener('pointerup', onUp);\n }, [onMove, onUp]);\n\n const edge = sideRight ? 'left-0' : 'right-0';\n // With a top/bottom slot the pane becomes a 3-region flex column: a padded\n // pinned header, the scrolling middle (`sidebar`, grows to fill), and a\n // padded, top-bordered pinned footer. Without slots it renders exactly as\n // before (the whole pane scrolls) so existing consumers are unaffected.\n const hasSlots = sidebarTop != null || sidebarBottom != null;\n const sidebarPane = (\n <div className=\"relative flex h-full shrink-0 flex-col\" style={{ width }}>\n <div className={`flex h-full flex-col ${hasSlots ? 'overflow-hidden' : 'overflow-y-auto'} ${sidebarClassName}`}>\n {sidebarTop != null && (\n <div className=\"shrink-0 px-2 pt-3 pb-2\">{sidebarTop}</div>\n )}\n {hasSlots ? (\n <div className=\"min-h-0 flex-1 overflow-y-auto\">{sidebar}</div>\n ) : (\n sidebar\n )}\n {sidebarBottom != null && (\n <div className=\"mt-auto shrink-0 border-t border-gray-200 px-2 py-3\">{sidebarBottom}</div>\n )}\n </div>\n {/* Resize handle — pinned to the sidebar's inner edge, fixed while it scrolls. */}\n <div\n onPointerDown={startDrag}\n onDoubleClick={() => setWidth(clamp(defaultWidth, minWidth, maxWidth))}\n title={strings.resize}\n className={`group/resize absolute inset-y-0 ${edge} z-10 w-2 cursor-col-resize`}\n >\n <div className={`absolute inset-y-0 ${edge} w-px bg-transparent transition-colors group-hover/resize:bg-[var(--accent-500,var(--rosh-resize-handle))]`} />\n </div>\n </div>\n );\n const contentPane = (\n <div className={`flex min-w-0 flex-1 flex-col ${contentClassName}`}>\n {children}\n </div>\n );\n\n return (\n <div className={`flex h-full w-full overflow-hidden ${className}`}>\n {sideRight ? (\n <>{contentPane}{sidebarPane}</>\n ) : (\n <>{sidebarPane}{contentPane}</>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/shell/SidebarLayout.tsx"],"names":[],"mappings":"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","file":"chunk-R7QGMK6N.js","sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport type { ReactNode, PointerEvent as ReactPointerEvent } from 'react';\nimport { useBrowserState } from './hydration';\nimport { useShellStrings } from './strings';\n\n/**\n * Two-pane layout with a drag-to-resize sidebar (left or right).\n *\n * Renders flush (`h-full w-full`) — pair it with a `flushBody` window so the\n * sidebar runs from just under the title bar to the very bottom with no\n * surrounding padding. The user can drag the sidebar's inner edge to resize\n * it; pass `storageKey` to persist that width across reopens, or double-click\n * the handle to reset to `defaultWidth`. Set `side=\"right\"` to put the sidebar\n * on the right (e.g. a detail-panel layout).\n *\n * @example\n * <SidebarLayout sidebar={<MyNav />} storageKey=\"todo.sidebarWidth\">\n * <MyContent />\n * </SidebarLayout>\n */\nexport interface SidebarLayoutProps {\n /** Content of the left sidebar pane. When `sidebarTop`/`sidebarBottom` are\n * set this is the scrolling middle region (the filter nav); otherwise it is\n * the whole pane. */\n sidebar: ReactNode;\n /** Optional node pinned at the TOP of the sidebar, above `sidebar`, in a\n * padded non-scrolling region (e.g. a primary \"New X\" `SidebarActionButton`).\n * The list-window convention: primary create action lives here. */\n sidebarTop?: ReactNode;\n /** Optional node pinned at the BOTTOM of the sidebar, below `sidebar`, in a\n * padded, top-bordered non-scrolling region (e.g. an \"Export CSV\" button).\n * The middle `sidebar` grows to fill, so this stays flush to the bottom. */\n sidebarBottom?: ReactNode;\n /** Content of the main pane (opposite the sidebar). */\n children: ReactNode;\n /** Which side the sidebar sits on. Default `'left'`. When `'right'`, the\n * sidebar renders on the right with the resize handle on its left edge. */\n side?: 'left' | 'right';\n /** localStorage key to persist the sidebar width across reopens. When set,\n * the last dragged width is restored on mount. Omit for session-only width. */\n storageKey?: string;\n /** Initial sidebar width in px (used when nothing is persisted). Default 256. */\n defaultWidth?: number;\n /** Minimum sidebar width in px. Default 180. */\n minWidth?: number;\n /** Maximum sidebar width in px. Default 480. */\n maxWidth?: number;\n /** Extra classes for the outer flex row. */\n className?: string;\n /** Classes for the sidebar pane (background, border, …). The pane is a flex\n * column that scrolls its own overflow. Defaults to a light-grey,\n * right-bordered sidebar. */\n sidebarClassName?: string;\n /** Classes for the main content pane. Defaults to a white background. */\n contentClassName?: string;\n}\n\nconst clamp = (n: number, lo: number, hi: number) => Math.min(Math.max(n, lo), hi);\n\nexport default function SidebarLayout({\n sidebar,\n sidebarTop,\n sidebarBottom,\n children,\n side = 'left',\n storageKey,\n defaultWidth = 256,\n minWidth = 180,\n maxWidth = 480,\n className = '',\n sidebarClassName = side === 'right'\n ? 'border-l border-gray-200 bg-gray-50'\n : 'border-r border-gray-200 bg-gray-50',\n contentClassName = 'bg-white',\n}: SidebarLayoutProps) {\n const sideRight = side === 'right';\n const strings = useShellStrings().sidebar;\n // The saved width is the READER's, and a server has no reader: it draws\n // `defaultWidth`, hydration agrees with it, and the saved width replaces it\n // in a layout effect the moment hydration ends. A page that was never\n // server-rendered reads storage on its first render, as this always did.\n const [width, setWidth] = useBrowserState<number>(() => {\n if (storageKey) {\n let saved: string | null = null;\n // Guarded: a sandboxed frame or a locked-down profile throws on access.\n try { saved = window.localStorage.getItem(storageKey); } catch { /* treat as unsaved */ }\n const n = saved ? parseInt(saved, 10) : NaN;\n if (!Number.isNaN(n)) return clamp(n, minWidth, maxWidth);\n }\n return clamp(defaultWidth, minWidth, maxWidth);\n }, clamp(defaultWidth, minWidth, maxWidth));\n\n const dragRef = useRef<{ startX: number; startWidth: number } | null>(null);\n\n const onMove = useCallback((e: PointerEvent) => {\n const d = dragRef.current;\n if (!d) return;\n // Right-side sidebar grows as the handle is dragged left → invert the delta.\n const delta = (e.clientX - d.startX) * (sideRight ? -1 : 1);\n setWidth(clamp(d.startWidth + delta, minWidth, maxWidth));\n }, [minWidth, maxWidth, sideRight]);\n\n const onUp = useCallback(() => {\n dragRef.current = null;\n window.removeEventListener('pointermove', onMove);\n window.removeEventListener('pointerup', onUp);\n document.body.style.removeProperty('cursor');\n document.body.style.removeProperty('user-select');\n }, [onMove]);\n\n const startDrag = useCallback((e: ReactPointerEvent<HTMLDivElement>) => {\n e.preventDefault();\n dragRef.current = { startX: e.clientX, startWidth: width };\n window.addEventListener('pointermove', onMove);\n window.addEventListener('pointerup', onUp);\n document.body.style.cursor = 'col-resize';\n document.body.style.userSelect = 'none';\n }, [width, onMove, onUp]);\n\n // Persist (debounced) once the width settles.\n useEffect(() => {\n if (!storageKey) return;\n const id = window.setTimeout(() => {\n try { window.localStorage.setItem(storageKey, String(Math.round(width))); } catch { /* ignore quota / privacy mode */ }\n }, 200);\n return () => window.clearTimeout(id);\n }, [width, storageKey]);\n\n // Drop listeners if we unmount mid-drag.\n useEffect(() => () => {\n window.removeEventListener('pointermove', onMove);\n window.removeEventListener('pointerup', onUp);\n }, [onMove, onUp]);\n\n const edge = sideRight ? 'left-0' : 'right-0';\n // With a top/bottom slot the pane becomes a 3-region flex column: a padded\n // pinned header, the scrolling middle (`sidebar`, grows to fill), and a\n // padded, top-bordered pinned footer. Without slots it renders exactly as\n // before (the whole pane scrolls) so existing consumers are unaffected.\n const hasSlots = sidebarTop != null || sidebarBottom != null;\n const sidebarPane = (\n <div className=\"relative flex h-full shrink-0 flex-col\" style={{ width }}>\n <div className={`flex h-full flex-col ${hasSlots ? 'overflow-hidden' : 'overflow-y-auto'} ${sidebarClassName}`}>\n {sidebarTop != null && (\n <div className=\"shrink-0 px-2 pt-3 pb-2\">{sidebarTop}</div>\n )}\n {hasSlots ? (\n <div className=\"min-h-0 flex-1 overflow-y-auto\">{sidebar}</div>\n ) : (\n sidebar\n )}\n {sidebarBottom != null && (\n <div className=\"mt-auto shrink-0 border-t border-gray-200 px-2 py-3\">{sidebarBottom}</div>\n )}\n </div>\n {/* Resize handle — pinned to the sidebar's inner edge, fixed while it scrolls. */}\n <div\n onPointerDown={startDrag}\n onDoubleClick={() => setWidth(clamp(defaultWidth, minWidth, maxWidth))}\n title={strings.resize}\n className={`group/resize absolute inset-y-0 ${edge} z-10 w-2 cursor-col-resize`}\n >\n <div className={`absolute inset-y-0 ${edge} w-px bg-transparent transition-colors group-hover/resize:bg-[var(--accent-500,var(--rosh-resize-handle))]`} />\n </div>\n </div>\n );\n const contentPane = (\n <div className={`flex min-w-0 flex-1 flex-col ${contentClassName}`}>\n {children}\n </div>\n );\n\n return (\n <div className={`flex h-full w-full overflow-hidden ${className}`}>\n {sideRight ? (\n <>{contentPane}{sidebarPane}</>\n ) : (\n <>{sidebarPane}{contentPane}</>\n )}\n </div>\n );\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { currentShellStrings } from './chunk-72CQWN7O.js';
1
+ import { currentShellStrings } from './chunk-YE6GUDGP.js';
2
2
 
3
3
  // src/shell/toast.ts
4
4
  var TOAST_CONTAINER_ID = "toast-container";
@@ -191,5 +191,5 @@ var toast = {
191
191
  var toast_default = toast;
192
192
 
193
193
  export { toast_default };
194
- //# sourceMappingURL=chunk-4P44JXDS.js.map
195
- //# sourceMappingURL=chunk-4P44JXDS.js.map
194
+ //# sourceMappingURL=chunk-UTYKLGGT.js.map
195
+ //# sourceMappingURL=chunk-UTYKLGGT.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shell/toast.ts"],"names":[],"mappings":"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","file":"chunk-4P44JXDS.js","sourcesContent":["/**\n * Two presentations:\n *\n * 1. toast.success / error / info — brief operation feedback, top-center,\n * auto-dismiss (success/error ~3s, info ~4.5s). The everyday \"what just\n * happened\" feedback — most messages want this.\n * 2. toast.notify — system notification, top-right card, stays 10s, dismissible.\n * For an alert worth lingering on; reach for it deliberately, not by default.\n *\n * (Historically `toast.info` rendered the top-right notification card; it now\n * renders a brief toast — that persistent card moved to `toast.notify`.)\n *\n * ── Built for a strict Content-Security-Policy ──\n *\n * Every node here is made with `createElement`/`createElementNS` and dressed\n * with CLASSES from ui.css (`.rosh-toast`, `.rosh-notif`…). There is no HTML\n * string, no `style=\"…\"` attribute and no `on…=` handler anywhere in the\n * markup, and that is load-bearing rather than tidy:\n *\n * - An inline handler attribute is inline SCRIPT. Under any `script-src`\n * without `'unsafe-inline'` (a nonce-based policy, which is what Next.js\n * apps run) the browser refuses it and files a violation report. The\n * notification card's close button used to carry one for its hover colour,\n * so every `toast.notify` on such a site was a CSP report.\n * - A `style` attribute that arrives through markup is governed by\n * `style-src`; a policy with a nonce or a hash there ignores\n * `'unsafe-inline'` and drops it. Styles set through the CSSOM\n * (`el.style.opacity = …`) are not, which is why the enter/leave motion\n * below still writes `el.style` directly — that is the one dynamic part.\n * - No `innerHTML` also means nothing here needs a Trusted Types policy.\n *\n * The cost is that the toast's look now lives in ui.css, which every consumer\n * already imports (alone, or inside styles.css). Its colours are custom\n * properties there (`--rosh-toast-*`) with the values it always had, so a host\n * can restyle a toast without forking it.\n * `tests/strictCsp.test.ts` holds the DOM to all of this.\n */\n\nimport { currentShellStrings } from './strings';\n\nconst TOAST_CONTAINER_ID = 'toast-container';\nconst TOAST_BOTTOM_CONTAINER_ID = 'toast-container-bottom';\nconst NOTIF_CONTAINER_ID = 'notif-container';\nconst FADE_MS = 300;\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\nexport type ToastPlacement = 'top' | 'bottom';\n\nexport interface ToastOptions {\n /** Milliseconds before auto-dismiss. Ignored when `sticky`. */\n duration?: number;\n /**\n * Stay until dismissed. For a message the user must actually act on — the\n * kind that must not scroll past while they are looking at a customer.\n * Implies tap-to-dismiss is the only way out, which it already is.\n */\n sticky?: boolean;\n /**\n * `bottom` centres toasts along the bottom edge. On a till the top of the\n * screen is the cart total and the bottom is nearest the hand.\n */\n placement?: ToastPlacement;\n /**\n * Drop a message identical to one already showing, and RESTART its timer\n * rather than stacking a second copy. A failing poll otherwise writes the\n * same sentence a dozen times. The restart matters: the user asked twice, so\n * the message should persist, not expire on the first one's schedule.\n */\n dedupe?: boolean;\n}\n\n/**\n * Defaults for every subsequent toast, set once at app startup — the same\n * \"consumer wires this once\" shape as `setShellApiClient`/`setShellNavIcons`.\n * A till calls `toast.configure({ placement: 'bottom', duration: 6000,\n * dedupe: true })` instead of repeating those options at 40 call sites.\n * Per-call options still win.\n */\nlet defaults: ToastOptions = {};\n\n/** Live toasts by dedupe key, so a repeat can find and refresh its own. */\nconst live = new Map<string, { el: HTMLElement; restart: () => void; dismiss: () => void }>();\n\nfunction getOrCreate(id: string, className: string): HTMLElement {\n let el = document.getElementById(id);\n if (!el) {\n el = document.createElement('div');\n el.id = id;\n el.className = className;\n document.body.appendChild(el);\n }\n return el;\n}\n\ntype Tone = 'success' | 'error' | 'warning' | 'info' | 'loading';\n\n/**\n * Outline glyphs (Heroicons' paths), stroked in `currentColor` so the colour\n * comes from the tone class in ui.css rather than from an attribute here.\n * `loading` is an open arc that spins — the same vocabulary as LoadingSpinner.\n */\nconst GLYPHS: Record<Tone | 'bell', string> = {\n success: 'M5 13l4 4L19 7',\n error: 'M6 18L18 6M6 6l12 12',\n warning: '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 info: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',\n loading: 'M12 3a9 9 0 019 9',\n bell: 'M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0',\n};\n\nfunction glyph(name: Tone | 'bell', className: string): SVGSVGElement {\n const svg = document.createElementNS(SVG_NS, 'svg');\n svg.setAttribute('class', className);\n svg.setAttribute('fill', 'none');\n svg.setAttribute('viewBox', '0 0 24 24');\n svg.setAttribute('stroke', 'currentColor');\n svg.setAttribute('stroke-width', '2');\n const path = document.createElementNS(SVG_NS, 'path');\n path.setAttribute('stroke-linecap', 'round');\n if (name !== 'loading') path.setAttribute('stroke-linejoin', 'round');\n path.setAttribute('d', GLYPHS[name]);\n svg.appendChild(path);\n return svg;\n}\n\nfunction div(className: string, text?: string): HTMLDivElement {\n const el = document.createElement('div');\n el.className = className;\n if (text !== undefined) el.textContent = text;\n return el;\n}\n\n// ── Toast (operation feedback) — top-center, brief ──\n\nfunction showToast(variant: Tone, message: string,\n opts?: ToastOptions): { dismiss: () => void } {\n const opts_ = { ...defaults, ...opts };\n const placement: ToastPlacement = opts_.placement ?? 'top';\n const sticky = opts_.sticky ?? false;\n\n // A repeat of a message already on screen refreshes it instead of stacking.\n const key = `${placement}|${variant}|${message}`;\n if (opts_.dedupe) {\n const existing = live.get(key);\n if (existing) { existing.restart(); return { dismiss: existing.dismiss }; }\n }\n\n // A loading toast is the START of an operation — the sound belongs to the\n // outcome, which follows in a moment.\n if (variant !== 'loading') {\n import('../utils/sounds').then(s => {\n if (variant === 'success') s.playSuccess();\n else if (variant === 'error') s.playError();\n else s.playNotification();\n }).catch(() => {});\n }\n\n const container = placement === 'bottom'\n ? getOrCreate(TOAST_BOTTOM_CONTAINER_ID, 'fixed bottom-4 left-1/2 -translate-x-1/2 z-[9999] flex flex-col-reverse gap-2 items-center pointer-events-none')\n : getOrCreate(TOAST_CONTAINER_ID, 'fixed top-4 left-1/2 -translate-x-1/2 z-[9999] flex flex-col gap-2 items-center pointer-events-none');\n\n // Enter from the edge the toast is anchored to, so it reads as arriving from\n // off-screen rather than drifting in from the wrong direction.\n const offscreen = placement === 'bottom' ? 'translateY(10px)' : 'translateY(-10px)';\n\n // The tone is a CLASS: `.rosh-tone-warning` and friends in ui.css colour the\n // glyph through `--rosh-toast-*`. Warning is amber and deliberately not the\n // error red: a warning is something to read before continuing, not something\n // that failed, and painting both red teaches people to ignore the colour.\n const el = div('pointer-events-auto cursor-pointer rosh-toast');\n el.style.opacity = '0';\n el.style.transform = `${offscreen} scale(0.95)`;\n // A toast IS a status message, and it appears without the user moving focus\n // — so without a live region it is announced to nobody. `alert` is\n // assertive and interrupts whatever is being read, which is right for a\n // failure and wrong for a confirmation the user already expected.\n el.setAttribute('role', variant === 'error' ? 'alert' : 'status');\n // Read the whole toast, not the word that changed. Without it a repeat that\n // only alters a number announces the number alone.\n el.setAttribute('aria-atomic', 'true');\n\n el.appendChild(glyph(variant, `h-4 w-4 rosh-tone-${variant}${variant === 'loading' ? ' animate-spin' : ''}`));\n const span = document.createElement('span');\n span.textContent = message;\n el.appendChild(span);\n\n container.appendChild(el);\n requestAnimationFrame(() => { el.style.opacity = '1'; el.style.transform = 'translateY(0) scale(1)'; });\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n let gone = false;\n const dismiss = () => {\n if (gone) return;\n gone = true;\n if (timer) clearTimeout(timer);\n live.delete(key);\n el.style.opacity = '0';\n el.style.transform = `${offscreen} scale(0.95)`;\n setTimeout(() => el.remove(), FADE_MS);\n };\n\n // Info messages are usually a sentence (\"no matches — check X\"); give them a\n // beat longer to read than the terse success/error confirmations.\n const duration = opts_.duration ?? (variant === 'info' ? 4500 : 3000);\n const restart = () => {\n if (timer) clearTimeout(timer);\n if (!sticky) timer = setTimeout(dismiss, duration);\n };\n restart();\n\n // Hold the timer while the pointer rests on it. Three seconds is enough to\n // read a confirmation and not enough to read an address someone leaned in\n // for — and a toast that vanishes as you reach for it cannot be re-read at\n // all, since there is no history to open.\n el.addEventListener('mouseenter', () => { if (timer) clearTimeout(timer); });\n el.addEventListener('mouseleave', () => { if (!gone) restart(); });\n\n // Tap anywhere on the toast to dismiss it. A toast has no other click\n // affordance, and a sticky one has no other exit at all — `notify` has\n // behaved this way since it existed.\n el.addEventListener('click', dismiss);\n\n live.set(key, { el, restart, dismiss });\n // Internal handle — `toast.promise` swaps its loading toast for the outcome.\n return { dismiss };\n}\n\n// ── Notification (system alert) — top-right, stays longer ──\n\nfunction showNotification(message: string, opts?: { duration?: number }) {\n import('../utils/sounds').then(s => s.playNotification()).catch(() => {});\n\n const container = getOrCreate(NOTIF_CONTAINER_ID, 'fixed top-4 right-4 z-[9999] flex flex-col gap-3 items-end pointer-events-none');\n\n const el = div('pointer-events-auto cursor-pointer rosh-notif');\n el.style.opacity = '0';\n el.style.transform = 'translateX(30px) scale(0.95)';\n\n const badge = div('rosh-notif-badge');\n badge.appendChild(glyph('bell', 'h-5 w-5'));\n const body = div('rosh-notif-body');\n // The heading is the app's catalog word — read here, outside React.\n body.append(div('rosh-notif-title', currentShellStrings().toast.notification), div('notif-msg rosh-notif-msg', message));\n // The close button's hover colour is a `:hover` rule in ui.css. It was an\n // `onmouseenter` attribute — inline script, refused by a strict CSP.\n const close = document.createElement('button');\n close.className = 'rosh-notif-close';\n close.textContent = '×';\n el.append(badge, body, close);\n\n const dismiss = () => {\n el.style.opacity = '0';\n el.style.transform = 'translateX(30px) scale(0.95)';\n setTimeout(() => el.remove(), FADE_MS);\n };\n\n close.addEventListener('click', (e) => { e.stopPropagation(); dismiss(); });\n el.addEventListener('click', dismiss);\n\n container.appendChild(el);\n requestAnimationFrame(() => { el.style.opacity = '1'; el.style.transform = 'translateX(0) scale(1)'; });\n\n setTimeout(dismiss, opts?.duration ?? 10000);\n}\n\nconst toast = {\n success: (message: string, opts?: ToastOptions) => showToast('success', message, opts),\n error: (message: string, opts?: ToastOptions) => showToast('error', message, opts),\n /**\n * Something the user should read before continuing — a partial success, a\n * setting that will not apply until they do something else. Distinct from\n * `error`, which is \"this did not happen\".\n */\n warning: (message: string, opts?: ToastOptions) => showToast('warning', message, opts),\n info: (message: string, opts?: ToastOptions) => showToast('info', message, opts),\n // Persistent top-right notification card (the old toast.info presentation).\n notify: (message: string, opts?: { duration?: number }) => showNotification(message, opts),\n /**\n * One toast for one async operation: a spinning \"loading\" toast while `p`\n * is pending, swapped for a success or error toast when it settles. The\n * promise is returned untouched, so `await toast.promise(save(), …)` still\n * throws to the caller — this narrates the operation, it does not handle it.\n *\n * `error` is REQUIRED, and there is deliberately no fallback that prints\n * the exception: a raw `e.message` in a toast is how internals leak to the\n * screen (the same reasoning that keeps ErrorBoundary's stack behind\n * `showDetails`). The success/error messages may be functions of the\n * resolved value / the rejection, for the \"Saved 3 rows\" case.\n */\n promise: <T,>(\n p: Promise<T>,\n msgs: {\n loading: string;\n success: string | ((value: T) => string);\n error: string | ((error: unknown) => string);\n },\n opts?: ToastOptions,\n ): Promise<T> => {\n const pending = showToast('loading', msgs.loading, { ...opts, sticky: true });\n p.then(\n value => {\n pending.dismiss();\n showToast('success', typeof msgs.success === 'function' ? msgs.success(value) : msgs.success, opts);\n },\n error => {\n pending.dismiss();\n showToast('error', typeof msgs.error === 'function' ? msgs.error(error) : msgs.error, opts);\n },\n );\n return p;\n },\n /**\n * Set defaults for every subsequent toast. Call once at app startup; later\n * calls replace the whole object rather than merging, so a consumer sets its\n * policy in one place. Per-call options still take precedence.\n */\n configure: (next: ToastOptions) => { defaults = { ...next }; },\n};\n\nexport default toast;\n"]}
1
+ {"version":3,"sources":["../src/shell/toast.ts"],"names":[],"mappings":"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","file":"chunk-UTYKLGGT.js","sourcesContent":["/**\n * Two presentations:\n *\n * 1. toast.success / error / info — brief operation feedback, top-center,\n * auto-dismiss (success/error ~3s, info ~4.5s). The everyday \"what just\n * happened\" feedback — most messages want this.\n * 2. toast.notify — system notification, top-right card, stays 10s, dismissible.\n * For an alert worth lingering on; reach for it deliberately, not by default.\n *\n * (Historically `toast.info` rendered the top-right notification card; it now\n * renders a brief toast — that persistent card moved to `toast.notify`.)\n *\n * ── Built for a strict Content-Security-Policy ──\n *\n * Every node here is made with `createElement`/`createElementNS` and dressed\n * with CLASSES from ui.css (`.rosh-toast`, `.rosh-notif`…). There is no HTML\n * string, no `style=\"…\"` attribute and no `on…=` handler anywhere in the\n * markup, and that is load-bearing rather than tidy:\n *\n * - An inline handler attribute is inline SCRIPT. Under any `script-src`\n * without `'unsafe-inline'` (a nonce-based policy, which is what Next.js\n * apps run) the browser refuses it and files a violation report. The\n * notification card's close button used to carry one for its hover colour,\n * so every `toast.notify` on such a site was a CSP report.\n * - A `style` attribute that arrives through markup is governed by\n * `style-src`; a policy with a nonce or a hash there ignores\n * `'unsafe-inline'` and drops it. Styles set through the CSSOM\n * (`el.style.opacity = …`) are not, which is why the enter/leave motion\n * below still writes `el.style` directly — that is the one dynamic part.\n * - No `innerHTML` also means nothing here needs a Trusted Types policy.\n *\n * The cost is that the toast's look now lives in ui.css, which every consumer\n * already imports (alone, or inside styles.css). Its colours are custom\n * properties there (`--rosh-toast-*`) with the values it always had, so a host\n * can restyle a toast without forking it.\n * `tests/strictCsp.test.ts` holds the DOM to all of this.\n */\n\nimport { currentShellStrings } from './strings';\n\nconst TOAST_CONTAINER_ID = 'toast-container';\nconst TOAST_BOTTOM_CONTAINER_ID = 'toast-container-bottom';\nconst NOTIF_CONTAINER_ID = 'notif-container';\nconst FADE_MS = 300;\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\nexport type ToastPlacement = 'top' | 'bottom';\n\nexport interface ToastOptions {\n /** Milliseconds before auto-dismiss. Ignored when `sticky`. */\n duration?: number;\n /**\n * Stay until dismissed. For a message the user must actually act on — the\n * kind that must not scroll past while they are looking at a customer.\n * Implies tap-to-dismiss is the only way out, which it already is.\n */\n sticky?: boolean;\n /**\n * `bottom` centres toasts along the bottom edge. On a till the top of the\n * screen is the cart total and the bottom is nearest the hand.\n */\n placement?: ToastPlacement;\n /**\n * Drop a message identical to one already showing, and RESTART its timer\n * rather than stacking a second copy. A failing poll otherwise writes the\n * same sentence a dozen times. The restart matters: the user asked twice, so\n * the message should persist, not expire on the first one's schedule.\n */\n dedupe?: boolean;\n}\n\n/**\n * Defaults for every subsequent toast, set once at app startup — the same\n * \"consumer wires this once\" shape as `setShellApiClient`/`setShellNavIcons`.\n * A till calls `toast.configure({ placement: 'bottom', duration: 6000,\n * dedupe: true })` instead of repeating those options at 40 call sites.\n * Per-call options still win.\n */\nlet defaults: ToastOptions = {};\n\n/** Live toasts by dedupe key, so a repeat can find and refresh its own. */\nconst live = new Map<string, { el: HTMLElement; restart: () => void; dismiss: () => void }>();\n\nfunction getOrCreate(id: string, className: string): HTMLElement {\n let el = document.getElementById(id);\n if (!el) {\n el = document.createElement('div');\n el.id = id;\n el.className = className;\n document.body.appendChild(el);\n }\n return el;\n}\n\ntype Tone = 'success' | 'error' | 'warning' | 'info' | 'loading';\n\n/**\n * Outline glyphs (Heroicons' paths), stroked in `currentColor` so the colour\n * comes from the tone class in ui.css rather than from an attribute here.\n * `loading` is an open arc that spins — the same vocabulary as LoadingSpinner.\n */\nconst GLYPHS: Record<Tone | 'bell', string> = {\n success: 'M5 13l4 4L19 7',\n error: 'M6 18L18 6M6 6l12 12',\n warning: '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 info: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',\n loading: 'M12 3a9 9 0 019 9',\n bell: 'M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0',\n};\n\nfunction glyph(name: Tone | 'bell', className: string): SVGSVGElement {\n const svg = document.createElementNS(SVG_NS, 'svg');\n svg.setAttribute('class', className);\n svg.setAttribute('fill', 'none');\n svg.setAttribute('viewBox', '0 0 24 24');\n svg.setAttribute('stroke', 'currentColor');\n svg.setAttribute('stroke-width', '2');\n const path = document.createElementNS(SVG_NS, 'path');\n path.setAttribute('stroke-linecap', 'round');\n if (name !== 'loading') path.setAttribute('stroke-linejoin', 'round');\n path.setAttribute('d', GLYPHS[name]);\n svg.appendChild(path);\n return svg;\n}\n\nfunction div(className: string, text?: string): HTMLDivElement {\n const el = document.createElement('div');\n el.className = className;\n if (text !== undefined) el.textContent = text;\n return el;\n}\n\n// ── Toast (operation feedback) — top-center, brief ──\n\nfunction showToast(variant: Tone, message: string,\n opts?: ToastOptions): { dismiss: () => void } {\n const opts_ = { ...defaults, ...opts };\n const placement: ToastPlacement = opts_.placement ?? 'top';\n const sticky = opts_.sticky ?? false;\n\n // A repeat of a message already on screen refreshes it instead of stacking.\n const key = `${placement}|${variant}|${message}`;\n if (opts_.dedupe) {\n const existing = live.get(key);\n if (existing) { existing.restart(); return { dismiss: existing.dismiss }; }\n }\n\n // A loading toast is the START of an operation — the sound belongs to the\n // outcome, which follows in a moment.\n if (variant !== 'loading') {\n import('../utils/sounds').then(s => {\n if (variant === 'success') s.playSuccess();\n else if (variant === 'error') s.playError();\n else s.playNotification();\n }).catch(() => {});\n }\n\n const container = placement === 'bottom'\n ? getOrCreate(TOAST_BOTTOM_CONTAINER_ID, 'fixed bottom-4 left-1/2 -translate-x-1/2 z-[9999] flex flex-col-reverse gap-2 items-center pointer-events-none')\n : getOrCreate(TOAST_CONTAINER_ID, 'fixed top-4 left-1/2 -translate-x-1/2 z-[9999] flex flex-col gap-2 items-center pointer-events-none');\n\n // Enter from the edge the toast is anchored to, so it reads as arriving from\n // off-screen rather than drifting in from the wrong direction.\n const offscreen = placement === 'bottom' ? 'translateY(10px)' : 'translateY(-10px)';\n\n // The tone is a CLASS: `.rosh-tone-warning` and friends in ui.css colour the\n // glyph through `--rosh-toast-*`. Warning is amber and deliberately not the\n // error red: a warning is something to read before continuing, not something\n // that failed, and painting both red teaches people to ignore the colour.\n const el = div('pointer-events-auto cursor-pointer rosh-toast');\n el.style.opacity = '0';\n el.style.transform = `${offscreen} scale(0.95)`;\n // A toast IS a status message, and it appears without the user moving focus\n // — so without a live region it is announced to nobody. `alert` is\n // assertive and interrupts whatever is being read, which is right for a\n // failure and wrong for a confirmation the user already expected.\n el.setAttribute('role', variant === 'error' ? 'alert' : 'status');\n // Read the whole toast, not the word that changed. Without it a repeat that\n // only alters a number announces the number alone.\n el.setAttribute('aria-atomic', 'true');\n\n el.appendChild(glyph(variant, `h-4 w-4 rosh-tone-${variant}${variant === 'loading' ? ' animate-spin' : ''}`));\n const span = document.createElement('span');\n span.textContent = message;\n el.appendChild(span);\n\n container.appendChild(el);\n requestAnimationFrame(() => { el.style.opacity = '1'; el.style.transform = 'translateY(0) scale(1)'; });\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n let gone = false;\n const dismiss = () => {\n if (gone) return;\n gone = true;\n if (timer) clearTimeout(timer);\n live.delete(key);\n el.style.opacity = '0';\n el.style.transform = `${offscreen} scale(0.95)`;\n setTimeout(() => el.remove(), FADE_MS);\n };\n\n // Info messages are usually a sentence (\"no matches — check X\"); give them a\n // beat longer to read than the terse success/error confirmations.\n const duration = opts_.duration ?? (variant === 'info' ? 4500 : 3000);\n const restart = () => {\n if (timer) clearTimeout(timer);\n if (!sticky) timer = setTimeout(dismiss, duration);\n };\n restart();\n\n // Hold the timer while the pointer rests on it. Three seconds is enough to\n // read a confirmation and not enough to read an address someone leaned in\n // for — and a toast that vanishes as you reach for it cannot be re-read at\n // all, since there is no history to open.\n el.addEventListener('mouseenter', () => { if (timer) clearTimeout(timer); });\n el.addEventListener('mouseleave', () => { if (!gone) restart(); });\n\n // Tap anywhere on the toast to dismiss it. A toast has no other click\n // affordance, and a sticky one has no other exit at all — `notify` has\n // behaved this way since it existed.\n el.addEventListener('click', dismiss);\n\n live.set(key, { el, restart, dismiss });\n // Internal handle — `toast.promise` swaps its loading toast for the outcome.\n return { dismiss };\n}\n\n// ── Notification (system alert) — top-right, stays longer ──\n\nfunction showNotification(message: string, opts?: { duration?: number }) {\n import('../utils/sounds').then(s => s.playNotification()).catch(() => {});\n\n const container = getOrCreate(NOTIF_CONTAINER_ID, 'fixed top-4 right-4 z-[9999] flex flex-col gap-3 items-end pointer-events-none');\n\n const el = div('pointer-events-auto cursor-pointer rosh-notif');\n el.style.opacity = '0';\n el.style.transform = 'translateX(30px) scale(0.95)';\n\n const badge = div('rosh-notif-badge');\n badge.appendChild(glyph('bell', 'h-5 w-5'));\n const body = div('rosh-notif-body');\n // The heading is the app's catalog word — read here, outside React.\n body.append(div('rosh-notif-title', currentShellStrings().toast.notification), div('notif-msg rosh-notif-msg', message));\n // The close button's hover colour is a `:hover` rule in ui.css. It was an\n // `onmouseenter` attribute — inline script, refused by a strict CSP.\n const close = document.createElement('button');\n close.className = 'rosh-notif-close';\n close.textContent = '×';\n el.append(badge, body, close);\n\n const dismiss = () => {\n el.style.opacity = '0';\n el.style.transform = 'translateX(30px) scale(0.95)';\n setTimeout(() => el.remove(), FADE_MS);\n };\n\n close.addEventListener('click', (e) => { e.stopPropagation(); dismiss(); });\n el.addEventListener('click', dismiss);\n\n container.appendChild(el);\n requestAnimationFrame(() => { el.style.opacity = '1'; el.style.transform = 'translateX(0) scale(1)'; });\n\n setTimeout(dismiss, opts?.duration ?? 10000);\n}\n\nconst toast = {\n success: (message: string, opts?: ToastOptions) => showToast('success', message, opts),\n error: (message: string, opts?: ToastOptions) => showToast('error', message, opts),\n /**\n * Something the user should read before continuing — a partial success, a\n * setting that will not apply until they do something else. Distinct from\n * `error`, which is \"this did not happen\".\n */\n warning: (message: string, opts?: ToastOptions) => showToast('warning', message, opts),\n info: (message: string, opts?: ToastOptions) => showToast('info', message, opts),\n // Persistent top-right notification card (the old toast.info presentation).\n notify: (message: string, opts?: { duration?: number }) => showNotification(message, opts),\n /**\n * One toast for one async operation: a spinning \"loading\" toast while `p`\n * is pending, swapped for a success or error toast when it settles. The\n * promise is returned untouched, so `await toast.promise(save(), …)` still\n * throws to the caller — this narrates the operation, it does not handle it.\n *\n * `error` is REQUIRED, and there is deliberately no fallback that prints\n * the exception: a raw `e.message` in a toast is how internals leak to the\n * screen (the same reasoning that keeps ErrorBoundary's stack behind\n * `showDetails`). The success/error messages may be functions of the\n * resolved value / the rejection, for the \"Saved 3 rows\" case.\n */\n promise: <T,>(\n p: Promise<T>,\n msgs: {\n loading: string;\n success: string | ((value: T) => string);\n error: string | ((error: unknown) => string);\n },\n opts?: ToastOptions,\n ): Promise<T> => {\n const pending = showToast('loading', msgs.loading, { ...opts, sticky: true });\n p.then(\n value => {\n pending.dismiss();\n showToast('success', typeof msgs.success === 'function' ? msgs.success(value) : msgs.success, opts);\n },\n error => {\n pending.dismiss();\n showToast('error', typeof msgs.error === 'function' ? msgs.error(error) : msgs.error, opts);\n },\n );\n return p;\n },\n /**\n * Set defaults for every subsequent toast. Call once at app startup; later\n * calls replace the whole object rather than merging, so a consumer sets its\n * policy in one place. Per-call options still take precedence.\n */\n configure: (next: ToastOptions) => { defaults = { ...next }; },\n};\n\nexport default toast;\n"]}
@@ -1,5 +1,5 @@
1
1
  import { escapeHtml } from './chunk-MGGY74RY.js';
2
- import { useShellStrings } from './chunk-72CQWN7O.js';
2
+ import { useShellStrings } from './chunk-YE6GUDGP.js';
3
3
  import { useBrowserLayoutEffect } from './chunk-IRWSRJ4K.js';
4
4
  import { useRef, useState, useEffect, useCallback, useMemo } from 'react';
5
5
  import { createPortal } from 'react-dom';
@@ -818,5 +818,5 @@ function EditableGrid({ columns, data, onChange, onColumnsChange, fixedRows = fa
818
818
  }
819
819
 
820
820
  export { EditableGrid, withSpareRow };
821
- //# sourceMappingURL=chunk-T4VQHMG4.js.map
822
- //# sourceMappingURL=chunk-T4VQHMG4.js.map
821
+ //# sourceMappingURL=chunk-VNJJCH5A.js.map
822
+ //# sourceMappingURL=chunk-VNJJCH5A.js.map