efficient-shell 4.132.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.
- package/README.md +142 -18
- package/dist/{Browser-XPVJ2K7B.js → Browser-ZNX4WFUH.js} +13 -13
- package/dist/{Browser-XPVJ2K7B.js.map → Browser-ZNX4WFUH.js.map} +1 -1
- package/dist/{Calculator-ZXJPXG7F.js → Calculator-6FGNKOXU.js} +11 -11
- package/dist/{Calculator-ZXJPXG7F.js.map → Calculator-6FGNKOXU.js.map} +1 -1
- package/dist/ConfirmDialog-NPQMIGUR.js +4 -0
- package/dist/{ConfirmDialog-MTC7QMUJ.js.map → ConfirmDialog-NPQMIGUR.js.map} +1 -1
- package/dist/{CurrencyConverter-WSJ2BBSX.js → CurrencyConverter-FF7EXRYL.js} +11 -11
- package/dist/{CurrencyConverter-WSJ2BBSX.js.map → CurrencyConverter-FF7EXRYL.js.map} +1 -1
- package/dist/{Documents-ONGSXUJI.js → Documents-H3R7XSBH.js} +13 -13
- package/dist/{Documents-ONGSXUJI.js.map → Documents-H3R7XSBH.js.map} +1 -1
- package/dist/{Files-GQZMFWXQ.js → Files-ZHJIAUC2.js} +19 -19
- package/dist/{Files-GQZMFWXQ.js.map → Files-ZHJIAUC2.js.map} +1 -1
- package/dist/{Notepad-HM73XAM4.js → Notepad-VE7ISWRQ.js} +14 -14
- package/dist/{Notepad-HM73XAM4.js.map → Notepad-VE7ISWRQ.js.map} +1 -1
- package/dist/PdfViewer-SQBFYAGW.js +5 -0
- package/dist/{PdfViewer-M2YGZIYX.js.map → PdfViewer-SQBFYAGW.js.map} +1 -1
- package/dist/{PomodoroTimer-DG3LI5CX.js → PomodoroTimer-IR3WREED.js} +13 -13
- package/dist/{PomodoroTimer-DG3LI5CX.js.map → PomodoroTimer-IR3WREED.js.map} +1 -1
- package/dist/{Preview-TUSV4UMO.js → Preview-DAQGRNRM.js} +16 -16
- package/dist/{Preview-TUSV4UMO.js.map → Preview-DAQGRNRM.js.map} +1 -1
- package/dist/{Sidebar-BPU7RCZP.js → Sidebar-YW6H4NDJ.js} +3 -3
- package/dist/{Sidebar-BPU7RCZP.js.map → Sidebar-YW6H4NDJ.js.map} +1 -1
- package/dist/{Spreadsheet-3WTXCHYQ.js → Spreadsheet-HOCPHJOC.js} +14 -14
- package/dist/{Spreadsheet-3WTXCHYQ.js.map → Spreadsheet-HOCPHJOC.js.map} +1 -1
- package/dist/{Stock-YMPOMUAW.js → Stock-LVRQPURG.js} +11 -11
- package/dist/{Stock-YMPOMUAW.js.map → Stock-LVRQPURG.js.map} +1 -1
- package/dist/{Weather-NZO3CWLH.js → Weather-LZQGVDJL.js} +11 -11
- package/dist/{Weather-NZO3CWLH.js.map → Weather-LZQGVDJL.js.map} +1 -1
- package/dist/{WorldClock-HUC6D3AD.js → WorldClock-XPD7WOZT.js} +11 -11
- package/dist/{WorldClock-HUC6D3AD.js.map → WorldClock-XPD7WOZT.js.map} +1 -1
- package/dist/apps/index.d.ts +2 -2
- package/dist/apps/index.js +30 -30
- package/dist/apps/index.js.map +1 -1
- package/dist/brand.css +1 -1
- package/dist/{chunk-CT3HVZRZ.js → chunk-5O42JXQE.js} +2 -2
- package/dist/chunk-5O42JXQE.js.map +1 -0
- package/dist/{chunk-CGXMWHQW.js → chunk-5RPTS3BT.js} +3 -3
- package/dist/{chunk-CGXMWHQW.js.map → chunk-5RPTS3BT.js.map} +1 -1
- package/dist/{chunk-D276EMXS.js → chunk-6XJEJWNJ.js} +7 -3
- package/dist/chunk-6XJEJWNJ.js.map +1 -0
- package/dist/{chunk-I464GYJJ.js → chunk-BE7FZOFU.js} +2 -2
- package/dist/chunk-BE7FZOFU.js.map +1 -0
- package/dist/{chunk-CJYO4RVV.js → chunk-COWYSHG2.js} +5 -5
- package/dist/chunk-COWYSHG2.js.map +1 -0
- package/dist/{chunk-3L463T5H.js → chunk-DVZWYWSD.js} +2 -2
- package/dist/chunk-DVZWYWSD.js.map +1 -0
- package/dist/{chunk-MQPX6ERJ.js → chunk-E7TB6WD2.js} +4 -4
- package/dist/{chunk-MQPX6ERJ.js.map → chunk-E7TB6WD2.js.map} +1 -1
- package/dist/{chunk-SEWF4O3H.js → chunk-FIHOGVUP.js} +3 -3
- package/dist/chunk-FIHOGVUP.js.map +1 -0
- package/dist/{chunk-TQURTLK2.js → chunk-HUBJAUGG.js} +5 -5
- package/dist/{chunk-TQURTLK2.js.map → chunk-HUBJAUGG.js.map} +1 -1
- package/dist/{chunk-XH3ELPQZ.js → chunk-I2TAAJHS.js} +3 -3
- package/dist/chunk-I2TAAJHS.js.map +1 -0
- package/dist/{chunk-ZKHD4J73.js → chunk-ICTRCKUE.js} +10 -10
- package/dist/chunk-ICTRCKUE.js.map +1 -0
- package/dist/{chunk-E3TWIN7Z.js → chunk-JOPU6WPY.js} +7 -6
- package/dist/chunk-JOPU6WPY.js.map +1 -0
- package/dist/{chunk-7MA5XBWD.js → chunk-PVPPAWVM.js} +5 -5
- package/dist/{chunk-7MA5XBWD.js.map → chunk-PVPPAWVM.js.map} +1 -1
- package/dist/{chunk-FONEQRBH.js → chunk-R7QGMK6N.js} +3 -3
- package/dist/{chunk-FONEQRBH.js.map → chunk-R7QGMK6N.js.map} +1 -1
- package/dist/{chunk-LQTVTOSY.js → chunk-RT6DSB5Y.js} +45 -19
- package/dist/chunk-RT6DSB5Y.js.map +1 -0
- package/dist/{chunk-BFBWLCO6.js → chunk-TW7LPMVR.js} +2 -2
- package/dist/chunk-TW7LPMVR.js.map +1 -0
- package/dist/{chunk-EENVXIVU.js → chunk-UTYKLGGT.js} +3 -3
- package/dist/{chunk-EENVXIVU.js.map → chunk-UTYKLGGT.js.map} +1 -1
- package/dist/{chunk-KWEX4ZAF.js → chunk-VNJJCH5A.js} +3 -3
- package/dist/{chunk-KWEX4ZAF.js.map → chunk-VNJJCH5A.js.map} +1 -1
- package/dist/{chunk-TFXJRHJV.js → chunk-XJHZWSKV.js} +3 -3
- package/dist/{chunk-TFXJRHJV.js.map → chunk-XJHZWSKV.js.map} +1 -1
- package/dist/{chunk-VWZIGZFF.js → chunk-Y3GYJ7YR.js} +4 -4
- package/dist/{chunk-VWZIGZFF.js.map → chunk-Y3GYJ7YR.js.map} +1 -1
- package/dist/{chunk-BJ34QOR3.js → chunk-YA4EE7ST.js} +842 -272
- package/dist/chunk-YA4EE7ST.js.map +1 -0
- package/dist/{chunk-CLGNTGC5.js → chunk-YDHXWCCJ.js} +3 -3
- package/dist/{chunk-CLGNTGC5.js.map → chunk-YDHXWCCJ.js.map} +1 -1
- package/dist/{chunk-7IN7YZAL.js → chunk-YE6GUDGP.js} +4 -3
- package/dist/chunk-YE6GUDGP.js.map +1 -0
- package/dist/chunk-ZS7PMGUW.js +7 -0
- package/dist/{chunk-JMKYYPOF.js.map → chunk-ZS7PMGUW.js.map} +1 -1
- package/dist/file-intake/index.js +2 -2
- package/dist/{index-COwksV6G.d.ts → index-DVI9DMSu.d.ts} +383 -20
- package/dist/index.d.ts +3 -3
- package/dist/index.js +39 -39
- package/dist/index.js.map +1 -1
- package/dist/map/index.js +1 -1
- package/dist/markdown/index.d.ts +4 -4
- package/dist/markdown/index.js.map +1 -1
- package/dist/markup/index.d.ts +2 -2
- package/dist/markup/index.js.map +1 -1
- package/dist/page-tabs/index.js +5 -5
- package/dist/shell.css +1 -1
- package/dist/styles.css +4 -4
- package/dist/themes.css +3 -3
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +14 -14
- package/dist/ui.css +311 -18
- package/package.json +1 -1
- package/dist/ConfirmDialog-MTC7QMUJ.js +0 -4
- package/dist/PdfViewer-M2YGZIYX.js +0 -5
- package/dist/chunk-3L463T5H.js.map +0 -1
- package/dist/chunk-7IN7YZAL.js.map +0 -1
- package/dist/chunk-BFBWLCO6.js.map +0 -1
- package/dist/chunk-BJ34QOR3.js.map +0 -1
- package/dist/chunk-CJYO4RVV.js.map +0 -1
- package/dist/chunk-CT3HVZRZ.js.map +0 -1
- package/dist/chunk-D276EMXS.js.map +0 -1
- package/dist/chunk-E3TWIN7Z.js.map +0 -1
- package/dist/chunk-I464GYJJ.js.map +0 -1
- package/dist/chunk-JMKYYPOF.js +0 -7
- package/dist/chunk-LQTVTOSY.js.map +0 -1
- package/dist/chunk-SEWF4O3H.js.map +0 -1
- package/dist/chunk-XH3ELPQZ.js.map +0 -1
- package/dist/chunk-ZKHD4J73.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { onOverlayOpen, Z_LAYERS, registerModalEscapeInterceptor } from './chunk-
|
|
2
|
-
import { glassStyle, GLASS_DIVIDER } from './chunk-
|
|
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-
|
|
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-
|
|
503
|
-
//# sourceMappingURL=chunk-
|
|
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
|
-
import { setPdfPreview } from './chunk-
|
|
2
|
-
import { setSpreadsheetPreview } from './chunk-
|
|
3
|
-
import { toast_default } from './chunk-
|
|
1
|
+
import { setPdfPreview } from './chunk-DVZWYWSD.js';
|
|
2
|
+
import { setSpreadsheetPreview } from './chunk-5O42JXQE.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-
|
|
46
|
-
//# sourceMappingURL=chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
102
|
-
//# sourceMappingURL=chunk-
|
|
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-FONEQRBH.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,7 +1,7 @@
|
|
|
1
1
|
import { forwardRef, useId } from 'react';
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
3
|
|
|
4
|
-
// src/forms/
|
|
4
|
+
// src/forms/buttonStyles.ts
|
|
5
5
|
var BUTTON_BASE = "inline-flex items-center justify-center rounded-md font-medium transition-colors rosh-focus-ring focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-60";
|
|
6
6
|
var BUTTON_VARIANTS = {
|
|
7
7
|
primary: "bg-blue-600 text-white shadow-sm hover:bg-blue-700 focus-visible:ring-[color:var(--rosh-focus-ring-strong)]",
|
|
@@ -15,9 +15,10 @@ var BUTTON_VARIANTS = {
|
|
|
15
15
|
* your password?", "Use a different address" — where a boxed button would
|
|
16
16
|
* claim more of the page than the action deserves.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
* can be middle-clicked and read off the status
|
|
20
|
-
* that merely look quiet.
|
|
18
|
+
* `Button` with this variant is still a <button>. Anything that NAVIGATES
|
|
19
|
+
* should be an anchor, so it can be middle-clicked and read off the status
|
|
20
|
+
* bar; this is for actions that merely look quiet. An anchor that wants this
|
|
21
|
+
* look takes it from `buttonClasses({ variant: 'link' })`.
|
|
21
22
|
*/
|
|
22
23
|
link: "text-blue-600 underline-offset-4 hover:underline focus-visible:ring-[color:var(--rosh-focus-ring)]"
|
|
23
24
|
};
|
|
@@ -52,6 +53,42 @@ var LINK_SIZES = {
|
|
|
52
53
|
"touch-lg": "gap-2 text-lg",
|
|
53
54
|
"touch-xl": "gap-3 text-2xl"
|
|
54
55
|
};
|
|
56
|
+
var SQUARE = {
|
|
57
|
+
sm: "h-6 w-6",
|
|
58
|
+
md: "h-8 w-8",
|
|
59
|
+
lg: "h-10 w-10",
|
|
60
|
+
"touch-sm": "h-11 w-11",
|
|
61
|
+
"touch": "h-14 w-14",
|
|
62
|
+
"touch-lg": "h-16 w-16",
|
|
63
|
+
"touch-xl": "h-20 w-20"
|
|
64
|
+
};
|
|
65
|
+
function buttonClasses({
|
|
66
|
+
variant = "primary",
|
|
67
|
+
size = "md",
|
|
68
|
+
block = false,
|
|
69
|
+
className = ""
|
|
70
|
+
} = {}) {
|
|
71
|
+
const link = variant === "link";
|
|
72
|
+
return [
|
|
73
|
+
// `link` takes the base WITHOUT the box: no rounding, no padding from the
|
|
74
|
+
// size table. It cannot cancel those by overriding — two padding utilities
|
|
75
|
+
// in one class attribute resolve by compiled-stylesheet order, not by the
|
|
76
|
+
// order they were written — so it never receives them, and keeps only the
|
|
77
|
+
// size's gap and text size.
|
|
78
|
+
link ? LINK_BASE : BUTTON_BASE,
|
|
79
|
+
BUTTON_VARIANTS[variant],
|
|
80
|
+
link ? LINK_SIZES[size] : SIZES[size],
|
|
81
|
+
block ? "w-full" : "",
|
|
82
|
+
className
|
|
83
|
+
].filter(Boolean).join(" ");
|
|
84
|
+
}
|
|
85
|
+
function iconButtonClasses({
|
|
86
|
+
variant = "ghost",
|
|
87
|
+
size = "md",
|
|
88
|
+
className = ""
|
|
89
|
+
} = {}) {
|
|
90
|
+
return [BUTTON_BASE, BUTTON_VARIANTS[variant], SQUARE[size], "shrink-0 p-0", className].filter(Boolean).join(" ");
|
|
91
|
+
}
|
|
55
92
|
function Spinner() {
|
|
56
93
|
return /* @__PURE__ */ jsxs("svg", { className: "h-3.5 w-3.5 animate-spin", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
|
|
57
94
|
/* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
@@ -84,18 +121,7 @@ var Button = forwardRef(function Button2({
|
|
|
84
121
|
disabled: isDisabled,
|
|
85
122
|
"aria-busy": loading || void 0,
|
|
86
123
|
"aria-describedby": reasonId,
|
|
87
|
-
className:
|
|
88
|
-
// `link` takes the base WITHOUT the box: no rounding, no padding from
|
|
89
|
-
// the size table. It cannot cancel those by overriding — two padding
|
|
90
|
-
// utilities in one class attribute resolve by compiled-stylesheet
|
|
91
|
-
// order, not by the order they were written — so it never receives
|
|
92
|
-
// them, and keeps only the size's gap and text size.
|
|
93
|
-
variant === "link" ? LINK_BASE : BUTTON_BASE,
|
|
94
|
-
BUTTON_VARIANTS[variant],
|
|
95
|
-
variant === "link" ? LINK_SIZES[size] : SIZES[size],
|
|
96
|
-
block ? "w-full" : "",
|
|
97
|
-
className
|
|
98
|
-
].filter(Boolean).join(" "),
|
|
124
|
+
className: buttonClasses({ variant, size, block, className }),
|
|
99
125
|
...rest,
|
|
100
126
|
children: [
|
|
101
127
|
loading ? /* @__PURE__ */ jsx(Spinner, {}) : leftIcon,
|
|
@@ -112,6 +138,6 @@ var Button = forwardRef(function Button2({
|
|
|
112
138
|
});
|
|
113
139
|
var Button_default = Button;
|
|
114
140
|
|
|
115
|
-
export {
|
|
116
|
-
//# sourceMappingURL=chunk-
|
|
117
|
-
//# sourceMappingURL=chunk-
|
|
141
|
+
export { Button_default, buttonClasses, iconButtonClasses };
|
|
142
|
+
//# sourceMappingURL=chunk-RT6DSB5Y.js.map
|
|
143
|
+
//# sourceMappingURL=chunk-RT6DSB5Y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/forms/buttonStyles.ts","../src/forms/Button.tsx"],"names":["Button"],"mappings":"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","file":"chunk-RT6DSB5Y.js","sourcesContent":["/**\n * Button styling — the class strings `Button` and `IconButton` wear, and the\n * two public functions that compose them, `buttonClasses` and\n * `iconButtonClasses`. Both components call those functions, so the look a host\n * borrows and the look the kit renders are one string, not two that agree.\n *\n * A module of its own, beside forms/styles.ts and for the reason that one\n * exists: the look is a value a host needs WITHOUT the component. A link drawn\n * as a button — \"Add a business\", \"View invoice\" — has to be an `<a>`, so it\n * can be middle-clicked, opened in a new tab and read off the status bar, and\n * `Button` can only render a `<button>`. Until these were exported, a host's\n * only way to that look was to copy the strings, and a copy is wrong from the\n * first release that touches the original.\n *\n * Named after `inputClasses`, which is the same idea for a text field. Not IN\n * forms/styles.ts: that module is the input look, its strings are pinned as\n * contract by `tests/touchPrimitives.test.tsx`, and the button's types and\n * ladders would double it to say something else. And React-free, like that\n * one: nothing here renders, so nothing here imports React. That keeps the door\n * open to re-exporting both from an entry without `\"use client\"` — the only way\n * a server component could ever CALL them. Through `efficient-shell/ui` they are\n * client references like every kit function (README → \"Server rendering\").\n */\n\n/**\n * `ghost-danger` is a destructive action sitting in a row of ordinary ones —\n * Clear, Discard, Remove — where solid `danger` would be wrong. Two solid\n * shouting buttons on one screen means neither is read, so the destructive one\n * borrows `ghost`'s weight and keeps only the colour. The colour is the point:\n * it lets someone tell what the button costs WITHOUT reading it, which under\n * time pressure is what actually happens.\n */\nexport type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'ghost-danger' | 'danger' | 'link';\n/**\n * `sm`/`md`/`lg` are the desktop scale. The `touch-*` rungs are for a finger on\n * glass — a till, a warehouse tablet — and are a SEPARATE ladder on purpose:\n * a till's idea of \"medium\" is 56px, and naming it `md` would have made the\n * same word mean two sizes depending on which app you were reading.\n *\n * Nothing picks a touch size automatically. An app that wants them asks for\n * them, so a desktop portal cannot grow finger-sized buttons by accident.\n */\nexport type ButtonSize = 'sm' | 'md' | 'lg' | 'touch-sm' | 'touch' | 'touch-lg' | 'touch-xl';\n\n/**\n * Shared with `IconButton`'s composition below, which builds its own class\n * string rather than passing a square size through `buttonClasses`' `className`\n * — two competing padding utilities in one attribute resolve by\n * compiled-stylesheet order, not by the order they were written (the reasoning\n * in forms/styles.ts applies here identically).\n *\n * The PARTS stay module-private, and that is deliberate: the public way to the\n * look is the composed string. Composing is where the rules live — the `link`\n * variant does not override the box, it never receives it — and a host\n * assembling `BUTTON_BASE + BUTTON_VARIANTS.link + …` by hand would put the\n * box back. A host restyling the parts would also be a host the theme system\n * cannot reach; one wearing the whole string is reached like the kit is.\n */\nconst BUTTON_BASE =\n 'inline-flex items-center justify-center rounded-md font-medium transition-colors ' +\n 'rosh-focus-ring focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-60';\n\nconst BUTTON_VARIANTS: Record<ButtonVariant, string> = {\n primary: 'bg-blue-600 text-white shadow-sm hover:bg-blue-700 focus-visible:ring-[color:var(--rosh-focus-ring-strong)]',\n secondary: 'border border-gray-300 bg-white text-gray-700 shadow-sm hover:bg-gray-50 focus-visible:ring-[color:var(--rosh-focus-ring)]',\n ghost: 'text-gray-700 hover:bg-gray-100 focus-visible:ring-[color:var(--rosh-focus-ring)]',\n 'ghost-danger': 'text-red-600 hover:bg-red-50 focus-visible:ring-[color:var(--rosh-focus-ring-invalid)]',\n danger: 'bg-red-600 text-white shadow-sm hover:bg-red-700 focus-visible:ring-[color:var(--rosh-focus-ring-invalid-strong)]',\n /**\n * A button that reads as a link: no box, no padding, underlined on hover.\n * For an action that belongs in running text or beside a field — \"Forgot\n * your password?\", \"Use a different address\" — where a boxed button would\n * claim more of the page than the action deserves.\n *\n * `Button` with this variant is still a <button>. Anything that NAVIGATES\n * should be an anchor, so it can be middle-clicked and read off the status\n * bar; this is for actions that merely look quiet. An anchor that wants this\n * look takes it from `buttonClasses({ variant: 'link' })`.\n */\n link: 'text-blue-600 underline-offset-4 hover:underline focus-visible:ring-[color:var(--rosh-focus-ring)]',\n};\n\n// A Record keyed by the union, not a ternary chain: adding a member without a\n// size here is then a compile error rather than a silent fall-through to the\n// default branch. `sm`/`md` are byte-for-byte what they have always been, and\n// `tests/touchPrimitives.test.tsx` pins them against a captured literal.\n//\n// Every rung is also asserted to be the same height as the IconButton rung of\n// the same name (`tests/buttonLinkAndLarge.test.tsx`) — pinning the two sides\n// separately is how `lg` first shipped 4px short of its square twin.\n//\n// The touch rungs carry an explicit `h-*` where the desktop pair uses `py-*`.\n// That is deliberate: a hit target has to be a guaranteed height, not a height\n// implied by the current font size. They are separate map entries and never\n// concatenated with each other, so no two competing utilities land in one class\n// attribute.\nconst SIZES: Record<ButtonSize, string> = {\n sm: 'gap-1 px-2.5 py-1 text-xs',\n md: 'gap-1.5 px-3 py-1.5 text-sm',\n // `py-2.5`, not `py-2`: this rung has to come out 40px to match IconButton's\n // `h-10`, which is the promise IconButton's docblock makes for every rung.\n // `py-2` gives 36px (20px line box + 8px twice) and puts a 4px step between a\n // Button and an IconButton sitting in the same row.\n lg: 'gap-2 px-4 py-2.5 text-sm',\n // 44px is the WCAG 2.5.5 floor, and a floor is not a comfortable size. Use it\n // ONLY for chrome that sits outside the task — switching screens, ending a\n // shift — and never on the path someone is actually working through. It\n // exists so a dense toolbar has something legitimate to reach for instead of\n // borrowing the desktop `sm`, which is half this height.\n 'touch-sm': 'gap-1.5 h-11 px-4 text-base',\n 'touch': 'gap-2 h-14 px-5 text-base', // 56px — the comfortable default\n 'touch-lg': 'gap-2 h-16 px-6 text-lg', // 64px — primary action on a page\n 'touch-xl': 'gap-3 h-20 px-8 text-2xl', // 80px — keypad keys, tender\n};\n\n// Everything BUTTON_BASE has except the box: no rounding (there is nothing to\n// round) and no `justify-center` (a link sits in a line of text, it does not\n// centre itself in a shape).\nconst LINK_BASE =\n 'inline-flex items-center font-medium transition-colors ' +\n 'rosh-focus-ring focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-60';\n\n// Only the gap and the text size survive from each rung — the padding is what\n// the link variant exists to shed.\nconst LINK_SIZES: Record<ButtonSize, string> = {\n sm: 'gap-1 text-xs',\n md: 'gap-1.5 text-sm',\n lg: 'gap-2 text-sm',\n 'touch-sm': 'gap-1.5 text-base',\n 'touch': 'gap-2 text-base',\n 'touch-lg': 'gap-2 text-lg',\n 'touch-xl': 'gap-3 text-2xl',\n};\n\n/**\n * Square, and matching `Button`'s heights rung for rung: `sm` is 24px in both\n * (py-1 either side of a 16px line box), `md` 32px, and the touch rungs carry\n * the same explicit heights for the same reason — a hit target has to be a\n * guaranteed size, not one implied by the current font.\n *\n * A Record keyed by the union, so a new ButtonSize member without an entry here\n * is a compile error rather than a silent fall-through.\n */\nconst SQUARE: Record<ButtonSize, string> = {\n sm: 'h-6 w-6',\n md: 'h-8 w-8',\n lg: 'h-10 w-10',\n 'touch-sm': 'h-11 w-11',\n 'touch': 'h-14 w-14',\n 'touch-lg': 'h-16 w-16',\n 'touch-xl': 'h-20 w-20',\n};\n\n/** What `buttonClasses` takes: `Button`'s look props, and nothing that behaves. */\nexport interface ButtonClassesOptions {\n /** Defaults to `primary`, as `Button`'s does. */\n variant?: ButtonVariant;\n /** Defaults to `md`, as `Button`'s does. */\n size?: ButtonSize;\n /** Stretch to the full width of the container (`w-full`). */\n block?: boolean;\n /**\n * Appended last, exactly as `Button` appends its `className`. For layout —\n * a margin, `shrink-0` — never a second padding, colour or display: those\n * compete with the string's own and resolve by compiled-stylesheet order.\n */\n className?: string;\n}\n\n/**\n * The class string `Button` puts on its `<button>` for the same inputs, with\n * the same defaults. `Button` renders through this function, so the two cannot\n * disagree; `tests/buttonClasses.test.tsx` holds them equal for every variant\n * and size.\n *\n * It is for an element that has to look like a button and cannot be one —\n * above all a link, a router's link included:\n *\n * <Link href=\"/organisations/new\" className={buttonClasses({ variant: 'secondary' })}>\n * Add a business\n * </Link>\n *\n * A `<button>` gets some of its behaviour from being a button, and the string\n * cannot hand that to an `<a>`. What the anchor needs instead:\n *\n * - **Disabled is not an attribute of `<a>`.** The string's `disabled:`\n * variants match `:disabled`, which an anchor never is: on an `<a>` they are\n * inert — harmless, and no help. A link that cannot be followed should stop\n * being one: drop the `href`, which leaves it neither focusable nor\n * followable — and without a role, so give it `role=\"link\"` back with\n * `aria-disabled=\"true\"`, and it is announced as a link that is unavailable\n * rather than as plain text. Dim it with `opacity-60 cursor-not-allowed` in\n * `className`, which compete with nothing here. A Next `<Link>` requires an `href`, so a\n * disabled one is a bare `<a>` — or, simpler and better, a disabled `Button`,\n * which does all of that and also takes `disabledReason`.\n * - **The cursor differs.** The string sets none (only the inert\n * `disabled:cursor-not-allowed`). An `<a href>` shows the pointer from the\n * browser's own stylesheet; a `<button>` shows whatever the host's preflight\n * gives it, which under Tailwind v4 is the plain arrow. So a row with one of\n * each shows two cursors. Settle it once, for both, in the host's base layer\n * — `button:not(:disabled) { cursor: pointer }` is the line in Tailwind's\n * own v4 upgrade guide — rather than per call, where it would drift.\n * - **Display is `inline-flex`, and must stay so.** An `<a>` is inline, and an\n * inline box ignores `height` and takes no room for vertical padding: the\n * string's `inline-flex` is what gives it the button's box, its touch-rung\n * height and the gap between an icon and its label. Never pass another\n * display utility (`flex`, `block`) in `className` — two in one attribute\n * resolve by stylesheet order. For full width ask for `block: true`, which\n * keeps `inline-flex` and adds `w-full`.\n * - **Without preflight, an `<a>` keeps its underline.** Tailwind's\n * preflight makes an anchor inherit its text decoration; a host running the\n * kit without it gets the browser's underline under every label, so add\n * `no-underline` there.\n *\n * `loading`, `leftIcon`/`rightIcon` and `disabledReason` are `Button`'s alone.\n * Put an icon in as a child — the size's `gap-*` spaces it as the slots do.\n *\n * Through `efficient-shell/ui` this is a client reference, like every kit\n * function: call it in a `'use client'` module. A server component that\n * imports it receives a reference it cannot call.\n */\nexport function buttonClasses({\n variant = 'primary',\n size = 'md',\n block = false,\n className = '',\n}: ButtonClassesOptions = {}): string {\n const link = variant === 'link';\n return [\n // `link` takes the base WITHOUT the box: no rounding, no padding from the\n // size table. It cannot cancel those by overriding — two padding utilities\n // in one class attribute resolve by compiled-stylesheet order, not by the\n // order they were written — so it never receives them, and keeps only the\n // size's gap and text size.\n link ? LINK_BASE : BUTTON_BASE,\n BUTTON_VARIANTS[variant],\n link ? LINK_SIZES[size] : SIZES[size],\n block ? 'w-full' : '',\n className,\n ].filter(Boolean).join(' ');\n}\n\n/** What `iconButtonClasses` takes: `IconButton`'s look props. */\nexport interface IconButtonClassesOptions {\n /**\n * Defaults to `ghost`, as `IconButton`'s does. `link` is excluded for the\n * reason it is excluded there: it exists to shed the box, and a square icon\n * button is nothing but the box.\n */\n variant?: Exclude<ButtonVariant, 'link'>;\n /** Defaults to `md`. Square, and the same height as `buttonClasses`' rung. */\n size?: ButtonSize;\n /** Appended last; the same rule as `ButtonClassesOptions.className`. */\n className?: string;\n}\n\n/**\n * The class string `IconButton` puts on its `<button>`, for an icon-only link\n * — a row's \"open\" arrow, an \"open in a new tab\" glyph. `IconButton` renders\n * through it, as `Button` does through `buttonClasses`.\n *\n * Everything `buttonClasses` says about an `<a>` holds here, and one thing\n * more, which is the reason `IconButton` is a component of its own: it REQUIRES\n * an `aria-label` in its props, so forgetting the name is a compile error. A\n * class string cannot require anything. An icon-only `<a>` without an\n * `aria-label` is a link with no name, read out as its URL or not at all — put\n * one on it.\n */\nexport function iconButtonClasses({\n variant = 'ghost',\n size = 'md',\n className = '',\n}: IconButtonClassesOptions = {}): string {\n return [BUTTON_BASE, BUTTON_VARIANTS[variant], SQUARE[size], 'shrink-0 p-0', className]\n .filter(Boolean)\n .join(' ');\n}\n","/**\n * Button — the kit's standard button. `primary` uses `bg-blue-600`, which the\n * theme system remaps to the active accent (and to a readable tone in dark\n * mode), so a primary button always follows the user's chosen accent.\n *\n * Controlled by the consumer like any native button (spread `onClick`, `type`,\n * `form`, …); the component only adds the look, a `loading` spinner state, and\n * optional icon slots.\n */\nimport { forwardRef, useId, type ButtonHTMLAttributes, type ReactNode } from 'react';\n\n// The look lives in ./buttonStyles, React-free, so a host can wear it on an\n// element that is not a <button> (`buttonClasses`). The types travel with it\n// and are re-exported here, where every import of them has always pointed.\nimport { buttonClasses, type ButtonSize, type ButtonVariant } from './buttonStyles';\n\nexport type { ButtonSize, ButtonVariant } from './buttonStyles';\n\nexport interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className'> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n /** Show a spinner and disable the button while an action is in flight. */\n loading?: boolean;\n leftIcon?: ReactNode;\n rightIcon?: ReactNode;\n /** Stretch to the full width of the container. */\n block?: boolean;\n className?: string;\n /**\n * Why this button is disabled, rendered as text BESIDE it — never as a\n * `title` tooltip. A tooltip needs a hover, a touchscreen has no hover, and a\n * cashier facing a dead button with no explanation calls a manager.\n *\n * Only rendered while the button is actually disabled (or loading); ignored\n * otherwise, so it is safe to pass unconditionally. Note it changes the\n * rendered shape: the button gains a wrapper element, which matters to a\n * parent `flex gap-*` row or a `:first-child` selector.\n */\n disabledReason?: ReactNode;\n}\n\nfunction Spinner() {\n return (\n <svg className=\"h-3.5 w-3.5 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" />\n <path className=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z\" />\n </svg>\n );\n}\n\nconst Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n { variant = 'primary', size = 'md', loading = false, leftIcon, rightIcon, block = false,\n disabled, type = 'button', children, className = '', disabledReason, ...rest },\n ref,\n) {\n // useId, not a module counter: a counter assigns different ids on the server\n // and the client and breaks hydration. Called unconditionally — it is a hook.\n const generatedId = useId();\n const isDisabled = disabled || loading;\n const showReason = isDisabled && disabledReason != null && disabledReason !== '';\n const reasonId = showReason ? generatedId : undefined;\n\n const button = (\n <button\n ref={ref}\n type={type}\n disabled={isDisabled}\n aria-busy={loading || undefined}\n aria-describedby={reasonId}\n // The one place this component's look is decided — and the same call a\n // host makes to put that look on a link. `loading`, `disabled` and\n // `disabledReason` change the element and its wrapper, never the string.\n className={buttonClasses({ variant, size, block, className })}\n {...rest}\n >\n {loading ? <Spinner /> : leftIcon}\n {children}\n {!loading && rightIcon}\n </button>\n );\n\n // No reason to show ⇒ the bare <button>, byte-identical to every release\n // before this prop existed. Only the disabled-with-a-reason case wraps.\n if (!showReason) return button;\n\n return (\n <span className={block ? 'flex w-full flex-col gap-1' : 'inline-flex flex-col items-start gap-1'}>\n {button}\n <span id={reasonId} className=\"text-xs text-red-600\">{disabledReason}</span>\n </span>\n );\n});\n\nexport default Button;\n"]}
|
|
@@ -12,5 +12,5 @@ function claimBrowserStartUrl(stage) {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export { claimBrowserStartUrl, peekBrowserStartUrl, setBrowserStartUrl };
|
|
15
|
-
//# sourceMappingURL=chunk-
|
|
16
|
-
//# sourceMappingURL=chunk-
|
|
15
|
+
//# sourceMappingURL=chunk-TW7LPMVR.js.map
|
|
16
|
+
//# sourceMappingURL=chunk-TW7LPMVR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/apps/_browserStage.ts"],"names":[],"mappings":";AAeA,IAAI,eAAA,GAA0C,IAAA;AAC9C,IAAI,cAAA,GAAiB,CAAA;AAKd,SAAS,mBAAmB,GAAA,EAAmB;AACpD,EAAA,eAAA,GAAkB,EAAE,KAAA,EAAO,EAAE,cAAA,EAAgB,GAAA,EAAI;AACnD;AAGO,SAAS,mBAAA,GAA8C;AAC5D,EAAA,OAAO,eAAA;AACT;AAKO,SAAS,qBAAqB,KAAA,EAA8B;AACjE,EAAA,IAAI,eAAA,KAAoB,OAAO,eAAA,GAAkB,IAAA;AACnD","file":"chunk-TW7LPMVR.js","sourcesContent":["/**\n * Browser start-URL staging — the tiny consumer-facing surface of the Browser\n * app. Lives apart from Browser.tsx so importing `setBrowserStartUrl` (from\n * `efficient-shell/apps`) never drags the Browser implementation into a host's\n * startup bundle. The Browser window component stays behind its React.lazy\n * dynamic import and drains the stage on mount via the @internal peek/claim\n * helpers below.\n */\n\n/** @internal Staged URL awaiting the next Browser window mount. */\nexport interface PendingStartUrl {\n token: number;\n url: string;\n}\n\nlet pendingStartUrl: PendingStartUrl | null = null;\nlet nextStartToken = 0;\n\n/** Stage a URL for the next Browser window mount — pair with\n * `openPage('/browser')`. Used by \"open this link in the Browser\" flows\n * (e.g. links inside an email body). */\nexport function setBrowserStartUrl(url: string): void {\n pendingStartUrl = { token: ++nextStartToken, url };\n}\n\n/** @internal Render-phase peek — see Browser.tsx for the drain protocol. */\nexport function peekBrowserStartUrl(): PendingStartUrl | null {\n return pendingStartUrl;\n}\n\n/** @internal Commit-phase claim. The identity check keeps a URL staged\n * *after* the claimant's render-phase peek available for the window it\n * belongs to. */\nexport function claimBrowserStartUrl(stage: PendingStartUrl): void {\n if (pendingStartUrl === stage) pendingStartUrl = null;\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { currentShellStrings } from './chunk-
|
|
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-
|
|
195
|
-
//# sourceMappingURL=chunk-
|
|
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-EENVXIVU.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-
|
|
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-
|
|
822
|
-
//# sourceMappingURL=chunk-
|
|
821
|
+
//# sourceMappingURL=chunk-VNJJCH5A.js.map
|
|
822
|
+
//# sourceMappingURL=chunk-VNJJCH5A.js.map
|