react-os-shell 4.11.1 → 4.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +28 -0
  2. package/dist/{Browser-OO5A5MCO.js → Browser-SFNEZ6KW.js} +6 -6
  3. package/dist/{Browser-OO5A5MCO.js.map → Browser-SFNEZ6KW.js.map} +1 -1
  4. package/dist/{Calculator-U7JLNROS.js → Calculator-SJBZNZWR.js} +5 -5
  5. package/dist/{Calculator-U7JLNROS.js.map → Calculator-SJBZNZWR.js.map} +1 -1
  6. package/dist/ConfirmDialog-LZ2JQBHA.js +4 -0
  7. package/dist/{ConfirmDialog-Y4BYT77R.js.map → ConfirmDialog-LZ2JQBHA.js.map} +1 -1
  8. package/dist/{CurrencyConverter-2UFQ75WE.js → CurrencyConverter-5OUQSITK.js} +5 -5
  9. package/dist/{CurrencyConverter-2UFQ75WE.js.map → CurrencyConverter-5OUQSITK.js.map} +1 -1
  10. package/dist/{Documents-V6UUFZDR.js → Documents-IUV2A2R6.js} +6 -6
  11. package/dist/{Documents-V6UUFZDR.js.map → Documents-IUV2A2R6.js.map} +1 -1
  12. package/dist/{Files-XNVUBQ2S.js → Files-RZSUSCOS.js} +8 -8
  13. package/dist/{Files-XNVUBQ2S.js.map → Files-RZSUSCOS.js.map} +1 -1
  14. package/dist/{Notepad-2J7U6U23.js → Notepad-DFPJKEBG.js} +6 -6
  15. package/dist/{Notepad-2J7U6U23.js.map → Notepad-DFPJKEBG.js.map} +1 -1
  16. package/dist/{PomodoroTimer-Q53KOASQ.js → PomodoroTimer-5H3Q5SNS.js} +6 -6
  17. package/dist/{PomodoroTimer-Q53KOASQ.js.map → PomodoroTimer-5H3Q5SNS.js.map} +1 -1
  18. package/dist/{Preview-YWVKSSAM.js → Preview-JTMOV35N.js} +6 -6
  19. package/dist/{Preview-YWVKSSAM.js.map → Preview-JTMOV35N.js.map} +1 -1
  20. package/dist/{Sidebar-ITLRERXE.js → Sidebar-IMACKSHS.js} +10 -21
  21. package/dist/Sidebar-IMACKSHS.js.map +1 -0
  22. package/dist/{Spreadsheet-LQN4GG2D.js → Spreadsheet-2R43CIYR.js} +6 -6
  23. package/dist/{Spreadsheet-LQN4GG2D.js.map → Spreadsheet-2R43CIYR.js.map} +1 -1
  24. package/dist/{Stock-SRUFZGQC.js → Stock-FLWCMQZR.js} +5 -5
  25. package/dist/{Stock-SRUFZGQC.js.map → Stock-FLWCMQZR.js.map} +1 -1
  26. package/dist/{Weather-47E3IRKF.js → Weather-DKWGAHWC.js} +5 -5
  27. package/dist/{Weather-47E3IRKF.js.map → Weather-DKWGAHWC.js.map} +1 -1
  28. package/dist/{WorldClock-7L2YHNC3.js → WorldClock-5UXXPCBB.js} +5 -5
  29. package/dist/{WorldClock-7L2YHNC3.js.map → WorldClock-5UXXPCBB.js.map} +1 -1
  30. package/dist/apps/index.js +18 -18
  31. package/dist/{chunk-TUXLGFXJ.js → chunk-AFOTTLRS.js} +5 -5
  32. package/dist/{chunk-TUXLGFXJ.js.map → chunk-AFOTTLRS.js.map} +1 -1
  33. package/dist/{chunk-E2EGGV2Z.js → chunk-CVVS2OJO.js} +4 -4
  34. package/dist/{chunk-E2EGGV2Z.js.map → chunk-CVVS2OJO.js.map} +1 -1
  35. package/dist/{chunk-KILLK5UH.js → chunk-H4IEH6FK.js} +3 -3
  36. package/dist/{chunk-KILLK5UH.js.map → chunk-H4IEH6FK.js.map} +1 -1
  37. package/dist/{chunk-RVT2A2MO.js → chunk-IVLDAUYM.js} +11 -5
  38. package/dist/chunk-IVLDAUYM.js.map +1 -0
  39. package/dist/{chunk-HC6N2OXO.js → chunk-N2UW3CAS.js} +3 -3
  40. package/dist/{chunk-HC6N2OXO.js.map → chunk-N2UW3CAS.js.map} +1 -1
  41. package/dist/{chunk-JROEOUJD.js → chunk-UI43AZAK.js} +3 -3
  42. package/dist/{chunk-JROEOUJD.js.map → chunk-UI43AZAK.js.map} +1 -1
  43. package/dist/chunk-WDXP2Y4T.js +7 -0
  44. package/dist/{chunk-V2JNA3GW.js.map → chunk-WDXP2Y4T.js.map} +1 -1
  45. package/dist/{chunk-XVHRGMS3.js → chunk-XJCAUC2Y.js} +3 -3
  46. package/dist/{chunk-XVHRGMS3.js.map → chunk-XJCAUC2Y.js.map} +1 -1
  47. package/dist/{chunk-H5NDQZ4A.js → chunk-ZPCGIR62.js} +3 -3
  48. package/dist/{chunk-H5NDQZ4A.js.map → chunk-ZPCGIR62.js.map} +1 -1
  49. package/dist/index.d.ts +26 -4
  50. package/dist/index.js +196 -205
  51. package/dist/index.js.map +1 -1
  52. package/package.json +1 -1
  53. package/dist/ConfirmDialog-Y4BYT77R.js +0 -4
  54. package/dist/Sidebar-ITLRERXE.js.map +0 -1
  55. package/dist/chunk-RVT2A2MO.js.map +0 -1
  56. package/dist/chunk-V2JNA3GW.js +0 -7
package/dist/index.js CHANGED
@@ -1,26 +1,26 @@
1
1
  import { PREVIEW_OPENED_EVENT, openPreviewFile } from './chunk-DINNWYVB.js';
2
2
  export { Breadcrumbs } from './chunk-DINNWYVB.js';
3
- import { subscribePomo, getPomoSnapshot } from './chunk-JROEOUJD.js';
4
- export { setShellTodoProvider } from './chunk-JROEOUJD.js';
3
+ import { subscribePomo, getPomoSnapshot } from './chunk-UI43AZAK.js';
4
+ export { setShellTodoProvider } from './chunk-UI43AZAK.js';
5
5
  import { SidebarLayout } from './chunk-YG45DAUT.js';
6
6
  export { SidebarLayout } from './chunk-YG45DAUT.js';
7
7
  export { ImageAnnotator_default as ImageAnnotator } from './chunk-QU5L5ZYM.js';
8
8
  import { toast_default } from './chunk-VENYVK3L.js';
9
9
  export { toast_default as toast } from './chunk-VENYVK3L.js';
10
- import { publishDesktopFolders, requestFilesTrashView, FolderGlyph, requestFilesDesktopFolderView, FileIconTile } from './chunk-H5NDQZ4A.js';
10
+ import { publishDesktopFolders, requestFilesTrashView, FolderGlyph, requestFilesDesktopFolderView, FileIconTile } from './chunk-ZPCGIR62.js';
11
11
  import { setPdfPreview } from './chunk-BV6SQ46N.js';
12
12
  import { playNotification, playStartup, soundsEnabled, getSoundConfig, SOUND_PACK_KEYS, SOUND_PACKS, SOUND_TYPES, SOUND_TYPE_LABELS, setSoundForType, previewSound, setAllSounds, playLogout } from './chunk-D7PYW2QS.js';
13
- import { useAuth } from './chunk-KILLK5UH.js';
14
- export { setShellAuthBridge } from './chunk-KILLK5UH.js';
13
+ import { useAuth } from './chunk-H4IEH6FK.js';
14
+ export { setShellAuthBridge } from './chunk-H4IEH6FK.js';
15
15
  import { withSpareRow, EditableGrid } from './chunk-DC6BYGZ6.js';
16
16
  export { EditableGrid } from './chunk-DC6BYGZ6.js';
17
17
  import './chunk-4SQU5YV6.js';
18
- import { APP_VERSION, VERSION } from './chunk-V2JNA3GW.js';
19
- export { VERSION } from './chunk-V2JNA3GW.js';
20
- import { registerModalEscapeInterceptor, useIsMobile, cancelWindowFront, requestWindowFront, useWindowManager, PopupMenu, PopupMenuLabel, PopupMenuDivider, PopupMenuItem, WINDOW_REGISTRY, isPageEntry, Modal, setPerfCollecting, drainPerfEvents, useShellPrefs, markMenuOpen, SIDEBAR_STRIP_W, forgetMaximizedWindowBoxes, ModalActions, useModalActive, client_default, LoadingSpinner, isShellApiClientConfigured, useUndo, CancelButton, setWindowPosition } from './chunk-E2EGGV2Z.js';
21
- export { CancelButton, ConfirmProvider, CopyButton, DocFavStar, Modal, ModalActions, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, ShellPrefsProvider, UndoProvider, WindowCrashedFallback, WindowErrorBoundary, WindowManagerProvider, WindowTitle, beginWindowGesture, commitExposeHighlight, confirm, confirmDestructive, exitExposeMode, getActiveWindowRoute, getExposeHighlight, getWindowPosition, isEntityEntry, isPageEntry, markMenuOpen, prompt, registerModalEscapeInterceptor, requestWindowFront, setExposeHighlight, setShellApiClient, setShellWindowRegistry, setWindowDefaultPosition, setWindowPosition, subscribeExposeHighlight, toggleExposeMode, useIsActiveWindow, useLocalStoragePrefs, useModalActive, useShellPrefs, useUndo, useUndoable, useUndoableState, useWidgetSettings, useWindowDirty, useWindowManager, useWindowMenuItem, useWindowTitle } from './chunk-E2EGGV2Z.js';
22
- import { glassStyle, startMenuCategories, navSections, isSection, GLASS_INPUT_BG, navIcons, isReachable, sectionIcons, useShellAuth, visibleChildren } from './chunk-RVT2A2MO.js';
23
- export { GLASS_DIVIDER, GLASS_INPUT_BG, ShellAuthProvider, glassStyle, setShellNavIcons, useShellAuth } from './chunk-RVT2A2MO.js';
18
+ import { APP_VERSION, VERSION } from './chunk-WDXP2Y4T.js';
19
+ export { VERSION } from './chunk-WDXP2Y4T.js';
20
+ import { registerModalEscapeInterceptor, useIsMobile, cancelWindowFront, requestWindowFront, useWindowManager, PopupMenu, PopupMenuLabel, PopupMenuDivider, PopupMenuItem, WINDOW_REGISTRY, isPageEntry, Modal, setPerfCollecting, drainPerfEvents, useShellPrefs, markMenuOpen, SIDEBAR_STRIP_W, forgetMaximizedWindowBoxes, ModalActions, useModalActive, client_default, LoadingSpinner, isShellApiClientConfigured, useUndo, CancelButton, setWindowPosition } from './chunk-CVVS2OJO.js';
21
+ export { CancelButton, ConfirmProvider, CopyButton, DocFavStar, Modal, ModalActions, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, ShellPrefsProvider, UndoProvider, WindowCrashedFallback, WindowErrorBoundary, WindowManagerProvider, WindowTitle, beginWindowGesture, commitExposeHighlight, confirm, confirmDestructive, exitExposeMode, getActiveWindowRoute, getExposeHighlight, getWindowPosition, isEntityEntry, isPageEntry, markMenuOpen, prompt, registerModalEscapeInterceptor, requestWindowFront, setExposeHighlight, setShellApiClient, setShellWindowRegistry, setWindowDefaultPosition, setWindowPosition, subscribeExposeHighlight, toggleExposeMode, useIsActiveWindow, useLocalStoragePrefs, useModalActive, useShellPrefs, useUndo, useUndoable, useUndoableState, useWidgetSettings, useWindowDirty, useWindowManager, useWindowMenuItem, useWindowTitle } from './chunk-CVVS2OJO.js';
22
+ import { glassStyle, startMenuCategories, navVisible, navSections, isSection, GLASS_INPUT_BG, navIcons, isReachable, sectionIcons, useShellAuth, visibleChildren } from './chunk-IVLDAUYM.js';
23
+ export { GLASS_DIVIDER, GLASS_INPUT_BG, ShellAuthProvider, glassStyle, setShellNavIcons, useShellAuth } from './chunk-IVLDAUYM.js';
24
24
  import { createContext, lazy, forwardRef, useCallback, useState, useRef, useId, useEffect, useLayoutEffect, useSyncExternalStore, useMemo, useContext, isValidElement, cloneElement, Suspense, Fragment as Fragment$1, Children } from 'react';
25
25
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
26
26
  import { Dialog, DialogBackdrop, DialogPanel } from '@headlessui/react';
@@ -3575,6 +3575,67 @@ function LogoutAnimation({ onComplete, subtitle }) {
3575
3575
  }
3576
3576
  );
3577
3577
  }
3578
+
3579
+ // src/shell/menuPath.ts
3580
+ function openMenuLevel(path, depth, anchor) {
3581
+ if (path.length === depth + 1 && path[depth].key === anchor.key) return path;
3582
+ return [...path.slice(0, depth), anchor];
3583
+ }
3584
+ function closeMenuBelow(path, depth) {
3585
+ const keep = Math.max(0, depth);
3586
+ return path.length > keep ? path.slice(0, keep) : path;
3587
+ }
3588
+ function resolveMenuLevels(rootItems, path, childrenOf) {
3589
+ if (path.length === 0 || rootItems.length === 0) return [];
3590
+ const levels = [rootItems];
3591
+ for (let i = 1; i < path.length; i++) {
3592
+ const parent = levels[i - 1].find((item) => item.to === path[i].key);
3593
+ const kids = parent ? childrenOf(parent) : [];
3594
+ if (kids.length === 0) break;
3595
+ levels.push(kids);
3596
+ }
3597
+ return levels;
3598
+ }
3599
+ function clampMenuTop(anchorY, height, minTop, maxBottom) {
3600
+ const span = Math.max(0, maxBottom - minTop);
3601
+ const h = Math.min(height, span);
3602
+ const top = anchorY - h / 2;
3603
+ if (top < minTop) return minTop;
3604
+ if (top + h > maxBottom) return Math.max(minTop, maxBottom - h);
3605
+ return top;
3606
+ }
3607
+ function menuPanelLeft(ownerLeft, ownerRight, width, viewportW, gap, preferLeft = false, margin = 8) {
3608
+ const onRight = ownerRight + gap;
3609
+ const onLeft = ownerLeft - gap - width;
3610
+ const rightFits = onRight + width <= viewportW - margin;
3611
+ const leftFits = onLeft >= margin;
3612
+ if (preferLeft && leftFits) return { left: onLeft, flipped: true };
3613
+ if (rightFits) return { left: onRight, flipped: false };
3614
+ if (leftFits) return { left: onLeft, flipped: true };
3615
+ return preferLeft ? { left: margin, flipped: true } : { left: Math.max(margin, viewportW - margin - width), flipped: false };
3616
+ }
3617
+ var PANEL_GAP = 4;
3618
+ var CLOSE_DELAY = 200;
3619
+ function MenuPanel({ left, anchorY, estHeight, minTop, maxBottom, flipped, className, style, onMouseEnter, onMouseLeave, children }) {
3620
+ const ref = useRef(null);
3621
+ useLayoutEffect(() => {
3622
+ const el = ref.current;
3623
+ if (!el) return;
3624
+ el.style.top = `${clampMenuTop(anchorY, el.offsetHeight, minTop, maxBottom)}px`;
3625
+ });
3626
+ return /* @__PURE__ */ jsx(
3627
+ "div",
3628
+ {
3629
+ ref,
3630
+ "data-menu-panel": true,
3631
+ className: `fixed rounded-2xl overflow-hidden ${className ?? ""}`,
3632
+ style: { left, top: clampMenuTop(anchorY, estHeight, minTop, maxBottom), animation: `${flipped ? "submenu-in-left" : "submenu-in"} 0.1s ease-out`, ...style },
3633
+ onMouseEnter,
3634
+ onMouseLeave,
3635
+ children
3636
+ }
3637
+ );
3638
+ }
3578
3639
  function StartMenu({
3579
3640
  open,
3580
3641
  onClose,
@@ -3600,55 +3661,28 @@ function StartMenu({
3600
3661
  virtualSections.map((v) => [v.label, v])
3601
3662
  );
3602
3663
  const { hasAnyPerm } = useAuth();
3603
- const footerItems = (categories.footerItems ?? []).filter((item) => !item.perms || hasAnyPerm(item.perms));
3664
+ const footerItems = (categories.footerItems ?? []).filter((item) => navVisible(item, hasAnyPerm));
3604
3665
  const isMobile = useIsMobile();
3605
- const [hoveredSection, setHoveredSection] = useState(null);
3606
- const [hoveredY, setHoveredY] = useState(0);
3607
- const [hoveredChild, setHoveredChild] = useState(null);
3608
- const [hoveredChildY, setHoveredChildY] = useState(0);
3666
+ const [openPath, setOpenPath] = useState([]);
3609
3667
  const [search, setSearch] = useState("");
3610
3668
  const [searchIdx, setSearchIdx] = useState(0);
3611
3669
  const menuRef = useRef(null);
3612
- const flyoutRef = useRef(null);
3613
- const subFlyoutRef = useRef(null);
3614
- const hoverTimeout = useRef();
3615
- const childHoverTimeout = useRef();
3616
- const [measuredFlyout, setMeasuredFlyout] = useState(null);
3617
- const [measuredSub, setMeasuredSub] = useState(null);
3670
+ const closeTimer = useRef();
3618
3671
  useEffect(() => {
3619
3672
  if (!open) {
3620
3673
  setSearch("");
3621
- setHoveredSection(null);
3622
- setHoveredChild(null);
3674
+ setOpenPath([]);
3623
3675
  setSearchIdx(0);
3624
3676
  }
3625
3677
  }, [open]);
3626
- useEffect(() => {
3627
- setHoveredChild(null);
3628
- }, [hoveredSection]);
3678
+ useEffect(() => () => clearTimeout(closeTimer.current), []);
3679
+ const deepestOpen = openPath.length > 0 ? openPath[openPath.length - 1].key : null;
3629
3680
  useEffect(() => {
3630
3681
  if (open) markMenuOpen("menu", "start");
3631
3682
  }, [open]);
3632
3683
  useEffect(() => {
3633
- if (hoveredSection) markMenuOpen("submenu", hoveredSection);
3634
- }, [hoveredSection]);
3635
- useEffect(() => {
3636
- if (hoveredChild) markMenuOpen("submenu", hoveredChild);
3637
- }, [hoveredChild]);
3638
- useLayoutEffect(() => {
3639
- if (!flyoutRef.current || !hoveredSection || search.length >= 2) return;
3640
- const h = flyoutRef.current.offsetHeight;
3641
- if (measuredFlyout?.key !== hoveredSection || measuredFlyout.h !== h) {
3642
- setMeasuredFlyout({ key: hoveredSection, h });
3643
- }
3644
- }, [hoveredSection, search, measuredFlyout]);
3645
- useLayoutEffect(() => {
3646
- if (!subFlyoutRef.current || !hoveredChild || search.length >= 2) return;
3647
- const h = subFlyoutRef.current.offsetHeight;
3648
- if (measuredSub?.key !== hoveredChild || measuredSub.h !== h) {
3649
- setMeasuredSub({ key: hoveredChild, h });
3650
- }
3651
- }, [hoveredChild, search, measuredSub]);
3684
+ if (deepestOpen) markMenuOpen("submenu", deepestOpen);
3685
+ }, [deepestOpen]);
3652
3686
  useEffect(() => {
3653
3687
  if (!open) return;
3654
3688
  const handler = (e) => {
@@ -3673,7 +3707,7 @@ function StartMenu({
3673
3707
  if (isMobile) {
3674
3708
  const allItems = [];
3675
3709
  const pushItem = (it, sectionLabel) => {
3676
- if (it.perms && !hasAnyPerm(it.perms)) return;
3710
+ if (!navVisible(it, hasAnyPerm)) return;
3677
3711
  if (!isReachable(it, hasAnyPerm)) return;
3678
3712
  allItems.push({ item: it, sectionLabel });
3679
3713
  for (const c of visibleChildren(it, hasAnyPerm)) pushItem(c, it.label);
@@ -3681,7 +3715,7 @@ function StartMenu({
3681
3715
  for (const entry of navSections2) {
3682
3716
  if (isSection(entry)) {
3683
3717
  const sec = entry;
3684
- if (sec.perms && !hasAnyPerm(sec.perms)) continue;
3718
+ if (!navVisible(sec, hasAnyPerm)) continue;
3685
3719
  for (const it of sec.items) pushItem(it, sec.label);
3686
3720
  } else {
3687
3721
  pushItem(entry);
@@ -3740,10 +3774,11 @@ function StartMenu({
3740
3774
  const systemSections = navSections2.filter((item) => isSection(item) && systemLabels.has(item.label));
3741
3775
  const footerSections = navSections2.filter((item) => isSection(item) && footerLabels.has(item.label));
3742
3776
  const hasAppsGroup = topItems.length > 0 || systemSections.length > 0 || virtualSections.length > 0;
3743
- const getVisibleItems = (section) => section.items.filter((item) => !item.perms || hasAnyPerm(item.perms));
3777
+ const getVisibleItems = (section) => section.items.filter((item) => navVisible(item, hasAnyPerm));
3744
3778
  const visibleChildren2 = (item) => visibleChildren(item, hasAnyPerm);
3779
+ const submenuItems = (item) => visibleChildren2(item).filter((child) => isReachable(child, hasAnyPerm));
3745
3780
  const matchTree = (it, sectionLabel) => {
3746
- if (it.perms && !hasAnyPerm(it.perms)) return [];
3781
+ if (!navVisible(it, hasAnyPerm)) return [];
3747
3782
  if (!isReachable(it, hasAnyPerm)) return [];
3748
3783
  const kids = visibleChildren2(it);
3749
3784
  const hits = [];
@@ -3774,78 +3809,69 @@ function StartMenu({
3774
3809
  if (icon && isValidElement(icon)) return cloneElement(icon, { className: "h-4 w-4" });
3775
3810
  return null;
3776
3811
  };
3812
+ const hoveredSection = openPath.length > 0 ? openPath[0].key : null;
3777
3813
  const hoveredVirtual = hoveredSection ? virtualByLabel[hoveredSection] : void 0;
3778
3814
  const hoveredData = hoveredVirtual ? null : hoveredSection ? [...erpSections, ...systemSections, ...footerSections].find((s) => s.label === hoveredSection) : null;
3779
3815
  const flyoutItems = (hoveredVirtual ? hoveredVirtual.items : hoveredData ? getVisibleItems(hoveredData) : []).filter((item) => isReachable(item, hasAnyPerm));
3780
3816
  const menuDensity = typeof document !== "undefined" ? getComputedStyle(document.documentElement).getPropertyValue("--menu-density")?.trim() || "normal" : "normal";
3781
3817
  const density = menuDensity === "tight" || menuDensity === "large" ? menuDensity : "normal";
3782
3818
  const sizeConfigByDensity = {
3783
- tight: { small: { w: "w-52", fw: "w-44", text: "text-xs", py: "py-1", px: "px-3", mw: 208, itemH: 24 }, medium: { w: "w-56", fw: "w-48", text: "text-xs", py: "py-1", px: "px-3", mw: 224, itemH: 26 }, large: { w: "w-64", fw: "w-52", text: "text-sm", py: "py-1.5", px: "px-3", mw: 256, itemH: 30 } },
3784
- normal: { small: { w: "w-56", fw: "w-48", text: "text-xs", py: "py-1.5", px: "px-3", mw: 224, itemH: 30 }, medium: { w: "w-64", fw: "w-56", text: "text-sm", py: "py-1.5", px: "px-4", mw: 256, itemH: 32 }, large: { w: "w-72", fw: "w-60", text: "text-sm", py: "py-2", px: "px-4", mw: 288, itemH: 36 } },
3785
- large: { small: { w: "w-56", fw: "w-48", text: "text-xs", py: "py-2", px: "px-3", mw: 224, itemH: 32 }, medium: { w: "w-64", fw: "w-56", text: "text-sm", py: "py-2", px: "px-4", mw: 256, itemH: 36 }, large: { w: "w-72", fw: "w-60", text: "text-sm", py: "py-2", px: "px-4", mw: 288, itemH: 36 } }
3819
+ tight: { small: { w: "w-52", fw: "w-44", fwPx: 176, text: "text-xs", py: "py-1", px: "px-3", mw: 208, itemH: 24 }, medium: { w: "w-56", fw: "w-48", fwPx: 192, text: "text-xs", py: "py-1", px: "px-3", mw: 224, itemH: 26 }, large: { w: "w-64", fw: "w-52", fwPx: 208, text: "text-sm", py: "py-1.5", px: "px-3", mw: 256, itemH: 30 } },
3820
+ normal: { small: { w: "w-56", fw: "w-48", fwPx: 192, text: "text-xs", py: "py-1.5", px: "px-3", mw: 224, itemH: 30 }, medium: { w: "w-64", fw: "w-56", fwPx: 224, text: "text-sm", py: "py-1.5", px: "px-4", mw: 256, itemH: 32 }, large: { w: "w-72", fw: "w-60", fwPx: 240, text: "text-sm", py: "py-2", px: "px-4", mw: 288, itemH: 36 } },
3821
+ large: { small: { w: "w-56", fw: "w-48", fwPx: 192, text: "text-xs", py: "py-2", px: "px-3", mw: 224, itemH: 32 }, medium: { w: "w-64", fw: "w-56", fwPx: 224, text: "text-sm", py: "py-2", px: "px-4", mw: 256, itemH: 36 }, large: { w: "w-72", fw: "w-60", fwPx: 240, text: "text-sm", py: "py-2", px: "px-4", mw: 288, itemH: 36 } }
3786
3822
  };
3787
3823
  const sizeConfig = sizeConfigByDensity[density][size];
3788
3824
  const menuGlass = glassStyle();
3789
3825
  const itemCls = `w-full flex items-center gap-2 rounded-lg ${sizeConfig.px} ${sizeConfig.py} ${sizeConfig.text}`;
3790
- const flyoutEstH = flyoutItems.length * sizeConfig.itemH + 12;
3791
- const flyoutH = measuredFlyout?.key === hoveredSection ? measuredFlyout.h : flyoutEstH;
3792
- const menuWidth = sizeConfig.mw;
3793
3826
  const viewportH = typeof window !== "undefined" ? window.innerHeight : 800;
3827
+ const viewportW = typeof window !== "undefined" ? window.innerWidth : 1280;
3794
3828
  const minTop = (taskbarPosition === "top" ? taskbarH : 0) + 8;
3795
3829
  const maxBottom = viewportH - (taskbarPosition === "bottom" ? taskbarH : 0) - 8;
3796
3830
  const availH = Math.max(0, maxBottom - minTop);
3797
- const flyoutBoxH = Math.min(flyoutH, availH);
3798
- let flyoutTop = hoveredY - flyoutBoxH / 2;
3799
- if (flyoutTop < minTop) flyoutTop = minTop;
3800
- if (flyoutTop + flyoutBoxH > maxBottom) flyoutTop = Math.max(minTop, maxBottom - flyoutBoxH);
3801
- const handleSectionHover = (label, e) => {
3802
- clearTimeout(hoverTimeout.current);
3803
- const rect = e.currentTarget.getBoundingClientRect();
3804
- setHoveredY(rect.top + rect.height / 2);
3805
- setHoveredSection(label);
3831
+ const levels = resolveMenuLevels(flyoutItems, openPath, submenuItems);
3832
+ const cancelClose = () => clearTimeout(closeTimer.current);
3833
+ const nextPanelLeft = (depth, row) => {
3834
+ const owner = row.closest("[data-menu-panel]");
3835
+ if (!owner) return { left: 0, flipped: false };
3836
+ const ownerLeft = depth === 0 ? owner.getBoundingClientRect().left : openPath[depth - 1]?.left ?? 0;
3837
+ const ownerRight = depth === 0 ? owner.getBoundingClientRect().right : ownerLeft + owner.offsetWidth;
3838
+ const preferLeft = depth > 0 && (openPath[depth - 1]?.flipped ?? false);
3839
+ return menuPanelLeft(ownerLeft, ownerRight, sizeConfig.fwPx, viewportW, PANEL_GAP, preferLeft);
3840
+ };
3841
+ const openSubmenu = (depth, key, e) => {
3842
+ cancelClose();
3843
+ const row = e.currentTarget;
3844
+ const rect = row.getBoundingClientRect();
3845
+ const anchor = { key, y: rect.top + rect.height / 2, ...nextPanelLeft(depth, row) };
3846
+ setOpenPath((prev) => openMenuLevel(prev, depth, anchor));
3806
3847
  };
3848
+ const scheduleClose = (depth) => {
3849
+ cancelClose();
3850
+ closeTimer.current = setTimeout(() => setOpenPath((prev) => closeMenuBelow(prev, depth)), CLOSE_DELAY);
3851
+ };
3852
+ const chevron = /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5 ml-auto text-gray-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8.25 4.5l7.5 7.5-7.5 7.5" }) });
3807
3853
  const renderSection = (section, isErp) => {
3808
- if (section.perms && !hasAnyPerm(section.perms)) return null;
3854
+ if (!navVisible(section, hasAnyPerm)) return null;
3809
3855
  const items = getVisibleItems(section);
3810
3856
  if (items.length === 0) return null;
3811
3857
  const isHovered = hoveredSection === section.label;
3812
- return /* @__PURE__ */ jsxs(
3813
- "div",
3814
- {
3815
- onMouseEnter: (e) => handleSectionHover(section.label, e),
3816
- onMouseLeave: () => {
3817
- hoverTimeout.current = setTimeout(() => setHoveredSection(null), 200);
3818
- },
3819
- children: [
3820
- /* @__PURE__ */ jsxs("button", { className: `${itemCls} transition-colors ${isHovered ? "bg-blue-50 text-blue-700" : "text-gray-700 hover:bg-blue-50 hover:text-blue-700"}`, children: [
3821
- secIcon(section.label),
3822
- /* @__PURE__ */ jsx("span", { className: isErp ? "font-medium" : "", children: section.label }),
3823
- /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5 ml-auto text-gray-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8.25 4.5l7.5 7.5-7.5 7.5" }) })
3824
- ] }),
3825
- section.dividerAfter && /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" })
3826
- ]
3827
- },
3828
- section.label
3829
- );
3858
+ return /* @__PURE__ */ jsxs("div", { onMouseEnter: (e) => openSubmenu(0, section.label, e), children: [
3859
+ /* @__PURE__ */ jsxs("button", { className: `${itemCls} transition-colors ${isHovered ? "bg-blue-50 text-blue-700" : "text-gray-700 hover:bg-blue-50 hover:text-blue-700"}`, children: [
3860
+ secIcon(section.label),
3861
+ /* @__PURE__ */ jsx("span", { className: isErp ? "font-medium" : "", children: section.label }),
3862
+ chevron
3863
+ ] }),
3864
+ section.dividerAfter && /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" })
3865
+ ] }, section.label);
3830
3866
  };
3831
3867
  const renderVirtualSection = (v) => {
3832
3868
  if (v.items.length === 0) return null;
3833
3869
  const isHovered = hoveredSection === v.label;
3834
- return /* @__PURE__ */ jsx(
3835
- "div",
3836
- {
3837
- onMouseEnter: (e) => handleSectionHover(v.label, e),
3838
- onMouseLeave: () => {
3839
- hoverTimeout.current = setTimeout(() => setHoveredSection(null), 200);
3840
- },
3841
- children: /* @__PURE__ */ jsxs("button", { className: `${itemCls} transition-colors ${isHovered ? "bg-blue-50 text-blue-700" : "text-gray-700 hover:bg-blue-50 hover:text-blue-700"}`, children: [
3842
- v.icon,
3843
- /* @__PURE__ */ jsx("span", { children: v.label }),
3844
- /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5 ml-auto text-gray-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8.25 4.5l7.5 7.5-7.5 7.5" }) })
3845
- ] })
3846
- },
3847
- v.label
3848
- );
3870
+ return /* @__PURE__ */ jsx("div", { onMouseEnter: (e) => openSubmenu(0, v.label, e), children: /* @__PURE__ */ jsxs("button", { className: `${itemCls} transition-colors ${isHovered ? "bg-blue-50 text-blue-700" : "text-gray-700 hover:bg-blue-50 hover:text-blue-700"}`, children: [
3871
+ v.icon,
3872
+ /* @__PURE__ */ jsx("span", { children: v.label }),
3873
+ chevron
3874
+ ] }) }, v.label);
3849
3875
  };
3850
3876
  return (
3851
3877
  /* z-[1100]: sit above the entire window stack — normal windows climb to
@@ -3859,40 +3885,50 @@ function StartMenu({
3859
3885
  /* @__PURE__ */ jsxs(
3860
3886
  "div",
3861
3887
  {
3888
+ "data-menu-panel": true,
3862
3889
  className: `${sizeConfig.w} rounded-2xl flex ${isVertical ? "flex-col-reverse" : "flex-col"} overflow-hidden`,
3863
3890
  style: { animation: "menu-in 0.15s ease-out", ...menuGlass },
3891
+ onMouseEnter: () => scheduleClose(0),
3892
+ onMouseLeave: () => scheduleClose(0),
3864
3893
  children: [
3865
- /* @__PURE__ */ jsx("div", { className: `px-3 ${isVertical ? "pb-3 pt-2 border-t border-white/20" : "pt-3 pb-2"}`, children: /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-2 ${GLASS_INPUT_BG} rounded-lg px-2.5 py-1.5`, children: [
3866
- /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5 text-gray-400 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" }) }),
3867
- /* @__PURE__ */ jsx(
3868
- "input",
3869
- {
3870
- value: search,
3871
- onChange: (e) => {
3872
- setSearch(e.target.value);
3873
- setHoveredSection(null);
3874
- setSearchIdx(0);
3875
- },
3876
- onKeyDown: (e) => {
3877
- if (search.length >= 2 && searchResults.length > 0) {
3878
- if (e.key === "ArrowDown") {
3879
- e.preventDefault();
3880
- setSearchIdx((i) => Math.min(i + 1, searchResults.length - 1));
3881
- } else if (e.key === "ArrowUp") {
3882
- e.preventDefault();
3883
- setSearchIdx((i) => Math.max(i - 1, 0));
3884
- } else if (e.key === "Enter") {
3885
- e.preventDefault();
3886
- handleClick(searchResults[searchIdx].to);
3887
- }
3894
+ /* @__PURE__ */ jsx(
3895
+ "div",
3896
+ {
3897
+ className: `px-3 ${isVertical ? "pb-3 pt-2 border-t border-white/20" : "pt-3 pb-2"}`,
3898
+ onMouseEnter: () => scheduleClose(0),
3899
+ children: /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-2 ${GLASS_INPUT_BG} rounded-lg px-2.5 py-1.5`, children: [
3900
+ /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5 text-gray-400 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" }) }),
3901
+ /* @__PURE__ */ jsx(
3902
+ "input",
3903
+ {
3904
+ value: search,
3905
+ onChange: (e) => {
3906
+ setSearch(e.target.value);
3907
+ setOpenPath([]);
3908
+ setSearchIdx(0);
3909
+ },
3910
+ onKeyDown: (e) => {
3911
+ if (search.length >= 2 && searchResults.length > 0) {
3912
+ if (e.key === "ArrowDown") {
3913
+ e.preventDefault();
3914
+ setSearchIdx((i) => Math.min(i + 1, searchResults.length - 1));
3915
+ } else if (e.key === "ArrowUp") {
3916
+ e.preventDefault();
3917
+ setSearchIdx((i) => Math.max(i - 1, 0));
3918
+ } else if (e.key === "Enter") {
3919
+ e.preventDefault();
3920
+ handleClick(searchResults[searchIdx].to);
3921
+ }
3922
+ }
3923
+ },
3924
+ placeholder: "Search...",
3925
+ className: "flex-1 bg-transparent text-xs outline-none placeholder-gray-400",
3926
+ autoFocus: true
3888
3927
  }
3889
- },
3890
- placeholder: "Search...",
3891
- className: "flex-1 bg-transparent text-xs outline-none placeholder-gray-400",
3892
- autoFocus: true
3893
- }
3894
- )
3895
- ] }) }),
3928
+ )
3929
+ ] })
3930
+ }
3931
+ ),
3896
3932
  search.length >= 2 ? /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto px-1 pb-2 max-h-[400px]", children: searchResults.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400 text-center py-4", children: "No results" }) : searchResults.map((r, i) => /* @__PURE__ */ jsxs(
3897
3933
  "button",
3898
3934
  {
@@ -3912,6 +3948,7 @@ function StartMenu({
3912
3948
  "button",
3913
3949
  {
3914
3950
  onClick: () => handleClick(item.to),
3951
+ onMouseEnter: () => scheduleClose(0),
3915
3952
  className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`,
3916
3953
  children: [
3917
3954
  iconEl(item.to),
@@ -3924,7 +3961,7 @@ function StartMenu({
3924
3961
  (footerSections.length > 0 || footerItems.length > 0) && /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" }),
3925
3962
  erpSections.map((s) => renderSection(s, true)),
3926
3963
  erpSections.length > 0 && hasAppsGroup && /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" }),
3927
- topItems.map((item) => /* @__PURE__ */ jsxs("div", { children: [
3964
+ topItems.map((item) => /* @__PURE__ */ jsxs("div", { onMouseEnter: () => scheduleClose(0), children: [
3928
3965
  /* @__PURE__ */ jsxs(
3929
3966
  "button",
3930
3967
  {
@@ -3942,7 +3979,7 @@ function StartMenu({
3942
3979
  virtualSections.map((v) => renderVirtualSection(v))
3943
3980
  ] }),
3944
3981
  !isVertical && /* @__PURE__ */ jsxs(Fragment, { children: [
3945
- topItems.map((item) => /* @__PURE__ */ jsxs("div", { children: [
3982
+ topItems.map((item) => /* @__PURE__ */ jsxs("div", { onMouseEnter: () => scheduleClose(0), children: [
3946
3983
  /* @__PURE__ */ jsxs(
3947
3984
  "button",
3948
3985
  {
@@ -3966,6 +4003,7 @@ function StartMenu({
3966
4003
  "button",
3967
4004
  {
3968
4005
  onClick: () => handleClick(item.to),
4006
+ onMouseEnter: () => scheduleClose(0),
3969
4007
  className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`,
3970
4008
  children: [
3971
4009
  iconEl(item.to),
@@ -3976,7 +4014,7 @@ function StartMenu({
3976
4014
  ))
3977
4015
  ] })
3978
4016
  ] }),
3979
- /* @__PURE__ */ jsx("div", { className: `${isVertical ? "border-b" : "border-t"} border-white/20 p-1`, children: /* @__PURE__ */ jsxs(
4017
+ /* @__PURE__ */ jsx("div", { className: `${isVertical ? "border-b" : "border-t"} border-white/20 p-1`, onMouseEnter: () => scheduleClose(0), children: /* @__PURE__ */ jsxs(
3980
4018
  "div",
3981
4019
  {
3982
4020
  onClick: () => handleClick("/profile"),
@@ -4003,43 +4041,36 @@ function StartMenu({
4003
4041
  ]
4004
4042
  }
4005
4043
  ),
4006
- hoveredSection && flyoutItems.length > 0 && search.length < 2 && /* @__PURE__ */ jsx(
4007
- "div",
4044
+ search.length < 2 && levels.map((items, i) => /* @__PURE__ */ jsx(
4045
+ MenuPanel,
4008
4046
  {
4009
- ref: flyoutRef,
4010
- className: `fixed ${sizeConfig.fw} rounded-2xl overflow-hidden`,
4011
- style: { left: menuRef.current ? menuRef.current.getBoundingClientRect().right + 4 : menuWidth + 12, top: flyoutTop, animation: "submenu-in 0.1s ease-out", ...menuGlass },
4012
- onMouseEnter: () => clearTimeout(hoverTimeout.current),
4013
- onMouseLeave: () => {
4014
- hoverTimeout.current = setTimeout(() => {
4015
- setHoveredSection(null);
4016
- setHoveredChild(null);
4017
- }, 200);
4018
- },
4019
- children: /* @__PURE__ */ jsx("div", { className: "py-1 px-1 overflow-y-auto overscroll-contain", style: { maxHeight: availH }, children: flyoutItems.map((item) => {
4020
- const hasChildren = visibleChildren2(item).length > 0;
4021
- const isChildHovered = hoveredChild === item.to;
4047
+ left: openPath[i].left,
4048
+ anchorY: openPath[i].y,
4049
+ estHeight: items.length * sizeConfig.itemH + 12,
4050
+ minTop,
4051
+ maxBottom,
4052
+ flipped: openPath[i].flipped,
4053
+ className: sizeConfig.fw,
4054
+ style: menuGlass,
4055
+ onMouseEnter: () => scheduleClose(i + 1),
4056
+ onMouseLeave: () => scheduleClose(i),
4057
+ children: /* @__PURE__ */ jsx("div", { className: "py-1 px-1 overflow-y-auto overscroll-contain", style: { maxHeight: availH }, children: items.map((item) => {
4058
+ const kids = submenuItems(item);
4059
+ const isOpen = openPath[i + 1]?.key === item.to;
4022
4060
  return /* @__PURE__ */ jsxs(
4023
4061
  "div",
4024
4062
  {
4025
- onMouseEnter: hasChildren ? (e) => {
4026
- clearTimeout(childHoverTimeout.current);
4027
- const rect = e.currentTarget.getBoundingClientRect();
4028
- setHoveredChildY(rect.top + rect.height / 2);
4029
- setHoveredChild(item.to);
4030
- } : () => {
4031
- childHoverTimeout.current = setTimeout(() => setHoveredChild(null), 200);
4032
- },
4063
+ onMouseEnter: kids.length > 0 ? (e) => openSubmenu(i + 1, item.to, e) : () => scheduleClose(i + 1),
4033
4064
  children: [
4034
4065
  /* @__PURE__ */ jsxs(
4035
4066
  "button",
4036
4067
  {
4037
- onClick: () => handleClick(item.to),
4038
- className: `${itemCls} transition-colors ${isChildHovered ? "bg-blue-50 text-blue-700" : "text-gray-700 hover:bg-blue-50 hover:text-blue-700"}`,
4068
+ onClick: kids.length > 0 ? (e) => openSubmenu(i + 1, item.to, e) : () => handleClick(item.to),
4069
+ className: `${itemCls} transition-colors ${isOpen ? "bg-blue-50 text-blue-700" : "text-gray-700 hover:bg-blue-50 hover:text-blue-700"}`,
4039
4070
  children: [
4040
4071
  iconEl(item.to),
4041
4072
  /* @__PURE__ */ jsx("span", { children: item.label }),
4042
- hasChildren && /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5 ml-auto text-gray-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8.25 4.5l7.5 7.5-7.5 7.5" }) })
4073
+ kids.length > 0 && chevron
4043
4074
  ]
4044
4075
  }
4045
4076
  ),
@@ -4049,55 +4080,14 @@ function StartMenu({
4049
4080
  item.to
4050
4081
  );
4051
4082
  }) })
4052
- }
4053
- ),
4054
- (() => {
4055
- if (search.length >= 2 || !hoveredChild) return null;
4056
- const parent = flyoutItems.find((it) => it.to === hoveredChild);
4057
- const kids = parent ? visibleChildren2(parent) : [];
4058
- if (!parent || kids.length === 0) return null;
4059
- const flyoutRect = flyoutRef.current?.getBoundingClientRect();
4060
- const subLeft = flyoutRect ? flyoutRect.right + 4 : 0;
4061
- const subEstH = kids.length * sizeConfig.itemH + 12;
4062
- const subH = measuredSub?.key === hoveredChild ? measuredSub.h : subEstH;
4063
- const subBoxH = Math.min(subH, availH);
4064
- let subTop = hoveredChildY - subBoxH / 2;
4065
- if (subTop < minTop) subTop = minTop;
4066
- if (subTop + subBoxH > maxBottom) subTop = Math.max(minTop, maxBottom - subBoxH);
4067
- return /* @__PURE__ */ jsx(
4068
- "div",
4069
- {
4070
- ref: subFlyoutRef,
4071
- className: `fixed ${sizeConfig.fw} rounded-2xl overflow-hidden`,
4072
- style: { left: subLeft, top: subTop, animation: "submenu-in 0.1s ease-out", ...menuGlass },
4073
- onMouseEnter: () => {
4074
- clearTimeout(hoverTimeout.current);
4075
- clearTimeout(childHoverTimeout.current);
4076
- },
4077
- onMouseLeave: () => {
4078
- childHoverTimeout.current = setTimeout(() => setHoveredChild(null), 200);
4079
- },
4080
- children: /* @__PURE__ */ jsx("div", { className: "py-1 px-1 overflow-y-auto overscroll-contain", style: { maxHeight: availH }, children: kids.map((child) => /* @__PURE__ */ jsxs("div", { children: [
4081
- /* @__PURE__ */ jsxs(
4082
- "button",
4083
- {
4084
- onClick: () => handleClick(child.to),
4085
- className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`,
4086
- children: [
4087
- iconEl(child.to),
4088
- /* @__PURE__ */ jsx("span", { children: child.label })
4089
- ]
4090
- }
4091
- ),
4092
- child.dividerAfter && /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" })
4093
- ] }, child.to)) })
4094
- }
4095
- );
4096
- })()
4083
+ },
4084
+ `${i}:${openPath[i].key}`
4085
+ ))
4097
4086
  ] }),
4098
4087
  /* @__PURE__ */ jsx("style", { children: `
4099
4088
  @keyframes menu-in { from { opacity: 0; transform: scale(0.95) translateY(8px); } to { opacity: 1; transform: scale(1) translateY(0); } }
4100
4089
  @keyframes submenu-in { from { opacity: 0; transform: translateX(-4px); } to { opacity: 1; transform: translateX(0); } }
4090
+ @keyframes submenu-in-left { from { opacity: 0; transform: translateX(4px); } to { opacity: 1; transform: translateX(0); } }
4101
4091
  ` })
4102
4092
  ] })
4103
4093
  );
@@ -4144,7 +4134,7 @@ function MobileAppLanding({
4144
4134
  }
4145
4135
  );
4146
4136
  }
4147
- var Sidebar = lazy(() => import('./Sidebar-ITLRERXE.js'));
4137
+ var Sidebar = lazy(() => import('./Sidebar-IMACKSHS.js'));
4148
4138
  function useFavorites(wallpapers) {
4149
4139
  const { prefs, save } = useShellPrefs();
4150
4140
  const favorites = prefs.favorite_pages || [];
@@ -6365,6 +6355,7 @@ function Kanban({
6365
6355
  isLoading = false,
6366
6356
  loadingText = "Loading\u2026",
6367
6357
  emptyState,
6358
+ showColumnsWhenEmpty = false,
6368
6359
  columnEmptyText = "Drop here",
6369
6360
  onAddItem,
6370
6361
  addItemText = "Add item"
@@ -6436,7 +6427,7 @@ function Kanban({
6436
6427
  reset();
6437
6428
  };
6438
6429
  if (isLoading) return /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-500 p-4", children: loadingText });
6439
- if (items.length === 0) {
6430
+ if (items.length === 0 && !showColumnsWhenEmpty) {
6440
6431
  return /* @__PURE__ */ jsx(Fragment, { children: emptyState ?? /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-500 p-4", children: "No items." }) });
6441
6432
  }
6442
6433
  return /* @__PURE__ */ jsx("div", { ref: boardRef, className: "flex-1 overflow-x-auto grid-scroll", children: /* @__PURE__ */ jsx("div", { className: "flex gap-3 h-full min-w-max pb-2", children: columns.map((col) => {