diff --git a/packages/app/package.json b/packages/app/package.json index c18727b8..3ccaf90b 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -31,6 +31,7 @@ "typecheck:test": "tsc -p tsconfig.test.json --noEmit" }, "dependencies": { + "@base-ui/react": "^1.7.0", "@codemirror/commands": "6.10.3", "@codemirror/lang-json": "6.0.2", "@codemirror/lang-markdown": "6.5.0", diff --git a/packages/app/src/components/AppMenuBar.tsx b/packages/app/src/components/AppMenuBar.tsx new file mode 100644 index 00000000..f963d386 --- /dev/null +++ b/packages/app/src/components/AppMenuBar.tsx @@ -0,0 +1,121 @@ +import { Menu } from "@base-ui/react/menu"; +import { Menubar } from "@base-ui/react/menubar"; +import { ChevronRight } from "lucide-react"; +import type { ContextMenuEntry, ContextMenuItem, ContextMenuSubmenu } from "./ContextMenu"; + +export type AppMenuConfig = { + id: string; + label: string; + /** Extra classes for this menu's trigger, e.g. app-name weight or per-menu color. */ + triggerClassName?: string; +}; + +export type AppMenuBarProps = { + menus: AppMenuConfig[]; + getEntries: (menuId: string) => ContextMenuEntry[]; + triggerClassName?: string; +}; + +const positionerClassName = "z-[2147483647]"; + +const popupClassName = + "min-w-[216px] max-w-[min(280px,calc(100vw-16px))] rounded-lg border border-(--border-default) bg-(--bg-primary) p-1 text-[13px] leading-5 font-[520] text-(--text-primary) shadow-[0_14px_36px_color-mix(in_srgb,var(--text-heading)_16%,transparent)] outline-none select-none"; + +const itemClassName = + "flex h-7 w-full items-center justify-between gap-4 rounded-md border-0 bg-transparent px-2 text-left font-inherit text-(--text-primary) outline-none transition-[background-color,color,opacity] duration-150 ease-out cursor-pointer hover:bg-(--bg-hover) hover:text-(--text-heading) focus-visible:bg-(--bg-hover) focus-visible:text-(--text-heading) focus-visible:outline-none data-disabled:opacity-45 data-disabled:cursor-default data-disabled:pointer-events-none"; + +const separatorClassName = "my-1 h-px bg-(--border-default)"; + +const acceleratorClassName = + "shrink-0 text-[12px] font-[520] text-(--text-secondary)"; + +const submenuTriggerClassName = + "flex h-7 w-full items-center justify-between gap-4 rounded-md border-0 bg-transparent px-2 text-left font-inherit text-(--text-primary) outline-none transition-[background-color,color,opacity] duration-150 ease-out cursor-pointer hover:bg-(--bg-hover) hover:text-(--text-heading) focus-visible:bg-(--bg-hover) focus-visible:text-(--text-heading) focus-visible:outline-none data-disabled:opacity-45 data-disabled:cursor-default data-disabled:pointer-events-none"; + +function formatAccelerator(accelerator: string) { + return accelerator.replace(/CmdOrCtrl/gu, "Cmd/Ctrl"); +} + +export function AppMenuBar({ menus, getEntries, triggerClassName }: AppMenuBarProps) { + return ( + + {menus.map((menu) => ( + + + {menu.label} + + + + + + + + + + ))} + + ); +} + +function AppMenuEntries({ entries }: { entries: ContextMenuEntry[] }) { + return entries.map((entry, index) => { + if (entry.kind === "separator") { + return
; + } + + if (entry.kind === "submenu") { + return ; + } + + return ; + }); +} + +function AppMenuItem({ entry }: { entry: ContextMenuItem }) { + const disabled = Boolean(entry.disabled); + const acceleratorLabel = entry.accelerator ? formatAccelerator(entry.accelerator) : null; + const accessibleLabel = acceleratorLabel ? `${entry.label} ${acceleratorLabel}` : entry.label; + + return ( + { + if (disabled) return; + Promise.resolve(entry.onSelect?.()).catch(() => {}); + }} + > + + {entry.icon ? {entry.icon} : null} + {entry.label} + + {entry.accelerator ? ( + {acceleratorLabel} + ) : null} + + ); +} + +function AppSubmenu({ entry }: { entry: ContextMenuSubmenu }) { + const disabled = Boolean(entry.disabled); + + return ( + + + {entry.label} + + + + + + + + + + ); +} diff --git a/packages/app/src/components/NativeTitleBar.test.tsx b/packages/app/src/components/NativeTitleBar.test.tsx index 58b94f76..f2c92f26 100644 --- a/packages/app/src/components/NativeTitleBar.test.tsx +++ b/packages/app/src/components/NativeTitleBar.test.tsx @@ -638,74 +638,74 @@ describe("NativeTitleBar", () => { /> ); - fireEvent.click(screen.getByRole("button", { name: "Markra" })); + fireEvent.click(screen.getByRole("menuitem", { name: "Markra" })); expect(screen.getByRole("menu", { name: "Markra" })).toBeInTheDocument(); fireEvent.click(screen.getByRole("menuitem", { name: "About Markra" })); expect(showAbout).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByRole("button", { name: "Markra" })); + fireEvent.click(screen.getByRole("menuitem", { name: "Markra" })); fireEvent.click(screen.getByRole("menuitem", { name: "Settings... Ctrl+," })); expect(openSettings).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByRole("button", { name: "Markra" })); + fireEvent.click(screen.getByRole("menuitem", { name: "Markra" })); fireEvent.click(screen.getByRole("menuitem", { name: "Check for updates" })); expect(checkForUpdates).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByRole("button", { name: "Markra" })); + fireEvent.click(screen.getByRole("menuitem", { name: "Markra" })); fireEvent.click(screen.getByRole("menuitem", { name: "Quit Markra" })); expect(exitApp).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByRole("button", { name: "File" })); + fireEvent.click(screen.getByRole("menuitem", { name: "File" })); expect(screen.getByRole("menu", { name: "File" })).toBeInTheDocument(); fireEvent.click(screen.getByRole("menuitem", { name: "New Ctrl+N" })); expect(openBlankEditorWindow).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByRole("button", { name: "File" })); + fireEvent.click(screen.getByRole("menuitem", { name: "File" })); fireEvent.click(screen.getByRole("menuitem", { name: "Open... Ctrl+O" })); expect(openMarkdown).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByRole("button", { name: "File" })); + fireEvent.click(screen.getByRole("menuitem", { name: "File" })); fireEvent.click(screen.getByRole("menuitem", { name: "Save Ctrl+S" })); expect(saveMarkdown).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByRole("button", { name: "File" })); + fireEvent.click(screen.getByRole("menuitem", { name: "File" })); const saveAsItem = screen.getByRole("menuitem", { name: "Save As... Ctrl+Shift+S" }); expect(saveAsItem).not.toBeDisabled(); fireEvent.click(saveAsItem); expect(saveMarkdownAs).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByRole("button", { name: "File" })); + fireEvent.click(screen.getByRole("menuitem", { name: "File" })); const syncItem = screen.getByRole("menuitem", { name: "Sync now Ctrl+Alt+R" }); expect(syncItem).not.toBeDisabled(); fireEvent.click(syncItem); expect(syncNow).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByRole("button", { name: "File" })); + fireEvent.click(screen.getByRole("menuitem", { name: "File" })); const exportMenuItem = screen.getByRole("menuitem", { name: "Export" }); expect(exportMenuItem).not.toBeDisabled(); - fireEvent.pointerEnter(exportMenuItem); + fireEvent.click(exportMenuItem); const exportPdfItem = screen.getByRole("menuitem", { name: "Export PDF Ctrl+Alt+P" }); expect(exportPdfItem).not.toBeDisabled(); fireEvent.click(exportPdfItem); expect(exportPdf).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByRole("button", { name: "File" })); + fireEvent.click(screen.getByRole("menuitem", { name: "File" })); const reopenedExportMenuItem = screen.getByRole("menuitem", { name: "Export" }); - fireEvent.pointerEnter(reopenedExportMenuItem); + fireEvent.click(reopenedExportMenuItem); const exportMarkdownItem = screen.getByRole("menuitem", { name: "Export Markdown with attachments" }); expect(exportMarkdownItem).not.toBeDisabled(); fireEvent.click(exportMarkdownItem); expect(exportMarkdown).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByRole("button", { name: "Edit" })); + fireEvent.click(screen.getByRole("menuitem", { name: "Edit" })); expect(screen.getByRole("menu", { name: "Edit" })).toBeInTheDocument(); expect(screen.getByRole("menuitem", { name: "Undo Ctrl+Z" })).toBeInTheDocument(); expect(screen.getByRole("menuitem", { name: "Select All Ctrl+A" })).toBeInTheDocument(); - fireEvent.click(screen.getByRole("button", { name: "Format" })); + fireEvent.click(screen.getByRole("menuitem", { name: "Format" })); expect(screen.getByRole("menu", { name: "Format" })).toBeInTheDocument(); const boldItem = screen.getByRole("menuitem", { name: "Bold Ctrl+B" }); @@ -713,10 +713,10 @@ describe("NativeTitleBar", () => { fireEvent.click(boldItem); expect(formatBold).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByRole("button", { name: "Format" })); + fireEvent.click(screen.getByRole("menuitem", { name: "Format" })); expect(screen.getByRole("menuitem", { name: "Heading 1 Ctrl+Alt+1" })).toBeInTheDocument(); - fireEvent.click(screen.getByRole("button", { name: "View" })); + fireEvent.click(screen.getByRole("menuitem", { name: "View" })); expect(screen.getByRole("menu", { name: "View" })).toBeInTheDocument(); expect(screen.queryByRole("menuitem", { name: "Enter Full Screen" })).not.toBeInTheDocument(); @@ -728,23 +728,91 @@ describe("NativeTitleBar", () => { fireEvent.click(screen.getByRole("button", { name: "Maximize or restore window" })); expect(toggleWindowMaximized).toHaveBeenCalledTimes(2); - fireEvent.click(screen.getByRole("button", { name: "View" })); + fireEvent.click(screen.getByRole("menuitem", { name: "View" })); const aiCommandItem = screen.getByRole("menuitem", { name: "AI writing command Ctrl+Shift+J" }); expect(aiCommandItem).not.toBeDisabled(); fireEvent.click(aiCommandItem); expect(toggleAiCommand).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByRole("button", { name: "View" })); + fireEvent.click(screen.getByRole("menuitem", { name: "View" })); const foldsItem = screen.getByRole("menuitem", { name: "Toggle all folds Ctrl+Alt+T" }); expect(foldsItem).not.toBeDisabled(); fireEvent.click(foldsItem); expect(toggleAllFolds).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByRole("button", { name: "View" })); + fireEvent.click(screen.getByRole("menuitem", { name: "View" })); fireEvent.click(screen.getByRole("menuitem", { name: "Toggle file list Ctrl+Shift+M" })); expect(toggleMarkdownFiles).toHaveBeenCalledTimes(1); }); + it("marks disabled Windows app menu items with data-disabled styling hooks", () => { + const onOpenMarkdown = vi.fn(); + render( + {}} + onOpenBlankEditorWindow={vi.fn()} + onOpenMarkdown={onOpenMarkdown} + onSaveMarkdown={() => {}} + onToggleMarkdownFiles={() => {}} + onToggleTheme={() => {}} + /> + ); + + fireEvent.click(screen.getByRole("menuitem", { name: "Markra" })); + + const hiddenItem = screen.getByRole("menuitem", { name: "Hide Markra" }); + expect(hiddenItem).toHaveAttribute("data-disabled"); + expect(hiddenItem.className).toContain("data-disabled:opacity-45"); + expect(hiddenItem.className).toContain("data-disabled:pointer-events-none"); + + fireEvent.click(screen.getByRole("menuitem", { name: "File" })); + + const saveItem = screen.getByRole("menuitem", { name: "Save Ctrl+S" }); + expect(saveItem).toHaveAttribute("data-disabled"); + expect(saveItem.className).toContain("data-disabled:opacity-45"); + + const newItem = screen.getByRole("menuitem", { name: "New Ctrl+N" }); + expect(newItem).not.toHaveAttribute("data-disabled"); + expect(newItem.className).toContain("hover:bg-(--bg-hover)"); + }); + + it("switches the open Windows app menu when hovering another menubar trigger", () => { + render( + {}} + onOpenBlankEditorWindow={() => {}} + onOpenMarkdown={() => {}} + onSaveMarkdown={() => {}} + onToggleMarkdownFiles={() => {}} + onToggleTheme={() => {}} + /> + ); + + fireEvent.click(screen.getByRole("menuitem", { name: "Markra" })); + expect(screen.getByRole("menu", { name: "Markra" })).toBeInTheDocument(); + + fireEvent.mouseEnter(screen.getByRole("menuitem", { name: "File" }), { + clientX: 80, + clientY: 10 + }); + + expect(screen.getByRole("menu", { name: "File" })).toBeInTheDocument(); + expect(screen.queryByRole("menu", { name: "Markra" })).not.toBeInTheDocument(); + }); + it("toggles the Windows window state from self-drawn titlebar double-click mouse downs", () => { const toggleWindowMaximized = vi.fn(); const { container } = render( diff --git a/packages/app/src/components/WindowsNativeTitleBar.tsx b/packages/app/src/components/WindowsNativeTitleBar.tsx index 1f8ddf36..1bbb1aef 100644 --- a/packages/app/src/components/WindowsNativeTitleBar.tsx +++ b/packages/app/src/components/WindowsNativeTitleBar.tsx @@ -14,19 +14,14 @@ import { contextMenuItem, contextMenuSeparator, contextMenuSubmenu, - showContextMenu, type ContextMenuEntry } from "./ContextMenu"; import { WindowsWindowControls } from "./WindowsWindowControls"; import { IconButton, Tooltip } from "@markra/ui"; +import { AppMenuBar, type AppMenuConfig } from "./AppMenuBar"; type WindowsAppMenuId = "app" | "file" | "edit" | "format" | "view"; -type WindowsAppMenuConfig = { - id: WindowsAppMenuId; - label: string; -}; - type WindowsNativeTitleBarProps = { aiAgentOpen: boolean; aiAgentResizing: boolean; @@ -183,16 +178,21 @@ export function WindowsNativeTitleBar({
) : null; - const windowsAppMenus: WindowsAppMenuConfig[] = [ - { id: "file", label: label("menu.file") }, - { id: "edit", label: label("menu.edit") }, - { id: "format", label: label("menu.format") }, - { id: "view", label: label("menu.view") } + const windowsAppMenus: AppMenuConfig[] = [ + { + id: "app", + label: "Markra", + triggerClassName: "font-[620] text-(--text-heading)" + }, + { id: "file", label: label("menu.file"), triggerClassName: "text-(--text-primary)" }, + { id: "edit", label: label("menu.edit"), triggerClassName: "text-(--text-primary)" }, + { id: "format", label: label("menu.format"), triggerClassName: "text-(--text-primary)" }, + { id: "view", label: label("menu.view"), triggerClassName: "text-(--text-primary)" } ]; const runToggleWindowMaximized = () => { runOptionalWindowsAction(onToggleWindowMaximized); }; - const windowsAppMenuEntries = (menuId: WindowsAppMenuId): ContextMenuEntry[] => { + const windowsAppMenuEntries = (menuId: string): ContextMenuEntry[] => { if (menuId === "app") { return [ windowsMenuItem("showAbout", label("menu.aboutMarkra"), undefined, onShowAbout), @@ -303,20 +303,6 @@ export function WindowsNativeTitleBar({ contextMenuItem("toggleTheme", themeActionLabel, undefined, onToggleTheme) ]; }; - const showWindowsAppMenu = (menu: WindowsAppMenuConfig, event: ReactMouseEvent) => { - event.preventDefault(); - event.stopPropagation(); - - const rect = event.currentTarget.getBoundingClientRect(); - showContextMenu(event.currentTarget.ownerDocument, { - ariaLabel: menu.label, - entries: windowsAppMenuEntries(menu.id), - position: { - x: rect.left, - y: rect.bottom - } - }); - }; const handleWindowsTitlebarMouseDownCapture = (event: ReactMouseEvent) => { if (!nativeWindowChrome || !onToggleWindowMaximized || isWindowsTitlebarInteractiveTarget(event.target)) return; if (event.button !== 0 || event.detail !== 2) return; @@ -364,27 +350,13 @@ export function WindowsNativeTitleBar({ ) : null} - - {windowsAppMenus.map((menu) => ( - - ))} +
event.stopPropagation()}> + +
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5abfe581..805a8b0b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -239,6 +239,9 @@ importers: packages/app: dependencies: + '@base-ui/react': + specifier: ^1.7.0 + version: 1.7.0(@types/react@19.2.7)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@codemirror/commands': specifier: 6.10.3 version: 6.10.3 @@ -856,6 +859,33 @@ packages: resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} + '@base-ui/react@1.7.0': + resolution: {integrity: sha512-j+8QjX44C32jrXD/qyEAGpFr70FRpGL2CY61mQd9nBPWN737CK0xxD1ceJ055rW4RtdvFDT1e7otzdlfxvsYug==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@date-fns/tz': ^1.2.0 + '@types/react': ^17 || ^18 || ^19 + date-fns: ^4.0.0 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + '@date-fns/tz': + optional: true + '@types/react': + optional: true + date-fns: + optional: true + + '@base-ui/utils@0.3.2': + resolution: {integrity: sha512-oWy1aq/I2GmYjpl4PhEAhzflF8VPGKgZeq0xAWTbfD5KBWyxcN0ZP2+WHSUm/5Z6lVMBDLReLcoXwSYoRc/zNQ==} + peerDependencies: + '@types/react': ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + '@types/react': + optional: true + '@braintree/sanitize-url@7.1.2': resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} @@ -1003,6 +1033,21 @@ packages: '@noble/hashes': optional: true + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} + + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} + + '@floating-ui/react-dom@2.1.9': + resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} + '@fontsource-variable/noto-sans-sc@5.3.0': resolution: {integrity: sha512-lNar1dF7Ik/lHNPo/7JWG0TolXY29LtsqYgMvEysooZ5bsO9uH4shJmRrwyJ3PjyTPljhpMJEK0jDuLSU4vJ1w==} @@ -1174,36 +1219,42 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} @@ -1327,24 +1378,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.4': resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.4': resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.4': resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.4': resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==} @@ -1414,30 +1469,35 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tauri-apps/cli-linux-arm64-musl@2.11.0': resolution: {integrity: sha512-DtSE8ZBlB9H+L+eHkfZ3myt00EVEyAB3e41juEHoE2qT88fgVlJvyrwa9SZYc/xTwCS9TnmK+R84tpg+ZsAg7Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tauri-apps/cli-linux-riscv64-gnu@2.11.0': resolution: {integrity: sha512-5QdgS4LD+kntClI1aj2JmwjW38LosNXxwCe8viIHEwqYIWuMPdNEIau6/cLogI38Yzx9DnfCPRfEWLyI+5li8Q==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] '@tauri-apps/cli-linux-x64-gnu@2.11.0': resolution: {integrity: sha512-5UynPXo3Zq9khjVdAbD+YogeLltdVUeOah2ioSIM3tu6H7wY9vMy6rgGJhv9r5R8ZXmk9GttMippdqYJWrnLnA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tauri-apps/cli-linux-x64-musl@2.11.0': resolution: {integrity: sha512-CNz7fHbApz1Zyhhq73jtGn9JqgNEV/lIWnTnUo6h6ujw+mHsTmkLszvJSM8W6JBaDjNpTTFr/RSNoVL5FMwcTg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tauri-apps/cli-win32-arm64-msvc@2.11.0': resolution: {integrity: sha512-K+br+VXZ+Xx0n/9FdWohpW5Ugq+2FQUpJScqcPl1hTxXfh3fgjYgt4qA2NgrjlJo+zZPNrmUMl+NLvm0ufEqBQ==} @@ -2350,24 +2410,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -2743,6 +2807,9 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + reselect@5.3.0: + resolution: {integrity: sha512-XGoLeRAVzUTcJ1qkxPQhDJyIZ5d6zzZD9nT7AEZOaaU9UbWclhycElmhO+VD5bFeLuzhPBaOV2oXC8uG35ZSpg==} + retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -2950,6 +3017,11 @@ packages: unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + uuid@14.0.0: resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} hasBin: true @@ -3495,6 +3567,29 @@ snapshots: '@babel/runtime@7.29.2': {} + '@base-ui/react@1.7.0(@types/react@19.2.7)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@base-ui/utils': 0.3.2(@types/react@19.2.7)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@floating-ui/react-dom': 2.1.9(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@floating-ui/utils': 0.2.12 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + use-sync-external-store: 1.6.0(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.7 + + '@base-ui/utils@0.3.2(@types/react@19.2.7)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@floating-ui/utils': 0.2.12 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + reselect: 5.3.0 + use-sync-external-store: 1.6.0(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.7 + '@braintree/sanitize-url@7.1.2': {} '@bramus/specificity@2.4.2': @@ -3708,6 +3803,23 @@ snapshots: optionalDependencies: '@noble/hashes': 2.2.0 + '@floating-ui/core@1.8.0': + dependencies: + '@floating-ui/utils': 0.2.12 + + '@floating-ui/dom@1.8.0': + dependencies: + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 + + '@floating-ui/react-dom@2.1.9(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@floating-ui/dom': 1.8.0 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@floating-ui/utils@0.2.12': {} + '@fontsource-variable/noto-sans-sc@5.3.0': {} '@google/genai@1.52.0': @@ -5729,6 +5841,8 @@ snapshots: require-from-string@2.0.2: {} + reselect@5.3.0: {} + retry@0.13.1: {} robust-predicates@3.0.3: {} @@ -5940,6 +6054,10 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + use-sync-external-store@1.6.0(react@19.2.5): + dependencies: + react: 19.2.5 + uuid@14.0.0: {} validate-npm-package-license@3.0.4: